@mozaic-ds/vue 2.7.0 → 2.9.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/dist/mozaic-vue.css +1 -1
- package/dist/mozaic-vue.d.ts +183 -100
- package/dist/mozaic-vue.js +2918 -978
- package/dist/mozaic-vue.js.map +1 -1
- package/dist/mozaic-vue.umd.cjs +5 -1
- package/dist/mozaic-vue.umd.cjs.map +1 -1
- package/package.json +3 -2
- package/src/components/drawer/MDrawer.spec.ts +144 -5
- package/src/components/drawer/MDrawer.vue +94 -40
- package/src/components/drawer/README.md +2 -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/iconbutton/MIconButton.vue +5 -0
- 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/loadingoverlay/MLoadingOverlay.stories.ts +1 -1
- package/src/components/modal/MModal.spec.ts +63 -3
- package/src/components/modal/MModal.vue +50 -8
- package/src/components/modal/README.md +2 -0
- package/src/components/phonenumber/MPhoneNumber.spec.ts +294 -0
- package/src/components/phonenumber/MPhoneNumber.stories.ts +88 -0
- package/src/components/phonenumber/MPhoneNumber.vue +271 -0
- package/src/components/phonenumber/README.md +26 -0
- package/src/components/pincode/README.md +1 -1
- package/src/components/quantityselector/MQuantitySelector.stories.ts +0 -7
- package/src/components/togglegroup/MToggleGroup.vue +0 -2
- package/src/components/togglegroup/README.md +1 -1
- package/src/main.ts +1 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
|
+
import { action } from 'storybook/actions';
|
|
3
|
+
import { ref } from 'vue';
|
|
4
|
+
|
|
5
|
+
import MPhoneNumber from './MPhoneNumber.vue';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof MPhoneNumber> = {
|
|
8
|
+
title: 'Form Elements/Phone Number',
|
|
9
|
+
component: MPhoneNumber,
|
|
10
|
+
tags: ['v2'],
|
|
11
|
+
parameters: {
|
|
12
|
+
docs: {
|
|
13
|
+
description: {
|
|
14
|
+
component:
|
|
15
|
+
'A phone number input is a specialized input field designed to capture and validate phone numbers, ensuring correct formatting based on country-specific dialing codes. It often includes a country selector that automatically adjusts the international dialing code. This component improves user experience by standardizing phone number entries, reducing errors, and facilitating global compatibility. It is commonly used in registration forms, authentication flows, and contact information fields.',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
render: (args) => ({
|
|
20
|
+
components: { MPhoneNumber },
|
|
21
|
+
setup() {
|
|
22
|
+
const modelValue = ref(args.modelValue);
|
|
23
|
+
const isValid = ref(false);
|
|
24
|
+
|
|
25
|
+
const handleUpdate = (val: string) => {
|
|
26
|
+
modelValue.value = val;
|
|
27
|
+
action('update:modelValue')(val);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const handleValid = (valid: boolean) => {
|
|
31
|
+
isValid.value = valid;
|
|
32
|
+
action('valid')(valid);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return { args, modelValue, isValid, handleUpdate, handleValid };
|
|
36
|
+
},
|
|
37
|
+
template: `
|
|
38
|
+
<MPhoneNumber
|
|
39
|
+
v-bind="{ ...args, modelValue }"
|
|
40
|
+
@update:modelValue="handleUpdate"
|
|
41
|
+
@valid="handleValid"
|
|
42
|
+
/>
|
|
43
|
+
`,
|
|
44
|
+
}),
|
|
45
|
+
};
|
|
46
|
+
export default meta;
|
|
47
|
+
type Story = StoryObj<typeof MPhoneNumber>;
|
|
48
|
+
|
|
49
|
+
export const Default: Story = {};
|
|
50
|
+
|
|
51
|
+
export const Size: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
size: 's',
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const IsInvalid: Story = {
|
|
58
|
+
args: {
|
|
59
|
+
isInvalid: true,
|
|
60
|
+
modelValue: '1912',
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const NoPrefix: Story = {
|
|
65
|
+
args: {
|
|
66
|
+
prefix: false,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const NoFlag: Story = {
|
|
71
|
+
args: {
|
|
72
|
+
flag: false,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const Disabled: Story = {
|
|
77
|
+
args: {
|
|
78
|
+
modelValue: '0103948374',
|
|
79
|
+
disabled: true,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const ReadOnly: Story = {
|
|
84
|
+
args: {
|
|
85
|
+
modelValue: '0103948374',
|
|
86
|
+
readonly: true,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div id="mc-phone-number-input" class="mc-phone-number-input">
|
|
3
|
+
<div
|
|
4
|
+
class="mc-phone-number-input__select-wrapper"
|
|
5
|
+
:class="selectWrapperClass"
|
|
6
|
+
>
|
|
7
|
+
<select
|
|
8
|
+
id="selectComponentId"
|
|
9
|
+
v-model="selectedCountry"
|
|
10
|
+
name="selectComponentName"
|
|
11
|
+
class="mc-select mc-phone-number-input__select"
|
|
12
|
+
:class="sizeSelectClass"
|
|
13
|
+
:disabled="isDisabled"
|
|
14
|
+
:readonly="isReadOnly"
|
|
15
|
+
>
|
|
16
|
+
<option value="" selected hidden></option>
|
|
17
|
+
<option
|
|
18
|
+
v-for="country in countries"
|
|
19
|
+
:key="country"
|
|
20
|
+
:value="country"
|
|
21
|
+
:data-flag="country.toLowerCase()"
|
|
22
|
+
>
|
|
23
|
+
{{ getCountryName(country) }} (+{{ getCountryCallingCode(country) }})
|
|
24
|
+
</option>
|
|
25
|
+
</select>
|
|
26
|
+
|
|
27
|
+
<div class="mc-phone-number-input__select-display">
|
|
28
|
+
<div class="mc-phone-number-input__flag">
|
|
29
|
+
<img
|
|
30
|
+
class="mc-phone-number-input__flag-image"
|
|
31
|
+
:src="getFlagUrl(selectedCountry)"
|
|
32
|
+
:alt="getCountryName(selectedCountry)"
|
|
33
|
+
width="20"
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
<ChevronDown20
|
|
37
|
+
class="mc-phone-number-input__chevron"
|
|
38
|
+
aria-hidden="true"
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="mc-text-input mc-phone-number-input__input" :class="inputClass">
|
|
44
|
+
<span v-if="prefix" class="mc-phone-number-input__country-code">
|
|
45
|
+
+{{ getCountryCallingCode(selectedCountry) }}
|
|
46
|
+
</span>
|
|
47
|
+
<input
|
|
48
|
+
v-model="phoneNumber"
|
|
49
|
+
type="tel"
|
|
50
|
+
:id="id"
|
|
51
|
+
class="mc-phone-number-input__control mc-text-input__control"
|
|
52
|
+
:placeholder="dynamicPlaceholder"
|
|
53
|
+
name="phone-number"
|
|
54
|
+
:aria-invalid="isInvalid"
|
|
55
|
+
:disabled="isDisabled"
|
|
56
|
+
:readonly="isReadOnly"
|
|
57
|
+
@input="handleInput"
|
|
58
|
+
/>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</template>
|
|
62
|
+
|
|
63
|
+
<script setup lang="ts">
|
|
64
|
+
import { ref, computed, watch } from 'vue';
|
|
65
|
+
import parsePhoneNumberFromString, {
|
|
66
|
+
isValidPhoneNumber,
|
|
67
|
+
getCountries,
|
|
68
|
+
getCountryCallingCode,
|
|
69
|
+
getExampleNumber,
|
|
70
|
+
type CountryCode,
|
|
71
|
+
} from 'libphonenumber-js';
|
|
72
|
+
import examples from 'libphonenumber-js/mobile/examples';
|
|
73
|
+
import ChevronDown20 from '@mozaic-ds/icons-vue/src/components/ChevronDown20/ChevronDown20.vue';
|
|
74
|
+
/**
|
|
75
|
+
* A phone number input is a specialized input field designed to capture and validate phone numbers, ensuring correct formatting based on country-specific dialing codes. It often includes a country selector that automatically adjusts the international dialing code. This component improves user experience by standardizing phone number entries, reducing errors, and facilitating global compatibility. It is commonly used in registration forms, authentication flows, and contact information fields.
|
|
76
|
+
*/
|
|
77
|
+
const props = withDefaults(
|
|
78
|
+
defineProps<{
|
|
79
|
+
/**
|
|
80
|
+
* A unique identifier for the phone number input element, used to associate the label with the form element.
|
|
81
|
+
*/
|
|
82
|
+
id: string;
|
|
83
|
+
/**
|
|
84
|
+
* The current value of the phone number input field.
|
|
85
|
+
*/
|
|
86
|
+
modelValue?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Default country code for phone number formatting (e.g., 'FR', 'US', 'PT').
|
|
89
|
+
*/
|
|
90
|
+
defaultCountry?: CountryCode;
|
|
91
|
+
/**
|
|
92
|
+
* A placeholder text to show in the phone number input when it is empty.
|
|
93
|
+
*/
|
|
94
|
+
placeholder?: string;
|
|
95
|
+
/**
|
|
96
|
+
* Determines the size of the phone number input.
|
|
97
|
+
*/
|
|
98
|
+
size?: 's' | 'm';
|
|
99
|
+
/**
|
|
100
|
+
* If `true`, applies an invalid state to the password input.
|
|
101
|
+
*/
|
|
102
|
+
isInvalid?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* If `true`, disables the input, making it non-interactive.
|
|
105
|
+
*/
|
|
106
|
+
disabled?: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* If `true`, the input is read-only (cannot be edited).
|
|
109
|
+
*/
|
|
110
|
+
readonly?: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* If `true`, display the country calling code prefix (+33, +1, etc.).
|
|
113
|
+
*/
|
|
114
|
+
prefix?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* If `true`, display the country flag selector
|
|
117
|
+
*/
|
|
118
|
+
flag?: boolean;
|
|
119
|
+
}>(),
|
|
120
|
+
{
|
|
121
|
+
modelValue: '',
|
|
122
|
+
defaultCountry: 'FR' as CountryCode,
|
|
123
|
+
size: 'm',
|
|
124
|
+
prefix: true,
|
|
125
|
+
flag: true,
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const phoneNumber = ref(props.modelValue);
|
|
130
|
+
const selectedCountry = ref<CountryCode>(props.defaultCountry);
|
|
131
|
+
const countries = getCountries();
|
|
132
|
+
|
|
133
|
+
const dynamicPlaceholder = computed(() => {
|
|
134
|
+
if (props.placeholder && props.placeholder.length > 0) {
|
|
135
|
+
return props.placeholder;
|
|
136
|
+
}
|
|
137
|
+
return getExampleNumber(selectedCountry.value, examples)?.formatNational();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const isValid = computed(() => {
|
|
141
|
+
if (!phoneNumber.value) return false;
|
|
142
|
+
try {
|
|
143
|
+
return isValidPhoneNumber(phoneNumber.value, selectedCountry.value);
|
|
144
|
+
} catch {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const parsedNumber = computed(() => {
|
|
150
|
+
if (!phoneNumber.value) return null;
|
|
151
|
+
try {
|
|
152
|
+
return parsePhoneNumberFromString(phoneNumber.value, selectedCountry.value);
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const getCountryName = (
|
|
159
|
+
countryCode: CountryCode,
|
|
160
|
+
locale: string = 'fr',
|
|
161
|
+
): string => {
|
|
162
|
+
try {
|
|
163
|
+
const regionNames = new Intl.DisplayNames([locale], { type: 'region' });
|
|
164
|
+
return regionNames.of(countryCode) || countryCode;
|
|
165
|
+
} catch {
|
|
166
|
+
return countryCode;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
watch(
|
|
171
|
+
[phoneNumber, selectedCountry],
|
|
172
|
+
() => {
|
|
173
|
+
// Auto-format input when number is valid
|
|
174
|
+
if (isValid.value && parsedNumber.value) {
|
|
175
|
+
const formattedNational = parsedNumber.value.formatNational();
|
|
176
|
+
if (formattedNational !== phoneNumber.value) {
|
|
177
|
+
phoneNumber.value = formattedNational;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Emit international format for v-model
|
|
182
|
+
const fullNumber = parsedNumber.value?.number || phoneNumber.value;
|
|
183
|
+
emit('update:modelValue', fullNumber);
|
|
184
|
+
emit('valid', isValid.value);
|
|
185
|
+
},
|
|
186
|
+
{ flush: 'post' },
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
watch(
|
|
190
|
+
() => props.modelValue,
|
|
191
|
+
(newValue) => {
|
|
192
|
+
if (newValue && newValue !== phoneNumber.value) {
|
|
193
|
+
if (newValue.startsWith('+')) {
|
|
194
|
+
try {
|
|
195
|
+
const parsed = parsePhoneNumberFromString(
|
|
196
|
+
newValue,
|
|
197
|
+
selectedCountry.value,
|
|
198
|
+
);
|
|
199
|
+
if (parsed) {
|
|
200
|
+
phoneNumber.value = parsed.formatNational();
|
|
201
|
+
} else {
|
|
202
|
+
phoneNumber.value = newValue;
|
|
203
|
+
}
|
|
204
|
+
} catch {
|
|
205
|
+
phoneNumber.value = newValue;
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
phoneNumber.value = newValue;
|
|
209
|
+
}
|
|
210
|
+
} else if (!newValue) {
|
|
211
|
+
phoneNumber.value = '';
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
{ immediate: true },
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const sizeSelectClass = computed(() => {
|
|
218
|
+
return props.size !== 'm' ? `mc-select--${props.size}` : '';
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const sizeInputClass = computed(() => {
|
|
222
|
+
return props.size !== 'm' ? `mc-text-input--${props.size}` : '';
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const isDisabled = computed(() => props.disabled);
|
|
226
|
+
const isReadOnly = computed(() => props.readonly);
|
|
227
|
+
|
|
228
|
+
const selectWrapperClass = computed(() => {
|
|
229
|
+
return { 'mc-phone-number-input__select-wrapper--hidden': !props.flag };
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const inputClass = computed(() => {
|
|
233
|
+
const hasValue = phoneNumber.value && phoneNumber.value.trim().length > 0;
|
|
234
|
+
const isInvalidState = props.isInvalid || (hasValue && !isValid.value);
|
|
235
|
+
return [sizeInputClass.value, { 'is-invalid': isInvalidState }];
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const getFlagUrl = (countryCode: CountryCode): string => {
|
|
239
|
+
return `https://flagcdn.com/${countryCode.toLowerCase()}.svg`;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const handleInput = (event: Event) => {
|
|
243
|
+
const input = event.target as HTMLInputElement;
|
|
244
|
+
const value = input.value;
|
|
245
|
+
const sanitized = value.replace(/[^0-9+\s()-]/g, '');
|
|
246
|
+
|
|
247
|
+
phoneNumber.value = sanitized;
|
|
248
|
+
if (value !== sanitized) {
|
|
249
|
+
input.value = sanitized;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const emit = defineEmits<{
|
|
254
|
+
/**
|
|
255
|
+
* Emits when the input value changes, updating the `modelValue` prop.
|
|
256
|
+
*/
|
|
257
|
+
'update:modelValue': [value: string];
|
|
258
|
+
/**
|
|
259
|
+
* Emits when the validation state of the phone number changes.
|
|
260
|
+
*/
|
|
261
|
+
valid: [isValid: boolean];
|
|
262
|
+
}>();
|
|
263
|
+
</script>
|
|
264
|
+
|
|
265
|
+
<style lang="scss">
|
|
266
|
+
@use '@mozaic-ds/styles/components/text-input';
|
|
267
|
+
@use '@mozaic-ds/styles/components/button';
|
|
268
|
+
@use '@mozaic-ds/styles/components/listbox';
|
|
269
|
+
@use '@mozaic-ds/styles/components/select';
|
|
270
|
+
@use '@mozaic-ds/styles/components/phone-number-input';
|
|
271
|
+
</style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# MPhoneNumber
|
|
2
|
+
|
|
3
|
+
A phone number input is a specialized input field designed to capture and validate phone numbers, ensuring correct formatting based on country-specific dialing codes. It often includes a country selector that automatically adjusts the international dialing code. This component improves user experience by standardizing phone number entries, reducing errors, and facilitating global compatibility. It is commonly used in registration forms, authentication flows, and contact information fields.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Props
|
|
7
|
+
|
|
8
|
+
| Name | Description | Type | Default |
|
|
9
|
+
| --- | --- | --- | --- |
|
|
10
|
+
| `id*` | A unique identifier for the phone number input element, used to associate the label with the form element. | `string` | - |
|
|
11
|
+
| `modelValue` | The current value of the phone number input field. | `string` | `""` |
|
|
12
|
+
| `defaultCountry` | Default country code for phone number formatting (e.g., 'FR', 'US', 'PT'). | `CountryCode` | `"FR" as CountryCode` |
|
|
13
|
+
| `placeholder` | A placeholder text to show in the phone number input when it is empty. | `string` | - |
|
|
14
|
+
| `size` | Determines the size of the phone number input. | `"s"` `"m"` | `"m"` |
|
|
15
|
+
| `isInvalid` | If `true`, applies an invalid state to the password input. | `boolean` | - |
|
|
16
|
+
| `disabled` | If `true`, disables the input, making it non-interactive. | `boolean` | - |
|
|
17
|
+
| `readonly` | If `true`, the input is read-only (cannot be edited). | `boolean` | - |
|
|
18
|
+
| `prefix` | If `true`, display the country calling code prefix (+33, +1, etc.). | `boolean` | `true` |
|
|
19
|
+
| `flag` | If `true`, display the country flag selector | `boolean` | `true` |
|
|
20
|
+
|
|
21
|
+
## Events
|
|
22
|
+
|
|
23
|
+
| Name | Description | Type |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `update:modelValue` | - | `[value: string]` |
|
|
26
|
+
| `valid` | - | `[isValid: boolean]` |
|
|
@@ -19,4 +19,4 @@ A pincode input is a specialized input field used to enter short numeric codes,
|
|
|
19
19
|
|
|
20
20
|
| Name | Description | Type |
|
|
21
21
|
| --- | --- | --- |
|
|
22
|
-
| `update:modelValue` |
|
|
22
|
+
| `update:modelValue` | Emits when the pincode value changes, updating the modelValue prop. | [value: string] |
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
:id="option.id"
|
|
6
6
|
:key="option.id"
|
|
7
7
|
:label="option.label"
|
|
8
|
-
:is-invalid="option.isInvalid"
|
|
9
8
|
:name="name"
|
|
10
9
|
:class="classObjectItem"
|
|
11
10
|
:model-value="modelValue ? modelValue.includes(option.value) : undefined"
|
|
@@ -40,7 +39,6 @@ const props = defineProps<{
|
|
|
40
39
|
label: string;
|
|
41
40
|
value: string;
|
|
42
41
|
disabled?: boolean;
|
|
43
|
-
isInvalid?: boolean;
|
|
44
42
|
size?: 's' | 'm';
|
|
45
43
|
}>;
|
|
46
44
|
/**
|
|
@@ -10,7 +10,7 @@ A toggle is a switch component that allows users to enable or disable a setting,
|
|
|
10
10
|
| `name*` | The name attribute for the toggle element, typically used for form submission. | `string` | - |
|
|
11
11
|
| `modelValue` | Property used to manage the values checked by v-model
|
|
12
12
|
(Do not use directly) | `string[]` | - |
|
|
13
|
-
| `options*` | List of properties of each toggle of the toggle group. | `{ id: string; label: string; value: string; disabled?: boolean` `undefined;
|
|
13
|
+
| `options*` | List of properties of each toggle of the toggle group. | `{ id: string; label: string; value: string; disabled?: boolean` `undefined; size?: "s"` `"m"` `undefined; }[]` | - |
|
|
14
14
|
| `inline` | If `true`, make the form element of the group inline. | `boolean` | - |
|
|
15
15
|
|
|
16
16
|
## Events
|
package/src/main.ts
CHANGED
|
@@ -23,6 +23,7 @@ export { default as MNumberBadge } from './components/numberbadge/MNumberBadge.v
|
|
|
23
23
|
export { default as MOverlay } from './components/overlay/MOverlay.vue';
|
|
24
24
|
export { default as MPagination } from './components/pagination/MPagination.vue';
|
|
25
25
|
export { default as MPasswordInput } from './components/passwordinput/MPasswordInput.vue';
|
|
26
|
+
export { default as MPhoneNumber } from './components/phonenumber/MPhoneNumber.vue';
|
|
26
27
|
export { default as MPincode } from './components/pincode/MPincode.vue';
|
|
27
28
|
export { default as MQuantitySelector } from './components/quantityselector/MQuantitySelector.vue';
|
|
28
29
|
export { default as MRadio } from './components/radio/MRadio.vue';
|