vueless 1.3.7-beta.2 → 1.3.7-beta.4

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 (37) hide show
  1. package/components.d.ts +1 -0
  2. package/components.ts +1 -0
  3. package/constants.d.ts +2 -0
  4. package/constants.js +2 -0
  5. package/icons/storybook/help.svg +1 -0
  6. package/icons/storybook/logout.svg +1 -0
  7. package/package.json +2 -2
  8. package/ui.container-col/UCol.vue +0 -1
  9. package/ui.container-collapsible/UCollapsible.vue +190 -0
  10. package/ui.container-collapsible/config.ts +45 -0
  11. package/ui.container-collapsible/constants.ts +1 -0
  12. package/ui.container-collapsible/storybook/docs.mdx +17 -0
  13. package/ui.container-collapsible/storybook/stories.ts +238 -0
  14. package/ui.container-collapsible/tests/UCollapsible.test.ts +571 -0
  15. package/ui.container-collapsible/types.ts +57 -0
  16. package/ui.dropdown/UDropdown.vue +324 -0
  17. package/ui.dropdown/config.ts +27 -0
  18. package/ui.dropdown/constants.ts +1 -0
  19. package/ui.dropdown/storybook/docs.mdx +17 -0
  20. package/ui.dropdown/storybook/stories.ts +286 -0
  21. package/ui.dropdown/tests/UDropdown.test.ts +631 -0
  22. package/ui.dropdown/types.ts +127 -0
  23. package/ui.dropdown-badge/UDropdownBadge.vue +119 -227
  24. package/ui.dropdown-badge/config.ts +18 -15
  25. package/ui.dropdown-badge/storybook/stories.ts +13 -40
  26. package/ui.dropdown-badge/tests/UDropdownBadge.test.ts +201 -67
  27. package/ui.dropdown-button/UDropdownButton.vue +121 -226
  28. package/ui.dropdown-button/config.ts +32 -28
  29. package/ui.dropdown-button/storybook/stories.ts +15 -42
  30. package/ui.dropdown-button/tests/UDropdownButton.test.ts +189 -73
  31. package/ui.dropdown-link/UDropdownLink.vue +123 -233
  32. package/ui.dropdown-link/config.ts +15 -18
  33. package/ui.dropdown-link/storybook/stories.ts +31 -58
  34. package/ui.dropdown-link/tests/UDropdownLink.test.ts +190 -71
  35. package/ui.form-listbox/UListbox.vue +2 -3
  36. package/ui.form-listbox/config.ts +2 -2
  37. package/ui.form-select/config.ts +1 -1
@@ -1,21 +1,17 @@
1
1
  <script setup lang="ts">
2
- import { nextTick, computed, ref, useId, useTemplateRef } from "vue";
3
- import { isEqual } from "lodash-es";
2
+ import { computed, useTemplateRef } from "vue";
4
3
 
5
4
  import { useUI } from "../composables/useUI";
6
5
  import { getDefaults } from "../utils/ui";
7
6
 
8
7
  import UIcon from "../ui.image-icon/UIcon.vue";
9
8
  import UButton from "../ui.button/UButton.vue";
10
- import UListbox from "../ui.form-listbox/UListbox.vue";
11
-
12
- import vClickOutside from "../v.click-outside/vClickOutside";
9
+ import UDropdown from "../ui.dropdown/UDropdown.vue";
13
10
 
14
11
  import defaultConfig from "./config";
15
12
  import { COMPONENT_NAME } from "./constants";
16
13
 
17
14
  import type { Props, Config } from "./types";
18
- import type { Option, SelectedValue } from "../ui.form-listbox/types";
19
15
 
20
16
  defineOptions({ inheritAttrs: false });
21
17
 
@@ -63,66 +59,9 @@ const emit = defineEmits([
63
59
  "update:search",
64
60
  ]);
65
61
 
