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
@@ -0,0 +1,127 @@
1
+ import defaultConfig from "./config";
2
+
3
+ import type { Option } from "../ui.form-listbox/types";
4
+ import type { ComponentConfig, UnknownObject } from "../types";
5
+
6
+ export type Config = typeof defaultConfig;
7
+
8
+ export interface Props {
9
+ /**
10
+ * Selected dropdown value.
11
+ */
12
+ modelValue?: string | number | UnknownObject | (string | number | UnknownObject)[];
13
+
14
+ /**
15
+ * Dropdown label.
16
+ */
17
+ label?: string;
18
+
19
+ /**
20
+ * Determines how many selected option labels are shown in the label.
21
+ */
22
+ labelDisplayCount?: number;
23
+
24
+ /**
25
+ * Options list.
26
+ */
27
+ options?: Option[];
28
+
29
+ /**
30
+ * Label key in the item object of options.
31
+ */
32
+ labelKey?: string;
33
+
34
+ /**
35
+ * Value key in the item object of options.
36
+ */
37
+ valueKey?: string;
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
+
59
+ /**
60
+ * Dropdown color.
61
+ */
62
+ color?:
63
+ | "primary"
64
+ | "secondary"
65
+ | "error"
66
+ | "warning"
67
+ | "success"
68
+ | "info"
69
+ | "notice"
70
+ | "neutral"
71
+ | "grayscale";
72
+
73
+ /**
74
+ * Dropdown size.
75
+ */
76
+ size?: "sm" | "md" | "lg";
77
+
78
+ /**
79
+ * Shows input to search value in a list.
80
+ */
81
+ searchable?: boolean;
82
+
83
+ /**
84
+ * Search input model value for the dropdown list.
85
+ */
86
+ search?: string;
87
+
88
+ /**
89
+ * Close dropdown on option select.
90
+ */
91
+ closeOnSelect?: boolean;
92
+
93
+ /**
94
+ * Allows multiple selection.
95
+ */
96
+ multiple?: boolean;
97
+
98
+ /**
99
+ * Disable the dropdown.
100
+ */
101
+ disabled?: boolean;
102
+
103
+ /**
104
+ * The position of a dropdown list on the x-axis.
105
+ */
106
+ xPosition?: "left" | "right";
107
+
108
+ /**
109
+ * The position of a dropdown list on the y-axis.
110
+ */
111
+ yPosition?: "top" | "bottom";
112
+
113
+ /**
114
+ * Unique element id.
115
+ */
116
+ id?: string;
117
+
118
+ /**
119
+ * Component config object.
120
+ */
121
+ config?: ComponentConfig<Config>;
122
+
123
+ /**
124
+ * Data-test attribute for automated testing.
125
+ */
126
+ dataTest?: string | null;
127
+ }
@@ -1,21 +1,17 @@
1
1
  <script setup lang="ts">
