@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
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
:aria-hidden="!open"
|
|
11
11
|
v-bind="$attrs"
|
|
12
12
|
@keydown.esc="onClose"
|
|
13
|
+
@click.stop
|
|
13
14
|
>
|
|
14
15
|
<div class="mc-modal__dialog" role="document">
|
|
15
16
|
<header class="mc-modal__header">
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
</template>
|
|
48
49
|
|
|
49
50
|
<script setup lang="ts">
|
|
50
|
-
import { computed, watch, type VNode } from 'vue';
|
|
51
|
+
import { computed, onMounted, onUnmounted, watch, type VNode } from 'vue';
|
|
51
52
|
import Cross24 from '@mozaic-ds/icons-vue/src/components/Cross24/Cross24.vue';
|
|
52
53
|
import MIconButton from '../iconbutton/MIconButton.vue';
|
|
53
54
|
import MOverlay from '../overlay/MOverlay.vue';
|
|
@@ -72,6 +73,10 @@ const props = withDefaults(
|
|
|
72
73
|
* if `true`, display the close button.
|
|
73
74
|
*/
|
|
74
75
|
closable?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* if `false`, lock the scroll when open.
|
|
78
|
+
*/
|
|
79
|
+
scroll?: boolean;
|
|
75
80
|
/**
|
|
76
81
|
* if `true`, close the modal when clicking the overlay.
|
|
77
82
|
*/
|
|
@@ -79,6 +84,7 @@ const props = withDefaults(
|
|
|
79
84
|
}>(),
|
|
80
85
|
{
|
|
81
86
|
closable: true,
|
|
87
|
+
scroll: true,
|
|
82
88
|
},
|
|
83
89
|
);
|
|
84
90
|
|
|
@@ -107,12 +113,38 @@ const classObject = computed(() => {
|
|
|
107
113
|
};
|
|
108
114
|
});
|
|
109
115
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
const isClient =
|
|
117
|
+
typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
118
|
+
|
|
119
|
+
const lockScroll = () => {
|
|
120
|
+
if (!isClient) return;
|
|
121
|
+
document.body.style.overflow = 'hidden';
|
|
122
|
+
document.documentElement.style.overflow = 'hidden';
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const unlockScroll = () => {
|
|
126
|
+
if (!isClient) return;
|
|
127
|
+
document.body.style.overflow = '';
|
|
128
|
+
document.documentElement.style.overflow = '';
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
onMounted(() => {
|
|
132
|
+
watch(
|
|
133
|
+
() => props.open,
|
|
134
|
+
(isOpen) => {
|
|
135
|
+
emit('update:open', isOpen);
|
|
136
|
+
if (props.scroll === false) {
|
|
137
|
+
if (isOpen) lockScroll();
|
|
138
|
+
else unlockScroll();
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
{ immediate: true },
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
onUnmounted(() => {
|
|
146
|
+
unlockScroll();
|
|
147
|
+
});
|
|
116
148
|
|
|
117
149
|
const onClickOverlay = () => {
|
|
118
150
|
if (props.closeOnOverlay) {
|
|
@@ -11,6 +11,7 @@ A modal is a dialog window that appears on top of the main content, requiring us
|
|
|
11
11
|
| `title*` | Title of the modal. | `string` | - |
|
|
12
12
|
| `description` | Description of the modal. | `string` | - |
|
|
13
13
|
| `closable` | if `true`, display the close button. | `boolean` | `true` |
|
|
14
|
+
| `scroll` | if `false`, lock the scroll when open. | `boolean` | `true` |
|
|
14
15
|
| `closeOnOverlay` | if `true`, close the modal when clicking the overlay. | `boolean` | - |
|
|
15
16
|
|
|
16
17
|
## Slots
|
|
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
|
2
2
|
import { mount, VueWrapper } from '@vue/test-utils';
|
|
3
3
|
import { nextTick } from 'vue';
|
|
4
4
|
import MPhoneNumber from './MPhoneNumber.vue';
|
|
5
|
-
import { isValidPhoneNumber } from 'libphonenumber-js';
|
|
5
|
+
import { isValidPhoneNumber, type CountryCode } from 'libphonenumber-js';
|
|
6
6
|
|
|
7
7
|
vi.mock('libphonenumber-js', () => ({
|
|
8
8
|
default: vi.fn(),
|
|
@@ -291,4 +291,113 @@ describe('MPhoneNumber', () => {
|
|
|
291
291
|
expect((input.element as HTMLInputElement).value).toBe('+33123456789');
|
|
292
292
|
});
|
|
293
293
|
});
|
|
294
|
+
|
|
295
|
+
describe('Locale', () => {
|
|
296
|
+
it('should use French locale by default', () => {
|
|
297
|
+
const options = wrapper.findAll('option');
|
|
298
|
+
const franceOption = options.find(
|
|
299
|
+
(opt) => opt.attributes('value') === 'FR',
|
|
300
|
+
);
|
|
301
|
+
expect(franceOption?.text()).toContain('France');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('should display country names in specified locale', () => {
|
|
305
|
+
wrapper = mount(MPhoneNumber, {
|
|
306
|
+
props: { ...defaultProps, locale: 'en' },
|
|
307
|
+
});
|
|
308
|
+
const options = wrapper.findAll('option');
|
|
309
|
+
const franceOption = options.find(
|
|
310
|
+
(opt) => opt.attributes('value') === 'FR',
|
|
311
|
+
);
|
|
312
|
+
// Country names should be translated according to the locale
|
|
313
|
+
expect(franceOption?.text()).toBeTruthy();
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('should update country names when locale prop changes', async () => {
|
|
317
|
+
await wrapper.setProps({ locale: 'es' });
|
|
318
|
+
await nextTick();
|
|
319
|
+
const options = wrapper.findAll('option');
|
|
320
|
+
expect(options.length).toBeGreaterThan(1);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
describe('Country Codes', () => {
|
|
325
|
+
it('should display all countries by default', () => {
|
|
326
|
+
const options = wrapper.findAll('option');
|
|
327
|
+
// Minus 1 for the empty hidden option
|
|
328
|
+
expect(options.length - 1).toBe(5); // Mocked to return 5 countries
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('should display only specified countries when countryCodes prop is provided', () => {
|
|
332
|
+
wrapper = mount(MPhoneNumber, {
|
|
333
|
+
props: {
|
|
334
|
+
...defaultProps,
|
|
335
|
+
countryCodes: ['FR', 'US', 'GB'] as CountryCode[],
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
const options = wrapper.findAll('option');
|
|
339
|
+
// Plus 1 for the empty hidden option
|
|
340
|
+
expect(options.length).toBe(4);
|
|
341
|
+
expect(options.some((opt) => opt.attributes('value') === 'FR')).toBe(
|
|
342
|
+
true,
|
|
343
|
+
);
|
|
344
|
+
expect(options.some((opt) => opt.attributes('value') === 'US')).toBe(
|
|
345
|
+
true,
|
|
346
|
+
);
|
|
347
|
+
expect(options.some((opt) => opt.attributes('value') === 'GB')).toBe(
|
|
348
|
+
true,
|
|
349
|
+
);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('should update available countries when countryCodes prop changes', async () => {
|
|
353
|
+
await wrapper.setProps({ countryCodes: ['FR', 'DE'] as CountryCode[] });
|
|
354
|
+
await nextTick();
|
|
355
|
+
const options = wrapper.findAll('option');
|
|
356
|
+
// Plus 1 for the empty hidden option
|
|
357
|
+
expect(options.length).toBe(3);
|
|
358
|
+
expect(options.some((opt) => opt.attributes('value') === 'FR')).toBe(
|
|
359
|
+
true,
|
|
360
|
+
);
|
|
361
|
+
expect(options.some((opt) => opt.attributes('value') === 'DE')).toBe(
|
|
362
|
+
true,
|
|
363
|
+
);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('should work with single country in countryCodes', () => {
|
|
367
|
+
wrapper = mount(MPhoneNumber, {
|
|
368
|
+
props: {
|
|
369
|
+
...defaultProps,
|
|
370
|
+
countryCodes: ['FR'] as CountryCode[],
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
const options = wrapper.findAll('option');
|
|
374
|
+
// Plus 1 for the empty hidden option
|
|
375
|
+
expect(options.length).toBe(2);
|
|
376
|
+
expect(options.some((opt) => opt.attributes('value') === 'FR')).toBe(
|
|
377
|
+
true,
|
|
378
|
+
);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('should combine locale and countryCodes props', () => {
|
|
382
|
+
wrapper = mount(MPhoneNumber, {
|
|
383
|
+
props: {
|
|
384
|
+
...defaultProps,
|
|
385
|
+
locale: 'en',
|
|
386
|
+
countryCodes: ['US', 'GB', 'CA'] as CountryCode[],
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
const options = wrapper.findAll('option');
|
|
390
|
+
// Plus 1 for the empty hidden option
|
|
391
|
+
expect(options.length).toBe(4);
|
|
392
|
+
expect(options.some((opt) => opt.attributes('value') === 'US')).toBe(
|
|
393
|
+
true,
|
|
394
|
+
);
|
|
395
|
+
expect(options.some((opt) => opt.attributes('value') === 'GB')).toBe(
|
|
396
|
+
true,
|
|
397
|
+
);
|
|
398
|
+
expect(options.some((opt) => opt.attributes('value') === 'CA')).toBe(
|
|
399
|
+
true,
|
|
400
|
+
);
|
|
401
|
+
});
|
|
402
|
+
});
|
|
294
403
|
});
|
|
@@ -86,3 +86,17 @@ export const ReadOnly: Story = {
|
|
|
86
86
|
readonly: true,
|
|
87
87
|
},
|
|
88
88
|
};
|
|
89
|
+
|
|
90
|
+
export const LimitedCountries: Story = {
|
|
91
|
+
args: {
|
|
92
|
+
countryCodes: ['FR', 'US', 'GB', 'DE', 'ES', 'IT'],
|
|
93
|
+
},
|
|
94
|
+
parameters: {
|
|
95
|
+
docs: {
|
|
96
|
+
description: {
|
|
97
|
+
story:
|
|
98
|
+
'Limit the country selector to only specific countries instead of showing all available countries.',
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
};
|
|
@@ -116,6 +116,14 @@ const props = withDefaults(
|
|
|
116
116
|
* If `true`, display the country flag selector
|
|
117
117
|
*/
|
|
118
118
|
flag?: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Locale for displaying country names (e.g., 'fr', 'en', 'es', 'pt').
|
|
121
|
+
*/
|
|
122
|
+
locale?: string;
|
|
123
|
+
/**
|
|
124
|
+
* List of country codes to display in the selector. If not provided, all countries will be shown.
|
|
125
|
+
*/
|
|
126
|
+
countryCodes?: CountryCode[];
|
|
119
127
|
}>(),
|
|
120
128
|
{
|
|
121
129
|
modelValue: '',
|
|
@@ -123,12 +131,15 @@ const props = withDefaults(
|
|
|
123
131
|
size: 'm',
|
|
124
132
|
prefix: true,
|
|
125
133
|
flag: true,
|
|
134
|
+
locale: 'en',
|
|
126
135
|
},
|
|
127
136
|
);
|
|
128
137
|
|
|
129
138
|
const phoneNumber = ref(props.modelValue);
|
|
130
139
|
const selectedCountry = ref<CountryCode>(props.defaultCountry);
|
|
131
|
-
const countries =
|
|
140
|
+
const countries = computed(() => {
|
|
141
|
+
return props.countryCodes || getCountries();
|
|
142
|
+
});
|
|
132
143
|
|
|
133
144
|
const dynamicPlaceholder = computed(() => {
|
|
134
145
|
if (props.placeholder && props.placeholder.length > 0) {
|
|
@@ -155,12 +166,11 @@ const parsedNumber = computed(() => {
|
|
|
155
166
|
}
|
|
156
167
|
});
|
|
157
168
|
|
|
158
|
-
const getCountryName = (
|
|
159
|
-
countryCode: CountryCode,
|
|
160
|
-
locale: string = 'fr',
|
|
161
|
-
): string => {
|
|
169
|
+
const getCountryName = (countryCode: CountryCode): string => {
|
|
162
170
|
try {
|
|
163
|
-
const regionNames = new Intl.DisplayNames([locale], {
|
|
171
|
+
const regionNames = new Intl.DisplayNames([props.locale], {
|
|
172
|
+
type: 'region',
|
|
173
|
+
});
|
|
164
174
|
return regionNames.of(countryCode) || countryCode;
|
|
165
175
|
} catch {
|
|
166
176
|
return countryCode;
|
|
@@ -39,7 +39,7 @@ All it has to do is insert the following code into its main Sass file (entrypoin
|
|
|
39
39
|
/>
|
|
40
40
|
|
|
41
41
|
> [!NOTE]
|
|
42
|
-
> The `<presetName>` string should be replaced by the name of the preset you want, one of the following values: `adeo |
|
|
42
|
+
> The `<presetName>` string should be replaced by the name of the preset you want, one of the following values: `adeo | mbrand`.
|
|
43
43
|
> As the `leroymerlin` preset is the default preset, you don't need to use this syntax to use it.
|
|
44
44
|
|
|
45
45
|
For example, for ADEO
|
package/src/main.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { default as MAvatar } from './components/avatar/MAvatar.vue';
|
|
|
2
2
|
export { default as MBreadcrumb } from './components/breadcrumb/MBreadcrumb.vue';
|
|
3
3
|
export { default as MButton } from './components/button/MButton.vue';
|
|
4
4
|
export { default as MCallout } from './components/callout/MCallout.vue';
|
|
5
|
+
export { default as MCarousel } from './components/carousel/MCarousel.vue';
|
|
5
6
|
export { default as MCheckbox } from './components/checkbox/MCheckbox.vue';
|
|
6
7
|
export { default as MCheckboxGroup } from './components/checkboxgroup/MCheckboxGroup.vue';
|
|
7
8
|
export { default as MCircularProgressbar } from './components/circularprogressbar/MCircularProgressbar.vue';
|