@mozaic-ds/vue 2.8.0 → 2.10.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/README.md +4 -6
- package/dist/mozaic-vue.css +1 -1
- package/dist/mozaic-vue.d.ts +334 -252
- package/dist/mozaic-vue.js +1367 -1182
- package/dist/mozaic-vue.js.map +1 -1
- package/dist/mozaic-vue.umd.cjs +5 -5
- package/dist/mozaic-vue.umd.cjs.map +1 -1
- package/package.json +14 -13
- package/src/components/carousel/MCarousel.spec.ts +138 -0
- package/src/components/carousel/MCarousel.stories.ts +94 -0
- package/src/components/carousel/MCarousel.vue +154 -0
- package/src/components/carousel/README.md +18 -0
- package/src/components/drawer/MDrawer.spec.ts +81 -9
- package/src/components/drawer/MDrawer.vue +76 -46
- package/src/components/drawer/README.md +1 -0
- package/src/components/field/MField.spec.ts +94 -85
- package/src/components/field/MField.stories.ts +16 -0
- package/src/components/field/MField.vue +8 -1
- package/src/components/field/README.md +1 -0
- package/src/components/flag/MFlag.stories.ts +1 -1
- package/src/components/loader/MLoader.spec.ts +41 -0
- package/src/components/loader/MLoader.vue +7 -1
- package/src/components/loader/README.md +1 -1
- package/src/components/modal/MModal.spec.ts +34 -9
- package/src/components/modal/MModal.vue +39 -7
- package/src/components/modal/README.md +1 -0
- package/src/components/phonenumber/MPhoneNumber.spec.ts +110 -1
- package/src/components/phonenumber/MPhoneNumber.stories.ts +14 -0
- package/src/components/phonenumber/MPhoneNumber.vue +16 -6
- package/src/components/textinput/MTextInput.stories.ts +1 -1
- package/src/components/usingPresets.mdx +1 -1
- package/src/main.ts +1 -0
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
:aria-hidden="!open"
|
|
15
15
|
v-bind="$attrs"
|
|
16
16
|
@keydown.esc="onClose"
|
|
17
|
+
@click.stop
|
|
17
18
|
>
|
|
18
19
|
<div class="mc-drawer__dialog" role="document">
|
|
19
20
|
<div class="mc-drawer__header">
|
|
@@ -64,7 +65,7 @@
|
|
|
64
65
|
</template>
|
|
65
66
|
|
|
66
67
|
<script setup lang="ts">
|
|
67
|
-
import { computed, watch, type VNode, ref } from 'vue';
|
|
68
|
+
import { computed, watch, type VNode, ref, onMounted, onUnmounted } from 'vue';
|
|
68
69
|
import ArrowBack24 from '@mozaic-ds/icons-vue/src/components/ArrowBack24/ArrowBack24.vue';
|
|
69
70
|
import Cross24 from '@mozaic-ds/icons-vue/src/components/Cross24/Cross24.vue';
|
|
70
71
|
import MIconButton from '../iconbutton/MIconButton.vue';
|
|
@@ -72,36 +73,45 @@ import MOverlay from '../overlay/MOverlay.vue';
|
|
|
72
73
|
/**
|
|
73
74
|
* 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.
|
|
74
75
|
*/
|
|
75
|
-
const props =
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
76
|
+
const props = withDefaults(
|
|
77
|
+
defineProps<{
|
|
78
|
+
/**
|
|
79
|
+
* If `true`, display the drawer.
|
|
80
|
+
*/
|
|
81
|
+
open?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Position of the drawer.
|
|
84
|
+
*/
|
|
85
|
+
position?: 'left' | 'right';
|
|
86
|
+
/**
|
|
87
|
+
* If `true`, the drawer have a bigger width.
|
|
88
|
+
*/
|
|
89
|
+
extended?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* If `true`, display the back button.
|
|
92
|
+
*/
|
|
93
|
+
back?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Title of the drawer.
|
|
96
|
+
*/
|
|
97
|
+
title: string;
|
|
98
|
+
/**
|
|
99
|
+
* Title of the content of the drawer.
|
|
100
|
+
*/
|
|
101
|
+
contentTitle?: string;
|
|
102
|
+
/**
|
|
103
|
+
* if `false`, lock the scroll when open.
|
|
104
|
+
*/
|
|
105
|
+
scroll?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* if `true`, close the drawer when clicking the overlay.
|
|
108
|
+
*/
|
|
109
|
+
closeOnOverlay?: boolean;
|
|
110
|
+
}>(),
|
|
111
|
+
{
|
|
112
|
+
scroll: true,
|
|
113
|
+
},
|
|
114
|
+
);
|
|
105
115
|
|
|
106
116
|
defineSlots<{
|
|
107
117
|
/**
|
|
@@ -123,22 +133,42 @@ const classObject = computed(() => {
|
|
|
123
133
|
};
|
|
124
134
|
});
|
|
125
135
|
|
|
126
|
-
watch(
|
|
127
|
-
() => props.open,
|
|
128
|
-
(newValue) => {
|
|
129
|
-
emit('update:open', newValue);
|
|
130
|
-
},
|
|
131
|
-
);
|
|
132
|
-
|
|
133
136
|
const titleRef = ref<HTMLElement | null>(null);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
137
|
+
const isClient =
|
|
138
|
+
typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
139
|
+
|
|
140
|
+
const lockScroll = () => {
|
|
141
|
+
if (!isClient) return;
|
|
142
|
+
document.body.style.overflow = 'hidden';
|
|
143
|
+
document.documentElement.style.overflow = 'hidden';
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const unlockScroll = () => {
|
|
147
|
+
if (!isClient) return;
|
|
148
|
+
document.body.style.overflow = '';
|
|
149
|
+
document.documentElement.style.overflow = '';
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
onMounted(() => {
|
|
153
|
+
watch(
|
|
154
|
+
() => props.open,
|
|
155
|
+
(isOpen) => {
|
|
156
|
+
emit('update:open', isOpen);
|
|
157
|
+
if (isOpen && titleRef.value) {
|
|
158
|
+
titleRef.value.focus();
|
|
159
|
+
}
|
|
160
|
+
if (props.scroll === false) {
|
|
161
|
+
if (isOpen) lockScroll();
|
|
162
|
+
else unlockScroll();
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
{ immediate: true },
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
onUnmounted(() => {
|
|
170
|
+
unlockScroll();
|
|
171
|
+
});
|
|
142
172
|
|
|
143
173
|
const onClickOverlay = () => {
|
|
144
174
|
if (props.closeOnOverlay) {
|
|
@@ -13,6 +13,7 @@ A drawer is a sliding panel that appears from the side of the screen, providing
|
|
|
13
13
|
| `back` | If `true`, display the back button. | `boolean` | - |
|
|
14
14
|
| `title*` | Title of the drawer. | `string` | - |
|
|
15
15
|
| `contentTitle` | Title of the content of the drawer. | `string` | - |
|
|
16
|
+
| `scroll` | if `false`, lock the scroll when open. | `boolean` | `true` |
|
|
16
17
|
| `closeOnOverlay` | if `true`, close the drawer when clicking the overlay. | `boolean` | - |
|
|
17
18
|
|
|
18
19
|
## Slots
|
|
@@ -1,166 +1,175 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils';
|
|
2
2
|
import { describe, it, expect } from 'vitest';
|
|
3
3
|
import MField from './MField.vue';
|
|
4
|
+
import MLoader from '../loader/MLoader.vue';
|
|
4
5
|
|
|
5
6
|
describe('MField component', () => {
|
|
6
|
-
it('
|
|
7
|
+
it('renders the label and associates it with the correct id', () => {
|
|
7
8
|
const wrapper = mount(MField, {
|
|
8
9
|
props: {
|
|
9
|
-
id: '
|
|
10
|
-
label: '
|
|
10
|
+
id: 'username',
|
|
11
|
+
label: 'Username',
|
|
11
12
|
},
|
|
12
13
|
});
|
|
13
14
|
|
|
14
|
-
const label = wrapper.find('
|
|
15
|
+
const label = wrapper.find('.mc-field__label');
|
|
15
16
|
expect(label.exists()).toBe(true);
|
|
16
|
-
expect(label.text()).
|
|
17
|
+
expect(label.text()).toContain('Username');
|
|
18
|
+
expect(label.attributes('for')).toBe('username');
|
|
17
19
|
});
|
|
18
20
|
|
|
19
|
-
it('
|
|
21
|
+
it('renders the requirement text when provided', () => {
|
|
20
22
|
const wrapper = mount(MField, {
|
|
21
23
|
props: {
|
|
22
|
-
id: '
|
|
23
|
-
label: '
|
|
24
|
+
id: 'email',
|
|
25
|
+
label: 'Email',
|
|
24
26
|
requirementText: 'required',
|
|
25
27
|
},
|
|
26
28
|
});
|
|
27
29
|
|
|
28
|
-
const
|
|
29
|
-
expect(
|
|
30
|
-
expect(
|
|
30
|
+
const requirement = wrapper.find('.mc-field__requirement');
|
|
31
|
+
expect(requirement.exists()).toBe(true);
|
|
32
|
+
expect(requirement.text()).toContain('(required)');
|
|
31
33
|
});
|
|
32
34
|
|
|
33
|
-
it('
|
|
35
|
+
it('does not render requirement text when not provided', () => {
|
|
34
36
|
const wrapper = mount(MField, {
|
|
35
|
-
props: {
|
|
36
|
-
id: 'input-id',
|
|
37
|
-
label: 'Field Label',
|
|
38
|
-
},
|
|
37
|
+
props: { id: 'email', label: 'Email' },
|
|
39
38
|
});
|
|
40
39
|
|
|
41
|
-
const
|
|
42
|
-
expect(
|
|
40
|
+
const requirement = wrapper.find('.mc-field__requirement');
|
|
41
|
+
expect(requirement.exists()).toBe(false);
|
|
43
42
|
});
|
|
44
43
|
|
|
45
|
-
it('
|
|
44
|
+
it('renders help text when helpId and helpText are provided', () => {
|
|
46
45
|
const wrapper = mount(MField, {
|
|
47
46
|
props: {
|
|
48
|
-
id: '
|
|
49
|
-
label: '
|
|
50
|
-
helpText: '
|
|
51
|
-
helpId: 'help
|
|
47
|
+
id: 'password',
|
|
48
|
+
label: 'Password',
|
|
49
|
+
helpText: 'At least 8 characters.',
|
|
50
|
+
helpId: 'password-help',
|
|
52
51
|
},
|
|
53
52
|
});
|
|
54
53
|
|
|
55
|
-
const
|
|
56
|
-
expect(
|
|
57
|
-
expect(
|
|
54
|
+
const help = wrapper.find('.mc-field__help');
|
|
55
|
+
expect(help.exists()).toBe(true);
|
|
56
|
+
expect(help.attributes('id')).toBe('password-help');
|
|
57
|
+
expect(help.text()).toBe('At least 8 characters.');
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
it('
|
|
60
|
+
it('does not render help text if helpId or helpText is missing', () => {
|
|
61
61
|
const wrapper = mount(MField, {
|
|
62
62
|
props: {
|
|
63
|
-
id: '
|
|
64
|
-
label: '
|
|
63
|
+
id: 'password',
|
|
64
|
+
label: 'Password',
|
|
65
|
+
helpText: 'Help text',
|
|
65
66
|
},
|
|
66
67
|
});
|
|
67
68
|
|
|
68
|
-
const
|
|
69
|
-
expect(
|
|
69
|
+
const help = wrapper.find('.mc-field__help');
|
|
70
|
+
expect(help.exists()).toBe(false);
|
|
70
71
|
});
|
|
71
72
|
|
|
72
|
-
it('
|
|
73
|
+
it('renders slot content inside .mc-field__content', () => {
|
|
73
74
|
const wrapper = mount(MField, {
|
|
74
|
-
props: {
|
|
75
|
-
|
|
76
|
-
label: 'Field Label',
|
|
77
|
-
isValid: true,
|
|
78
|
-
message: 'Valid input',
|
|
79
|
-
},
|
|
75
|
+
props: { id: 'field1', label: 'Field Label' },
|
|
76
|
+
slots: { default: '<input id="field1" />' },
|
|
80
77
|
});
|
|
81
78
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
);
|
|
85
|
-
expect(wrapper.find('.mc-field__validation-message').text()).toBe(
|
|
86
|
-
'Valid input',
|
|
87
|
-
);
|
|
79
|
+
const content = wrapper.find('.mc-field__content');
|
|
80
|
+
expect(content.exists()).toBe(true);
|
|
81
|
+
expect(content.find('input').exists()).toBe(true);
|
|
88
82
|
});
|
|
89
83
|
|
|
90
|
-
it('
|
|
84
|
+
it('renders validation message when message and valid state are provided', () => {
|
|
91
85
|
const wrapper = mount(MField, {
|
|
92
86
|
props: {
|
|
93
|
-
id: '
|
|
94
|
-
label: '
|
|
95
|
-
|
|
96
|
-
|
|
87
|
+
id: 'email',
|
|
88
|
+
label: 'Email',
|
|
89
|
+
isValid: true,
|
|
90
|
+
messageId: 'msg1',
|
|
91
|
+
message: 'Looks good!',
|
|
97
92
|
},
|
|
98
93
|
});
|
|
99
94
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
);
|
|
103
|
-
expect(
|
|
104
|
-
|
|
105
|
-
);
|
|
95
|
+
const message = wrapper.find('.mc-field__validation-message');
|
|
96
|
+
expect(message.exists()).toBe(true);
|
|
97
|
+
expect(message.attributes('id')).toBe('msg1');
|
|
98
|
+
expect(message.text()).toContain('Looks good!');
|
|
99
|
+
expect(message.classes()).toContain('is-valid');
|
|
106
100
|
});
|
|
107
101
|
|
|
108
|
-
it('
|
|
102
|
+
it('renders validation message when invalid', () => {
|
|
109
103
|
const wrapper = mount(MField, {
|
|
110
104
|
props: {
|
|
111
|
-
id: '
|
|
112
|
-
label: '
|
|
113
|
-
|
|
114
|
-
|
|
105
|
+
id: 'email',
|
|
106
|
+
label: 'Email',
|
|
107
|
+
isInvalid: true,
|
|
108
|
+
messageId: 'msg2',
|
|
109
|
+
message: 'Invalid email address.',
|
|
115
110
|
},
|
|
116
111
|
});
|
|
117
112
|
|
|
118
|
-
const
|
|
119
|
-
expect(
|
|
120
|
-
expect(
|
|
113
|
+
const message = wrapper.find('.mc-field__validation-message');
|
|
114
|
+
expect(message.exists()).toBe(true);
|
|
115
|
+
expect(message.text()).toContain('Invalid email address.');
|
|
116
|
+
expect(message.classes()).toContain('is-invalid');
|
|
121
117
|
});
|
|
122
118
|
|
|
123
|
-
it('
|
|
119
|
+
it('renders loader and message when loading', () => {
|
|
124
120
|
const wrapper = mount(MField, {
|
|
125
121
|
props: {
|
|
126
|
-
id: '
|
|
127
|
-
label: '
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
122
|
+
id: 'loadingField',
|
|
123
|
+
label: 'Loading Label',
|
|
124
|
+
isLoading: true,
|
|
125
|
+
messageId: 'loading-msg',
|
|
126
|
+
message: 'Loading...',
|
|
131
127
|
},
|
|
132
128
|
});
|
|
133
129
|
|
|
134
|
-
const
|
|
135
|
-
expect(
|
|
136
|
-
expect(
|
|
130
|
+
const message = wrapper.find('.mc-field__validation-message');
|
|
131
|
+
expect(message.exists()).toBe(true);
|
|
132
|
+
expect(message.classes()).toContain('is-loading');
|
|
133
|
+
|
|
134
|
+
const loader = wrapper.findComponent(MLoader);
|
|
135
|
+
expect(loader.exists()).toBe(true);
|
|
136
|
+
expect(loader.props('size')).toBe('xs');
|
|
137
137
|
});
|
|
138
138
|
|
|
139
|
-
it('
|
|
139
|
+
it('does not render validation message when no state (valid/invalid/loading)', () => {
|
|
140
140
|
const wrapper = mount(MField, {
|
|
141
141
|
props: {
|
|
142
|
-
id: '
|
|
143
|
-
label: '
|
|
144
|
-
|
|
145
|
-
message: '
|
|
142
|
+
id: 'email',
|
|
143
|
+
label: 'Email',
|
|
144
|
+
messageId: 'msg3',
|
|
145
|
+
message: 'Message without state',
|
|
146
146
|
},
|
|
147
147
|
});
|
|
148
148
|
|
|
149
|
-
const
|
|
150
|
-
expect(
|
|
151
|
-
expect(validationMessage.text()).toBe('There is an error');
|
|
149
|
+
const message = wrapper.find('.mc-field__validation-message');
|
|
150
|
+
expect(message.exists()).toBe(false);
|
|
152
151
|
});
|
|
153
152
|
|
|
154
|
-
it('
|
|
153
|
+
it('renders multiple validation states correctly', async () => {
|
|
155
154
|
const wrapper = mount(MField, {
|
|
156
155
|
props: {
|
|
157
|
-
id: '
|
|
158
|
-
label: 'Field
|
|
156
|
+
id: 'stateField',
|
|
157
|
+
label: 'Field',
|
|
158
|
+
messageId: 'msg4',
|
|
159
|
+
message: 'State message',
|
|
159
160
|
isValid: true,
|
|
160
161
|
},
|
|
161
162
|
});
|
|
162
163
|
|
|
163
|
-
|
|
164
|
-
expect(
|
|
164
|
+
let message = wrapper.find('.mc-field__validation-message');
|
|
165
|
+
expect(message.classes()).toContain('is-valid');
|
|
166
|
+
|
|
167
|
+
await wrapper.setProps({ isValid: false, isInvalid: true });
|
|
168
|
+
message = wrapper.find('.mc-field__validation-message');
|
|
169
|
+
expect(message.classes()).toContain('is-invalid');
|
|
170
|
+
|
|
171
|
+
await wrapper.setProps({ isInvalid: false, isLoading: true });
|
|
172
|
+
message = wrapper.find('.mc-field__validation-message');
|
|
173
|
+
expect(message.classes()).toContain('is-loading');
|
|
165
174
|
});
|
|
166
175
|
});
|
|
@@ -125,6 +125,22 @@ export const InputInvalid: Story = {
|
|
|
125
125
|
},
|
|
126
126
|
};
|
|
127
127
|
|
|
128
|
+
export const InputLoading: Story = {
|
|
129
|
+
args: {
|
|
130
|
+
label: 'Label',
|
|
131
|
+
id: 'inputLoadingId',
|
|
132
|
+
isLoading: true,
|
|
133
|
+
message: 'Loading message (Be concise and use comprehensive words).',
|
|
134
|
+
default: `
|
|
135
|
+
<MTextInput
|
|
136
|
+
id="inputInvalidId"
|
|
137
|
+
placeholder="Placeholder"
|
|
138
|
+
@update:modelValue="handleUpdate"
|
|
139
|
+
/>
|
|
140
|
+
`,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
128
144
|
export const Textarea: Story = {
|
|
129
145
|
args: {
|
|
130
146
|
label: 'Label',
|
|
@@ -16,11 +16,12 @@
|
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
18
|
<span
|
|
19
|
-
v-if="(isValid || isInvalid) && message"
|
|
19
|
+
v-if="(isValid || isInvalid || isLoading) && message"
|
|
20
20
|
class="mc-field__validation-message"
|
|
21
21
|
:id="messageId"
|
|
22
22
|
:class="classObjectValidation"
|
|
23
23
|
>
|
|
24
|
+
<MLoader v-if="isLoading" size="xs"></MLoader>
|
|
24
25
|
{{ message }}
|
|
25
26
|
</span>
|
|
26
27
|
</div>
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
|
|
29
30
|
<script setup lang="ts">
|
|
30
31
|
import { computed, type VNode } from 'vue';
|
|
32
|
+
import MLoader from '../loader/MLoader.vue';
|
|
31
33
|
/**
|
|
32
34
|
* A field label is a text element that identifies the purpose of an input field, providing users with clear guidance on what information to enter. It is typically placed above the input field and may include indicators for required or optional fields. Field Labels improve form usability, accessibility, and data entry accuracy by ensuring users understand the expected input.
|
|
33
35
|
*/
|
|
@@ -60,6 +62,10 @@ const props = defineProps<{
|
|
|
60
62
|
* If `true`, applies an invalid state to the form field.
|
|
61
63
|
*/
|
|
62
64
|
isInvalid?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* If `true`, applies a loading state to the form field.
|
|
67
|
+
*/
|
|
68
|
+
isLoading?: boolean;
|
|
63
69
|
/**
|
|
64
70
|
* The value of the `id` attribute set on the **validationMessage** element. _This value is mandatory when using a validationMessage in order to guarantee the accessibility of the component._
|
|
65
71
|
*/
|
|
@@ -81,6 +87,7 @@ const classObjectValidation = computed(() => {
|
|
|
81
87
|
return {
|
|
82
88
|
'is-valid': props.isValid,
|
|
83
89
|
'is-invalid': props.isInvalid,
|
|
90
|
+
'is-loading': props.isLoading,
|
|
84
91
|
};
|
|
85
92
|
});
|
|
86
93
|
</script>
|
|
@@ -14,6 +14,7 @@ A field label is a text element that identifies the purpose of an input field, p
|
|
|
14
14
|
| `helpId` | The value of the `id` attribute set on the **helpText** element. _This value is mandatory when using a helpText in order to guarantee the accessibility of the component._ | `string` | - |
|
|
15
15
|
| `isValid` | If `true`, applies a valid state to the form field. | `boolean` | - |
|
|
16
16
|
| `isInvalid` | If `true`, applies an invalid state to the form field. | `boolean` | - |
|
|
17
|
+
| `isLoading` | If `true`, applies a loading state to the form field. | `boolean` | - |
|
|
17
18
|
| `messageId` | The value of the `id` attribute set on the **validationMessage** element. _This value is mandatory when using a validationMessage in order to guarantee the accessibility of the component._ | `string` | - |
|
|
18
19
|
| `message` | message displayed when the form field has a valid or invalid state, usually indicating validation or errors. | `string` | - |
|
|
19
20
|
|
|
@@ -101,4 +101,45 @@ describe('MLoader component', () => {
|
|
|
101
101
|
await wrapper.setProps({ size: 'l' });
|
|
102
102
|
expect(circle.attributes('r')).toBe('19');
|
|
103
103
|
});
|
|
104
|
+
|
|
105
|
+
it('applies mc-loader--text-visible class when text prop is provided', () => {
|
|
106
|
+
const wrapper = mount(MLoader, {
|
|
107
|
+
props: { text: 'Loading data...' },
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const loader = wrapper.find('.mc-loader');
|
|
111
|
+
expect(loader.classes()).toContain('mc-loader--text-visible');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('does not apply mc-loader--text-visible class when no text is provided', () => {
|
|
115
|
+
const wrapper = mount(MLoader);
|
|
116
|
+
const loader = wrapper.find('.mc-loader');
|
|
117
|
+
expect(loader.classes()).not.toContain('mc-loader--text-visible');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('uses default props correctly (appearance=standard, size=m)', () => {
|
|
121
|
+
const wrapper = mount(MLoader);
|
|
122
|
+
const loader = wrapper.find('.mc-loader');
|
|
123
|
+
|
|
124
|
+
expect(loader.classes()).not.toContain('mc-loader--standard');
|
|
125
|
+
expect(loader.classes()).not.toContain('mc-loader--m');
|
|
126
|
+
|
|
127
|
+
const svg = wrapper.find('svg.mc-loader__icon');
|
|
128
|
+
expect(svg.attributes('viewBox')).toBe('0 0 32 32');
|
|
129
|
+
|
|
130
|
+
const circle = wrapper.find('circle.mc-loader__path');
|
|
131
|
+
expect(circle.attributes('r')).toBe('9');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('sets correct viewBox for xs size', async () => {
|
|
135
|
+
const wrapper = mount(MLoader, { props: { size: 'xs' } });
|
|
136
|
+
const svg = wrapper.find('svg.mc-loader__icon');
|
|
137
|
+
expect(svg.attributes('viewBox')).toBe('0 0 20 20');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('sets correct circle radius for xs size', async () => {
|
|
141
|
+
const wrapper = mount(MLoader, { props: { size: 'xs' } });
|
|
142
|
+
const circle = wrapper.find('circle.mc-loader__path');
|
|
143
|
+
expect(circle.attributes('r')).toBe('6');
|
|
144
|
+
});
|
|
104
145
|
});
|
|
@@ -34,7 +34,7 @@ const props = withDefaults(
|
|
|
34
34
|
/**
|
|
35
35
|
* Defines the size of the loader.
|
|
36
36
|
*/
|
|
37
|
-
size?: 's' | 'm' | 'l';
|
|
37
|
+
size?: 'xs' | 's' | 'm' | 'l';
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* Text to display alongside the loader when using the loader inside an `Overlay`.
|
|
@@ -60,6 +60,9 @@ const setViewBox = computed(() => {
|
|
|
60
60
|
let viewBox: string;
|
|
61
61
|
|
|
62
62
|
switch (props.size) {
|
|
63
|
+
case 'xs':
|
|
64
|
+
viewBox = '0 0 20 20';
|
|
65
|
+
break;
|
|
63
66
|
case 's':
|
|
64
67
|
viewBox = '0 0 24 24';
|
|
65
68
|
break;
|
|
@@ -76,6 +79,9 @@ const setCircleRadius = computed(() => {
|
|
|
76
79
|
let circleRadius: number;
|
|
77
80
|
|
|
78
81
|
switch (props.size) {
|
|
82
|
+
case 'xs':
|
|
83
|
+
circleRadius = 6;
|
|
84
|
+
break;
|
|
79
85
|
case 's':
|
|
80
86
|
circleRadius = 6;
|
|
81
87
|
break;
|
|
@@ -8,5 +8,5 @@ A loader is a visual indicator used to inform users that a process is in progres
|
|
|
8
8
|
| Name | Description | Type | Default |
|
|
9
9
|
| --- | --- | --- | --- |
|
|
10
10
|
| `appearance` | Specifies the visual appearance of the loader. | `"standard"` `"inverse"` `"accent"` | `"standard"` |
|
|
11
|
-
| `size` | Defines the size of the loader. | `"s"` `"m"` `"l"` | `"m"` |
|
|
11
|
+
| `size` | Defines the size of the loader. | `"s"` `"m"` `"l"` `"xs"` | `"m"` |
|
|
12
12
|
| `text` | Text to display alongside the loader when using the loader inside an `Overlay`. | `string` | - |
|
|
@@ -54,8 +54,9 @@ describe('MModal component', () => {
|
|
|
54
54
|
|
|
55
55
|
await wrapper.find('button.mc-modal__close').trigger('click');
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
expect(
|
|
57
|
+
const emitted = wrapper.emitted('update:open');
|
|
58
|
+
expect(emitted).toBeTruthy();
|
|
59
|
+
expect(emitted![emitted!.length - 1]).toEqual([false]);
|
|
59
60
|
});
|
|
60
61
|
|
|
61
62
|
it('renders slots content', () => {
|
|
@@ -110,8 +111,10 @@ describe('MModal component', () => {
|
|
|
110
111
|
|
|
111
112
|
await wrapper.findComponent({ name: 'MOverlay' }).trigger('click');
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
expect(
|
|
114
|
+
const emitted = wrapper.emitted('update:open');
|
|
115
|
+
expect(emitted).toBeTruthy();
|
|
116
|
+
|
|
117
|
+
expect(emitted![emitted!.length - 1]).toEqual([false]);
|
|
115
118
|
});
|
|
116
119
|
|
|
117
120
|
it('does not emit update:open when overlay clicked and closeOnOverlay is false', async () => {
|
|
@@ -122,17 +125,39 @@ describe('MModal component', () => {
|
|
|
122
125
|
|
|
123
126
|
await wrapper.findComponent({ name: 'MOverlay' }).trigger('click');
|
|
124
127
|
|
|
125
|
-
|
|
128
|
+
const emitted = wrapper.emitted('update:open');
|
|
129
|
+
|
|
130
|
+
if (emitted) {
|
|
131
|
+
const lastValue = emitted[emitted.length - 1][0];
|
|
132
|
+
expect(lastValue).not.toBe(false);
|
|
133
|
+
} else {
|
|
134
|
+
expect(emitted).toBeFalsy();
|
|
135
|
+
}
|
|
126
136
|
});
|
|
127
137
|
|
|
128
|
-
it('
|
|
138
|
+
it('locks scroll when open and unlocks when closed', async () => {
|
|
129
139
|
const wrapper = mount(MModal, {
|
|
130
|
-
props: { open:
|
|
140
|
+
props: { open: false, title: 'Title', scroll: false },
|
|
131
141
|
global: { stubs },
|
|
132
142
|
});
|
|
133
143
|
|
|
134
|
-
await wrapper.
|
|
144
|
+
await wrapper.setProps({ open: true });
|
|
145
|
+
expect(document.body.style.overflow).toBe('hidden');
|
|
146
|
+
expect(document.documentElement.style.overflow).toBe('hidden');
|
|
147
|
+
|
|
148
|
+
await wrapper.setProps({ open: false });
|
|
149
|
+
expect(document.body.style.overflow).toBe('');
|
|
150
|
+
expect(document.documentElement.style.overflow).toBe('');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('does not lock scroll if scroll prop is true', async () => {
|
|
154
|
+
const wrapper = mount(MModal, {
|
|
155
|
+
props: { open: false, title: 'Title', scroll: true },
|
|
156
|
+
global: { stubs },
|
|
157
|
+
});
|
|
135
158
|
|
|
136
|
-
|
|
159
|
+
await wrapper.setProps({ open: true });
|
|
160
|
+
expect(document.body.style.overflow).toBe('');
|
|
161
|
+
expect(document.documentElement.style.overflow).toBe('');
|
|
137
162
|
});
|
|
138
163
|
});
|