@mozaic-ds/vue 2.15.0 → 2.16.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/dist/mozaic-vue.css +1 -1
- package/dist/mozaic-vue.d.ts +963 -374
- package/dist/mozaic-vue.js +7736 -3601
- package/dist/mozaic-vue.js.map +1 -1
- package/dist/mozaic-vue.umd.cjs +24 -5
- package/dist/mozaic-vue.umd.cjs.map +1 -1
- package/package.json +3 -2
- package/src/components/actionlistbox/MActionListbox.spec.ts +14 -0
- package/src/components/actionlistbox/MActionListbox.stories.ts +15 -8
- package/src/components/actionlistbox/MActionListbox.vue +13 -1
- package/src/components/actionlistbox/README.md +2 -1
- package/src/components/button/README.md +2 -0
- package/src/components/combobox/MCombobox.spec.ts +246 -0
- package/src/components/combobox/MCombobox.stories.ts +190 -0
- package/src/components/combobox/MCombobox.vue +277 -0
- package/src/components/combobox/README.md +52 -0
- package/src/components/field/MField.stories.ts +105 -0
- package/src/components/optionListbox/MOptionListbox.spec.ts +527 -0
- package/src/components/optionListbox/MOptionListbox.vue +470 -0
- package/src/components/optionListbox/README.md +63 -0
- package/src/components/stepperstacked/MStepperStacked.spec.ts +162 -0
- package/src/components/stepperstacked/MStepperStacked.stories.ts +57 -0
- package/src/components/stepperstacked/MStepperStacked.vue +106 -0
- package/src/components/stepperstacked/README.md +15 -0
- package/src/components/textinput/MTextInput.vue +13 -1
- package/src/components/textinput/README.md +15 -1
- package/src/main.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mozaic-ds/vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.16.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Mozaic-Vue is the Vue.js implementation of ADEO Design system",
|
|
6
6
|
"author": "ADEO - ADEO Design system",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"*.d.ts"
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@mozaic-ds/styles": "^2.
|
|
44
|
+
"@mozaic-ds/styles": "^2.13.0",
|
|
45
45
|
"@mozaic-ds/web-fonts": "^1.65.0",
|
|
46
46
|
"postcss-scss": "^4.0.9",
|
|
47
47
|
"vue": "^3.5.13"
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"@storybook/addon-themes": "^10.0.4",
|
|
58
58
|
"@storybook/vue3-vite": "^10.0.4",
|
|
59
59
|
"@types/jsdom": "^28.0.0",
|
|
60
|
+
"@types/lodash": "^4.17.23",
|
|
60
61
|
"@vitejs/plugin-vue": "^6.0.1",
|
|
61
62
|
"@vitest/coverage-v8": "^4.0.7",
|
|
62
63
|
"@vitest/eslint-plugin": "^1.1.38",
|
|
@@ -24,6 +24,7 @@ const items = [
|
|
|
24
24
|
icon: DummyIcon,
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
|
+
id: 'move',
|
|
27
28
|
label: 'Move to...',
|
|
28
29
|
icon: DummyIcon,
|
|
29
30
|
},
|
|
@@ -121,4 +122,17 @@ describe('MActionListbox', () => {
|
|
|
121
122
|
expect(wrapper.emitted('close')).toBeTruthy();
|
|
122
123
|
expect(wrapper.emitted('close')?.length).toBe(1);
|
|
123
124
|
});
|
|
125
|
+
|
|
126
|
+
it('emits "action" when an item is clicked', async () => {
|
|
127
|
+
const wrapper = mountComponent({ title: 'Action List' });
|
|
128
|
+
|
|
129
|
+
const actions = wrapper.findAll('.mc-action-list__button');
|
|
130
|
+
await actions[0].trigger('click');
|
|
131
|
+
|
|
132
|
+
expect(wrapper.emitted('action')).toBeTruthy();
|
|
133
|
+
expect(wrapper.emitted('action')?.[0][0]).toBe(0);
|
|
134
|
+
|
|
135
|
+
await actions[1].trigger('click');
|
|
136
|
+
expect(wrapper.emitted('action')?.[1][0]).toBe('move');
|
|
137
|
+
});
|
|
124
138
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
|
+
import { action } from 'storybook/actions';
|
|
2
3
|
|
|
3
4
|
import MActionListbox from './MActionListbox.vue';
|
|
4
5
|
import MButton from '../button/MButton.vue';
|
|
@@ -25,10 +26,10 @@ const meta: Meta<typeof MActionListbox> = {
|
|
|
25
26
|
<template>
|
|
26
27
|
<MActionListbox
|
|
27
28
|
:items="[
|
|
28
|
-
{ label: 'Duplicate', icon: Copy20, disabled: true },
|
|
29
|
-
{ label: 'Move to...', icon: ArrowTopRight20 },
|
|
30
|
-
{ label: 'Download', icon: Download20 },
|
|
31
|
-
{ label: 'Delete', icon: Trash20, appearance: 'danger', divider: true }
|
|
29
|
+
{ id: 'item-1', label: 'Duplicate', icon: Copy20, disabled: true },
|
|
30
|
+
{ id: 'item-2', label: 'Move to...', icon: ArrowTopRight20 },
|
|
31
|
+
{ id: 'item-3', label: 'Download', icon: Download20 },
|
|
32
|
+
{ id: 'item-4', label: 'Delete', icon: Trash20, appearance: 'danger', divider: true }
|
|
32
33
|
]"
|
|
33
34
|
title="Listbox title (optional)"
|
|
34
35
|
/>
|
|
@@ -41,19 +42,23 @@ const meta: Meta<typeof MActionListbox> = {
|
|
|
41
42
|
title: 'Listbox title (optional)',
|
|
42
43
|
items: [
|
|
43
44
|
{
|
|
45
|
+
id: 'item-1',
|
|
44
46
|
label: 'Duplicate',
|
|
45
47
|
icon: Copy20,
|
|
46
48
|
disabled: true,
|
|
47
49
|
},
|
|
48
50
|
{
|
|
51
|
+
id: 'item-2',
|
|
49
52
|
label: 'Move to...',
|
|
50
53
|
icon: ArrowTopRight20,
|
|
51
54
|
},
|
|
52
55
|
{
|
|
56
|
+
id: 'item-3',
|
|
53
57
|
label: 'Download',
|
|
54
58
|
icon: Download20,
|
|
55
59
|
},
|
|
56
60
|
{
|
|
61
|
+
id: 'item-4',
|
|
57
62
|
label: 'Delete',
|
|
58
63
|
icon: Trash20,
|
|
59
64
|
appearance: 'danger',
|
|
@@ -64,10 +69,11 @@ const meta: Meta<typeof MActionListbox> = {
|
|
|
64
69
|
render: (args) => ({
|
|
65
70
|
components: { MActionListbox },
|
|
66
71
|
setup() {
|
|
67
|
-
|
|
72
|
+
const handleAction = action('action');
|
|
73
|
+
return { args, handleAction };
|
|
68
74
|
},
|
|
69
75
|
template: `
|
|
70
|
-
<MActionListbox v-bind="args" />
|
|
76
|
+
<MActionListbox v-bind="args" @action="handleAction" />
|
|
71
77
|
`,
|
|
72
78
|
}),
|
|
73
79
|
};
|
|
@@ -80,11 +86,12 @@ export const Activator: Story = {
|
|
|
80
86
|
render: (args) => ({
|
|
81
87
|
components: { MActionListbox, MButton },
|
|
82
88
|
setup() {
|
|
83
|
-
|
|
89
|
+
const handleAction = action('action');
|
|
90
|
+
return { args, handleAction };
|
|
84
91
|
},
|
|
85
92
|
template: `
|
|
86
93
|
<div>
|
|
87
|
-
<MActionListbox v-bind="args">
|
|
94
|
+
<MActionListbox v-bind="args" @action="handleAction">
|
|
88
95
|
<template #activator="{id}">
|
|
89
96
|
<MButton :popovertarget="id">Activator</MButton>
|
|
90
97
|
</template>
|
|
@@ -42,7 +42,11 @@
|
|
|
42
42
|
]"
|
|
43
43
|
role="menuitem"
|
|
44
44
|
>
|
|
45
|
-
<button
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
class="mc-action-list__button"
|
|
48
|
+
@click="emit('action', item?.id || index)"
|
|
49
|
+
>
|
|
46
50
|
<component
|
|
47
51
|
v-if="item.icon"
|
|
48
52
|
class="mc-action-list__icon"
|
|
@@ -85,6 +89,10 @@ const props = withDefaults(
|
|
|
85
89
|
*/
|
|
86
90
|
|
|
87
91
|
items: Array<{
|
|
92
|
+
/**
|
|
93
|
+
* Unique identifier for the item.
|
|
94
|
+
*/
|
|
95
|
+
id?: string;
|
|
88
96
|
/**
|
|
89
97
|
* The icon displayed for the item from Mozaic-icon-vue.
|
|
90
98
|
*/
|
|
@@ -115,6 +123,10 @@ const emit = defineEmits<{
|
|
|
115
123
|
* Emits when the close button is clicked.
|
|
116
124
|
*/
|
|
117
125
|
(on: 'close'): void;
|
|
126
|
+
/**
|
|
127
|
+
* Emits when an item is clicked, providing its id or index.
|
|
128
|
+
*/
|
|
129
|
+
(on: 'action', value: string | number): void;
|
|
118
130
|
}>();
|
|
119
131
|
|
|
120
132
|
const slots = defineSlots<{
|
|
@@ -9,7 +9,7 @@ An action list is a contextual menu that presents a list of available actions re
|
|
|
9
9
|
| --- | --- | --- | --- |
|
|
10
10
|
| `title` | title displayed in mobile version. | `string` | - |
|
|
11
11
|
| `position` | Defines the position of the listbox relative to its trigger or container. | `"bottom"` `"top"` `"left"` `"right"` | `"bottom"` |
|
|
12
|
-
| `items*` | An array of objects that allows you to provide all the data needed to generate the content for each item. | `{ icon?: Component` `undefined; label: string; disabled?: boolean` `undefined; appearance?: "standard"` `"danger"` `undefined; divider?: boolean` `undefined; }[]` | - |
|
|
12
|
+
| `items*` | An array of objects that allows you to provide all the data needed to generate the content for each item. | `{ id?: string` `undefined; icon?: Component` `undefined; label: string; disabled?: boolean` `undefined; appearance?: "standard"` `"danger"` `undefined; divider?: boolean` `undefined; }[]` | - |
|
|
13
13
|
|
|
14
14
|
## Slots
|
|
15
15
|
|
|
@@ -22,6 +22,7 @@ An action list is a contextual menu that presents a list of available actions re
|
|
|
22
22
|
| Name | Description | Type |
|
|
23
23
|
| --- | --- | --- |
|
|
24
24
|
| `close` | Emits when the close button is clicked. | [] |
|
|
25
|
+
| `action` | Emits when an item is clicked, providing its id or index. | [value: string | number] |
|
|
25
26
|
|
|
26
27
|
## Dependencies
|
|
27
28
|
|
|
@@ -41,6 +41,7 @@ style MButton fill:#008240,stroke:#333,stroke-width:4px
|
|
|
41
41
|
|
|
42
42
|
- [MFileUploaderItem](../fileuploaderitem)
|
|
43
43
|
- [MNavigationIndicator](../navigationindicator)
|
|
44
|
+
- [MOptionListbox](../optionListbox)
|
|
44
45
|
- [MPagination](../pagination)
|
|
45
46
|
- [MPasswordInput](../passwordinput)
|
|
46
47
|
- [MStepperBottomBar](../stepperbottombar)
|
|
@@ -52,6 +53,7 @@ style MButton fill:#008240,stroke:#333,stroke-width:4px
|
|
|
52
53
|
graph TD;
|
|
53
54
|
MFileUploaderItem --> MButton
|
|
54
55
|
MNavigationIndicator --> MButton
|
|
56
|
+
MOptionListbox --> MButton
|
|
55
57
|
MPagination --> MButton
|
|
56
58
|
MPasswordInput --> MButton
|
|
57
59
|
MStepperBottomBar --> MButton
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import { defineComponent, ref, nextTick } from 'vue';
|
|
4
|
+
import MCombobox from './MCombobox.vue';
|
|
5
|
+
|
|
6
|
+
const MOptionListboxStub = defineComponent({
|
|
7
|
+
name: 'MOptionListbox',
|
|
8
|
+
props: [
|
|
9
|
+
'modelValue',
|
|
10
|
+
'open',
|
|
11
|
+
'multiple',
|
|
12
|
+
'search',
|
|
13
|
+
'actions',
|
|
14
|
+
'checkableSections',
|
|
15
|
+
'searchPlaceholder',
|
|
16
|
+
'selectLabel',
|
|
17
|
+
'clearLabel',
|
|
18
|
+
'options',
|
|
19
|
+
'id',
|
|
20
|
+
],
|
|
21
|
+
emits: ['update:modelValue', 'open', 'close'],
|
|
22
|
+
setup() {
|
|
23
|
+
const activeIndex = ref(-1);
|
|
24
|
+
const listboxEl = ref(document.createElement('div'));
|
|
25
|
+
|
|
26
|
+
// On crée une fonction mock que l’on expose
|
|
27
|
+
const toggleValue = vi.fn();
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
activeIndex,
|
|
31
|
+
listboxEl,
|
|
32
|
+
handleKeydown: () => {},
|
|
33
|
+
toggleValue, // <- expose ici
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
template: `<div />`,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const MTagStub = defineComponent({
|
|
40
|
+
name: 'MTag',
|
|
41
|
+
props: ['id', 'label', 'type', 'size'],
|
|
42
|
+
emits: ['remove-tag'],
|
|
43
|
+
template: `<div class="m-tag-stub">{{ label }}</div>`,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const MButtonStub = defineComponent({
|
|
47
|
+
name: 'MButton',
|
|
48
|
+
props: ['outlined', 'size'],
|
|
49
|
+
emits: ['click'],
|
|
50
|
+
template: `<button @click="$emit('click')"><slot/></button>`,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const CrossCircleFilled24 = defineComponent({
|
|
54
|
+
name: 'CrossCircleFilled24',
|
|
55
|
+
template: `<svg/>`,
|
|
56
|
+
});
|
|
57
|
+
const ChevronDown24 = defineComponent({
|
|
58
|
+
name: 'ChevronDown24',
|
|
59
|
+
template: `<svg/>`,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('MCombobox', () => {
|
|
63
|
+
const options = [
|
|
64
|
+
{ label: 'One', value: 1 },
|
|
65
|
+
{ label: 'Two', value: 2 },
|
|
66
|
+
{ label: 'Three', value: 3 },
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
it('renders placeholder when no selection', () => {
|
|
70
|
+
const wrapper = mount(MCombobox, {
|
|
71
|
+
props: { modelValue: null, options },
|
|
72
|
+
global: {
|
|
73
|
+
components: {
|
|
74
|
+
MOptionListbox: MOptionListboxStub,
|
|
75
|
+
MTag: MTagStub,
|
|
76
|
+
MButton: MButtonStub,
|
|
77
|
+
CrossCircleFilled24,
|
|
78
|
+
ChevronDown24,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const control = wrapper.find('.mc-combobox__control');
|
|
84
|
+
expect(control.exists()).toBe(true);
|
|
85
|
+
expect(control.text()).toBe('Select an option');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('renders selected label for single value', () => {
|
|
89
|
+
const wrapper = mount(MCombobox, {
|
|
90
|
+
props: { modelValue: 1, options },
|
|
91
|
+
global: {
|
|
92
|
+
components: {
|
|
93
|
+
MOptionListbox: MOptionListboxStub,
|
|
94
|
+
MTag: MTagStub,
|
|
95
|
+
MButton: MButtonStub,
|
|
96
|
+
CrossCircleFilled24,
|
|
97
|
+
ChevronDown24,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const control = wrapper.find('.mc-combobox__control');
|
|
103
|
+
expect(control.text()).toBe('One');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('multiple selection shows joins values', async () => {
|
|
107
|
+
const wrapper = mount(MCombobox, {
|
|
108
|
+
props: { modelValue: [1, 2], multiple: true, options },
|
|
109
|
+
global: {
|
|
110
|
+
components: {
|
|
111
|
+
MOptionListbox: MOptionListboxStub,
|
|
112
|
+
MTag: MTagStub,
|
|
113
|
+
MButton: MButtonStub,
|
|
114
|
+
CrossCircleFilled24,
|
|
115
|
+
ChevronDown24,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(wrapper.find('.mc-combobox__control').text()).toBe('1, 2');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('toggles listbox open/close on control click', async () => {
|
|
124
|
+
const wrapper = mount(MCombobox, {
|
|
125
|
+
props: { modelValue: null, options },
|
|
126
|
+
global: {
|
|
127
|
+
components: {
|
|
128
|
+
MOptionListbox: MOptionListboxStub,
|
|
129
|
+
MTag: MTagStub,
|
|
130
|
+
MButton: MButtonStub,
|
|
131
|
+
CrossCircleFilled24,
|
|
132
|
+
ChevronDown24,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const root = wrapper.find('.mc-combobox');
|
|
138
|
+
const control = wrapper.find('.mc-combobox__control');
|
|
139
|
+
|
|
140
|
+
await control.trigger('click');
|
|
141
|
+
expect(root.classes()).toContain('mc-combobox--open');
|
|
142
|
+
|
|
143
|
+
await control.trigger('click');
|
|
144
|
+
expect(root.classes()).not.toContain('mc-combobox--open');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('clear button clears selection and emits update:modelValue', async () => {
|
|
148
|
+
const wrapperSingle = mount(MCombobox, {
|
|
149
|
+
props: { modelValue: 1, clearable: true, options },
|
|
150
|
+
global: {
|
|
151
|
+
components: {
|
|
152
|
+
MOptionListbox: MOptionListboxStub,
|
|
153
|
+
MTag: MTagStub,
|
|
154
|
+
MButton: MButtonStub,
|
|
155
|
+
CrossCircleFilled24,
|
|
156
|
+
ChevronDown24,
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const clearBtnSingle = wrapperSingle.find('.mc-combobox__clear');
|
|
162
|
+
expect(clearBtnSingle.exists()).toBe(true);
|
|
163
|
+
await clearBtnSingle.trigger('click');
|
|
164
|
+
const emittedSingle = wrapperSingle.emitted('update:modelValue') || [];
|
|
165
|
+
expect(emittedSingle.length).toBeGreaterThan(0);
|
|
166
|
+
expect(emittedSingle[emittedSingle.length - 1][0]).toBeNull();
|
|
167
|
+
|
|
168
|
+
const wrapperMulti = mount(MCombobox, {
|
|
169
|
+
props: { modelValue: [1], multiple: true, clearable: true, options },
|
|
170
|
+
global: {
|
|
171
|
+
components: {
|
|
172
|
+
MOptionListbox: MOptionListboxStub,
|
|
173
|
+
MTag: MTagStub,
|
|
174
|
+
MButton: MButtonStub,
|
|
175
|
+
CrossCircleFilled24,
|
|
176
|
+
ChevronDown24,
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const clearBtnMulti = wrapperMulti.find('.mc-combobox__clear');
|
|
182
|
+
expect(clearBtnMulti.exists()).toBe(true);
|
|
183
|
+
await clearBtnMulti.trigger('click');
|
|
184
|
+
const emittedMulti = wrapperMulti.emitted('update:modelValue') || [];
|
|
185
|
+
expect(emittedMulti.length).toBeGreaterThan(0);
|
|
186
|
+
|
|
187
|
+
const last = emittedMulti[emittedMulti.length - 1][0];
|
|
188
|
+
expect(Array.isArray(last)).toBe(true);
|
|
189
|
+
expect(last).toEqual([]);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('activeDescendant reflects child listbox activeIndex', async () => {
|
|
193
|
+
const wrapper = mount(MCombobox, {
|
|
194
|
+
props: { modelValue: null, options },
|
|
195
|
+
global: {
|
|
196
|
+
components: {
|
|
197
|
+
MOptionListbox: MOptionListboxStub,
|
|
198
|
+
MTag: MTagStub,
|
|
199
|
+
MButton: MButtonStub,
|
|
200
|
+
CrossCircleFilled24,
|
|
201
|
+
ChevronDown24,
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const listboxRef = (wrapper.vm as InstanceType<typeof MCombobox>).$refs
|
|
207
|
+
.listbox as { activeIndex: number };
|
|
208
|
+
expect(listboxRef).toBeTruthy();
|
|
209
|
+
|
|
210
|
+
listboxRef.activeIndex = 2;
|
|
211
|
+
await nextTick();
|
|
212
|
+
|
|
213
|
+
const control = wrapper.find('.mc-combobox__control');
|
|
214
|
+
const attr = control.attributes()['aria-activedescendant'];
|
|
215
|
+
expect(attr).toBeTruthy();
|
|
216
|
+
|
|
217
|
+
expect(attr.includes('-2')).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('clicking outside closes the listbox', async () => {
|
|
221
|
+
const wrapper = mount(MCombobox, {
|
|
222
|
+
props: { modelValue: null, options },
|
|
223
|
+
global: {
|
|
224
|
+
components: {
|
|
225
|
+
MOptionListbox: MOptionListboxStub,
|
|
226
|
+
MTag: MTagStub,
|
|
227
|
+
MButton: MButtonStub,
|
|
228
|
+
CrossCircleFilled24,
|
|
229
|
+
ChevronDown24,
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
attachTo: document.body,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const root = wrapper.find('.mc-combobox');
|
|
236
|
+
const control = wrapper.find('.mc-combobox__control');
|
|
237
|
+
|
|
238
|
+
await control.trigger('click');
|
|
239
|
+
expect(root.classes()).toContain('mc-combobox--open');
|
|
240
|
+
|
|
241
|
+
document.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
242
|
+
|
|
243
|
+
await nextTick();
|
|
244
|
+
expect(root.classes()).not.toContain('mc-combobox--open');
|
|
245
|
+
});
|
|
246
|
+
});
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
|
+
import MCombobox from './MCombobox.vue';
|
|
3
|
+
import { computed, ref } from 'vue';
|
|
4
|
+
import type { ListboxOption } from '../optionListbox/MOptionListbox.vue';
|
|
5
|
+
import MTag from '../tag/MTag.vue';
|
|
6
|
+
import MButton from '../button/MButton.vue';
|
|
7
|
+
|
|
8
|
+
const defaultOptions = Array.from({ length: 12 }).map((_el, index) => {
|
|
9
|
+
return {
|
|
10
|
+
label: `Option ${index + 1}`,
|
|
11
|
+
value: `option${index + 1}`,
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
let optionCount = 0;
|
|
16
|
+
|
|
17
|
+
const optionsWithSections: ListboxOption[] = Array.from({ length: 12 }).map(
|
|
18
|
+
(_el, index) => {
|
|
19
|
+
const isSection = index % 3 === 0;
|
|
20
|
+
|
|
21
|
+
if (!isSection) {
|
|
22
|
+
optionCount++;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
label: `${isSection ? 'Section' : 'Option'} ${isSection ? index / 3 + 1 : optionCount}`,
|
|
27
|
+
value: !isSection ? `option${optionCount}` : undefined,
|
|
28
|
+
type: isSection ? 'section' : 'option',
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const meta: Meta<typeof MCombobox> = {
|
|
34
|
+
title: 'Form elements/Combobox',
|
|
35
|
+
component: MCombobox,
|
|
36
|
+
tags: ['v2'],
|
|
37
|
+
parameters: {
|
|
38
|
+
docs: {
|
|
39
|
+
description: {
|
|
40
|
+
component:
|
|
41
|
+
'A combobox is an input field that allows users to select an option from a dropdown list or enter a custom value. It combines the functionality of a text input and a dropdown menu, providing flexibility and ease of use in forms and user interfaces.',
|
|
42
|
+
},
|
|
43
|
+
story: {
|
|
44
|
+
height: '300px',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
args: {
|
|
49
|
+
modelValue: null,
|
|
50
|
+
checkableSections: true,
|
|
51
|
+
options: defaultOptions,
|
|
52
|
+
},
|
|
53
|
+
render: (args) => ({
|
|
54
|
+
components: { MCombobox },
|
|
55
|
+
setup() {
|
|
56
|
+
const counterLabel = computed(
|
|
57
|
+
() => `${(args.modelValue as Array<unknown>)?.length} selected`,
|
|
58
|
+
);
|
|
59
|
+
return { args, counterLabel };
|
|
60
|
+
},
|
|
61
|
+
template: `
|
|
62
|
+
<MCombobox v-bind="args" v-model="args.modelValue" :counter-label="counterLabel"></MCombobox>
|
|
63
|
+
`,
|
|
64
|
+
}),
|
|
65
|
+
};
|
|
66
|
+
export default meta;
|
|
67
|
+
type Story = StoryObj<typeof MCombobox>;
|
|
68
|
+
|
|
69
|
+
export const Default: Story = {};
|
|
70
|
+
|
|
71
|
+
export const Multiple: Story = {
|
|
72
|
+
args: {
|
|
73
|
+
multiple: true,
|
|
74
|
+
modelValue: [],
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const SearchInput: Story = {
|
|
79
|
+
args: {
|
|
80
|
+
search: true,
|
|
81
|
+
modelValue: [],
|
|
82
|
+
multiple: true,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const ActionButtons: Story = {
|
|
87
|
+
args: {
|
|
88
|
+
modelValue: [],
|
|
89
|
+
multiple: true,
|
|
90
|
+
actions: true,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const WithSections: Story = {
|
|
95
|
+
args: {
|
|
96
|
+
options: optionsWithSections,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const SelectableSections: Story = {
|
|
101
|
+
args: {
|
|
102
|
+
modelValue: [],
|
|
103
|
+
multiple: true,
|
|
104
|
+
options: optionsWithSections,
|
|
105
|
+
checkableSections: true,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const Clearable = {
|
|
110
|
+
args: {
|
|
111
|
+
clearable: true,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const Disabled: Story = {
|
|
116
|
+
args: {
|
|
117
|
+
disabled: true,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const Readonly: Story = {
|
|
122
|
+
args: {
|
|
123
|
+
readonly: true,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const Invalid: Story = {
|
|
128
|
+
args: {
|
|
129
|
+
invalid: true,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const AdditionalInformation: Story = {
|
|
134
|
+
args: {
|
|
135
|
+
options: defaultOptions.map((option) => ({
|
|
136
|
+
...option,
|
|
137
|
+
content: 'Additional information',
|
|
138
|
+
})),
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export const RemovableTags: Story = {
|
|
143
|
+
args: {
|
|
144
|
+
modelValue: [],
|
|
145
|
+
options: defaultOptions,
|
|
146
|
+
multiple: true,
|
|
147
|
+
},
|
|
148
|
+
render: (args) => ({
|
|
149
|
+
components: { MCombobox, MTag, MButton },
|
|
150
|
+
setup() {
|
|
151
|
+
function findByValue(value: string | number) {
|
|
152
|
+
return args.options.find((option) => option.value === value);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const open = ref(false);
|
|
156
|
+
const maxResultsDisplayed = ref(9);
|
|
157
|
+
const counterLabel = computed(
|
|
158
|
+
() => `${(args.modelValue as Array<unknown>)?.length} selected`,
|
|
159
|
+
);
|
|
160
|
+
return { args, counterLabel, maxResultsDisplayed, findByValue, open };
|
|
161
|
+
},
|
|
162
|
+
template: `
|
|
163
|
+
<MCombobox v-bind="args" v-model="args.modelValue" :counter-label="counterLabel" @update:open="open = $event"></MCombobox>
|
|
164
|
+
|
|
165
|
+
<template
|
|
166
|
+
v-if="!open"
|
|
167
|
+
>
|
|
168
|
+
<div style="width: 300px; display: flex;align-items: center;gap: 10px;flex-wrap: wrap;margin: 16px 0;">
|
|
169
|
+
<MTag
|
|
170
|
+
v-for="(item, index) in args.modelValue?.slice(0, maxResultsDisplayed)"
|
|
171
|
+
:key="index"
|
|
172
|
+
id="tag"
|
|
173
|
+
:label="findByValue(item)?.label || ''"
|
|
174
|
+
type="removable"
|
|
175
|
+
size="s"
|
|
176
|
+
/>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<MButton
|
|
180
|
+
v-if="args.modelValue?.length > maxResultsDisplayed"
|
|
181
|
+
outlined
|
|
182
|
+
size="s"
|
|
183
|
+
@click="maxResultsDisplayed = args.modelValue?.length"
|
|
184
|
+
>
|
|
185
|
+
Show more
|
|
186
|
+
</MButton>
|
|
187
|
+
</template>
|
|
188
|
+
`,
|
|
189
|
+
}),
|
|
190
|
+
};
|