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

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 (179) hide show
  1. package/LICENSE +51 -0
  2. package/README.md +218 -84
  3. package/dist/mozaic-vue.css +1 -1
  4. package/dist/mozaic-vue.d.ts +920 -0
  5. package/dist/mozaic-vue.js +877 -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 +80 -50
  11. package/src/components/Contributing.mdx +118 -0
  12. package/src/components/GettingStarted.mdx +39 -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/button/MButton.spec.ts +191 -0
  19. package/src/components/button/MButton.stories.ts +66 -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/field/MField.spec.ts +166 -0
  28. package/src/components/field/MField.stories.ts +376 -0
  29. package/src/components/field/MField.vue +78 -61
  30. package/src/components/fieldgroup/MFieldGroup.spec.ts +165 -0
  31. package/src/components/fieldgroup/MFieldGroup.stories.ts +274 -0
  32. package/src/components/fieldgroup/MFieldGroup.vue +79 -0
  33. package/src/components/iconbutton/MIconButton.spec.ts +108 -0
  34. package/src/components/iconbutton/MIconButton.stories.ts +66 -0
  35. package/src/components/iconbutton/MIconButton.vue +73 -0
  36. package/src/components/link/MLink.spec.ts +154 -0
  37. package/src/components/link/MLink.stories.ts +98 -0
  38. package/src/components/link/MLink.vue +86 -109
  39. package/src/components/loader/MLoader.spec.ts +104 -0
  40. package/src/components/loader/MLoader.stories.ts +45 -0
  41. package/src/components/loader/MLoader.vue +65 -55
  42. package/src/components/overlay/MOverlay.spec.ts +51 -0
  43. package/src/components/overlay/MOverlay.stories.ts +40 -0
  44. package/src/components/overlay/MOverlay.vue +27 -19
  45. package/src/components/quantityselector/MQuantitySelector.spec.ts +262 -0
  46. package/src/components/quantityselector/MQuantitySelector.stories.ts +89 -0
  47. package/src/components/quantityselector/MQuantitySelector.vue +160 -136
  48. package/src/components/radio/MRadio.spec.ts +104 -0
  49. package/src/components/radio/MRadio.stories.ts +68 -0
  50. package/src/components/radio/MRadio.vue +56 -39
  51. package/src/components/radiogroup/MRadioGroup.spec.ts +54 -0
  52. package/src/components/radiogroup/MRadioGroup.stories.ts +61 -0
  53. package/src/components/radiogroup/MRadioGroup.vue +79 -0
  54. package/src/components/select/MSelect.spec.ts +114 -0
  55. package/src/components/select/MSelect.stories.ts +101 -0
  56. package/src/components/select/MSelect.vue +77 -119
  57. package/src/components/statusbadge/MStatusBadge.stories.ts +45 -0
  58. package/src/components/statusbadge/MStatusBadge.vue +40 -0
  59. package/src/components/statusbadge/MStatusDot.vue +32 -0
  60. package/src/components/statusbadge/MstatusBadge.spec.ts +16 -0
  61. package/src/components/textarea/MTextArea.spec.ts +112 -0
  62. package/src/components/textarea/MTextArea.stories.ts +67 -0
  63. package/src/components/textarea/MTextArea.vue +81 -42
  64. package/src/components/textinput/MTextInput.spec.ts +121 -0
  65. package/src/components/textinput/MTextInput.stories.ts +114 -0
  66. package/src/components/textinput/MTextInput.vue +127 -47
  67. package/src/components/toggle/MToggle.spec.ts +99 -0
  68. package/src/components/toggle/MToggle.stories.ts +68 -0
  69. package/src/components/toggle/MToggle.vue +63 -103
  70. package/src/components/usingIcons.mdx +43 -0
  71. package/src/components/usingPresets.mdx +125 -0
  72. package/src/main.ts +39 -0
  73. package/dist/demo.html +0 -1
  74. package/dist/mozaic-vue.adeo.css +0 -45
  75. package/dist/mozaic-vue.adeo.umd.js +0 -41775
  76. package/dist/mozaic-vue.common.js +0 -41765
  77. package/dist/mozaic-vue.common.js.map +0 -1
  78. package/dist/mozaic-vue.umd.js +0 -41776
  79. package/dist/mozaic-vue.umd.js.map +0 -1
  80. package/dist/mozaic-vue.umd.min.js +0 -4
  81. package/dist/mozaic-vue.umd.min.js.map +0 -1
  82. package/postinstall.js +0 -3
  83. package/src/components/accordion/MAccordion.vue +0 -128
  84. package/src/components/accordion/index.js +0 -7
  85. package/src/components/autocomplete/MAutocomplete.vue +0 -198
  86. package/src/components/autocomplete/index.js +0 -7
  87. package/src/components/badge/index.js +0 -7
  88. package/src/components/breadcrumb/MBreadcrumb.vue +0 -73
  89. package/src/components/breadcrumb/index.js +0 -7
  90. package/src/components/button/index.js +0 -7
  91. package/src/components/card/MCard.vue +0 -78
  92. package/src/components/card/index.js +0 -7
  93. package/src/components/checkbox/MCheckboxGroup.vue +0 -155
  94. package/src/components/checkbox/index.js +0 -12
  95. package/src/components/container/MContainer.vue +0 -33
  96. package/src/components/container/index.js +0 -7
  97. package/src/components/datatable/MDataTable.vue +0 -651
  98. package/src/components/datatable/MDataTableHeader.vue +0 -55
  99. package/src/components/datatable/MDataTableTop.vue +0 -35
  100. package/src/components/datatable/helpers.js +0 -132
  101. package/src/components/datatable/index.js +0 -12
  102. package/src/components/field/index.js +0 -7
  103. package/src/components/fileuploader/MFileResult.vue +0 -149
  104. package/src/components/fileuploader/MFileUploader.vue +0 -142
  105. package/src/components/fileuploader/index.js +0 -7
  106. package/src/components/flag/MFlag.vue +0 -46
  107. package/src/components/flag/index.js +0 -7
  108. package/src/components/heading/MHeading.vue +0 -75
  109. package/src/components/heading/index.js +0 -7
  110. package/src/components/hero/MHero.vue +0 -93
  111. package/src/components/hero/index.js +0 -7
  112. package/src/components/icon/MIcon.vue +0 -120
  113. package/src/components/icon/index.js +0 -7
  114. package/src/components/index.js +0 -43
  115. package/src/components/layer/MLayer.vue +0 -208
  116. package/src/components/layer/index.js +0 -7
  117. package/src/components/link/index.js +0 -7
  118. package/src/components/listbox/MListBox.vue +0 -106
  119. package/src/components/listbox/index.js +0 -7
  120. package/src/components/loader/index.js +0 -7
  121. package/src/components/modal/MModal.vue +0 -179
  122. package/src/components/modal/index.js +0 -7
  123. package/src/components/notification/MNotification.vue +0 -110
  124. package/src/components/notification/index.js +0 -7
  125. package/src/components/optionbutton/MOptionButton.vue +0 -67
  126. package/src/components/optionbutton/index.js +0 -7
  127. package/src/components/optioncard/MOptionCard.vue +0 -132
  128. package/src/components/optioncard/index.js +0 -7
  129. package/src/components/optiongroup/MOptionGroup.vue +0 -18
  130. package/src/components/optiongroup/index.js +0 -7
  131. package/src/components/overlay/MOverlayLoader.vue +0 -43
  132. package/src/components/overlay/index.js +0 -12
  133. package/src/components/pagination/MPagination.vue +0 -162
  134. package/src/components/pagination/index.js +0 -7
  135. package/src/components/passwordinput/MPasswordInput.vue +0 -96
  136. package/src/components/passwordinput/index.js +0 -7
  137. package/src/components/phonenumber/MPhoneNumber.vue +0 -390
  138. package/src/components/phonenumber/index.js +0 -7
  139. package/src/components/progressbar/MProgress.vue +0 -102
  140. package/src/components/progressbar/index.js +0 -7
  141. package/src/components/quantityselector/index.js +0 -7
  142. package/src/components/radio/MRadioGroup.vue +0 -111
  143. package/src/components/radio/index.js +0 -12
  144. package/src/components/ratingstars/MStarsInput.vue +0 -118
  145. package/src/components/ratingstars/MStarsResult.vue +0 -89
  146. package/src/components/ratingstars/index.js +0 -12
  147. package/src/components/select/index.js +0 -7
  148. package/src/components/stepper/MStepper.vue +0 -70
  149. package/src/components/stepper/index.js +0 -7
  150. package/src/components/tabs/MTab.vue +0 -184
  151. package/src/components/tabs/index.js +0 -7
  152. package/src/components/tags/MTag.vue +0 -173
  153. package/src/components/tags/index.js +0 -7
  154. package/src/components/textarea/index.js +0 -7
  155. package/src/components/textinput/MTextInputField.vue +0 -105
  156. package/src/components/textinput/MTextInputIcon.vue +0 -42
  157. package/src/components/textinput/index.js +0 -7
  158. package/src/components/toggle/index.js +0 -7
  159. package/src/components/tooltip/MTooltip.vue +0 -42
  160. package/src/components/tooltip/index.js +0 -7
  161. package/src/index.js +0 -62
  162. package/src/shims-tsx.d.ts +0 -13
  163. package/src/shims.vue.d.ts +0 -4
  164. package/src/tokens/adeo/android/colors.xml +0 -391
  165. package/src/tokens/adeo/android/font_dimens.xml +0 -18
  166. package/src/tokens/adeo/css/_variables.scss +0 -385
  167. package/src/tokens/adeo/css/root.scss +0 -387
  168. package/src/tokens/adeo/ios/StyleDictionaryColor.h +0 -399
  169. package/src/tokens/adeo/ios/StyleDictionaryColor.m +0 -411
  170. package/src/tokens/adeo/ios/StyleDictionaryColor.swift +0 -394
  171. package/src/tokens/adeo/ios/StyleDictionarySize.h +0 -69
  172. package/src/tokens/adeo/ios/StyleDictionarySize.m +0 -70
  173. package/src/tokens/adeo/ios/StyleDictionarySize.swift +0 -71
  174. package/src/tokens/adeo/js/tokens.js +0 -483
  175. package/src/tokens/adeo/js/tokensObject.js +0 -10354
  176. package/src/tokens/adeo/scss/_tokens.scss +0 -1300
  177. package/src/utils/mozaicClasses.js +0 -16
  178. package/src/utils/theme.validator.js +0 -19
  179. package/types/index.d.ts +0 -100
