@mozaic-ds/vue 1.0.0-beta.3 → 1.0.0-beta.5

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 (191) hide show
  1. package/LICENSE +51 -0
  2. package/README.md +224 -82
  3. package/dist/mozaic-vue.css +1 -1
  4. package/dist/mozaic-vue.d.ts +1202 -0
  5. package/dist/mozaic-vue.js +1220 -0
  6. package/dist/mozaic-vue.js.map +1 -0
  7. package/dist/mozaic-vue.umd.cjs +2 -0
  8. package/dist/mozaic-vue.umd.cjs.map +1 -0
  9. package/env.d.ts +1 -0
  10. package/package.json +81 -50
  11. package/src/components/Contributing.mdx +118 -0
  12. package/src/components/GettingStarted.mdx +50 -0
  13. package/src/components/Introduction.mdx +54 -0
  14. package/src/components/Support.mdx +18 -0
  15. package/src/components/badge/MBadge.spec.ts +16 -0
  16. package/src/components/badge/MBadge.stories.ts +50 -0
  17. package/src/components/badge/MBadge.vue +36 -34
  18. package/src/components/breadcrumb/MBreadcrumb.spec.ts +105 -0
  19. package/src/components/breadcrumb/MBreadcrumb.stories.ts +57 -0
  20. package/src/components/breadcrumb/MBreadcrumb.vue +52 -55
  21. package/src/components/button/MButton.spec.ts +191 -0
  22. package/src/components/button/MButton.stories.ts +66 -0
  23. package/src/components/button/MButton.vue +98 -154
  24. package/src/components/checkbox/MCheckbox.spec.ts +104 -0
  25. package/src/components/checkbox/MCheckbox.stories.ts +83 -0
  26. package/src/components/checkbox/MCheckbox.vue +60 -101
  27. package/src/components/checkboxgroup/MCheckboxGroup.spec.ts +78 -0
  28. package/src/components/checkboxgroup/MCheckboxGroup.stories.ts +61 -0
  29. package/src/components/checkboxgroup/MCheckboxGroup.vue +97 -0
  30. package/src/components/field/MField.spec.ts +166 -0
  31. package/src/components/field/MField.stories.ts +376 -0
  32. package/src/components/field/MField.vue +78 -61
  33. package/src/components/fieldgroup/MFieldGroup.spec.ts +165 -0
  34. package/src/components/fieldgroup/MFieldGroup.stories.ts +423 -0
  35. package/src/components/fieldgroup/MFieldGroup.vue +79 -0
  36. package/src/components/iconbutton/MIconButton.spec.ts +108 -0
  37. package/src/components/iconbutton/MIconButton.stories.ts +66 -0
  38. package/src/components/iconbutton/MIconButton.vue +73 -0
  39. package/src/components/link/MLink.spec.ts +154 -0
  40. package/src/components/link/MLink.stories.ts +98 -0
  41. package/src/components/link/MLink.vue +86 -109
  42. package/src/components/loader/MLoader.spec.ts +104 -0
  43. package/src/components/loader/MLoader.stories.ts +45 -0
  44. package/src/components/loader/MLoader.vue +65 -55
  45. package/src/components/overlay/MOverlay.spec.ts +51 -0
  46. package/src/components/overlay/MOverlay.stories.ts +40 -0
  47. package/src/components/overlay/MOverlay.vue +27 -19
  48. package/src/components/passwordinput/MPasswordInput.spec.ts +104 -0
  49. package/src/components/passwordinput/MPasswordInput.stories.ts +75 -0
  50. package/src/components/passwordinput/MPasswordInput.vue +129 -76
  51. package/src/components/quantityselector/MQuantitySelector.spec.ts +262 -0
  52. package/src/components/quantityselector/MQuantitySelector.stories.ts +89 -0
  53. package/src/components/quantityselector/MQuantitySelector.vue +160 -136
  54. package/src/components/radio/MRadio.spec.ts +104 -0
  55. package/src/components/radio/MRadio.stories.ts +68 -0
  56. package/src/components/radio/MRadio.vue +56 -39
  57. package/src/components/radiogroup/MRadioGroup.spec.ts +54 -0
  58. package/src/components/radiogroup/MRadioGroup.stories.ts +61 -0
  59. package/src/components/radiogroup/MRadioGroup.vue +79 -0
  60. package/src/components/select/MSelect.spec.ts +114 -0
  61. package/src/components/select/MSelect.stories.ts +101 -0
  62. package/src/components/select/MSelect.vue +77 -119
  63. package/src/components/statusbadge/MStatusBadge.stories.ts +45 -0
  64. package/src/components/statusbadge/MStatusBadge.vue +40 -0
  65. package/src/components/statusbadge/MstatusBadge.spec.ts +16 -0
  66. package/src/components/statusdot/MStatusDot.spec.ts +51 -0
  67. package/src/components/statusdot/MStatusDot.stories.ts +48 -0
  68. package/src/components/statusdot/MStatusDot.vue +36 -0
  69. package/src/components/statusnotification/MStatusNotification.spec.ts +99 -0
  70. package/src/components/statusnotification/MStatusNotification.stories.ts +96 -0
  71. package/src/components/statusnotification/MStatusNotification.vue +106 -0
  72. package/src/components/textarea/MTextArea.spec.ts +112 -0
  73. package/src/components/textarea/MTextArea.stories.ts +67 -0
  74. package/src/components/textarea/MTextArea.vue +81 -42
  75. package/src/components/textinput/MTextInput.spec.ts +121 -0
  76. package/src/components/textinput/MTextInput.stories.ts +114 -0
  77. package/src/components/textinput/MTextInput.vue +127 -47
  78. package/src/components/toggle/MToggle.spec.ts +99 -0
  79. package/src/components/toggle/MToggle.stories.ts +68 -0
  80. package/src/components/toggle/MToggle.vue +63 -103
  81. package/src/components/togglegroup/MToggleGroup.spec.ts +78 -0
  82. package/src/components/togglegroup/MToggleGroup.stories.ts +61 -0
  83. package/src/components/togglegroup/MToggleGroup.vue +97 -0
  84. package/src/components/usingIcons.mdx +43 -0
  85. package/src/components/usingPresets.mdx +125 -0
  86. package/src/main.ts +47 -0
  87. package/dist/demo.html +0 -1
  88. package/dist/mozaic-vue.adeo.css +0 -45
  89. package/dist/mozaic-vue.adeo.umd.js +0 -41775
  90. package/dist/mozaic-vue.common.js +0 -41765
  91. package/dist/mozaic-vue.common.js.map +0 -1
  92. package/dist/mozaic-vue.umd.js +0 -41776
  93. package/dist/mozaic-vue.umd.js.map +0 -1
  94. package/dist/mozaic-vue.umd.min.js +0 -4
  95. package/dist/mozaic-vue.umd.min.js.map +0 -1
  96. package/postinstall.js +0 -3
  97. package/src/components/accordion/MAccordion.vue +0 -128
  98. package/src/components/accordion/index.js +0 -7
  99. package/src/components/autocomplete/MAutocomplete.vue +0 -198
  100. package/src/components/autocomplete/index.js +0 -7
  101. package/src/components/badge/index.js +0 -7
  102. package/src/components/breadcrumb/index.js +0 -7
  103. package/src/components/button/index.js +0 -7
  104. package/src/components/card/MCard.vue +0 -78
  105. package/src/components/card/index.js +0 -7
  106. package/src/components/checkbox/MCheckboxGroup.vue +0 -155
  107. package/src/components/checkbox/index.js +0 -12
  108. package/src/components/container/MContainer.vue +0 -33
  109. package/src/components/container/index.js +0 -7
  110. package/src/components/datatable/MDataTable.vue +0 -651
  111. package/src/components/datatable/MDataTableHeader.vue +0 -55
  112. package/src/components/datatable/MDataTableTop.vue +0 -35
  113. package/src/components/datatable/helpers.js +0 -132
  114. package/src/components/datatable/index.js +0 -12
  115. package/src/components/field/index.js +0 -7
  116. package/src/components/fileuploader/MFileResult.vue +0 -149
  117. package/src/components/fileuploader/MFileUploader.vue +0 -142
  118. package/src/components/fileuploader/index.js +0 -7
  119. package/src/components/flag/MFlag.vue +0 -46
  120. package/src/components/flag/index.js +0 -7
  121. package/src/components/heading/MHeading.vue +0 -75
  122. package/src/components/heading/index.js +0 -7
  123. package/src/components/hero/MHero.vue +0 -93
  124. package/src/components/hero/index.js +0 -7
  125. package/src/components/icon/MIcon.vue +0 -120
  126. package/src/components/icon/index.js +0 -7
  127. package/src/components/index.js +0 -43
  128. package/src/components/layer/MLayer.vue +0 -208
  129. package/src/components/layer/index.js +0 -7
  130. package/src/components/link/index.js +0 -7
  131. package/src/components/listbox/MListBox.vue +0 -106
  132. package/src/components/listbox/index.js +0 -7
  133. package/src/components/loader/index.js +0 -7
  134. package/src/components/modal/MModal.vue +0 -179
  135. package/src/components/modal/index.js +0 -7
  136. package/src/components/notification/MNotification.vue +0 -110
  137. package/src/components/notification/index.js +0 -7
  138. package/src/components/optionbutton/MOptionButton.vue +0 -67
  139. package/src/components/optionbutton/index.js +0 -7
  140. package/src/components/optioncard/MOptionCard.vue +0 -132
  141. package/src/components/optioncard/index.js +0 -7
  142. package/src/components/optiongroup/MOptionGroup.vue +0 -18
  143. package/src/components/optiongroup/index.js +0 -7
  144. package/src/components/overlay/MOverlayLoader.vue +0 -43
  145. package/src/components/overlay/index.js +0 -12
  146. package/src/components/pagination/MPagination.vue +0 -162
  147. package/src/components/pagination/index.js +0 -7
  148. package/src/components/passwordinput/index.js +0 -7
  149. package/src/components/phonenumber/MPhoneNumber.vue +0 -390
  150. package/src/components/phonenumber/index.js +0 -7
  151. package/src/components/progressbar/MProgress.vue +0 -102
  152. package/src/components/progressbar/index.js +0 -7
  153. package/src/components/quantityselector/index.js +0 -7
  154. package/src/components/radio/MRadioGroup.vue +0 -111
  155. package/src/components/radio/index.js +0 -12
  156. package/src/components/ratingstars/MStarsInput.vue +0 -118
  157. package/src/components/ratingstars/MStarsResult.vue +0 -89
  158. package/src/components/ratingstars/index.js +0 -12
  159. package/src/components/select/index.js +0 -7
  160. package/src/components/stepper/MStepper.vue +0 -70
  161. package/src/components/stepper/index.js +0 -7
  162. package/src/components/tabs/MTab.vue +0 -184
  163. package/src/components/tabs/index.js +0 -7
  164. package/src/components/tags/MTag.vue +0 -173
  165. package/src/components/tags/index.js +0 -7
  166. package/src/components/textarea/index.js +0 -7
  167. package/src/components/textinput/MTextInputField.vue +0 -105
  168. package/src/components/textinput/MTextInputIcon.vue +0 -42
  169. package/src/components/textinput/index.js +0 -7
  170. package/src/components/toggle/index.js +0 -7
  171. package/src/components/tooltip/MTooltip.vue +0 -42
  172. package/src/components/tooltip/index.js +0 -7
  173. package/src/index.js +0 -62
  174. package/src/shims-tsx.d.ts +0 -13
  175. package/src/shims.vue.d.ts +0 -4
  176. package/src/tokens/adeo/android/colors.xml +0 -391
  177. package/src/tokens/adeo/android/font_dimens.xml +0 -18
  178. package/src/tokens/adeo/css/_variables.scss +0 -385
  179. package/src/tokens/adeo/css/root.scss +0 -387
  180. package/src/tokens/adeo/ios/StyleDictionaryColor.h +0 -399
  181. package/src/tokens/adeo/ios/StyleDictionaryColor.m +0 -411
  182. package/src/tokens/adeo/ios/StyleDictionaryColor.swift +0 -394
  183. package/src/tokens/adeo/ios/StyleDictionarySize.h +0 -69
  184. package/src/tokens/adeo/ios/StyleDictionarySize.m +0 -70
  185. package/src/tokens/adeo/ios/StyleDictionarySize.swift +0 -71
  186. package/src/tokens/adeo/js/tokens.js +0 -483
  187. package/src/tokens/adeo/js/tokensObject.js +0 -10354
  188. package/src/tokens/adeo/scss/_tokens.scss +0 -1300
  189. package/src/utils/mozaicClasses.js +0 -16
  190. package/src/utils/theme.validator.js +0 -19
  191. package/types/index.d.ts +0 -100
