@mozaic-ds/vue 1.0.0-rc.3 → 2.1.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 (222) 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 +1664 -0
  5. package/dist/mozaic-vue.js +1943 -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/usingIcons.mdx +35 -0
  115. package/src/components/usingPresets.mdx +128 -0
  116. package/src/main.ts +32 -0
  117. package/dist/demo.html +0 -1
  118. package/dist/mozaic-vue.adeo.css +0 -47
  119. package/dist/mozaic-vue.adeo.umd.js +0 -31341
  120. package/dist/mozaic-vue.common.js +0 -31331
  121. package/dist/mozaic-vue.common.js.map +0 -1
  122. package/dist/mozaic-vue.umd.js +0 -31342
  123. package/dist/mozaic-vue.umd.js.map +0 -1
  124. package/dist/mozaic-vue.umd.min.js +0 -4
  125. package/dist/mozaic-vue.umd.min.js.map +0 -1
  126. package/postinstall.js +0 -3
  127. package/src/components/accordion/MAccordion.vue +0 -128
  128. package/src/components/accordion/index.js +0 -7
  129. package/src/components/autocomplete/MAutocomplete.vue +0 -380
  130. package/src/components/autocomplete/index.js +0 -7
  131. package/src/components/badge/MBadge.vue +0 -43
  132. package/src/components/badge/index.js +0 -7
  133. package/src/components/breadcrumb/index.js +0 -7
  134. package/src/components/button/index.js +0 -7
  135. package/src/components/card/MCard.vue +0 -78
  136. package/src/components/card/index.js +0 -7
  137. package/src/components/checkbox/MCheckboxGroup.vue +0 -163
  138. package/src/components/checkbox/index.js +0 -12
  139. package/src/components/container/MContainer.vue +0 -33
  140. package/src/components/container/index.js +0 -7
  141. package/src/components/datatable/MDataTable.vue +0 -651
  142. package/src/components/datatable/MDataTableHeader.vue +0 -55
  143. package/src/components/datatable/MDataTableTop.vue +0 -35
  144. package/src/components/datatable/helpers.js +0 -132
  145. package/src/components/datatable/index.js +0 -12
  146. package/src/components/dropdown/MDropdown.vue +0 -317
  147. package/src/components/dropdown/index.js +0 -7
  148. package/src/components/field/index.js +0 -7
  149. package/src/components/fileuploader/MFileResult.vue +0 -149
  150. package/src/components/fileuploader/MFileUploader.vue +0 -142
  151. package/src/components/fileuploader/index.js +0 -7
  152. package/src/components/flag/index.js +0 -7
  153. package/src/components/heading/MHeading.vue +0 -75
  154. package/src/components/heading/index.js +0 -7
  155. package/src/components/hero/MHero.vue +0 -93
  156. package/src/components/hero/index.js +0 -7
  157. package/src/components/icon/MIcon.vue +0 -136
  158. package/src/components/icon/index.js +0 -7
  159. package/src/components/index.js +0 -44
  160. package/src/components/layer/MLayer.vue +0 -208
  161. package/src/components/layer/index.js +0 -7
  162. package/src/components/link/index.js +0 -7
  163. package/src/components/listbox/MListBox.vue +0 -146
  164. package/src/components/listbox/MListBoxActions.vue +0 -251
  165. package/src/components/listbox/index.js +0 -12
  166. package/src/components/loader/index.js +0 -7
  167. package/src/components/modal/index.js +0 -7
  168. package/src/components/notification/MNotification.vue +0 -110
  169. package/src/components/notification/index.js +0 -7
  170. package/src/components/optionbutton/MOptionButton.vue +0 -67
  171. package/src/components/optionbutton/index.js +0 -7
  172. package/src/components/optioncard/MOptionCard.vue +0 -132
  173. package/src/components/optioncard/index.js +0 -7
  174. package/src/components/optiongroup/MOptionGroup.vue +0 -18
  175. package/src/components/optiongroup/index.js +0 -7
  176. package/src/components/overlay/MOverlayLoader.vue +0 -43
  177. package/src/components/overlay/index.js +0 -12
  178. package/src/components/pagination/index.js +0 -7
  179. package/src/components/passwordinput/index.js +0 -7
  180. package/src/components/phonenumber/MPhoneNumber.vue +0 -398
  181. package/src/components/phonenumber/index.js +0 -7
  182. package/src/components/progressbar/MProgress.vue +0 -102
  183. package/src/components/progressbar/index.js +0 -7
  184. package/src/components/quantityselector/index.js +0 -7
  185. package/src/components/radio/MRadioGroup.vue +0 -111
  186. package/src/components/radio/index.js +0 -12
  187. package/src/components/ratingstars/MStarsInput.vue +0 -119
  188. package/src/components/ratingstars/MStarsResult.vue +0 -89
  189. package/src/components/ratingstars/index.js +0 -12
  190. package/src/components/select/index.js +0 -7
  191. package/src/components/stepper/MStepper.vue +0 -111
  192. package/src/components/stepper/index.js +0 -7
  193. package/src/components/tabs/MTab.vue +0 -204
  194. package/src/components/tabs/index.js +0 -7
  195. package/src/components/tags/MTag.vue +0 -175
  196. package/src/components/tags/index.js +0 -7
  197. package/src/components/textarea/index.js +0 -7
  198. package/src/components/textinput/MTextInputField.vue +0 -105
  199. package/src/components/textinput/MTextInputIcon.vue +0 -42
  200. package/src/components/textinput/index.js +0 -7
  201. package/src/components/toggle/index.js +0 -7
  202. package/src/components/tooltip/MTooltip.vue +0 -42
  203. package/src/components/tooltip/index.js +0 -7
  204. package/src/index.js +0 -63
  205. package/src/shims-tsx.d.ts +0 -13
  206. package/src/shims.vue.d.ts +0 -4
  207. package/src/tokens/adeo/android/colors.xml +0 -452
  208. package/src/tokens/adeo/android/font_dimens.xml +0 -18
  209. package/src/tokens/adeo/css/_variables.scss +0 -446
  210. package/src/tokens/adeo/css/root.scss +0 -448
  211. package/src/tokens/adeo/ios/StyleDictionaryColor.h +0 -460
  212. package/src/tokens/adeo/ios/StyleDictionaryColor.m +0 -472
  213. package/src/tokens/adeo/ios/StyleDictionaryColor.swift +0 -455
  214. package/src/tokens/adeo/ios/StyleDictionarySize.h +0 -69
  215. package/src/tokens/adeo/ios/StyleDictionarySize.m +0 -70
  216. package/src/tokens/adeo/ios/StyleDictionarySize.swift +0 -71
  217. package/src/tokens/adeo/js/tokens.js +0 -544
  218. package/src/tokens/adeo/js/tokensObject.js +0 -11733
  219. package/src/tokens/adeo/scss/_tokens.scss +0 -1522
  220. package/src/utils/mozaicClasses.js +0 -16
  221. package/src/utils/theme.validator.js +0 -19
  222. package/types/index.d.ts +0 -104
