vueless 1.2.8 → 1.2.10-beta.0

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.
Files changed (78) hide show
  1. package/constants.d.ts +4 -0
  2. package/constants.js +4 -0
  3. package/icons/storybook/rocket_launch.svg +1 -0
  4. package/index.d.ts +3 -1
  5. package/index.ts +3 -1
  6. package/package.json +9 -5
  7. package/plugin-vite.js +6 -1
  8. package/types.ts +14 -2
  9. package/ui.button/config.ts +4 -4
  10. package/ui.button/tests/UButton.test.ts +3 -3
  11. package/ui.button-toggle/config.ts +2 -2
  12. package/ui.container-accordion/UAccordion.vue +0 -1
  13. package/ui.container-accordion/config.ts +1 -1
  14. package/ui.container-accordion/storybook/stories.ts +13 -1
  15. package/ui.container-accordion-item/UAccordionItem.vue +17 -4
  16. package/ui.container-accordion-item/config.ts +1 -1
  17. package/ui.container-accordion-item/storybook/stories.ts +26 -1
  18. package/ui.container-accordion-item/tests/UAccordionItem.test.ts +186 -0
  19. package/ui.container-card/config.ts +1 -1
  20. package/ui.data-table/config.ts +4 -4
  21. package/ui.dropdown-badge/UDropdownBadge.vue +68 -3
  22. package/ui.dropdown-badge/config.ts +5 -1
  23. package/ui.dropdown-badge/storybook/stories.ts +280 -4
  24. package/ui.dropdown-badge/tests/UDropdownBadge.test.ts +194 -0
  25. package/ui.dropdown-badge/types.ts +30 -0
  26. package/ui.dropdown-button/UDropdownButton.vue +69 -6
  27. package/ui.dropdown-button/config.ts +5 -1
  28. package/ui.dropdown-button/storybook/stories.ts +288 -3
  29. package/ui.dropdown-button/tests/UDropdownButton.test.ts +190 -0
  30. package/ui.dropdown-button/types.ts +30 -0
  31. package/ui.dropdown-link/UDropdownLink.vue +69 -6
  32. package/ui.dropdown-link/config.ts +5 -1
  33. package/ui.dropdown-link/storybook/stories.ts +281 -4
  34. package/ui.dropdown-link/tests/UDropdownLink.test.ts +194 -0
  35. package/ui.dropdown-link/types.ts +30 -0
  36. package/ui.form-calendar/config.ts +4 -2
  37. package/ui.form-checkbox/config.ts +1 -1
  38. package/ui.form-checkbox/tests/UCheckbox.test.ts +2 -2
  39. package/ui.form-checkbox-group/tests/UCheckboxGroup.test.ts +2 -2
  40. package/ui.form-date-picker-range/config.ts +1 -1
  41. package/ui.form-input/UInput.vue +4 -2
  42. package/ui.form-input/config.ts +1 -1
  43. package/ui.form-input/tests/UInput.test.ts +2 -2
  44. package/ui.form-input-counter/UInputCounter.vue +25 -1
  45. package/ui.form-input-counter/config.ts +7 -2
  46. package/ui.form-input-counter/tests/UInputCounter.test.ts +85 -1
  47. package/ui.form-input-counter/types.ts +25 -0
  48. package/ui.form-input-file/tests/UInputFile.test.ts +2 -2
  49. package/ui.form-input-number/UInputNumber.vue +15 -3
  50. package/ui.form-input-number/utilFormat.ts +17 -7
  51. package/ui.form-input-password/UInputPassword.vue +23 -1
  52. package/ui.form-label/ULabel.vue +10 -4
  53. package/ui.form-label/tests/ULabel.test.ts +29 -12
  54. package/ui.form-listbox/UListbox.vue +21 -9
  55. package/ui.form-listbox/config.ts +1 -1
  56. package/ui.form-listbox/storybook/stories.ts +188 -1
  57. package/ui.form-listbox/tests/UListbox.test.ts +36 -0
  58. package/ui.form-listbox/types.ts +5 -0
  59. package/ui.form-radio/config.ts +1 -1
  60. package/ui.form-radio/tests/URadio.test.ts +2 -2
  61. package/ui.form-radio-group/tests/URadioGroup.test.ts +2 -2
  62. package/ui.form-select/USelect.vue +20 -2
  63. package/ui.form-select/config.ts +2 -1
  64. package/ui.form-select/storybook/stories.ts +31 -4
  65. package/ui.form-select/tests/USelect.test.ts +143 -0
  66. package/ui.form-select/types.ts +10 -0
  67. package/ui.form-textarea/config.ts +1 -1
  68. package/ui.form-textarea/tests/UTextarea.test.ts +2 -2
  69. package/ui.text-alert/config.ts +1 -1
  70. package/ui.text-badge/config.ts +1 -1
  71. package/utils/helper.ts +4 -0
  72. package/utils/node/dynamicProps.d.ts +5 -2
  73. package/utils/node/dynamicProps.js +126 -53
  74. package/utils/node/helper.d.ts +10 -7
  75. package/utils/node/helper.js +59 -2
  76. package/utils/node/tailwindSafelist.js +9 -2
  77. package/utils/theme.ts +75 -31
  78. package/utils/ui.ts +32 -3