@@ -1,153 +1,177 @@
1
1
  <template>
2
- <div class="mc-quantity-selector" :class="cssFieldElementClass">
3
- <m-button
4
- class="mc-quantity-selector__button-left"
5
- theme="bordered"
6
- icon="ControlLess32"
7
- icon-position="left"
8
- :aria-label="decrementAriaLabel"
9
- :aria-controls="id"
10
- :disabled="currentValue === valuemin"
11
- :size="small ? 's' : null"
12
- tabindex="-1"
13
- @click="decrement()"
14
- />
15
-
16
- <MTextInputField
2
+ <div class="mc-quantity-selector" :class="classObject">
3
+ <input
17
4
  :id="id"
18
- :value="currentValue"
5
+ v-model="currentValue"
6
+ class="mc-quantity-selector__control"
19
7
  type="number"
20
- class="mc-quantity-selector__input"
21
- name="quantity-selector-input"
22
- :aria-label="inputAriaLabel"
8
+ :name="name"
9
+ :disabled="disabled"
10
+ :min="min"
11
+ :max="max"
12
+ :step="step"
13
+ :readonly="readonly"
14
+ :aria-invalid="isInvalid"
15
+ :aria-valuemin="min"
16
+ :aria-valuemax="max"
23
17
  :aria-valuenow="currentValue"
