vueless 1.2.8-beta.1 → 1.2.8-beta.11
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 +2 -0
- package/index.ts +2 -0
- package/package.json +1 -1
- package/plugin-vite.js +6 -1
- package/types.ts +14 -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.dropdown-badge/UDropdownBadge.vue +51 -2
- 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 +51 -2
- 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 +51 -2
- 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-checkbox/tests/UCheckbox.test.ts +2 -2
- package/ui.form-checkbox-group/tests/UCheckboxGroup.test.ts +2 -2
- package/ui.form-input/UInput.vue +4 -2
- 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 -5
- 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/tests/URadio.test.ts +2 -2
- package/ui.form-radio-group/tests/URadioGroup.test.ts +2 -2
- package/ui.form-select/USelect.vue +19 -2
- package/ui.form-select/config.ts +1 -0
- 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/tests/UTextarea.test.ts +2 -2
- 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 +2 -2
- package/utils/theme.ts +61 -5
- package/utils/ui.ts +32 -3
|
@@ -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
|
|
@@ -36,6 +36,26 @@ export interface Props {
|
|
|
36
36
|
*/
|
|
37
37
|
valueKey?: string;
|
|
38
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
|
+
|
|
39
59
|
/**
|
|
40
60
|
* Badge variant.
|
|
41
61
|
*/
|
|
@@ -70,6 +90,16 @@ export interface Props {
|
|
|
70
90
|
*/
|
|
71
91
|
searchable?: boolean;
|
|
72
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Search input model value for the dropdown list.
|
|
95
|
+
*/
|
|
96
|
+
search?: string;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Close dropdown on option select.
|
|
100
|
+
*/
|
|
101
|
+
closeOnSelect?: boolean;
|
|
102
|
+
|
|
73
103
|
/**
|
|
74
104
|
* Allows multiple selection.
|
|
75
105
|
*/
|
|
@@ -55,6 +55,12 @@ 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
|
provide("hideDropdownOptions", hideOptions);
|
|
@@ -78,6 +84,11 @@ const dropdownValue = computed({
|
|
|
78
84
|
set: (value) => emit("update:modelValue", value),
|
|
79
85
|
});
|
|
80
86
|
|
|
87
|
+
const dropdownSearch = computed({
|
|
88
|
+
get: () => props.search ?? "",
|
|
89
|
+
set: (value: string) => emit("update:search", value),
|
|
90
|
+
});
|
|
91
|
+
|
|
81
92
|
const selectedOptions = computed(() => {
|
|
82
93
|
if (props.multiple) {
|
|
83
94
|
return props.options.filter((option) => {
|
|
@@ -139,7 +150,7 @@ function getFullOptionLabels(value: Option | Option[]) {
|
|
|
139
150
|
function onClickOption(option: Option) {
|
|
140
151
|
emit("clickOption", option);
|
|
141
152
|
|
|
142
|
-
if (!props.multiple) hideOptions();
|
|
153
|
+
if (!props.multiple && props.closeOnSelect) hideOptions();
|
|
143
154
|
}
|
|
144
155
|
|
|
145
156
|
function onClickButton() {
|
|
@@ -245,16 +256,54 @@ const { getDataTest, config, dropdownButtonAttrs, listboxAttrs, toggleIconAttrs,
|
|
|
245
256
|
v-if="isShownOptions"
|
|
246
257
|
ref="dropdown-list"
|
|
247
258
|
v-model="dropdownValue"
|
|
259
|
+
v-model:search="dropdownSearch"
|
|
248
260
|
:searchable="searchable"
|
|
249
261
|
:multiple="multiple"
|
|
250
262
|
:color="color"
|
|
251
263
|
:options="options"
|
|
264
|
+
:options-limit="optionsLimit"
|
|
265
|
+
:visible-options="visibleOptions"
|
|
252
266
|
:label-key="labelKey"
|
|
253
267
|
:value-key="valueKey"
|
|
268
|
+
:group-label-key="groupLabelKey"
|
|
269
|
+
:group-value-key="groupValueKey"
|
|
254
270
|
v-bind="listboxAttrs"
|
|
255
271
|
:data-test="getDataTest('list')"
|
|
256
272
|
@click-option="onClickOption"
|
|
257
273
|
@search-change="onSearchChange"
|
|
258
|
-
|
|
274
|
+
@update:search="(value) => emit('update:search', value)"
|
|
275
|
+
>
|
|
276
|
+
<template #before-option="{ option, index }">
|
|
277
|
+
<!--
|
|
278
|
+
@slot Use it to add something before option.
|
|
279
|
+
@binding {object} option
|
|
280
|
+
@binding {number} index
|
|
281
|
+
-->
|
|
282
|
+
<slot name="before-option" :option="option" :index="index" />
|
|
283
|
+
</template>
|
|
284
|
+
|
|
285
|
+
<template #option="{ option, index }">
|
|
286
|
+
<!--
|
|
287
|
+
@slot Use it to customize the option.
|
|
288
|
+
@binding {object} option
|
|
289
|
+
@binding {number} index
|
|
290
|
+
-->
|
|
291
|
+
<slot name="option" :option="option" :index="index" />
|
|
292
|
+
</template>
|
|
293
|
+
|
|
294
|
+
<template #after-option="{ option, index }">
|
|
295
|
+
<!--
|
|
296
|
+
@slot Use it to add something after option.
|
|
297
|
+
@binding {object} option
|
|
298
|
+
@binding {number} index
|
|
299
|
+
-->
|
|
300
|
+
<slot name="after-option" :option="option" :index="index" />
|
|
301
|
+
</template>
|
|
302
|
+
|
|
303
|
+
<template #empty>
|
|
304
|
+
<!-- @slot Use it to add something instead of empty state. -->
|
|
305
|
+
<slot name="empty" />
|
|
306
|
+
</template>
|
|
307
|
+
</UListbox>
|
|
259
308
|
</div>
|
|
260
309
|
</template>
|
|
@@ -51,15 +51,19 @@ export default /*tw*/ {
|
|
|
51
51
|
variant: "solid",
|
|
52
52
|
labelKey: "label",
|
|
53
53
|
valueKey: "id",
|
|
54
|
+
groupLabelKey: "label",
|
|
54
55
|
yPosition: "bottom",
|
|
55
56
|
xPosition: "left",
|
|
57
|
+
optionsLimit: 0,
|
|
58
|
+
visibleOptions: 8,
|
|
59
|
+
labelDisplayCount: 2,
|
|
56
60
|
searchable: false,
|
|
57
61
|
round: false,
|
|
58
62
|
block: false,
|
|
59
63
|
square: false,
|
|
60
64
|
disabled: false,
|
|
61
65
|
multiple: false,
|
|
62
|
-
|
|
66
|
+
closeOnSelect: true,
|
|
63
67
|
/* icons */
|
|
64
68
|
toggleIcon: "keyboard_arrow_down",
|
|
65
69
|
},
|