@mozaic-ds/vue 2.14.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.
Files changed (52) hide show
  1. package/dist/mozaic-vue.css +1 -1
  2. package/dist/mozaic-vue.d.ts +1582 -500
  3. package/dist/mozaic-vue.js +8020 -3218
  4. package/dist/mozaic-vue.js.map +1 -1
  5. package/dist/mozaic-vue.umd.cjs +24 -5
  6. package/dist/mozaic-vue.umd.cjs.map +1 -1
  7. package/package.json +6 -4
  8. package/src/components/DarkMode.mdx +115 -0
  9. package/src/components/actionlistbox/MActionListbox.spec.ts +20 -10
  10. package/src/components/actionlistbox/MActionListbox.stories.ts +15 -8
  11. package/src/components/actionlistbox/MActionListbox.vue +15 -12
  12. package/src/components/actionlistbox/README.md +2 -1
  13. package/src/components/avatar/MAvatar.stories.ts +1 -1
  14. package/src/components/breadcrumb/MBreadcrumb.vue +2 -2
  15. package/src/components/button/README.md +2 -0
  16. package/src/components/combobox/MCombobox.spec.ts +246 -0
  17. package/src/components/combobox/MCombobox.stories.ts +190 -0
  18. package/src/components/combobox/MCombobox.vue +277 -0
  19. package/src/components/combobox/README.md +52 -0
  20. package/src/components/field/MField.stories.ts +105 -0
  21. package/src/components/optionListbox/MOptionListbox.spec.ts +527 -0
  22. package/src/components/optionListbox/MOptionListbox.vue +470 -0
  23. package/src/components/optionListbox/README.md +63 -0
  24. package/src/components/pageheader/MPageHeader.spec.ts +12 -12
  25. package/src/components/pageheader/MPageHeader.stories.ts +9 -1
  26. package/src/components/pageheader/MPageHeader.vue +3 -6
  27. package/src/components/segmentedcontrol/MSegmentedControl.spec.ts +57 -25
  28. package/src/components/segmentedcontrol/MSegmentedControl.stories.ts +6 -19
  29. package/src/components/segmentedcontrol/MSegmentedControl.vue +27 -13
  30. package/src/components/segmentedcontrol/README.md +6 -3
  31. package/src/components/select/MSelect.vue +4 -3
  32. package/src/components/sidebar/stories/DefaultCase.stories.vue +2 -2
  33. package/src/components/sidebar/stories/README.md +8 -0
  34. package/src/components/sidebar/stories/WithExpandOnly.stories.vue +1 -1
  35. package/src/components/sidebar/stories/WithProfileInfoOnly.stories.vue +2 -2
  36. package/src/components/sidebar/stories/WithSingleLevel.stories.vue +2 -2
  37. package/src/components/stepperinline/MStepperInline.spec.ts +63 -28
  38. package/src/components/stepperinline/MStepperInline.stories.ts +18 -10
  39. package/src/components/stepperinline/MStepperInline.vue +24 -10
  40. package/src/components/stepperinline/README.md +6 -2
  41. package/src/components/stepperstacked/MStepperStacked.spec.ts +162 -0
  42. package/src/components/stepperstacked/MStepperStacked.stories.ts +57 -0
  43. package/src/components/stepperstacked/MStepperStacked.vue +106 -0
  44. package/src/components/stepperstacked/README.md +15 -0
  45. package/src/components/tabs/MTabs.stories.ts +18 -0
  46. package/src/components/tabs/MTabs.vue +30 -14
  47. package/src/components/tabs/Mtabs.spec.ts +56 -10
  48. package/src/components/tabs/README.md +6 -3
  49. package/src/components/textinput/MTextInput.vue +13 -1
  50. package/src/components/textinput/README.md +15 -1
  51. package/src/components/tileclickable/README.md +1 -1
  52. package/src/main.ts +10 -2
