@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,96 +1,149 @@
1
1
  <template>
2
- <div class="mc-password-input" :class="cssFieldElementClass">
2
+ <div class="mc-password-input mc-text-input" :class="classObject">
3
3
  <input
4
- :type="setType"
5
- :required="required"
6
- class="mc-text-input mc-password-input__control"
7
- :class="setInputClasses"
4
+ class="mc-password-input__control mc-text-input__control"
5
+ v-model="modelValue"
6
+ :id="id"
7
+ :type="inputType"
8
+ :name="name"
9
+ :placeholder="placeholder"
10
+ :disabled="disabled"
8
11
  :aria-invalid="isInvalid"
12
+ :readonly="readonly"
13
+ v-bind="$attrs"
14
+ @input="
15
+ emit('update:modelValue', ($event.target as HTMLInputElement).value)
16
+ "
9
17
  />
10
-
11
- <button
18
+ <div v-if="isClearable && modelValue" class="mc-controls-options">
19
+ <button
20
+ class="mc-controls-options__button"
21
+ @click="clearValue"
22
+ >
23
+ <CrossCircleFilled24
24
+ class="mc-controls-options__icon"
25
+ aria-hidden="true"
26
+ />
27
+ <span class="mc-controls-options__label">{{ clearLabel }}</span>
28
+ </button>
29
+ </div>
30
+ <MButton
12
31
  ref="button"
13
- type="button"
14
- class="mc-password-input__button"
15
32
  role="switch"
16
- :aria-pressed="setAriaPressed"
17
- @click="onClick"
33
+ :aria-checked="ariaChecked"
34
+ :disabled="disabled"
35
+ @click="toggleVisibility"
36
+ size="s"
37
+ ghost
18
38
  >
19
- {{ setButtonLabel }}
20
- </button>
39
+ {{ isVisible ? buttonLabel.hide : buttonLabel.show }}
40
+ </MButton>
21
41
  </div>
22
42
  </template>
23
43
 
