vueless 0.0.158 → 0.0.160

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "0.0.158",
3
+ "version": "0.0.160",
4
4
  "license": "MIT",
5
5
  "description": "Vue Styleless Component Framework.",
6
6
  "homepage": "https://vueless.com",
@@ -13,13 +13,13 @@ export default /*tw*/ {
13
13
  separated: {
14
14
  false: `
15
15
  flex-nowrap -space-x-px gap-0
16
- [&>*:first-child]:rounded-l-lg [&>*:first-child]:rounded-r-none
17
- [&>*:last-child]:rounded-r-lg [&>*:last-child]:rounded-l-none
16
+ [&>:first-child]:rounded-l-lg [&>:last-child]:rounded-r-lg
17
+ [&>:first-child>*>*]:rounded-l-lg [&>:last-child>*>*]:rounded-r-lg
18
18
  `,
19
19
  },
20
20
  },
21
21
  compoundVariants: [
22
- { separated: false, variant: "thirdary", class: "space-x-0" },
22
+ { separated: false, variant: "thirdary", class: "space-x-px" },
23
23
  { separated: false, multiple: true, class: "space-x-px" },
24
24
  ],
25
25
  },
@@ -12,7 +12,13 @@ export default /*tw*/ {
12
12
  false: "rounded-none",
13
13
  },
14
14
  variant: {
15
- thirdary: "border-none hover:bg-brand-600 hover:bg-opacity-5",
15
+ thirdary: `
16
+ !text-gray-900 bg-brand-600
17
+ border-transparent bg-opacity-10
18
+ hover:border-transparent hover:bg-opacity-15
19
+ focus:border-transparent focus:bg-opacity-15
20
+ active:border-transparent active:bg-opacity-20
21
+ `,
16
22
  },
17
23
  },
18
24
  },
@@ -23,7 +29,7 @@ export default /*tw*/ {
23
29
  variant: {
24
30
  primary: "!text-white bg-brand-600 border-brand-600 disabled:border-brand-600",
25
31
  secondary: "text-brand-600 border-brand-600 disabled:border-brand-600 bg-brand-600 bg-opacity-10",
26
- thirdary: "text-brand-600 bg-brand-600 bg-opacity-10",
32
+ thirdary: " bg-brand-600 bg-opacity-20 hover:bg-opacity-20 focus:bg-opacity-20",
27
33
  },
28
34
  },
29
35
  },
@@ -6,7 +6,7 @@ import defaultConfig from "../configs/default.config";
6
6
 
