@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.
- package/dist/mozaic-vue.css +1 -1
- package/dist/mozaic-vue.d.ts +1582 -500
- package/dist/mozaic-vue.js +8020 -3218
- 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 +6 -4
- package/src/components/DarkMode.mdx +115 -0
- package/src/components/actionlistbox/MActionListbox.spec.ts +20 -10
- package/src/components/actionlistbox/MActionListbox.stories.ts +15 -8
- package/src/components/actionlistbox/MActionListbox.vue +15 -12
- package/src/components/actionlistbox/README.md +2 -1
- package/src/components/avatar/MAvatar.stories.ts +1 -1
- package/src/components/breadcrumb/MBreadcrumb.vue +2 -2
- 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/pageheader/MPageHeader.spec.ts +12 -12
- package/src/components/pageheader/MPageHeader.stories.ts +9 -1
- package/src/components/pageheader/MPageHeader.vue +3 -6
- package/src/components/segmentedcontrol/MSegmentedControl.spec.ts +57 -25
- package/src/components/segmentedcontrol/MSegmentedControl.stories.ts +6 -19
- package/src/components/segmentedcontrol/MSegmentedControl.vue +27 -13
- package/src/components/segmentedcontrol/README.md +6 -3
- package/src/components/select/MSelect.vue +4 -3
- package/src/components/sidebar/stories/DefaultCase.stories.vue +2 -2
- package/src/components/sidebar/stories/README.md +8 -0
- package/src/components/sidebar/stories/WithExpandOnly.stories.vue +1 -1
- package/src/components/sidebar/stories/WithProfileInfoOnly.stories.vue +2 -2
- package/src/components/sidebar/stories/WithSingleLevel.stories.vue +2 -2
- package/src/components/stepperinline/MStepperInline.spec.ts +63 -28
- package/src/components/stepperinline/MStepperInline.stories.ts +18 -10
- package/src/components/stepperinline/MStepperInline.vue +24 -10
- package/src/components/stepperinline/README.md +6 -2
- 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/tabs/MTabs.stories.ts +18 -0
- package/src/components/tabs/MTabs.vue +30 -14
- package/src/components/tabs/Mtabs.spec.ts +56 -10
- package/src/components/tabs/README.md +6 -3
- package/src/components/textinput/MTextInput.vue +13 -1
- package/src/components/textinput/README.md +15 -1
- package/src/components/tileclickable/README.md +1 -1
- package/src/main.ts +10 -2
|
@@ -4,9 +4,9 @@ import MSegmentedControl from './MSegmentedControl.vue';
|
|
|
4
4
|
|
|
5
5
|
describe('MSegmentedControl.vue', () => {
|
|
6
6
|
const segments = [
|
|
7
|
-
{ label: 'First' },
|
|
8
|
-
{ label: 'Second' },
|
|
9
|
-
{ label: 'Third' },
|
|
7
|
+
{ id: '1', label: 'First' },
|
|
8
|
+
{ id: '2', label: 'Second' },
|
|
9
|
+
{ id: '3', label: 'Third' },
|
|
10
10
|
];
|
|
11
11
|
|
|
12
12
|
it('renders segments with correct labels', () => {
|
|
@@ -23,42 +23,74 @@ describe('MSegmentedControl.vue', () => {
|
|
|
23
23
|
|
|
24
24
|
it('sets default active segment based on modelValue prop', () => {
|
|
25
25
|
const wrapper = mount(MSegmentedControl, {
|
|
26
|
-
props: { segments, modelValue: 1 },
|
|
26
|
+
props: { segments, modelValue: '1' },
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
const buttons = wrapper.findAll('button');
|
|
30
|
-
expect(buttons[
|
|
30
|
+
expect(buttons[0].classes()).toContain(
|
|
31
31
|
'mc-segmented-control__segment--selected',
|
|
32
32
|
);
|
|
33
|
-
expect(buttons[
|
|
33
|
+
expect(buttons[0].attributes('aria-checked')).toBe('true');
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
describe('Segment selection - string type model', () => {
|
|
37
|
+
it('emits update:modelValue and changes active segment on click', async () => {
|
|
38
|
+
const wrapper = mount(MSegmentedControl, {
|
|
39
|
+
props: { segments, modelValue: '1' },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const buttons = wrapper.findAll('button');
|
|
43
|
+
await buttons[2].trigger('click');
|
|
44
|
+
|
|
45
|
+
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
|
|
46
|
+
expect(wrapper.emitted('update:modelValue')![0]).toEqual(['3']);
|
|
47
|
+
|
|
48
|
+
expect(buttons[2].classes()).toContain(
|
|
49
|
+
'mc-segmented-control__segment--selected',
|
|
50
|
+
);
|
|
51
|
+
expect(buttons[2].attributes('aria-checked')).toBe('true');
|
|
39
52
|
});
|
|
40
53
|
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
it('does not emit update event if clicking already active segment', async () => {
|
|
55
|
+
const wrapper = mount(MSegmentedControl, {
|
|
56
|
+
props: { segments, modelValue: '2' },
|
|
57
|
+
});
|
|
43
58
|
|
|
44
|
-
|
|
45
|
-
|
|
59
|
+
const buttons = wrapper.findAll('button');
|
|
60
|
+
await buttons[1].trigger('click');
|
|
46
61
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
);
|
|
50
|
-
expect(buttons[2].attributes('aria-checked')).toBe('true');
|
|
62
|
+
expect(wrapper.emitted('update:modelValue')).toBeFalsy();
|
|
63
|
+
});
|
|
51
64
|
});
|
|
52
65
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
66
|
+
describe('Segment selection - number type model', () => {
|
|
67
|
+
it('emits update:modelValue and changes active segment on click', async () => {
|
|
68
|
+
const wrapper = mount(MSegmentedControl, {
|
|
69
|
+
props: { segments, modelValue: 0 },
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const buttons = wrapper.findAll('button');
|
|
73
|
+
await buttons[2].trigger('click');
|
|
74
|
+
|
|
75
|
+
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
|
|
76
|
+
expect(wrapper.emitted('update:modelValue')![0]).toEqual([2]);
|
|
77
|
+
|
|
78
|
+
expect(buttons[2].classes()).toContain(
|
|
79
|
+
'mc-segmented-control__segment--selected',
|
|
80
|
+
);
|
|
81
|
+
expect(buttons[2].attributes('aria-checked')).toBe('true');
|
|
56
82
|
});
|
|
57
83
|
|
|
58
|
-
|
|
59
|
-
|
|
84
|
+
it('does not emit update event if clicking already active segment', async () => {
|
|
85
|
+
const wrapper = mount(MSegmentedControl, {
|
|
86
|
+
props: { segments, modelValue: 1 },
|
|
87
|
+
});
|
|
60
88
|
|
|
61
|
-
|
|
89
|
+
const buttons = wrapper.findAll('button');
|
|
90
|
+
await buttons[1].trigger('click');
|
|
91
|
+
|
|
92
|
+
expect(wrapper.emitted('update:modelValue')).toBeFalsy();
|
|
93
|
+
});
|
|
62
94
|
});
|
|
63
95
|
|
|
64
96
|
it('applies full width class when full prop is true', () => {
|
|
@@ -87,7 +119,7 @@ describe('MSegmentedControl.vue', () => {
|
|
|
87
119
|
|
|
88
120
|
it('updates active segment when modelValue prop changes', async () => {
|
|
89
121
|
const wrapper = mount(MSegmentedControl, {
|
|
90
|
-
props: { segments, modelValue:
|
|
122
|
+
props: { segments, modelValue: '1' },
|
|
91
123
|
});
|
|
92
124
|
|
|
93
125
|
const buttons = wrapper.findAll('button');
|
|
@@ -95,7 +127,7 @@ describe('MSegmentedControl.vue', () => {
|
|
|
95
127
|
'mc-segmented-control__segment--selected',
|
|
96
128
|
);
|
|
97
129
|
|
|
98
|
-
await wrapper.setProps({ modelValue:
|
|
130
|
+
await wrapper.setProps({ modelValue: '3' });
|
|
99
131
|
|
|
100
132
|
expect(buttons[2].classes()).toContain(
|
|
101
133
|
'mc-segmented-control__segment--selected',
|
|
@@ -15,17 +15,22 @@ const meta: Meta<typeof MSegmentedControl> = {
|
|
|
15
15
|
},
|
|
16
16
|
},
|
|
17
17
|
args: {
|
|
18
|
+
modelValue: 'label1',
|
|
18
19
|
segments: [
|
|
19
20
|
{
|
|
21
|
+
id: 'label1',
|
|
20
22
|
label: 'Label',
|
|
21
23
|
},
|
|
22
24
|
{
|
|
25
|
+
id: 'label2',
|
|
23
26
|
label: 'Label',
|
|
24
27
|
},
|
|
25
28
|
{
|
|
29
|
+
id: 'label3',
|
|
26
30
|
label: 'Label',
|
|
27
31
|
},
|
|
28
32
|
{
|
|
33
|
+
id: 'label4',
|
|
29
34
|
label: 'Label',
|
|
30
35
|
},
|
|
31
36
|
],
|
|
@@ -40,6 +45,7 @@ const meta: Meta<typeof MSegmentedControl> = {
|
|
|
40
45
|
template: `
|
|
41
46
|
<MSegmentedControl
|
|
42
47
|
v-bind="args"
|
|
48
|
+
v-model="args.modelValue"
|
|
43
49
|
@update:modelValue="handleUpdate"
|
|
44
50
|
></MSegmentedControl>
|
|
45
51
|
`,
|
|
@@ -50,25 +56,6 @@ type Story = StoryObj<typeof MSegmentedControl>;
|
|
|
50
56
|
|
|
51
57
|
export const Default: Story = {};
|
|
52
58
|
|
|
53
|
-
export const Icons: Story = {
|
|
54
|
-
args: {
|
|
55
|
-
segments: [
|
|
56
|
-
{
|
|
57
|
-
label: 'Label',
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
label: 'Label',
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
label: 'Label',
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
label: 'Label',
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
|
|
72
59
|
export const Size: Story = {
|
|
73
60
|
args: { size: 'm' },
|
|
74
61
|
};
|
|
@@ -6,11 +6,14 @@
|
|
|
6
6
|
type="button"
|
|
7
7
|
class="mc-segmented-control__segment"
|
|
8
8
|
:class="{
|
|
9
|
-
'mc-segmented-control__segment--selected': isSegmentSelected(
|
|
9
|
+
'mc-segmented-control__segment--selected': isSegmentSelected(
|
|
10
|
+
index,
|
|
11
|
+
segment.id,
|
|
12
|
+
),
|
|
10
13
|
}"
|
|
11
|
-
:aria-checked="isSegmentSelected(index)"
|
|
14
|
+
:aria-checked="isSegmentSelected(index, segment.id)"
|
|
12
15
|
role="radio"
|
|
13
|
-
@click="onClickSegment(index)"
|
|
16
|
+
@click="onClickSegment(index, segment.id)"
|
|
14
17
|
>
|
|
15
18
|
{{ segment.label }}
|
|
16
19
|
</button>
|
|
@@ -25,9 +28,12 @@ import { computed, ref, watch } from 'vue';
|
|
|
25
28
|
const props = withDefaults(
|
|
26
29
|
defineProps<{
|
|
27
30
|
/**
|
|
28
|
-
*
|
|
31
|
+
* Defines the currently active tab.
|
|
32
|
+
*
|
|
33
|
+
* - If a `number` is provided, it represents the index of the segment.
|
|
34
|
+
* - If a `string` is provided, it must match the `id` of one of the segments.
|
|
29
35
|
*/
|
|
30
|
-
modelValue?: number;
|
|
36
|
+
modelValue?: string | number;
|
|
31
37
|
/**
|
|
32
38
|
* if `true`, the segmented control take the full width.
|
|
33
39
|
*/
|
|
@@ -40,6 +46,10 @@ const props = withDefaults(
|
|
|
40
46
|
* An array of objects that allows you to provide all the data needed to generate the content for each segment.
|
|
41
47
|
*/
|
|
42
48
|
segments: Array<{
|
|
49
|
+
/**
|
|
50
|
+
* Unique identifier for the segment.
|
|
51
|
+
*/
|
|
52
|
+
id?: string;
|
|
43
53
|
/**
|
|
44
54
|
* The label displayed for the segment.
|
|
45
55
|
*/
|
|
@@ -59,7 +69,7 @@ const classObject = computed(() => {
|
|
|
59
69
|
};
|
|
60
70
|
});
|
|
61
71
|
|
|
62
|
-
const modelValue = ref(props.modelValue);
|
|
72
|
+
const modelValue = ref<string | number | undefined>(props.modelValue);
|
|
63
73
|
|
|
64
74
|
watch(
|
|
65
75
|
() => props.modelValue,
|
|
@@ -68,22 +78,26 @@ watch(
|
|
|
68
78
|
},
|
|
69
79
|
);
|
|
70
80
|
|
|
71
|
-
const onClickSegment = (index: number) => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
81
|
+
const onClickSegment = (index: number, id?: string) => {
|
|
82
|
+
const value = typeof props.modelValue === 'number' ? index : id;
|
|
83
|
+
|
|
84
|
+
if (value !== modelValue.value) {
|
|
85
|
+
modelValue.value = value;
|
|
86
|
+
emit('update:modelValue', value);
|
|
75
87
|
}
|
|
76
88
|
};
|
|
77
89
|
|
|
78
|
-
const isSegmentSelected = (index: number) => {
|
|
79
|
-
|
|
90
|
+
const isSegmentSelected = (index: number, id?: string) => {
|
|
91
|
+
const value = typeof props.modelValue === 'number' ? index : id;
|
|
92
|
+
|
|
93
|
+
return modelValue.value === value;
|
|
80
94
|
};
|
|
81
95
|
|
|
82
96
|
const emit = defineEmits<{
|
|
83
97
|
/**
|
|
84
98
|
* Emits when the selected segment changes, updating the modelValue prop.
|
|
85
99
|
*/
|
|
86
|
-
(on: 'update:modelValue', value
|
|
100
|
+
(on: 'update:modelValue', value?: string | number): void;
|
|
87
101
|
}>();
|
|
88
102
|
</script>
|
|
89
103
|
|
|
@@ -7,13 +7,16 @@ A Segmented Control allows users to switch between multiple options or views wit
|
|
|
7
7
|
|
|
8
8
|
| Name | Description | Type | Default |
|
|
9
9
|
| --- | --- | --- | --- |
|
|
10
|
-
| `modelValue` |
|
|
10
|
+
| `modelValue` | Defines the currently active tab.
|
|
11
|
+
|
|
12
|
+
- If a `number` is provided, it represents the index of the segment.
|
|
13
|
+
- If a `string` is provided, it must match the `id` of one of the segments. | `string` `number` | `0` |
|
|
11
14
|
| `full` | if `true`, the segmented control take the full width. | `boolean` | - |
|
|
12
15
|
| `size` | Determines the size of the segmented control. | `"s"` `"m"` | `"s"` |
|
|
13
|
-
| `segments*` | An array of objects that allows you to provide all the data needed to generate the content for each segment. | `{ label: string; }[]` | - |
|
|
16
|
+
| `segments*` | An array of objects that allows you to provide all the data needed to generate the content for each segment. | `{ id?: string` `undefined; label: string; }[]` | - |
|
|
14
17
|
|
|
15
18
|
## Events
|
|
16
19
|
|
|
17
20
|
| Name | Description | Type |
|
|
18
21
|
| --- | --- | --- |
|
|
19
|
-
| `update:modelValue` | Emits when the selected segment changes, updating the modelValue prop. | [value
|
|
22
|
+
| `update:modelValue` | Emits when the selected segment changes, updating the modelValue prop. | [value?: string | number] |
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
@close="emit('close')"
|
|
6
6
|
>
|
|
7
7
|
<template #header>
|
|
8
|
-
<MSidebarHeader title="Adeo Design System" logo="/logo.svg" />
|
|
8
|
+
<MSidebarHeader title="Adeo Design System" logo="/mozaic-vue/logo.svg" />
|
|
9
9
|
</template>
|
|
10
10
|
|
|
11
11
|
<template #shortcuts>
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
title="Dieter Rams"
|
|
50
50
|
subtitle="Industrial designer"
|
|
51
51
|
href="#"
|
|
52
|
-
avatar="/images/Avatar.png"
|
|
52
|
+
avatar="/mozaic-vue/images/Avatar.png"
|
|
53
53
|
@log-out="emit('log-out')"
|
|
54
54
|
>
|
|
55
55
|
<MSidebarNavItem
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
@close="emit('close')"
|
|
6
6
|
>
|
|
7
7
|
<template #header>
|
|
8
|
-
<MSidebarHeader title="Adeo Design System" logo="/logo.svg" />
|
|
8
|
+
<MSidebarHeader title="Adeo Design System" logo="/mozaic-vue/logo.svg" />
|
|
9
9
|
</template>
|
|
10
10
|
|
|
11
11
|
<template #shortcuts>
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
title="Dieter Rams"
|
|
51
51
|
subtitle="Industrial designer"
|
|
52
52
|
href="#"
|
|
53
|
-
avatar="/images/Avatar.png"
|
|
53
|
+
avatar="/mozaic-vue/images/Avatar.png"
|
|
54
54
|
@log-out="emit('log-out')"
|
|
55
55
|
>
|
|
56
56
|
<MSidebarNavItem
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
@close="emit('close')"
|
|
6
6
|
>
|
|
7
7
|
<template #header>
|
|
8
|
-
<MSidebarHeader title="Adeo Design System" logo="/logo.svg" />
|
|
8
|
+
<MSidebarHeader title="Adeo Design System" logo="/mozaic-vue/logo.svg" />
|
|
9
9
|
</template>
|
|
10
10
|
|
|
11
11
|
<template #shortcuts>
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
title="Dieter Rams"
|
|
34
34
|
subtitle="Industrial designer"
|
|
35
35
|
href="#"
|
|
36
|
-
img-src="/images/Avatar.png"
|
|
36
|
+
img-src="/mozaic-vue/images/Avatar.png"
|
|
37
37
|
@log-out="emit('log-out')"
|
|
38
38
|
>
|
|
39
39
|
<MSidebarNavItem
|
|
@@ -4,15 +4,15 @@ import { describe, it, expect } from 'vitest';
|
|
|
4
4
|
|
|
5
5
|
describe('MStepperInline', () => {
|
|
6
6
|
const defaultSteps = [
|
|
7
|
-
{ label: 'Step 1' },
|
|
8
|
-
{ label: 'Step 2' },
|
|
9
|
-
{ label: 'Step 3' },
|
|
7
|
+
{ id: '1', label: 'Step 1' },
|
|
8
|
+
{ id: '2', label: 'Step 2' },
|
|
9
|
+
{ id: '3', label: 'Step 3' },
|
|
10
10
|
];
|
|
11
11
|
|
|
12
12
|
it('renders correctly with default props', () => {
|
|
13
13
|
const wrapper = mount(MStepperInline, {
|
|
14
14
|
props: {
|
|
15
|
-
currentStep: 1,
|
|
15
|
+
currentStep: '1',
|
|
16
16
|
steps: defaultSteps,
|
|
17
17
|
},
|
|
18
18
|
});
|
|
@@ -28,12 +28,12 @@ describe('MStepperInline', () => {
|
|
|
28
28
|
|
|
29
29
|
it('displays additional information when provided', () => {
|
|
30
30
|
const stepsWithInfo = [
|
|
31
|
-
{ label: 'Step 1', additionalInfo: 'Info 1' },
|
|
32
|
-
{ label: 'Step 2', additionalInfo: 'Info 2' },
|
|
31
|
+
{ id: '1', label: 'Step 1', additionalInfo: 'Info 1' },
|
|
32
|
+
{ id: '1', label: 'Step 2', additionalInfo: 'Info 2' },
|
|
33
33
|
];
|
|
34
34
|
const wrapper = mount(MStepperInline, {
|
|
35
35
|
props: {
|
|
36
|
-
currentStep: 1,
|
|
36
|
+
currentStep: '1',
|
|
37
37
|
steps: stepsWithInfo,
|
|
38
38
|
},
|
|
39
39
|
});
|
|
@@ -46,33 +46,68 @@ describe('MStepperInline', () => {
|
|
|
46
46
|
);
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
describe('currentStep as a string', () => {
|
|
50
|
+
it('marks the correct step as active', async () => {
|
|
51
|
+
const wrapper = mount(MStepperInline, {
|
|
52
|
+
props: {
|
|
53
|
+
currentStep: '2',
|
|
54
|
+
steps: defaultSteps,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const labels = wrapper.findAll('.mc-stepper-inline__label');
|
|
59
|
+
expect(labels[1].classes()).toContain('is-current');
|
|
60
|
+
expect(labels[0].classes()).not.toContain('is-current');
|
|
61
|
+
|
|
62
|
+
await wrapper.setProps({ currentStep: '3' });
|
|
63
|
+
expect(labels[2].classes()).toContain('is-current');
|
|
55
64
|
});
|
|
56
65
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
it('marks completed steps', () => {
|
|
67
|
+
const wrapper = mount(MStepperInline, {
|
|
68
|
+
props: {
|
|
69
|
+
currentStep: '3',
|
|
70
|
+
steps: defaultSteps,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
60
73
|
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
const items = wrapper.findAll('.mc-stepper-inline__item');
|
|
75
|
+
expect(items[0].classes()).toContain('is-completed');
|
|
76
|
+
expect(items[1].classes()).toContain('is-completed');
|
|
77
|
+
expect(items[2].classes()).not.toContain('is-completed');
|
|
78
|
+
});
|
|
63
79
|
});
|
|
64
80
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
currentStep:
|
|
69
|
-
|
|
70
|
-
|
|
81
|
+
describe('currentStep as number', () => {
|
|
82
|
+
it('clamps to the first step when currentStep is below 1', () => {
|
|
83
|
+
const wrapper = mount(MStepperInline, {
|
|
84
|
+
props: { currentStep: 0, steps: defaultSteps },
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const circles = wrapper.findAll('.mc-stepper-inline__circle');
|
|
88
|
+
expect(circles[0].classes()).toContain('is-current');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('clamps to the last step when currentStep exceeds steps length', () => {
|
|
92
|
+
const wrapper = mount(MStepperInline, {
|
|
93
|
+
props: { currentStep: 99, steps: defaultSteps },
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const items = wrapper.findAll('.mc-stepper-inline__item');
|
|
97
|
+
expect(items[items.length - 1].classes()).not.toContain('is-completed');
|
|
98
|
+
expect(items[0].classes()).toContain('is-completed');
|
|
99
|
+
expect(items[1].classes()).toContain('is-completed');
|
|
71
100
|
});
|
|
72
101
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
102
|
+
it('sets the correct step as current when currentStep is a valid number', () => {
|
|
103
|
+
const wrapper = mount(MStepperInline, {
|
|
104
|
+
props: { currentStep: 2, steps: defaultSteps },
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const labels = wrapper.findAll('.mc-stepper-inline__label');
|
|
108
|
+
expect(labels[1].classes()).toContain('is-current');
|
|
109
|
+
expect(labels[0].classes()).not.toContain('is-current');
|
|
110
|
+
expect(labels[2].classes()).not.toContain('is-current');
|
|
111
|
+
});
|
|
77
112
|
});
|
|
78
113
|
});
|
|
@@ -14,12 +14,12 @@ const meta: Meta<typeof MStepperInline> = {
|
|
|
14
14
|
},
|
|
15
15
|
},
|
|
16
16
|
args: {
|
|
17
|
-
currentStep:
|
|
17
|
+
currentStep: 'step1',
|
|
18
18
|
steps: [
|
|
19
|
-
{ label: 'Cart' },
|
|
20
|
-
{ label: 'Delivery address' },
|
|
21
|
-
{ label: 'Payment' },
|
|
22
|
-
{ label: 'Order confirmation' },
|
|
19
|
+
{ id: 'step1', label: 'Cart' },
|
|
20
|
+
{ id: 'step2', label: 'Delivery address' },
|
|
21
|
+
{ id: 'step3', label: 'Payment' },
|
|
22
|
+
{ id: 'step4', label: 'Order confirmation' },
|
|
23
23
|
],
|
|
24
24
|
},
|
|
25
25
|
render: (args) => ({
|
|
@@ -38,12 +38,20 @@ export const Default: Story = {};
|
|
|
38
38
|
|
|
39
39
|
export const AditionnalInfo: Story = {
|
|
40
40
|
args: {
|
|
41
|
-
currentStep:
|
|
41
|
+
currentStep: 'step2',
|
|
42
42
|
steps: [
|
|
43
|
-
{ label: 'Cart', additionalInfo: 'Additional information' },
|
|
44
|
-
{
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
{ id: 'step1', label: 'Cart', additionalInfo: 'Additional information' },
|
|
44
|
+
{
|
|
45
|
+
id: 'step2',
|
|
46
|
+
label: 'Delivery address',
|
|
47
|
+
additionalInfo: 'Additional information',
|
|
48
|
+
},
|
|
49
|
+
{ id: 'step3', label: 'Payment', additionalInfo: 'Additional information' },
|
|
50
|
+
{
|
|
51
|
+
id: 'step4',
|
|
52
|
+
label: 'Order confirmation',
|
|
53
|
+
additionalInfo: 'Additional information',
|
|
54
|
+
},
|
|
47
55
|
],
|
|
48
56
|
},
|
|
49
57
|
};
|
|
@@ -52,13 +52,21 @@ import { Check24, ChevronRight24 } from '@mozaic-ds/icons-vue';
|
|
|
52
52
|
const props = withDefaults(
|
|
53
53
|
defineProps<{
|
|
54
54
|
/**
|
|
55
|
-
*
|
|
55
|
+
* Defines the currently active step.
|
|
56
|
+
*
|
|
57
|
+
* - If a `number` is provided, it represents the 1-based position of the step
|
|
58
|
+
* in the `steps` array (e.g. `1` for the first step).
|
|
59
|
+
* - If a `string` is provided, it must match the `id` of one of the steps.
|
|
56
60
|
*/
|
|
57
|
-
currentStep?: number;
|
|
61
|
+
currentStep?: string | number;
|
|
58
62
|
/**
|
|
59
63
|
* Steps of the stepper inline.
|
|
60
64
|
*/
|
|
61
65
|
steps?: Array<{
|
|
66
|
+
/**
|
|
67
|
+
* Uniquer identifier for the step.
|
|
68
|
+
*/
|
|
69
|
+
id?: string;
|
|
62
70
|
/**
|
|
63
71
|
* Label of the step.
|
|
64
72
|
*/
|
|
@@ -70,20 +78,26 @@ const props = withDefaults(
|
|
|
70
78
|
}>;
|
|
71
79
|
}>(),
|
|
72
80
|
{
|
|
73
|
-
currentStep:
|
|
81
|
+
currentStep: 0,
|
|
74
82
|
steps: () => [],
|
|
75
83
|
},
|
|
76
84
|
);
|
|
77
85
|
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
);
|
|
81
|
-
|
|
86
|
+
const activeStep = computed(() => {
|
|
87
|
+
if (typeof props.currentStep === 'number') {
|
|
88
|
+
return Math.min(Math.max(props.currentStep, 1), props.steps.length) - 1;
|
|
89
|
+
} else {
|
|
90
|
+
const activeIndex = props.steps.findIndex(
|
|
91
|
+
(step) => step.id === props.currentStep,
|
|
92
|
+
);
|
|
93
|
+
return activeIndex === -1 ? 0 : activeIndex;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
82
96
|
|
|
83
97
|
const stepStates = computed(() =>
|
|
84
|
-
steps.map((_, i) => ({
|
|
85
|
-
completed: i
|
|
86
|
-
current: i
|
|
98
|
+
props.steps.map((_, i) => ({
|
|
99
|
+
completed: i < activeStep.value,
|
|
100
|
+
current: i === activeStep.value,
|
|
87
101
|
})),
|
|
88
102
|
);
|
|
89
103
|
</script>
|
|
@@ -7,5 +7,9 @@ A stepper is a navigation component that guides users through a sequence of step
|
|
|
7
7
|
|
|
8
8
|
| Name | Description | Type | Default |
|
|
9
9
|
| --- | --- | --- | --- |
|
|
10
|
-
| `currentStep` |
|
|
11
|
-
|
|
10
|
+
| `currentStep` | Defines the currently active step.
|
|
11
|
+
|
|
12
|
+
- If a `number` is provided, it represents the 1-based position of the step
|
|
13
|
+
in the `steps` array (e.g. `1` for the first step).
|
|
14
|
+
- If a `string` is provided, it must match the `id` of one of the steps. | `string` `number` | `0` |
|
|
15
|
+
| `steps` | Steps of the stepper inline. | `{ id?: string` `undefined; label: string; additionalInfo?: string` `undefined; }[]` | `[]` |
|