@@ -1,54 +1,92 @@
1
1
  <template>
2
2
  <textarea
3
+ :id="id"
3
4
  class="mc-textarea"
4
- :class="[
5
- { 'is-valid': isValid, 'is-invalid': isInvalid },
6
- cssFieldElementClass,
7
- ]"
5
+ :class="classObject"
8
6
  :aria-invalid="isInvalid"
9
- :value="value"
10
- spellcheck="false"
11
- v-on="inputListeners"
7
+ :value="modelValue"
8
+ :name="name"
9
+ :placeholder="placeholder"
10
+ :disabled="disabled"
11
+ :minlength="minLength"
12
+ :maxlength="maxLength"
13
+ :rows="rows"
14
+ :readonly="readonly"
15
+ v-bind="$attrs"
16
+ @input="
17
+ emit('update:modelValue', ($event.target as HTMLInputElement).value)
18
+ "
12
19
  />
13
20
  </template>
14
21
 
15
- <script>
16
- export default {
17
- name: 'MTextArea',
18
- inject: {
19
- cssFieldElementClass: {
20
- default: '',
21
- },
22
- },
23
- props: {
24
- value: {
25
- type: String,
26
- default: null,
27
- },
28
- isValid: {
29
- type: Boolean,
30
- default: false,
31
- },
32
- isInvalid: {
33
- type: Boolean,
34
- default: false,
35
- },
36
- },
37
- computed: {
38
- inputListeners: function () {
39
- // see => https://vuejs.org/v2/guide/components-custom-events.html#Binding-Native-Events-to-Components
40
- var vm = this;
41
- return Object.assign({}, this.$listeners, {
42
- input: function (event) {
43
- vm.$emit('input', event.target.value);
44
- },
45
- });
46
- },
22
+ <script setup lang="ts">
23
+ import { computed } from 'vue';
24
+
25
+ /**
26
+ * A textarea is a form element for multi-line text input, ideal for longer content like comments or descriptions.
27
+ */
28
+ const props = withDefaults(
29
+ defineProps<{
30
+ /**
31
+ * A unique identifier for the textarea, used to associate the label with the form element.
32
+ */
33
+ id: string;
34
+ /**
35
+ * The name attribute for the textarea element, used for form submission.
36
+ */
37
+ name?: string;
38
+ /**
39
+ * The current value of the textarea field.
40
+ */
41
+ modelValue?: string | number;
42
+ /**
43
+ * Text displayed when the textarea is empty.
44
+ */
45
+ placeholder?: string;
46
+ /**
47
+ * If `true`, the textarea is marked as invalid.
48
+ */
49
+ isInvalid?: boolean;
50
+ /**
51
+ * If `true`, the textarea is disabled and non-interactive.
52
+ */
53
+ disabled?: boolean;
54
+ /**
55
+ * The number of visible text lines in the textarea.
56
+ */
57
+ rows?: number;
58
+ /**
59
+ * Minimum number of characters required for the textarea.
60
+ */
61
+ minLength?: number;
62
+ /**
63
+ * Maximum number of characters allowed in the textarea.
64
+ */
65
+ maxLength?: number;
66
+ /**
67
+ * If `true`, the textarea is read-only (cannot be edited).
68
+ */
69
+ readonly?: boolean;
70
+ }>(),
71
+ {
72
+ rows: 2,
47
73
  },
48
- };
74
+ );
75
+
76
+ const classObject = computed(() => {
77
+ return {
78
+ 'is-invalid': props.isInvalid,
79
+ };
80
+ });
81
+
82
+ const emit = defineEmits<{
83
+ /**
84
+ * Emits when the textarea value changes, updating the modelValue prop.
85
+ */
86
+ (on: 'update:modelValue', value: string | number): void;
87
+ }>();
49
88
  </script>
50
89
 
51
- <style lang="scss">
52
- @import 'settings-tools/_all-settings';
53
- @import 'components/_c.textarea';
90
+ <style lang="scss" scoped>
91
+ @use '@mozaic-ds/styles/components/textarea';
54
92
  </style>
@@ -0,0 +1,121 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import { describe, it, expect } from 'vitest';
3
+ import MTextInput from './MTextInput.vue';
4
+ import Search24 from '@mozaic-ds/icons-vue/src/components/Search24/Search24.vue';
5
+
6
+ describe('MTextInput component', () => {
7
+ it('should render correctly with the given props', () => {
8
+ const wrapper = mount(MTextInput, {
9
+ props: {
10
+ id: 'input-id',
11
+ modelValue: 'test',
12
+ placeholder: 'Enter text',
13
+ inputType: 'text',
14
+ size: 's',
15
+ },
16
+ });
17
+
18
+ const input = wrapper.find('input');
19
+
20
+ expect(input.attributes('id')).toBe('input-id');
21
+ expect(input.attributes('placeholder')).toBe('Enter text');
22
+ expect(input.attributes('type')).toBe('text');
23
+ expect(wrapper.classes()).toContain('mc-text-input--s');
24
+ });
25
+
26
+ it('should update modelValue when input value changes', async () => {
27
+ const wrapper = mount(MTextInput, {
28
+ props: {
29
+ id: 'input-id',
30
+ modelValue: 'initial value',
31
+ },
32
+ });
33
+
34
+ const input = wrapper.find('input');
35
+ await input.setValue('new value');
36
+
37
+ expect(wrapper.emitted()['update:modelValue']).toBeTruthy();
38
+ expect(wrapper.emitted()['update:modelValue'][0]).toEqual(['new value']);
39
+ });
40
+
41
+ it('should render Search24 icon when passed to icon slot', () => {
42
+ const wrapper = mount(MTextInput, {
43
+ props: {
44
+ id: 'input-id',
45
+ modelValue: 'test',
46
+ },
47
+ slots: {
48
+ icon: Search24,
49
+ },
50
+ });
51
+
52
+ const iconWrapper = wrapper.find('.mc-text-input__icon');
53
+ expect(iconWrapper.exists()).toBe(true);
54
+ expect(iconWrapper.findComponent(Search24).exists()).toBe(true);
55
+ });
56
+
57
+ it('should render the clear button when isClearable is true and modelValue is not empty', () => {
58
+ const wrapper = mount(MTextInput, {
59
+ props: {
60
+ id: 'input-id',
61
+ modelValue: 'text content',
62
+ isClearable: true,
63
+ },
64
+ });
65
+
66
+ const clearButton = wrapper.find('.mc-controls-options__button');
67
+ expect(clearButton.exists()).toBe(true);
68
+ });
69
+
70
+ it('should clear the input value when the clear button is clicked', async () => {
71
+ const wrapper = mount(MTextInput, {
72
+ props: {
73
+ id: 'input-id',
74
+ modelValue: 'text content',
75
+ isClearable: true,
76
+ },
77
+ });
78
+
79
+ const clearButton = wrapper.find('.mc-controls-options__button');
80
+ await clearButton.trigger('click');
81
+
82
+ expect(wrapper.vm.modelValue).toBe('');
83
+ expect(wrapper.emitted()['update:modelValue'][0]).toEqual(['']);
84
+ });
85
+
86
+ it('should not render the clear button if isClearable is false', () => {
87
+ const wrapper = mount(MTextInput, {
88
+ props: {
89
+ id: 'input-id',
90
+ modelValue: 'text content',
91
+ isClearable: false,
92
+ },
93
+ });
94
+
95
+ const clearButton = wrapper.find('.mc-controls-options__button');
96
+ expect(clearButton.exists()).toBe(false);
97
+ });
98
+
99
+ it('should apply the correct classes based on validation props', () => {
100
+ const wrapper = mount(MTextInput, {
101
+ props: {
102
+ id: 'input-id',
103
+ modelValue: 'text content',
104
+ isInvalid: true,
105
+ },
106
+ });
107
+
108
+ expect(wrapper.classes()).toContain('is-invalid');
109
+ });
110
+
111
+ it('should not apply is-invalid if not specified', () => {
112
+ const wrapper = mount(MTextInput, {
113
+ props: {
114
+ id: 'input-id',
115
+ modelValue: 'text content',
116
+ },
117
+ });
118
+
119
+ expect(wrapper.classes()).not.toContain('is-invalid');
120
+ });
121
+ });
@@ -0,0 +1,107 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite';
2
+ import { action } from 'storybook/actions';
3
+
4
+ import MTextInput from './MTextInput.vue';
5
+ import Search24 from '@mozaic-ds/icons-vue/src/components/Search24/Search24.vue';
6
+
7
+ const meta: Meta<typeof MTextInput> = {
8
+ title: 'Form Elements/TextInput',
9
+ component: MTextInput,
10
+ parameters: {
11
+ docs: {
12
+ description: {
13
+ component:
14
+ 'A text input is a single-line input that allows users to enter and edit short text-based content. It is commonly used for names, email addresses, search queries, and form entries. Text Inputs often include placeholders, validation rules, and assistive text to guide users and ensure accurate data entry.<br><br> To put a label, requierement text, help text or to apply a valid or invalid message, the examples are available in the [Field section](/docs/form-elements-field--docs#input).',
15
+ },
16
+ },
17
+ },
18
+ args: {
19
+ id: 'textInputId',
20
+ placeholder: 'Placeholder',
21
+ },
22
+ render: (args) => ({
23
+ components: { MTextInput, Search24 },
24
+ setup() {
25
+ const handleUpdate = action('update:modelValue');
26
+
27
+ return { args, handleUpdate };
28
+ },
29
+ template: `
30
+ <MTextInput
31
+ v-bind="args"
32
+ @update:modelValue="handleUpdate"
33
+ />
34
+ `,
35
+ }),
36
+ };
37
+ export default meta;
38
+ type Story = StoryObj<typeof MTextInput>;
39
+
40
+ export const WithValue: Story = {
41
+ args: {
42
+ id: 'withValueId',
43
+ modelValue: 'Value of the input component',
44
+ isClearable: true,
45
+ },
46
+ };
47
+
48
+ export const Default: Story = {};
49
+
50
+ export const Small: Story = {
51
+ args: {
52
+ id: 'smallId',
53
+ size: 's',
54
+ },
55
+ };
56
+
57
+ export const WithIcon: Story = {
58
+ args: {
59
+ id: 'withIconId',
60
+ icon: `
61
+ <template v-slot:icon>
62
+ <Search24/>
63
+ </template>
64
+ `,
65
+ },
66
+ render: (args) => ({
67
+ components: { MTextInput, Search24 },
68
+ setup() {
69
+ return { args };
70
+ },
71
+ template: `
72
+ <MTextInput v-bind="args">
73
+ ${args.icon}
74
+ </MTextInput>
75
+ `,
76
+ }),
77
+ };
78
+
79
+ export const minValue: Story = {
80
+ args: {
81
+ id: 'minValueId',
82
+ inputType: 'number',
83
+ modelValue: '4',
84
+ min: '3',
85
+ },
86
+ };
87
+
88
+ export const Disabled: Story = {
89
+ args: {
90
+ id: 'disableId',
91
+ disabled: true,
92
+ },
93
+ };
94
+
95
+ export const ReadOnly: Story = {
96
+ args: {
97
+ id: 'readonlyId',
98
+ readonly: true,
99
+ },
100
+ };
101
+
102
+ export const Invalid: Story = {
103
+ args: {
104
+ id: 'invalidId',
105
+ isInvalid: true,
106
+ },
107
+ };
@@ -1,58 +1,138 @@
1
1
  <template>
2
- <div
3
- v-if="icon"
4
- key="icon-input"
5
- class="mc-left-icon-input"
6
- :class="cssFieldElementClass"
7
- >
8
- <m-text-input-icon :icon="icon" />
9
- <m-text-input-field
10
- ref="mField"
11
- v-bind="[$attrs, $props]"
12
- :class="textInputFieldClass"
13
- v-on="$listeners"
2
+ <div class="mc-text-input" :class="classObject">
3
+ <span v-if="$slots.icon" class="mc-text-input__icon">
4
+ <slot name="icon" />
5
+ </span>
6
+
7
+ <input
8
+ :id="id"
9
+ class="mc-text-input__control"
10
+ :value="modelValue"
11
+ :type="inputType"
12
+ :name="name"
13
+ :placeholder="placeholder"
14
+ :disabled="disabled"
15
+ :aria-invalid="isInvalid"
16
+ :readonly="readonly"
17
+ v-bind="$attrs"
18
+ @input="
19
+ emit('update:modelValue', ($event.target as HTMLInputElement).value)
20
+ "
14
21
  />
15
- </div>
16
22
 
17
- <m-text-input-field
18
- v-else
19
- key="input-only"
20
- ref="mField"
21
- v-bind="[$attrs, $props]"
22
- :class="cssFieldElementClass"
23
- v-on="$listeners"
24
- />
23
+ <div v-if="isClearable && modelValue" class="mc-controls-options">
24
+ <button
25
+ type="button"
26
+ class="mc-controls-options__button"
27
+ @click="clearValue"
28
+ >
29
+ <CrossCircleFilled24
30
+ class="mc-controls-options__icon"
31
+ aria-hidden="true"
32
+ />
33
+ <span class="mc-controls-options__label">{clearLabel}</span>
34
+ </button>
35
+ </div>
36
+ </div>
25
37
  </template>
26
38
 
27
- <script>
28
- import MTextInputField from './MTextInputField.vue';
29
- import MTextInputIcon from './MTextInputIcon.vue';
39
+ <script setup lang="ts">
40
+ import { computed, ref, type VNode } from 'vue';
41
+ import CrossCircleFilled24 from '@mozaic-ds/icons-vue/src/components/CrossCircleFilled24/CrossCircleFilled24.vue';
30
42
 
31
- export default {
32
- name: 'MTextInput',
33
-
34
- components: {
35
- MTextInputField,
36
- MTextInputIcon,
43
+ /**
44
+ * Inputs are used to create input fields with text on a single line. Their states depends on the user interaction or the context.
45
+ */
46
+ const props = withDefaults(
47
+ defineProps<{
48
+ /**
49
+ * A unique identifier for the input element, used to associate the label with the form element.
50
+ */
51
+ id: string;
52
+ /**
53
+ * The name attribute for the input element, typically used for form submission.
54
+ */
55
+ name?: string;
56
+ /**
57
+ * The current value of the input field.
58
+ */
59
+ modelValue?: string | number;
60
+ /**
61
+ * A placeholder text to show in the input when it is empty.
62
+ */
63
+ placeholder?: string;
64
+ /**
65
+ * Defines the type of input
66
+ */
67
+ inputType?:
68
+ | 'date'
69
+ | 'email'
70
+ | 'number'
71
+ | 'password'
72
+ | 'search'
73
+ | 'tel'
74
+ | 'text';
75
+ /**
76
+ * If `true`, applies an invalid state to the input.
77
+ */
78
+ isInvalid?: boolean;
79
+ /**
80
+ * If `true`, disables the input, making it non-interactive.
81
+ */
82
+ disabled?: boolean;
83
+ /**
84
+ * Determines the size of the input
85
+ */
86
+ size?: 's' | 'm';
87
+ /**
88
+ * If `true`, the input is read-only (cannot be edited).
89
+ */
90
+ readonly?: boolean;
91
+ /**
92
+ * If `true`, a clear button will appear when the input has a value.
93
+ */
94
+ isClearable?: boolean;
95
+ /**
96
+ * The label text for the clear button
97
+ */
98
+ clearLabel?: string;
99
+ }>(),
100
+ {
101
+ inputType: 'text',
102
+ size: 'm',
103
+ clearLabel: 'clear content',
37
104
  },
105
+ );
38
106
 
39
- inject: {
40
- cssFieldElementClass: {
41
- default: '',
42
- },
43
- },
107
+ defineSlots<{
108
+ /**
109
+ * Use this slot to insert an icon in the input
110
+ */
111
+ icon?: VNode;
112
+ }>();
44
113
 
45
- inheritAttrs: false,
46
-
47
- props: {
48
- icon: {
49
- type: String,
50
- default: null,
51
- },
52
- textInputFieldClass: {
53
- type: String,
54
- default: null,
55
- },
56
- },
114
+ const classObject = computed(() => {
115
+ return {
116
+ [`mc-text-input--${props.size}`]: props.size && props.size != 'm',
117
+ 'is-invalid': props.isInvalid,
118
+ };
119
+ });
120
+
121
+ const modelValue = ref(props.modelValue);
122
+ const clearValue = () => {
123
+ modelValue.value = '';
124
+ emit('update:modelValue', '');
57
125
  };
126
+
127
+ const emit = defineEmits<{
128
+ /**
129
+ * Emits when the input value changes, updating the `modelValue` prop.
130
+ */
131
+ (on: 'update:modelValue', value: string | number): void;
132
+ }>();
58
133
  </script>
134
+
135
+ <style lang="scss" scoped>
136
+ @use '@mozaic-ds/styles/components/controls-options';
137
+ @use '@mozaic-ds/styles/components/text-input';
138
+ </style>
@@ -0,0 +1,99 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import { describe, it, expect } from 'vitest';
3
+ import MToggle from './MToggle.vue';
4
+
5
+ describe('MToggle Component', () => {
6
+ it('should render the toggle with default props', () => {
7
+ const wrapper = mount(MToggle, {
8
+ props: {
9
+ id: 'test-toggle',
10
+ },
11
+ });
12
+
13
+ const input = wrapper.find('input');
14
+ expect(input.exists()).toBe(true);
15
+ expect(input.attributes().id).toBe('test-toggle');
16
+ expect(input.classes()).toContain('mc-toggle__input');
17
+ });
18
+
19
+ it('should bind and update modelValue (v-model)', async () => {
20
+ const wrapper = mount(MToggle, {
21
+ props: {
22
+ id: 'test-toggle',
23
+ modelValue: false,
24
+ },
25
+ });
26
+
27
+ const input = wrapper.find('input');
28
+ expect(input.element.checked).toBe(false);
29
+
30
+ await input.setChecked(true);
31
+
32
+ const emittedEvent = wrapper.emitted()['update:modelValue'];
33
+ expect(emittedEvent).toBeDefined();
34
+ expect(emittedEvent![0]).toEqual([true]);
35
+ });
36
+
37
+ it('is disabled when the disabled prop is true', () => {
38
+ const wrapper = mount(MToggle, {
39
+ props: {
40
+ id: 'test-toggle',
41
+ disabled: true,
42
+ },
43
+ });
44
+
45
+ const checkbox = wrapper.find('input');
46
+ expect(checkbox.element.disabled).toBe(true);
47
+ });
48
+
49
+ it('should render the label if provided', () => {
50
+ const wrapper = mount(MToggle, {
51
+ props: {
52
+ id: 'test-toggle',
53
+ label: 'Enable feature',
54
+ },
55
+ });
56
+
57
+ const label = wrapper.find('.mc-toggle__label');
58
+ expect(label.exists()).toBe(true);
59
+ expect(label.text()).toBe('Enable feature');
60
+ });
61
+
62
+ it('should not render the label if not provided', () => {
63
+ const wrapper = mount(MToggle, {
64
+ props: {
65
+ id: 'test-toggle',
66
+ },
67
+ });
68
+
69
+ const label = wrapper.find('.mc-toggle__label');
70
+ expect(label.exists()).toBe(false);
71
+ });
72
+
73
+ it('should apply the correct size class based on the size prop', () => {
74
+ const wrapper = mount(MToggle, {
75
+ props: {
76
+ id: 'test-toggle',
77
+ size: 'm',
78
+ },
79
+ });
80
+
81
+ expect(wrapper.classes()).toContain('mc-toggle--m');
82
+ });
83
+
84
+ it('should emit updated modelValue on toggle change', async () => {
85
+ const wrapper = mount(MToggle, {
86
+ props: {
87
+ id: 'test-toggle',
88
+ modelValue: false,
89
+ },
90
+ });
91
+
92
+ const input = wrapper.find('input');
93
+ await input.setChecked(true);
94
+
95
+ const emittedEvent = wrapper.emitted()['update:modelValue'];
96
+ expect(emittedEvent).toBeDefined();
97
+ expect(emittedEvent![0]).toEqual([true]);
98
+ });
99
+ });