66
- type UListboxRef = InstanceType<typeof UListbox>;
67
-
68
- const isShownOptions = ref(false);
69
- const isClickingOption = ref(false);
70
- const listboxRef = useTemplateRef<UListboxRef>("dropdown-list");
71
- const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper");
72
-
73
- const elementId = props.id || useId();
74
-
75
- const dropdownValue = computed({
76
- get: () => {
77
- if (props.multiple && !Array.isArray(props.modelValue)) {
78
- return props.modelValue ? [props.modelValue] : [];
79
- }
80
-
81
- return props.modelValue;
82
- },
83
- set: (value) => emit("update:modelValue", value),
84
- });
85
-
86
- const dropdownSearch = computed({
87
- get: () => props.search ?? "",
88
- set: (value: string) => emit("update:search", value),
89
- });
90
-
91
- const selectedOptions = computed(() => {
92
- if (props.multiple) {
93
- return props.options.filter((option) => {
94
- return (
95
- option[props.valueKey] &&
96
- (dropdownValue.value as SelectedValue[]).find((selected) =>
97
- isEqual(selected, option[props.valueKey]),
98
- )
99
- );
100
- });
101
- }
102
-
103
- return [
104
- props.options.find(
105
- (option) => option[props.valueKey] && isEqual(option[props.valueKey], dropdownValue.value),
106
- ),
107
- ].filter((option) => !!option);
108
- });
109
-
110
- const buttonLabel = computed(() => {
111
- if (!props.labelDisplayCount || !selectedOptions.value.length) {
112
- return props.label;
113
- }
62
+ type UDropdownRef = InstanceType<typeof UDropdown>;
114
63
 
115
- const selectedLabels = selectedOptions.value
116
- .slice(0, props.labelDisplayCount)
117
- .map((option) => option[props.labelKey]);
118
- const restLabelCount = selectedOptions.value.length - props.labelDisplayCount;
119
-
120
- if (restLabelCount > 0) {
121
- selectedLabels.push(`+${restLabelCount}`);
122
- }
123
-
124
- return selectedLabels.join(", ");
125
- });
64
+ const dropdownRef = useTemplateRef<UDropdownRef>("dropdown");
126
65
 