@@ -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
+ };
@@ -0,0 +1,277 @@
1
+ <template>
2
+ <div
3
+ ref="combobox"
4
+ :class="{
5
+ 'mc-combobox': true,
6
+ 'mc-combobox--open': isOpen,
7
+ 'mc-combobox--multiple': multiple,
8
+ 'mc-combobox--invalid': invalid,
9
+ 'mc-combobox--s': size === 's',
10
+ 'mc-combobox--disabled': disabled,
11
+ 'mc-combobox--readonly': readonly,
12
+ }"
13
+ >
14
+ <div class="mc-combobox__input">
15
+ <button
16
+ ref="comboboxControl"
17
+ :aria-expanded="isOpen ? 'true' : 'false'"
18
+ aria-haspopup="listbox"
19
+ :aria-controls="`listbox-${id}`"
20
+ :disabled="disabled"
21
+ class="mc-combobox__control"
22
+ aria-label="Combobox input"
23
+ @click="toggleListbox"
24
+ v-bind="
25
+ search
26
+ ? {}
27
+ : {
28
+ role: 'combobox',
29
+ 'aria-activedescendant': activeDescendantId,
30
+ onKeydown: listbox?.handleKeydown,
31
+ }
32
+ "
33
+ >
34
+ {{ selectionLabel }}
35
+ </button>
36
+ <span v-if="multiple && hasValue" class="mc-combobox__counter">
37
+ {{ counterLabel }}
38
+ </span>
39
+
40
+ <button
41
+ v-if="clearable && hasValue"
42
+ type="button"
43
+ class="mc-combobox__clear"
44
+ aria-label="Clear selection"
45
+ @click="listbox?.clearSelection"
46
+ >
47
+ <CrossCircleFilled24 />
48
+ </button>
49
+
50
+ <button
51
+ type="button"
52
+ :tabindex="-1"
53
+ class="mc-combobox__icon"
54
+ aria-label="Expand options list"
55
+ @click="toggleListbox"
56
+ >
57
+ <ChevronDown24 />
58
+ </button>
59
+ </div>
60
+
61
+ <MOptionListbox
62
+ ref="listbox"
63
+ v-model="selection"
64
+ :id
65
+ :open="isOpen"
66
+ :multiple
67
+ :search
68
+ :actions
69
+ :checkable-sections
70
+ :search-placeholder
71
+ :select-label
72
+ :clear-label
73
+ :options
74
+ class="mc-combobox__listbox"
75
+ @open="open"
76
+ @close="
77
+ () => {
78
+ close();
79
+ comboboxControl?.focus();
80
+ }
81
+ "
82
+ >
83
+ <template #optionPrefix>
84
+ <slot name="optionPrefix" />
85
+ </template>
86
+ </MOptionListbox>
87
+ </div>
88
+ </template>
89
+
90
+ <script setup lang="ts">
91
+ import MOptionListbox, {
92
+ type ListboxOption,
93
+ } from '../optionListbox/MOptionListbox.vue';
94
+ import { CrossCircleFilled24, ChevronDown24 } from '@mozaic-ds/icons-vue';
95
+ import {
96
+ computed,
97
+ onBeforeUnmount,
98
+ ref,
99
+ useId,
100
+ useTemplateRef,
101
+ type VNode,
102
+ } from 'vue';
103
+
104
+ /**
105
+ * 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.
106
+ */
107
+ const props = withDefaults(
108
+ defineProps<{
109
+ /**
110
+ * The current selected value(s) of the combobox.
111
+ */
112
+ modelValue: string | number | null | (string | number)[];
113
+ /**
114
+ * Enable multiple selection mode.
115
+ */
116
+ multiple?: boolean;
117
+ /**
118
+ * Size of the combobox: small ('s') or medium ('m').
119
+ */
120
+ size?: 's' | 'm';
121
+ /**
122
+ * Disable the combobox input.
123
+ */
124
+ disabled?: boolean;
125
+ /**
126
+ * Make the combobox read-only.
127
+ */
128
+ readonly?: boolean;
129
+ /**
130
+ * Mark the combobox as invalid.
131
+ */
132
+ invalid?: boolean;
133
+ /**
134
+ * Show a clear selection button.
135
+ */
136
+ clearable?: boolean;
137
+ /**
138
+ * Enable the search input inside the listbox.
139
+ */
140
+ search?: boolean;
141
+ /**
142
+ * Show select all / clear buttons when multiple.
143
+ */
144
+ actions?: boolean;
145
+ /**
146
+ * Enable checkable section headers in the listbox.
147
+ */
148
+ checkableSections?: boolean;
149
+ /**
150
+ * Placeholder text when no value is selected.
151
+ */
152
+ placeholder?: string;
153
+ /**
154
+ * Label for the selected items counter.
155
+ */
156
+ counterLabel?: string;
157
+ /**
158
+ * Placeholder text for the search input.
159
+ */
160
+ searchPlaceholder?: string;
161
+ /**
162
+ * Label for the "Select all" button.
163
+ */
164
+ selectLabel?: string;
165
+ /**
166
+ * Label for the "Clear selection" button.
167
+ */
168
+ clearLabel?: string;
169
+ /**
170
+ * Array of options to display in the listbox.
171
+ */
172
+ options: Array<ListboxOption>;
173
+ }>(),
174
+ {
175
+ placeholder: 'Select an option',
176
+ counterLabel: '99 selected',
177
+ searchPlaceholder: 'Find an option...',
178
+ selectLabel: 'Select all',
179
+ clearLabel: 'Clear',
180
+ size: 'm',
181
+ },
182
+ );
183
+
184
+ const emit = defineEmits<{
185
+ /**
186
+ * Emitted when the selected value(s) change.
187
+ */
188
+ (
189
+ on: 'update:modelValue',
190
+ value: string | number | null | (string | number)[],
191
+ ): void;
192
+ /**
193
+ * Emitted when the listbox is opened or closed.
194
+ */
195
+ (on: 'update:open', value: boolean): void;
196
+ }>();
197
+
198
+ defineSlots<{
199
+ /**
200
+ * Use this slot to add a prefix to options.
201
+ */
202
+ optionPrefix: VNode;
203
+ }>();
204
+
205
+ const id = useId();
206
+
207
+ const combobox = useTemplateRef('combobox');
208
+ const listbox = useTemplateRef('listbox');
209
+ const comboboxControl = useTemplateRef('comboboxControl');
210
+
211
+ const isOpen = ref(false);
212
+
213
+ const activeDescendantId = computed(() => {
214
+ const activeIndex = listbox.value?.activeIndex || -1;
215
+ return activeIndex >= 0 ? `option-${id}-${activeIndex}` : undefined;
216
+ });
217
+
218
+ const selection = computed({
219
+ get() {
220
+ return props.modelValue;
221
+ },
222
+ set(value: string | number | null | (string | number)[]) {
223
+ emit('update:modelValue', value);
224
+ },
225
+ });
226
+
227
+ const selectionLabel = computed(() => {
228
+ if (Array.isArray(selection.value)) {
229
+ if (!selection.value.length) return props.placeholder;
230
+ return (selection.value as (string | number)[]).join(', ');
231
+ }
232
+
233
+ return (
234
+ props.options.find((option) => option.value === selection.value)?.label ||
235
+ props.placeholder
236
+ );
237
+ });
238
+
239
+ const hasValue = computed(() =>
240
+ props.multiple
241
+ ? !!(selection.value as (string | number)[]).length
242
+ : !!selection.value,
243
+ );
244
+
245
+ function handleClickOutside(event: MouseEvent) {
246
+ if (
247
+ !combobox.value?.contains(event.target as Node) &&
248
+ !listbox.value?.listboxEl?.contains(event.target as Node)
249
+ ) {
250
+ close();
251
+ }
252
+ }
253
+
254
+ function open() {
255
+ isOpen.value = true;
256
+ emit('update:open', true);
257
+ document.addEventListener('click', handleClickOutside);
258
+ }
259
+
260
+ function close() {
261
+ isOpen.value = false;
262
+ emit('update:open', false);
263
+ document.removeEventListener('click', handleClickOutside);
264
+ }
265
+
266
+ function toggleListbox() {
267
+ return isOpen.value ? close() : open();
268
+ }
269
+
270
+ onBeforeUnmount(() => {
271
+ document.removeEventListener('click', handleClickOutside);
272
+ });
273
+ </script>
274
+
275
+ <style lang="scss" scoped>
276
+ @use '@mozaic-ds/styles/components/combobox';
277
+ </style>
@@ -0,0 +1,52 @@
1
+ # MCombobox
2
+
3
+ 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.
4
+
5
+
6
+ ## Props
7
+
8
+ | Name | Description | Type | Default |
9
+ | --- | --- | --- | --- |
10
+ | `modelValue*` | The current selected value(s) of the combobox. | `string` `number` `(string` `number)[]` `null` | - |
11
+ | `multiple` | Enable multiple selection mode. | `boolean` | - |
12
+ | `size` | Size of the combobox: small ('s') or medium ('m'). | `"s"` `"m"` | `"m"` |
13
+ | `disabled` | Disable the combobox input. | `boolean` | - |
14
+ | `readonly` | Make the combobox read-only. | `boolean` | - |
15
+ | `invalid` | Mark the combobox as invalid. | `boolean` | - |
16
+ | `clearable` | Show a clear selection button. | `boolean` | - |
17
+ | `search` | Enable the search input inside the listbox. | `boolean` | - |
18
+ | `actions` | Show select all / clear buttons when multiple. | `boolean` | - |
19
+ | `checkableSections` | Enable checkable section headers in the listbox. | `boolean` | - |
20
+ | `placeholder` | Placeholder text when no value is selected. | `string` | `"Select an option"` |
21
+ | `counterLabel` | Label for the selected items counter. | `string` | `"99 selected"` |
22
+ | `searchPlaceholder` | Placeholder text for the search input. | `string` | `"Find an option..."` |
23
+ | `selectLabel` | Label for the "Select all" button. | `string` | `"Select all"` |
24
+ | `clearLabel` | Label for the "Clear selection" button. | `string` | `"Clear"` |
25
+ | `options*` | Array of options to display in the listbox. | `ListboxOption[]` | - |
26
+
27
+ ## Slots
28
+
29
+ | Name | Description |
30
+ | --- | --- |
31
+ | `optionPrefix` | Use this slot to add a prefix to options. |
32
+
33
+ ## Events
34
+
35
+ | Name | Description | Type |
36
+ | --- | --- | --- |
37
+ | `update:modelValue` | - | `[value: string` `number` `(string` `number)[]` `null]` |
38
+ | `update:open` | Emitted when the selected value(s) change. / ( on: 'update:modelValue', value: string | number | null | (string | number)[], ): void; /** Emitted when the listbox is opened or closed. | [value: boolean] |
39
+
40
+ ## Dependencies
41
+
42
+ ### Depends on
43
+
44
+ - [MOptionListbox](../optionListbox)
45
+
46
+ ### Graph
47
+
48
+ ```mermaid
49
+ graph TD;
50
+ MCombobox --> MOptionListbox
51
+ style MCombobox fill:#008240,stroke:#333,stroke-width:4px
52
+ ```
@@ -5,6 +5,8 @@ import MField from './MField.vue';
5
5
  import MTextInput from '../textinput/MTextInput.vue';
6
6
  import MTextArea from '../textarea/MTextArea.vue';
7
7
  import MSelect from '../select/MSelect.vue';
8
+ import MCombobox from '../combobox/MCombobox.vue';
9
+ import { ref } from 'vue';
8
10
 
9
11
  const meta: Meta<typeof MField> = {
10
12
  title: 'Form Elements/Field',
@@ -383,3 +385,106 @@ export const SelectInvalid: Story = {
383
385
  `,
384
386
  }),
385
387
  };
388
+
389
+ export const ComboboxValid: Story = {
390
+ args: {
391
+ label: 'Label',
392
+ id: 'comboboxValidId',
393
+ requirementText: 'required',
394
+ helpText: 'Help text',
395
+ isValid: true,
396
+ message: 'Validation message (Be concise and use comprehensive words).',
397
+ default: `
398
+ <MCombobox
399
+ v-model="value"
400
+ :options="
401
+ [
402
+ {
403
+ label: 'Option 1',
404
+ value: 'option1',
405
+ },
406
+ {
407
+ label: 'Option 2',
408
+ value: 'option2',
409
+ },
410
+ {
411
+ label: 'Option 3',
412
+ value: 'option3',
413
+ },
414
+ {
415
+ label: 'Option 4',
416
+ value: 'option4',
417
+ },
418
+ ]
419
+ "
420
+ @update:modelValue="handleUpdate"
421
+ />
422
+ `,
423
+ },
424
+ render: (args) => ({
425
+ components: { MField, MCombobox },
426
+ setup() {
427
+ const value = ref(null);
428
+ const handleUpdate = action('update:modelValue');
429
+
430
+ return { args, handleUpdate, value };
431
+ },
432
+ template: `
433
+ <MField v-bind="args">
434
+ ${args.default}
435
+ </MField>
436
+ `,
437
+ }),
438
+ };
439
+
440
+ export const ComboboxInvalid: Story = {
441
+ args: {
442
+ label: 'Label',
443
+ id: 'comboboxValidId',
444
+ requirementText: 'required',
445
+ helpText: 'Help text',
446
+ isInvalid: true,
447
+ message: 'Error message (Be concise and use comprehensive words)',
448
+ default: `
449
+ <MCombobox
450
+ v-model="value"
451
+ invalid
452
+ :options="
453
+ [
454
+ {
455
+ label: 'Option 1',
456
+ value: 'option1',
457
+ },
458
+ {
459
+ label: 'Option 2',
460
+ value: 'option2',
461
+ },
462
+ {
463
+ label: 'Option 3',
464
+ value: 'option3',
465
+ },
466
+ {
467
+ label: 'Option 4',
468
+ value: 'option4',
469
+ },
470
+ ]
471
+ "
472
+ @update:modelValue="handleUpdate"
473
+ />
474
+ `,
475
+ },
476
+ render: (args) => ({
477
+ components: { MField, MCombobox },
478
+ setup() {
479
+ const value = ref(null);
480
+ const handleUpdate = action('update:modelValue');
481
+
482
+ return { args, handleUpdate, value };
483
+ },
484
+ template: `
485
+ <MField v-bind="args">
486
+ ${args.default}
487
+ </MField>
488
+ `,
489
+ }),
490
+ };