@@ -36,6 +36,26 @@ export interface Props {
36
36
  */
37
37
  valueKey?: string;
38
38
 
39
+ /**
40
+ * Set a name of the property containing the group label.
41
+ */
42
+ groupLabelKey?: string;
43
+
44
+ /**
45
+ * Set a name of the property containing the group values.
46
+ */
47
+ groupValueKey?: string;
48
+
49
+ /**
50
+ * Number of options displayed in the dropdown.
51
+ */
52
+ optionsLimit?: number;
53
+
54
+ /**
55
+ * Number of options you can see without a scroll.
56
+ */
57
+ visibleOptions?: number;
58
+
39
59
  /**
40
60
  * Badge variant.
41
61
  */
@@ -70,6 +90,16 @@ export interface Props {
70
90
  */
71
91
  searchable?: boolean;
72
92
 
93
+ /**
94
+ * Search input model value for the dropdown list.
95
+ */
96
+ search?: string;
97
+
98
+ /**
99
+ * Close dropdown on option select.
100
+ */
101
+ closeOnSelect?: boolean;
102
+
73
103
  /**
74
104
  * Allows multiple selection.
75
105
  */
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { nextTick, computed, provide, ref, useId, useTemplateRef } from "vue";
2
+ import { nextTick, computed, ref, useId, useTemplateRef } from "vue";
3
3
  import { isEqual } from "lodash-es";
4
4
 
5
5
  import useUI from "../composables/useUI";
@@ -55,13 +55,18 @@ const emit = defineEmits([
55
55
  * @property {string} query
56
56
  */
57
57
  "searchChange",
58
- ]);
59
58
 
60
- provide("hideDropdownOptions", hideOptions);
59
+ /**
60
+ * Triggers when the search v-model updates.
61
+ * @property {string} query
62
+ */
63
+ "update:search",
64
+ ]);
61
65
 
62
66
  type UListboxRef = InstanceType<typeof UListbox>;
63
67
 
64
68
  const isShownOptions = ref(false);
69
+ const isClickingOption = ref(false);
65
70
  const listboxRef = useTemplateRef<UListboxRef>("dropdown-list");
66
71
  const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper");
67
72
 
@@ -78,6 +83,11 @@ const dropdownValue = computed({
78
83
  set: (value) => emit("update:modelValue", value),
79
84
  });
80
85
 