7
7
  export function useAttrs(props) {
8
8
  const { config, getAttrs } = useUI(defaultConfig, () => props.config);
9
- const { dropzoneWrapper, placeholder, selectedItem } = config.value;
9
+ const { dropzoneWrapper, placeholder, contentWrapper } = config.value;
10
10
 
11
11
  const cvaDropzoneWrapper = cva({
12
12
  base: dropzoneWrapper.base,
@@ -20,10 +20,10 @@ export function useAttrs(props) {
20
20
  compoundVariants: placeholder.compoundVariants,
21
21
  });
22
22
 
23
- const cvaSelectedItem = cva({
24
- base: selectedItem.base,
25
- variants: selectedItem.variants,
26
- compoundVariants: selectedItem.compoundVariants,
23
+ const cvaContentWrapper = cva({
24
+ base: contentWrapper.base,
25
+ variants: contentWrapper.variants,
26
+ compoundVariants: contentWrapper.compoundVariants,
27
27
  });
28
28
 
29
29
  const dropzoneWrapperClasses = computed(() =>
@@ -41,9 +41,9 @@ export function useAttrs(props) {
41
41
  }),
42
42
  );
43
43
 
44
- const selectedItemClasses = computed(() =>
45
- cvaSelectedItem({
46
- size: props.size,
44
+ const contentWrapperClasses = computed(() =>
45
+ cvaContentWrapper({
46
+ multiple: props.multiple,
47
47
  }),
48
48
  );
49
49
 
@@ -51,14 +51,14 @@ export function useAttrs(props) {
51
51
  const buttonAttrs = getAttrs("button", { isComponent: true });
52
52
  const dropzoneWrapperAttrs = getAttrs("dropzoneWrapper", { classes: dropzoneWrapperClasses });
53
53
  const descriptionAttrs = getAttrs("description", { isComponent: true });
54
- const contentWrapperAttrs = getAttrs("contentWrapper");
55
54
  const buttonWrapperAttrs = getAttrs("buttonWrapper");
56
55
  const placeholderAttrs = getAttrs("placeholder", { classes: placeholderClasses });
57
56
  const clearIconAttrs = getAttrs("clearIcon", { isComponent: true });
57
+ const removeItemIconAttrs = getAttrs("removeItemIcon", { isComponent: true });
58
58
  const chooseFileIconAttrs = getAttrs("chooseFileIcon", { isComponent: true });
59
59
  const inputAttrs = getAttrs("input");
60
- const fileListAttrs = getAttrs("fileList");
61
- const selectedItemAttrs = getAttrs("selectedItem", { classes: selectedItemClasses });
60
+ const fileListAttrs = getAttrs("fileList", { isComponent: true });
61
+ const contentWrapperAttrs = getAttrs("contentWrapper", { classes: contentWrapperClasses });
62
62
 
63
63
  return {
64
64
  config,
@@ -72,7 +72,7 @@ export function useAttrs(props) {
72
72
  placeholderAttrs,
73
73
  fileListAttrs,
74
74
  buttonWrapperAttrs,
75
- selectedItemAttrs,
76
75
  chooseFileIconAttrs,
76
+ removeItemIconAttrs,
77
77
  };
78
78
  }
@@ -11,7 +11,7 @@ export default /*tw*/ {
11
11
  },
12
12
  dropzoneWrapper: {
13
13
  base: `
14
- size-auto w-full rounded border border-dashed border-gray-300 bg-white
14
+ size-auto w-full rounded border border-solid border-gray-300 bg-white
15
15
  p-4 px-5 py-6 hover:border-gray-400
16
16
  `,
17
17
  variants: {
@@ -21,24 +21,20 @@ export default /*tw*/ {
21
21
  },
22
22
  compoundVariants: [
23
23
  { labelAlign: "topInside", label: true, size: "sm", class: "pt-8" },
24
- { labelAlign: "topInside", label: true, size: "md", class: "py-10" },
24
+ { labelAlign: "topInside", label: true, size: "md", class: "pt-10" },
25
25
  { labelAlign: "topInside", label: true, size: "lg", class: "pt-12" },
26
26
  ],
27
27
  },
28
28
  description: "text-gray-700",
29
- contentWrapper: "relative mt-3 flex w-full gap-6 justify-between items-start rounded bg-brand-50 p-3",
30
- fileList: "pr-4 shrink-0 text-gray-700 flex-grow flex flex-col gap-4",
31
- placeholder: {
32
- base: "pr-4 text-gray-700 flex-grow",
29
+ contentWrapper: {
30
+ base: "relative mt-3 flex w-full gap-6 justify-between items-start rounded bg-brand-50 p-3",
33
31
  variants: {
34
- size: {
35
- sm: "text-sm",
36
- md: "text-base",
37
- lg: "text-lg",
32
+ multiple: {
33
+ false: "items-center",
38
34
  },
39
35
  },
40
36
  },
41
- selectedItem: {
37
+ placeholder: {
42
38
  base: "pr-4 text-gray-700 flex-grow",
43
39
  variants: {
44
40
  size: {
@@ -53,8 +49,10 @@ export default /*tw*/ {
53
49
  chooseFileIconName: "attach_file",
54
50
  clearIcon: "",
55
51
  clearIconName: "close",
56
- dropzoneWrapperHover: "border-gray-400",
57
- dropzoneWrapperError: "hover:border-red-400 border-red-300",
52
+ removeItemIcon: "",
53
+ removeItemIconName: "close",
54
+ dropzoneWrapperHover: "border-gray-400 border-dashed",
55
+ dropzoneWrapperError: "hover:border-red-400 border-dashed border-red-300",
58
56
  input: "sr-only pointer-events-none size-0 opacity-0",
59
57
  buttonWrapper: "flex gap-4 items-center",
60
58
  i18n: {
@@ -10,6 +10,9 @@ export default {
10
10
  id: "30232",
11
11
  title: "Form Inputs & Controls / Input File",
12
12
  component: UInputFile,
13
+ args: {
14
+ label: "Vueless file input",
15
+ },
13
16
  argTypes: {
14
17
  ...getArgTypes(UInputFile.name),
15
18
  },
@@ -37,6 +40,27 @@ const DefaultTemplate = (args) => ({
37
40
  `,
38
41
  });
39
42
 
43
+ const LabelAlignTemplate = (args, { argTypes } = {}) => ({
44
+ components: { UInputFile, URow },
45
+ setup() {
46
+ return {
47
+ args,
48
+ alignOptions: argTypes.labelAlign.options,
49
+ };
50
+ },
51
+ template: `
52
+ <URow class="!flex-col" gap="xl">
53
+ <div v-for="(align, index) in alignOptions" :key="index">
54
+ <UInputFile
55
+ :label-align="align"
56
+ v-bind="args"
57
+ :label="align"
58
+ />
59
+ </div>
60
+ </URow>
61
+ `,
62
+ });
63
+
40
64
  const SizesTemplate = (args, { argTypes } = {}) => ({
41
65
  components: { UInputFile, URow },
42
66
  setup() {
@@ -60,8 +84,17 @@ const SizesTemplate = (args, { argTypes } = {}) => ({
60
84
  export const Default = DefaultTemplate.bind({});
61
85
  Default.args = {};
62
86
 
87
+ export const multiple = DefaultTemplate.bind({});
88
+ multiple.args = { multiple: true };
89
+
90
+ export const allowedFileTypes = DefaultTemplate.bind({});
91
+ allowedFileTypes.args = { allowedFileTypes: ["png", "jpeg"], label: "Allow only png and jpeg" };
92
+
63
93
  export const error = DefaultTemplate.bind({});
64
94
  error.args = { error: "some error" };
65
95
 
66
96
  export const sizes = SizesTemplate.bind({});
67
97
  sizes.args = {};
98
+
99
+ export const labelAlign = LabelAlignTemplate.bind({});
100
+ labelAlign.args = {};
@@ -9,18 +9,21 @@
9
9
 
10
10
  <span v-if="!isValue" v-bind="placeholderAttrs" v-text="currentLocale.noFile" />
11
11
 
12
- <div v-else v-bind="fileListAttrs">
13
- <template v-if="props.multiple">
14
- <span
15
- v-for="(file, idx) in currentFiles"
16
- :key="idx"
17
- v-bind="selectedItemAttrs"
18
- v-text="file.name"
12
+ <UFiles :size="size" v-bind="fileListAttrs" :file-list="fileList">
13
+ <template #right="{ file }">
14
+ <UIcon
15
+ v-if="props.multiple"
16
+ pill
17
+ internal
18
+ interactive
19
+ size="2xs"
20
+ :name="config.removeItemIconName"
21
+ :data-cy="`${dataCy}-remove-item`"
22
+ v-bind="removeItemIconAttrs"
23
+ @click.stop.prevent="onClickRemoveItem(file.id)"
19
24
  />
20
25
  </template>
21
-
22
- <span v-else v-bind="selectedItemAttrs" v-text="currentFiles.name" />
23
- </div>
26
+ </UFiles>
24
27
 
25
28
  <div v-bind="buttonWrapperAttrs">
26
29
  <template v-if="Array.isArray(currentFiles) || !currentFiles">
@@ -31,6 +34,7 @@
31
34
  variant="thirdary"
32
35
  :size="nestedComponentSize"
33
36
  :label="currentLocale.uploadFile"
37
+ :data-cy="`${dataCy}-upload`"
34
38
  v-bind="buttonAttrs"
35
39
  >
36
40
  <template #right>
@@ -61,6 +65,7 @@
61
65
  interactive
62
66
  :size="nestedComponentSize"
63
67
  :name="config.clearIconName"
68
+ :data-cy="`${dataCy}-clear`"
64
69
  v-bind="clearIconAttrs"
65
70
  @click="onClickResetFiles"
66
71
  />
@@ -71,13 +76,14 @@
71
76
  </template>
72
77
 
73
78
  <script setup>
74
- import { computed, nextTick, onBeforeUnmount, onMounted, ref } from "vue";
79
+ import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
75
80
  import { merge } from "lodash-es";
76
81
 
77
82
  import UText from "../ui.text-block";
78
83
  import UIcon from "../ui.image-icon";
79
84
  import ULabel from "../ui.form-label";
80
85
  import UButton from "../ui.button";
86
+ import UFiles from "../ui.text-files";
81
87
 
82
88
  import UIService, { getRandomId } from "../service.ui";
83
89
  import { getFileMbSize } from "./services/fileForm.service";
@@ -178,6 +184,14 @@ const props = defineProps({
178
184
  type: Object,
179
185
  default: () => ({}),
180
186
  },
187
+
188
+ /**
189
+ * Data-cy attribute for automated testing.
190
+ */
191
+ dataCy: {
192
+ type: String,
193
+ default: "",
194
+ },
181
195
  });
182
196
 
183
197
  const emit = defineEmits(["update:modelValue", "update:error"]);
@@ -200,7 +214,7 @@ const {
200
214
  inputAttrs,
201
215
  fileListAttrs,
202
216
  buttonWrapperAttrs,
203
- selectedItemAttrs,
217
+ removeItemIconAttrs,
204
218
  } = useAttrs(props);
205
219
 
206
220
  const i18nGlobal = tm(UInputFile);
@@ -234,7 +248,7 @@ const accept = computed(() => {
234
248
 
235
249
  const isValue = computed(() => {
236
250
  return (
237
- (Array.isArray(currentFiles.value) && currentFiles.value.length) ||
251
+ (Array.isArray(currentFiles.value) && currentFiles.value?.length) ||
238
252
  (!Array.isArray(currentFiles.value) && currentFiles.value)
239
253
  );
240
254
  });
@@ -249,6 +263,14 @@ const nestedComponentSize = computed(() => {
249
263
  return size;
250
264
  });
251
265
 
266
+ const fileList = computed(() => {
267
+ if (Array.isArray(currentFiles.value)) {
268
+ return currentFiles.value;
269
+ }
270
+
271
+ return currentFiles.value ? [currentFiles.value] : [];
272
+ });
273
+
252
274
  onMounted(() => {
253
275
  dropZoneRef.value.addEventListener("dragover", onDragOver);
254
276
  dropZoneRef.value.addEventListener("dragleave", onDragLeave);
@@ -259,6 +281,23 @@ onBeforeUnmount(() => {
259
281
  dropZoneRef.value.removeEventListener("dragleave", onDragLeave);
260
282
  });
261
283
 
284
+ watch(
285
+ () => props.multiple,
286
+ () => {
287
+ if (!props.multiple && Array.isArray(currentFiles.value)) {
288
+ currentFiles.value = currentFiles.value[0];
289
+ }
290
+
291
+ if (props.multiple && !Array.isArray(currentFiles.value)) {
292
+ currentFiles.value = currentFiles.value ? [currentFiles.value] : [];
293
+ }
294
+ },
295
+ );
296
+
297
+ function removeDuplicates(files) {
298
+ return files.filter((file) => !fileList.value.find((item) => item.name === file.name));
299
+ }
300
+
262
301
  function validate(file) {
263
302
  const targetFileSize = getFileMbSize(file);
264
303
 
@@ -287,7 +326,7 @@ function onChangeFile(event) {
287
326
  }
288
327
 
289
328
  currentFiles.value = props.multiple
290
- ? [...currentFiles.value, Array.from(event.target.files).at(0)]
329
+ ? [...(currentFiles.value || []), ...removeDuplicates(Array.from(event.target.files))]
291
330
  : Array.from(event.target.files).at(0);
292
331
 
293
332
  if (fileInputRef.value) fileInputRef.value.value = "";
@@ -314,24 +353,32 @@ function onDragLeave(event) {
314
353
  function onDrop(event) {
315
354
  event.preventDefault();
316
355
 
317
- let targetFile = null;
356
+ let targetFiles = null;
318
357
 
319
358
  if (event.dataTransfer.items) {
320
- targetFile = [...event.dataTransfer.items].find((item) => item.kind === "file")?.getAsFile();
359
+ targetFiles = [...event.dataTransfer.items]
360
+ .filter((item) => item.kind === "file")
361
+ .map((item) => item.getAsFile());
321
362
  } else {
322
- targetFile = [...event.dataTransfer.files].at(0);
363
+ targetFiles = [...event.dataTransfer.files];
323
364
  }
324
365
 
325
- if (targetFile) validate(targetFile);
366
+ if (targetFiles.length) targetFiles.forEach(validate);
326
367
 
327
368
  nextTick(() => {
328
- if (currentError.value || !targetFile) {
369
+ if (currentError.value || !targetFiles.length) {
329
370
  onClickResetFiles();
330
371
 
331
372
  return;
332
373
  }
333
374
 
334
- currentFiles.value = props.multiple ? [...currentFiles.value, targetFile] : targetFile;
375
+ currentFiles.value = props.multiple
376
+ ? [...(currentFiles.value || []), ...removeDuplicates(targetFiles)]
377
+ : targetFiles[0];
335
378
  });
336
379
  }
380
+
381
+ function onClickRemoveItem(id) {
382
+ currentFiles.value = currentFiles.value?.filter((file) => file.name !== id);
383
+ }
337
384
  </script>
@@ -43,9 +43,5 @@ export default /*tw*/ {
43
43
  bordered: false,
44
44
  closeIcon: false,
45
45
  },
46
- safelist: (colors) => [
47
- { pattern: `bg-(${colors})-50` },
48
- { pattern: `text-(${colors})-700` },
49
- { pattern: `border-(${colors})-100` },
50
- ],
46
+ safelist: (colors) => [{ pattern: `bg-(${colors})-50` }, { pattern: `text-(${colors})-700` }, { pattern: `border-(${colors})-100` }],
51
47
  };
@@ -6,32 +6,33 @@ import defaultConfig from "../configs/default.config";
6
6
 
7
7
  export function useAttrs(props, { focus }) {
8
8
  const { config, getAttrs } = useUI(defaultConfig, () => props.config);
9
- const { text } = config.value;
9
+ const { label } = config.value;
10
10
 
11
- const cvaText = cva({
12
- base: text.base,
13
- variants: text.variants,
14
- compoundVariants: text.compoundVariants,
11
+ const cvaLabel = cva({
12
+ base: label.base,
13
+ variants: label.variants,
14
+ compoundVariants: label.compoundVariants,
15
15
  });
16
16
 
17
- const textClasses = computed(() =>
18
- cvaText({
17
+ const labelClasses = computed(() =>
18
+ cvaLabel({
19
19
  focus: Boolean(focus.value),
20
+ size: props.size,
20
21
  }),
21
22
  );
22
23
 
23
- const textAttrs = getAttrs("text", { classes: textClasses });
24
+ const labelAttrs = getAttrs("label", { classes: labelClasses });
24
25
  const fileAttrs = getAttrs("file", { isComponent: true });
25
26
  const infoAttrs = getAttrs("info");
26
27
  const iconAttrs = getAttrs("icon", { isComponent: true });
27
- const itemImageAttrs = getAttrs("itemImage");
28
+ const imageAttrs = getAttrs("image");
28
29
 
29
30
  return {
30
31
  config,
31
32
  fileAttrs,
32
33
  infoAttrs,
33
34
  iconAttrs,
34
- textAttrs,
35
- itemImageAttrs,
35
+ labelAttrs,
36
+ imageAttrs,
36
37
  };
37
38
  }
@@ -1,15 +1,20 @@
1
1
  export default /*tw*/ {
2
2
  file: "",
3
- info: "flex items-start",
4
- icon: "mr-3",
3
+ info: "flex items-center gap-2 ",
4
+ icon: "",
5
5
  iconName: "description",
6
- itemImage: "rounded-sm",
7
- text: {
8
- base: "whitespace-normal pt-0.5 hover:underline hover:decoration-dashed hover:underline-offset-4",
6
+ image: "rounded-sm max-w-7",
7
+ label: {
8
+ base: "text-gray-700 whitespace-normal hover:underline hover:decoration-dashed hover:underline-offset-4",
9
9
  variants: {
10
10
  focus: {
11
11
  true: "underline decoration-dashed underline-offset-4",
12
12
  },
13
+ size: {
14
+ sm: "text-sm",
15
+ md: "text-base",
16
+ lg: "text-lg",
17
+ },
13
18
  },
14
19
  },
15
20
  };
@@ -0,0 +1,5 @@
1
+ /*
2
+ This const is needed to prevent the issue in script setup:
3
+ `defineProps` is referencing locally declared variables. (vue/valid-define-props)
4
+ */
5
+ export const UFile = "UFile";
@@ -1,9 +1,9 @@
1
1
  <template>
2
2
  <ULink :url="url" no-ring target-blank :data-cy="dataCy" v-bind="fileAttrs">
3
- <slot name="left" :file="{ id, text, url, imageUrl }" />
3
+ <slot name="left" :file="{ id, label, url, imageUrl }" />
4
4
 
5
5
  <div v-bind="infoAttrs">
6
- <img v-if="imageUrl" :src="imageUrl" v-bind="itemImageAttrs" />
6
+ <img v-if="imageUrl" :src="imageUrl" v-bind="imageAttrs" />
7
7
 
8
8
  <UIcon
9
9
  v-else
@@ -16,7 +16,7 @@
16
16
  @blur="onBlur"
17
17
  />
18
18
 
19
- <span v-bind="textAttrs" v-text="label" />
19
+ <span v-bind="labelAttrs" v-text="label" />
20
20
  </div>
21
21
 
22
22
  <slot name="right" :file="{ id, label, url, imageUrl }" />
@@ -28,9 +28,12 @@ import { ref } from "vue";
28
28
 
29
29
  import ULink from "../ui.button-link";
30
30
  import UIcon from "../ui.image-icon";
31
- import { getRandomId } from "../service.ui";
31
+
32
+ import UIService, { getRandomId } from "../service.ui";
32
33
 
33
34
  import { useAttrs } from "./composables/attrs.composable";
35
+ import { UFile } from "./constants";
36
+ import defaultConfig from "./configs/default.config";
34
37
 
35
38
  /* Should be a string for correct web-types gen */
36
39
  defineOptions({ name: "UFile", inheritAttrs: false });
@@ -69,6 +72,15 @@ const props = defineProps({
69
72
  default: () => getRandomId(),
70
73
  },
71
74
 
75
+ /**
76
+ * Set size.
77
+ * @values sm, md, lg
78
+ */
79
+ size: {
80
+ type: String,
81
+ default: UIService.get(defaultConfig, UFile).default.size,
82
+ },
83
+
72
84
  /**
73
85
  * Sets data-cy attribute for automated testing.
74
86
  */
@@ -80,7 +92,7 @@ const props = defineProps({
80
92
 
81
93
  const focus = ref(false);
82
94
 
83
- const { config, fileAttrs, infoAttrs, iconAttrs, textAttrs, itemImageAttrs } = useAttrs(props, {
95
+ const { config, fileAttrs, infoAttrs, iconAttrs, labelAttrs, imageAttrs } = useAttrs(props, {
84
96
  focus,
85
97
  });
86
98
 
@@ -1,17 +1,26 @@
1
+ import { computed } from "vue";
1
2
  import useUI from "../../composable.ui";
3
+ import { cva } from "../../service.ui";
2
4
 
3
5
  import defaultConfig from "../configs/default.config";
4
6
 
5
7
  export function useAttrs(props) {
6
- const { getAttrs } = useUI(defaultConfig, () => props.config);
8
+ const { getAttrs, config } = useUI(defaultConfig, () => props.config);
9
+ const { body } = config.value;
10
+
11
+ const cvaBody = cva({
12
+ base: body.base,
13
+ variants: body.variants,
14
+ compoundVariants: body.compoundVariants,
15
+ });
16
+
17
+ const bodyClasses = computed(() => cvaBody({ label: Boolean(props.label) }));
7
18
 
8
- const filesAttrs = getAttrs("files");
9
19
  const labelAttrs = getAttrs("label");
10
- const bodyAttrs = getAttrs("body");
20
+ const bodyAttrs = getAttrs("body", { classes: bodyClasses });
11
21
  const fileAttrs = getAttrs("file", { isComponent: true });
12
22
 
13
23
  return {
14
- filesAttrs,
15
24
  labelAttrs,
16
25
  bodyAttrs,
17
26
  fileAttrs,
@@ -1,9 +1,16 @@
1
1
  export default /*tw*/ {
2
- files: "",
3
2
  label: "block text-sm font-normal text-gray-500",
4
- body: "space-y-2 mt-4",
3
+ body: {
4
+ base: "flex gap-4 flex-col",
5
+ variants: {
6
+ label: {
7
+ true: "mt-4",
8
+ },
9
+ },
10
+ },
5
11
  file: "block",
6
12
  defaultVariants: {
7
13
  placement: "topInside",
14
+ size: "md",
8
15
  },
9
16
  };
@@ -11,9 +11,9 @@ export default {
11
11
  component: UFiles,
12
12
  args: {
13
13
  label: "Label",
14
- options: [
15
- { text: "some file text 1", url: "https://storybook.js.org/" },
16
- { text: "some file text 2", url: "https://storybook.js.org/" },
14
+ fileList: [
15
+ new File(["foo"], "foo.txt", { type: "text/plain" }),
16
+ new File(["bar"], "foo.txt", { type: "text/plain" }),
17
17
  ],
18
18
  },
19
19
  argTypes: {
@@ -1,31 +1,31 @@
1
1
  <template>
2
- <div :data-cy="dataCy" v-bind="filesAttrs">
3
- <ULabel :label="label" :description="description" v-bind="labelAttrs">
4
- <div v-bind="bodyAttrs">
5
- <slot>
6
- <UFile
7
- v-for="(option, index) in options"
8
- :id="option.id"
9
- :key="option.text"
10
- :label="option.text"
11
- :url="option.url"
12
- :data-cy="`${dataCy}-item-${index}`"
13
- v-bind="fileAttrs"
14
- >
15
- <template #left="{ file }">
16
- <!-- @slot Use it to add something left. -->
17
- <slot name="left" :file="file" />
18
- </template>
2
+ <ULabel :label="label" :description="description" v-bind="labelAttrs">
3
+ <div v-bind="bodyAttrs">
4
+ <slot>
5
+ <UFile
6
+ v-for="file in formattedFileList"
7
+ :id="file.id"
8
+ :key="file.id"
9
+ :label="file.label"
10
+ :url="file.url"
11
+ :image-url="file.imageUrl"
12
+ :size="size"
13
+ v-bind="fileAttrs"
14
+ :data-cy="`${dataCy}-item`"
15
+ >
16
+ <template #left="{ file: currentFile }">
17
+ <!-- @slot Use it to add something left. -->
18
+ <slot name="left" :file="currentFile" />
19
+ </template>
19
20
 
20
- <template #right="{ file }">
21
- <!-- @slot Use it to add something right. -->
22
- <slot name="right" :file="file" />
23
- </template>
24
- </UFile>
25
- </slot>
26
- </div>
27
- </ULabel>
28
- </div>
21
+ <template #right="{ file: currentFile }">
22
+ <!-- @slot Use it to add something right. -->
23
+ <slot name="right" :file="currentFile" />
24
+ </template>
25
+ </UFile>
26
+ </slot>
27
+ </div>
28
+ </ULabel>
29
29
  </template>
30
30
 
31
31
  <script setup>
@@ -36,6 +36,7 @@ import UIService from "../service.ui";
36
36
  import { UFiles } from "./constants";
37
37
  import defaultConfig from "./configs/default.config";
38
38
  import { useAttrs } from "./composables/attrs.composable";
39
+ import { computed } from "vue";
39
40
 
40
41
  /* Should be a string for correct web-types gen */
41
42
  defineOptions({ name: "UFiles", inheritAttrs: false });
@@ -67,13 +68,22 @@ const props = defineProps({
67
68
  },
68
69
 
69
70
  /**
70
- * Set options for files.
71
+ * List of file objects.
71
72
  */
72
- options: {
73
+ fileList: {
73
74
  type: Array,
74
75
  default: () => [],
75
76
  },
76
77
 
78
+ /**
79
+ * Set size.
80
+ * @values sm, md, lg
81
+ */
82
+ size: {
83
+ type: String,
84
+ default: UIService.get(defaultConfig, UFiles).default.size,
85
+ },
86
+
77
87
  /**
78
88
  * Sets data-cy attribute for automated testing.
79
89
  */
@@ -83,5 +93,16 @@ const props = defineProps({
83
93
  },
84
94
  });
85
95
 
86
- const { filesAttrs, labelAttrs, bodyAttrs, fileAttrs } = useAttrs(props);
96
+ const { labelAttrs, bodyAttrs, fileAttrs } = useAttrs(props);
97
+
98
+ const formattedFileList = computed(() =>
99
+ props.fileList.map((file) => {
100
+ return {
101
+ id: file.name,
102
+ label: file.name,
103
+ url: URL.createObjectURL(file),
104
+ imageUrl: file.type.includes("image") ? URL.createObjectURL(file) : undefined,
105
+ };
106
+ }),
107
+ );
87
108
  </script>
package/web-types.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "framework": "vue",
3
3
  "name": "vueless",
4
- "version": "0.0.158",
4
+ "version": "0.0.160",
5
5
  "contributions": {
6
6
  "html": {
7
7
  "description-markup": "markdown",
@@ -2840,6 +2840,15 @@
2840
2840
  },
2841
2841
  "default": "() => getRandomId()"
2842
2842
  },
2843
+ {
2844
+ "name": "size",
2845
+ "description": "Set size.",
2846
+ "value": {
2847
+ "kind": "expression",
2848
+ "type": "'sm' | 'md' | 'lg'"
2849
+ },
2850
+ "default": "UIService.get(defaultConfig, UFile).default.size"
2851
+ },
2843
2852
  {
2844
2853
  "name": "dataCy",
2845
2854
  "description": "Sets data-cy attribute for automated testing.",
@@ -2895,14 +2904,23 @@
2895
2904
  "default": "topInside"
2896
2905
  },
2897
2906
  {
2898
- "name": "options",
2899
- "description": "Set options for files.",
2907
+ "name": "fileList",
2908
+ "description": "List of file objects.",
2900
2909
  "value": {
2901
2910
  "kind": "expression",
2902
2911
  "type": "array"
2903
2912
  },
2904
2913
  "default": "[]"
2905
2914
  },
2915
+ {
2916
+ "name": "size",
2917
+ "description": "Set size.",
2918
+ "value": {
2919
+ "kind": "expression",
2920
+ "type": "'sm' | 'md' | 'lg'"
2921
+ },
2922
+ "default": "md"
2923
+ },
2906
2924
  {
2907
2925
  "name": "dataCy",
2908
2926
  "description": "Sets data-cy attribute for automated testing.",
@@ -3595,6 +3613,15 @@
3595
3613
  "type": "object"
3596
3614
  },
3597
3615
  "default": "{}"
3616
+ },
3617
+ {
3618
+ "name": "dataCy",
3619
+ "description": "Data-cy attribute for automated testing.",
3620
+ "value": {
3621
+ "kind": "expression",
3622
+ "type": "string"
3623
+ },
3624
+ "default": "\"\""
3598
3625
  }
3599
3626
  ],
3600
3627
  "events": [