vueless 0.0.99 → 0.0.101

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 -960 960 960"><path d="M737.33-324.39q0 105.46-74.69 177.91-74.69 72.46-180.26 72.46-105.58 0-180.35-72.46-74.77-72.45-74.77-177.85v-383.82q0-74.63 53.41-126.35 53.42-51.72 127.75-51.72 74.34 0 127.69 51.72 53.35 51.72 53.35 126.35v363.82q0 43.66-31.56 74.62-31.55 30.97-75.81 30.97-44.26 0-75.61-30.64t-31.35-74.95v-370h66.46v370q0 16.05 11.97 27.59t29.2 11.54q17.24 0 28.74-11.5 11.5-11.51 11.5-27.63v-363.58q.24-47.35-33.38-79.6-33.62-32.25-81.35-32.25-47.74 0-81.14 32.19-33.41 32.19-33.41 79.42v383.82q.24 77.83 55.57 130.96 55.33 53.13 133.62 53.13 77.86 0 133.03-53.16 55.17-53.17 54.93-130.93v-396.93h66.46v396.87Z"/></svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "0.0.99",
3
+ "version": "0.0.101",
4
4
  "license": "MIT",
5
5
  "description": "Vue Styleless Component Framework.",
6
6
  "homepage": "https://vueless.com",
@@ -25,9 +25,6 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@tailwindcss/forms": "^0.5.7",
28
- "@uppy/core": "^3.10.1",
29
- "@uppy/drag-drop": "^3.1.0",
30
- "@uppy/vue": "^1.1.2",
31
28
  "cva": "^1.0.0-beta.1",
32
29
  "lodash-es": "^4.17.21",
33
30
  "tailwind-merge": "^2.3.0",
