@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.
- 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 +111 -159
- 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 -31341
- package/dist/mozaic-vue.common.js +0 -31331
- package/dist/mozaic-vue.common.js.map +0 -1
- package/dist/mozaic-vue.umd.js +0 -31342
- 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 -380
- 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 -204
- package/src/components/tabs/index.js +0 -7
- package/src/components/tags/MTag.vue +0 -175
- 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 -452
- package/src/tokens/adeo/android/font_dimens.xml +0 -18
- package/src/tokens/adeo/css/_variables.scss +0 -446
- package/src/tokens/adeo/css/root.scss +0 -448
- package/src/tokens/adeo/ios/StyleDictionaryColor.h +0 -460
- package/src/tokens/adeo/ios/StyleDictionaryColor.m +0 -472
- package/src/tokens/adeo/ios/StyleDictionaryColor.swift +0 -455
- 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 -544
- package/src/tokens/adeo/js/tokensObject.js +0 -11733
- package/src/tokens/adeo/scss/_tokens.scss +0 -1522
- 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,103 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import MModal from './MModal.vue';
|
|
4
|
+
|
|
5
|
+
const stubs = {
|
|
6
|
+
Cross24: { template: '<svg />' },
|
|
7
|
+
MIconButton: {
|
|
8
|
+
template: `<button @click="$emit('click')"><slot name="icon"/></button>`,
|
|
9
|
+
},
|
|
10
|
+
MOverlay: {
|
|
11
|
+
template: `<div class="overlay"><slot/></div>`,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe('MModal component', () => {
|
|
16
|
+
it('renders title and description', () => {
|
|
17
|
+
const wrapper = mount(MModal, {
|
|
18
|
+
props: {
|
|
19
|
+
open: true,
|
|
20
|
+
title: 'Test Modal',
|
|
21
|
+
description: 'This is a description',
|
|
22
|
+
},
|
|
23
|
+
global: { stubs },
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
expect(wrapper.text()).toContain('Test Modal');
|
|
27
|
+
expect(wrapper.text()).toContain('This is a description');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('renders close button if closable is true', () => {
|
|
31
|
+
const wrapper = mount(MModal, {
|
|
32
|
+
props: { open: true, title: 'Title', closable: true },
|
|
33
|
+
global: { stubs },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect(wrapper.find('button.mc-modal__close').exists()).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('does not render close button if closable is false', () => {
|
|
40
|
+
const wrapper = mount(MModal, {
|
|
41
|
+
props: { open: true, title: 'Title', closable: false },
|
|
42
|
+
global: { stubs },
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(wrapper.find('button.mc-modal__close').exists()).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('emits update:open with false when close button clicked', async () => {
|
|
49
|
+
const wrapper = mount(MModal, {
|
|
50
|
+
props: { open: true, title: 'Title', closable: true },
|
|
51
|
+
global: { stubs },
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await wrapper.find('button.mc-modal__close').trigger('click');
|
|
55
|
+
|
|
56
|
+
expect(wrapper.emitted('update:open')).toBeTruthy();
|
|
57
|
+
expect(wrapper.emitted('update:open')![0]).toEqual([false]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('renders slots content', () => {
|
|
61
|
+
const wrapper = mount(MModal, {
|
|
62
|
+
props: { open: true, title: 'Title' },
|
|
63
|
+
slots: {
|
|
64
|
+
default: '<div class="default-slot">Default Content</div>',
|
|
65
|
+
icon: '<span class="icon-slot">ICON</span>',
|
|
66
|
+
footer: '<div class="footer-slot">Footer Content</div>',
|
|
67
|
+
link: '<a href="#" class="link-slot">Link Content</a>',
|
|
68
|
+
},
|
|
69
|
+
global: { stubs },
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(wrapper.find('.icon-slot').exists()).toBe(true);
|
|
73
|
+
expect(wrapper.find('.default-slot').exists()).toBe(true);
|
|
74
|
+
expect(wrapper.find('.footer-slot').exists()).toBe(true);
|
|
75
|
+
expect(wrapper.find('.link-slot').exists()).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('has aria-hidden set correctly based on open prop', async () => {
|
|
79
|
+
const wrapper = mount(MModal, {
|
|
80
|
+
props: { open: false, title: 'Title' },
|
|
81
|
+
global: { stubs },
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(wrapper.find('.mc-modal').attributes('aria-hidden')).toBe('true');
|
|
85
|
+
|
|
86
|
+
await wrapper.setProps({ open: true });
|
|
87
|
+
|
|
88
|
+
expect(wrapper.find('.mc-modal').attributes('aria-hidden')).toBe('false');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('adds "is-open" class when open is true', async () => {
|
|
92
|
+
const wrapper = mount(MModal, {
|
|
93
|
+
props: { open: false, title: 'Title' },
|
|
94
|
+
global: { stubs },
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(wrapper.find('.mc-modal').classes()).not.toContain('is-open');
|
|
98
|
+
|
|
99
|
+
await wrapper.setProps({ open: true });
|
|
100
|
+
|
|
101
|
+
expect(wrapper.find('.mc-modal').classes()).toContain('is-open');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
|
+
import { ref, watch } from 'vue';
|
|
3
|
+
import { action } from 'storybook/actions';
|
|
4
|
+
import MModal from './MModal.vue';
|
|
5
|
+
import MButton from '../button/MButton.vue';
|
|
6
|
+
import MLink from '../link/MLink.vue';
|
|
7
|
+
import InfoCircle32 from '@mozaic-ds/icons-vue/src/components/InfoCircle32/InfoCircle32.vue';
|
|
8
|
+
import ExternalLink24 from '@mozaic-ds/icons-vue/src/components/ExternalLink24/ExternalLink24.vue';
|
|
9
|
+
|
|
10
|
+
const meta: Meta<typeof MModal> = {
|
|
11
|
+
title: 'Overlay/Modal',
|
|
12
|
+
component: MModal,
|
|
13
|
+
parameters: {
|
|
14
|
+
layout: 'fullscreen',
|
|
15
|
+
docs: {
|
|
16
|
+
story: { height: '400px' },
|
|
17
|
+
description: {
|
|
18
|
+
component:
|
|
19
|
+
'A modal is a dialog window that appears on top of the main content, requiring user interaction before returning to the main interface. It is used to focus attention on a specific task, provide important information, or request confirmation for an action. Modals typically include a title, description, and primary/secondary actions and should be used for single, focused tasks to avoid disrupting the user experience.',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
args: {
|
|
24
|
+
open: true,
|
|
25
|
+
title: 'Modal title',
|
|
26
|
+
description: `A modal is a dialog window allowing you to focus the user's attention on a specific task, a piece of information or a mandatory action. It must be used for single action only and must have a call to action button in the bottom.`,
|
|
27
|
+
footer: `
|
|
28
|
+
<MButton ghost>Cancel</MButton>
|
|
29
|
+
<MButton>Primary action</MButton>
|
|
30
|
+
`,
|
|
31
|
+
},
|
|
32
|
+
render: (args) => ({
|
|
33
|
+
components: { MModal, MButton, MLink, InfoCircle32, ExternalLink24 },
|
|
34
|
+
setup() {
|
|
35
|
+
const handleUpdate = action('update:open');
|
|
36
|
+
const openState = ref(args.open);
|
|
37
|
+
|
|
38
|
+
watch(
|
|
39
|
+
() => args.open,
|
|
40
|
+
(val) => {
|
|
41
|
+
openState.value = val;
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const onUpdateOpen = (val: boolean) => {
|
|
46
|
+
openState.value = val;
|
|
47
|
+
handleUpdate(val);
|
|
48
|
+
args.open = val;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return { args, openState, onUpdateOpen };
|
|
52
|
+
},
|
|
53
|
+
template: `
|
|
54
|
+
<MModal
|
|
55
|
+
v-bind="args"
|
|
56
|
+
@update:open="onUpdateOpen"
|
|
57
|
+
v-model:open="openState"
|
|
58
|
+
>
|
|
59
|
+
<template v-if="${'icon' in args}" v-slot:icon>${args.icon}</template>
|
|
60
|
+
<template v-if="${'default' in args}" v-slot>${args.default}</template>
|
|
61
|
+
<template v-if="${'link' in args}" v-slot:link>${args.link}</template>
|
|
62
|
+
<template v-if="${'footer' in args}" v-slot:footer>${args.footer}</template>
|
|
63
|
+
</MModal>
|
|
64
|
+
`,
|
|
65
|
+
}),
|
|
66
|
+
};
|
|
67
|
+
export default meta;
|
|
68
|
+
type Story = StoryObj<typeof MModal>;
|
|
69
|
+
|
|
70
|
+
export const Default: Story = {};
|
|
71
|
+
|
|
72
|
+
export const Icon = {
|
|
73
|
+
args: {
|
|
74
|
+
icon: '<InfoCircle32/>',
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const ValidationOnly = {
|
|
79
|
+
args: {
|
|
80
|
+
footer: `
|
|
81
|
+
<MButton>Primary action</MButton>
|
|
82
|
+
`,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const TwoOptions = {
|
|
87
|
+
args: {
|
|
88
|
+
footer: `
|
|
89
|
+
<MButton outlined>Secondary action</MButton>
|
|
90
|
+
<MButton>Primary action</MButton>
|
|
91
|
+
`,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const Cancel = {
|
|
96
|
+
args: {
|
|
97
|
+
footer: `
|
|
98
|
+
<MButton ghost>Cancel</MButton>
|
|
99
|
+
<MButton outlined>Secondary action</MButton>
|
|
100
|
+
<MButton>Primary action</MButton>
|
|
101
|
+
`,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const Link = {
|
|
106
|
+
args: {
|
|
107
|
+
link: `
|
|
108
|
+
<MLink class="mc-modal__link" href="#" size="m" icon-position="right">
|
|
109
|
+
Learn more
|
|
110
|
+
<template #icon><ExternalLink24 /></template>
|
|
111
|
+
</MLink>
|
|
112
|
+
`,
|
|
113
|
+
footer: `
|
|
114
|
+
<MButton ghost>Cancel</MButton>
|
|
115
|
+
<MButton>Primary action</MButton>
|
|
116
|
+
`,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export const Danger = {
|
|
121
|
+
args: {
|
|
122
|
+
footer: `
|
|
123
|
+
<MButton ghost>Cancel</MButton>
|
|
124
|
+
<MButton appearance="danger">Primary action</MButton>
|
|
125
|
+
`,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
@@ -1,179 +1,131 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
ref="modal"
|
|
2
|
+
<MOverlay :is-visible="open" dialogLabel="modalTitle">
|
|
3
|
+
<section
|
|
5
4
|
class="mc-modal"
|
|
6
|
-
|
|
5
|
+
:class="classObject"
|
|
7
6
|
role="dialog"
|
|
8
|
-
aria-labelledby="
|
|
7
|
+
aria-labelledby="modalTitle"
|
|
8
|
+
:aria-modal="open ? 'true' : 'false'"
|
|
9
|
+
tabindex="-1"
|
|
9
10
|
:aria-hidden="!open"
|
|
10
|
-
|
|
11
|
+
v-bind="$attrs"
|
|
12
|
+
@keydown.esc="onClose"
|
|
11
13
|
>
|
|
12
|
-
<div
|
|
13
|
-
class="mc-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
>
|
|
23
|
-
{{ modalTitle }}
|
|
24
|
-
</component>
|
|
25
|
-
<button
|
|
26
|
-
v-if="displayCloseButton"
|
|
27
|
-
ref="close"
|
|
14
|
+
<div class="mc-modal__dialog" role="document">
|
|
15
|
+
<header class="mc-modal__header">
|
|
16
|
+
<span v-if="$slots.icon" class="mc-modal__icon">
|
|
17
|
+
<slot name="icon" />
|
|
18
|
+
</span>
|
|
19
|
+
<h2 class="mc-modal__title" id="modalTitle">
|
|
20
|
+
{{ title }}
|
|
21
|
+
</h2>
|
|
22
|
+
<MIconButton
|
|
23
|
+
v-if="closable"
|
|
28
24
|
class="mc-modal__close"
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
aria-label="Close"
|
|
26
|
+
ghost
|
|
27
|
+
@click="onClose"
|
|
31
28
|
>
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
</
|
|
35
|
-
</
|
|
36
|
-
</
|
|
37
|
-
<
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
</article>
|
|
46
|
-
</div>
|
|
47
|
-
<div v-if="$slots.footer" class="mc-modal__footer">
|
|
29
|
+
<template #icon>
|
|
30
|
+
<Cross24 aria-hidden="true" />
|
|
31
|
+
</template>
|
|
32
|
+
</MIconButton>
|
|
33
|
+
</header>
|
|
34
|
+
<main class="mc-modal__body">
|
|
35
|
+
<p>{{ description }}</p>
|
|
36
|
+
<slot />
|
|
37
|
+
</main>
|
|
38
|
+
<footer v-if="$slots.footer" class="mc-modal__footer">
|
|
39
|
+
<span class="mc-modal__link">
|
|
40
|
+
<slot name="link" />
|
|
41
|
+
</span>
|
|
48
42
|
<slot name="footer" />
|
|
49
|
-
</
|
|
43
|
+
</footer>
|
|
50
44
|
</div>
|
|
51
|
-
</
|
|
52
|
-
|
|
53
|
-
<div
|
|
54
|
-
class="mc-modal-overlay"
|
|
55
|
-
:class="{ 'is-visible': open }"
|
|
56
|
-
tabindex="-1"
|
|
57
|
-
role="dialog"
|
|
58
|
-
/>
|
|
59
|
-
</keep-alive>
|
|
60
|
-
</div>
|
|
45
|
+
</section>
|
|
46
|
+
</MOverlay>
|
|
61
47
|
</template>
|
|
62
48
|
|
|
63
|
-
<script>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
title:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
},
|
|
93
|
-
displayCloseButton: {
|
|
94
|
-
type: Boolean,
|
|
95
|
-
default: true,
|
|
96
|
-
},
|
|
49
|
+
<script setup lang="ts">
|
|
50
|
+
import { computed, watch, type VNode } from 'vue';
|
|
51
|
+
import Cross24 from '@mozaic-ds/icons-vue/src/components/Cross24/Cross24.vue';
|
|
52
|
+
import MIconButton from '../iconbutton/MIconButton.vue';
|
|
53
|
+
import MOverlay from '../overlay/MOverlay.vue';
|
|
54
|
+
/**
|
|
55
|
+
* A modal is a dialog window that appears on top of the main content, requiring user interaction before returning to the main interface. It is used to focus attention on a specific task, provide important information, or request confirmation for an action. Modals typically include a title, description, and primary/secondary actions and should be used for single, focused tasks to avoid disrupting the user experience.
|
|
56
|
+
*/
|
|
57
|
+
const props = withDefaults(
|
|
58
|
+
defineProps<{
|
|
59
|
+
/**
|
|
60
|
+
* if `true`, display the modal.
|
|
61
|
+
*/
|
|
62
|
+
open?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Title of the modal
|
|
65
|
+
*/
|
|
66
|
+
title: string;
|
|
67
|
+
/**
|
|
68
|
+
* Description of the modal
|
|
69
|
+
*/
|
|
70
|
+
description?: string;
|
|
71
|
+
/**
|
|
72
|
+
* if `true`, display the close button.
|
|
73
|
+
*/
|
|
74
|
+
closable?: boolean;
|
|
75
|
+
}>(),
|
|
76
|
+
{
|
|
77
|
+
closable: true,
|
|
97
78
|
},
|
|
79
|
+
);
|
|
98
80
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
methods: {
|
|
118
|
-
trapFocus: function () {
|
|
119
|
-
const modal = document.querySelector('.mc-modal');
|
|
120
|
-
const focusableElements = modal.querySelectorAll(
|
|
121
|
-
'a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
const firstFocusableElement = focusableElements[0]; // get first element to be focused inside modal
|
|
125
|
-
const lastFocusableElement =
|
|
126
|
-
focusableElements[focusableElements.length - 1];
|
|
127
|
-
|
|
128
|
-
document.addEventListener('keydown', function (e) {
|
|
129
|
-
const isTabPressed = e.key === 'Tab' || e.keyCode === 9;
|
|
81
|
+
defineSlots<{
|
|
82
|
+
/**
|
|
83
|
+
* Use this slot to insert an icon next to the title of the modal
|
|
84
|
+
*/
|
|
85
|
+
icon?: VNode;
|
|
86
|
+
/**
|
|
87
|
+
* Use this slot to insert the content of the modal
|
|
88
|
+
*/
|
|
89
|
+
default?: VNode;
|
|
90
|
+
/**
|
|
91
|
+
* Use this slot to insert a link in the footer
|
|
92
|
+
*/
|
|
93
|
+
link?: VNode;
|
|
94
|
+
/**
|
|
95
|
+
* Use this slot to insert buttons in the footer
|
|
96
|
+
*/
|
|
97
|
+
footer?: VNode;
|
|
98
|
+
}>();
|
|
130
99
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
100
|
+
const classObject = computed(() => {
|
|
101
|
+
return {
|
|
102
|
+
'is-open': props.open,
|
|
103
|
+
};
|
|
104
|
+
});
|
|
134
105
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
e.preventDefault();
|
|
140
|
-
}
|
|
141
|
-
} else {
|
|
142
|
-
// if tab key is pressed
|
|
143
|
-
if (document.activeElement === lastFocusableElement) {
|
|
144
|
-
// if focused has reached to last focusable element then focus first focusable element after pressing tab
|
|
145
|
-
firstFocusableElement.focus(); // add focus for the first focusable element
|
|
146
|
-
e.preventDefault();
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
},
|
|
151
|
-
|
|
152
|
-
closeModal() {
|
|
153
|
-
this.$emit('update:open', false);
|
|
154
|
-
},
|
|
155
|
-
|
|
156
|
-
setOverflow: function () {
|
|
157
|
-
const modal = this.$refs.modal;
|
|
158
|
-
const bodyHeight = this.$refs.body.clientHeight;
|
|
159
|
-
const contentHeight = this.$refs.content.clientHeight;
|
|
160
|
-
|
|
161
|
-
if (contentHeight > bodyHeight) {
|
|
162
|
-
modal.classList.add('mc-modal--overflow');
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
|
|
166
|
-
setFocusOnModal() {
|
|
167
|
-
const modal = this.$refs.modal;
|
|
168
|
-
|
|
169
|
-
modal.focus();
|
|
170
|
-
},
|
|
106
|
+
watch(
|
|
107
|
+
() => props.open,
|
|
108
|
+
(newValue) => {
|
|
109
|
+
emit('update:open', newValue);
|
|
171
110
|
},
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const onClose = () => {
|
|
114
|
+
emit('update:open', false);
|
|
172
115
|
};
|
|
116
|
+
|
|
117
|
+
const emit = defineEmits<{
|
|
118
|
+
/**
|
|
119
|
+
* Emits when the checkbox value changes, updating the modelValue prop.
|
|
120
|
+
*/
|
|
121
|
+
(on: 'update:open', value: boolean): void;
|
|
122
|
+
}>();
|
|
173
123
|
</script>
|
|
174
124
|
|
|
175
|
-
<style lang="scss">
|
|
176
|
-
@
|
|
177
|
-
|
|
178
|
-
|
|
125
|
+
<style lang="scss" scoped>
|
|
126
|
+
@use '@mozaic-ds/styles/components/modal';
|
|
127
|
+
|
|
128
|
+
.mc-overlay {
|
|
129
|
+
filter: none;
|
|
130
|
+
}
|
|
179
131
|
</style>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import MNumberBadge from './MNumberBadge.vue';
|
|
4
|
+
|
|
5
|
+
describe('MNumberBadge component', () => {
|
|
6
|
+
it('renders the label correctly', () => {
|
|
7
|
+
const wrapper = mount(MNumberBadge, {
|
|
8
|
+
props: { label: 42 },
|
|
9
|
+
});
|
|
10
|
+
expect(wrapper.text()).toBe('42');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('applies no modifier class by default', () => {
|
|
14
|
+
const wrapper = mount(MNumberBadge, {
|
|
15
|
+
props: { label: 1 },
|
|
16
|
+
});
|
|
17
|
+
expect(wrapper.classes()).toContain('mc-number-badge');
|
|
18
|
+
expect(wrapper.classes()).not.toContain('mc-number-badge--standard');
|
|
19
|
+
expect(wrapper.classes()).not.toContain('mc-number-badge--s');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('applies appearance modifier class when set to danger', () => {
|
|
23
|
+
const wrapper = mount(MNumberBadge, {
|
|
24
|
+
props: {
|
|
25
|
+
label: 5,
|
|
26
|
+
appearance: 'danger',
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
expect(wrapper.classes()).toContain('mc-number-badge');
|
|
30
|
+
expect(wrapper.classes()).toContain('mc-number-badge--danger');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('applies size modifier class when size is m', () => {
|
|
34
|
+
const wrapper = mount(MNumberBadge, {
|
|
35
|
+
props: {
|
|
36
|
+
label: 10,
|
|
37
|
+
size: 'm',
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
expect(wrapper.classes()).toContain('mc-number-badge');
|
|
41
|
+
expect(wrapper.classes()).toContain('mc-number-badge--m');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('applies both appearance and size modifier classes when set', () => {
|
|
45
|
+
const wrapper = mount(MNumberBadge, {
|
|
46
|
+
props: {
|
|
47
|
+
label: 3,
|
|
48
|
+
appearance: 'accent',
|
|
49
|
+
size: 'm',
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
expect(wrapper.classes()).toContain('mc-number-badge');
|
|
53
|
+
expect(wrapper.classes()).toContain('mc-number-badge--accent');
|
|
54
|
+
expect(wrapper.classes()).toContain('mc-number-badge--m');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
|
+
import MNumberBadge from './MNumberBadge.vue';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof MNumberBadge> = {
|
|
5
|
+
title: 'Indicators/Number Badge',
|
|
6
|
+
component: MNumberBadge,
|
|
7
|
+
parameters: {
|
|
8
|
+
docs: {
|
|
9
|
+
description: {
|
|
10
|
+
component:
|
|
11
|
+
'A Number Badge represents a numeric count, often used to indicate notifications, updates, or items requiring attention. Its distinct appearance makes it easy to spot changes at a glance, ensuring users stay informed without breaking their workflow. Badges are commonly attached to icons, buttons, or tabs to provide contextual awareness.',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
args: { label: 99 },
|
|
16
|
+
render: (args) => ({
|
|
17
|
+
components: { MNumberBadge },
|
|
18
|
+
setup() {
|
|
19
|
+
return { args };
|
|
20
|
+
},
|
|
21
|
+
template: `
|
|
22
|
+
<MNumberBadge v-bind="args"></MNumberBadge>
|
|
23
|
+
`,
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
export default meta;
|
|
27
|
+
type Story = StoryObj<typeof MNumberBadge>;
|
|
28
|
+
|
|
29
|
+
export const Standard: Story = {};
|
|
30
|
+
|
|
31
|
+
export const Accent: Story = {
|
|
32
|
+
args: { appearance: 'accent' },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const Danger: Story = {
|
|
36
|
+
args: { appearance: 'danger' },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const Inverse: Story = {
|
|
40
|
+
globals: {
|
|
41
|
+
backgrounds: { value: 'inverse' },
|
|
42
|
+
},
|
|
43
|
+
args: { appearance: 'inverse' },
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const Size: Story = {
|
|
47
|
+
args: { size: 'm' },
|
|
48
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span class="mc-number-badge" :class="classObject">
|
|
3
|
+
{{ label }}
|
|
4
|
+
</span>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { computed } from 'vue';
|
|
9
|
+
/**
|
|
10
|
+
* A badge indicates the status of an entity and can evolve at any time.
|
|
11
|
+
*/
|
|
12
|
+
const props = withDefaults(
|
|
13
|
+
defineProps<{
|
|
14
|
+
/**
|
|
15
|
+
* Content of the badge
|
|
16
|
+
*/
|
|
17
|
+
label: number;
|
|
18
|
+
/**
|
|
19
|
+
* Allows to define the Badge style
|
|
20
|
+
*/
|
|
21
|
+
appearance?: 'danger' | 'accent' | 'inverse' | 'standard';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Allows to define the Badge size
|
|
25
|
+
*/
|
|
26
|
+
size?: 's' | 'm';
|
|
27
|
+
}>(),
|
|
28
|
+
{
|
|
29
|
+
appearance: 'standard',
|
|
30
|
+
size: 's',
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const classObject = computed(() => {
|
|
35
|
+
return {
|
|
36
|
+
[`mc-number-badge--${props.appearance}`]:
|
|
37
|
+
props.appearance && props.appearance != 'standard',
|
|
38
|
+
[`mc-number-badge--${props.size}`]: props.size && props.size != 's',
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<style lang="scss" scoped>
|
|
44
|
+
@use '@mozaic-ds/styles/components/number-badge';
|
|
45
|
+
</style>
|