@@ -1,84 +1,94 @@
1
1
  <template>
2
- <div class="mc-loader" :class="setClasses">
2
+ <div class="mc-loader" :class="classObject">
3
3
  <span class="mc-loader__spinner">
4
4
  <svg
5
5
  class="mc-loader__icon"
6
6
  xmlns="http://www.w3.org/2000/svg"
7
- :viewBox="viewBox"
7
+ :viewBox="setViewBox"
8
+ aria-hidden="true"
8
9
  >
9
- <circle class="mc-loader__path" cx="50%" cy="50%" :r="pathR" />
10
+ <circle
11
+ class="mc-loader__path"
12
+ cx="50%"
13
+ cy="50%"
14
+ :r="setCircleRadius"
15
+ ></circle>
10
16
  </svg>
11
17
  </span>
12
-
13
- <span v-if="text" class="mc-loader__text">{{ text }}</span>
18
+ <p v-if="text" class="mc-loader__text" role="status">{{ text }}</p>
14
19
  </div>
15
20
  </template>
16
21
 
17
- <script>
18
- export default {
19
- name: 'MLoader',
20
-
21
- props: {
22
+ <script setup lang="ts">
23
+ import { computed } from 'vue';
24
+ /**
25
+ * A loader indicates that content or data is being loaded or processed, providing visual feedback to users during wait times.
26
+ */
27
+ const props = withDefaults(
28
+ defineProps<{
22
29
  /**
23
- * Loader size
24
- * @values s, m , l
30
+ * Specifies the visual appearance of the loader.
25
31
  */
26
- size: {
27
- type: String,
28
- default: 'm',
29
- validator: (value) => ['s', 'm', 'l'].includes(value),
30
- },
32
+ appearance?: 'standard' | 'accent' | 'inverse';
31
33
 
32
34
  /**
33
- * Loader theme
34
- * @values dark, light, primary
35
+ * Defines the size of the loader.
35
36
  */
36
- theme: {
37
- type: String,
38
- default: 'primary',
39
- validator: (value) => ['dark', 'light', 'primary'].includes(value),
40
- },
37
+ size?: 's' | 'm' | 'l';
41
38
 
42
39
  /**
43
- * Loader text - when using the loader inside an overlay
40
+ * Text to display alongside the loader when using the loader inside an `Overlay`.
44
41
  */
45
- text: {
46
- type: String,
47
- default: undefined,
48
- },
42
+ text?: string;
43
+ }>(),
44
+ {
45
+ appearance: 'standard',
46
+ size: 'm',
49
47
  },
48
+ );
50
49
 
51
- computed: {
52
- setClasses() {
53
- const classes = [];
50
+ const classObject = computed(() => {
51
+ return {
52
+ [`mc-loader--${props.size}`]: props.size && props.size !== 'm',
53
+ [`mc-loader--${props.appearance}`]:
54
+ props.appearance && props.appearance !== 'standard',
55
+ };
56
+ });
54
57
 
55
- if (this.size && this.size !== 'm') {
56
- classes.push(`mc-loader--${this.size}`);
57
- }
58
+ const setViewBox = computed(() => {
59
+ let viewBox: string;
58
60
 
59
- if (this.theme && this.theme !== 'primary') {
60
- classes.push(`mc-loader--${this.theme}`);
61
- }
61
+ switch (props.size) {
62
+ case 's':
63
+ viewBox = '0 0 24 24';
64
+ break;
65
+ case 'l':
66
+ viewBox = '0 0 64 64';
67
+ break;
68
+ default:
69
+ viewBox = '0 0 32 32';
70
+ }
71
+ return viewBox;
72
+ });
62
73
 
63
- return classes;
64
- },
74
+ const setCircleRadius = computed(() => {
75
+ let circleRadius: number;
65
76
 
66
- viewBox() {
67
- return this.size === 'm'
68
- ? '0 0 32 32'
69
- : this.size === 's'
70
- ? '0 0 24 24'
71
- : '0 0 64 64';
72
- },
77
+ switch (props.size) {
78
+ case 's':
79
+ circleRadius = 6;
80
+ break;
81
+ case 'l':
82
+ circleRadius = 19;
83
+ break;
84
+ default:
85
+ circleRadius = 9;
86
+ }
73
87
 
74
- pathR() {
75
- return this.size === 'm' ? 9 : this.size === 's' ? 6 : 19;
76
- },
77
- },
78
- };
88
+ return circleRadius;
89
+ });
79
90
  </script>
80
91
 
81
- <style lang="scss">
82
- @import 'settings-tools/all-settings';
83
- @import 'components/c.loader';
92
+ <style lang="scss" scoped>
93
+ @use '@mozaic-ds/styles/components/loader';
84
94
  </style>
@@ -0,0 +1,51 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import { describe, it, expect } from 'vitest';
3
+ import MOverlay from './MOverlay.vue';
4
+
5
+ describe('MOverlay.vue', () => {
6
+ it('should be visible when isVisible is true', () => {
7
+ const wrapper = mount(MOverlay, {
8
+ props: {
9
+ isVisible: true,
10
+ },
11
+ });
12
+
13
+ expect(wrapper.classes()).toContain('is-visible');
14
+ });
15
+
16
+ it('should not be visible when isVisible is false', () => {
17
+ const wrapper = mount(MOverlay, {
18
+ props: {
19
+ isVisible: false,
20
+ },
21
+ });
22
+
23
+ expect(wrapper.classes()).not.toContain('is-visible');
24
+ });
25
+
26
+ it('should apply the dialog label for accessibility', () => {
27
+ const label = 'Overlay Dialog';
28
+ const wrapper = mount(MOverlay, {
29
+ props: {
30
+ dialogLabel: label,
31
+ },
32
+ });
33
+
34
+ expect(
35
+ wrapper.find('div[role="dialog"]').attributes('aria-labelledby'),
36
+ ).toBe(label);
37
+ });
38
+
39
+ it('should render the content inside the overlay', () => {
40
+ const wrapper = mount(MOverlay, {
41
+ props: {
42
+ isVisible: true,
43
+ },
44
+ slots: {
45
+ default: 'Overlay Content',
46
+ },
47
+ });
48
+
49
+ expect(wrapper.text()).toContain('Overlay Content');
50
+ });
51
+ });
@@ -0,0 +1,40 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3';
2
+ import MOverlay from './MOverlay.vue';
3
+
4
+ const meta: Meta<typeof MOverlay> = {
5
+ title: 'Overlay/Overlay',
6
+ component: MOverlay,
7
+ parameters: {
8
+ docs: {
9
+ description: {
10
+ component:
11
+ 'An overlay component is a UI element that appears above the main content to display additional information or interactions, often blocking or dimming the background.',
12
+ },
13
+ },
14
+ },
15
+ args: {
16
+ isVisible: true,
17
+ },
18
+ argTypes: {
19
+ $slots: {
20
+ table: {
21
+ disable: true,
22
+ },
23
+ },
24
+ },
25
+ render: (args) => ({
26
+ components: { MOverlay },
27
+ setup() {
28
+ return { args };
29
+ },
30
+ template: `
31
+ <MOverlay v-bind="args">
32
+ <template v-if="${'default' in args}" v-slot>${args.default}</template>
33
+ </MOverlay>
34
+ `,
35
+ }),
36
+ };
37
+ export default meta;
38
+ type Story = StoryObj<typeof MOverlay>;
39
+
40
+ export const Default: Story = {};
@@ -1,28 +1,36 @@
1
1
  <template>
2
2
  <div class="mc-overlay" :class="{ 'is-visible': isVisible }">
3
- <!-- @slot Use this slot to insert a centered content inside the overlay -->
4
- <slot />
3
+ <div role="dialog" tabindex="-1" :aria-labelledby="dialogLabel">
4
+ <slot />
5
+ </div>
5
6
  </div>
6
7
  </template>
7
8
 
8
- <script>
9
- export default {
10
- name: 'MOverlay',
9
+ <script setup lang="ts">
10
+ import type { VNode } from 'vue';
11
11
 
12
- props: {
13
- /**
14
- * Define if the overlay is visible or not
15
- * @values false, true
16
- */
17
- isVisible: {
18
- type: Boolean,
19
- default: false,
20
- },
21
- },
22
- };
12
+ /**
13
+ * An overlay component is a UI element that appears above the main content to display additional information or interactions, often blocking or dimming the background.
14
+ */
15
+ defineProps<{
16
+ /**
17
+ * Controls the visibility of the overlay.
18
+ */
19
+ isVisible?: boolean;
20
+ /**
21
+ * Accessible label for the overlay dialog.
22
+ */
23
+ dialogLabel?: string;
24
+ }>();
25
+
26
+ defineSlots<{
27
+ /**
28
+ * Use this slot to insert a centered content inside the overlay
29
+ */
30
+ default?: VNode;
31
+ }>();
23
32
  </script>
24
33
 
25
- <style lang="scss">
26
- @import 'settings-tools/all-settings';
27
- @import 'components/c.overlay';
34
+ <style lang="scss" scoped>
35
+ @use '@mozaic-ds/styles/components/overlay';
28
36
  </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
+ '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.',
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
+ };