@@ -149,6 +149,7 @@ export default /*tw*/ {
149
149
  color: "brand",
150
150
  variant: "primary",
151
151
  size: "md",
152
+ tag: "button",
152
153
  pill: false,
153
154
  block: false,
154
155
  square: false,
@@ -1,5 +1,6 @@
1
1
  <template>
2
- <button
2
+ <component
3
+ :is="tag"
3
4
  :id="id"
4
5
  ref="buttonRef"
5
6
  :disabled="disabled"
@@ -19,7 +20,7 @@
19
20
 
20
21
  <!-- @slot Use it to add something after text. -->
21
22
  <slot v-if="!loading" name="right" />
22
- </button>
23
+ </component>
23
24
  </template>
24
25
 
25
26
  <script setup>
@@ -136,6 +137,11 @@ const props = defineProps({
136
137
  default: () => ({}),
137
138
  },
138
139
 
140
+ tag: {
141
+ type: String,
142
+ default: UIService.get(defaultConfig, UButton).default.tag,
143
+ },
144
+
139
145
  /**
140
146
  * Data-cy attribute for automated testing.
141
147
  */
@@ -2,7 +2,7 @@ export default /*tw*/ {
2
2
  htmlBody: "bg-gray-50 group/body-gray",
3
3
  wrapperMobile: "overflow-x-hidden mb-[theme('spacing.mobile-menu-height')]",
4
4
  wrapper: {
5
- base: "min-h-screen overflow-auto UNotifyPage",
5
+ base: "min-h-screen UNotifyPage",
6
6
  variants: {
7
7
  width: {
8
8
  xs: "md:w-[25rem] md:max-[500px]:!w-full",
@@ -360,7 +360,7 @@ function transformValue(value, exp) {
360
360
  }
361
361
 
362
362
  function setLabelPosition() {
363
- if (props.labelOutside || !hasSlotContent(slots["left"])) return;
363
+ if (props.labelAlign === "top" || !hasSlotContent(slots["left"])) return;
364
364
 
365
365
  const leftSlotWidth = leftSlotWrapper.value.getBoundingClientRect().width;
366
366
 
@@ -1,82 +1,67 @@
1
1
  import { computed } from "vue";
2
2
  import useUI from "../../composable.ui";
3
- import { cva, cx } from "../../service.ui";
3
+ import { cva } from "../../service.ui";
4
4
 
5
5
  import defaultConfig from "../configs/default.config";
6
6
 
7
- export function useAttrs(props, { errorMessage, dragOver }) {
8
- const { config, getAttrs, hasSlotContent } = useUI(defaultConfig, () => props.config);
9
- const { upload, block } = config.value;
7
+ export function useAttrs(props) {
8
+ const { config, getAttrs } = useUI(defaultConfig, () => props.config);
9
+ const { dropzoneWrapper, placeholder } = config.value;
10
10
 
11
- const cvaUpload = cva({
12
- base: upload.base,
13
- variants: upload.variants,
14
- compoundVariants: upload.compoundVariants,
11
+ const cvaDropzoneWrapper = cva({
12
+ base: dropzoneWrapper.base,
13
+ variants: dropzoneWrapper.variants,
14
+ compoundVariants: dropzoneWrapper.compoundVariants,
15
15
  });
16
16
 
17
- const cvaBlock = cva({
18
- base: block.base,
19
- variants: block.variants,
20
- compoundVariants: block.compoundVariants,
17
+ const cvaPlaceholder = cva({
18
+ base: placeholder.base,
19
+ variants: placeholder.variants,
20
+ compoundVariants: placeholder.compoundVariants,
21
21
  });
22
22
 
23
- const uploadClasses = computed(() =>
24
- cvaUpload({
25
- size: props.size,
23
+ const dropzoneWrapperClasses = computed(() =>
24
+ cvaDropzoneWrapper({
25
+ error: Boolean(props.error),
26
26
  label: Boolean(props.label),
27
- error: Boolean(errorMessage.value),
27
+ labelAlign: props.labelAlign,
28
+ size: props.size,
28
29
  }),
29
30
  );
30
31
 
31
- const blockClasses = computed(() =>
32
- cvaBlock({
33
- label: Boolean(props.label),
32
+ const placeholderClasses = computed(() =>
33
+ cvaPlaceholder({
34
+ size: props.size,
34
35
  }),
35
36
  );
36
37
 
37
- const uploadAttrs = getAttrs("upload", { classes: uploadClasses });
38
- const buttonAttrs = getAttrs("button", { isComponent: true });
39
- const iconUploadFileAttrs = getAttrs("iconUploadFile", { isComponent: true });
40
- const wrapperAttrs = getAttrs("wrapper");
41
38
  const labelAttrs = getAttrs("label", { isComponent: true });
42
- const blockAttrs = getAttrs("block", { classes: blockClasses });
43
- const descriptionAttrs = getAttrs("description");
44
- const listAttrs = getAttrs("list");
45
- const itemCloseAttrs = getAttrs("itemClose");
46
- const iconCloseRaw = getAttrs("iconClose", {
47
- classes: itemCloseAttrs.value.class,
39
+ const buttonAttrs = getAttrs("button", { isComponent: true });
40
+ const dropzoneWrapperAttrs = getAttrs("dropzoneWrapper", { classes: dropzoneWrapperClasses });
41
+ const descriptionAttrs = getAttrs("description", { isComponent: true });
42
+ const buttonWrapperAttrs = getAttrs("buttonWrapper");
43
+ const placeholderWrapperAttrs = getAttrs("placeholderWrapper");
44
+ const placeholderAttrs = getAttrs("placeholder", {
45
+ classes: placeholderClasses,
48
46
  isComponent: true,
49
47
  });
50
-
51
- const iconCloseAttrs = computed(() => ({
52
- ...iconCloseRaw.value,
53
- class: cx([iconCloseRaw.value.class, itemCloseAttrs.value.class]),
54
- }));
55
-
56
- const uppyUploadRaw = getAttrs("uppyUpload");
57
-
58
- const uppyUploadAttrs = computed(() => ({
59
- ...uppyUploadRaw.value,
60
- class: cx([uppyUploadRaw.value.class, dragOver.value && config.dragOver]),
61
- }));
62
-
63
- const uploadSlotAttrs = getAttrs("uploadSlot");
64
- const filesAttrs = getAttrs("files", { isComponent: true });
48
+ const iconPlaceholderAttrs = getAttrs("iconPlaceholder", { isComponent: true });
49
+ const iconCloseAttrs = getAttrs("iconClose", { isComponent: true });
50
+ const iconUploadFileAttrs = getAttrs("iconUploadFile", { isComponent: true });
51
+ const inputAttrs = getAttrs("input");
65
52
 
66
53
  return {
67
54
  config,
68
- uploadAttrs,
69
- buttonAttrs,
70
- iconUploadFileAttrs,
71
- iconCloseAttrs,
72
- wrapperAttrs,
55
+ inputAttrs,
73
56
  labelAttrs,
74
- blockAttrs,
57
+ buttonAttrs,
58
+ dropzoneWrapperAttrs,
75
59
  descriptionAttrs,
76
- listAttrs,
77
- uppyUploadAttrs,
78
- uploadSlotAttrs,
79
- filesAttrs,
80
- hasSlotContent,
60
+ buttonWrapperAttrs,
61
+ placeholderWrapperAttrs,
62
+ iconPlaceholderAttrs,
63
+ iconCloseAttrs,
64
+ iconUploadFileAttrs,
65
+ placeholderAttrs,
81
66
  };
82
67
  }
@@ -1,59 +1,63 @@
1
1
  export default /*tw*/ {
2
- wrapper: "w-full relative",
3
- label: "group/label mb-2 font-normal text-gray-500",
4
- block: {
5
- base: "mb-4 space-x-2 flex",
6
- variants: {
7
- label: {
8
- true: "mt-4",
9
- },
2
+ label: {
3
+ wrapper: "relative w-full",
4
+ label: {
5
+ compoundVariants: [
6
+ { align: "topInside", size: "sm", class: "left-5 top-4" },
7
+ { align: "topInside", size: "md", class: "left-5 top-5" },
8
+ { align: "topInside", size: "lg", class: "left-5 top-6" },
9
+ ],
10
10
  },
11
11
  },
12
- description: "group/description font-normal text-gray-700",
13
- list: "mb-4 space-y-4",
14
- button: "w-full",
15
- iconUploadFileName: "upload_file",
16
- iconDescriptionName: "description",
17
- iconCloseName: "close",
18
- iconClose: "ml-1.5",
19
- iconUploadFile: "",
20
- itemClose: "z-20",
21
- uploadSlot: "w-fit",
22
- files: "mt-4",
23
- fileUploadHover: "border-gray-400 bg-gray-50",
24
- dragOver: "h-full w-full",
25
- uppyUpload: "opacity-0 absolute left-0 top-0 h-0 w-0 [&_.uppy-Root]:h-full [&_.uppy-Root]:w-full",
26
- errorFileUpload: "hover:border-red-400 border-red-300",
27
- upload: {
12
+ dropzoneWrapper: {
28
13
  base: `
29
- relative rounded-lg border border-dashed border-gray-300 bg-white h-auto w-auto p-4
30
- hover:border-gray-400 hover:bg-gray-50
14
+ size-auto w-full rounded border border-dashed border-gray-300 bg-white
15
+ p-4 px-5 py-6 hover:border-gray-400
31
16
  `,
32
17
  variants: {
33
- size: {
34
- sm: "group/label:text-xs group/description:text-xs group/image:h-8 group/image:w-8 group/title:text-sm",
35
- md: "group/text-sm group/description:text-sm group/image:h-10 group/image:w-10 group/title:text-base",
36
- ld: "group/label:text-base group/description:text-base group/image:h-12 group/image:w-12 group/title:text-lg",
37
- },
38
18
  error: {
39
19
  true: "border-red-300 hover:border-red-400",
40
20
  },
41
21
  },
22
+ compoundVariants: [
23
+ { labelAlign: "topInside", label: true, size: "sm", class: "pt-8" },
24
+ { labelAlign: "topInside", label: true, size: "md", class: "py-10" },
25
+ { labelAlign: "topInside", label: true, size: "lg", class: "pt-12" },
26
+ ],
42
27
  },
28
+ description: "text-gray-700",
29
+ buttonWrapper: "relative mt-3 flex w-full gap-52 justify-between rounded bg-brand-50 p-4",
30
+ placeholderWrapper: "flex items-center gap-2",
31
+ placeholder: {
32
+ base: "pr-4 shrink-0 text-ellipsis overflow-hidden text-gray-700 text-nowrap",
33
+ variants: {
34
+ size: {
35
+ sm: "text-sm",
36
+ md: "text-base",
37
+ lg: "text-lg",
38
+ },
39
+ },
40
+ },
41
+ iconPlaceholderName: "attach_file",
42
+ iconPlaceholder: "-rotate-45",
43
+ button: "hover:cursor-pointer",
44
+ iconUploadFileName: "upload_file",
45
+ iconCloseName: "close",
46
+ iconClose: "",
47
+ iconUploadFile: "",
48
+ dropzoneWrapperHover: "border-gray-400 bg-gray-50",
49
+ dropzoneWrapperError: "hover:border-red-400 border-red-300",
50
+ input: "sr-only pointer-events-none size-0 opacity-0",
43
51
  i18n: {
44
- selectOrDragImage: "Select or drag file in this area.",
45
- canAttachFilesFormat: "You can attach files in the format:",
46
- cannotAttachFilesStart: "Attaching files in",
47
- cannotAttachFilesEnd: "format is not supported.",
48
- selectFile: "Select file",
52
+ sizeError: "File size is too big.",
53
+ formatError: "Format is not supported.",
54
+ noFile: "No file selected",
55
+ uploadFile: "Upload file",
49
56
  },
50
57
  defaultVariants: {
51
58
  size: "md",
52
59
  labelAlign: "topInside",
53
60
  allowedFileTypes: [".png", ".jpg", ".jpeg", ".pdf", ".txt", ".doc", ".docx", "xls", ".xlsx"],
54
- maxFileSize: 1,
55
- maxFiles: 3,
56
- local: false,
57
- multiple: false,
61
+ maxFileSize: 100,
58
62
  },
59
63
  };
@@ -1,11 +1,10 @@
1
1
  import { getArgTypes } from "../service.storybook";
2
2
 
3
3
  import UInputFile from "../ui.form-input-file";
4
- import UButton from "../ui.button";
5
4
  import URow from "../ui.container-row";
6
5
 
7
6
  export default {
8
- id: "3020",
7
+ id: "30232",
9
8
  title: "Form Inputs & Controls / Input File",
10
9
  component: UInputFile,
11
10
  argTypes: {
@@ -18,29 +17,23 @@ const DefaultTemplate = (args) => ({
18
17
  setup() {
19
18
  return { args };
20
19
  },
20
+ data() {
21
+ return {
22
+ files: [],
23
+ error: args.error || "",
24
+ };
25
+ },
21
26
  template: `
22
27
  <URow>
23
28
  <UInputFile
24
29
  v-bind="args"
30
+ v-model="files"
31
+ v-model:error="error"
25
32
  />
26
33
  </URow>
27
34
  `,
28
35
  });
29
36
 
30
- const SlotTemplate = (args) => ({
31
- components: { UInputFile, UButton },
32
- setup() {
33
- return { args };
34
- },
35
- template: `
36
- <UInputFile
37
- v-bind="args"
38
- >
39
- ${args.slotTemplate}
40
- </UInputFile>
41
- `,
42
- });
43
-
44
37
  const SizesTemplate = (args, { argTypes } = {}) => ({
45
38
  components: { UInputFile, URow },
46
39
  setup() {
@@ -64,23 +57,8 @@ const SizesTemplate = (args, { argTypes } = {}) => ({
64
57
  export const Default = DefaultTemplate.bind({});
65
58
  Default.args = {};
66
59
 
67
- export const allowedSomeFileType = DefaultTemplate.bind({});
68
- allowedSomeFileType.args = { allowedFileTypes: [".png", ".jpg", ".jpeg"] };
69
-
70
- export const sizes = SizesTemplate.bind({});
71
- sizes.args = {};
72
-
73
- export const multiple = DefaultTemplate.bind({});
74
- multiple.args = { multiple: true };
75
-
76
60
  export const error = DefaultTemplate.bind({});
77
61
  error.args = { error: "some error" };
78
62
 
79
- export const slotDefault = SlotTemplate.bind({});
80
- slotDefault.args = {
81
- slotTemplate: `
82
- <template #default>
83
- <UButton class="w-[40rem]" label="Select file" color="red" />
84
- </template>
85
- `,
86
- };
63
+ export const sizes = SizesTemplate.bind({});
64
+ sizes.args = {};
@@ -1,84 +1,83 @@
1
1
  <template>
2
- <div v-bind="wrapperAttrs">
3
- <div v-if="!hasSlotContent($slots['default'])">
4
- <ULabel
5
- :label="props.label"
6
- :description="props.description"
7
- :align="labelAlign"
8
- :error="errorMessage"
9
- v-bind="labelAttrs"
10
- >
11
- <div :id="fileId" :data-cy="dataCy" v-bind="uploadAttrs">
12
- <div v-if="!filesData.length" v-bind="blockAttrs">
13
- <UIcon
14
- internal
15
- color="gray"
16
- :size="componentSize"
17
- :name="config.iconUploadFileName"
18
- v-bind="iconUploadFileAttrs"
19
- />
20
-
21
- <div v-bind="descriptionAttrs" v-text="descriptionText" />
22
- </div>
23
-
24
- <div v-bind="listAttrs">
25
- <UFiles :options="filesList" v-bind="filesAttrs">
26
- <template #right="file">
27
- <UIcon
28
- internal
29
- interactive
30
- color="gray"
31
- :size="componentSize"
32
- :name="config.iconCloseName"
33
- v-bind="iconCloseAttrs"
34
- @click="onClickDeleteFile(file)"
35
- />
36
- </template>
37
- </UFiles>
38
- </div>
2
+ <ULabel :error="error" :align="labelAlign" :label="label" :size="size" v-bind="labelAttrs">
3
+ <div ref="dropZoneRef" :ondrop="onDrop" v-bind="dropzoneWrapperAttrs">
4
+ <UText :size="nestedComponentSize" v-bind="descriptionAttrs" :html="description" />
5
+
6
+ <div v-bind="buttonWrapperAttrs">
7
+ <div v-bind="placeholderWrapperAttrs">
8
+ <UIcon
9
+ pill
10
+ internal
11
+ :size="nestedComponentSize"
12
+ :name="config.iconPlaceholderName"
13
+ v-bind="iconPlaceholderAttrs"
14
+ />
15
+ <span v-bind="placeholderAttrs" v-text="placeholder" />
16
+ </div>
39
17
 
18
+ <template v-if="!currentFiles.length">
40
19
  <UButton
41
- :label="currentLocale.selectFile"
42
- :size="size"
20
+ class="hover:cursor-pointer"
21
+ :label="currentLocale.uploadFile"
43
22
  variant="thirdary"
44
23
  filled
45
- :data-cy="`${dataCy}-upload`"
24
+ :size="nestedComponentSize"
46
25
  v-bind="buttonAttrs"
47
- @click="onClickUploadFile"
26
+ tag="label"
27
+ :for="id"
28
+ >
29
+ <template #right>
30
+ <UIcon
31
+ internal
32
+ :size="nestedComponentSize"
33
+ :name="config.iconUploadFileName"
34
+ v-bind="iconUploadFileAttrs"
35
+ />
36
+ </template>
37
+ </UButton>
38
+
39
+ <input
40
+ :id="id"
41
+ ref="fileInputRef"
42
+ type="file"
43
+ :accept="accept"
44
+ v-bind="inputAttrs"
45
+ @change="onChangeFile"
48
46
  />
49
-
50
- <DragDrop :id="uppyId" :uppy="uppy" v-bind="uppyUploadAttrs" />
51
- </div>
52
- </ULabel>
47
+ </template>
48
+ <UIcon
49
+ v-else
50
+ interactive
51
+ internal
52
+ :size="nestedComponentSize"
53
+ :name="config.iconCloseName"
54
+ pill
55
+ v-bind="iconCloseAttrs"
56
+ @click="onClickResetFiles"
57
+ />
58
+ </div>
53
59
  </div>
54
-
55
- <div v-else :id="fileId" :data-cy="dataCy" v-bind="uploadSlotAttrs" @click="onClickUploadFile">
56
- <slot />
57
-
58
- <DragDrop :id="uppyId" :uppy="uppy" v-bind="uppyUploadAttrs" />
59
- </div>
60
- </div>
60
+ </ULabel>
61
61
  </template>
62
62
 
63
63
  <script setup>
64
- import Uppy from "@uppy/core";
65
- import DragDrop from "@uppy/vue/src/drag-drop";
64
+ import { computed, nextTick, onBeforeUnmount, onMounted, ref } from "vue";
66
65
  import { merge } from "lodash-es";
67
66
 
68
- import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
69
-
67
+ import UText from "../ui.text-block";
70
68
  import UIcon from "../ui.image-icon";
71
69
  import ULabel from "../ui.form-label";
72
70
  import UButton from "../ui.button";
73
- import UFiles from "../ui.text-files";
74
71
 
75
72
  import UIService, { getRandomId } from "../service.ui";
73
+ import { getFileMbSize } from "./services/fileForm.service";
76
74
 
77
- import { UInputFile } from "./constants";
78
- import defaultConfig from "./configs/default.config";
79
75
  import { useAttrs } from "./composables/attrs.composable";
80
76
  import { useLocale } from "../composable.locale";
81
77
 
78
+ import { UInputFile } from "./constants";
79
+ import defaultConfig from "./configs/default.config";
80
+
82
81
  /* Should be a string for correct web-types gen */
83
82
  defineOptions({ name: "UInputFile" });
84
83
 
@@ -88,7 +87,7 @@ const props = defineProps({
88
87
  */
89
88
  label: {
90
89
  type: String,
91
- default: "",
90
+ default: "Label",
92
91
  },
93
92
 
94
93
  /**
@@ -96,7 +95,7 @@ const props = defineProps({
96
95
  */
97
96
  description: {
98
97
  type: String,
99
- default: "",
98
+ default: "Some description here",
100
99
  },
101
100
 
102
101
  /**
@@ -108,22 +107,11 @@ const props = defineProps({
108
107
  default: UIService.get(defaultConfig, UInputFile).default.labelAlign,
109
108
  },
110
109
 
111
- /**
112
- * Set files data.
113
- */
114
- files: {
110
+ modelValue: {
115
111
  type: Array,
116
112
  default: () => [],
117
113
  },
118
114
 
119
- /**
120
- * Selected files will be imported when a form will be submitted
121
- */
122
- local: {
123
- type: Boolean,
124
- default: UIService.get(defaultConfig, UInputFile).default.local,
125
- },
126
-
127
115
  /**
128
116
  * Set max file size in megabytes.
129
117
  */
@@ -132,22 +120,6 @@ const props = defineProps({
132
120
  default: UIService.get(defaultConfig, UInputFile).default.maxFileSize,
133
121
  },
134
122
 
135
- /**
136
- * Allow to select multiple files.
137
- */
138
- multiple: {
139
- type: Boolean,
140
- default: UIService.get(defaultConfig, UInputFile).default.multiple,
141
- },
142
-
143
- /**
144
- * Set max number files for upload
145
- */
146
- maxFiles: {
147
- type: Number,
148
- default: UIService.get(defaultConfig, UInputFile).default.maxFiles,
149
- },
150
-
151
123
  /**
152
124
  * Set allowed file types.
153
125
  */
@@ -157,20 +129,20 @@ const props = defineProps({
157
129
  },
158
130
 
159
131
  /**
160
- * Set error text for component.
132
+ * Set size.
133
+ * @values sm, md, lg
161
134
  */
162
- error: {
135
+ size: {
163
136
  type: String,
164
- default: "",
137
+ default: UIService.get(defaultConfig, UInputFile).default.size,
165
138
  },
166
139
 
167
140
  /**
168
- * Set size.
169
- * @values sm, md, lg
141
+ * Set error text for component.
170
142
  */
171
- size: {
143
+ error: {
172
144
  type: String,
173
- default: UIService.get(defaultConfig, UInputFile).default.size,
145
+ default: "",
174
146
  },
175
147
 
176
148
  /**
@@ -181,211 +153,147 @@ const props = defineProps({
181
153
  type: String,
182
154
  default: () => getRandomId(),
183
155
  },
184
-
185
156
  /**
186
- * Sets data-cy attribute for automated testing.
157
+ * Sets component ui config object.
187
158
  */
188
- dataCy: {
189
- type: String,
190
- default: "",
159
+ config: {
160
+ type: Object,
161
+ default: () => ({}),
191
162
  },
192
163
  });
193
164
 
194
- const emit = defineEmits(["changeFiles", "deleteFile"]);
165
+ const emit = defineEmits(["update:modelValue", "update:error"]);
195
166
 
196
167
  const { tm } = useLocale();
197
168
 
198
- const filesData = ref([]);
199
- const selectedFiles = ref([]);
200
- const errorMessage = ref("");
201
- const dragOver = ref(false);
202
- const errorFilesTypes = ref([]);
169
+ const dropZoneRef = ref(null);
170
+ const fileInputRef = ref(null);
203
171
 
204
172
  const {
205
173
  config,
206
- uploadAttrs,
207
- buttonAttrs,
208
- iconUploadFileAttrs,
209
- iconCloseAttrs,
210
- wrapperAttrs,
211
174
  labelAttrs,
212
- blockAttrs,
175
+ buttonAttrs,
176
+ dropzoneWrapperAttrs,
213
177
  descriptionAttrs,
214
- listAttrs,
215
- uppyUploadAttrs,
216
- uploadSlotAttrs,
217
- filesAttrs,
218
- hasSlotContent,
219
- } = useAttrs(props, { errorMessage, dragOver });
178
+ buttonWrapperAttrs,
179
+ placeholderWrapperAttrs,
180
+ iconPlaceholderAttrs,
181
+ iconCloseAttrs,
182
+ iconUploadFileAttrs,
183
+ placeholderAttrs,
184
+ inputAttrs,
185
+ } = useAttrs(props);
220
186
 
221
187
  const currentLocale = computed(() => merge(tm("UInputFile"), props.config.i18n));
222
188
 
223
- const filesList = computed(() => {
224
- return filesData.value.map((file) => {
225
- return {
226
- text: file.name,
227
- id: file.id,
228
- };
229
- });
189
+ const currentFiles = computed({
190
+ get: () => props.modelValue,
191
+ set: (newValue) => emit("update:modelValue", newValue),
230
192
  });
231
193
 
232
- const fileId = computed(() => {
233
- return `file-${props.id}`;
194
+ const currentError = computed({
195
+ get: () => props.error,
196
+ set: (newValue) => emit("update:error", newValue),
234
197
  });
235
198
 
236
- const uppyId = computed(() => {
237
- return `uppy-${props.id}`;
199
+ const accept = computed(() => {
200
+ return props.allowedFileTypes.join(",");
238
201
  });
239
202
 
240
- const fileUpload = computed(() => {
241
- return document.getElementById(fileId.value);
203
+ const extensionNames = computed(() => {
204
+ return props.allowedFileTypes.map((type) => type.replace(".", ""));
242
205
  });
243
206
 
244
- const uppyUpload = computed(() => {
245
- return document.getElementById(uppyId.value);
207
+ const placeholder = computed(() => {
208
+ return currentFiles.value.length ? currentFiles.value[0].name : currentLocale.value.noFile;
246
209
  });
247
210
 
248
- const allowedFilesForUpload = computed(() => {
249
- const allowedFormat = props.allowedFileTypes.join(", ");
250
-
251
- return `${currentLocale.value.canAttachFilesFormat} ${allowedFormat}`;
252
- });
253
-
254
- const descriptionText = computed(() => {
255
- return `${currentLocale.value.selectOrDragImage} ${allowedFilesForUpload.value}`;
256
- });
211
+ const nestedComponentSize = computed(() => {
212
+ let size = "sm";
257
213
 
258
- const componentSize = computed(() => {
259
- const size = {
260
- sm: "xs",
261
- md: "sm",
262
- lg: "md",
263
- };
214
+ if (props.size === "sm") size = "xs";
215
+ if (props.size === "md") size = "sm";
216
+ if (props.size === "lg") size = "md";
264
217
 
265
- return size[props.size];
218
+ return size;
266
219
  });
267
220
 
268
- const maxFileSizeInBytes = computed(() => {
269
- return props.maxFileSize * Math.pow(2, 20);
221
+ onMounted(() => {
222
+ dropZoneRef.value.addEventListener("dragover", onDragOver);
223
+ dropZoneRef.value.addEventListener("dragleave", onDragLeave);
270
224
  });
271
225
 
272
- const maxFilesNumber = computed(() => {
273
- return props.multiple ? props.maxFiles : 1;
226
+ onBeforeUnmount(() => {
227
+ dropZoneRef.value.removeEventListener("dragover", onDragOver);
228
+ dropZoneRef.value.removeEventListener("dragleave", onDragLeave);
274
229
  });
275
230
 
276
- const uppy = computed(() =>
277
- new Uppy({
278
- id: uppyId.value,
279
- restrictions: {
280
- maxFileSize: maxFileSizeInBytes,
281
- maxNumberOfFiles: maxFilesNumber,
282
- allowedFileTypes: props.allowedFileTypes,
283
- },
284
- }).on("restriction-failed", (file, error) => {
285
- const isFile = errorFilesTypes.value.find((item) => item === file.extension);
286
-
287
- if (error && !isFile) errorFilesTypes.value.push(`.${file.extension}`);
288
- }),
289
- );
290
-
291
- watch(selectedFiles, onChangeSelectedFiles, { deep: true });
292
- watch(() => props.files, onChangeFiles, { deep: true });
293
- watch(errorFilesTypes, onChangeErrorFilesTypes, { deep: true });
294
- watch(() => props.error, onChangeError, { deep: true, immediate: true });
295
-
296
- onMounted(() => {
297
- uppy.value.on("file-added", () => {
298
- uploadFiles();
299
- });
231
+ function validate(file) {
232
+ const targetFileSize = getFileMbSize(file);
300
233
 
301
- addEventListener("dragover", () => (dragOver.value = true));
234
+ const isValidType = extensionNames.value.some((item) => file.type.includes(item));
235
+ const isValidSize = targetFileSize <= props.maxFileSize;
302
236
 
303
- if (uppyUpload.value) {
304
- uppyUpload.value.addEventListener("dragover", onDragOver, true);
305
- uppyUpload.value.addEventListener("dragleave", onDragLeave, true);
237
+ if (!isValidSize) {
238
+ currentError.value = currentLocale.value.sizeError;
306
239
  }
307
- });
308
240
 
309
- onBeforeUnmount(() => {
310
- removeEventListener("dragover", () => (dragOver.value = true));
311
- uppyUpload.value.removeEventListener("dragover", onDragOver, true);
312
- uppyUpload.value.removeEventListener("dragleave", onDragLeave, true);
313
- });
314
-
315
- function onClickUploadFile() {
316
- document.querySelector(`#${fileId.value} .uppy-Root button`).click();
241
+ if (!isValidType) {
242
+ currentError.value = currentLocale.value.formatError;
243
+ }
317
244
  }
318
245
 
319
- function uploadFiles() {
320
- const { files } = uppy.value.getState();
246
+ function onChangeFile(event) {
247
+ validate(event.target.files[0]);
321
248
 
322
- selectedFiles.value = Object.values(files);
323
- }
249
+ if (currentError.value) {
250
+ onClickResetFiles();
324
251
 
325
- function onDragOver() {
326
- fileUpload.value.classList.add(config.fileUploadHover);
327
- }
252
+ return;
253
+ }
328
254
 
329
- function onDragLeave() {
330
- fileUpload.value.classList.remove(config.fileUploadHover);
331
- dragOver.value = false;
255
+ currentFiles.value = Array.from(event.target.files);
332
256
  }
333
257
 
334
- function onChangeSelectedFiles() {
335
- uppy.value.cancelAll();
336
-
337
- const selectedFilesData = selectedFiles.value.map((file) => {
338
- file.data.id = getRandomId();
339
-
340
- return file.data;
341
- });
258
+ function onClickResetFiles() {
259
+ currentFiles.value = [];
342
260
 
343
- if (!props.error && errorMessage.value) {
344
- errorMessage.value = "";
345
- errorFilesTypes.value = [];
346
- }
261
+ if (fileInputRef.value) fileInputRef.value.value = "";
262
+ }
347
263
 
348
- if (props.local) filesData.value = selectedFilesData;
264
+ function onDragOver(event) {
265
+ event.preventDefault();
349
266
 
350
- emit("changeFiles", selectedFilesData);
267
+ dropZoneRef.value.classList.add(config.value.dropzoneWrapperHover.split(" "));
351
268
  }
352
269
 
353
- function onChangeFiles() {
354
- filesData.value = props.files;
355
-
356
- if (!props.error && errorMessage.value) {
357
- errorMessage.value = "";
358
- errorFilesTypes.value = [];
359
- }
270
+ function onDragLeave(event) {
271
+ event.preventDefault();
360
272
 
361
- onDragLeave();
273
+ dropZoneRef.value.classList.remove(config.value.dropzoneWrapperHover.split(" "));
362
274
  }
363
275
 
364
- function onChangeError() {
365
- errorMessage.value = props.error;
366
- }
276
+ function onDrop(event) {
277
+ event.preventDefault();
367
278
 
368
- function onChangeErrorFilesTypes() {
369
- if (errorFilesTypes.value.length) {
370
- const error = errorFilesTypes.value.join(", ");
371
- const cannotAttachFilesStart = currentLocale.value.cannotAttachFilesStart;
372
- const cannotAttachFilesEnd = currentLocale.value.cannotAttachFilesEnd;
279
+ let targetFile = null;
373
280
 
374
- errorMessage.value = `${cannotAttachFilesStart} ${error} ${cannotAttachFilesEnd}`;
281
+ if (event.dataTransfer.items) {
282
+ targetFile = [...event.dataTransfer.items].find((item) => item.kind === "file")?.getAsFile();
283
+ } else {
284
+ targetFile = [...event.dataTransfer.files].at(0);
375
285
  }
376
- }
377
-
378
- function onClickDeleteFile(data) {
379
- const index = filesData.value.findIndex((file) => file.id === data.file.id);
380
286
 
381
- if (~index) {
382
- filesData.value.splice(index, 1);
287
+ if (targetFile) validate(targetFile);
383
288
 
384
- uppy.value.removeFile(data.file.id);
289
+ nextTick(() => {
290
+ if (currentError.value || !targetFile) {
291
+ onClickResetFiles();
385
292
 
386
- emit("deleteFile", data.file.id);
293
+ return;
294
+ }
387
295
 
388
- if (props.local) emit("changeFiles", filesData.value);
389
- }
296
+ currentFiles.value = [targetFile];
297
+ });
390
298
  }
391
299
  </script>
@@ -0,0 +1,5 @@
1
+ const bytesInMegabyte = 1024 * 1024;
2
+
3
+ export function getFileMbSize(file) {
4
+ return (file.size / bytesInMegabyte).toFixed(2);
5
+ }
@@ -6,7 +6,7 @@ export default /*tw*/ {
6
6
  decimalScale: 2,
7
7
  decimalSeparator: ",",
8
8
  thousandsSeparator: " ",
9
- labelOutside: false,
9
+ labelAlign: "topInside",
10
10
  positiveOnly: false,
11
11
  rawValuePrefix: false,
12
12
  minus: false,
@@ -2,7 +2,7 @@ import { getArgTypes, getSlotNames } from "../service.storybook";
2
2
 
3
3
  import UInputMoney from "../ui.form-input-money";
4
4
  import URow from "../ui.container-row";
5
- import UGroup from "../ui.container-group";
5
+ // import UGroup from "../ui.container-group";
6
6
 
7
7
  export default {
8
8
  id: "3030",
@@ -56,29 +56,30 @@ const SizesTemplate = (args, { argTypes } = {}) => ({
56
56
  `,
57
57
  });
58
58
 
59
- const LabelPlacementTemplate = (args) => ({
60
- components: { UInputMoney, UGroup },
61
- setup() {
62
- return {
63
- args,
64
- };
65
- },
66
- template: `
67
- <UGroup size="lg">
68
- <UInputMoney
69
- v-bind="args"
70
- :label-outside="true"
71
- label="top"
72
- />
73
-
74
- <UInputMoney
75
- v-bind="args"
76
- :label-outside="false"
77
- label="topInside"
78
- />
79
- </UGroup>
80
- `,
81
- });
59
+ // TODO: Rewrite to labelAlign
60
+ // const LabelPlacementTemplate = (args) => ({
61
+ // components: { UInputMoney, UGroup },
62
+ // setup() {
63
+ // return {
64
+ // args,
65
+ // };
66
+ // },
67
+ // template: `
68
+ // <UGroup size="lg">
69
+ // <UInputMoney
70
+ // v-bind="args"
71
+ // :label-outside="true"
72
+ // label="top"
73
+ // />
74
+ //
75
+ // <UInputMoney
76
+ // v-bind="args"
77
+ // :label-outside="false"
78
+ // label="topInside"
79
+ // />
80
+ // </UGroup>
81
+ // `,
82
+ // });
82
83
 
83
84
  export const Default = DefaultTemplate.bind({});
84
85
  Default.args = {};
@@ -89,8 +90,8 @@ sizes.args = {};
89
90
  export const symbol = DefaultTemplate.bind({});
90
91
  symbol.args = { symbol: "€" };
91
92
 
92
- export const labelOutside = LabelPlacementTemplate.bind({});
93
- labelOutside.args = {};
93
+ // export const labelOutside = LabelPlacementTemplate.bind({});
94
+ // labelOutside.args = {};
94
95
 
95
96
  export const placeholder = DefaultTemplate.bind({});
96
97
  placeholder.args = { placeholder: "Placeholder" };
@@ -5,7 +5,7 @@
5
5
  v-model="formattedValue"
6
6
  :size="size"
7
7
  :label="localLabel"
8
- :label-outside="labelOutside"
8
+ :label-align="labelAlign"
9
9
  :placeholder="placeholder"
10
10
  :description="description"
11
11
  :error="error"
@@ -67,11 +67,12 @@ const props = defineProps({
67
67
  },
68
68
 
69
69
  /**
70
- * Show label outside the input block.
70
+ * Set label placement related from the default slot.
71
+ * @values top, topInside, topWithDesc, bottom, left, right
71
72
  */
72
- labelOutside: {
73
- type: Boolean,
74
- default: UIService.get(defaultConfig, UInputMoney).default.labelOutside,
73
+ labelAlign: {
74
+ type: String,
75
+ default: UIService.get(defaultConfig, UInputMoney).default.labelAlign,
75
76
  },
76
77
 
77
78
  /**
@@ -13,8 +13,8 @@ export default /*tw*/ {
13
13
  button: "",
14
14
  defaultVariants: {
15
15
  size: "md",
16
+ labelAlign: "topInside",
16
17
  disabled: false,
17
18
  searchButton: false,
18
- labelOutside: false,
19
19
  },
20
20
  };
@@ -12,7 +12,7 @@
12
12
  :type="search"
13
13
  :input-mode="search"
14
14
  :data-cy="dataCy"
15
- :label-outside="labelOutside"
15
+ :label-align="labelAlign"
16
16
  v-bind="inputAttrs"
17
17
  @keyup.enter="onClickSearch"
18
18
  >
@@ -136,11 +136,12 @@ const props = defineProps({
136
136
  },
137
137
 
138
138
  /**
139
- * Show label outside the input block.
139
+ * Set label placement related from the default slot.
140
+ * @values top, topInside, topWithDesc, bottom, left, right
140
141
  */
141
- labelOutside: {
142
- type: Boolean,
143
- default: UIService.get(defaultConfig, UInputSearch).default.labelOutside,
142
+ labelAlign: {
143
+ type: String,
144
+ default: UIService.get(defaultConfig, UInputSearch).default.labelAlign,
144
145
  },
145
146
 
146
147
  /**
@@ -713,7 +713,7 @@ function removeElement(option, shouldClose = true) {
713
713
  }
714
714
 
715
715
  function setLabelPosition() {
716
- if (props.labelOutside || !hasSlotContent(slots["left"])) return;
716
+ if (props.labelAlign === "top" || !hasSlotContent(slots["left"])) return;
717
717
 
718
718
  const leftSlotWidth = leftSlotWrapperRef.value.getBoundingClientRect().width;
719
719
  const innerWrapperPaddingLeft = parseInt(
package/web-types.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "framework": "vue",
3
3
  "name": "vueless",
4
- "version": "0.0.99",
4
+ "version": "0.0.101",
5
5
  "contributions": {
6
6
  "html": {
7
7
  "description-markup": "markdown",
@@ -635,6 +635,14 @@
635
635
  },
636
636
  "default": "{}"
637
637
  },
638
+ {
639
+ "name": "tag",
640
+ "value": {
641
+ "kind": "expression",
642
+ "type": "string"
643
+ },
644
+ "default": "button"
645
+ },
638
646
  {
639
647
  "name": "dataCy",
640
648
  "description": "Data-cy attribute for automated testing.",
@@ -3440,7 +3448,7 @@
3440
3448
  "kind": "expression",
3441
3449
  "type": "string"
3442
3450
  },
3443
- "default": "\"\""
3451
+ "default": "\"Label\""
3444
3452
  },
3445
3453
  {
3446
3454
  "name": "description",
@@ -3449,7 +3457,7 @@
3449
3457
  "kind": "expression",
3450
3458
  "type": "string"
3451
3459
  },
3452
- "default": "\"\""
3460
+ "default": "\"Some description here\""
3453
3461
  },
3454
3462
  {
3455
3463
  "name": "labelAlign",
@@ -3461,23 +3469,13 @@
3461
3469
  "default": "topInside"
3462
3470
  },
3463
3471
  {
3464
- "name": "files",
3465
- "description": "Set files data.",
3472
+ "name": "modelValue",
3466
3473
  "value": {
3467
3474
  "kind": "expression",
3468
3475
  "type": "array"
3469
3476
  },
3470
3477
  "default": "[]"
3471
3478
  },
3472
- {
3473
- "name": "local",
3474
- "description": "Selected files will be imported when a form will be submitted",
3475
- "value": {
3476
- "kind": "expression",
3477
- "type": "boolean"
3478
- },
3479
- "default": "false"
3480
- },
3481
3479
  {
3482
3480
  "name": "maxFileSize",
3483
3481
  "description": "Set max file size in megabytes.",
@@ -3485,25 +3483,7 @@
3485
3483
  "kind": "expression",
3486
3484
  "type": "number"
3487
3485
  },
3488
- "default": "1"
3489
- },
3490
- {
3491
- "name": "multiple",
3492
- "description": "Allow to select multiple files.",
3493
- "value": {
3494
- "kind": "expression",
3495
- "type": "boolean"
3496
- },
3497
- "default": "false"
3498
- },
3499
- {
3500
- "name": "maxFiles",
3501
- "description": "Set max number files for upload",
3502
- "value": {
3503
- "kind": "expression",
3504
- "type": "number"
3505
- },
3506
- "default": "3"
3486
+ "default": "100"
3507
3487
  },
3508
3488
  {
3509
3489
  "name": "allowedFileTypes",
@@ -3515,22 +3495,22 @@
3515
3495
  "default": ".png,.jpg,.jpeg,.pdf,.txt,.doc,.docx,xls,.xlsx"
3516
3496
  },
3517
3497
  {
3518
- "name": "error",
3519
- "description": "Set error text for component.",
3498
+ "name": "size",
3499
+ "description": "Set size.",
3520
3500
  "value": {
3521
3501
  "kind": "expression",
3522
- "type": "string"
3502
+ "type": "'sm' | 'md' | 'lg'"
3523
3503
  },
3524
- "default": "\"\""
3504
+ "default": "md"
3525
3505
  },
3526
3506
  {
3527
- "name": "size",
3528
- "description": "Set size.",
3507
+ "name": "error",
3508
+ "description": "Set error text for component.",
3529
3509
  "value": {
3530
3510
  "kind": "expression",
3531
- "type": "'sm' | 'md' | 'lg'"
3511
+ "type": "string"
3532
3512
  },
3533
- "default": "md"
3513
+ "default": "\"\""
3534
3514
  },
3535
3515
  {
3536
3516
  "name": "id",
@@ -3542,26 +3522,21 @@
3542
3522
  "default": "() => getRandomId()"
3543
3523
  },
3544
3524
  {
3545
- "name": "dataCy",
3546
- "description": "Sets data-cy attribute for automated testing.",
3525
+ "name": "config",
3526
+ "description": "Sets component ui config object.",
3547
3527
  "value": {
3548
3528
  "kind": "expression",
3549
- "type": "string"
3529
+ "type": "object"
3550
3530
  },
3551
- "default": "\"\""
3531
+ "default": "{}"
3552
3532
  }
3553
3533
  ],
3554
3534
  "events": [
3555
3535
  {
3556
- "name": "changeFiles"
3536
+ "name": "update:modelValue"
3557
3537
  },
3558
3538
  {
3559
- "name": "deleteFile"
3560
- }
3561
- ],
3562
- "slots": [
3563
- {
3564
- "name": "default"
3539
+ "name": "update:error"
3565
3540
  }
3566
3541
  ],
3567
3542
  "source": {
@@ -3592,13 +3567,13 @@
3592
3567
  "default": "\"\""
3593
3568
  },
3594
3569
  {
3595
- "name": "labelOutside",
3596
- "description": "Show label outside the input block.",
3570
+ "name": "labelAlign",
3571
+ "description": "Set label placement related from the default slot.",
3597
3572
  "value": {
3598
3573
  "kind": "expression",
3599
- "type": "boolean"
3574
+ "type": "'top' | 'topInside' | 'topWithDesc' | 'bottom' | 'left' | 'right'"
3600
3575
  },
3601
- "default": "false"
3576
+ "default": "topInside"
3602
3577
  },
3603
3578
  {
3604
3579
  "name": "symbol",
@@ -4057,13 +4032,13 @@
4057
4032
  "default": "\"\""
4058
4033
  },
4059
4034
  {
4060
- "name": "labelOutside",
4061
- "description": "Show label outside the input block.",
4035
+ "name": "labelAlign",
4036
+ "description": "Set label placement related from the default slot.",
4062
4037
  "value": {
4063
4038
  "kind": "expression",
4064
- "type": "boolean"
4039
+ "type": "'top' | 'topInside' | 'topWithDesc' | 'bottom' | 'left' | 'right'"
4065
4040
  },
4066
- "default": "false"
4041
+ "default": "topInside"
4067
4042
  },
4068
4043
  {
4069
4044
  "name": "placeholder",