@mozaic-ds/vue 2.6.1 → 2.7.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 (88) hide show
  1. package/README.md +3 -3
  2. package/dist/mozaic-vue.css +1 -1
  3. package/dist/mozaic-vue.d.ts +105 -63
  4. package/dist/mozaic-vue.js +1137 -1082
  5. package/dist/mozaic-vue.js.map +1 -1
  6. package/dist/mozaic-vue.umd.cjs +1 -1
  7. package/dist/mozaic-vue.umd.cjs.map +1 -1
  8. package/package.json +8 -6
  9. package/src/components/Contributing.mdx +1 -1
  10. package/src/components/GettingStarted.mdx +1 -1
  11. package/src/components/Introduction.mdx +1 -1
  12. package/src/components/Support.mdx +1 -1
  13. package/src/components/avatar/MAvatar.stories.ts +1 -0
  14. package/src/components/avatar/README.md +16 -0
  15. package/src/components/breadcrumb/README.md +11 -0
  16. package/src/components/button/MButton.stories.ts +1 -2
  17. package/src/components/button/MButton.vue +6 -1
  18. package/src/components/button/README.md +24 -0
  19. package/src/components/callout/MCallout.stories.ts +1 -0
  20. package/src/components/callout/README.md +19 -0
  21. package/src/components/checkbox/README.md +23 -0
  22. package/src/components/checkboxgroup/README.md +20 -0
  23. package/src/components/circularprogressbar/MCircularProgressbar.stories.ts +1 -0
  24. package/src/components/circularprogressbar/README.md +14 -0
  25. package/src/components/container/MContainer.stories.ts +8 -0
  26. package/src/components/container/MContainer.vue +1 -1
  27. package/src/components/container/README.md +16 -0
  28. package/src/components/datepicker/MDatepicker.stories.ts +1 -0
  29. package/src/components/datepicker/README.md +24 -0
  30. package/src/components/divider/MDivider.stories.ts +1 -0
  31. package/src/components/divider/README.md +18 -0
  32. package/src/components/drawer/MDrawer.spec.ts +28 -0
  33. package/src/components/drawer/MDrawer.stories.ts +1 -0
  34. package/src/components/drawer/MDrawer.vue +9 -2
  35. package/src/components/drawer/README.md +29 -0
  36. package/src/components/field/MField.vue +1 -1
  37. package/src/components/field/README.md +24 -0
  38. package/src/components/fieldgroup/README.md +22 -0
  39. package/src/components/flag/README.md +11 -0
  40. package/src/components/iconbutton/MIconButton.stories.ts +1 -0
  41. package/src/components/iconbutton/MIconButton.vue +1 -1
  42. package/src/components/iconbutton/README.md +21 -0
  43. package/src/components/linearprogressbarbuffer/MLinearProgressbarBuffer.stories.ts +1 -0
  44. package/src/components/linearprogressbarbuffer/README.md +11 -0
  45. package/src/components/linearprogressbarpercentage/MLinearProgressbarPercentage.stories.ts +1 -0
  46. package/src/components/linearprogressbarpercentage/README.md +10 -0
  47. package/src/components/link/MLink.vue +0 -2
  48. package/src/components/link/README.md +23 -0
  49. package/src/components/loader/MLoader.vue +1 -1
  50. package/src/components/loader/README.md +12 -0
  51. package/src/components/loadingoverlay/README.md +11 -0
  52. package/src/components/modal/README.md +28 -0
  53. package/src/components/numberbadge/README.md +12 -0
  54. package/src/components/overlay/README.md +17 -0
  55. package/src/components/pagination/README.md +20 -0
  56. package/src/components/passwordinput/README.md +25 -0
  57. package/src/components/pincode/MPincode.spec.ts +4 -1
  58. package/src/components/pincode/MPincode.stories.ts +1 -0
  59. package/src/components/pincode/MPincode.vue +5 -1
  60. package/src/components/pincode/README.md +22 -0
  61. package/src/components/quantityselector/README.md +27 -0
  62. package/src/components/radio/README.md +21 -0
  63. package/src/components/radiogroup/README.md +21 -0
  64. package/src/components/segmentedcontrol/MSegmentedControl.spec.ts +116 -0
  65. package/src/components/segmentedcontrol/MSegmentedControl.stories.ts +78 -0
  66. package/src/components/segmentedcontrol/MSegmentedControl.vue +92 -0
  67. package/src/components/segmentedcontrol/README.md +19 -0
  68. package/src/components/select/README.md +24 -0
  69. package/src/components/statusbadge/README.md +11 -0
  70. package/src/components/statusdot/MStatusDot.stories.ts +1 -0
  71. package/src/components/statusdot/README.md +11 -0
  72. package/src/components/statusnotification/README.md +25 -0
  73. package/src/components/tabs/MTabs.stories.ts +23 -1
  74. package/src/components/tabs/MTabs.vue +8 -0
  75. package/src/components/tabs/Mtabs.spec.ts +29 -8
  76. package/src/components/tabs/README.md +20 -0
  77. package/src/components/tag/README.md +25 -0
  78. package/src/components/textarea/README.md +25 -0
  79. package/src/components/textinput/README.md +32 -0
  80. package/src/components/toaster/MToaster.stories.ts +1 -0
  81. package/src/components/toaster/README.md +28 -0
  82. package/src/components/toggle/README.md +21 -0
  83. package/src/components/togglegroup/MToggleGroup.vue +1 -1
  84. package/src/components/togglegroup/README.md +20 -0
  85. package/src/components/tooltip/README.md +19 -0
  86. package/src/components/usingIcons.mdx +1 -1
  87. package/src/components/usingPresets.mdx +1 -1
  88. package/src/main.ts +1 -0