86
+ const dropdownSearch = computed({
87
+ get: () => props.search ?? "",
88
+ set: (value: string) => emit("update:search", value),
89
+ });
90
+
81
91
  const selectedOptions = computed(() => {
82
92
  if (props.multiple) {
83
93
  return props.options.filter((option) => {
@@ -137,9 +147,23 @@ function getFullOptionLabels(value: Option | Option[]) {
137
147
  }
138
148
 
139
149
  function onClickOption(option: Option) {
150
+ isClickingOption.value = true;
151
+
140
152
  emit("clickOption", option);
141
153
 
142
- if (!props.multiple) hideOptions();
154
+ if (!props.multiple && props.closeOnSelect) hideOptions();
155
+
156
+ nextTick(() => {
157
+ setTimeout(() => {
158
+ isClickingOption.value = false;
159
+ }, 10);
160
+ });
161
+ }
162
+
163
+ function handleClickOutside() {
164
+ if (isClickingOption.value) return;
165
+
166
+ hideOptions();
143
167
  }
144
168
 
145
169
  function onClickButton() {
@@ -154,6 +178,7 @@ function onClickButton() {
154
178
 
155
179
  function hideOptions() {
156
180
  isShownOptions.value = false;
181
+ dropdownSearch.value = "";
157
182
 
158
183
  emit("close");
159
184
  }
@@ -188,7 +213,7 @@ const { getDataTest, config, dropdownButtonAttrs, listboxAttrs, toggleIconAttrs,
188
213
  <template>
189
214
  <div
190
215
  ref="wrapper"
191
- v-click-outside="hideOptions"
216
+ v-click-outside="handleClickOutside"
192
217
  v-bind="wrapperAttrs"
193
218
  :data-test="getDataTest('wrapper')"
194
219
  >
@@ -245,16 +270,54 @@ const { getDataTest, config, dropdownButtonAttrs, listboxAttrs, toggleIconAttrs,
245
270
  v-if="isShownOptions"
246
271
  ref="dropdown-list"
247
272
  v-model="dropdownValue"
273
+ v-model:search="dropdownSearch"
248
274
  :searchable="searchable"
249
275
  :multiple="multiple"
250
276
  :color="color"
251
277
  :options="options"
278
+ :options-limit="optionsLimit"
279
+ :visible-options="visibleOptions"
252
280
  :label-key="labelKey"
253
281
  :value-key="valueKey"
282
+ :group-label-key="groupLabelKey"
283
+ :group-value-key="groupValueKey"
254
284
  v-bind="listboxAttrs"
255
285
  :data-test="getDataTest('list')"
256
286
  @click-option="onClickOption"
257
287
  @search-change="onSearchChange"
258
- />
288
+ @update:search="(value) => emit('update:search', value)"
289
+ >
290
+ <template #before-option="{ option, index }">
291
+ <!--
292
+ @slot Use it to add something before option.
293
+ @binding {object} option
294
+ @binding {number} index
295
+ -->
296
+ <slot name="before-option" :option="option" :index="index" />
297
+ </template>
298
+
299
+ <template #option="{ option, index }">
300
+ <!--
301
+ @slot Use it to customize the option.
302
+ @binding {object} option
303
+ @binding {number} index
304
+ -->
305
+ <slot name="option" :option="option" :index="index" />
306
+ </template>
307
+
308
+ <template #after-option="{ option, index }">
309
+ <!--
310
+ @slot Use it to add something after option.
311
+ @binding {object} option
312
+ @binding {number} index
313
+ -->
314
+ <slot name="after-option" :option="option" :index="index" />
315
+ </template>
316
+
317
+ <template #empty>
318
+ <!-- @slot Use it to add something instead of empty state. -->
319
+ <slot name="empty" />
320
+ </template>
321
+ </UListbox>
259
322
  </div>
260
323
  </template>
@@ -51,15 +51,19 @@ export default /*tw*/ {
51
51
  variant: "solid",
52
52
  labelKey: "label",
53
53
  valueKey: "id",
54
+ groupLabelKey: "label",
54
55
  yPosition: "bottom",
55
56
  xPosition: "left",
57
+ optionsLimit: 0,
58
+ visibleOptions: 8,
59
+ labelDisplayCount: 2,
56
60
  searchable: false,
57
61
  round: false,
58
62
  block: false,
59
63
  square: false,
60
64
  disabled: false,
61
65
  multiple: false,
62
- labelDisplayCount: 2,
66
+ closeOnSelect: true,
63
67
  /* icons */
64
68
  toggleIcon: "keyboard_arrow_down",
65
69
  },
@@ -12,10 +12,18 @@ import UCol from "../../ui.container-col/UCol.vue";
12
12
  import UIcon from "../../ui.image-icon/UIcon.vue";
13
13
  import ULink from "../../ui.button-link/ULink.vue";
14
14
  import UAvatar from "../../ui.image-avatar/UAvatar.vue";
15
+ import UBadge from "../../ui.text-badge/UBadge.vue";
16
+ import UText from "../../ui.text-block/UText.vue";
17
+ import ULoader from "../../ui.loader/ULoader.vue";
15
18
 
16
19
  import type { Meta, StoryFn } from "@storybook/vue3-vite";
17
20
  import type { Props } from "../types";
18
21
 
22
+ import johnDoe from "../../ui.form-select/storybook/assets/images/john-doe.png";
23
+ import emilyDavis from "../../ui.form-select/storybook/assets/images/emily-davis.png";
24
+ import alexJohnson from "../../ui.form-select/storybook/assets/images/alex-johnson.png";
25
+ import patMorgan from "../../ui.form-select/storybook/assets/images/pat-morgan.png";
26
+
19
27
  interface DefaultUDropdownButtonArgs extends Props {
20
28
  slotTemplate?: string;
21
29
  }
@@ -54,7 +62,7 @@ export default {
54
62
  const DefaultTemplate: StoryFn<DefaultUDropdownButtonArgs> = (
55
63
  args: DefaultUDropdownButtonArgs,
56
64
  ) => ({
57
- components: { UDropdownButton, UIcon, ULink, UAvatar },
65
+ components: { UDropdownButton, UIcon, ULink, UAvatar, ULoader, URow, UText },
58
66
  setup: () => ({ args, slots: getSlotNames(UDropdownButton.__name) }),
59
67
  template: `
60
68
  <UDropdownButton v-bind="args">
@@ -111,6 +119,35 @@ const MultiEnumTemplate: StoryFn<EnumUDropdownButtonArgs> = (
111
119
  `,
112
120
  });
113
121
 
122
+ const GroupValuesTemplate: StoryFn<DefaultUDropdownButtonArgs> = (
123
+ args: DefaultUDropdownButtonArgs,
124
+ ) => ({
125
+ components: { UDropdownButton },
126
+ setup() {
127
+ return {
128
+ args,
129
+ };
130
+ },
131
+ template: `
132
+ <UDropdownButton
133
+ v-bind="args"
134
+ v-model="args.modelValue"
135
+ label="Single"
136
+ :config="{ listbox: 'min-w-[200px]' }"
137
+ class="max-w-96 mr-20"
138
+ />
139
+
140
+ <UDropdownButton
141
+ v-bind="args"
142
+ v-model="args.modelValueMultiple"
143
+ label="Multiple"
144
+ multiple
145
+ :config="{ listbox: 'min-w-[200px]' }"
146
+ class="mt-5 max-w-96"
147
+ />
148
+ `,
149
+ });
150
+
114
151
  export const Default = DefaultTemplate.bind({});
115
152
  Default.args = {};
116
153
  Default.parameters = {
@@ -121,6 +158,9 @@ Default.parameters = {
121
158
  },
122
159
  };
123
160
 
161
+ export const Disabled = DefaultTemplate.bind({});
162
+ Disabled.args = { disabled: true };
163
+
124
164
  export const Searchable = DefaultTemplate.bind({});
125
165
  Searchable.args = { searchable: true };
126
166
  Searchable.parameters = {
@@ -131,6 +171,27 @@ Searchable.parameters = {
131
171
  },
132
172
  };
133
173
 
174
+ export const SearchModelValue = DefaultTemplate.bind({});
175
+ SearchModelValue.args = { searchable: true, search: "Copy" };
176
+ SearchModelValue.parameters = {
177
+ docs: {
178
+ story: {
179
+ height: "250px",
180
+ },
181
+ },
182
+ };
183
+
184
+ export const NoCloseOnSelect = SelectableTemplate.bind({});
185
+ NoCloseOnSelect.args = {
186
+ modelValue: "pending",
187
+ options: [
188
+ { label: "Active", id: "active" },
189
+ { label: "Pending", id: "pending" },
190
+ { label: "Archived", id: "archived" },
191
+ ],
192
+ closeOnSelect: false,
193
+ };
194
+
134
195
  export const OptionSelection = SelectableTemplate.bind({});
135
196
  OptionSelection.args = {
136
197
  label: "Select status",
@@ -176,8 +237,59 @@ ListboxYPosition.parameters = {
176
237
  storyClasses: "h-[350px] flex items-center px-6 pt-8 pb-12",
177
238
  };
178
239
 
179
- export const Disabled = DefaultTemplate.bind({});
180
- Disabled.args = { disabled: true };
240
+ export const GroupValue = GroupValuesTemplate.bind({});
241
+ GroupValue.args = {
242
+ modelValue: "",
243
+ groupValueKey: "libs",
244
+ groupLabelKey: "language",
245
+ labelKey: "name",
246
+ valueKey: "name",
247
+ options: [
248
+ {
249
+ language: "Javascript",
250
+ libs: [{ name: "Vue.js" }, { name: "Adonis" }],
251
+ },
252
+ {
253
+ language: "Ruby",
254
+ libs: [
255
+ { name: "Frameworks", isSubGroup: true, level: 2 },
256
+ { name: "Rails", level: 3 },
257
+ { name: "Sinatra", level: 3 },
258
+ ],
259
+ },
260
+ {
261
+ language: "Other",
262
+ libs: [{ name: "Laravel" }, { name: "Phoenix" }],
263
+ },
264
+ ],
265
+ };
266
+ GroupValue.parameters = {
267
+ docs: {
268
+ story: {
269
+ height: "400px",
270
+ },
271
+ },
272
+ };
273
+
274
+ export const OptionsLimit = DefaultTemplate.bind({});
275
+ OptionsLimit.args = { optionsLimit: 2 };
276
+ OptionsLimit.parameters = {
277
+ docs: {
278
+ description: {
279
+ story: "`optionsLimit` prop controls the number of options displayed in the dropdown.",
280
+ },
281
+ },
282
+ };
283
+
284
+ export const VisibleOptions = DefaultTemplate.bind({});
285
+ VisibleOptions.args = { visibleOptions: 2 };
286
+ VisibleOptions.parameters = {
287
+ docs: {
288
+ description: {
289
+ story: "`visibleOptions` prop controls the number of options you can see without a scroll.",
290
+ },
291
+ },
292
+ };
181
293
 
182
294
  export const WithoutToggleIcon = Default.bind({});
183
295
  WithoutToggleIcon.args = { toggleIcon: false };
@@ -228,3 +340,176 @@ ToggleSlot.args = {
228
340
  </template>
229
341
  `,
230
342
  };
343
+
344
+ export const EmptySlot = DefaultTemplate.bind({});
345
+ EmptySlot.args = {
346
+ options: [],
347
+ slotTemplate: `
348
+ <template #empty>
349
+ <URow align="center">
350
+ <ULoader loading size="sm" />
351
+ <UText label="Loading, this may take a while..." />
352
+ </URow>
353
+ </template>
354
+ `,
355
+ };
356
+
357
+ export const OptionSlots: StoryFn<DefaultUDropdownButtonArgs> = (args) => ({
358
+ components: { UDropdownButton, URow, UCol, UAvatar, UIcon, UBadge, UText },
359
+ setup: () => ({ args, johnDoe, emilyDavis, alexJohnson, patMorgan }),
360
+ template: `
361
+ <URow>
362
+ <UDropdownButton
363
+ v-model="args.beforeOptionModel"
364
+ label="Before option slot"
365
+ :options="[
366
+ {
367
+ label: 'John Doe',
368
+ id: '1',
369
+ role: 'Developer',
370
+ avatar: johnDoe,
371
+ status: 'online',
372
+ statusColor: 'success',
373
+ },
374
+ {
375
+ label: 'Jane Smith',
376
+ id: '2',
377
+ role: 'Designer',
378
+ avatar: emilyDavis,
379
+ status: 'away',
380
+ statusColor: 'warning',
381
+ },
382
+ {
383
+ label: 'Mike Johnson',
384
+ id: '3',
385
+ role: 'Product Manager',
386
+ avatar: alexJohnson,
387
+ status: 'offline',
388
+ statusColor: 'grayscale',
389
+ },
390
+ {
391
+ label: 'Sarah Wilson',
392
+ id: '4',
393
+ role: 'QA Engineer',
394
+ avatar: patMorgan,
395
+ status: 'online',
396
+ statusColor: 'success',
397
+ },
398
+ ]"
399
+ >
400
+ <template #before-option="{ option }">
401
+ <UAvatar :src="option.avatar" size="sm" />
402
+ </template>
403
+ </UDropdownButton>
404
+
405
+ <UDropdownButton
406
+ v-model="args.optionModel"
407
+ label="Option slot"
408
+ :options="[
409
+ {
410
+ label: 'John Doe',
411
+ id: '1',
412
+ role: 'Developer',
413
+ avatar: johnDoe,
414
+ status: 'online',
415
+ statusColor: 'success',
416
+ },
417
+ {
418
+ label: 'Jane Smith',
419
+ id: '2',
420
+ role: 'Designer',
421
+ avatar: emilyDavis,
422
+ status: 'away',
423
+ statusColor: 'warning',
424
+ },
425
+ {
426
+ label: 'Mike Johnson',
427
+ id: '3',
428
+ role: 'Product Manager',
429
+ avatar: alexJohnson,
430
+ status: 'offline',
431
+ statusColor: 'grayscale',
432
+ },
433
+ {
434
+ label: 'Sarah Wilson',
435
+ id: '4',
436
+ role: 'QA Engineer',
437
+ avatar: patMorgan,
438
+ status: 'online',
439
+ statusColor: 'success',
440
+ },
441
+ ]"
442
+ >
443
+ <template #option="{ option }">
444
+ <URow align="center" gap="xs">
445
+ <UCol gap="none">
446
+ <UText size="sm">{{ option.label }}</UText>
447
+ <UText variant="lifted" size="xs">{{ option.role }}</UText>
448
+ </UCol>
449
+ <UBadge
450
+ :label="option.status"
451
+ :color="option.statusColor"
452
+ size="sm"
453
+ variant="subtle"
454
+ />
455
+ </URow>
456
+ </template>
457
+ </UDropdownButton>
458
+
459
+ <UDropdownButton
460
+ v-model="args.afterOptionModel"
461
+ label="After option slot"
462
+ :options="[
463
+ {
464
+ label: 'John Doe',
465
+ id: '1',
466
+ role: 'Developer',
467
+ avatar: johnDoe,
468
+ status: 'online',
469
+ statusColor: 'success',
470
+ },
471
+ {
472
+ label: 'Jane Smith',
473
+ id: '2',
474
+ role: 'Designer',
475
+ avatar: emilyDavis,
476
+ status: 'away',
477
+ statusColor: 'warning',
478
+ },
479
+ {
480
+ label: 'Mike Johnson',
481
+ id: '3',
482
+ role: 'Product Manager',
483
+ avatar: alexJohnson,
484
+ status: 'offline',
485
+ statusColor: 'grayscale',
486
+ },
487
+ {
488
+ label: 'Sarah Wilson',
489
+ id: '4',
490
+ role: 'QA Engineer',
491
+ avatar: patMorgan,
492
+ status: 'online',
493
+ statusColor: 'success',
494
+ },
495
+ ]"
496
+ >
497
+ <template #after-option="{ option }">
498
+ <UBadge
499
+ :label="option.status"
500
+ :color="option.statusColor"
501
+ size="sm"
502
+ variant="subtle"
503
+ />
504
+ </template>
505
+ </UDropdownButton>
506
+ </URow>
507
+ `,
508
+ });
509
+ OptionSlots.parameters = {
510
+ docs: {
511
+ story: {
512
+ height: "300px",
513
+ },
514
+ },
515
+ };