vueless 0.0.99 → 0.0.100

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.100",
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,78 @@
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
-
70
- import UIcon from "../ui.image-icon";
71
- import ULabel from "../ui.form-label";
72
- import UButton from "../ui.button";
73
- import UFiles from "../ui.text-files";
74
-
75
67
  import UIService, { getRandomId } from "../service.ui";
68
+ import { getFileMbSize } from "./services/fileForm.service";
76
69
 
77
- import { UInputFile } from "./constants";
78
- import defaultConfig from "./configs/default.config";
79
70
  import { useAttrs } from "./composables/attrs.composable";
80
71
  import { useLocale } from "../composable.locale";
81
72
 
73
+ import { UInputFile } from "./constants";
74
+ import defaultConfig from "./configs/default.config";
75
+
82
76
  /* Should be a string for correct web-types gen */
83
77
  defineOptions({ name: "UInputFile" });
84
78
 
@@ -88,7 +82,7 @@ const props = defineProps({
88
82
  */
89
83
  label: {
90
84
  type: String,
91
- default: "",
85
+ default: "Label",
92
86
  },
93
87
 
94
88
  /**
@@ -96,7 +90,7 @@ const props = defineProps({
96
90
  */
97
91
  description: {
98
92
  type: String,
99
- default: "",
93
+ default: "Some description here",
100
94
  },
101
95
 
102
96
  /**
@@ -108,22 +102,11 @@ const props = defineProps({
108
102
  default: UIService.get(defaultConfig, UInputFile).default.labelAlign,
109
103
  },
110
104
 
111
- /**
112
- * Set files data.
113
- */
114
- files: {
105
+ modelValue: {
115
106
  type: Array,
116
107
  default: () => [],
117
108
  },
118
109
 
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
110
  /**
128
111
  * Set max file size in megabytes.
129
112
  */
@@ -132,22 +115,6 @@ const props = defineProps({
132
115
  default: UIService.get(defaultConfig, UInputFile).default.maxFileSize,
133
116
  },
134
117
 
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
118
  /**
152
119
  * Set allowed file types.
153
120
  */
@@ -157,20 +124,20 @@ const props = defineProps({
157
124
  },
158
125
 
159
126
  /**
160
- * Set error text for component.
127
+ * Set size.
128
+ * @values sm, md, lg
161
129
  */
162
- error: {
130
+ size: {
163
131
  type: String,
164
- default: "",
132
+ default: UIService.get(defaultConfig, UInputFile).default.size,
165
133
  },
166
134
 
167
135
  /**
168
- * Set size.
169
- * @values sm, md, lg
136
+ * Set error text for component.
170
137
  */
171
- size: {
138
+ error: {
172
139
  type: String,
173
- default: UIService.get(defaultConfig, UInputFile).default.size,
140
+ default: "",
174
141
  },
175
142
 
176
143
  /**
@@ -181,211 +148,147 @@ const props = defineProps({
181
148
  type: String,
182
149
  default: () => getRandomId(),
183
150
  },
184
-
185
151
  /**
186
- * Sets data-cy attribute for automated testing.
152
+ * Sets component ui config object.
187
153
  */
188
- dataCy: {
189
- type: String,
190
- default: "",
154
+ config: {
155
+ type: Object,
156
+ default: () => ({}),
191
157
  },
192
158
  });
193
159
 
194
- const emit = defineEmits(["changeFiles", "deleteFile"]);
160
+ const emit = defineEmits(["update:modelValue", "update:error"]);
195
161
 
196
162
  const { tm } = useLocale();
197
163
 
198
- const filesData = ref([]);
199
- const selectedFiles = ref([]);
200
- const errorMessage = ref("");
201
- const dragOver = ref(false);
202
- const errorFilesTypes = ref([]);
164
+ const dropZoneRef = ref(null);
165
+ const fileInputRef = ref(null);
203
166
 
204
167
  const {
205
168
  config,
206
- uploadAttrs,
207
- buttonAttrs,
208
- iconUploadFileAttrs,
209
- iconCloseAttrs,
210
- wrapperAttrs,
211
169
  labelAttrs,
212
- blockAttrs,
170
+ buttonAttrs,
171
+ dropzoneWrapperAttrs,
213
172
  descriptionAttrs,
214
- listAttrs,
215
- uppyUploadAttrs,
216
- uploadSlotAttrs,
217
- filesAttrs,
218
- hasSlotContent,
219
- } = useAttrs(props, { errorMessage, dragOver });
173
+ buttonWrapperAttrs,
174
+ placeholderWrapperAttrs,
175
+ iconPlaceholderAttrs,
176
+ iconCloseAttrs,
177
+ iconUploadFileAttrs,
178
+ placeholderAttrs,
179
+ inputAttrs,
180
+ } = useAttrs(props);
220
181
 
221
182
  const currentLocale = computed(() => merge(tm("UInputFile"), props.config.i18n));
222
183
 
223
- const filesList = computed(() => {
224
- return filesData.value.map((file) => {
225
- return {
226
- text: file.name,
227
- id: file.id,
228
- };
229
- });
184
+ const currentFiles = computed({
185
+ get: () => props.modelValue,
186
+ set: (newValue) => emit("update:modelValue", newValue),
230
187
  });
231
188
 
232
- const fileId = computed(() => {
233
- return `file-${props.id}`;
189
+ const currentError = computed({
190
+ get: () => props.error,
191
+ set: (newValue) => emit("update:error", newValue),
234
192
  });
235
193
 
236
- const uppyId = computed(() => {
237
- return `uppy-${props.id}`;
194
+ const accept = computed(() => {
195
+ return props.allowedFileTypes.join(",");
238
196
  });
239
197
 
240
- const fileUpload = computed(() => {
241
- return document.getElementById(fileId.value);
198
+ const extensionNames = computed(() => {
199
+ return props.allowedFileTypes.map((type) => type.replace(".", ""));
242
200
  });
243
201
 
244
- const uppyUpload = computed(() => {
245
- return document.getElementById(uppyId.value);
246
- });
247
-
248
- const allowedFilesForUpload = computed(() => {
249
- const allowedFormat = props.allowedFileTypes.join(", ");
250
-
251
- return `${currentLocale.value.canAttachFilesFormat} ${allowedFormat}`;
202
+ const placeholder = computed(() => {
203
+ return currentFiles.value.length ? currentFiles.value[0].name : currentLocale.value.noFile;
252
204
  });
253
205
 
254
- const descriptionText = computed(() => {
255
- return `${currentLocale.value.selectOrDragImage} ${allowedFilesForUpload.value}`;
256
- });
206
+ const nestedComponentSize = computed(() => {
207
+ let size = "sm";
257
208
 
258
- const componentSize = computed(() => {
259
- const size = {
260
- sm: "xs",
261
- md: "sm",
262
- lg: "md",
263
- };
209
+ if (props.size === "sm") size = "xs";
210
+ if (props.size === "md") size = "sm";
211
+ if (props.size === "lg") size = "md";
264
212
 
265
- return size[props.size];
213
+ return size;
266
214
  });
267
215
 
268
- const maxFileSizeInBytes = computed(() => {
269
- return props.maxFileSize * Math.pow(2, 20);
216
+ onMounted(() => {
217
+ dropZoneRef.value.addEventListener("dragover", onDragOver);
218
+ dropZoneRef.value.addEventListener("dragleave", onDragLeave);
270
219
  });
271
220
 
272
- const maxFilesNumber = computed(() => {
273
- return props.multiple ? props.maxFiles : 1;
221
+ onBeforeUnmount(() => {
222
+ dropZoneRef.value.removeEventListener("dragover", onDragOver);
223
+ dropZoneRef.value.removeEventListener("dragleave", onDragLeave);
274
224
  });
275
225
 
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
- });
226
+ function validate(file) {
227
+ const targetFileSize = getFileMbSize(file);
300
228
 
301
- addEventListener("dragover", () => (dragOver.value = true));
229
+ const isValidType = extensionNames.value.some((item) => file.type.includes(item));
230
+ const isValidSize = targetFileSize <= props.maxFileSize;
302
231
 
303
- if (uppyUpload.value) {
304
- uppyUpload.value.addEventListener("dragover", onDragOver, true);
305
- uppyUpload.value.addEventListener("dragleave", onDragLeave, true);
232
+ if (!isValidSize) {
233
+ currentError.value = currentLocale.value.sizeError;
306
234
  }
307
- });
308
-
309
- onBeforeUnmount(() => {
310
- removeEventListener("dragover", () => (dragOver.value = true));
311
- uppyUpload.value.removeEventListener("dragover", onDragOver, true);
312
- uppyUpload.value.removeEventListener("dragleave", onDragLeave, true);
313
- });
314
235
 
315
- function onClickUploadFile() {
316
- document.querySelector(`#${fileId.value} .uppy-Root button`).click();
236
+ if (!isValidType) {
237
+ currentError.value = currentLocale.value.formatError;
238
+ }
317
239
  }
318
240
 
319
- function uploadFiles() {
320
- const { files } = uppy.value.getState();
241
+ function onChangeFile(event) {
242
+ validate(event.target.files[0]);
321
243
 
322
- selectedFiles.value = Object.values(files);
323
- }
244
+ if (currentError.value) {
245
+ onClickResetFiles();
324
246
 
325
- function onDragOver() {
326
- fileUpload.value.classList.add(config.fileUploadHover);
327
- }
247
+ return;
248
+ }
328
249
 
329
- function onDragLeave() {
330
- fileUpload.value.classList.remove(config.fileUploadHover);
331
- dragOver.value = false;
250
+ currentFiles.value = Array.from(event.target.files);
332
251
  }
333
252
 
334
- function onChangeSelectedFiles() {
335
- uppy.value.cancelAll();
253
+ function onClickResetFiles() {
254
+ currentFiles.value = [];
336
255
 
337
- const selectedFilesData = selectedFiles.value.map((file) => {
338
- file.data.id = getRandomId();
339
-
340
- return file.data;
341
- });
342
-
343
- if (!props.error && errorMessage.value) {
344
- errorMessage.value = "";
345
- errorFilesTypes.value = [];
346
- }
256
+ if (fileInputRef.value) fileInputRef.value.value = "";
257
+ }
347
258
 
348
- if (props.local) filesData.value = selectedFilesData;
259
+ function onDragOver(event) {
260
+ event.preventDefault();
349
261
 
350
- emit("changeFiles", selectedFilesData);
262
+ dropZoneRef.value.classList.add(config.value.dropzoneWrapperHover.split(" "));
351
263
  }
352
264
 
353
- function onChangeFiles() {
354
- filesData.value = props.files;
355
-
356
- if (!props.error && errorMessage.value) {
357
- errorMessage.value = "";
358
- errorFilesTypes.value = [];
359
- }
265
+ function onDragLeave(event) {
266
+ event.preventDefault();
360
267
 
361
- onDragLeave();
268
+ dropZoneRef.value.classList.remove(config.value.dropzoneWrapperHover.split(" "));
362
269
  }
363
270
 
364
- function onChangeError() {
365
- errorMessage.value = props.error;
366
- }
271
+ function onDrop(event) {
272
+ event.preventDefault();
367
273
 
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;
274
+ let targetFile = null;
373
275
 
374
- errorMessage.value = `${cannotAttachFilesStart} ${error} ${cannotAttachFilesEnd}`;
276
+ if (event.dataTransfer.items) {
277
+ targetFile = [...event.dataTransfer.items].find((item) => item.kind === "file")?.getAsFile();
278
+ } else {
279
+ targetFile = [...event.dataTransfer.files].at(0);
375
280
  }
376
- }
377
-
378
- function onClickDeleteFile(data) {
379
- const index = filesData.value.findIndex((file) => file.id === data.file.id);
380
281
 
381
- if (~index) {
382
- filesData.value.splice(index, 1);
282
+ if (targetFile) validate(targetFile);
383
283
 
384
- uppy.value.removeFile(data.file.id);
284
+ nextTick(() => {
285
+ if (currentError.value || !targetFile) {
286
+ onClickResetFiles();
385
287
 
386
- emit("deleteFile", data.file.id);
288
+ return;
289
+ }
387
290
 
388
- if (props.local) emit("changeFiles", filesData.value);
389
- }
291
+ currentFiles.value = [targetFile];
292
+ });
390
293
  }
391
294
  </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.100",
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",