@@ -0,0 +1,11 @@
1
+ # MLinearProgressbarBuffer
2
+
3
+ A linear progress bar (Buffer) visually represents the progress of a task along a horizontal track, often indicating both current progress and a secondary buffered state. This type of progress bar is commonly used for loading processes, file uploads, or streaming indicators, where part of the task is completed while another portion is preloaded or buffered. It provides users with real-time feedback on task advancement.
4
+
5
+
6
+ ## Props
7
+
8
+ | Name | Description | Type | Default |
9
+ | --- | --- | --- | --- |
10
+ | `size` | Allows to define the progress bar size. | `"s"` `"m"` `"l"` | - |
11
+ | `value` | The current value of the progress bar. | `number` | `0` |
@@ -4,6 +4,7 @@ import MLinearProgressbarPercentage from './MLinearProgressbarPercentage.vue';
4
4
  const meta: Meta<typeof MLinearProgressbarPercentage> = {
5
5
  title: 'Indicators/Linear Progress Bar (Percentage)',
6
6
  component: MLinearProgressbarPercentage,
7
+ tags: ['v2'],
7
8
  parameters: {
8
9
  docs: {
9
10
  description: {
@@ -0,0 +1,10 @@
1
+ # MLinearProgressbarPercentage
2
+
3
+ A linear progress bar (Percentage) visually represents the completion of a task along a horizontal track, displaying the exact progress in percentage within the bar. It is commonly used for file uploads, installations, form completion, or any process requiring user awareness of progress. The percentage label provides clear and immediate feedback, helping users track progress with precision.
4
+
5
+
6
+ ## Props
7
+
8
+ | Name | Description | Type | Default |
9
+ | --- | --- | --- | --- |
10
+ | `value` | The current value of the progress bar. | `number` | `0` |
@@ -62,8 +62,6 @@ const props = withDefaults(
62
62
  router?: boolean;
63
63
  }>(),
64
64
  {
65
- href: undefined,
66
- target: undefined,
67
65
  appearance: 'standard',
68
66
  size: 's',
69
67
  iconPosition: 'left',
@@ -0,0 +1,23 @@
1
+ # MLink
2
+
3
+ A link is an interactive text element used to navigate between pages, sections, or external resources. It is typically underlined and styled to indicate its clickable nature. Links can be standalone or embedded within text, and they may include icons to reinforce their purpose. They are essential for navigation and content referencing in web and application interfaces.
4
+
5
+
6
+ ## Props
7
+
8
+ | Name | Description | Type | Default |
9
+ | --- | --- | --- | --- |
10
+ | `iconPosition` | Position of the icon relative to the text. | `"left"` `"right"` | `"left"` |
11
+ | `appearance` | Allows to define the link appearance. | `"standard"` `"inverse"` `"accent"` `"secondary"` | `"standard"` |
12
+ | `size` | Allows to define the link size. | `"s"` `"m"` | `"s"` |
13
+ | `href` | URL for the link (for external links or the `to` prop for `router-link`). | `string` | - |
14
+ | `target` | Where to open the link. | `"_self"` `"_blank"` `"_parent"` `"_top"` | - |
15
+ | `inline` | Specify wether the link is inline. | `boolean` | - |
16
+ | `router` | If `true`, the link will be rendered as a `router-link` for internal navigation (Vue Router). | `boolean` | - |
17
+
18
+ ## Slots
19
+
20
+ | Name | Description |
21
+ | --- | --- |
22
+ | `default` | Use this slot to insert the textual content of the Link. |
23
+ | `icon` | Use this slot to insert an icon for the Link. |
@@ -22,7 +22,7 @@
22
22
  <script setup lang="ts">
23
23
  import { computed } from 'vue';
24
24
  /**
25
- * A loader indicates that content or data is being loaded or processed, providing visual feedback to users during wait times.
25
+ * A loader is a visual indicator used to inform users that a process is in progress, typically during data fetching, page loading, or background operations. It provides feedback that the system is working, helping to manage user expectations and reduce perceived wait time.
26
26
  */
27
27
  const props = withDefaults(
28
28
  defineProps<{
@@ -0,0 +1,12 @@
1
+ # MLoader
2
+
3
+ A loader is a visual indicator used to inform users that a process is in progress, typically during data fetching, page loading, or background operations. It provides feedback that the system is working, helping to manage user expectations and reduce perceived wait time.
4
+
5
+
6
+ ## Props
7
+
8
+ | Name | Description | Type | Default |
9
+ | --- | --- | --- | --- |
10
+ | `appearance` | Specifies the visual appearance of the loader. | `"standard"` `"inverse"` `"accent"` | `"standard"` |
11
+ | `size` | Defines the size of the loader. | `"s"` `"m"` `"l"` | `"m"` |
12
+ | `text` | Text to display alongside the loader when using the loader inside an `Overlay`. | `string` | - |
@@ -0,0 +1,11 @@
1
+ # MLoadingOverlay
2
+
3
+ A loading overlay is a full-screen or container-level layer that indicates a process is in progress, preventing user interaction until the task is completed. It includes a progress indicator, and a message to inform users about the loading state. Loading Overlays are commonly used in data-heavy applications, form submissions, and page transitions to enhance user experience by managing wait times effectively.
4
+
5
+
6
+ ## Props
7
+
8
+ | Name | Description | Type | Default |
9
+ | --- | --- | --- | --- |
10
+ | `isVisible` | Controls the visibility of the loading overlay. | `boolean` | - |
11
+ | `text` | Text of the loading overlay. | `string` | - |
@@ -0,0 +1,28 @@
1
+ # MModal
2
+
3
+ A modal is a dialog window that appears on top of the main content, requiring user interaction before returning to the main interface. It is used to focus attention on a specific task, provide important information, or request confirmation for an action. Modals typically include a title, description, and primary/secondary actions and should be used for single, focused tasks to avoid disrupting the user experience.
4
+
5
+
6
+ ## Props
7
+
8
+ | Name | Description | Type | Default |
9
+ | --- | --- | --- | --- |
10
+ | `open` | if `true`, display the modal. | `boolean` | - |
11
+ | `title*` | Title of the modal. | `string` | - |
12
+ | `description` | Description of the modal. | `string` | - |
13
+ | `closable` | if `true`, display the close button. | `boolean` | `true` |
14
+
15
+ ## Slots
16
+
17
+ | Name | Description |
18
+ | --- | --- |
19
+ | `icon` | Use this slot to insert an icon next to the title of the modal. |
20
+ | `default` | Use this slot to insert the content of the modal. |
21
+ | `link` | Use this slot to insert a link in the footer. |
22
+ | `footer` | Use this slot to insert buttons in the footer. |
23
+
24
+ ## Events
25
+
26
+ | Name | Description | Type |
27
+ | --- | --- | --- |
28
+ | `update:open` | Emits when the modal display changes, updating the modelValue prop. | [value: boolean] |
@@ -0,0 +1,12 @@
1
+ # MNumberBadge
2
+
3
+ A Number Badge represents a numeric count, often used to indicate notifications, updates, or items requiring attention. Its distinct appearance makes it easy to spot changes at a glance, ensuring users stay informed without breaking their workflow. Badges are commonly attached to icons, buttons, or tabs to provide contextual awareness.
4
+
5
+
6
+ ## Props
7
+
8
+ | Name | Description | Type | Default |
9
+ | --- | --- | --- | --- |
10
+ | `label*` | Content of the badge. | `number` | - |
11
+ | `appearance` | Allows to define the badge appearance. | `"standard"` `"inverse"` `"accent"` `"danger"` | `"standard"` |
12
+ | `size` | Allows to define the badge size. | `"s"` `"m"` | `"s"` |
@@ -0,0 +1,17 @@
1
+ # MOverlay
2
+
3
+ An overlay is a semi-transparent layer that appears on top of the main content, typically used to dim the background and focus user attention on a specific element. It is often combined with modals, popovers, or loading states to create a visual separation between the foreground and background. Overlays help prevent unintended interactions while keeping the primary content accessible.
4
+
5
+
6
+ ## Props
7
+
8
+ | Name | Description | Type | Default |
9
+ | --- | --- | --- | --- |
10
+ | `isVisible` | Controls the visibility of the overlay. | `boolean` | - |
11
+ | `dialogLabel` | Accessible label for the overlay dialog. | `string` | - |
12
+
13
+ ## Slots
14
+
15
+ | Name | Description |
16
+ | --- | --- |
17
+ | `default` | Use this slot to insert a centered content inside the overlay. |
@@ -0,0 +1,20 @@
1
+ # MPagination
2
+
3
+ Pagination is a navigation component that allows users to browse through large sets of content by dividing it into discrete pages. It typically includes previous and next buttons, numeric page selectors, or dropdowns to jump between pages efficiently. Pagination improves usability and performance in content-heavy applications such as tables, search results, and articles by preventing long scrolls and reducing page load times.
4
+
5
+
6
+ ## Props
7
+
8
+ | Name | Description | Type | Default |
9
+ | --- | --- | --- | --- |
10
+ | `id*` | A unique identifier for the pagination. | `string` | - |
11
+ | `modelValue*` | The current value of the selected page. | `number` | - |
12
+ | `compact` | If `true`, display a compact version without the select. | `boolean` | - |
13
+ | `options*` | Define the available choices for the pagination select element. | `{ id?: string` `undefined; text: string; value: number; }[]` | - |
14
+ | `selectLabel` | Accessible label for the select of the pagination. | `string` | - |
15
+
16
+ ## Events
17
+
18
+ | Name | Description | Type |
19
+ | --- | --- | --- |
20
+ | `update:modelValue` | Emits when the pagination value changes, updating the modelValue prop. | [value: number] |
@@ -0,0 +1,25 @@
1
+ # MPasswordInput
2
+
3
+ A password input is a specialized input field used to securely enter and manage passwords. It typically masks the characters entered to protect sensitive information from being seen. It includes a toggle button to show or hide the password, improving usability while maintaining security. Password inputs are commonly used in login forms, account creation, and authentication flows.
4
+
5
+
6
+ ## Props
7
+
8
+ | Name | Description | Type | Default |
9
+ | --- | --- | --- | --- |
10
+ | `id*` | A unique identifier for the password input element, used to associate the label with the form element. | `string` | - |
11
+ | `name` | The name attribute for the password input element, typically used for form submission. | `string` | - |
12
+ | `modelValue` | The current value of the password input field. | `string` `number` | - |
13
+ | `placeholder` | A placeholder text to show in the password input when it is empty. | `string` | - |
14
+ | `isInvalid` | If `true`, applies an invalid state to the password input. | `boolean` | - |
15
+ | `disabled` | If `true`, disables the password input, making it non-interactive. | `boolean` | - |
16
+ | `readonly` | If `true`, the password input is read-only (cannot be edited). | `boolean` | - |
17
+ | `isClearable` | If `true`, a clear button will appear when the password input has a value. | `boolean` | - |
18
+ | `clearLabel` | The label text for the clear button. | `string` | `"Clear content"` |
19
+ | `buttonLabel` | Labels of the button displayed when showing or hiding the password. | `{ show: string; hide: string; }` | `{ show: "Show", hide: "Hide" }` |
20
+
21
+ ## Events
22
+
23
+ | Name | Description | Type |
24
+ | --- | --- | --- |
25
+ | `update:modelValue` | Emits when the input value changes, updating the `modelValue` prop. | [value: string | number] |
@@ -93,7 +93,10 @@ describe('MPincode component', () => {
93
93
  },
94
94
  });
95
95
 
96
- expect(wrapper.classes()).toContain('is-invalid');
96
+ const inputs = wrapper.findAll('input');
97
+ for (const input of inputs) {
98
+ expect(input.classes()).toContain('is-invalid');
99
+ }
97
100
  });
98
101
 
99
102
  it('disables inputs when disabled is true', () => {
@@ -6,6 +6,7 @@ import MPincode from './MPincode.vue';
6
6
  const meta: Meta<typeof MPincode> = {
7
7
  title: 'Form Elements/Pincode',
8
8
  component: MPincode,
9
+ tags: ['v2'],
9
10
  parameters: {
10
11
  docs: {
11
12
  description: {
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="mc-pincode-input" :class="classObject" @paste="onPaste">
2
+ <div class="mc-pincode-input" @paste="onPaste">
3
3
  <input
4
4
  v-for="(digit, index) in otp"
5
5
  :key="index"
@@ -12,6 +12,7 @@
12
12
  autocomplete="one-time-code"
13
13
  :name="name || `pincode-${id}`"
14
14
  class="mc-pincode-input__control"
15
+ :class="classObject"
15
16
  :disabled="disabled"
16
17
  :readonly="readonly"
17
18
  :value="digit"
@@ -77,6 +78,9 @@ const classObject = computed(() => {
77
78
  });
78
79
 
79
80
  const emit = defineEmits<{
81
+ /**
82
+ * Emits when the pincode value changes, updating the modelValue prop.
83
+ */
80
84
  (on: 'update:modelValue', value: string): void;
81
85
  }>();
82
86
 
@@ -0,0 +1,22 @@
1
+ # MPincode
2
+
3
+ A pincode input is a specialized input field used to enter short numeric codes, such as verification codes, security PINs, or authentication tokens. It typically separates each digit into individual fields to improve readability and ease of entry. This component is commonly used in two-factor authentication (2FA), password recovery, and secure access flows, ensuring a structured and user-friendly experience.<br><br> To put a label, requierement text, help text or to apply a valid or invalid message, the examples are available in the [Field section](/docs/form-elements-field--docs#input).
4
+
5
+
6
+ ## Props
7
+
8
+ | Name | Description | Type | Default |
9
+ | --- | --- | --- | --- |
10
+ | `id*` | A unique identifier for the pincode element, used to associate the label with the form element. | `string` | - |
11
+ | `length` | The number of input displayed in the pincode element. | `4` `5` `6` | `6` |
12
+ | `name` | The name attribute for the pincode element, typically used for form submission. | `string` | - |
13
+ | `modelValue` | The current value of the pincode field. | `string` `number` | - |
14
+ | `isInvalid` | If `true`, applies an invalid state to the pincode. | `boolean` | - |
15
+ | `disabled` | If `true`, disables the pincode, making it non-interactive. | `boolean` | - |
16
+ | `readonly` | If `true`, the pincode is read-only (cannot be edited). | `boolean` | - |
17
+
18
+ ## Events
19
+
20
+ | Name | Description | Type |
21
+ | --- | --- | --- |
22
+ | `update:modelValue` | - | `[value: string]` |
@@ -0,0 +1,27 @@
1
+ # MQuantitySelector
2
+
3
+ A quantity selector is an input component that allows users to increment or decrement a numeric value, typically using plus (+) and minus (−) buttons. It provides a simple and efficient way to adjust quantities without manual typing, ensuring controlled input. This component is commonly used in e-commerce, inventory management, and settings where users need to specify amounts.
4
+
5
+
6
+ ## Props
7
+
8
+ | Name | Description | Type | Default |
9
+ | --- | --- | --- | --- |
10
+ | `id*` | A unique identifier for the quantity selector element, used to associate the label with the form element. | `string` | - |
11
+ | `name` | The name attribute for the quantity selector element, typically used for form submission. | `string` | `"quantity-selector-input"` |
12
+ | `modelValue` | The current value of the quantity selector field. | `number` | `1` |
13
+ | `isInvalid` | If `true`, applies an invalid state to the quantity selector. | `boolean` | - |
14
+ | `disabled` | If `true`, disables the quantity selector, making it non-interactive. | `boolean` | - |
15
+ | `size` | Determines the size of the quantity selector. | `"s"` `"m"` | `"m"` |
16
+ | `min` | Minimum acceptable value for the quantity selector. | `number` | `1` |
17
+ | `max` | Maximum acceptable value for the quantity selector. | `number` | `100` |
18
+ | `step` | Determines how much the value will change per click when the quantity is increased or decreased. | `number` | `1` |
19
+ | `readonly` | If `true`, the quantity selector is read-only (cannot be edited). | `boolean` | - |
20
+ | `incrementlabel` | The label text for the increment button. | `string` | `"Increment"` |
21
+ | `decrementLabel` | The label text for the decrement button. | `string` | `"Decrement"` |
22
+
23
+ ## Events
24
+
25
+ | Name | Description | Type |
26
+ | --- | --- | --- |
27
+ | `update:modelValue` | Emits when the quantity selector value changes, updating the `modelValue` prop. | [value: number] |
@@ -0,0 +1,21 @@
1
+ # MRadio
2
+
3
+ A radio button is a selection control that allows users to choose a single option from a list of mutually exclusive choices. Unlike checkboxes, only one option can be selected at a time within the same group. Radio Buttons are commonly used in forms, surveys, and settings where a single choice must be made.
4
+
5
+
6
+ ## Props
7
+
8
+ | Name | Description | Type | Default |
9
+ | --- | --- | --- | --- |
10
+ | `id*` | A unique identifier for the radio, used to associate the label with the form element. | `string` | - |
11
+ | `name` | The name attribute for the radio element, typically used for form submission. | `string` | - |
12
+ | `label` | The text label displayed next to the radio. | `string` | - |
13
+ | `modelValue` | The radio's checked state, bound via v-model. | `boolean` | - |
14
+ | `isInvalid` | If `true`, applies an invalid state to the radio. | `boolean` | - |
15
+ | `disabled` | If `true`, disables the radio, making it non-interactive. | `boolean` | - |
16
+
17
+ ## Events
18
+
19
+ | Name | Description | Type |
20
+ | --- | --- | --- |
21
+ | `update:modelValue` | Emits when the radio value changes, updating the modelValue prop. | [value: boolean] |
@@ -0,0 +1,21 @@
1
+ # MRadioGroup
2
+
3
+ A radio button is a selection control that allows users to choose a single option from a list of mutually exclusive choices. Unlike checkboxes, only one option can be selected at a time within the same group. Radio Buttons are commonly used in forms, surveys, and settings where a single choice must be made.<br><br> To put a label, requierement text, help text or to apply a valid or invalid message, the examples are available in the [Field Group section](/docs/form-elements-field-group--docs#radio-group).
4
+
5
+
6
+ ## Props
7
+
8
+ | Name | Description | Type | Default |
9
+ | --- | --- | --- | --- |
10
+ | `name*` | The name attribute for the radio element, typically used for form submission. | `string` | - |
11
+ | `modelValue` | Property used to manage the values checked by v-model
12
+ (Do not use directly) | `string` | - |
13
+ | `options*` | list of properties of each radio button of the radio group | `{ id: string; label: string; value: string; disabled?: boolean` `undefined; }[]` | - |
14
+ | `isInvalid` | If `true`, applies an invalid state to the radio group. | `boolean` | - |
15
+ | `inline` | If `true`, make the form element of the group inline. | `boolean` | - |
16
+
17
+ ## Events
18
+
19
+ | Name | Description | Type |
20
+ | --- | --- | --- |
21
+ | `update:modelValue` | Emits when the radio group value changes, updating the modelValue prop. | [value: string] |
@@ -0,0 +1,116 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import { describe, it, expect } from 'vitest';
3
+ import MSegmentedControl from './MSegmentedControl.vue';
4
+
5
+ describe('MSegmentedControl.vue', () => {
6
+ const segments = [
7
+ { label: 'First' },
8
+ { label: 'Second' },
9
+ { label: 'Third' },
10
+ ];
11
+
12
+ it('renders segments with correct labels', () => {
13
+ const wrapper = mount(MSegmentedControl, {
14
+ props: { segments },
15
+ });
16
+
17
+ const buttons = wrapper.findAll('button');
18
+ expect(buttons).toHaveLength(segments.length);
19
+ buttons.forEach((btn, i) => {
20
+ expect(btn.text()).toBe(segments[i].label);
21
+ });
22
+ });
23
+
24
+ it('sets default active segment based on modelValue prop', () => {
25
+ const wrapper = mount(MSegmentedControl, {
26
+ props: { segments, modelValue: 1 },
27
+ });
28
+
29
+ const buttons = wrapper.findAll('button');
30
+ expect(buttons[1].classes()).toContain(
31
+ 'mc-segmented-control__segment--selected',
32
+ );
33
+ expect(buttons[1].attributes('aria-checked')).toBe('true');
34
+ });
35
+
36
+ it('emits update:modelValue and changes active segment on click', async () => {
37
+ const wrapper = mount(MSegmentedControl, {
38
+ props: { segments, modelValue: 0 },
39
+ });
40
+
41
+ const buttons = wrapper.findAll('button');
42
+ await buttons[2].trigger('click');
43
+
44
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy();
45
+ expect(wrapper.emitted('update:modelValue')![0]).toEqual([2]);
46
+
47
+ expect(buttons[2].classes()).toContain(
48
+ 'mc-segmented-control__segment--selected',
49
+ );
50
+ expect(buttons[2].attributes('aria-checked')).toBe('true');
51
+ });
52
+
53
+ it('does not emit update event if clicking already active segment', async () => {
54
+ const wrapper = mount(MSegmentedControl, {
55
+ props: { segments, modelValue: 1 },
56
+ });
57
+
58
+ const buttons = wrapper.findAll('button');
59
+ await buttons[1].trigger('click');
60
+
61
+ expect(wrapper.emitted('update:modelValue')).toBeFalsy();
62
+ });
63
+
64
+ it('applies full width class when full prop is true', () => {
65
+ const wrapper = mount(MSegmentedControl, {
66
+ props: { segments, full: true },
67
+ });
68
+
69
+ expect(wrapper.classes()).toContain('mc-segmented-control--full');
70
+ });
71
+
72
+ it('applies size class when size prop is "m"', () => {
73
+ const wrapper = mount(MSegmentedControl, {
74
+ props: { segments, size: 'm' },
75
+ });
76
+
77
+ expect(wrapper.classes()).toContain('mc-segmented-control--m');
78
+ });
79
+
80
+ it('does not apply size class when size is "s" (default)', () => {
81
+ const wrapper = mount(MSegmentedControl, {
82
+ props: { segments },
83
+ });
84
+
85
+ expect(wrapper.classes()).not.toContain('mc-segmented-control--s');
86
+ });
87
+
88
+ it('updates active segment when modelValue prop changes', async () => {
89
+ const wrapper = mount(MSegmentedControl, {
90
+ props: { segments, modelValue: 0 },
91
+ });
92
+
93
+ const buttons = wrapper.findAll('button');
94
+ expect(buttons[0].classes()).toContain(
95
+ 'mc-segmented-control__segment--selected',
96
+ );
97
+
98
+ await wrapper.setProps({ modelValue: 2 });
99
+
100
+ expect(buttons[2].classes()).toContain(
101
+ 'mc-segmented-control__segment--selected',
102
+ );
103
+ expect(buttons[2].attributes('aria-checked')).toBe('true');
104
+ });
105
+
106
+ it('adds role="radio" attribute to buttons', () => {
107
+ const wrapper = mount(MSegmentedControl, {
108
+ props: { segments },
109
+ });
110
+
111
+ const buttons = wrapper.findAll('button');
112
+ buttons.forEach((button) => {
113
+ expect(button.attributes('role')).toBe('radio');
114
+ });
115
+ });
116
+ });
@@ -0,0 +1,78 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite';
2
+ import { action } from 'storybook/actions';
3
+
4
+ import MSegmentedControl from './MSegmentedControl.vue';
5
+
6
+ const meta: Meta<typeof MSegmentedControl> = {
7
+ title: 'Action/Segmented Control',
8
+ component: MSegmentedControl,
9
+ parameters: {
10
+ docs: {
11
+ description: {
12
+ component:
13
+ 'A Segmented Control allows users to switch between multiple options or views within a single container. It provides a compact and efficient way to toggle between sections without requiring a dropdown or separate navigation. Segmented Controls are commonly used in filters, tabbed navigation, and content selection to enhance user interaction and accessibility.',
14
+ },
15
+ },
16
+ },
17
+ args: {
18
+ segments: [
19
+ {
20
+ label: 'Label',
21
+ },
22
+ {
23
+ label: 'Label',
24
+ },
25
+ {
26
+ label: 'Label',
27
+ },
28
+ {
29
+ label: 'Label',
30
+ },
31
+ ],
32
+ },
33
+ render: (args) => ({
34
+ components: { MSegmentedControl },
35
+ setup() {
36
+ const handleUpdate = action('update:modelValue');
37
+
38
+ return { args, handleUpdate };
39
+ },
40
+ template: `
41
+ <MSegmentedControl
42
+ v-bind="args"
43
+ @update:modelValue="handleUpdate"
44
+ ></MSegmentedControl>
45
+ `,
46
+ }),
47
+ };
48
+ export default meta;
49
+ type Story = StoryObj<typeof MSegmentedControl>;
50
+
51
+ export const Default: Story = {};
52
+
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
+ export const Size: Story = {
73
+ args: { size: 'm' },
74
+ };
75
+
76
+ export const Full: Story = {
77
+ args: { full: true },
78
+ };
@@ -0,0 +1,92 @@
1
+ <template>
2
+ <div class="mc-segmented-control" :class="classObject" role="radiogroup">
3
+ <button
4
+ v-for="(segment, index) in segments"
5
+ :key="`segment-${index}`"
6
+ type="button"
7
+ class="mc-segmented-control__segment"
8
+ :class="{
9
+ 'mc-segmented-control__segment--selected': isSegmentSelected(index),
10
+ }"
11
+ :aria-checked="isSegmentSelected(index)"
12
+ role="radio"
13
+ @click="onClickSegment(index)"
14
+ >
15
+ {{ segment.label }}
16
+ </button>
17
+ </div>
18
+ </template>
19
+
20
+ <script setup lang="ts">
21
+ import { computed, ref, watch } from 'vue';
22
+ /**
23
+ * A Segmented Control allows users to switch between multiple options or views within a single container. It provides a compact and efficient way to toggle between sections without requiring a dropdown or separate navigation. Segmented Controls are commonly used in filters, tabbed navigation, and content selection to enhance user interaction and accessibility.
24
+ */
25
+ const props = withDefaults(
26
+ defineProps<{
27
+ /**
28
+ * The selected segment index, bound via v-model.
29
+ */
30
+ modelValue?: number;
31
+ /**
32
+ * if `true`, the segmented control take the full width.
33
+ */
34
+ full?: boolean;
35
+ /**
36
+ * Determines the size of the segmented control.
37
+ */
38
+ size?: 's' | 'm';
39
+ /**
40
+ * An array of objects that allows you to provide all the data needed to generate the content for each segment.
41
+ */
42
+ segments: Array<{
43
+ /**
44
+ * The label displayed for the segment.
45
+ */
46
+ label: string;
47
+ }>;
48
+ }>(),
49
+ {
50
+ modelValue: 0,
51
+ size: 's',
52
+ },
53
+ );
54
+
55
+ const classObject = computed(() => {
56
+ return {
57
+ 'mc-segmented-control--full': props.full,
58
+ [`mc-segmented-control--${props.size}`]: props.size && props.size != 's',
59
+ };
60
+ });
61
+
62
+ const modelValue = ref(props.modelValue);
63
+
64
+ watch(
65
+ () => props.modelValue,
66
+ (newVal) => {
67
+ modelValue.value = newVal;
68
+ },
69
+ );
70
+
71
+ const onClickSegment = (index: number) => {
72
+ if (index !== modelValue.value) {
73
+ modelValue.value = index;
74
+ emit('update:modelValue', index);
75
+ }
76
+ };
77
+
78
+ const isSegmentSelected = (index: number) => {
79
+ return modelValue.value === index;
80
+ };
81
+
82
+ const emit = defineEmits<{
83
+ /**
84
+ * Emits when the selected segment changes, updating the modelValue prop.
85
+ */
86
+ (on: 'update:modelValue', value: number): void;
87
+ }>();
88
+ </script>
89
+
90
+ <style lang="scss" scoped>
91
+ @use '@mozaic-ds/styles/components/segmented-control';
92
+ </style>