24
- <script>
25
- export default {
26
- name: 'MPasswordInput',
44
+ <script setup lang="ts">
45
+ import { computed, ref } from 'vue';
46
+ import CrossCircleFilled24 from '@mozaic-ds/icons-vue/src/components/CrossCircleFilled24/CrossCircleFilled24.vue';
47
+ import MButton from '../button/MButton.vue';
27
48
 
28
- inject: {
29
- cssFieldElementClass: {
30
- default: '',
31
- },
49
+ /**
50
+ * 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.
51
+ */
52
+ const props = withDefaults(
53
+ defineProps<{
54
+ /**
55
+ * A unique identifier for the password input element, used to associate the label with the form element.
56
+ */
57
+ id: string;
58
+ /**
59
+ * The name attribute for the password input element, typically used for form submission.
60
+ */
61
+ name?: string;
62
+ /**
63
+ * The current value of the password input field.
64
+ */
65
+ modelValue?: string | number;
66
+ /**
67
+ * A placeholder text to show in the password input when it is empty.
68
+ */
69
+ placeholder?: string;
70
+ /**
71
+ * If `true`, applies an invalid state to the password input.
72
+ */
73
+ isInvalid?: boolean;
74
+ /**
75
+ * If `true`, disables the password input, making it non-interactive.
76
+ */
77
+ disabled?: boolean;
78
+ /**
79
+ * If `true`, the password input is read-only (cannot be edited).
80
+ */
81
+ readonly?: boolean;
82
+ /**
83
+ * If `true`, a clear button will appear when the password input has a value.
84
+ */
85
+ isClearable?: boolean;
86
+ /**
87
+ * The label text for the clear button
88
+ */
89
+ clearLabel?: string;
90
+ /**
91
+ * Labels of the button displayed when showing or hiding the password
92
+ */
93
+ buttonLabel?: {
94
+ show: string;
95
+ hide: string;
96
+ };
97
+ }>(),
98
+ {
99
+ clearLabel: 'Clear content',
100
+ buttonLabel: () => ({ show: 'Show', hide: 'Hide' }),
32
101
  },
102
+ );
33
103
 
34
- props: {
35
- required: {
36
- type: Boolean,
37
- default: false,
38
- },
39
- value: {
40
- type: [String, Number],
41
- default: null,
42
- },
43
- isValid: {
44
- type: Boolean,
45
- default: false,
46
- },
47
- isInvalid: {
48
- type: Boolean,
49
- default: false,
50
- },
51
- buttonLabel: {
52
- type: Object,
53
- default: function () {
54
- return { show: 'Show', hide: 'Hide' };
55
- },
56
- },
57
- },
104
+ const classObject = computed(() => ({
105
+ 'is-invalid': props.isInvalid,
106
+ }));
58
107
 
59
- data: function () {
60
- return {
61
- isVisible: false,
62
- };
63
- },
108
+ // Local state management
109
+ const modelValue = ref(props.modelValue);
110
+ const isVisible = ref(false);
64
111
 
65
- computed: {
66
- setType() {
67
- return this.isVisible ? 'text' : 'password';
68
- },
69
- setInputClasses() {
70
- return {
71
- 'is-valid': this.isValid,
72
- 'is-invalid': this.isInvalid,
73
- };
74
- },
75
- setAriaPressed() {
76
- return this.isVisible ? 'true' : 'false';
77
- },
78
- setButtonLabel() {
79
- return this.isVisible ? this.buttonLabel.hide : this.buttonLabel.show;
80
- },
81
- },
112
+ /**
113
+ * Clear the input value.
114
+ */
115
+ const clearValue = () => {
116
+ modelValue.value = '';
117
+ emit('update:modelValue', '');
118
+ };
82
119
 
83
- methods: {
84
- onClick() {
85
- const aria = this.$refs.button.getAttribute('aria-pressed') === 'false';
86
- this.isVisible = aria;
87
- },
88
- },
120
+ /**
121
+ * Toggle the visibility of the password.
122
+ */
123
+ const toggleVisibility = () => {
124
+ isVisible.value = !isVisible.value;
89
125
  };
126
+
127
+ /**
128
+ * Compute the input type based on visibility state.
129
+ */
130
+ const inputType = computed(() => (isVisible.value ? 'text' : 'password'));
131
+
132
+ /**
133
+ * Aria attributes for accessibility.
134
+ */
135
+ const ariaChecked = computed(() => (isVisible.value ? 'true' : 'false'));
136
+
137
+ const emit = defineEmits<{
138
+ /**
139
+ * Emits when the input value changes, updating the `modelValue` prop.
140
+ */
141
+ (on: 'update:modelValue', value: string | number): void;
142
+ }>();
90
143
  </script>
91
144
 
92
- <style lang="scss">
93
- @import 'settings-tools/_all-settings';
94
- @import 'components/_c.text-input';
95
- @import 'components/_c.password-input';
145
+ <style lang="scss" scoped>
146
+ @use '@mozaic-ds/styles/components/controls-options';
147
+ @use '@mozaic-ds/styles/components/text-input';
148
+ @use '@mozaic-ds/styles/components/password-input';
96
149
  </style>
@@ -0,0 +1,262 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import { describe, it, expect } from 'vitest';
3
+ import MQuantitySelector from './MQuantitySelector.vue';
4
+
5
+ describe('MQuantitySelector component', () => {
6
+ it('renders the component with default values', () => {
7
+ const wrapper = mount(MQuantitySelector, {
8
+ props: {
9
+ id: 'quantity-selector-1',
10
+ },
11
+ });
12
+
13
+ const input = wrapper.find('input');
14
+ const increaseButton = wrapper.find(
15
+ '.mc-quantity-selector__button--increase',
16
+ );
17
+ const decreaseButton = wrapper.find(
18
+ '.mc-quantity-selector__button--decrease',
19
+ );
20
+
21
+ expect(input.exists()).toBe(true);
22
+ expect(input.element.value).toBe('1');
23
+ expect(input.attributes('min')).toBe('1');
24
+ expect(input.attributes('max')).toBe('100');
25
+ expect(input.attributes('step')).toBe('1');
26
+
27
+ expect(increaseButton.exists()).toBe(true);
28
+ expect(decreaseButton.exists()).toBe(true);
29
+ expect(increaseButton.attributes('disabled')).toBeUndefined();
30
+ expect(decreaseButton.attributes('disabled')).toBeDefined();
31
+ });
32
+
33
+ it('emits an update when value changes via input', async () => {
34
+ const wrapper = mount(MQuantitySelector, {
35
+ props: {
36
+ id: 'quantity-selector-1',
37
+ modelValue: 5,
38
+ },
39
+ });
40
+
41
+ const input = wrapper.find('input');
42
+ await input.setValue(10);
43
+
44
+ const emittedValue = wrapper.emitted('update:modelValue');
45
+ expect(emittedValue).toBeTruthy();
46
+ expect(emittedValue?.[0]).toEqual([10]);
47
+ });
48
+
49
+ it('increments value when increase button is clicked', async () => {
50
+ const wrapper = mount(MQuantitySelector, {
51
+ props: {
52
+ id: 'quantity-selector-1',
53
+ modelValue: 5,
54
+ max: 10,
55
+ step: 1,
56
+ },
57
+ });
58
+
59
+ const increaseButton = wrapper.find(
60
+ '.mc-quantity-selector__button--increase',
61
+ );
62
+ await increaseButton.trigger('click');
63
+
64
+ const input = wrapper.find('input');
65
+ expect(input.element.value).toBe('6');
66
+ });
67
+
68
+ it('decrements value when decrease button is clicked', async () => {
69
+ const wrapper = mount(MQuantitySelector, {
70
+ props: {
71
+ id: 'quantity-selector-1',
72
+ modelValue: 5,
73
+ min: 1,
74
+ step: 1,
75
+ },
76
+ });
77
+
78
+ const decreaseButton = wrapper.find(
79
+ '.mc-quantity-selector__button--decrease',
80
+ );
81
+ await decreaseButton.trigger('click');
82
+
83
+ const input = wrapper.find('input');
84
+ expect(input.element.value).toBe('4');
85
+ });
86
+
87
+ it('does not increment beyond max value', async () => {
88
+ const wrapper = mount(MQuantitySelector, {
89
+ props: {
90
+ id: 'quantity-selector-1',
91
+ modelValue: 10,
92
+ max: 10,
93
+ step: 1,
94
+ },
95
+ });
96
+
97
+ const increaseButton = wrapper.find(
98
+ '.mc-quantity-selector__button--increase',
99
+ );
100
+ await increaseButton.trigger('click');
101
+
102
+ const input = wrapper.find('input');
103
+ expect(input.element.value).toBe('10');
104
+ });
105
+
106
+ it('does not decrement below min value', async () => {
107
+ const wrapper = mount(MQuantitySelector, {
108
+ props: {
109
+ id: 'quantity-selector-1',
110
+ modelValue: 1,
111
+ min: 1,
112
+ step: 1,
113
+ },
114
+ });
115
+
116
+ const decreaseButton = wrapper.find(
117
+ '.mc-quantity-selector__button--decrease',
118
+ );
119
+ await decreaseButton.trigger('click');
120
+
121
+ const input = wrapper.find('input');
122
+ expect(input.element.value).toBe('1');
123
+ });
124
+
125
+ it('disables input and buttons when disabled prop is true', async () => {
126
+ const wrapper = mount(MQuantitySelector, {
127
+ props: {
128
+ id: 'quantity-selector-1',
129
+ disabled: true,
130
+ },
131
+ });
132
+
133
+ const input = wrapper.find('input');
134
+ const increaseButton = wrapper.find(
135
+ '.mc-quantity-selector__button--increase',
136
+ );
137
+ const decreaseButton = wrapper.find(
138
+ '.mc-quantity-selector__button--decrease',
139
+ );
140
+
141
+ expect(input.attributes('disabled')).toBeDefined();
142
+ expect(increaseButton.attributes('disabled')).toBeDefined();
143
+ expect(decreaseButton.attributes('disabled')).toBeDefined();
144
+ });
145
+
146
+ it('renders with custom size class', () => {
147
+ const wrapper = mount(MQuantitySelector, {
148
+ props: {
149
+ id: 'quantity-selector-1',
150
+ size: 's',
151
+ },
152
+ });
153
+
154
+ const div = wrapper.find('.mc-quantity-selector');
155
+ expect(div.classes()).toContain('mc-quantity-selector--s');
156
+ });
157
+
158
+ it('hides increase and decrease buttons when readonly is true', () => {
159
+ const wrapper = mount(MQuantitySelector, {
160
+ props: {
161
+ id: 'quantity-selector-1',
162
+ readonly: true,
163
+ },
164
+ });
165
+
166
+ const increaseButton = wrapper.find(
167
+ '.mc-quantity-selector__button--increase',
168
+ );
169
+ const decreaseButton = wrapper.find(
170
+ '.mc-quantity-selector__button--decrease',
171
+ );
172
+
173
+ expect(increaseButton.exists()).toBe(false);
174
+ expect(decreaseButton.exists()).toBe(false);
175
+ });
176
+
177
+ it('renders and tests aria-label and aria-invalid for accessibility', () => {
178
+ const wrapper = mount(MQuantitySelector, {
179
+ props: {
180
+ id: 'quantity-selector-1',
181
+ isInvalid: true,
182
+ ariaLabel: 'Select quantity',
183
+ },
184
+ });
185
+
186
+ const input = wrapper.find('input');
187
+ expect(input.attributes('aria-label')).toBe('Select quantity');
188
+ expect(input.attributes('aria-invalid')).toBe('true');
189
+ });
190
+
191
+ it('does not allow invalid input values (non-numeric)', async () => {
192
+ const wrapper = mount(MQuantitySelector, {
193
+ props: {
194
+ id: 'quantity-selector-1',
195
+ },
196
+ });
197
+
198
+ const input = wrapper.find('input');
199
+ await input.setValue('abc');
200
+ expect(input.element.value).toBe('1');
201
+ });
202
+
203
+ it('supports custom step values', async () => {
204
+ const wrapper = mount(MQuantitySelector, {
205
+ props: {
206
+ id: 'quantity-selector-1',
207
+ modelValue: 10,
208
+ step: 5,
209
+ },
210
+ });
211
+
212
+ const increaseButton = wrapper.find(
213
+ '.mc-quantity-selector__button--increase',
214
+ );
215
+ await increaseButton.trigger('click');
216
+
217
+ const input = wrapper.find('input');
218
+ expect(input.element.value).toBe('15');
219
+ });
220
+
221
+ it('renders custom increment and decrement labels', () => {
222
+ const wrapper = mount(MQuantitySelector, {
223
+ props: {
224
+ id: 'quantity-selector-1',
225
+ incrementlabel: 'Increase Quantity',
226
+ decrementLabel: 'Decrease Quantity',
227
+ },
228
+ });
229
+
230
+ const increaseButton = wrapper.find(
231
+ '.mc-quantity-selector__button--increase',
232
+ );
233
+ const decreaseButton = wrapper.find(
234
+ '.mc-quantity-selector__button--decrease',
235
+ );
236
+
237
+ expect(increaseButton.text()).toBe('Increase Quantity');
238
+ expect(decreaseButton.text()).toBe('Decrease Quantity');
239
+ });
240
+
241
+ it('correctly sets aria-controls', () => {
242
+ const wrapper = mount(MQuantitySelector, {
243
+ props: {
244
+ id: 'quantity-selector-1',
245
+ },
246
+ });
247
+
248
+ const increaseButton = wrapper.find(
249
+ '.mc-quantity-selector__button--increase',
250
+ );
251
+ const decreaseButton = wrapper.find(
252
+ '.mc-quantity-selector__button--decrease',
253
+ );
254
+
255
+ expect(increaseButton.attributes('aria-controls')).toBe(
256
+ 'quantity-selector-1',
257
+ );
258
+ expect(decreaseButton.attributes('aria-controls')).toBe(
259
+ 'quantity-selector-1',
260
+ );
261
+ });
262
+ });
@@ -0,0 +1,89 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3';
2
+ import { action } from '@storybook/addon-actions';
3
+
4
+ import MQuantitySelector from './MQuantitySelector.vue';
5
+
6
+ const meta: Meta<typeof MQuantitySelector> = {
7
+ title: 'Form Elements/Quantity Selector',
8
+ component: MQuantitySelector,
9
+ parameters: {
10
+ docs: {
11
+ description: {
12
+ component:
13
+ '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.',
14
+ },
15
+ },
16
+ },
17
+ args: {
18
+ id: 'quantitySelectorId',
19
+ modelValue: 10,
20
+ min: 5,
21
+ max: 20,
22
+ },
23
+ argTypes: {
24
+ size: {
25
+ control: 'radio',
26
+ options: ['s', 'm'],
27
+ },
28
+ },
29
+ render: (args) => ({
30
+ components: { MQuantitySelector },
31
+ setup() {
32
+ const handleUpdate = action('update:modelValue');
33
+
34
+ return { args, handleUpdate };
35
+ },
36
+ template: `
37
+ <MQuantitySelector
38
+ v-bind="args"
39
+ @update:modelValue="handleUpdate"
40
+ />
41
+ `,
42
+ }),
43
+ };
44
+ export default meta;
45
+ type Story = StoryObj<typeof MQuantitySelector>;
46
+
47
+ export const Default: Story = {};
48
+
49
+ export const Step: Story = {
50
+ args: {
51
+ id: 'stepId',
52
+ step: 5,
53
+ },
54
+ };
55
+
56
+ export const Small: Story = {
57
+ args: {
58
+ id: 'smallId',
59
+ size: 's',
60
+ },
61
+ };
62
+
63
+ export const Disabled: Story = {
64
+ args: {
65
+ id: 'disableId',
66
+ disabled: true,
67
+ },
68
+ };
69
+
70
+ export const ReadOnly: Story = {
71
+ args: {
72
+ id: 'readonlyId',
73
+ readonly: true,
74
+ },
75
+ };
76
+
77
+ export const Invalid: Story = {
78
+ args: {
79
+ id: 'invalidId',
80
+ isInvalid: true,
81
+ },
82
+ };
83
+
84
+ export const readOnly: Story = {
85
+ args: {
86
+ id: 'readonlyId',
87
+ readonly: true,
88
+ },
89
+ };