@mozaic-ds/vue 1.0.0-rc.3 → 2.2.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 (224) hide show
  1. package/LICENSE +51 -0
  2. package/README.md +76 -77
  3. package/dist/mozaic-vue.css +1 -1
  4. package/dist/mozaic-vue.d.ts +1718 -0
  5. package/dist/mozaic-vue.js +1971 -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 +78 -51
  11. package/src/components/Contributing.mdx +118 -0
  12. package/src/components/GettingStarted.mdx +45 -0
  13. package/src/components/Introduction.mdx +100 -0
  14. package/src/components/Support.mdx +18 -0
  15. package/src/components/breadcrumb/MBreadcrumb.spec.ts +105 -0
  16. package/src/components/breadcrumb/MBreadcrumb.stories.ts +82 -0
  17. package/src/components/breadcrumb/MBreadcrumb.vue +52 -55
  18. package/src/components/button/MButton.spec.ts +191 -0
  19. package/src/components/button/MButton.stories.ts +59 -0
  20. package/src/components/button/MButton.vue +98 -154
  21. package/src/components/checkbox/MCheckbox.spec.ts +104 -0
  22. package/src/components/checkbox/MCheckbox.stories.ts +83 -0
  23. package/src/components/checkbox/MCheckbox.vue +60 -101
  24. package/src/components/checkboxgroup/MCheckboxGroup.spec.ts +78 -0
  25. package/src/components/checkboxgroup/MCheckboxGroup.stories.ts +61 -0
  26. package/src/components/checkboxgroup/MCheckboxGroup.vue +97 -0
  27. package/src/components/datepicker/MDatepicker.spec.ts +95 -0
  28. package/src/components/datepicker/MDatepicker.stories.ts +75 -0
  29. package/src/components/datepicker/MDatepicker.vue +114 -0
  30. package/src/components/divider/MDivider.spec.ts +57 -0
  31. package/src/components/divider/MDivider.stories.ts +64 -0
  32. package/src/components/divider/MDivider.vue +56 -0
  33. package/src/components/drawer/MDrawer.spec.ts +100 -0
  34. package/src/components/drawer/MDrawer.stories.ts +128 -0
  35. package/src/components/drawer/MDrawer.vue +140 -0
  36. package/src/components/field/MField.spec.ts +166 -0
  37. package/src/components/field/MField.stories.ts +369 -0
  38. package/src/components/field/MField.vue +78 -61
  39. package/src/components/fieldgroup/MFieldGroup.spec.ts +165 -0
  40. package/src/components/fieldgroup/MFieldGroup.stories.ts +416 -0
  41. package/src/components/fieldgroup/MFieldGroup.vue +79 -0
  42. package/src/components/flag/MFlag.spec.ts +46 -0
  43. package/src/components/flag/MFlag.stories.ts +46 -0
  44. package/src/components/flag/MFlag.vue +28 -39
  45. package/src/components/iconbutton/MIconButton.spec.ts +108 -0
  46. package/src/components/iconbutton/MIconButton.stories.ts +74 -0
  47. package/src/components/iconbutton/MIconButton.vue +73 -0
  48. package/src/components/link/MLink.spec.ts +154 -0
  49. package/src/components/link/MLink.stories.ts +89 -0
  50. package/src/components/link/MLink.vue +86 -120
  51. package/src/components/loader/MLoader.spec.ts +104 -0
  52. package/src/components/loader/MLoader.stories.ts +43 -0
  53. package/src/components/loader/MLoader.vue +66 -55
  54. package/src/components/loadingoverlay/MLoadingOverlay.spec.ts +37 -0
  55. package/src/components/loadingoverlay/MLoadingOverlay.stories.ts +40 -0
  56. package/src/components/loadingoverlay/MLoadingOverlay.vue +28 -0
  57. package/src/components/modal/MModal.spec.ts +103 -0
  58. package/src/components/modal/MModal.stories.ts +127 -0
  59. package/src/components/modal/MModal.vue +111 -159
  60. package/src/components/numberbadge/MNumberBadge.spec.ts +56 -0
  61. package/src/components/numberbadge/MNumberBadge.stories.ts +48 -0
  62. package/src/components/numberbadge/MNumberBadge.vue +45 -0
  63. package/src/components/overlay/MOverlay.spec.ts +51 -0
  64. package/src/components/overlay/MOverlay.stories.ts +35 -0
  65. package/src/components/overlay/MOverlay.vue +27 -19
  66. package/src/components/pagination/MPagination.spec.ts +123 -0
  67. package/src/components/pagination/MPagination.stories.ts +83 -0
  68. package/src/components/pagination/MPagination.vue +120 -140
  69. package/src/components/passwordinput/MPasswordInput.spec.ts +104 -0
  70. package/src/components/passwordinput/MPasswordInput.stories.ts +75 -0
  71. package/src/components/passwordinput/MPasswordInput.vue +126 -77
  72. package/src/components/pincode/MPincode.spec.ts +126 -0
  73. package/src/components/pincode/MPincode.stories.ts +68 -0
  74. package/src/components/pincode/MPincode.vue +148 -0
  75. package/src/components/quantityselector/MQuantitySelector.spec.ts +262 -0
  76. package/src/components/quantityselector/MQuantitySelector.stories.ts +89 -0
  77. package/src/components/quantityselector/MQuantitySelector.vue +159 -148
  78. package/src/components/radio/MRadio.spec.ts +104 -0
  79. package/src/components/radio/MRadio.stories.ts +68 -0
  80. package/src/components/radio/MRadio.vue +56 -39
  81. package/src/components/radiogroup/MRadioGroup.spec.ts +54 -0
  82. package/src/components/radiogroup/MRadioGroup.stories.ts +61 -0
  83. package/src/components/radiogroup/MRadioGroup.vue +79 -0
  84. package/src/components/select/MSelect.spec.ts +114 -0
  85. package/src/components/select/MSelect.stories.ts +101 -0
  86. package/src/components/select/MSelect.vue +77 -119
  87. package/src/components/statusbadge/MStatusBadge.stories.ts +45 -0
  88. package/src/components/statusbadge/MStatusBadge.vue +40 -0
  89. package/src/components/statusbadge/MstatusBadge.spec.ts +16 -0
  90. package/src/components/statusdot/MStatusDot.spec.ts +51 -0
  91. package/src/components/statusdot/MStatusDot.stories.ts +48 -0
  92. package/src/components/statusdot/MStatusDot.vue +36 -0
  93. package/src/components/statusnotification/MStatusNotification.spec.ts +103 -0
  94. package/src/components/statusnotification/MStatusNotification.stories.ts +89 -0
  95. package/src/components/statusnotification/MStatusNotification.vue +106 -0
  96. package/src/components/tabs/MTabs.stories.ts +104 -0
  97. package/src/components/tabs/MTabs.vue +113 -0
  98. package/src/components/tabs/Mtabs.spec.ts +149 -0
  99. package/src/components/tag/MTag.spec.ts +107 -0
  100. package/src/components/tag/MTag.stories.ts +75 -0
  101. package/src/components/tag/MTag.vue +151 -0
  102. package/src/components/textarea/MTextArea.spec.ts +112 -0
  103. package/src/components/textarea/MTextArea.stories.ts +67 -0
  104. package/src/components/textarea/MTextArea.vue +81 -43
  105. package/src/components/textinput/MTextInput.spec.ts +121 -0
  106. package/src/components/textinput/MTextInput.stories.ts +107 -0
  107. package/src/components/textinput/MTextInput.vue +127 -47
  108. package/src/components/toggle/MToggle.spec.ts +99 -0
  109. package/src/components/toggle/MToggle.stories.ts +68 -0
  110. package/src/components/toggle/MToggle.vue +63 -103
  111. package/src/components/togglegroup/MToggleGroup.spec.ts +78 -0
  112. package/src/components/togglegroup/MToggleGroup.stories.ts +61 -0
  113. package/src/components/togglegroup/MToggleGroup.vue +97 -0
  114. package/src/components/tooltip/MTooltip.spec.ts +47 -0
  115. package/src/components/tooltip/MTooltip.stories.ts +59 -0
  116. package/src/components/tooltip/MTooltip.vue +49 -32
  117. package/src/components/usingIcons.mdx +35 -0
  118. package/src/components/usingPresets.mdx +128 -0
  119. package/src/main.ts +33 -0
  120. package/dist/demo.html +0 -1
  121. package/dist/mozaic-vue.adeo.css +0 -47
  122. package/dist/mozaic-vue.adeo.umd.js +0 -31341
  123. package/dist/mozaic-vue.common.js +0 -31331
  124. package/dist/mozaic-vue.common.js.map +0 -1
  125. package/dist/mozaic-vue.umd.js +0 -31342
  126. package/dist/mozaic-vue.umd.js.map +0 -1
  127. package/dist/mozaic-vue.umd.min.js +0 -4
  128. package/dist/mozaic-vue.umd.min.js.map +0 -1
  129. package/postinstall.js +0 -3
  130. package/src/components/accordion/MAccordion.vue +0 -128
  131. package/src/components/accordion/index.js +0 -7
  132. package/src/components/autocomplete/MAutocomplete.vue +0 -380
  133. package/src/components/autocomplete/index.js +0 -7
  134. package/src/components/badge/MBadge.vue +0 -43
  135. package/src/components/badge/index.js +0 -7
  136. package/src/components/breadcrumb/index.js +0 -7
  137. package/src/components/button/index.js +0 -7
  138. package/src/components/card/MCard.vue +0 -78
  139. package/src/components/card/index.js +0 -7
  140. package/src/components/checkbox/MCheckboxGroup.vue +0 -163
  141. package/src/components/checkbox/index.js +0 -12
  142. package/src/components/container/MContainer.vue +0 -33
  143. package/src/components/container/index.js +0 -7
  144. package/src/components/datatable/MDataTable.vue +0 -651
  145. package/src/components/datatable/MDataTableHeader.vue +0 -55
  146. package/src/components/datatable/MDataTableTop.vue +0 -35
  147. package/src/components/datatable/helpers.js +0 -132
  148. package/src/components/datatable/index.js +0 -12
  149. package/src/components/dropdown/MDropdown.vue +0 -317
  150. package/src/components/dropdown/index.js +0 -7
  151. package/src/components/field/index.js +0 -7
  152. package/src/components/fileuploader/MFileResult.vue +0 -149
  153. package/src/components/fileuploader/MFileUploader.vue +0 -142
  154. package/src/components/fileuploader/index.js +0 -7
  155. package/src/components/flag/index.js +0 -7
  156. package/src/components/heading/MHeading.vue +0 -75
  157. package/src/components/heading/index.js +0 -7
  158. package/src/components/hero/MHero.vue +0 -93
  159. package/src/components/hero/index.js +0 -7
  160. package/src/components/icon/MIcon.vue +0 -136
  161. package/src/components/icon/index.js +0 -7
  162. package/src/components/index.js +0 -44
  163. package/src/components/layer/MLayer.vue +0 -208
  164. package/src/components/layer/index.js +0 -7
  165. package/src/components/link/index.js +0 -7
  166. package/src/components/listbox/MListBox.vue +0 -146
  167. package/src/components/listbox/MListBoxActions.vue +0 -251
  168. package/src/components/listbox/index.js +0 -12
  169. package/src/components/loader/index.js +0 -7
  170. package/src/components/modal/index.js +0 -7
  171. package/src/components/notification/MNotification.vue +0 -110
  172. package/src/components/notification/index.js +0 -7
  173. package/src/components/optionbutton/MOptionButton.vue +0 -67
  174. package/src/components/optionbutton/index.js +0 -7
  175. package/src/components/optioncard/MOptionCard.vue +0 -132
  176. package/src/components/optioncard/index.js +0 -7
  177. package/src/components/optiongroup/MOptionGroup.vue +0 -18
  178. package/src/components/optiongroup/index.js +0 -7
  179. package/src/components/overlay/MOverlayLoader.vue +0 -43
  180. package/src/components/overlay/index.js +0 -12
  181. package/src/components/pagination/index.js +0 -7
  182. package/src/components/passwordinput/index.js +0 -7
  183. package/src/components/phonenumber/MPhoneNumber.vue +0 -398
  184. package/src/components/phonenumber/index.js +0 -7
  185. package/src/components/progressbar/MProgress.vue +0 -102
  186. package/src/components/progressbar/index.js +0 -7
  187. package/src/components/quantityselector/index.js +0 -7
  188. package/src/components/radio/MRadioGroup.vue +0 -111
  189. package/src/components/radio/index.js +0 -12
  190. package/src/components/ratingstars/MStarsInput.vue +0 -119
  191. package/src/components/ratingstars/MStarsResult.vue +0 -89
  192. package/src/components/ratingstars/index.js +0 -12
  193. package/src/components/select/index.js +0 -7
  194. package/src/components/stepper/MStepper.vue +0 -111
  195. package/src/components/stepper/index.js +0 -7
  196. package/src/components/tabs/MTab.vue +0 -204
  197. package/src/components/tabs/index.js +0 -7
  198. package/src/components/tags/MTag.vue +0 -175
  199. package/src/components/tags/index.js +0 -7
  200. package/src/components/textarea/index.js +0 -7
  201. package/src/components/textinput/MTextInputField.vue +0 -105
  202. package/src/components/textinput/MTextInputIcon.vue +0 -42
  203. package/src/components/textinput/index.js +0 -7
  204. package/src/components/toggle/index.js +0 -7
  205. package/src/components/tooltip/index.js +0 -7
  206. package/src/index.js +0 -63
  207. package/src/shims-tsx.d.ts +0 -13
  208. package/src/shims.vue.d.ts +0 -4
  209. package/src/tokens/adeo/android/colors.xml +0 -452
  210. package/src/tokens/adeo/android/font_dimens.xml +0 -18
  211. package/src/tokens/adeo/css/_variables.scss +0 -446
  212. package/src/tokens/adeo/css/root.scss +0 -448
  213. package/src/tokens/adeo/ios/StyleDictionaryColor.h +0 -460
  214. package/src/tokens/adeo/ios/StyleDictionaryColor.m +0 -472
  215. package/src/tokens/adeo/ios/StyleDictionaryColor.swift +0 -455
  216. package/src/tokens/adeo/ios/StyleDictionarySize.h +0 -69
  217. package/src/tokens/adeo/ios/StyleDictionarySize.m +0 -70
  218. package/src/tokens/adeo/ios/StyleDictionarySize.swift +0 -71
  219. package/src/tokens/adeo/js/tokens.js +0 -544
  220. package/src/tokens/adeo/js/tokensObject.js +0 -11733
  221. package/src/tokens/adeo/scss/_tokens.scss +0 -1522
  222. package/src/utils/mozaicClasses.js +0 -16
  223. package/src/utils/theme.validator.js +0 -19
  224. package/types/index.d.ts +0 -104
