@mozaic-ds/vue 1.0.0-rc.2 → 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.
- package/LICENSE +51 -0
- package/README.md +76 -77
- package/dist/mozaic-vue.css +1 -1
- package/dist/mozaic-vue.d.ts +1664 -0
- package/dist/mozaic-vue.js +1943 -0
- package/dist/mozaic-vue.js.map +1 -0
- package/dist/mozaic-vue.umd.cjs +2 -0
- package/dist/mozaic-vue.umd.cjs.map +1 -0
- package/env.d.ts +1 -0
- package/package.json +78 -51
- package/src/components/Contributing.mdx +118 -0
- package/src/components/GettingStarted.mdx +45 -0
- package/src/components/Introduction.mdx +100 -0
- package/src/components/Support.mdx +18 -0
- package/src/components/breadcrumb/MBreadcrumb.spec.ts +105 -0
- package/src/components/breadcrumb/MBreadcrumb.stories.ts +82 -0
- package/src/components/breadcrumb/MBreadcrumb.vue +52 -55
- package/src/components/button/MButton.spec.ts +191 -0
- package/src/components/button/MButton.stories.ts +59 -0
- package/src/components/button/MButton.vue +98 -154
- package/src/components/checkbox/MCheckbox.spec.ts +104 -0
- package/src/components/checkbox/MCheckbox.stories.ts +83 -0
- package/src/components/checkbox/MCheckbox.vue +60 -101
- package/src/components/checkboxgroup/MCheckboxGroup.spec.ts +78 -0
- package/src/components/checkboxgroup/MCheckboxGroup.stories.ts +61 -0
- package/src/components/checkboxgroup/MCheckboxGroup.vue +97 -0
- package/src/components/datepicker/MDatepicker.spec.ts +95 -0
- package/src/components/datepicker/MDatepicker.stories.ts +75 -0
- package/src/components/datepicker/MDatepicker.vue +114 -0
- package/src/components/divider/MDivider.spec.ts +57 -0
- package/src/components/divider/MDivider.stories.ts +64 -0
- package/src/components/divider/MDivider.vue +56 -0
- package/src/components/drawer/MDrawer.spec.ts +100 -0
- package/src/components/drawer/MDrawer.stories.ts +128 -0
- package/src/components/drawer/MDrawer.vue +140 -0
- package/src/components/field/MField.spec.ts +166 -0
- package/src/components/field/MField.stories.ts +369 -0
- package/src/components/field/MField.vue +78 -61
- package/src/components/fieldgroup/MFieldGroup.spec.ts +165 -0
- package/src/components/fieldgroup/MFieldGroup.stories.ts +416 -0
- package/src/components/fieldgroup/MFieldGroup.vue +79 -0
- package/src/components/flag/MFlag.spec.ts +46 -0
- package/src/components/flag/MFlag.stories.ts +46 -0
- package/src/components/flag/MFlag.vue +28 -39
- package/src/components/iconbutton/MIconButton.spec.ts +108 -0
- package/src/components/iconbutton/MIconButton.stories.ts +74 -0
- package/src/components/iconbutton/MIconButton.vue +73 -0
- package/src/components/link/MLink.spec.ts +154 -0
- package/src/components/link/MLink.stories.ts +89 -0
- package/src/components/link/MLink.vue +86 -120
- package/src/components/loader/MLoader.spec.ts +104 -0
- package/src/components/loader/MLoader.stories.ts +43 -0
- package/src/components/loader/MLoader.vue +66 -55
- package/src/components/loadingoverlay/MLoadingOverlay.spec.ts +37 -0
- package/src/components/loadingoverlay/MLoadingOverlay.stories.ts +40 -0
- package/src/components/loadingoverlay/MLoadingOverlay.vue +28 -0
- package/src/components/modal/MModal.spec.ts +103 -0
- package/src/components/modal/MModal.stories.ts +127 -0
- package/src/components/modal/MModal.vue +106 -154
- package/src/components/numberbadge/MNumberBadge.spec.ts +56 -0
- package/src/components/numberbadge/MNumberBadge.stories.ts +48 -0
- package/src/components/numberbadge/MNumberBadge.vue +45 -0
- package/src/components/overlay/MOverlay.spec.ts +51 -0
- package/src/components/overlay/MOverlay.stories.ts +35 -0
- package/src/components/overlay/MOverlay.vue +27 -19
- package/src/components/pagination/MPagination.spec.ts +123 -0
- package/src/components/pagination/MPagination.stories.ts +83 -0
- package/src/components/pagination/MPagination.vue +120 -140
- package/src/components/passwordinput/MPasswordInput.spec.ts +104 -0
- package/src/components/passwordinput/MPasswordInput.stories.ts +75 -0
- package/src/components/passwordinput/MPasswordInput.vue +126 -77
- package/src/components/pincode/MPincode.spec.ts +126 -0
- package/src/components/pincode/MPincode.stories.ts +68 -0
- package/src/components/pincode/MPincode.vue +148 -0
- package/src/components/quantityselector/MQuantitySelector.spec.ts +262 -0
- package/src/components/quantityselector/MQuantitySelector.stories.ts +89 -0
- package/src/components/quantityselector/MQuantitySelector.vue +159 -148
- package/src/components/radio/MRadio.spec.ts +104 -0
- package/src/components/radio/MRadio.stories.ts +68 -0
- package/src/components/radio/MRadio.vue +56 -39
- package/src/components/radiogroup/MRadioGroup.spec.ts +54 -0
- package/src/components/radiogroup/MRadioGroup.stories.ts +61 -0
- package/src/components/radiogroup/MRadioGroup.vue +79 -0
- package/src/components/select/MSelect.spec.ts +114 -0
- package/src/components/select/MSelect.stories.ts +101 -0
- package/src/components/select/MSelect.vue +77 -119
- package/src/components/statusbadge/MStatusBadge.stories.ts +45 -0
- package/src/components/statusbadge/MStatusBadge.vue +40 -0
- package/src/components/statusbadge/MstatusBadge.spec.ts +16 -0
- package/src/components/statusdot/MStatusDot.spec.ts +51 -0
- package/src/components/statusdot/MStatusDot.stories.ts +48 -0
- package/src/components/statusdot/MStatusDot.vue +36 -0
- package/src/components/statusnotification/MStatusNotification.spec.ts +103 -0
- package/src/components/statusnotification/MStatusNotification.stories.ts +89 -0
- package/src/components/statusnotification/MStatusNotification.vue +106 -0
- package/src/components/tabs/MTabs.stories.ts +104 -0
- package/src/components/tabs/MTabs.vue +113 -0
- package/src/components/tabs/Mtabs.spec.ts +149 -0
- package/src/components/tag/MTag.spec.ts +107 -0
- package/src/components/tag/MTag.stories.ts +75 -0
- package/src/components/tag/MTag.vue +151 -0
- package/src/components/textarea/MTextArea.spec.ts +112 -0
- package/src/components/textarea/MTextArea.stories.ts +67 -0
- package/src/components/textarea/MTextArea.vue +81 -43
- package/src/components/textinput/MTextInput.spec.ts +121 -0
- package/src/components/textinput/MTextInput.stories.ts +107 -0
- package/src/components/textinput/MTextInput.vue +127 -47
- package/src/components/toggle/MToggle.spec.ts +99 -0
- package/src/components/toggle/MToggle.stories.ts +68 -0
- package/src/components/toggle/MToggle.vue +63 -103
- package/src/components/togglegroup/MToggleGroup.spec.ts +78 -0
- package/src/components/togglegroup/MToggleGroup.stories.ts +61 -0
- package/src/components/togglegroup/MToggleGroup.vue +97 -0
- package/src/components/usingIcons.mdx +35 -0
- package/src/components/usingPresets.mdx +128 -0
- package/src/main.ts +32 -0
- package/dist/demo.html +0 -1
- package/dist/mozaic-vue.adeo.css +0 -47
- package/dist/mozaic-vue.adeo.umd.js +0 -31109
- package/dist/mozaic-vue.common.js +0 -31099
- package/dist/mozaic-vue.common.js.map +0 -1
- package/dist/mozaic-vue.umd.js +0 -31110
- package/dist/mozaic-vue.umd.js.map +0 -1
- package/dist/mozaic-vue.umd.min.js +0 -4
- package/dist/mozaic-vue.umd.min.js.map +0 -1
- package/postinstall.js +0 -3
- package/src/components/accordion/MAccordion.vue +0 -128
- package/src/components/accordion/index.js +0 -7
- package/src/components/autocomplete/MAutocomplete.vue +0 -381
- package/src/components/autocomplete/index.js +0 -7
- package/src/components/badge/MBadge.vue +0 -43
- package/src/components/badge/index.js +0 -7
- package/src/components/breadcrumb/index.js +0 -7
- package/src/components/button/index.js +0 -7
- package/src/components/card/MCard.vue +0 -78
- package/src/components/card/index.js +0 -7
- package/src/components/checkbox/MCheckboxGroup.vue +0 -163
- package/src/components/checkbox/index.js +0 -12
- package/src/components/container/MContainer.vue +0 -33
- package/src/components/container/index.js +0 -7
- package/src/components/datatable/MDataTable.vue +0 -651
- package/src/components/datatable/MDataTableHeader.vue +0 -55
- package/src/components/datatable/MDataTableTop.vue +0 -35
- package/src/components/datatable/helpers.js +0 -132
- package/src/components/datatable/index.js +0 -12
- package/src/components/dropdown/MDropdown.vue +0 -317
- package/src/components/dropdown/index.js +0 -7
- package/src/components/field/index.js +0 -7
- package/src/components/fileuploader/MFileResult.vue +0 -149
- package/src/components/fileuploader/MFileUploader.vue +0 -142
- package/src/components/fileuploader/index.js +0 -7
- package/src/components/flag/index.js +0 -7
- package/src/components/heading/MHeading.vue +0 -75
- package/src/components/heading/index.js +0 -7
- package/src/components/hero/MHero.vue +0 -93
- package/src/components/hero/index.js +0 -7
- package/src/components/icon/MIcon.vue +0 -136
- package/src/components/icon/index.js +0 -7
- package/src/components/index.js +0 -44
- package/src/components/layer/MLayer.vue +0 -208
- package/src/components/layer/index.js +0 -7
- package/src/components/link/index.js +0 -7
- package/src/components/listbox/MListBox.vue +0 -146
- package/src/components/listbox/MListBoxActions.vue +0 -251
- package/src/components/listbox/index.js +0 -12
- package/src/components/loader/index.js +0 -7
- package/src/components/modal/index.js +0 -7
- package/src/components/notification/MNotification.vue +0 -110
- package/src/components/notification/index.js +0 -7
- package/src/components/optionbutton/MOptionButton.vue +0 -67
- package/src/components/optionbutton/index.js +0 -7
- package/src/components/optioncard/MOptionCard.vue +0 -132
- package/src/components/optioncard/index.js +0 -7
- package/src/components/optiongroup/MOptionGroup.vue +0 -18
- package/src/components/optiongroup/index.js +0 -7
- package/src/components/overlay/MOverlayLoader.vue +0 -43
- package/src/components/overlay/index.js +0 -12
- package/src/components/pagination/index.js +0 -7
- package/src/components/passwordinput/index.js +0 -7
- package/src/components/phonenumber/MPhoneNumber.vue +0 -398
- package/src/components/phonenumber/index.js +0 -7
- package/src/components/progressbar/MProgress.vue +0 -102
- package/src/components/progressbar/index.js +0 -7
- package/src/components/quantityselector/index.js +0 -7
- package/src/components/radio/MRadioGroup.vue +0 -111
- package/src/components/radio/index.js +0 -12
- package/src/components/ratingstars/MStarsInput.vue +0 -119
- package/src/components/ratingstars/MStarsResult.vue +0 -89
- package/src/components/ratingstars/index.js +0 -12
- package/src/components/select/index.js +0 -7
- package/src/components/stepper/MStepper.vue +0 -111
- package/src/components/stepper/index.js +0 -7
- package/src/components/tabs/MTab.vue +0 -198
- package/src/components/tabs/index.js +0 -7
- package/src/components/tags/MTag.vue +0 -173
- package/src/components/tags/index.js +0 -7
- package/src/components/textarea/index.js +0 -7
- package/src/components/textinput/MTextInputField.vue +0 -105
- package/src/components/textinput/MTextInputIcon.vue +0 -42
- package/src/components/textinput/index.js +0 -7
- package/src/components/toggle/index.js +0 -7
- package/src/components/tooltip/MTooltip.vue +0 -42
- package/src/components/tooltip/index.js +0 -7
- package/src/index.js +0 -63
- package/src/shims-tsx.d.ts +0 -13
- package/src/shims.vue.d.ts +0 -4
- package/src/tokens/adeo/android/colors.xml +0 -448
- package/src/tokens/adeo/android/font_dimens.xml +0 -18
- package/src/tokens/adeo/css/_variables.scss +0 -442
- package/src/tokens/adeo/css/root.scss +0 -444
- package/src/tokens/adeo/ios/StyleDictionaryColor.h +0 -456
- package/src/tokens/adeo/ios/StyleDictionaryColor.m +0 -468
- package/src/tokens/adeo/ios/StyleDictionaryColor.swift +0 -451
- package/src/tokens/adeo/ios/StyleDictionarySize.h +0 -69
- package/src/tokens/adeo/ios/StyleDictionarySize.m +0 -70
- package/src/tokens/adeo/ios/StyleDictionarySize.swift +0 -71
- package/src/tokens/adeo/js/tokens.js +0 -540
- package/src/tokens/adeo/js/tokensObject.js +0 -11641
- package/src/tokens/adeo/scss/_tokens.scss +0 -1514
- package/src/utils/mozaicClasses.js +0 -16
- package/src/utils/theme.validator.js +0 -19
- package/types/index.d.ts +0 -104
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import MTabs from './MTabs.vue';
|
|
4
|
+
import { defineComponent, h } from 'vue';
|
|
5
|
+
|
|
6
|
+
describe('MTabs.vue', () => {
|
|
7
|
+
const tabs = [{ label: 'Tab 1' }, { label: 'Tab 2' }, { label: 'Tab 3' }];
|
|
8
|
+
|
|
9
|
+
it('renders tabs with correct labels', () => {
|
|
10
|
+
const wrapper = mount(MTabs, {
|
|
11
|
+
props: {
|
|
12
|
+
tabs,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const tabElements = wrapper.findAll('li.mc-tabs__item');
|
|
17
|
+
expect(tabElements.length).toBe(tabs.length);
|
|
18
|
+
|
|
19
|
+
tabs.forEach((tab, i) => {
|
|
20
|
+
expect(tabElements[i].text()).toContain(tab.label);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('applies selected class and aria-selected attribute based on modelValue and updates on tab click', async () => {
|
|
25
|
+
const wrapper = mount(MTabs, {
|
|
26
|
+
props: {
|
|
27
|
+
tabs,
|
|
28
|
+
modelValue: 0,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const buttons = wrapper.findAll('button.mc-tabs__tab');
|
|
33
|
+
|
|
34
|
+
buttons.forEach((button, i) => {
|
|
35
|
+
if (i === 0) {
|
|
36
|
+
expect(button.classes()).toContain('mc-tabs__tab--selected');
|
|
37
|
+
expect(button.attributes('aria-selected')).toBe('true');
|
|
38
|
+
} else {
|
|
39
|
+
expect(button.classes()).not.toContain('mc-tabs__tab--selected');
|
|
40
|
+
expect(button.attributes('aria-selected')).toBe('false');
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await buttons[1].trigger('click');
|
|
45
|
+
|
|
46
|
+
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
|
|
47
|
+
expect(wrapper.emitted('update:modelValue')![0]).toEqual([1]);
|
|
48
|
+
|
|
49
|
+
await wrapper.setProps({ modelValue: 1 });
|
|
50
|
+
|
|
51
|
+
const updatedButtons = wrapper.findAll('button.mc-tabs__tab');
|
|
52
|
+
updatedButtons.forEach((button, i) => {
|
|
53
|
+
if (i === 1) {
|
|
54
|
+
expect(button.classes()).toContain('mc-tabs__tab--selected');
|
|
55
|
+
expect(button.attributes('aria-selected')).toBe('true');
|
|
56
|
+
} else {
|
|
57
|
+
expect(button.classes()).not.toContain('mc-tabs__tab--selected');
|
|
58
|
+
expect(button.attributes('aria-selected')).toBe('false');
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('adds divider and centered classes based on props', async () => {
|
|
64
|
+
const wrapper = mount(MTabs, {
|
|
65
|
+
props: {
|
|
66
|
+
tabs,
|
|
67
|
+
divider: true,
|
|
68
|
+
centered: true,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(wrapper.classes()).toContain('mc-tabs--centered');
|
|
73
|
+
expect(wrapper.findComponent({ name: 'MDivider' }).exists()).toBe(true);
|
|
74
|
+
|
|
75
|
+
await wrapper.setProps({ divider: false, centered: false });
|
|
76
|
+
|
|
77
|
+
expect(wrapper.classes()).not.toContain('mc-tabs--centered');
|
|
78
|
+
expect(wrapper.findComponent({ name: 'MDivider' }).exists()).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('sets aria-label on tablist based on description prop', () => {
|
|
82
|
+
const description = 'Main tabs navigation';
|
|
83
|
+
const wrapper = mount(MTabs, {
|
|
84
|
+
props: {
|
|
85
|
+
tabs,
|
|
86
|
+
description,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const ul = wrapper.find('ul[role="tablist"]');
|
|
91
|
+
expect(ul.attributes('aria-label')).toBe(description);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('renders no tabs if tabs prop is empty', () => {
|
|
95
|
+
const wrapper = mount(MTabs, { props: { tabs: [] } });
|
|
96
|
+
expect(wrapper.findAll('li.mc-tabs__item').length).toBe(0);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('emits update:modelValue on tab click if tab is not disabled', async () => {
|
|
100
|
+
const wrapper = mount(MTabs, {
|
|
101
|
+
props: {
|
|
102
|
+
tabs: [
|
|
103
|
+
{ label: 'Tab 1' },
|
|
104
|
+
{ label: 'Tab 2', disabled: true },
|
|
105
|
+
{ label: 'Tab 3' },
|
|
106
|
+
],
|
|
107
|
+
modelValue: 0,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const buttons = wrapper.findAll('button.mc-tabs__tab');
|
|
112
|
+
|
|
113
|
+
await buttons[2].trigger('click');
|
|
114
|
+
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
|
|
115
|
+
expect(wrapper.emitted('update:modelValue')![0]).toEqual([2]);
|
|
116
|
+
|
|
117
|
+
await buttons[1].trigger('click');
|
|
118
|
+
expect(wrapper.emitted('update:modelValue')!.length).toBe(1);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('renders icon component when icon prop is provided', () => {
|
|
122
|
+
const DummyIcon = defineComponent({
|
|
123
|
+
name: 'DummyIcon',
|
|
124
|
+
render() {
|
|
125
|
+
return h('svg', { class: 'dummy-icon' }, [
|
|
126
|
+
h('circle', { cx: 10, cy: 10, r: 10 }),
|
|
127
|
+
]);
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const tabsWithIcon = [
|
|
132
|
+
{ label: 'Tab 1', icon: DummyIcon },
|
|
133
|
+
{ label: 'Tab 2' },
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
const wrapper = mount(MTabs, {
|
|
137
|
+
props: {
|
|
138
|
+
tabs: tabsWithIcon,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const firstTabButton = wrapper.findAll('button.mc-tabs__tab')[0];
|
|
143
|
+
expect(firstTabButton.findComponent(DummyIcon).exists()).toBe(true);
|
|
144
|
+
expect(firstTabButton.find('svg.dummy-icon').exists()).toBe(true);
|
|
145
|
+
|
|
146
|
+
const secondTabButton = wrapper.findAll('button.mc-tabs__tab')[1];
|
|
147
|
+
expect(secondTabButton.findComponent(DummyIcon).exists()).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import MTag from './MTag.vue';
|
|
3
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
describe('MTag.vue', () => {
|
|
6
|
+
it('renders a selectable tag with a checkbox and label', async () => {
|
|
7
|
+
const wrapper = mount(MTag, {
|
|
8
|
+
props: {
|
|
9
|
+
type: 'selectable',
|
|
10
|
+
label: 'Test Tag',
|
|
11
|
+
modelValue: false,
|
|
12
|
+
id: 'test-tag-id',
|
|
13
|
+
name: 'test-tag',
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const checkbox = wrapper.find('input');
|
|
18
|
+
const label = wrapper.find('.mc-tag__label');
|
|
19
|
+
|
|
20
|
+
expect(checkbox.exists()).toBe(true);
|
|
21
|
+
expect(label.text()).toBe('Test Tag');
|
|
22
|
+
expect(checkbox.element.checked).toBe(false);
|
|
23
|
+
|
|
24
|
+
await checkbox.setChecked();
|
|
25
|
+
expect(wrapper.emitted()['update:modelValue'][0]).toEqual([true]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('renders an interactive tag as a button with label', () => {
|
|
29
|
+
const wrapper = mount(MTag, {
|
|
30
|
+
props: {
|
|
31
|
+
type: 'interactive',
|
|
32
|
+
label: 'Interactive Tag',
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const button = wrapper.find('button');
|
|
37
|
+
const label = wrapper.find('.mc-tag__label');
|
|
38
|
+
|
|
39
|
+
expect(button.exists()).toBe(true);
|
|
40
|
+
expect(label.text()).toBe('Interactive Tag');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('renders a contextualised tag with badge number', () => {
|
|
44
|
+
const wrapper = mount(MTag, {
|
|
45
|
+
props: {
|
|
46
|
+
type: 'contextualised',
|
|
47
|
+
label: 'Contextualised Tag',
|
|
48
|
+
contextualisedNumber: 42,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const badge = wrapper.findComponent({ name: 'MNumberBadge' });
|
|
53
|
+
const label = wrapper.find('.mc-tag__label');
|
|
54
|
+
|
|
55
|
+
expect(badge.exists()).toBe(true);
|
|
56
|
+
expect(badge.props('label')).toBe(42);
|
|
57
|
+
expect(label.text()).toBe('Contextualised Tag');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('renders a removable tag with a delete button and emits remove event', async () => {
|
|
61
|
+
const removeTag = vi.fn();
|
|
62
|
+
const wrapper = mount(MTag, {
|
|
63
|
+
props: {
|
|
64
|
+
type: 'removable',
|
|
65
|
+
label: 'Removable Tag',
|
|
66
|
+
id: 'removable-tag-id',
|
|
67
|
+
},
|
|
68
|
+
global: {
|
|
69
|
+
mocks: {
|
|
70
|
+
emit: removeTag,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const removeButton = wrapper.find('button.mc-tag-removable__remove');
|
|
76
|
+
expect(removeButton.exists()).toBe(true);
|
|
77
|
+
|
|
78
|
+
await removeButton.trigger('click');
|
|
79
|
+
expect(removeTag).toHaveBeenCalledWith('remove-tag', 'removable-tag-id');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('renders with the correct size classes based on the size prop', () => {
|
|
83
|
+
const wrapper = mount(MTag, {
|
|
84
|
+
props: {
|
|
85
|
+
type: 'informative',
|
|
86
|
+
label: 'Informative Tag',
|
|
87
|
+
size: 'l',
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const element = wrapper.find('span.mc-tag');
|
|
92
|
+
expect(element.classes()).toContain('mc-tag--l');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should disable the tag when the disabled prop is true', () => {
|
|
96
|
+
const wrapper = mount(MTag, {
|
|
97
|
+
props: {
|
|
98
|
+
type: 'selectable',
|
|
99
|
+
label: 'Disabled Tag',
|
|
100
|
+
disabled: true,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const checkbox = wrapper.find('input');
|
|
105
|
+
expect(checkbox.element.disabled).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
|
+
import { action } from 'storybook/actions';
|
|
3
|
+
import MTag from './MTag.vue';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof MTag> = {
|
|
6
|
+
title: 'Indicators/Tag',
|
|
7
|
+
component: MTag,
|
|
8
|
+
parameters: {
|
|
9
|
+
docs: {
|
|
10
|
+
description: {
|
|
11
|
+
component:
|
|
12
|
+
'A Status dot is a small visual indicator used to represent the state or condition of an element. It is often color-coded to convey different statuses at a glance, such as availability, activity, or urgency. Status Dots are commonly found in user presence indicators, system statuses, or process tracking to provide quick, unobtrusive feedback.',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
args: {
|
|
17
|
+
label: 'Tag label',
|
|
18
|
+
},
|
|
19
|
+
render: (args) => ({
|
|
20
|
+
components: { MTag },
|
|
21
|
+
setup() {
|
|
22
|
+
const handleUpdate = action('update:modelValue');
|
|
23
|
+
const handleRemoveTag = action('remove-tag');
|
|
24
|
+
|
|
25
|
+
return { args, handleUpdate, handleRemoveTag };
|
|
26
|
+
},
|
|
27
|
+
template: `
|
|
28
|
+
<MTag
|
|
29
|
+
v-bind="args"
|
|
30
|
+
@update:modelValue="handleUpdate"
|
|
31
|
+
@remove-tag="handleRemoveTag"
|
|
32
|
+
></MTag>
|
|
33
|
+
`,
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
export default meta;
|
|
37
|
+
type Story = StoryObj<typeof MTag>;
|
|
38
|
+
|
|
39
|
+
export const Default: Story = {};
|
|
40
|
+
|
|
41
|
+
export const Size: Story = {
|
|
42
|
+
args: { size: 's' },
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const Interactive: Story = {
|
|
46
|
+
args: { type: 'interactive' },
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const Disabled: Story = {
|
|
50
|
+
args: {
|
|
51
|
+
type: 'interactive',
|
|
52
|
+
disabled: true,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const Contextualised: Story = {
|
|
57
|
+
args: {
|
|
58
|
+
type: 'contextualised',
|
|
59
|
+
contextualisedNumber: 99,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const Removable: Story = {
|
|
64
|
+
args: {
|
|
65
|
+
type: 'removable',
|
|
66
|
+
id: 'tagId',
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const Selectable: Story = {
|
|
71
|
+
args: {
|
|
72
|
+
type: 'selectable',
|
|
73
|
+
modelValue: true,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<label
|
|
3
|
+
v-if="type === 'selectable'"
|
|
4
|
+
:for="id"
|
|
5
|
+
class="mc-tag"
|
|
6
|
+
:class="classObject"
|
|
7
|
+
>
|
|
8
|
+
<input
|
|
9
|
+
type="checkbox"
|
|
10
|
+
class="mc-tag__input"
|
|
11
|
+
:id="id"
|
|
12
|
+
:name="name"
|
|
13
|
+
:checked="modelValue"
|
|
14
|
+
:disabled="disabled"
|
|
15
|
+
@change="
|
|
16
|
+
emit('update:modelValue', ($event.target as HTMLInputElement).checked)
|
|
17
|
+
"
|
|
18
|
+
v-bind="$attrs"
|
|
19
|
+
/>
|
|
20
|
+
<span class="mc-tag__label">{{ label }}</span>
|
|
21
|
+
</label>
|
|
22
|
+
|
|
23
|
+
<button
|
|
24
|
+
v-else-if="type === 'interactive'"
|
|
25
|
+
class="mc-tag"
|
|
26
|
+
type="button"
|
|
27
|
+
:class="classObject"
|
|
28
|
+
:disabled="disabled"
|
|
29
|
+
v-bind="$attrs"
|
|
30
|
+
>
|
|
31
|
+
<span class="mc-tag__label">{{ label }}</span>
|
|
32
|
+
</button>
|
|
33
|
+
|
|
34
|
+
<button
|
|
35
|
+
v-else-if="type === 'contextualised'"
|
|
36
|
+
class="mc-tag"
|
|
37
|
+
type="button"
|
|
38
|
+
:class="classObject"
|
|
39
|
+
:disabled="disabled"
|
|
40
|
+
v-bind="$attrs"
|
|
41
|
+
>
|
|
42
|
+
<MNumberBadge
|
|
43
|
+
appearance="inverse"
|
|
44
|
+
:label="contextualisedNumber"
|
|
45
|
+
:size="size === 'l' ? 'm' : undefined"
|
|
46
|
+
/>
|
|
47
|
+
<span class="mc-tag__label">{{ label }}</span>
|
|
48
|
+
</button>
|
|
49
|
+
|
|
50
|
+
<span
|
|
51
|
+
v-else-if="type === 'removable'"
|
|
52
|
+
class="mc-tag"
|
|
53
|
+
:class="classObject"
|
|
54
|
+
v-bind="$attrs"
|
|
55
|
+
>
|
|
56
|
+
<span class="mc-tag__label">{{ label }}</span>
|
|
57
|
+
<button
|
|
58
|
+
class="mc-tag-removable__remove"
|
|
59
|
+
type="button"
|
|
60
|
+
@click="id && emit('remove-tag', id)"
|
|
61
|
+
>
|
|
62
|
+
<CrossCircleFilled24 class="mc-tag-removable__icon" aria-hidden="true" />
|
|
63
|
+
<span class="mc-tag-removable__text">removableLabel</span>
|
|
64
|
+
</button>
|
|
65
|
+
</span>
|
|
66
|
+
|
|
67
|
+
<!-- informative -->
|
|
68
|
+
<span v-else class="mc-tag" :class="classObject" v-bind="$attrs">
|
|
69
|
+
<span class="mc-tag__label">{{ label }}</span>
|
|
70
|
+
</span>
|
|
71
|
+
</template>
|
|
72
|
+
|
|
73
|
+
<script setup lang="ts">
|
|
74
|
+
import { computed } from 'vue';
|
|
75
|
+
import CrossCircleFilled24 from '@mozaic-ds/icons-vue/src/components/CrossCircleFilled24/CrossCircleFilled24.vue';
|
|
76
|
+
import MNumberBadge from '../numberbadge/MNumberBadge.vue';
|
|
77
|
+
/**
|
|
78
|
+
* A Tag is a UI element used to filter data, categorize, select or deselect an option. It can appear standalone, in a group, or embedded within other components. Depending on its use, a tag can be interactive (clickable, removable, selectable) or static (serving as a visual indicator).
|
|
79
|
+
*/
|
|
80
|
+
const props = withDefaults(
|
|
81
|
+
defineProps<{
|
|
82
|
+
/**
|
|
83
|
+
* Defines the behavior and layout of the tag.
|
|
84
|
+
*/
|
|
85
|
+
type?:
|
|
86
|
+
| 'informative'
|
|
87
|
+
| 'interactive'
|
|
88
|
+
| 'contextualised'
|
|
89
|
+
| 'removable'
|
|
90
|
+
| 'selectable';
|
|
91
|
+
/**
|
|
92
|
+
* Determines the size of the tag.
|
|
93
|
+
*/
|
|
94
|
+
size?: 's' | 'm' | 'l';
|
|
95
|
+
/**
|
|
96
|
+
* A unique identifier for the tag, used to associate the label with the form element. **Required** when type is 'selectable' or 'removable'.
|
|
97
|
+
*/
|
|
98
|
+
id?: string;
|
|
99
|
+
/**
|
|
100
|
+
* The name attribute for the tag element, typically used for form submission. (only relevant for type: 'selectable').
|
|
101
|
+
*/
|
|
102
|
+
name?: string;
|
|
103
|
+
/**
|
|
104
|
+
* The text label displayed next to the tag.
|
|
105
|
+
*/
|
|
106
|
+
label: string;
|
|
107
|
+
/**
|
|
108
|
+
* The tag's checked state, bound via v-model. Used only for type: 'selectable'.
|
|
109
|
+
*/
|
|
110
|
+
modelValue?: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* If `true`, disables the tag, making it non-interactive. Applicable to selectable, interactive, and contextualised types.
|
|
113
|
+
*/
|
|
114
|
+
disabled?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* A number displayed in the badge when the tag is contextualised.
|
|
117
|
+
*/
|
|
118
|
+
contextualisedNumber?: number;
|
|
119
|
+
/**
|
|
120
|
+
* Accessible label text for the remove button in removable tags.
|
|
121
|
+
*/
|
|
122
|
+
removableLabel?: string;
|
|
123
|
+
}>(),
|
|
124
|
+
{
|
|
125
|
+
type: 'informative',
|
|
126
|
+
contextualisedNumber: 99,
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const classObject = computed(() => {
|
|
131
|
+
return {
|
|
132
|
+
[`mc-tag-${props.type}`]: props.type && props.type != 'informative',
|
|
133
|
+
[`mc-tag--${props.size}`]: props.size && props.size != 'm',
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const emit = defineEmits<{
|
|
138
|
+
/**
|
|
139
|
+
* Emits when the tag value changes, updating the modelValue prop.
|
|
140
|
+
*/
|
|
141
|
+
(on: 'update:modelValue', value: boolean): void;
|
|
142
|
+
/**
|
|
143
|
+
* Emits when the remove button of a tag is clicked, passing the tag's ID.
|
|
144
|
+
*/
|
|
145
|
+
(on: 'remove-tag', id: string): void;
|
|
146
|
+
}>();
|
|
147
|
+
</script>
|
|
148
|
+
|
|
149
|
+
<style lang="scss" scoped>
|
|
150
|
+
@use '@mozaic-ds/styles/components/tag';
|
|
151
|
+
</style>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import MTextArea from './MTextArea.vue';
|
|
4
|
+
|
|
5
|
+
describe('MTextArea component', () => {
|
|
6
|
+
it('should render correctly with the given props', () => {
|
|
7
|
+
const wrapper = mount(MTextArea, {
|
|
8
|
+
props: {
|
|
9
|
+
id: 'textarea-id',
|
|
10
|
+
modelValue: 'Test value',
|
|
11
|
+
placeholder: 'Enter text here...',
|
|
12
|
+
rows: 5,
|
|
13
|
+
maxLength: 200,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const textarea = wrapper.find('textarea');
|
|
18
|
+
|
|
19
|
+
expect(textarea.attributes('id')).toBe('textarea-id');
|
|
20
|
+
expect(textarea.attributes('placeholder')).toBe('Enter text here...');
|
|
21
|
+
expect(textarea.attributes('rows')).toBe('5');
|
|
22
|
+
expect(textarea.attributes('maxlength')).toBe('200');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should apply is-invalid class when isInvalid prop is true', () => {
|
|
26
|
+
const wrapper = mount(MTextArea, {
|
|
27
|
+
props: {
|
|
28
|
+
id: 'textarea-id',
|
|
29
|
+
modelValue: 'Test value',
|
|
30
|
+
isInvalid: true,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const textarea = wrapper.find('textarea');
|
|
35
|
+
expect(textarea.classes()).toContain('is-invalid');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should update modelValue when the textarea value changes', async () => {
|
|
39
|
+
const wrapper = mount(MTextArea, {
|
|
40
|
+
props: {
|
|
41
|
+
id: 'textarea-id',
|
|
42
|
+
modelValue: 'Initial value',
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const textarea = wrapper.find('textarea');
|
|
47
|
+
await textarea.setValue('Updated value');
|
|
48
|
+
|
|
49
|
+
expect(wrapper.emitted()['update:modelValue']).toBeTruthy();
|
|
50
|
+
expect(wrapper.emitted()['update:modelValue'][0]).toEqual([
|
|
51
|
+
'Updated value',
|
|
52
|
+
]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should not allow changes if the textarea is disabled', async () => {
|
|
56
|
+
const wrapper = mount(MTextArea, {
|
|
57
|
+
props: {
|
|
58
|
+
id: 'textarea-id',
|
|
59
|
+
modelValue: 'Initial value',
|
|
60
|
+
disabled: true,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const textarea = wrapper.find('textarea');
|
|
65
|
+
expect(textarea.attributes('disabled')).toBeDefined();
|
|
66
|
+
|
|
67
|
+
await textarea.setValue('Updated value');
|
|
68
|
+
|
|
69
|
+
expect(wrapper.emitted()['update:modelValue']).toBeFalsy();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should be read-only if the readonly prop is true', async () => {
|
|
73
|
+
const wrapper = mount(MTextArea, {
|
|
74
|
+
props: {
|
|
75
|
+
id: 'textarea-id',
|
|
76
|
+
modelValue: 'Initial value',
|
|
77
|
+
readonly: true,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const textarea = wrapper.find('textarea');
|
|
82
|
+
expect(textarea.attributes('readonly')).toBeDefined();
|
|
83
|
+
|
|
84
|
+
await textarea.setValue('Updated value');
|
|
85
|
+
|
|
86
|
+
expect(textarea.attributes('value')).toBe('Initial value');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should apply a default classObject computed class', () => {
|
|
90
|
+
const wrapper = mount(MTextArea, {
|
|
91
|
+
props: {
|
|
92
|
+
id: 'textarea-id',
|
|
93
|
+
modelValue: 'Test value',
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(wrapper.classes()).toContain('mc-textarea');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should correctly bind rows attribute', () => {
|
|
101
|
+
const wrapper = mount(MTextArea, {
|
|
102
|
+
props: {
|
|
103
|
+
id: 'textarea-id',
|
|
104
|
+
modelValue: 'Test value',
|
|
105
|
+
rows: 4,
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const textarea = wrapper.find('textarea');
|
|
110
|
+
expect(textarea.attributes('rows')).toBe('4');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
|
+
import { action } from 'storybook/actions';
|
|
3
|
+
|
|
4
|
+
import MTextArea from './MTextArea.vue';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof MTextArea> = {
|
|
7
|
+
title: 'Form Elements/Textarea',
|
|
8
|
+
component: MTextArea,
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component:
|
|
13
|
+
'A text area is an input designed for multi-line text entry, allowing users to input longer content compared to a standard text input. It is commonly used for comments, feedback, descriptions, and messaging. Text areas can be resizable or fixed in height, depending on the context, and often include placeholder text, character limits, and validation messages to guide users.<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#textarea).',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
args: {
|
|
18
|
+
id: 'textareaId',
|
|
19
|
+
placeholder: 'Placeholder',
|
|
20
|
+
},
|
|
21
|
+
render: (args) => ({
|
|
22
|
+
components: { MTextArea },
|
|
23
|
+
setup() {
|
|
24
|
+
const handleUpdate = action('update:modelValue');
|
|
25
|
+
|
|
26
|
+
return { args, handleUpdate };
|
|
27
|
+
},
|
|
28
|
+
template: `
|
|
29
|
+
<MTextArea
|
|
30
|
+
v-bind="args"
|
|
31
|
+
@update:modelValue="handleUpdate"
|
|
32
|
+
/>
|
|
33
|
+
`,
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
export default meta;
|
|
37
|
+
type Story = StoryObj<typeof MTextArea>;
|
|
38
|
+
|
|
39
|
+
export const WithValue: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
id: 'withValueId',
|
|
42
|
+
modelValue: 'Value of the textarea component',
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const Default: Story = {};
|
|
47
|
+
|
|
48
|
+
export const Disabled: Story = {
|
|
49
|
+
args: {
|
|
50
|
+
id: 'disabledId',
|
|
51
|
+
disabled: true,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const ReadOnly: Story = {
|
|
56
|
+
args: {
|
|
57
|
+
id: 'readonlyId',
|
|
58
|
+
readonly: true,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const Invalid: Story = {
|
|
63
|
+
args: {
|
|
64
|
+
id: 'invalidId',
|
|
65
|
+
isInvalid: true,
|
|
66
|
+
},
|
|
67
|
+
};
|