24
- :aria-valuemin="valuemin"
25
- :aria-valuemax="valuemax"
26
- :placeholder="placeholder"
27
- :size="small ? 's' : null"
28
- role="spinbutton"
29
- @input="handle"
30
- @keypress="integerOnly && formatValue($event)"
18
+ v-bind="$attrs"
19
+ @change="onChange(Number(($event.target as HTMLInputElement).value))"
31
20
  />
32
-
33
- <m-button
34
- class="mc-quantity-selector__button-right"
35
- theme="bordered"
36
- icon="ControlMore32"
37
- icon-position="right"
38
- :aria-label="incrementAriaLabel"
21
+ <button
22
+ v-if="!readonly"
23
+ type="button"
39
24
  :aria-controls="id"
40
- :disabled="currentValue === valuemax"
41
- :size="small ? 's' : null"
25
+ class="mc-quantity-selector__button mc-quantity-selector__button--increase"
42
26
  tabindex="-1"
43
- @click="increment()"
44
- />
27
+ :disabled="disabled || currentValue === max"
28
+ @click="increment"
29
+ >
30
+ <span class="mc-quantity-selector__icon">
31
+ <More24 />
32
+ </span>
33
+ <span class="mc-quantity-selector__label">{{ incrementlabel }}</span>
34
+ </button>
35
+ <button
36
+ v-if="!readonly"
37
+ type="button"
38
+ :aria-controls="id"
39
+ class="mc-quantity-selector__button mc-quantity-selector__button--decrease"
40
+ tabindex="-1"
41
+ :disabled="disabled || currentValue === min"
42
+ @click="decrement"
43
+ >
44
+ <span class="mc-quantity-selector__icon">
45
+ <Less24 />
46
+ </span>
47
+ <span class="mc-quantity-selector__label">{{ decrementLabel }}</span>
48
+ </button>
45
49
  </div>
