vueless 1.3.7-beta.2 → 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 (32) 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
@@ -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 ULink from "../ui.button-link/ULink.vue";
10
- import ULisbox 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 { COMPONENT_NAME } from "./constants";
15
12
  import defaultConfig from "./config";
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 ULisboxRef = InstanceType<typeof ULisbox>;
67
-
68
- const isShownOptions = ref(false);
69
- const isClickingOption = ref(false);
70
- const listboxRef = useTemplateRef<ULisboxRef>("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 linkLabel = computed(() => {
111
- if (!props.labelDisplayCount || !selectedOptions.value.length) {
112
- return props.label;
113
- }
114
-
115
- const selectedLabels = selectedOptions.value
116
- .slice(0, props.labelDisplayCount)
117
- .map((option) => option[props.labelKey]);
118
- const restLabelCount = selectedOptions.value.length - props.labelDisplayCount;
62
+ type UDropdownRef = InstanceType<typeof UDropdown>;
119
63
 
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,197 +71,148 @@ 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 onClickLink() {
150
- if (props.disabled) return;
151
-
152
- isShownOptions.value = !isShownOptions.value;
153
-
154
- if (isShownOptions.value) {
155
- nextTick(() => listboxRef.value?.wrapperRef?.focus());
156
-
157
- emit("open");
158
- }
159
- }
160
-
161
- function hideOptions() {
162
- isShownOptions.value = false;
163
- dropdownSearch.value = "";
164
-
165
- emit("close");
166
- }
167
-
168
- function onClickOption(option: Option) {
169
- isClickingOption.value = true;
170
-
171
- emit("clickOption", option);
172
-
173
- if (!props.multiple && props.closeOnSelect) hideOptions();
174
-
175
- nextTick(() => {
176
- setTimeout(() => {
177
- isClickingOption.value = false;
178
- }, 10);
179
- });
180
- }
181
-
182
- function handleClickOutside() {
183
- if (isClickingOption.value) return;
184
-
185
- hideOptions();
74
+ function hide() {
75
+ dropdownRef.value?.hide();
186
76
  }
187
77
 
188
78
  defineExpose({
189
79
  /**
190
- * A reference to the component's wrapper element for direct DOM manipulation.
80
+ * A reference to the dropdown wrapper element for direct DOM manipulation.
191
81
  * @property {HTMLDivElement}
192
82
  */
193
- wrapperRef,
83
+ wrapperRef: computed(() => dropdownRef.value?.wrapperRef),
194
84
 
195
85
  /**
196
- * Hides the dropdown options.
86
+ * Hides the dropdown.
197
87
  * @property {function}
198
88
  */
199
- hideOptions,
89
+ hide,
200
90
  });
201
91
 
202
- /**
203
- * Get element / nested component attributes for each config token ✨
92
+ /*
93
+ * Vueless: Get element / nested component attributes for each config token ✨
204
94
  * Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
205
95
  */
206
96
  const mutatedProps = computed(() => ({
207
97
  /* component state, not a props */
208
- opened: isShownOptions.value,
98
+ opened: dropdownRef.value?.isOpened ?? false,
209
99
  }));
210
100
 
211
- const { config, getDataTest, wrapperAttrs, dropdownLinkAttrs, listboxAttrs, toggleIconAttrs } =
212
- useUI<Config>(defaultConfig, mutatedProps);
101
+ const { config, getDataTest, dropdownLinkAttrs, toggleLinkAttrs, toggleIconAttrs } = useUI<Config>(
102
+ defaultConfig,
103
+ mutatedProps,
104
+ "toggleLink",
105
+ );
213
106
  </script>
214
107
 
215
108
  <template>
216
- <div
217
- ref="wrapper"
218
- v-click-outside="handleClickOutside"
219
- tabindex="1"
220
- v-bind="wrapperAttrs"
221
- :data-test="getDataTest('wrapper')"
222
- @keydown.enter="onClickLink"
223
- @keydown.space.prevent="onClickLink"
109
+ <UDropdown
110
+ :id="id"
111
+ ref="dropdown"
112
+ :model-value="modelValue"
113
+ :label="label"
114
+ :label-display-count="labelDisplayCount"
115
+ :search="search"
116
+ :y-position="yPosition"
117
+ :x-position="xPosition"
118
+ :disabled="disabled"
119
+ :options="options"
120
+ :options-limit="optionsLimit"
121
+ :visible-options="visibleOptions"
122
+ :label-key="labelKey"
123
+ :value-key="valueKey"
124
+ :group-label-key="groupLabelKey"
125
+ :group-value-key="groupValueKey"
126
+ :searchable="searchable"
127
+ :multiple="multiple"
128
+ :color="color"
129
+ :size="size"
130
+ :close-on-select="closeOnSelect"
131
+ v-bind="dropdownLinkAttrs"
132
+ :data-test="dataTest"
133
+ @click-option="(option) => emit('clickOption', option)"
134
+ @update:model-value="(value) => emit('update:modelValue', value)"
135
+ @update:search="(value) => emit('update:search', value)"
136
+ @search-change="(query) => emit('searchChange', query)"
137
+ @open="emit('open')"
138
+ @close="emit('close')"
224
139
  >
225
- <!--
226
- @slot Use it to add something before the label.
227
- @binding {boolean} opened
228
- -->
229
- <slot name="left" :opened="isShownOptions" />
230
-
231
- <ULink
232
- :id="elementId"
233
- tabindex="-1"
234
- :size="size"
235
- :label="linkLabel"
236
- :color="color"
237
- :dashed="dashed"
238
- :disabled="disabled"
239
- :underlined="underlined"
240
- :title="getFullOptionLabels(selectedOptions)"
241
- v-bind="dropdownLinkAttrs"
242
- :data-test="getDataTest()"
243
- @click="onClickLink"
244
- >
245
- <template #default>
246
- <!--
247
- @slot Use it to add something instead of the default label.
248
- @binding {string} label
249
- @binding {boolean} opened
250
- -->
251
- <slot :label="linkLabel" :opened="isShownOptions" />
252
- </template>
253
- </ULink>
254
-
255
- <!--
256
- @slot Use it to add something instead of the toggle icon.
257
- @binding {boolean} opened
258
- @binding {function} toggle
259
- -->
260
- <slot name="toggle" :opened="isShownOptions" :toggle="onClickLink">
261
- <UIcon
262
- v-if="toggleIconName"
263
- interactive
140
+ <template #default="{ opened, displayLabel, fullLabel }">
141
+ <!--
142
+ @slot Use it to add something before the label.
143
+ @binding {boolean} opened
144
+ -->
145
+ <slot name="left" :opened="opened" />
146
+
147
+ <ULink
148
+ :size="size"
149
+ :label="displayLabel"
264
150
  :color="color"
151
+ :dashed="dashed"
265
152
  :disabled="disabled"
266
- :name="toggleIconName"
267
- v-bind="toggleIconAttrs"
268
- :data-test="getDataTest('toggle')"
269
- @click="onClickLink"
270
- />
271
- </slot>
272
-
273
- <ULisbox
274
- v-if="isShownOptions"
275
- ref="dropdown-list"
276
- v-model="dropdownValue"
277
- v-model:search="dropdownSearch"
278
- :searchable="searchable"
279
- :multiple="multiple"
280
- :size="size"
281
- :color="color"
282
- :options="options"
283
- :options-limit="optionsLimit"
284
- :visible-options="visibleOptions"
285
- :label-key="labelKey"
286
- :value-key="valueKey"
287
- :group-label-key="groupLabelKey"
288
- :group-value-key="groupValueKey"
289
- v-bind="listboxAttrs"
290
- :data-test="getDataTest('list')"
291
- @click-option="onClickOption"
292
- @search-change="onSearchChange"
293
- @update:search="(value) => emit('update:search', value)"
294
- >
295
- <template #before-option="{ option, index }">
296
- <!--
297
- @slot Use it to add something before option.
298
- @binding {object} option
299
- @binding {number} index
300
- -->
301
- <slot name="before-option" :option="option" :index="index" />
302
- </template>
303
-
304
- <template #option="{ option, index }">
305
- <!--
306
- @slot Use it to customize the option.
307
- @binding {object} option
308
- @binding {number} index
153
+ :underlined="underlined"
154
+ :title="fullLabel"
155
+ v-bind="toggleLinkAttrs"
156
+ tabindex="-1"
157
+ :data-test="getDataTest()"
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
309
164
  -->
310
- <slot name="option" :option="option" :index="index" />
311
- </template>
312
-
313
- <template #after-option="{ option, index }">
314
- <!--
315
- @slot Use it to add something after option.
316
- @binding {object} option
317
- @binding {number} index
318
- -->
319
- <slot name="after-option" :option="option" :index="index" />
320
- </template>
321
-
322
- <template #empty>
323
- <!-- @slot Use it to add something instead of empty state. -->
324
- <slot name="empty" />
325
- </template>
326
- </ULisbox>
327
- </div>
165
+ <slot :label="displayLabel" :opened="opened" />
166
+ </template>
167
+ </ULink>
168
+
169
+ <!--
170
+ @slot Use it to add something instead of the toggle icon.
171
+ @binding {boolean} opened
172
+ -->
173
+ <slot name="toggle" :opened="opened">
174
+ <UIcon
175
+ v-if="toggleIconName"
176
+ interactive
177
+ :color="color"
178
+ :disabled="disabled"
179
+ :name="toggleIconName"
180
+ v-bind="toggleIconAttrs"
181
+ :data-test="getDataTest('toggle')"
182
+ />
183
+ </slot>
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>
328
218
  </template>
@@ -1,9 +1,19 @@
1
1
  export default /*tw*/ {
2
- wrapper: `
3
- inline-flex gap-0.5 relative items-center justify-between rounded h-max
4
- focus-visible:outline focus-visible:outline-medium focus-visible:outline-offset-4 focus-visible:outline-{color}
5
- `,
6
- dropdownLink: "{ULink} focus-visible:outline-hidden",
2
+ dropdownLink: {
3
+ base: `
4
+ {UDropdown}
5
+ inline-flex gap-0.5 items-center justify-between rounded-small h-max
6
+ focus-visible:outline-offset-4
7
+ `,
8
+ defaults: {
9
+ size: {
10
+ sm: "sm",
11
+ md: "md",
12
+ lg: "lg",
13
+ },
14
+ },
15
+ },
16
+ toggleLink: "{ULink} focus-visible:outline-hidden",
7
17
  toggleIcon: {
8
18
  base: "{UIcon} block transition duration-300",
9
19
  defaults: {
@@ -15,19 +25,6 @@ export default /*tw*/ {
15
25
  },
16
26
  compoundVariants: [{ opened: true, class: "rotate-180" }],
17
27
  },
18
- listbox: {
19
- base: "{UListbox} w-fit",
20
- variants: {
21
- yPosition: {
22
- top: "bottom-full mb-2",
23
- bottom: "top-full mt-2",
24
- },
25
- xPosition: {
26
- left: "left-0",
27
- right: "right-0",
28
- },
29
- },
30
- },
31
28
  defaults: {
32
29
  color: "primary",
33
30
  size: "md",