@@ -1,166 +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="disabled ? disabled : currentValue === valuemin"
11
- :size="small ? 's' : null"
12
- tabindex="-1"
13
- type="button"
14
- @click="decrement()"
15
- />
16
-
17
- <MTextInputField
2
+ <div class="mc-quantity-selector" :class="classObject">
3
+ <input
18
4
  :id="id"
19
- ref="input"
20
- :value="currentValue"
5
+ v-model="currentValue"
6
+ class="mc-quantity-selector__control"
21
7
  type="number"
22
- class="mc-quantity-selector__input"
23
- name="quantity-selector-input"
24
- :aria-label="inputAriaLabel"
25
- :aria-valuenow="currentValue"
26
- :aria-valuemin="valuemin"
27
- :aria-valuemax="valuemax"
28
- :placeholder="placeholder"
29
- :size="small ? 's' : null"
8
+ :name="name"
30
9
  :disabled="disabled"
31
- role="spinbutton"
32
- @change="handle"
33
- @focus="onFocus"
34
- @keypress="integerOnly && formatValue($event)"
35
- v-on="$listeners"
10
+ :min="min"
11
+ :max="max"
12
+ :step="step"
13
+ :readonly="readonly"
14
+ :aria-invalid="isInvalid"
15
+ :aria-valuemin="min"
16
+ :aria-valuemax="max"
17
+ :aria-valuenow="currentValue"
18
+ v-bind="$attrs"
19
+ @change="onChange(Number(($event.target as HTMLInputElement).value))"
36
20
  />
