vueless 1.3.7-beta.1 → 1.3.7-beta.3

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 (33) 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/package.json +2 -2
  6. package/ui.container-col/UCol.vue +0 -1
  7. package/ui.container-collapsible/UCollapsible.vue +190 -0
  8. package/ui.container-collapsible/config.ts +45 -0
  9. package/ui.container-collapsible/constants.ts +1 -0
  10. package/ui.container-collapsible/storybook/docs.mdx +17 -0
  11. package/ui.container-collapsible/storybook/stories.ts +261 -0
  12. package/ui.container-collapsible/tests/UCollapsible.test.ts +571 -0
  13. package/ui.container-collapsible/types.ts +57 -0
  14. package/ui.dropdown/UDropdown.vue +324 -0
  15. package/ui.dropdown/config.ts +27 -0
  16. package/ui.dropdown/constants.ts +1 -0
  17. package/ui.dropdown/storybook/docs.mdx +17 -0
  18. package/ui.dropdown/storybook/stories.ts +286 -0
  19. package/ui.dropdown/tests/UDropdown.test.ts +631 -0
  20. package/ui.dropdown/types.ts +127 -0
  21. package/ui.dropdown-badge/UDropdownBadge.vue +119 -227
  22. package/ui.dropdown-badge/config.ts +18 -15
  23. package/ui.dropdown-badge/tests/UDropdownBadge.test.ts +201 -67
  24. package/ui.dropdown-button/UDropdownButton.vue +121 -226
  25. package/ui.dropdown-button/config.ts +32 -28
  26. package/ui.dropdown-button/tests/UDropdownButton.test.ts +189 -73
  27. package/ui.dropdown-link/UDropdownLink.vue +123 -233
  28. package/ui.dropdown-link/config.ts +15 -18
  29. package/ui.dropdown-link/tests/UDropdownLink.test.ts +190 -71
  30. package/ui.form-listbox/UListbox.vue +2 -3
  31. package/ui.form-listbox/config.ts +2 -2
  32. package/ui.form-select/config.ts +1 -1
  33. package/utils/node/helper.js +6 -5
@@ -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",