vueless 1.2.8-beta.1 → 1.2.8-beta.10
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 +3 -0
- package/constants.js +3 -0
- package/icons/storybook/rocket_launch.svg +1 -0
- package/package.json +1 -1
- 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/theme.ts +61 -5
|
@@ -12,10 +12,18 @@ import UCol from "../../ui.container-col/UCol.vue";
|
|
|
12
12
|
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
|
+
import UBadge from "../../ui.text-badge/UBadge.vue";
|
|
16
|
+
import UText from "../../ui.text-block/UText.vue";
|
|
17
|
+
import ULoader from "../../ui.loader/ULoader.vue";
|
|
15
18
|
|
|
16
19
|
import type { Meta, StoryFn } from "@storybook/vue3-vite";
|
|
17
20
|
import type { Props } from "../types";
|
|
18
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
|
+
|
|
19
27
|
interface DefaultUDropdownButtonArgs extends Props {
|
|
20
28
|
slotTemplate?: string;
|
|
21
29
|
}
|
|
@@ -54,7 +62,7 @@ export default {
|
|
|
54
62
|
const DefaultTemplate: StoryFn<DefaultUDropdownButtonArgs> = (
|
|
55
63
|
args: DefaultUDropdownButtonArgs,
|
|
56
64
|
) => ({
|
|
57
|
-
components: { UDropdownButton, UIcon, ULink, UAvatar },
|
|
65
|
+
components: { UDropdownButton, UIcon, ULink, UAvatar, ULoader, URow, UText },
|
|
58
66
|
setup: () => ({ args, slots: getSlotNames(UDropdownButton.__name) }),
|
|
59
67
|
template: `
|
|
60
68
|
<UDropdownButton v-bind="args">
|
|
@@ -111,6 +119,35 @@ const MultiEnumTemplate: StoryFn<EnumUDropdownButtonArgs> = (
|
|
|
111
119
|
`,
|
|
112
120
|
});
|
|
113
121
|
|
|
122
|
+
const GroupValuesTemplate: StoryFn<DefaultUDropdownButtonArgs> = (
|
|
123
|
+
args: DefaultUDropdownButtonArgs,
|
|
124
|
+
) => ({
|
|
125
|
+
components: { UDropdownButton },
|
|
126
|
+
setup() {
|
|
127
|
+
return {
|
|
128
|
+
args,
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
template: `
|
|
132
|
+
<UDropdownButton
|
|
133
|
+
v-bind="args"
|
|
134
|
+
v-model="args.modelValue"
|
|
135
|
+
label="Single"
|
|
136
|
+
:config="{ listbox: 'min-w-[200px]' }"
|
|
137
|
+
class="max-w-96 mr-20"
|
|
138
|
+
/>
|
|
139
|
+
|
|
140
|
+
<UDropdownButton
|
|
141
|
+
v-bind="args"
|
|
142
|
+
v-model="args.modelValueMultiple"
|
|
143
|
+
label="Multiple"
|
|
144
|
+
multiple
|
|
145
|
+
:config="{ listbox: 'min-w-[200px]' }"
|
|
146
|
+
class="mt-5 max-w-96"
|
|
147
|
+
/>
|
|
148
|
+
`,
|
|
149
|
+
});
|
|
150
|
+
|
|
114
151
|
export const Default = DefaultTemplate.bind({});
|
|
115
152
|
Default.args = {};
|
|
116
153
|
Default.parameters = {
|
|
@@ -121,6 +158,9 @@ Default.parameters = {
|
|
|
121
158
|
},
|
|
122
159
|
};
|
|
123
160
|
|
|
161
|
+
export const Disabled = DefaultTemplate.bind({});
|
|
162
|
+
Disabled.args = { disabled: true };
|
|
163
|
+
|
|
124
164
|
export const Searchable = DefaultTemplate.bind({});
|
|
125
165
|
Searchable.args = { searchable: true };
|
|
126
166
|
Searchable.parameters = {
|
|
@@ -131,6 +171,27 @@ Searchable.parameters = {
|
|
|
131
171
|
},
|
|
132
172
|
};
|
|
133
173
|
|
|
174
|
+
export const SearchModelValue = DefaultTemplate.bind({});
|
|
175
|
+
SearchModelValue.args = { searchable: true, search: "Copy" };
|
|
176
|
+
SearchModelValue.parameters = {
|
|
177
|
+
docs: {
|
|
178
|
+
story: {
|
|
179
|
+
height: "250px",
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export const NoCloseOnSelect = SelectableTemplate.bind({});
|
|
185
|
+
NoCloseOnSelect.args = {
|
|
186
|
+
modelValue: "pending",
|
|
187
|
+
options: [
|
|
188
|
+
{ label: "Active", id: "active" },
|
|
189
|
+
{ label: "Pending", id: "pending" },
|
|
190
|
+
{ label: "Archived", id: "archived" },
|
|
191
|
+
],
|
|
192
|
+
closeOnSelect: false,
|
|
193
|
+
};
|
|
194
|
+
|
|
134
195
|
export const OptionSelection = SelectableTemplate.bind({});
|
|
135
196
|
OptionSelection.args = {
|
|
136
197
|
label: "Select status",
|
|
@@ -176,8 +237,59 @@ ListboxYPosition.parameters = {
|
|
|
176
237
|
storyClasses: "h-[350px] flex items-center px-6 pt-8 pb-12",
|
|
177
238
|
};
|
|
178
239
|
|
|
179
|
-
export const
|
|
180
|
-
|
|
240
|
+
export const GroupValue = GroupValuesTemplate.bind({});
|
|
241
|
+
GroupValue.args = {
|
|
242
|
+
modelValue: "",
|
|
243
|
+
groupValueKey: "libs",
|
|
244
|
+
groupLabelKey: "language",
|
|
245
|
+
labelKey: "name",
|
|
246
|
+
valueKey: "name",
|
|
247
|
+
options: [
|
|
248
|
+
{
|
|
249
|
+
language: "Javascript",
|
|
250
|
+
libs: [{ name: "Vue.js" }, { name: "Adonis" }],
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
language: "Ruby",
|
|
254
|
+
libs: [
|
|
255
|
+
{ name: "Frameworks", isSubGroup: true, level: 2 },
|
|
256
|
+
{ name: "Rails", level: 3 },
|
|
257
|
+
{ name: "Sinatra", level: 3 },
|
|
258
|
+
],
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
language: "Other",
|
|
262
|
+
libs: [{ name: "Laravel" }, { name: "Phoenix" }],
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
};
|
|
266
|
+
GroupValue.parameters = {
|
|
267
|
+
docs: {
|
|
268
|
+
story: {
|
|
269
|
+
height: "400px",
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
export const OptionsLimit = DefaultTemplate.bind({});
|
|
275
|
+
OptionsLimit.args = { optionsLimit: 2 };
|
|
276
|
+
OptionsLimit.parameters = {
|
|
277
|
+
docs: {
|
|
278
|
+
description: {
|
|
279
|
+
story: "`optionsLimit` prop controls the number of options displayed in the dropdown.",
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
export const VisibleOptions = DefaultTemplate.bind({});
|
|
285
|
+
VisibleOptions.args = { visibleOptions: 2 };
|
|
286
|
+
VisibleOptions.parameters = {
|
|
287
|
+
docs: {
|
|
288
|
+
description: {
|
|
289
|
+
story: "`visibleOptions` prop controls the number of options you can see without a scroll.",
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
};
|
|
181
293
|
|
|
182
294
|
export const WithoutToggleIcon = Default.bind({});
|
|
183
295
|
WithoutToggleIcon.args = { toggleIcon: false };
|
|
@@ -228,3 +340,176 @@ ToggleSlot.args = {
|
|
|
228
340
|
</template>
|
|
229
341
|
`,
|
|
230
342
|
};
|
|
343
|
+
|
|
344
|
+
export const EmptySlot = DefaultTemplate.bind({});
|
|
345
|
+
EmptySlot.args = {
|
|
346
|
+
options: [],
|
|
347
|
+
slotTemplate: `
|
|
348
|
+
<template #empty>
|
|
349
|
+
<URow align="center">
|
|
350
|
+
<ULoader loading size="sm" />
|
|
351
|
+
<UText label="Loading, this may take a while..." />
|
|
352
|
+
</URow>
|
|
353
|
+
</template>
|
|
354
|
+
`,
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
export const OptionSlots: StoryFn<DefaultUDropdownButtonArgs> = (args) => ({
|
|
358
|
+
components: { UDropdownButton, URow, UCol, UAvatar, UIcon, UBadge, UText },
|
|
359
|
+
setup: () => ({ args, johnDoe, emilyDavis, alexJohnson, patMorgan }),
|
|
360
|
+
template: `
|
|
361
|
+
<URow>
|
|
362
|
+
<UDropdownButton
|
|
363
|
+
v-model="args.beforeOptionModel"
|
|
364
|
+
label="Before option slot"
|
|
365
|
+
:options="[
|
|
366
|
+
{
|
|
367
|
+
label: 'John Doe',
|
|
368
|
+
id: '1',
|
|
369
|
+
role: 'Developer',
|
|
370
|
+
avatar: johnDoe,
|
|
371
|
+
status: 'online',
|
|
372
|
+
statusColor: 'success',
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
label: 'Jane Smith',
|
|
376
|
+
id: '2',
|
|
377
|
+
role: 'Designer',
|
|
378
|
+
avatar: emilyDavis,
|
|
379
|
+
status: 'away',
|
|
380
|
+
statusColor: 'warning',
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
label: 'Mike Johnson',
|
|
384
|
+
id: '3',
|
|
385
|
+
role: 'Product Manager',
|
|
386
|
+
avatar: alexJohnson,
|
|
387
|
+
status: 'offline',
|
|
388
|
+
statusColor: 'grayscale',
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
label: 'Sarah Wilson',
|
|
392
|
+
id: '4',
|
|
393
|
+
role: 'QA Engineer',
|
|
394
|
+
avatar: patMorgan,
|
|
395
|
+
status: 'online',
|
|
396
|
+
statusColor: 'success',
|
|
397
|
+
},
|
|
398
|
+
]"
|
|
399
|
+
>
|
|
400
|
+
<template #before-option="{ option }">
|
|
401
|
+
<UAvatar :src="option.avatar" size="sm" />
|
|
402
|
+
</template>
|
|
403
|
+
</UDropdownButton>
|
|
404
|
+
|
|
405
|
+
<UDropdownButton
|
|
406
|
+
v-model="args.optionModel"
|
|
407
|
+
label="Option slot"
|
|
408
|
+
:options="[
|
|
409
|
+
{
|
|
410
|
+
label: 'John Doe',
|
|
411
|
+
id: '1',
|
|
412
|
+
role: 'Developer',
|
|
413
|
+
avatar: johnDoe,
|
|
414
|
+
status: 'online',
|
|
415
|
+
statusColor: 'success',
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
label: 'Jane Smith',
|
|
419
|
+
id: '2',
|
|
420
|
+
role: 'Designer',
|
|
421
|
+
avatar: emilyDavis,
|
|
422
|
+
status: 'away',
|
|
423
|
+
statusColor: 'warning',
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
label: 'Mike Johnson',
|
|
427
|
+
id: '3',
|
|
428
|
+
role: 'Product Manager',
|
|
429
|
+
avatar: alexJohnson,
|
|
430
|
+
status: 'offline',
|
|
431
|
+
statusColor: 'grayscale',
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
label: 'Sarah Wilson',
|
|
435
|
+
id: '4',
|
|
436
|
+
role: 'QA Engineer',
|
|
437
|
+
avatar: patMorgan,
|
|
438
|
+
status: 'online',
|
|
439
|
+
statusColor: 'success',
|
|
440
|
+
},
|
|
441
|
+
]"
|
|
442
|
+
>
|
|
443
|
+
<template #option="{ option }">
|
|
444
|
+
<URow align="center" gap="xs">
|
|
445
|
+
<UCol gap="none">
|
|
446
|
+
<UText size="sm">{{ option.label }}</UText>
|
|
447
|
+
<UText variant="lifted" size="xs">{{ option.role }}</UText>
|
|
448
|
+
</UCol>
|
|
449
|
+
<UBadge
|
|
450
|
+
:label="option.status"
|
|
451
|
+
:color="option.statusColor"
|
|
452
|
+
size="sm"
|
|
453
|
+
variant="subtle"
|
|
454
|
+
/>
|
|
455
|
+
</URow>
|
|
456
|
+
</template>
|
|
457
|
+
</UDropdownButton>
|
|
458
|
+
|
|
459
|
+
<UDropdownButton
|
|
460
|
+
v-model="args.afterOptionModel"
|
|
461
|
+
label="After option slot"
|
|
462
|
+
:options="[
|
|
463
|
+
{
|
|
464
|
+
label: 'John Doe',
|
|
465
|
+
id: '1',
|
|
466
|
+
role: 'Developer',
|
|
467
|
+
avatar: johnDoe,
|
|
468
|
+
status: 'online',
|
|
469
|
+
statusColor: 'success',
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
label: 'Jane Smith',
|
|
473
|
+
id: '2',
|
|
474
|
+
role: 'Designer',
|
|
475
|
+
avatar: emilyDavis,
|
|
476
|
+
status: 'away',
|
|
477
|
+
statusColor: 'warning',
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
label: 'Mike Johnson',
|
|
481
|
+
id: '3',
|
|
482
|
+
role: 'Product Manager',
|
|
483
|
+
avatar: alexJohnson,
|
|
484
|
+
status: 'offline',
|
|
485
|
+
statusColor: 'grayscale',
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
label: 'Sarah Wilson',
|
|
489
|
+
id: '4',
|
|
490
|
+
role: 'QA Engineer',
|
|
491
|
+
avatar: patMorgan,
|
|
492
|
+
status: 'online',
|
|
493
|
+
statusColor: 'success',
|
|
494
|
+
},
|
|
495
|
+
]"
|
|
496
|
+
>
|
|
497
|
+
<template #after-option="{ option }">
|
|
498
|
+
<UBadge
|
|
499
|
+
:label="option.status"
|
|
500
|
+
:color="option.statusColor"
|
|
501
|
+
size="sm"
|
|
502
|
+
variant="subtle"
|
|
503
|
+
/>
|
|
504
|
+
</template>
|
|
505
|
+
</UDropdownButton>
|
|
506
|
+
</URow>
|
|
507
|
+
`,
|
|
508
|
+
});
|
|
509
|
+
OptionSlots.parameters = {
|
|
510
|
+
docs: {
|
|
511
|
+
story: {
|
|
512
|
+
height: "300px",
|
|
513
|
+
},
|
|
514
|
+
},
|
|
515
|
+
};
|
|
@@ -300,6 +300,97 @@ describe("UDropdownButton.vue", () => {
|
|
|
300
300
|
|
|
301
301
|
expect(component.findComponent(UButton).attributes("data-test")).toBe(dataTest);
|
|
302
302
|
});
|
|
303
|
+
|
|
304
|
+
// OptionsLimit prop
|
|
305
|
+
it("passes optionsLimit prop to UListbox component", async () => {
|
|
306
|
+
const optionsLimit = 2;
|
|
307
|
+
|
|
308
|
+
const component = mount(UDropdownButton, {
|
|
309
|
+
props: {
|
|
310
|
+
optionsLimit,
|
|
311
|
+
options: defaultOptions,
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
await component.findComponent(UButton).trigger("click");
|
|
316
|
+
|
|
317
|
+
expect(component.findComponent(UListbox).props("optionsLimit")).toBe(optionsLimit);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("Search v-model – passes search to UListbox and filters", async () => {
|
|
321
|
+
const component = mount(UDropdownButton, {
|
|
322
|
+
props: {
|
|
323
|
+
searchable: true,
|
|
324
|
+
options: defaultOptions,
|
|
325
|
+
search: "Option 2",
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
await component.findComponent(UButton).trigger("click");
|
|
330
|
+
|
|
331
|
+
const listbox = component.getComponent(UListbox);
|
|
332
|
+
const options = listbox.findAll("[vl-child-key='option']");
|
|
333
|
+
|
|
334
|
+
expect(options).toHaveLength(1);
|
|
335
|
+
expect(options[0].text()).toBe("Option 2");
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// VisibleOptions prop
|
|
339
|
+
it("passes visibleOptions prop to UListbox component", async () => {
|
|
340
|
+
const visibleOptions = 5;
|
|
341
|
+
|
|
342
|
+
const component = mount(UDropdownButton, {
|
|
343
|
+
props: {
|
|
344
|
+
visibleOptions,
|
|
345
|
+
options: defaultOptions,
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
await component.findComponent(UButton).trigger("click");
|
|
350
|
+
|
|
351
|
+
expect(component.findComponent(UListbox).props("visibleOptions")).toBe(visibleOptions);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// GroupLabelKey prop
|
|
355
|
+
it("passes groupLabelKey prop to UListbox component", async () => {
|
|
356
|
+
const groupLabelKey = "category";
|
|
357
|
+
const groupedOptions = [
|
|
358
|
+
{ groupLabel: "Group 1", category: "group1" },
|
|
359
|
+
{ label: "Option 1", id: "option1", category: "group1" },
|
|
360
|
+
{ groupLabel: "Group 2", category: "group2" },
|
|
361
|
+
{ label: "Option 2", id: "option2", category: "group2" },
|
|
362
|
+
];
|
|
363
|
+
|
|
364
|
+
const component = mount(UDropdownButton, {
|
|
365
|
+
props: {
|
|
366
|
+
groupLabelKey,
|
|
367
|
+
options: groupedOptions,
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
await component.findComponent(UButton).trigger("click");
|
|
372
|
+
|
|
373
|
+
expect(component.findComponent(UListbox).props("groupLabelKey")).toBe(groupLabelKey);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// CloseOnSelect prop
|
|
377
|
+
it("keeps dropdown open when closeOnSelect is false", async () => {
|
|
378
|
+
const component = mount(UDropdownButton, {
|
|
379
|
+
props: {
|
|
380
|
+
options: defaultOptions,
|
|
381
|
+
closeOnSelect: false,
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
await component.findComponent(UButton).trigger("click");
|
|
386
|
+
expect(component.findComponent(UListbox).exists()).toBe(true);
|
|
387
|
+
|
|
388
|
+
const listbox = component.findComponent(UListbox);
|
|
389
|
+
|
|
390
|
+
listbox.vm.$emit("update:modelValue", 2);
|
|
391
|
+
|
|
392
|
+
expect(component.findComponent(UListbox).exists()).toBe(true);
|
|
393
|
+
});
|
|
303
394
|
});
|
|
304
395
|
|
|
305
396
|
// Slots tests
|
|
@@ -361,6 +452,105 @@ describe("UDropdownButton.vue", () => {
|
|
|
361
452
|
expect(component.find(`.${slotClass}`).exists()).toBe(true);
|
|
362
453
|
expect(component.find(`.${slotClass}`).text()).toBe(slotText);
|
|
363
454
|
});
|
|
455
|
+
|
|
456
|
+
// Before-option slot
|
|
457
|
+
it("renders content from before-option slot", async () => {
|
|
458
|
+
const label = "Dropdown Button";
|
|
459
|
+
const slotText = "Before";
|
|
460
|
+
const slotClass = "before-option-content";
|
|
461
|
+
|
|
462
|
+
const component = mount(UDropdownButton, {
|
|
463
|
+
props: {
|
|
464
|
+
label,
|
|
465
|
+
options: defaultOptions,
|
|
466
|
+
},
|
|
467
|
+
slots: {
|
|
468
|
+
"before-option": `<span class='${slotClass}'>${slotText}</span>`,
|
|
469
|
+
},
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
await component.findComponent(UButton).trigger("click");
|
|
473
|
+
|
|
474
|
+
const listbox = component.findComponent(UListbox);
|
|
475
|
+
const beforeOptionSlot = listbox.find(`.${slotClass}`);
|
|
476
|
+
|
|
477
|
+
expect(beforeOptionSlot.exists()).toBe(true);
|
|
478
|
+
expect(beforeOptionSlot.text()).toBe(slotText);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// Option slot
|
|
482
|
+
it("renders custom content from option slot", async () => {
|
|
483
|
+
const label = "Dropdown Button";
|
|
484
|
+
const slotClass = "custom-option-content";
|
|
485
|
+
|
|
486
|
+
const component = mount(UDropdownButton, {
|
|
487
|
+
props: {
|
|
488
|
+
label,
|
|
489
|
+
options: defaultOptions,
|
|
490
|
+
},
|
|
491
|
+
slots: {
|
|
492
|
+
option: `<span class='${slotClass}'>Custom {{ params.option.label }}</span>`,
|
|
493
|
+
},
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
await component.findComponent(UButton).trigger("click");
|
|
497
|
+
|
|
498
|
+
const listbox = component.findComponent(UListbox);
|
|
499
|
+
const customOptionSlot = listbox.find(`.${slotClass}`);
|
|
500
|
+
|
|
501
|
+
expect(customOptionSlot.exists()).toBe(true);
|
|
502
|
+
expect(customOptionSlot.text()).toBe("Custom Option 1");
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// After-option slot
|
|
506
|
+
it("renders content from after-option slot", async () => {
|
|
507
|
+
const label = "Dropdown Button";
|
|
508
|
+
const slotText = "After";
|
|
509
|
+
const slotClass = "after-option-content";
|
|
510
|
+
|
|
511
|
+
const component = mount(UDropdownButton, {
|
|
512
|
+
props: {
|
|
513
|
+
label,
|
|
514
|
+
options: defaultOptions,
|
|
515
|
+
},
|
|
516
|
+
slots: {
|
|
517
|
+
"after-option": `<span class='${slotClass}'>${slotText}</span>`,
|
|
518
|
+
},
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
await component.findComponent(UButton).trigger("click");
|
|
522
|
+
|
|
523
|
+
const listbox = component.findComponent(UListbox);
|
|
524
|
+
const afterOptionSlot = listbox.find(`.${slotClass}`);
|
|
525
|
+
|
|
526
|
+
expect(afterOptionSlot.exists()).toBe(true);
|
|
527
|
+
expect(afterOptionSlot.text()).toBe(slotText);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
// Empty slot
|
|
531
|
+
it("renders custom content from empty slot", async () => {
|
|
532
|
+
const label = "Dropdown Button";
|
|
533
|
+
const slotContent = "No options available";
|
|
534
|
+
const slotClass = "custom-empty";
|
|
535
|
+
|
|
536
|
+
const component = mount(UDropdownButton, {
|
|
537
|
+
props: {
|
|
538
|
+
label,
|
|
539
|
+
options: [],
|
|
540
|
+
},
|
|
541
|
+
slots: {
|
|
542
|
+
empty: `<span class='${slotClass}'>${slotContent}</span>`,
|
|
543
|
+
},
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
await component.findComponent(UButton).trigger("click");
|
|
547
|
+
|
|
548
|
+
const listbox = component.findComponent(UListbox);
|
|
549
|
+
const emptySlot = listbox.find(`.${slotClass}`);
|
|
550
|
+
|
|
551
|
+
expect(emptySlot.exists()).toBe(true);
|
|
552
|
+
expect(emptySlot.text()).toBe(slotContent);
|
|
553
|
+
});
|
|
364
554
|
});
|
|
365
555
|
|
|
366
556
|
// 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
|
* Button 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) => {
|
|
@@ -157,7 +168,7 @@ function hideOptions() {
|
|
|
157
168
|
function onClickOption(option: Option) {
|
|
158
169
|
emit("clickOption", option);
|
|
159
170
|
|
|
160
|
-
if (!props.multiple) hideOptions();
|
|
171
|
+
if (!props.multiple && props.closeOnSelect) hideOptions();
|
|
161
172
|
}
|
|
162
173
|
|
|
163
174
|
defineExpose({
|
|
@@ -249,17 +260,55 @@ const { config, getDataTest, wrapperAttrs, dropdownLinkAttrs, listboxAttrs, togg
|
|
|
249
260
|
v-if="isShownOptions"
|
|
250
261
|
ref="dropdown-list"
|
|
251
262
|
v-model="dropdownValue"
|
|
263
|
+
v-model:search="dropdownSearch"
|
|
252
264
|
:searchable="searchable"
|
|
253
265
|
:multiple="multiple"
|
|
254
266
|
:size="size"
|
|
255
267
|
:color="color"
|
|
256
268
|
:options="options"
|
|
269
|
+
:options-limit="optionsLimit"
|
|
270
|
+
:visible-options="visibleOptions"
|
|
257
271
|
:label-key="labelKey"
|
|
258
272
|
:value-key="valueKey"
|
|
273
|
+
:group-label-key="groupLabelKey"
|
|
274
|
+
:group-value-key="groupValueKey"
|
|
259
275
|
v-bind="listboxAttrs"
|
|
260
276
|
:data-test="getDataTest('list')"
|
|
261
277
|
@click-option="onClickOption"
|
|
262
278
|
@search-change="onSearchChange"
|
|
263
|
-
|
|
279
|
+
@update:search="(value) => emit('update:search', value)"
|
|
280
|
+
>
|
|
281
|
+
<template #before-option="{ option, index }">
|
|
282
|
+
<!--
|
|
283
|
+
@slot Use it to add something before option.
|
|
284
|
+
@binding {object} option
|
|
285
|
+
@binding {number} index
|
|
286
|
+
-->
|
|
287
|
+
<slot name="before-option" :option="option" :index="index" />
|
|
288
|
+
</template>
|
|
289
|
+
|
|
290
|
+
<template #option="{ option, index }">
|
|
291
|
+
<!--
|
|
292
|
+
@slot Use it to customize the option.
|
|
293
|
+
@binding {object} option
|
|
294
|
+
@binding {number} index
|
|
295
|
+
-->
|
|
296
|
+
<slot name="option" :option="option" :index="index" />
|
|
297
|
+
</template>
|
|
298
|
+
|
|
299
|
+
<template #after-option="{ option, index }">
|
|
300
|
+
<!--
|
|
301
|
+
@slot Use it to add something after option.
|
|
302
|
+
@binding {object} option
|
|
303
|
+
@binding {number} index
|
|
304
|
+
-->
|
|
305
|
+
<slot name="after-option" :option="option" :index="index" />
|
|
306
|
+
</template>
|
|
307
|
+
|
|
308
|
+
<template #empty>
|
|
309
|
+
<!-- @slot Use it to add something instead of empty state. -->
|
|
310
|
+
<slot name="empty" />
|
|
311
|
+
</template>
|
|
312
|
+
</ULisbox>
|
|
264
313
|
</div>
|
|
265
314
|
</template>
|
|
@@ -33,14 +33,18 @@ export default /*tw*/ {
|
|
|
33
33
|
size: "md",
|
|
34
34
|
labelKey: "label",
|
|
35
35
|
valueKey: "id",
|
|
36
|
+
groupLabelKey: "label",
|
|
36
37
|
yPosition: "bottom",
|
|
37
38
|
xPosition: "left",
|
|
39
|
+
optionsLimit: 0,
|
|
40
|
+
visibleOptions: 8,
|
|
41
|
+
labelDisplayCount: 2,
|
|
38
42
|
underlined: undefined,
|
|
39
43
|
dashed: false,
|
|
40
44
|
disabled: false,
|
|
41
45
|
searchable: false,
|
|
42
46
|
multiple: false,
|
|
43
|
-
|
|
47
|
+
closeOnSelect: true,
|
|
44
48
|
/* icons */
|
|
45
49
|
toggleIcon: "keyboard_arrow_down",
|
|
46
50
|
},
|