37
-
38
- <m-button
39
- class="mc-quantity-selector__button-right"
40
- theme="bordered"
41
- icon="ControlMore32"
42
- icon-position="right"
43
- :aria-label="incrementAriaLabel"
21
+ <button
22
+ v-if="!readonly"
23
+ type="button"
44
24
  :aria-controls="id"
45
- :disabled="disabled ? disabled : currentValue === valuemax"
46
- :size="small ? 's' : null"
25
+ class="mc-quantity-selector__button mc-quantity-selector__button--increase"
47
26
  tabindex="-1"
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"
48
37
  type="button"
49
- @click="increment()"
50
- />
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>
51
49
  </div>
52
50
  </template>
53
51
 
54
- <script>
55
- import MButton from '../button/MButton.vue';
56
- import MTextInputField from '../textinput/MTextInputField.vue';
57
-
58
- export default {
59
- 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';
60
56
 
61
- components: {
62
- MButton,
63
- MTextInputField,
64
- },
65
- inject: {
66
- cssFieldElementClass: {
67
- default: '',
68
- },
69
- },
70
- props: {
71
- id: {
72
- type: String,
73
- default: 'qty-selector',
74
- },
75
- value: {
76
- type: Number,
77
- default: 0,
78
- },
79
- inputAriaLabel: {
80
- type: String,
81
- default: 'Quantity Selector',
82
- },
83
- decrementAriaLabel: {
84
- type: String,
85
- default: 'Decrement',
86
- },
87
- incrementAriaLabel: {
88
- type: String,
89
- default: 'Increment',
90
- },
91
- valuemin: {
92
- type: Number,
93
- default: 1,
94
- },
95
- valuemax: {
96
- type: Number,
97
- default: 100,
98
- },
99
- placeholder: {
100
- type: String,
101
- default: null,
102
- },
103
- small: {
104
- type: Boolean,
105
- default: false,
106
- },
107
- integerOnly: {
108
- type: Boolean,
109
- default: false,
110
- },
111
- disabled: {
112
- type: Boolean,
113
- default: false,
114
- },
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',
115
120
  },
121
+ );
116
122
 
