@mozaic-ds/vue 1.0.0-beta.7 → 1.0.0-beta.9
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/README.md +1 -1
- package/dist/mozaic-vue.css +1 -1
- package/dist/mozaic-vue.d.ts +605 -210
- package/dist/mozaic-vue.js +1281 -629
- package/dist/mozaic-vue.js.map +1 -1
- package/dist/mozaic-vue.umd.cjs +1 -1
- package/dist/mozaic-vue.umd.cjs.map +1 -1
- package/package.json +8 -11
- package/src/components/Contributing.mdx +1 -1
- package/src/components/GettingStarted.mdx +2 -7
- package/src/components/Introduction.mdx +41 -21
- package/src/components/Support.mdx +1 -1
- package/src/components/breadcrumb/MBreadcrumb.stories.ts +11 -13
- package/src/components/breadcrumb/MBreadcrumb.vue +1 -1
- package/src/components/button/MButton.stories.ts +1 -8
- package/src/components/checkbox/MCheckbox.stories.ts +2 -2
- package/src/components/checkboxgroup/MCheckboxGroup.stories.ts +2 -2
- 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.stories.ts +2 -9
- package/src/components/fieldgroup/MFieldGroup.stories.ts +2 -9
- package/src/components/iconbutton/MIconButton.stories.ts +12 -4
- package/src/components/link/MLink.stories.ts +3 -12
- package/src/components/loader/MLoader.stories.ts +3 -5
- package/src/components/loader/MLoader.vue +1 -0
- 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 +131 -0
- package/src/components/numberbadge/MNumberBadge.stories.ts +3 -5
- package/src/components/overlay/MOverlay.stories.ts +3 -8
- package/src/components/pagination/MPagination.spec.ts +123 -0
- package/src/components/pagination/MPagination.stories.ts +83 -0
- package/src/components/pagination/MPagination.vue +142 -0
- package/src/components/passwordinput/MPasswordInput.stories.ts +2 -2
- package/src/components/passwordinput/MPasswordInput.vue +2 -5
- package/src/components/pincode/MPincode.spec.ts +126 -0
- package/src/components/pincode/MPincode.stories.ts +68 -0
- package/src/components/pincode/MPincode.vue +139 -0
- package/src/components/quantityselector/MQuantitySelector.stories.ts +2 -2
- package/src/components/radio/MRadio.stories.ts +2 -2
- package/src/components/radiogroup/MRadioGroup.stories.ts +2 -2
- package/src/components/select/MSelect.stories.ts +2 -2
- package/src/components/statusbadge/MStatusBadge.stories.ts +1 -1
- package/src/components/statusdot/MStatusDot.stories.ts +1 -1
- package/src/components/statusnotification/MStatusNotification.spec.ts +12 -8
- package/src/components/statusnotification/MStatusNotification.stories.ts +2 -9
- package/src/components/statusnotification/MStatusNotification.vue +8 -8
- 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.stories.ts +2 -2
- package/src/components/textinput/MTextInput.stories.ts +2 -9
- package/src/components/toggle/MToggle.stories.ts +2 -2
- package/src/components/togglegroup/MToggleGroup.stories.ts +2 -2
- package/src/components/usingIcons.mdx +5 -13
- package/src/components/usingPresets.mdx +12 -9
- package/src/main.ts +8 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
|
+
import { action } from 'storybook/actions';
|
|
3
|
+
import MDrawer from './MDrawer.vue';
|
|
4
|
+
import MButton from '../button/MButton.vue';
|
|
5
|
+
import { ref, watch } from 'vue';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof MDrawer> = {
|
|
8
|
+
title: 'Overlay/Drawer',
|
|
9
|
+
component: MDrawer,
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: 'fullscreen',
|
|
12
|
+
docs: {
|
|
13
|
+
story: { height: '600px' },
|
|
14
|
+
description: {
|
|
15
|
+
component:
|
|
16
|
+
'A drawer is a sliding panel that appears from the side of the screen, providing additional content, settings, or actions without disrupting the main view. It is often used for filtering options, or contextual details. It enhances usability by keeping interfaces clean while offering expandable functionality.',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
args: {
|
|
21
|
+
open: true,
|
|
22
|
+
title: 'Drawer title (optionnal)',
|
|
23
|
+
contentTitle: 'Content title',
|
|
24
|
+
default: `
|
|
25
|
+
<p>
|
|
26
|
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quod itaque
|
|
27
|
+
odit, eligendi non, minus soluta dicta, excepturi harum tempora
|
|
28
|
+
possimus dignissimos dolor rerum natus voluptatum quia. Architecto
|
|
29
|
+
temporibus repellendus sed.
|
|
30
|
+
</p>
|
|
31
|
+
<p>
|
|
32
|
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quod itaque
|
|
33
|
+
odit, eligendi non, minus soluta dicta, excepturi harum tempora
|
|
34
|
+
possimus dignissimos dolor rerum natus voluptatum quia. Architecto
|
|
35
|
+
temporibus repellendus sed.
|
|
36
|
+
</p>
|
|
37
|
+
`,
|
|
38
|
+
footer: `
|
|
39
|
+
<MButton>Button label</MButton>
|
|
40
|
+
<MButton ghost>Button label</MButton>
|
|
41
|
+
`,
|
|
42
|
+
},
|
|
43
|
+
render: (args) => ({
|
|
44
|
+
components: { MDrawer, MButton },
|
|
45
|
+
setup() {
|
|
46
|
+
const handleUpdate = action('update:open');
|
|
47
|
+
const openState = ref(args.open);
|
|
48
|
+
|
|
49
|
+
watch(
|
|
50
|
+
() => args.open,
|
|
51
|
+
(val) => {
|
|
52
|
+
openState.value = val;
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const onUpdateOpen = (val: boolean) => {
|
|
57
|
+
openState.value = val;
|
|
58
|
+
handleUpdate(val);
|
|
59
|
+
args.open = val;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return { args, openState, onUpdateOpen };
|
|
63
|
+
},
|
|
64
|
+
template: `
|
|
65
|
+
<MDrawer
|
|
66
|
+
v-bind="args"
|
|
67
|
+
@update:open="onUpdateOpen"
|
|
68
|
+
v-model:open="openState"
|
|
69
|
+
>
|
|
70
|
+
<template v-if="${'default' in args}" v-slot>${args.default}</template>
|
|
71
|
+
<template v-if="${'footer' in args}" v-slot:footer>${args.footer}</template>
|
|
72
|
+
</MDrawer>
|
|
73
|
+
`,
|
|
74
|
+
}),
|
|
75
|
+
};
|
|
76
|
+
export default meta;
|
|
77
|
+
type Story = StoryObj<typeof MDrawer>;
|
|
78
|
+
|
|
79
|
+
export const Default: Story = {
|
|
80
|
+
args: {
|
|
81
|
+
default: `
|
|
82
|
+
<!-- All the code below must be replaced by a form element. -->
|
|
83
|
+
<div class="content-slot content-slot-full">
|
|
84
|
+
Insert a form element here to replace this slot.
|
|
85
|
+
</div>
|
|
86
|
+
`,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const Left = {
|
|
91
|
+
args: {
|
|
92
|
+
position: 'left',
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const Back = {
|
|
97
|
+
args: {
|
|
98
|
+
back: true,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const ValidationOnly = {
|
|
103
|
+
args: {
|
|
104
|
+
footer: `
|
|
105
|
+
<MButton>Button label</MButton>
|
|
106
|
+
`,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const TwoOptions = {
|
|
111
|
+
args: {
|
|
112
|
+
footer: `
|
|
113
|
+
<MButton>Button label</MButton>
|
|
114
|
+
<MButton outlined>Button label</MButton>
|
|
115
|
+
`,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export const Cancel = {
|
|
120
|
+
args: {
|
|
121
|
+
extended: true,
|
|
122
|
+
footer: `
|
|
123
|
+
<MButton>Button label</MButton>
|
|
124
|
+
<MButton outlined>Button label</MButton>
|
|
125
|
+
<MButton ghost>Button label</MButton>
|
|
126
|
+
`,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<MOverlay :is-visible="open" dialogLabel="drawerTitle">
|
|
3
|
+
<section
|
|
4
|
+
class="mc-drawer"
|
|
5
|
+
:class="classObject"
|
|
6
|
+
role="dialog"
|
|
7
|
+
aria-labelledby="drawerTitle"
|
|
8
|
+
:aria-modal="open ? 'true' : 'false'"
|
|
9
|
+
tabindex="-1"
|
|
10
|
+
:aria-hidden="!open"
|
|
11
|
+
v-bind="$attrs"
|
|
12
|
+
@keydown.esc="onClose"
|
|
13
|
+
>
|
|
14
|
+
<div class="mc-drawer__dialog" role="document">
|
|
15
|
+
<div class="mc-drawer__header">
|
|
16
|
+
<MIconButton
|
|
17
|
+
v-if="back"
|
|
18
|
+
class="mc-drawer__back"
|
|
19
|
+
aria-label="Back"
|
|
20
|
+
ghost
|
|
21
|
+
@click="emit('back')"
|
|
22
|
+
>
|
|
23
|
+
<template #icon>
|
|
24
|
+
<ArrowBack24 aria-hidden="true" />
|
|
25
|
+
</template>
|
|
26
|
+
</MIconButton>
|
|
27
|
+
<h2 class="mc-drawer__title" id="drawerTitle">{{ title }}</h2>
|
|
28
|
+
<MIconButton
|
|
29
|
+
class="mc-drawer__close"
|
|
30
|
+
aria-label="Close"
|
|
31
|
+
ghost
|
|
32
|
+
@click="onClose"
|
|
33
|
+
>
|
|
34
|
+
<template #icon>
|
|
35
|
+
<Cross24 aria-hidden="true" />
|
|
36
|
+
</template>
|
|
37
|
+
</MIconButton>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="mc-drawer__body">
|
|
40
|
+
<div class="mc-drawer__content" tabindex="0">
|
|
41
|
+
<h2 v-if="contentTitle" class="mc-drawer__content__title">
|
|
42
|
+
{{ contentTitle }}
|
|
43
|
+
</h2>
|
|
44
|
+
<slot />
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
<div v-if="$slots.footer" class="mc-drawer__footer">
|
|
48
|
+
<slot name="footer" />
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</section>
|
|
52
|
+
</MOverlay>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<script setup lang="ts">
|
|
56
|
+
import { computed, watch, type VNode } from 'vue';
|
|
57
|
+
import ArrowBack24 from '@mozaic-ds/icons-vue/src/components/ArrowBack24/ArrowBack24.vue';
|
|
58
|
+
import Cross24 from '@mozaic-ds/icons-vue/src/components/Cross24/Cross24.vue';
|
|
59
|
+
import MIconButton from '../iconbutton/MIconButton.vue';
|
|
60
|
+
import MOverlay from '../overlay/MOverlay.vue';
|
|
61
|
+
/**
|
|
62
|
+
* A drawer is a sliding panel that appears from the side of the screen, providing additional content, settings, or actions without disrupting the main view. It is often used for filtering options, or contextual details. It enhances usability by keeping interfaces clean while offering expandable functionality.
|
|
63
|
+
*/
|
|
64
|
+
const props = defineProps<{
|
|
65
|
+
/**
|
|
66
|
+
* If `true`, display the drawer.
|
|
67
|
+
*/
|
|
68
|
+
open?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Position of the drawer
|
|
71
|
+
*/
|
|
72
|
+
position?: 'left' | 'right';
|
|
73
|
+
/**
|
|
74
|
+
* If `true`, the drawer have a bigger width.
|
|
75
|
+
*/
|
|
76
|
+
extended?: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* If `true`, display the back button.
|
|
79
|
+
*/
|
|
80
|
+
back?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Title of the drawer
|
|
83
|
+
*/
|
|
84
|
+
title: string;
|
|
85
|
+
/**
|
|
86
|
+
* Title of the content of the drawer
|
|
87
|
+
*/
|
|
88
|
+
contentTitle?: string;
|
|
89
|
+
}>();
|
|
90
|
+
|
|
91
|
+
defineSlots<{
|
|
92
|
+
/**
|
|
93
|
+
* Use this slot to insert the content of the drawer
|
|
94
|
+
*/
|
|
95
|
+
default?: VNode;
|
|
96
|
+
/**
|
|
97
|
+
* Use this slot to insert buttons in the footer
|
|
98
|
+
*/
|
|
99
|
+
footer?: VNode;
|
|
100
|
+
}>();
|
|
101
|
+
|
|
102
|
+
const classObject = computed(() => {
|
|
103
|
+
return {
|
|
104
|
+
'is-open': props.open,
|
|
105
|
+
'mc-drawer--extend': props.extended,
|
|
106
|
+
[`mc-drawer--${props.position}`]:
|
|
107
|
+
props.position && props.position != 'right',
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
watch(
|
|
112
|
+
() => props.open,
|
|
113
|
+
(newValue) => {
|
|
114
|
+
emit('update:open', newValue);
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const onClose = () => {
|
|
119
|
+
emit('update:open', false);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const emit = defineEmits<{
|
|
123
|
+
/**
|
|
124
|
+
* Emits when the drawer open state changes, updating the modelValue prop.
|
|
125
|
+
*/
|
|
126
|
+
(on: 'update:open', value: boolean): void;
|
|
127
|
+
/**
|
|
128
|
+
* Emits when click back button of the drawer.
|
|
129
|
+
*/
|
|
130
|
+
(on: 'back'): void;
|
|
131
|
+
}>();
|
|
132
|
+
</script>
|
|
133
|
+
|
|
134
|
+
<style lang="scss" scoped>
|
|
135
|
+
@use '@mozaic-ds/styles/components/drawer';
|
|
136
|
+
|
|
137
|
+
.mc-overlay {
|
|
138
|
+
filter: none;
|
|
139
|
+
}
|
|
140
|
+
</style>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/vue3';
|
|
2
|
-
import { action } from '
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
|
+
import { action } from 'storybook/actions';
|
|
3
3
|
|
|
4
4
|
import MField from './MField.vue';
|
|
5
5
|
import MTextInput from '../textinput/MTextInput.vue';
|
|
@@ -27,13 +27,6 @@ const meta: Meta<typeof MField> = {
|
|
|
27
27
|
</div>
|
|
28
28
|
`,
|
|
29
29
|
},
|
|
30
|
-
argTypes: {
|
|
31
|
-
$slots: {
|
|
32
|
-
table: {
|
|
33
|
-
disable: true,
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
30
|
render: (args) => ({
|
|
38
31
|
components: { MField, MTextInput },
|
|
39
32
|
setup() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/vue3';
|
|
2
|
-
import { action } from '
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
|
+
import { action } from 'storybook/actions';
|
|
3
3
|
|
|
4
4
|
import MFieldGroup from './MFieldGroup.vue';
|
|
5
5
|
import MCheckboxGroup from '../checkboxgroup/MCheckboxGroup.vue';
|
|
@@ -53,13 +53,6 @@ const meta: Meta<typeof MFieldGroup> = {
|
|
|
53
53
|
/>
|
|
54
54
|
`,
|
|
55
55
|
},
|
|
56
|
-
argTypes: {
|
|
57
|
-
$slots: {
|
|
58
|
-
table: {
|
|
59
|
-
disable: true,
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
56
|
render: (args) => ({
|
|
64
57
|
components: { MFieldGroup, MCheckboxGroup, MRadioGroup, MToggleGroup },
|
|
65
58
|
setup() {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/vue3';
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
2
|
|
|
3
3
|
import MIconButton from './MIconButton.vue';
|
|
4
|
+
import ChevronRight20 from '@mozaic-ds/icons-vue/src/components/ChevronRight20/ChevronRight20.vue';
|
|
4
5
|
import ChevronRight24 from '@mozaic-ds/icons-vue/src/components/ChevronRight24/ChevronRight24.vue';
|
|
6
|
+
import ChevronRight32 from '@mozaic-ds/icons-vue/src/components/ChevronRight32/ChevronRight32.vue';
|
|
5
7
|
|
|
6
8
|
const meta: Meta<typeof MIconButton> = {
|
|
7
9
|
title: 'Action/Icon Button',
|
|
@@ -31,7 +33,7 @@ const meta: Meta<typeof MIconButton> = {
|
|
|
31
33
|
},
|
|
32
34
|
},
|
|
33
35
|
render: (args) => ({
|
|
34
|
-
components: { MIconButton, ChevronRight24 },
|
|
36
|
+
components: { MIconButton, ChevronRight20, ChevronRight24, ChevronRight32 },
|
|
35
37
|
setup() {
|
|
36
38
|
return { args };
|
|
37
39
|
},
|
|
@@ -58,9 +60,15 @@ export const Ghost: Story = {
|
|
|
58
60
|
};
|
|
59
61
|
|
|
60
62
|
export const SizeS: Story = {
|
|
61
|
-
args: {
|
|
63
|
+
args: {
|
|
64
|
+
size: 's',
|
|
65
|
+
icon: '<ChevronRight20/>',
|
|
66
|
+
},
|
|
62
67
|
};
|
|
63
68
|
|
|
64
69
|
export const SizeL: Story = {
|
|
65
|
-
args: {
|
|
70
|
+
args: {
|
|
71
|
+
size: 'l',
|
|
72
|
+
icon: '<ChevronRight32/>',
|
|
73
|
+
},
|
|
66
74
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/vue3';
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
2
|
import MLink from './MLink.vue';
|
|
3
3
|
import ChevronLeft24 from '@mozaic-ds/icons-vue/src/components/ChevronLeft24/ChevronLeft24.vue';
|
|
4
4
|
import ChevronRight24 from '@mozaic-ds/icons-vue/src/components/ChevronRight24/ChevronRight24.vue';
|
|
@@ -18,13 +18,6 @@ const meta: Meta<typeof MLink> = {
|
|
|
18
18
|
default: 'Stand-alone link',
|
|
19
19
|
href: '#',
|
|
20
20
|
},
|
|
21
|
-
argTypes: {
|
|
22
|
-
$slots: {
|
|
23
|
-
table: {
|
|
24
|
-
disable: true,
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
21
|
render: (args) => ({
|
|
29
22
|
components: { MLink, ChevronRight24, ChevronLeft24 },
|
|
30
23
|
setup() {
|
|
@@ -67,10 +60,8 @@ export const Accent: Story = {
|
|
|
67
60
|
};
|
|
68
61
|
|
|
69
62
|
export const Inverse: Story = {
|
|
70
|
-
|
|
71
|
-
backgrounds: {
|
|
72
|
-
default: 'Inverse',
|
|
73
|
-
},
|
|
63
|
+
globals: {
|
|
64
|
+
backgrounds: { value: 'inverse' },
|
|
74
65
|
},
|
|
75
66
|
args: { appearance: 'inverse' },
|
|
76
67
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/vue3';
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
2
|
import MLoader from './MLoader.vue';
|
|
3
3
|
|
|
4
4
|
const meta: Meta<typeof MLoader> = {
|
|
@@ -32,10 +32,8 @@ export const Accent: Story = {
|
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
export const Inverse: Story = {
|
|
35
|
-
|
|
36
|
-
backgrounds: {
|
|
37
|
-
default: 'Inverse',
|
|
38
|
-
},
|
|
35
|
+
globals: {
|
|
36
|
+
backgrounds: { value: 'inverse' },
|
|
39
37
|
},
|
|
40
38
|
args: { appearance: 'inverse' },
|
|
41
39
|
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import MLoadingOverlay from './MLoadingOverlay.vue';
|
|
4
|
+
|
|
5
|
+
describe('MLoadingOverlay component', () => {
|
|
6
|
+
it('renders and applies isVisible class based on prop', () => {
|
|
7
|
+
const wrapper = mount(MLoadingOverlay, {
|
|
8
|
+
props: { isVisible: true, text: 'Loading data...' },
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
expect(wrapper.classes()).toContain('is-visible');
|
|
12
|
+
|
|
13
|
+
const dialog = wrapper.find('[role="dialog"]');
|
|
14
|
+
expect(dialog.attributes('aria-label')).toBe('Loading data...');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('does not have is-visible class when isVisible is false or undefined', () => {
|
|
18
|
+
const wrapper = mount(MLoadingOverlay, {
|
|
19
|
+
props: { isVisible: false },
|
|
20
|
+
});
|
|
21
|
+
expect(wrapper.classes()).not.toContain('is-visible');
|
|
22
|
+
|
|
23
|
+
const wrapperNoProp = mount(MLoadingOverlay);
|
|
24
|
+
expect(wrapperNoProp.classes()).not.toContain('is-visible');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('passes down attributes via $attrs', () => {
|
|
28
|
+
const wrapper = mount(MLoadingOverlay, {
|
|
29
|
+
props: { isVisible: true, text: 'Loading...' },
|
|
30
|
+
attrs: { id: 'custom-id', 'data-test': 'loading-overlay' },
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const dialog = wrapper.find('[role="dialog"]');
|
|
34
|
+
expect(dialog.attributes('id')).toBe('custom-id');
|
|
35
|
+
expect(dialog.attributes('data-test')).toBe('loading-overlay');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
|
+
import MLoadingOverlay from './MLoadingOverlay.vue';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof MLoadingOverlay> = {
|
|
5
|
+
title: 'Overlay/Loading Overlay',
|
|
6
|
+
component: MLoadingOverlay,
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: 'fullscreen',
|
|
9
|
+
docs: {
|
|
10
|
+
story: { height: '400px' },
|
|
11
|
+
description: {
|
|
12
|
+
component:
|
|
13
|
+
'An overlay component is a UI element that appears above the main content to display additional information or interactions, often blocking or dimming the background.',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
args: {
|
|
18
|
+
isVisible: true,
|
|
19
|
+
ariaLabel: 'Page loading',
|
|
20
|
+
},
|
|
21
|
+
render: (args) => ({
|
|
22
|
+
components: { MLoadingOverlay },
|
|
23
|
+
setup() {
|
|
24
|
+
return { args };
|
|
25
|
+
},
|
|
26
|
+
template: `
|
|
27
|
+
<MLoadingOverlay v-bind="args"/>
|
|
28
|
+
`,
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
export default meta;
|
|
32
|
+
type Story = StoryObj<typeof MLoadingOverlay>;
|
|
33
|
+
|
|
34
|
+
export const Default: Story = {};
|
|
35
|
+
|
|
36
|
+
export const text: Story = {
|
|
37
|
+
args: {
|
|
38
|
+
text: 'Insert your text here',
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="mc-loading-loader" :class="{ 'is-visible': isVisible }">
|
|
3
|
+
<div role="dialog" tabindex="-1" :aria-label="text" v-bind="$attrs">
|
|
4
|
+
<MLoader size="l" appearance="inverse" :text="text" />
|
|
5
|
+
</div>
|
|
6
|
+
</div>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup lang="ts">
|
|
10
|
+
import MLoader from '../loader/MLoader.vue';
|
|
11
|
+
/**
|
|
12
|
+
* A loading overlay is a full-screen or container-level layer that indicates a process is in progress, preventing user interaction until the task is completed. It includes a progress indicator, and a message to inform users about the loading state. Loading Overlays are commonly used in data-heavy applications, form submissions, and page transitions to enhance user experience by managing wait times effectively.
|
|
13
|
+
*/
|
|
14
|
+
defineProps<{
|
|
15
|
+
/**
|
|
16
|
+
* Controls the visibility of the loading overlay.
|
|
17
|
+
*/
|
|
18
|
+
isVisible?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Text of the loading overlay.
|
|
21
|
+
*/
|
|
22
|
+
text?: string;
|
|
23
|
+
}>();
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<style lang="scss" scoped>
|
|
27
|
+
@use '@mozaic-ds/styles/components/loading-overlay';
|
|
28
|
+
</style>
|
|
@@ -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
|
+
});
|