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