117
- data() {
118
- return {
119
- currentValue: this.value || this.valuemin,
120
- };
121
- },
122
- watch: {
123
- value() {
124
- this.currentValue = this.value || this.valuemin;
125
- },
126
- },
127
- methods: {
128
- handle(value) {
129
- this.currentValue = value;
130
- if (this.currentValue > this.valuemax) {
131
- this.currentValue = this.valuemax;
132
- }
133
- if (this.currentValue <= this.valuemin) {
134
- this.currentValue = this.valuemin;
135
- }
136
- this.$emit('input', this.currentValue);
137
- },
138
- increment() {
139
- if (this.currentValue < this.valuemax) {
140
- this.currentValue++;
141
- this.$emit('increment', this.currentValue);
142
- }
143
- },
144
- decrement() {
145
- if (this.currentValue > this.valuemin) {
146
- this.currentValue--;
147
- this.$emit('decrement', this.currentValue);
148
- }
149
- },
150
- formatValue(e) {
151
- const INTEGER_ONLY_REGEX = /[0-9/]+/;
152
- if (!INTEGER_ONLY_REGEX.test(e.key)) {
153
- e.preventDefault();
154
- }
155
- },
156
- onFocus(e) {
157
- e.target.select();
158
- },
159
- },
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
+ }
160
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);
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
+ }>();
161
173
  </script>
162
174
 
163
- <style lang="scss">
164
- @import 'settings-tools/_all-settings';
165
- @import 'components/_c.quantity-selector';
175
+ <style lang="scss" scoped>
176
+ @use '@mozaic-ds/styles/components/quantity-selector';
166
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-vite';
2
+ import { action } from 'storybook/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
+ });