2
- import { ref, computed, nextTick, 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 UBadge from "../ui.text-badge/UBadge.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 badgeLabel = 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 getFullOptionLabels(value: Option | Option[]) {
136
- const labelKey = props.labelKey;
137
-
138
- if (Array.isArray(value)) {
139
- return value.map((item) => item[labelKey]).join(", ");
140
- }
141
-
142
- return "";
143
- }
144
-
145
- function onSearchChange(query: string) {
146
- emit("searchChange", query);
147
- }
148
-
149
- function onClickBadge() {
150
- isShownOptions.value = !isShownOptions.value;
151
-
152
- if (isShownOptions.value) {
153
- nextTick(() => listboxRef.value?.wrapperRef?.focus());
154
-
155
- emit("open");
156
- }
157
- }
158
-
159
- function hideOptions() {
160
- isShownOptions.value = false;
161
- dropdownSearch.value = "";
162
-
163
- emit("close");
164
- }
165
-
166
- function onClickOption(option: Option) {
167
- isClickingOption.value = true;
168
-
169
- emit("clickOption", option);
170
-
171
- if (!props.multiple && props.closeOnSelect) hideOptions();
172
-
173
- nextTick(() => {
174
- setTimeout(() => {
175
- isClickingOption.value = false;
176
- }, 10);
177
- });
178
- }
179
-
180
- function handleClickOutside() {
181
- if (isClickingOption.value) return;
182
-
183
- hideOptions();
74
+ function hide() {
75
+ dropdownRef.value?.hide();
184
76
  }
185
77
 
186
78
  defineExpose({
@@ -188,137 +80,137 @@ 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, wrapperAttrs, dropdownBadgeAttrs, listboxAttrs, toggleIconAttrs } =
210
- useUI<Config>(defaultConfig, mutatedProps, "dropdownBadge");
101
+ const { getDataTest, config, toggleBadgeAttrs, dropdownBadgeAttrs, toggleIconAttrs } =
102
+ useUI<Config>(defaultConfig, mutatedProps, "toggleBadge");
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
+ :size="size"
127
+ :close-on-select="closeOnSelect"
128
+ v-bind="dropdownBadgeAttrs"
129
+ :data-test="dataTest"
130
+ @click-option="(option) => emit('clickOption', option)"
131
+ @update:model-value="(value) => emit('update:modelValue', value)"
132
+ @update:search="(value) => emit('update:search', value)"
133
+ @search-change="(query) => emit('searchChange', query)"
134
+ @open="emit('open')"
135
+ @close="emit('close')"
219
136
  >
220
- <UBadge
221
- :id="elementId"
222
- :label="badgeLabel"
223
- :size="size"
224
- :color="color"
225
- :variant="variant"
226
- :round="round"
227
- :title="getFullOptionLabels(selectedOptions)"
228
- v-bind="dropdownBadgeAttrs"
229
- tabindex="0"
230
- :data-test="getDataTest()"
231
- @click="onClickBadge"
232
- @keydown.enter="onClickBadge"
233
- @keydown.space.prevent="onClickBadge"
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="badgeLabel" :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
- </UBadge>
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
- :size="size"
277
- :color="color"
278
- :options="options"
279
- :options-limit="optionsLimit"
280
- :visible-options="visibleOptions"
281
- :label-key="labelKey"
282
- :value-key="valueKey"
283
- :group-label-key="groupLabelKey"
284
- :group-value-key="groupValueKey"
285
- v-bind="listboxAttrs"
286
- :data-test="getDataTest('list')"
287
- @click-option="onClickOption"
288
- @search-change="onSearchChange"
289
- @update:search="(value) => emit('update:search', value)"
290
- >
291
- <template #before-option="{ option, index }">
292
- <!--
293
- @slot Use it to add something before option.
294
- @binding {object} option
295
- @binding {number} index
137
+ <template #default="{ opened, displayLabel, fullLabel }">
138
+ <UBadge
139
+ :label="displayLabel"
140
+ :size="size"
141
+ :color="color"
142
+ :variant="variant"
143
+ :round="round"
144
+ :title="fullLabel"
145
+ v-bind="toggleBadgeAttrs"
146
+ tabindex="-1"
147
+ :data-test="getDataTest()"
148
+ >
149
+ <template #left>
150
+ <!--
151
+ @slot Use it to add something before the label.
152
+ @binding {boolean} opened
296
153
  -->
297
- <slot name="before-option" :option="option" :index="index" />
298
- </template>
299
-
300
- <template #option="{ option, index }">
301
- <!--
302
- @slot Use it to customize the option.
303
- @binding {object} option
304
- @binding {number} index
154
+ <slot name="left" :opened="opened" />
155
+ </template>
156
+
157
+ <template #default>
158
+ <!--
159
+ @slot Use it to add something instead of the default label.
160
+ @binding {string} label
161
+ @binding {boolean} opened
305
162
  -->
306
- <slot name="option" :option="option" :index="index" />
307
- </template>
163
+ <slot :label="displayLabel" :opened="opened" />
164
+ </template>
308
165
 
309
- <template #after-option="{ option, index }">
310
- <!--
311
- @slot Use it to add something after option.
312
- @binding {object} option
313
- @binding {number} index
166
+ <template #right>
167
+ <!--
168
+ @slot Use it to add something instead of the toggle icon.
169
+ @binding {boolean} opened
314
170
  -->
315
- <slot name="after-option" :option="option" :index="index" />
316
- </template>
317
-
318
- <template #empty>
319
- <!-- @slot Use it to add something instead of empty state. -->
320
- <slot name="empty" />
321
- </template>
322
- </UListbox>
323
- </div>
171
+ <slot name="toggle" :opened="opened">
172
+ <UIcon
173
+ v-if="toggleIconName"
174
+ color="inherit"
175
+ :name="toggleIconName"
176
+ v-bind="toggleIconAttrs"
177
+ :data-test="getDataTest('dropdown')"
178
+ />
179
+ </slot>
180
+ </template>
181
+ </UBadge>
182
+ </template>
183
+
184
+ <template #before-option="{ option, index }">
185
+ <!--
186
+ @slot Use it to add something before option.
187
+ @binding {object} option
188
+ @binding {number} index
189
+ -->
190
+ <slot name="before-option" :option="option" :index="index" />
191
+ </template>
192
+
193
+ <template #option="{ option, index }">
194
+ <!--
195
+ @slot Use it to customize the option.
196
+ @binding {object} option
197
+ @binding {number} index
198
+ -->
199
+ <slot name="option" :option="option" :index="index" />
200
+ </template>
201
+
202
+ <template #after-option="{ option, index }">
203
+ <!--
204
+ @slot Use it to add something after option.
205
+ @binding {object} option
206
+ @binding {number} index
207
+ -->
208
+ <slot name="after-option" :option="option" :index="index" />
209
+ </template>
210
+
211
+ <template #empty>
212
+ <!-- @slot Use it to add something instead of empty state. -->
213
+ <slot name="empty" />
214
+ </template>
215
+ </UDropdown>
324
216
  </template>
@@ -1,6 +1,15 @@
1
1
  export default /*tw*/ {
2
- wrapper: "relative inline-block h-max",
3
2
  dropdownBadge: {
3
+ base: "{UDropdown}",
4
+ defaults: {
5
+ size: {
6
+ sm: "sm",
7
+ md: "md",
8
+ lg: "lg",
9
+ },
10
+ },
11
+ },
12
+ toggleBadge: {
4
13
  base: "{UBadge}",
5
14
  variants: {
6
15
  disabled: {
@@ -9,7 +18,14 @@ export default /*tw*/ {
9
18
  },
10
19
  },
11
20
  toggleIcon: {
12
- base: "{UIcon} transition duration-300 -mr-0.5",
21
+ base: "{UIcon} transition duration-300",
22
+ variants: {
23
+ size: {
24
+ sm: "-ml-0.5 -mr-0.5",
25
+ md: "-ml-1 -mr-1",
26
+ lg: "-ml-1 -mr-1.5",
27
+ },
28
+ },
13
29
  defaults: {
14
30
  size: {
15
31
  sm: "2xs",
@@ -19,19 +35,6 @@ export default /*tw*/ {
19
35
  },
20
36
  compoundVariants: [{ opened: true, class: "rotate-180" }],
21
37
  },
22
- listbox: {
23
- base: "{UListbox} w-fit",
24
- variants: {
25
- yPosition: {
26
- top: "bottom-full mb-1.5",
27
- bottom: "top-full mt-1.5",
28
- },
29
- xPosition: {
30
- left: "left-0",
31
- right: "right-0",
32
- },
33
- },
34
- },
35
38
  defaults: {
36
39
  color: "primary",
37
40
  size: "md",
@@ -158,6 +158,13 @@ Default.parameters = {
158
158
 
159
159
  export const Disabled = DefaultTemplate.bind({});
160
160
  Disabled.args = { disabled: true };
161
+ Disabled.parameters = {
162
+ docs: {
163
+ story: {
164
+ height: "120px",
165
+ },
166
+ },
167
+ };
161
168
 
162
169
  export const Searchable = DefaultTemplate.bind({});
163
170
  Searchable.args = { searchable: true };
@@ -378,7 +385,7 @@ export const OptionSlots: StoryFn<DefaultUDropdownBadgeArgs> = (args) => ({
378
385
  ]"
379
386
  >
380
387
  <template #before-option="{ option }">
381
- <UAvatar :src="option.avatar" size="sm" />
388
+ <UAvatar :src="option.avatar" size="xs" />
382
389
  </template>
383
390
  </UDropdownBadge>
384
391
 
@@ -421,7 +428,7 @@ export const OptionSlots: StoryFn<DefaultUDropdownBadgeArgs> = (args) => ({
421
428
  ]"
422
429
  >
423
430
  <template #option="{ option }">
424
- <URow align="center" gap="xs">
431
+ <URow justify="between" align="center" gap="xs" block>
425
432
  <UCol gap="none">
426
433
  <UText size="sm">{{ option.label }}</UText>
427
434
  <UText variant="lifted" size="xs">{{ option.role }}</UText>
@@ -440,47 +447,13 @@ export const OptionSlots: StoryFn<DefaultUDropdownBadgeArgs> = (args) => ({
440
447
  v-model="args.afterOptionModel"
441
448
  label="After option slot"
442
449
  :options="[
443
- {
444
- label: 'John Doe',
445
- value: '1',
446
- role: 'Developer',
447
- avatar: johnDoe,
448
- status: 'online',
449
- statusColor: 'success',
450
- },
451
- {
452
- label: 'Jane Smith',
453
- value: '2',
454
- role: 'Designer',
455
- avatar: emilyDavis,
456
- status: 'away',
457
- statusColor: 'warning',
458
- },
459
- {
460
- label: 'Mike Johnson',
461
- value: '3',
462
- role: 'Product Manager',
463
- avatar: alexJohnson,
464
- status: 'offline',
465
- statusColor: 'grayscale',
466
- },
467
- {
468
- label: 'Sarah Wilson',
469
- value: '4',
470
- role: 'QA Engineer',
471
- avatar: patMorgan,
472
- status: 'online',
473
- statusColor: 'success',
474
- },
450
+ { label: 'John Doe', value: '1', verified: true },
451
+ { label: 'Jane Smith', value: '2', verified: true },
452
+ { label: 'Mike Johnson', value: '3', verified: false },
475
453
  ]"
476
454
  >
477
455
  <template #after-option="{ option }">
478
- <UBadge
479
- :label="option.status"
480
- :color="option.statusColor"
481
- size="sm"
482
- variant="subtle"
483
- />
456
+ <UIcon v-if="option.verified" name="verified" size="xs" color="success" />
484
457
  </template>
485
458
  </UDropdownBadge>
486
459
  </URow>