@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
@@ -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[1].classes()).toContain(
30
+ expect(buttons[0].classes()).toContain(
31
31
  'mc-segmented-control__segment--selected',
32
32
  );
33
- expect(buttons[1].attributes('aria-checked')).toBe('true');
33
+ expect(buttons[0].attributes('aria-checked')).toBe('true');
34
34
  });
35
35
 
36
- it('emits update:modelValue and changes active segment on click', async () => {
37
- const wrapper = mount(MSegmentedControl, {
38
- props: { segments, modelValue: 0 },
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
- const buttons = wrapper.findAll('button');
42
- await buttons[2].trigger('click');
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
- expect(wrapper.emitted('update:modelValue')).toBeTruthy();
45
- expect(wrapper.emitted('update:modelValue')![0]).toEqual([2]);
59
+ const buttons = wrapper.findAll('button');
60
+ await buttons[1].trigger('click');
46
61
 
47
- expect(buttons[2].classes()).toContain(
48
- 'mc-segmented-control__segment--selected',
49
- );
50
- expect(buttons[2].attributes('aria-checked')).toBe('true');
62
+ expect(wrapper.emitted('update:modelValue')).toBeFalsy();
63
+ });
51
64
  });
52
65
 
53
- it('does not emit update event if clicking already active segment', async () => {
54
- const wrapper = mount(MSegmentedControl, {
55
- props: { segments, modelValue: 1 },
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
- const buttons = wrapper.findAll('button');
59
- await buttons[1].trigger('click');
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
- expect(wrapper.emitted('update:modelValue')).toBeFalsy();
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: 0 },
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: 2 });
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(index),
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
- * The selected segment index, bound via v-model.
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
- if (index !== modelValue.value) {
73
- modelValue.value = index;
74
- emit('update:modelValue', index);
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
- return modelValue.value === index;
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: number): void;
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` | The selected segment index, bound via v-model. | `number` | `0` |
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: number] |
22
+ | `update:modelValue` | Emits when the selected segment changes, updating the modelValue prop. | [value?: string | number] |
@@ -1,7 +1,8 @@
1
1
  <template>
2
- <div :class="{
3
- 'mc-select': true,
4
- [`mc-select--${size}`]: props.size && props.size != 'm',
2
+ <div
3
+ :class="{
4
+ 'mc-select': true,
5
+ [`mc-select--${size}`]: props.size && props.size != 'm',
5
6
  }"
6
7
  >
7
8
  <select
@@ -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
@@ -2,6 +2,14 @@
2
2
 
3
3
 
4
4
 
5
+ ## Events
6
+
7
+ | Name | Description | Type |
8
+ | --- | --- | --- |
9
+ | `update:modelValue` | - | `any[]` |
10
+ | `close` | - | `any[]` |
11
+ | `log-out` | - | `any[]` |
12
+
5
13
  ## Dependencies
6
14
 
7
15
  ### Depends on
@@ -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>
@@ -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
- it('marks the correct step as active', async () => {
50
- const wrapper = mount(MStepperInline, {
51
- props: {
52
- currentStep: 2,
53
- steps: defaultSteps,
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
- const labels = wrapper.findAll('.mc-stepper-inline__label');
58
- expect(labels[1].classes()).toContain('is-current');
59
- expect(labels[0].classes()).not.toContain('is-current');
66
+ it('marks completed steps', () => {
67
+ const wrapper = mount(MStepperInline, {
68
+ props: {
69
+ currentStep: '3',
70
+ steps: defaultSteps,
71
+ },
72
+ });
60
73
 
61
- await wrapper.setProps({ currentStep: 3 });
62
- expect(labels[2].classes()).toContain('is-current');
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
- it('marks completed steps', () => {
66
- const wrapper = mount(MStepperInline, {
67
- props: {
68
- currentStep: 3,
69
- steps: defaultSteps,
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
- const items = wrapper.findAll('.mc-stepper-inline__item');
74
- expect(items[0].classes()).toContain('is-completed');
75
- expect(items[1].classes()).toContain('is-completed');
76
- expect(items[2].classes()).not.toContain('is-completed');
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: 1,
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: 2,
41
+ currentStep: 'step2',
42
42
  steps: [
43
- { label: 'Cart', additionalInfo: 'Additional information' },
44
- { label: 'Delivery address', additionalInfo: 'Additional information' },
45
- { label: 'Payment', additionalInfo: 'Additional information' },
46
- { label: 'Order confirmation', additionalInfo: 'Additional information' },
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
- * Current step of the stepper compact.
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: 1,
81
+ currentStep: 0,
74
82
  steps: () => [],
75
83
  },
76
84
  );
77
85
 
78
- const safeStep = computed(() =>
79
- Math.min(Math.max(props.currentStep, 1), props.steps.length),
80
- );
81
- const steps = props.steps;
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 + 1 < safeStep.value,
86
- current: i + 1 === safeStep.value,
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` | Current step of the stepper compact. | `number` | `1` |
11
- | `steps` | Steps of the stepper inline. | `{ label: string; additionalInfo?: string` `undefined; }[]` | `[]` |
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; }[]` | `[]` |