46
50
  </template>
47
51
 
48
- <script>
49
- import MButton from '../button/MButton.vue';
50
- import MTextInputField from '../textinput/MTextInputField.vue';
51
-
52
- export default {
53
- name: 'MQuantitySelector',
52
+ <script setup lang="ts">
53
+ import { computed, ref, watch } from 'vue';
54
+ import More24 from '@mozaic-ds/icons-vue/src/components/More24/More24.vue';
55
+ import Less24 from '@mozaic-ds/icons-vue/src/components/Less24/Less24.vue';
54
56
 
55
- components: {
56
- MButton,
57
- MTextInputField,
58
- },
59
- inject: {
60
- cssFieldElementClass: {
61
- default: '',
62
- },
63
- },
64
- props: {
65
- id: {
66
- type: String,
67
- default: 'qty-selector',
68
- },
69
- value: {
70
- type: Number,
71
- default: 0,
72
- },
73
- inputAriaLabel: {
74
- type: String,
75
- default: 'Quantity Selector',
76
- },
77
- decrementAriaLabel: {
78
- type: String,
79
- default: 'Decrement',
80
- },
81
- incrementAriaLabel: {
82
- type: String,
83
- default: 'Increment',
84
- },
85
- valuemin: {
86
- type: Number,
87
- default: 1,
88
- },
89
- valuemax: {
90
- type: Number,
91
- default: 100,
92
- },
93
- placeholder: {
94
- type: String,
95
- default: null,
96
- },
97
- small: {
98
- type: Boolean,
99
- default: false,
100
- },
101
- integerOnly: {
102
- type: Boolean,
103
- default: false,
104
- },
57
+ /**
58
+ * The quantity selector is a form element used to enter or select a number. This type of input is best used when the user needs to choose the quantity of a selected item, like a product before adding to cart for example.
59
+ */
60
+ const props = withDefaults(
61
+ defineProps<{
62
+ /**
63
+ * A unique identifier for the quantity selector element, used to associate the label with the form element.
64
+ */
65
+ id: string;
66
+ /**
67
+ * The name attribute for the quantity selector element, typically used for form submission.
68
+ */
69
+ name?: string;
70
+ /**
71
+ * The current value of the quantity selector field.
72
+ */
73
+ modelValue?: number;
74
+ /**
75
+ * If `true`, applies an invalid state to the quantity selector.
76
+ */
77
+ isInvalid?: boolean;
78
+ /**
79
+ * If `true`, disables the quantity selector, making it non-interactive.
80
+ */
81
+ disabled?: boolean;
82
+ /**
83
+ * Determines the size of the quantity selector
84
+ */
85
+ size?: 's' | 'm';
86
+ /**
87
+ * Minimum acceptable value for the quantity selector.
88
+ */
89
+ min?: number;
90
+ /**
91
+ * Maximum acceptable value for the quantity selector.
92
+ */
93
+ max?: number;
94
+ /**
95
+ * Determines how much the value will change per click when the quantity is increased or decreased.
96
+ */
97
+ step?: number;
98
+ /**
99
+ * If `true`, the quantity selector is read-only (cannot be edited).
100
+ */
101
+ readonly?: boolean;
102
+ /**
103
+ * The label text for the increment button.
104
+ */
105
+ incrementlabel?: string;
106
+ /**
107
+ * The label text for the decrement button.
108
+ */
109
+ decrementLabel?: string;
110
+ }>(),
111
+ {
112
+ modelValue: 1,
113
+ min: 1,
114
+ max: 100,
115
+ step: 1,
116
+ size: 'm',
117
+ name: 'quantity-selector-input',
118
+ incrementlabel: 'Increment',
119
+ decrementLabel: 'Decrement',
105
120
  },
121
+ );
106
122
 
107
- data() {
108
- return {
109
- currentValue: this.value || this.valuemin,
110
- };
111
- },
112
- watch: {
113
- value() {
114
- this.currentValue = this.value || this.valuemin;
115
- },
116
- },
117
- methods: {
118
- handle(value) {
119
- this.currentValue = value;
120
- if (this.currentValue > this.valuemax) {
121
- this.currentValue = this.valuemax;
122
- }
123
- if (this.currentValue < this.valuemin) {
124
- this.currentValue = this.valuemin;
125
- }
126
- this.$emit('input', this.currentValue);
127
- },
128
- increment() {
129
- if (this.currentValue < this.valuemax) {
130
- this.currentValue++;
131
- this.$emit('increment', this.currentValue);
132
- }
133
- },
134
- decrement() {
135
- if (this.currentValue > this.valuemin) {
136
- this.currentValue--;
137
- this.$emit('decrement', this.currentValue);
138
- }
139
- },
140
- formatValue(e) {
141
- const INTEGER_ONLY_REGEX = /[0-9/]+/;
142
- if (!INTEGER_ONLY_REGEX.test(e.key)) {
143
- e.preventDefault();
144
- }
145
- },
146
- },
123
+ const currentValue = ref(props.modelValue);
124
+
125
+ watch(currentValue, (newVal) => {
126
+ if (newVal !== props.modelValue) {
127
+ emit('update:modelValue', newVal);
128
+ }
129
+ });
130
+
131
+ const classObject = computed(() => {
132
+ return {
133
+ [`mc-quantity-selector--${props.size}`]: props.size && props.size != 'm',
134
+ 'is-invalid': props.isInvalid,
135
+ };
136
+ });
137
+
138
+ const increment = () => {
139
+ if (currentValue.value + props.step <= props.max) {
140
+ currentValue.value += props.step;
141
+ } else {
142
+ currentValue.value = props.max;
143
+ }
144
+ };
145
+
146
+ const decrement = () => {
147
+ if (currentValue.value - props.step > props.min) {
148
+ currentValue.value -= props.step;
149
+ } else {
150
+ currentValue.value = props.min;
151
+ }
152
+ };
153
+
154
+ const onChange = (value: number) => {
155
+ currentValue.value = value;
156
+
157
+ if (currentValue.value > props.max) {
158
+ currentValue.value = props.max;
159
+ }
160
+ if (currentValue.value <= props.min) {
161
+ currentValue.value = props.min;
162
+ }
163
+
164
+ emit('update:modelValue', currentValue.value);
147
165
  };
166
+
167
+ const emit = defineEmits<{
168
+ /**
169
+ * Emits when the quantity selector value changes, updating the `modelValue` prop.
170
+ */
171
+ (on: 'update:modelValue', value: number): void;
172
+ }>();
148
173
  </script>
149
174
 
150
- <style lang="scss">
151
- @import 'settings-tools/_all-settings';
152
- @import 'components/_c.quantity-selector';
175
+ <style lang="scss" scoped>
176
+ @use '@mozaic-ds/styles/components/quantity-selector';
153
177
  </style>
@@ -0,0 +1,104 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import MRadio from './MRadio.vue';
3
+ import { describe, it, expect } from 'vitest';
4
+
5
+ describe('MRadio component', () => {
6
+ it('renders a radio button with a label', () => {
7
+ const wrapper = mount(MRadio, {
8
+ props: {
9
+ id: 'radio1',
10
+ label: 'Option 1',
11
+ },
12
+ });
13
+
14
+ const radioInput = wrapper.find('input[type="radio"]');
15
+ expect(radioInput.exists()).toBe(true);
16
+
17
+ const label = wrapper.find('label');
18
+ expect(label.text()).toBe('Option 1');
19
+ });
20
+
21
+ it('binds modelValue correctly when checked', async () => {
22
+ const wrapper = mount(MRadio, {
23
+ props: {
24
+ id: 'radio1',
25
+ modelValue: false,
26
+ },
27
+ });
28
+
29
+ const radioInput = wrapper.find('input');
30
+
31
+ expect(radioInput.element.checked).toBe(false);
32
+
33
+ await radioInput.setChecked();
34
+
35
+ expect(wrapper.emitted()['update:modelValue'][0]).toEqual([true]);
36
+ });
37
+
38
+ it('sets the disabled attribute when disabled prop is true', () => {
39
+ const wrapper = mount(MRadio, {
40
+ props: {
41
+ id: 'radio1',
42
+ disabled: true,
43
+ },
44
+ });
45
+
46
+ const radioInput = wrapper.find('input[type="radio"]');
47
+ expect(radioInput.attributes('disabled')).toBeDefined();
48
+ });
49
+
50
+ it('applies the is-invalid class when isInvalid prop is true', () => {
51
+ const wrapper = mount(MRadio, {
52
+ props: {
53
+ id: 'radio1',
54
+ isInvalid: true,
55
+ },
56
+ });
57
+
58
+ const radioInput = wrapper.find('input[type="radio"]');
59
+ expect(radioInput.classes()).toContain('is-invalid');
60
+ });
61
+
62
+ it('does not apply the is-invalid class when isInvalid prop is false', () => {
63
+ const wrapper = mount(MRadio, {
64
+ props: {
65
+ id: 'radio1',
66
+ isInvalid: false,
67
+ },
68
+ });
69
+
70
+ const radioInput = wrapper.find('input[type="radio"]');
71
+ expect(radioInput.classes()).not.toContain('is-invalid');
72
+ });
73
+
74
+ it('emits update:modelValue on change', async () => {
75
+ const wrapper = mount(MRadio, {
76
+ props: {
77
+ id: 'radio1',
78
+ modelValue: false,
79
+ },
80
+ });
81
+
82
+ const radioInput = wrapper.find('input[type="radio"]');
83
+
84
+ await radioInput.setChecked();
85
+
86
+ expect(wrapper.emitted()['update:modelValue'][0]).toEqual([true]);
87
+ });
88
+
89
+ it('does not emit change when disabled', async () => {
90
+ const wrapper = mount(MRadio, {
91
+ props: {
92
+ id: 'radio1',
93
+ disabled: true,
94
+ modelValue: false,
95
+ },
96
+ });
97
+
98
+ const radioInput = wrapper.find('input[type="radio"]');
99
+
100
+ await radioInput.setChecked();
101
+
102
+ expect(wrapper.emitted()['update:modelValue']).toBeUndefined();
103
+ });
104
+ });
@@ -0,0 +1,68 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3';
2
+ import { action } from '@storybook/addon-actions';
3
+
4
+ import MRadio from './MRadio.vue';
5
+
6
+ const meta: Meta<typeof MRadio> = {
7
+ title: 'Form Elements/Radio',
8
+ component: MRadio,
9
+ parameters: {
10
+ docs: {
11
+ description: {
12
+ component:
13
+ '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.',
14
+ },
15
+ },
16
+ },
17
+ args: {
18
+ id: 'RadioId',
19
+ label: 'Label',
20
+ },
21
+ render: (args) => ({
22
+ components: { MRadio },
23
+ setup() {
24
+ const handleUpdate = action('update:modelValue');
25
+
26
+ return { args, handleUpdate };
27
+ },
28
+ template: `
29
+ <MRadio
30
+ v-bind="args"
31
+ @update:modelValue="handleUpdate"
32
+ />
33
+ `,
34
+ }),
35
+ };
36
+ export default meta;
37
+ type Story = StoryObj<typeof MRadio>;
38
+
39
+ export const Default: Story = {};
40
+
41
+ export const Checked: Story = {
42
+ args: {
43
+ modelValue: true,
44
+ id: 'checkedId',
45
+ },
46
+ };
47
+
48
+ export const Invalid: Story = {
49
+ args: {
50
+ isInvalid: true,
51
+ id: 'invalidId',
52
+ },
53
+ };
54
+
55
+ export const Disabled: Story = {
56
+ args: {
57
+ disabled: true,
58
+ id: 'disabledId',
59
+ },
60
+ };
61
+
62
+ export const CheckedDisabled: Story = {
63
+ args: {
64
+ modelValue: true,
65
+ disabled: true,
66
+ id: 'checkedDisabledId',
67
+ },
68
+ };
@@ -1,55 +1,72 @@
1
1
  <template>
2
- <div class="mc-radio" :class="rootClass">
2
+ <div class="mc-radio">
3
3
  <input
4
4
  :id="id"
5
5
  type="radio"
6
6
  class="mc-radio__input"
7
- :class="{ 'is-invalid': isInvalid }"
7
+ :class="classObject"
8
+ :name="name"
9
+ :checked="modelValue"
10
+ :disabled="disabled"
11
+ :aria-invalid="isInvalid"
8
12
  v-bind="$attrs"
9
- :checked="checked"
10
- @change="$emit('change', $event.target.checked)"
13
+ @change="
14
+ emit('update:modelValue', ($event.target as HTMLInputElement).checked)
15
+ "
11
16
  />
12
- <label :for="id" class="mc-radio__label">{{ label }}</label>
17
+ <label v-if="label" :for="id" class="mc-radio__label">
18
+ {{ label }}
19
+ </label>
13
20
  </div>
14
21
  </template>
15
22
 
16
- <script>
17
- export default {
18
- name: 'MRadio',
23
+ <script setup lang="ts">
24
+ import { computed } from 'vue';
19
25
 
20
- inheritAttrs: false,
26
+ /**
27
+ * A radio button is used to offer a unique choice to your user in a form. Unlike checkboxes, it can not be used alone.
28
+ */
29
+ const props = defineProps<{
30
+ /**
31
+ * A unique identifier for the radio, used to associate the label with the form element.
32
+ */
33
+ id: string;
34
+ /**
35
+ * The name attribute for the radio element, typically used for form submission.
36
+ */
37
+ name?: string;
38
+ /**
39
+ * The text label displayed next to the radio.
40
+ */
41
+ label?: string;
42
+ /**
43
+ * The radio's checked state, bound via v-model.
44
+ */
45
+ modelValue?: boolean;
46
+ /**
47
+ * If `true`, applies an invalid state to the radio.
48
+ */
49
+ isInvalid?: boolean;
50
+ /**
51
+ * If `true`, disables the radio, making it non-interactive.
52
+ */
53
+ disabled?: boolean;
54
+ }>();
21
55
 
22
- model: {
23
- prop: 'checked',
24
- event: 'change',
25
- },
56
+ const classObject = computed(() => {
57
+ return {
58
+ 'is-invalid': props.isInvalid,
59
+ };
60
+ });
26
61
 
27
- props: {
28
- id: {
29
- type: String,
30
- required: true,
31
- },
32
- label: {
33
- type: String,
34
- required: true,
35
- },
36
- checked: {
37
- type: Boolean,
38
- default: false,
39
- },
40
- isInvalid: {
41
- type: Boolean,
42
- default: false,
43
- },
44
- rootClass: {
45
- type: String,
46
- default: '',
47
- },
48
- },
49
- };
62
+ const emit = defineEmits<{
63
+ /**
64
+ * Emits when the radio value changes, updating the modelValue prop.
65
+ */
66
+ (on: 'update:modelValue', value: boolean): void;
67
+ }>();
50
68
  </script>
51
69
 
52
- <style lang="scss">
53
- @import 'settings-tools/_all-settings';
54
- @import 'components/_c.radio';
70
+ <style lang="scss" scoped>
71
+ @use '@mozaic-ds/styles/components/radio';
55
72
  </style>
@@ -0,0 +1,54 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import { describe, it, expect } from 'vitest';
3
+ import MRadioGroup from './MRadioGroup.vue';
4
+ import MField from '@/components/field/MField.vue';
5
+ import MRadio from '@/components/radio/MRadio.vue';
6
+
7
+ describe('MRadioGroup component', () => {
8
+ const options = [
9
+ { id: 'option-1', label: 'Option 1', value: 'option1' },
10
+ { id: 'option-2', label: 'Option 2', value: 'option2', disabled: true },
11
+ { id: 'option-3', label: 'Option 3', value: 'option3' },
12
+ ];
13
+
14
+ // eslint-disable-next-line
15
+ const mountComponent = (props: Record<string, any>) => {
16
+ return mount(MRadioGroup, {
17
+ global: {
18
+ components: { MField, MRadio },
19
+ },
20
+ props: {
21
+ name: 'radio-group',
22
+ options,
23
+ ...props,
24
+ },
25
+ });
26
+ };
27
+
28
+ it('renders the radio buttons correctly', () => {
29
+ const wrapper = mountComponent({});
30
+ expect(wrapper.findAll('input[type="radio"]')).toHaveLength(3);
31
+ });
32
+
33
+ it('binds the modelValue prop correctly', async () => {
34
+ const wrapper = mountComponent({ modelValue: 'option1' });
35
+
36
+ const radio1 = wrapper.find('#option-1');
37
+ const radio2 = wrapper.find('#option-2');
38
+ const radio3 = wrapper.find('#option-3');
39
+
40
+ expect(radio1.element.checked).toBe(true);
41
+ expect(radio2.element.checked).toBe(false);
42
+ expect(radio3.element.checked).toBe(false);
43
+ });
44
+
45
+ it('does not allow disabled options to be selected', async () => {
46
+ const wrapper = mountComponent({ modelValue: 'option1' });
47
+ const radio2 = wrapper.find('#option-2');
48
+
49
+ expect(radio2.attributes('disabled')).toBeDefined();
50
+ await radio2.setChecked();
51
+
52
+ expect(wrapper.emitted()['update:modelValue']).toBeUndefined();
53
+ });
54
+ });