127
66
  const toggleIconName = computed(() => {
128
67
  if (typeof props.toggleIcon === "string") {
@@ -132,55 +71,8 @@ const toggleIconName = computed(() => {
132
71
  return props.toggleIcon ? config.value.defaults.toggleIcon : "";
133
72
  });
134
73
 
135
- function onSearchChange(query: string) {
136
- emit("searchChange", query);
137
- }
138
-
139
- function getFullOptionLabels(value: Option | Option[]) {
140
- const labelKey = props.labelKey;
141
-
142
- if (Array.isArray(value)) {
143
- return value.map((item) => item[labelKey]).join(", ");
144
- }
145
-
146
- return "";
147
- }
148
-
149
- function onClickOption(option: Option) {
150
- isClickingOption.value = true;
151
-
152
- emit("clickOption", option);
153
-
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();
167
- }
168
-
169
- function onClickButton() {
170
- isShownOptions.value = !isShownOptions.value;
171
-
172
- if (isShownOptions.value) {
173
- nextTick(() => listboxRef.value?.wrapperRef?.focus());
174
-
175
- emit("open");
176
- }
177
- }
178
-
179
- function hideOptions() {
180
- isShownOptions.value = false;
181
- dropdownSearch.value = "";
182
-
183
- emit("close");
74
+ function hide() {
75
+ dropdownRef.value?.hide();
184
76
  }
185
77
 
186
78
  defineExpose({
@@ -188,136 +80,139 @@ defineExpose({
188
80
  * A reference to the component's wrapper element for direct DOM manipulation.
189
81
  * @property {HTMLDivElement}
190
82
  */
191
- wrapperRef,
83
+ wrapperRef: computed(() => dropdownRef.value?.wrapperRef),
192
84
 
193
85
  /**
194
- * Hides the dropdown options.
86
+ * Hides the dropdown.
195
87
  * @property {function}
196
88
  */
197
- hideOptions,
89
+ hide,
198
90
  });
199
91
 
200
- /**
201
- * Get element / nested component attributes for each config token ✨
92
+ /*
93
+ * Vueless: Get element / nested component attributes for each config token ✨
202
94
  * Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
203
95
  */
204
96
  const mutatedProps = computed(() => ({
205
97
  /* component state, not a props */
206
- opened: isShownOptions.value,
98
+ opened: dropdownRef.value?.isOpened ?? false,
207
99
  }));
208
100
 
209
- const { getDataTest, config, dropdownButtonAttrs, listboxAttrs, toggleIconAttrs, wrapperAttrs } =
210
- useUI<Config>(defaultConfig, mutatedProps, "dropdownButton");
101
+ const { getDataTest, config, toggleButtonAttrs, dropdownButtonAttrs, toggleIconAttrs } =
102
+ useUI<Config>(defaultConfig, mutatedProps, "toggleButton");
211
103
  </script>
212
104
 
213
105
  <template>
214
- <div
215
- ref="wrapper"
216
- v-click-outside="handleClickOutside"
217
- v-bind="wrapperAttrs"
218
- :data-test="getDataTest('wrapper')"
106
+ <UDropdown
107
+ :id="id"
108
+ ref="dropdown"
109
+ :model-value="modelValue"
110
+ :label="label"
111
+ :label-display-count="labelDisplayCount"
112
+ :search="search"
113
+ :y-position="yPosition"
114
+ :x-position="xPosition"
115
+ :disabled="disabled"
116
+ :options="options"
117
+ :options-limit="optionsLimit"
118
+ :visible-options="visibleOptions"
119
+ :label-key="labelKey"
120
+ :value-key="valueKey"
121
+ :group-label-key="groupLabelKey"
122
+ :group-value-key="groupValueKey"
123
+ :searchable="searchable"
124
+ :multiple="multiple"
125
+ :color="color"
126
+ :close-on-select="closeOnSelect"
127
+ v-bind="dropdownButtonAttrs"
128
+ :data-test="dataTest"
129
+ @click-option="(option) => emit('clickOption', option)"
130
+ @update:model-value="(value) => emit('update:modelValue', value)"
131
+ @update:search="(value) => emit('update:search', value)"
132
+ @search-change="(query) => emit('searchChange', query)"
133
+ @open="emit('open')"
134
+ @close="emit('close')"
219
135
  >
220
- <UButton
221
- :id="elementId"
222
- :label="buttonLabel"
223
- :size="size"
224
- :color="color"
225
- :block="block"
226
- :round="round"
227
- :square="square"
228
- :variant="variant"
229
- :disabled="disabled"
230
- :title="getFullOptionLabels(selectedOptions)"
231
- v-bind="dropdownButtonAttrs"
232
- :data-test="getDataTest()"
233
- @click="onClickButton"
234
- >
235
- <template #left>
236
- <!--
237
- @slot Use it to add something before the label.
238
- @binding {boolean} opened
239
- -->
240
- <slot name="left" :opened="isShownOptions" />
241
- </template>
242
-
243
- <template #default>
244
- <!--
245
- @slot Use it to add something instead of the default label.
246
- @binding {string} label
247
- @binding {boolean} opened
248
- -->
249
- <slot :label="buttonLabel" :opened="isShownOptions" />
250
- </template>
251
-
252
- <template #right>
253
- <!--
254
- @slot Use it to add something instead of the toggle icon.
255
- @binding {boolean} opened
256
- -->
257
- <slot name="toggle" :opened="isShownOptions">
258
- <UIcon
259
- v-if="toggleIconName"
260
- color="inherit"
261
- :name="toggleIconName"
262
- v-bind="toggleIconAttrs"
263
- :data-test="getDataTest('dropdown')"
264
- />
265
- </slot>
266
- </template>
267
- </UButton>
268
-
269
- <UListbox
270
- v-if="isShownOptions"
271
- ref="dropdown-list"
272
- v-model="dropdownValue"
273
- v-model:search="dropdownSearch"
274
- :searchable="searchable"
275
- :multiple="multiple"
276
- :color="color"
277
- :options="options"
278
- :options-limit="optionsLimit"
279
- :visible-options="visibleOptions"
280
- :label-key="labelKey"
281
- :value-key="valueKey"
282
- :group-label-key="groupLabelKey"
283
- :group-value-key="groupValueKey"
284
- v-bind="listboxAttrs"
285
- :data-test="getDataTest('list')"
286
- @click-option="onClickOption"
287
- @search-change="onSearchChange"
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
136
+ <template #default="{ opened, displayLabel, fullLabel }">
137
+ <UButton
138
+ :label="displayLabel"
139
+ :size="size"
140
+ :color="color"
141
+ :block="block"
142
+ :round="round"
143
+ :square="square"
144
+ :variant="variant"
145
+ :disabled="disabled"
146
+ :title="fullLabel"
147
+ v-bind="toggleButtonAttrs"
148
+ tabindex="-1"
149
+ :data-test="getDataTest()"
150
+ >
151
+ <template #left>
152
+ <!--
153
+ @slot Use it to add something before the label.
154
+ @binding {boolean} opened
295
155
  -->
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
156
+ <slot name="left" :opened="opened" />
157
+ </template>
158
+
159
+ <template #default>
160
+ <!--
161
+ @slot Use it to add something instead of the default label.
162
+ @binding {string} label
163
+ @binding {boolean} opened
304
164
  -->
305
- <slot name="option" :option="option" :index="index" />
306
- </template>
165
+ <slot :label="displayLabel" :opened="opened" />
166
+ </template>
307
167
 
308
- <template #after-option="{ option, index }">
309
- <!--
310
- @slot Use it to add something after option.
311
- @binding {object} option
312
- @binding {number} index
168
+ <template #right>
169
+ <!--
170
+ @slot Use it to add something instead of the toggle icon.
171
+ @binding {boolean} opened
313
172
  -->
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>
322
- </div>
173
+ <slot name="toggle" :opened="opened">
174
+ <UIcon
175
+ v-if="toggleIconName"
176
+ color="inherit"
177
+ :name="toggleIconName"
178
+ v-bind="toggleIconAttrs"
179
+ :data-test="getDataTest('dropdown')"
180
+ />
181
+ </slot>
182
+ </template>
183
+ </UButton>
184
+ </template>
185
+
186
+ <template #before-option="{ option, index }">
187
+ <!--
188
+ @slot Use it to add something before option.
189
+ @binding {object} option
190
+ @binding {number} index
191
+ -->
192
+ <slot name="before-option" :option="option" :index="index" />
193
+ </template>
194
+
195
+ <template #option="{ option, index }">
196
+ <!--
197
+ @slot Use it to customize the option.
198
+ @binding {object} option
199
+ @binding {number} index
200
+ -->
201
+ <slot name="option" :option="option" :index="index" />
202
+ </template>
203
+
204
+ <template #after-option="{ option, index }">
205
+ <!--
206
+ @slot Use it to add something after option.
207
+ @binding {object} option
208
+ @binding {number} index
209
+ -->
210
+ <slot name="after-option" :option="option" :index="index" />
211
+ </template>
212
+
213
+ <template #empty>
214
+ <!-- @slot Use it to add something instead of empty state. -->
215
+ <slot name="empty" />
216
+ </template>
217
+ </UDropdown>
323
218
  </template>
@@ -1,49 +1,53 @@
1
1
  export default /*tw*/ {
2
- wrapper: {
3
- base: "relative inline-block h-max",
2
+ dropdownButton: {
3
+ base: "{UDropdown}",
4
4
  variants: {
5
5
  block: {
6
- true: "w-full",
6
+ true: "!block w-full",
7
7
  },
8
8
  },
9
- },
10
- dropdownButton: "{UButton} justify-between",
11
- toggleIcon: {
12
- base: "{UIcon} transition duration-300 -mr-1",
13
9
  defaults: {
14
10
  size: {
15
- "2xs": "2xs",
16
- xs: "xs",
17
- sm: "sm",
18
- md: "sm",
19
- lg: "sm",
20
- xl: "sm",
11
+ "2xs": "sm",
12
+ xs: "sm",
13
+ sm: "md",
14
+ md: "md",
15
+ lg: "lg",
16
+ xl: "lg",
21
17
  },
22
18
  },
23
- compoundVariants: [{ opened: true, class: "rotate-180" }],
24
19
  },
25
- listbox: {
26
- base: "{UListbox} w-fit",
20
+ toggleButton: {
21
+ base: "{UButton} justify-between",
27
22
  variants: {
28
- yPosition: {
29
- top: "bottom-full mb-1.5",
30
- bottom: "top-full mt-1.5",
23
+ block: {
24
+ true: "w-full",
31
25
  },
32
- xPosition: {
33
- left: "left-0",
34
- right: "right-0",
26
+ },
27
+ },
28
+ toggleIcon: {
29
+ base: "{UIcon} transition duration-300",
30
+ variants: {
31
+ size: {
32
+ "2xs": "-ml-0.5 -mr-1",
33
+ xs: "-ml-1 -mr-1",
34
+ sm: "-ml-1 -mr-1.5",
35
+ md: "-ml-1 -mr-2",
36
+ lg: "-ml-1.5 -mr-2.5",
37
+ xl: "-ml-1.5 -mr-2.5",
35
38
  },
36
39
  },
37
40
  defaults: {
38
41
  size: {
39
- "2xs": "sm",
40
- xs: "sm",
41
- sm: "md",
42
- md: "md",
43
- lg: "lg",
44
- xl: "lg",
42
+ "2xs": "2xs",
43
+ xs: "xs",
44
+ sm: "sm",
45
+ md: "sm",
46
+ lg: "md",
47
+ xl: "md",
45
48
  },
46
49
  },
50
+ compoundVariants: [{ opened: true, class: "rotate-180" }],
47
51
  },
48
52
  defaults: {
49
53
  color: "primary",
@@ -160,13 +160,20 @@ Default.parameters = {
160
160
 
161
161
  export const Disabled = DefaultTemplate.bind({});
162
162
  Disabled.args = { disabled: true };
163
+ Disabled.parameters = {
164
+ docs: {
165
+ story: {
166
+ height: "120px",
167
+ },
168
+ },
169
+ };
163
170
 
164
171
  export const Searchable = DefaultTemplate.bind({});
165
172
  Searchable.args = { searchable: true };
166
173
  Searchable.parameters = {
167
174
  docs: {
168
175
  story: {
169
- height: "250px",
176
+ height: "270px",
170
177
  },
171
178
  },
172
179
  };
@@ -176,7 +183,7 @@ SearchModelValue.args = { searchable: true, search: "Copy" };
176
183
  SearchModelValue.parameters = {
177
184
  docs: {
178
185
  story: {
179
- height: "250px",
186
+ height: "270px",
180
187
  },
181
188
  },
182
189
  };
@@ -398,7 +405,7 @@ export const OptionSlots: StoryFn<DefaultUDropdownButtonArgs> = (args) => ({
398
405
  ]"
399
406
  >
400
407
  <template #before-option="{ option }">
401
- <UAvatar :src="option.avatar" size="sm" />
408
+ <UAvatar :src="option.avatar" size="xs" />
402
409
  </template>
403
410
  </UDropdownButton>
404
411
 
@@ -441,7 +448,7 @@ export const OptionSlots: StoryFn<DefaultUDropdownButtonArgs> = (args) => ({
441
448
  ]"
442
449
  >
443
450
  <template #option="{ option }">
444
- <URow align="center" gap="xs">
451
+ <URow justify="between" align="center" gap="xs" block>
445
452
  <UCol gap="none">
446
453
  <UText size="sm">{{ option.label }}</UText>
447
454
  <UText variant="lifted" size="xs">{{ option.role }}</UText>
@@ -460,47 +467,13 @@ export const OptionSlots: StoryFn<DefaultUDropdownButtonArgs> = (args) => ({
460
467
  v-model="args.afterOptionModel"
461
468
  label="After option slot"
462
469
  :options="[
463
- {
464
- label: 'John Doe',
465
- value: '1',
466
- role: 'Developer',
467
- avatar: johnDoe,
468
- status: 'online',
469
- statusColor: 'success',
470
- },
471
- {
472
- label: 'Jane Smith',
473
- value: '2',
474
- role: 'Designer',
475
- avatar: emilyDavis,
476
- status: 'away',
477
- statusColor: 'warning',
478
- },
479
- {
480
- label: 'Mike Johnson',
481
- value: '3',
482
- role: 'Product Manager',
483
- avatar: alexJohnson,
484
- status: 'offline',
485
- statusColor: 'grayscale',
486
- },
487
- {
488
- label: 'Sarah Wilson',
489
- value: '4',
490
- role: 'QA Engineer',
491
- avatar: patMorgan,
492
- status: 'online',
493
- statusColor: 'success',
494
- },
470
+ { label: 'John Doe', value: '1', verified: true },
471
+ { label: 'Jane Smith', value: '2', verified: true },
472
+ { label: 'Mike Johnson', value: '3', verified: false },
495
473
  ]"
496
474
  >
497
475
  <template #after-option="{ option }">
498
- <UBadge
499
- :label="option.status"
500
- :color="option.statusColor"
501
- size="sm"
502
- variant="subtle"
503
- />
476
+ <UIcon v-if="option.verified" name="verified" size="xs" color="success" />
504
477
  </template>
505
478
  </UDropdownButton>
506
479
  </URow>