@mozaic-ds/vue 1.0.0-beta.3 → 1.0.0-beta.4
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 +218 -84
- package/dist/mozaic-vue.css +1 -1
- package/dist/mozaic-vue.d.ts +920 -0
- package/dist/mozaic-vue.js +877 -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 +80 -50
- package/src/components/Contributing.mdx +118 -0
- package/src/components/GettingStarted.mdx +39 -0
- package/src/components/Introduction.mdx +54 -0
- package/src/components/Support.mdx +18 -0
- package/src/components/badge/MBadge.spec.ts +16 -0
- package/src/components/badge/MBadge.stories.ts +50 -0
- package/src/components/badge/MBadge.vue +36 -34
- package/src/components/button/MButton.spec.ts +191 -0
- package/src/components/button/MButton.stories.ts +66 -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/field/MField.spec.ts +166 -0
- package/src/components/field/MField.stories.ts +376 -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 +274 -0
- package/src/components/fieldgroup/MFieldGroup.vue +79 -0
- package/src/components/iconbutton/MIconButton.spec.ts +108 -0
- package/src/components/iconbutton/MIconButton.stories.ts +66 -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 +98 -0
- package/src/components/link/MLink.vue +86 -109
- package/src/components/loader/MLoader.spec.ts +104 -0
- package/src/components/loader/MLoader.stories.ts +45 -0
- package/src/components/loader/MLoader.vue +65 -55
- package/src/components/overlay/MOverlay.spec.ts +51 -0
- package/src/components/overlay/MOverlay.stories.ts +40 -0
- package/src/components/overlay/MOverlay.vue +27 -19
- package/src/components/quantityselector/MQuantitySelector.spec.ts +262 -0
- package/src/components/quantityselector/MQuantitySelector.stories.ts +89 -0
- package/src/components/quantityselector/MQuantitySelector.vue +160 -136
- 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/MStatusDot.vue +32 -0
- package/src/components/statusbadge/MstatusBadge.spec.ts +16 -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 -42
- package/src/components/textinput/MTextInput.spec.ts +121 -0
- package/src/components/textinput/MTextInput.stories.ts +114 -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/usingIcons.mdx +43 -0
- package/src/components/usingPresets.mdx +125 -0
- package/src/main.ts +39 -0
- package/dist/demo.html +0 -1
- package/dist/mozaic-vue.adeo.css +0 -45
- package/dist/mozaic-vue.adeo.umd.js +0 -41775
- package/dist/mozaic-vue.common.js +0 -41765
- package/dist/mozaic-vue.common.js.map +0 -1
- package/dist/mozaic-vue.umd.js +0 -41776
- 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 -198
- package/src/components/autocomplete/index.js +0 -7
- package/src/components/badge/index.js +0 -7
- package/src/components/breadcrumb/MBreadcrumb.vue +0 -73
- 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 -155
- 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/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/MFlag.vue +0 -46
- 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 -120
- package/src/components/icon/index.js +0 -7
- package/src/components/index.js +0 -43
- 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 -106
- package/src/components/listbox/index.js +0 -7
- package/src/components/loader/index.js +0 -7
- package/src/components/modal/MModal.vue +0 -179
- 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/MPagination.vue +0 -162
- package/src/components/pagination/index.js +0 -7
- package/src/components/passwordinput/MPasswordInput.vue +0 -96
- package/src/components/passwordinput/index.js +0 -7
- package/src/components/phonenumber/MPhoneNumber.vue +0 -390
- 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 -118
- 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 -70
- package/src/components/stepper/index.js +0 -7
- package/src/components/tabs/MTab.vue +0 -184
- package/src/components/tabs/index.js +0 -7
- package/src/components/tags/MTag.vue +0 -173
- 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 -62
- package/src/shims-tsx.d.ts +0 -13
- package/src/shims.vue.d.ts +0 -4
- package/src/tokens/adeo/android/colors.xml +0 -391
- package/src/tokens/adeo/android/font_dimens.xml +0 -18
- package/src/tokens/adeo/css/_variables.scss +0 -385
- package/src/tokens/adeo/css/root.scss +0 -387
- package/src/tokens/adeo/ios/StyleDictionaryColor.h +0 -399
- package/src/tokens/adeo/ios/StyleDictionaryColor.m +0 -411
- package/src/tokens/adeo/ios/StyleDictionaryColor.swift +0 -394
- 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 -483
- package/src/tokens/adeo/js/tokensObject.js +0 -10354
- package/src/tokens/adeo/scss/_tokens.scss +0 -1300
- package/src/utils/mozaicClasses.js +0 -16
- package/src/utils/theme.validator.js +0 -19
- package/types/index.d.ts +0 -100
|
@@ -1,84 +1,94 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="mc-loader" :class="
|
|
2
|
+
<div class="mc-loader" :class="classObject">
|
|
3
3
|
<span class="mc-loader__spinner">
|
|
4
4
|
<svg
|
|
5
5
|
class="mc-loader__icon"
|
|
6
6
|
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
-
:viewBox="
|
|
7
|
+
:viewBox="setViewBox"
|
|
8
|
+
aria-hidden="true"
|
|
8
9
|
>
|
|
9
|
-
<circle
|
|
10
|
+
<circle
|
|
11
|
+
class="mc-loader__path"
|
|
12
|
+
cx="50%"
|
|
13
|
+
cy="50%"
|
|
14
|
+
:r="setCircleRadius"
|
|
15
|
+
></circle>
|
|
10
16
|
</svg>
|
|
11
17
|
</span>
|
|
12
|
-
|
|
13
|
-
<span v-if="text" class="mc-loader__text">{{ text }}</span>
|
|
18
|
+
<p v-if="text" class="mc-loader__text" role="status">{{ text }}</p>
|
|
14
19
|
</div>
|
|
15
20
|
</template>
|
|
16
21
|
|
|
17
|
-
<script>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
<script setup lang="ts">
|
|
23
|
+
import { computed } from 'vue';
|
|
24
|
+
/**
|
|
25
|
+
* A loader indicates that content or data is being loaded or processed, providing visual feedback to users during wait times.
|
|
26
|
+
*/
|
|
27
|
+
const props = withDefaults(
|
|
28
|
+
defineProps<{
|
|
22
29
|
/**
|
|
23
|
-
*
|
|
24
|
-
* @values s, m , l
|
|
30
|
+
* Specifies the visual appearance of the loader.
|
|
25
31
|
*/
|
|
26
|
-
|
|
27
|
-
type: String,
|
|
28
|
-
default: 'm',
|
|
29
|
-
validator: (value) => ['s', 'm', 'l'].includes(value),
|
|
30
|
-
},
|
|
32
|
+
appearance?: 'standard' | 'accent' | 'inverse';
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
|
-
*
|
|
34
|
-
* @values dark, light, primary
|
|
35
|
+
* Defines the size of the loader.
|
|
35
36
|
*/
|
|
36
|
-
|
|
37
|
-
type: String,
|
|
38
|
-
default: 'primary',
|
|
39
|
-
validator: (value) => ['dark', 'light', 'primary'].includes(value),
|
|
40
|
-
},
|
|
37
|
+
size?: 's' | 'm' | 'l';
|
|
41
38
|
|
|
42
39
|
/**
|
|
43
|
-
*
|
|
40
|
+
* Text to display alongside the loader when using the loader inside an `Overlay`.
|
|
44
41
|
*/
|
|
45
|
-
text
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
text?: string;
|
|
43
|
+
}>(),
|
|
44
|
+
{
|
|
45
|
+
appearance: 'standard',
|
|
46
|
+
size: 'm',
|
|
49
47
|
},
|
|
48
|
+
);
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
const classObject = computed(() => {
|
|
51
|
+
return {
|
|
52
|
+
[`mc-loader--${props.size}`]: props.size && props.size !== 'm',
|
|
53
|
+
[`mc-loader--${props.appearance}`]:
|
|
54
|
+
props.appearance && props.appearance !== 'standard',
|
|
55
|
+
};
|
|
56
|
+
});
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
+
const setViewBox = computed(() => {
|
|
59
|
+
let viewBox: string;
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
switch (props.size) {
|
|
62
|
+
case 's':
|
|
63
|
+
viewBox = '0 0 24 24';
|
|
64
|
+
break;
|
|
65
|
+
case 'l':
|
|
66
|
+
viewBox = '0 0 64 64';
|
|
67
|
+
break;
|
|
68
|
+
default:
|
|
69
|
+
viewBox = '0 0 32 32';
|
|
70
|
+
}
|
|
71
|
+
return viewBox;
|
|
72
|
+
});
|
|
62
73
|
|
|
63
|
-
|
|
64
|
-
|
|
74
|
+
const setCircleRadius = computed(() => {
|
|
75
|
+
let circleRadius: number;
|
|
65
76
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
switch (props.size) {
|
|
78
|
+
case 's':
|
|
79
|
+
circleRadius = 6;
|
|
80
|
+
break;
|
|
81
|
+
case 'l':
|
|
82
|
+
circleRadius = 19;
|
|
83
|
+
break;
|
|
84
|
+
default:
|
|
85
|
+
circleRadius = 9;
|
|
86
|
+
}
|
|
73
87
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
};
|
|
88
|
+
return circleRadius;
|
|
89
|
+
});
|
|
79
90
|
</script>
|
|
80
91
|
|
|
81
|
-
<style lang="scss">
|
|
82
|
-
@
|
|
83
|
-
@import 'components/c.loader';
|
|
92
|
+
<style lang="scss" scoped>
|
|
93
|
+
@use '@mozaic-ds/styles/components/loader';
|
|
84
94
|
</style>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import MOverlay from './MOverlay.vue';
|
|
4
|
+
|
|
5
|
+
describe('MOverlay.vue', () => {
|
|
6
|
+
it('should be visible when isVisible is true', () => {
|
|
7
|
+
const wrapper = mount(MOverlay, {
|
|
8
|
+
props: {
|
|
9
|
+
isVisible: true,
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
expect(wrapper.classes()).toContain('is-visible');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should not be visible when isVisible is false', () => {
|
|
17
|
+
const wrapper = mount(MOverlay, {
|
|
18
|
+
props: {
|
|
19
|
+
isVisible: false,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
expect(wrapper.classes()).not.toContain('is-visible');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should apply the dialog label for accessibility', () => {
|
|
27
|
+
const label = 'Overlay Dialog';
|
|
28
|
+
const wrapper = mount(MOverlay, {
|
|
29
|
+
props: {
|
|
30
|
+
dialogLabel: label,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
expect(
|
|
35
|
+
wrapper.find('div[role="dialog"]').attributes('aria-labelledby'),
|
|
36
|
+
).toBe(label);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should render the content inside the overlay', () => {
|
|
40
|
+
const wrapper = mount(MOverlay, {
|
|
41
|
+
props: {
|
|
42
|
+
isVisible: true,
|
|
43
|
+
},
|
|
44
|
+
slots: {
|
|
45
|
+
default: 'Overlay Content',
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(wrapper.text()).toContain('Overlay Content');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3';
|
|
2
|
+
import MOverlay from './MOverlay.vue';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof MOverlay> = {
|
|
5
|
+
title: 'Overlay/Overlay',
|
|
6
|
+
component: MOverlay,
|
|
7
|
+
parameters: {
|
|
8
|
+
docs: {
|
|
9
|
+
description: {
|
|
10
|
+
component:
|
|
11
|
+
'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.',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
args: {
|
|
16
|
+
isVisible: true,
|
|
17
|
+
},
|
|
18
|
+
argTypes: {
|
|
19
|
+
$slots: {
|
|
20
|
+
table: {
|
|
21
|
+
disable: true,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
render: (args) => ({
|
|
26
|
+
components: { MOverlay },
|
|
27
|
+
setup() {
|
|
28
|
+
return { args };
|
|
29
|
+
},
|
|
30
|
+
template: `
|
|
31
|
+
<MOverlay v-bind="args">
|
|
32
|
+
<template v-if="${'default' in args}" v-slot>${args.default}</template>
|
|
33
|
+
</MOverlay>
|
|
34
|
+
`,
|
|
35
|
+
}),
|
|
36
|
+
};
|
|
37
|
+
export default meta;
|
|
38
|
+
type Story = StoryObj<typeof MOverlay>;
|
|
39
|
+
|
|
40
|
+
export const Default: Story = {};
|
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="mc-overlay" :class="{ 'is-visible': isVisible }">
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
<div role="dialog" tabindex="-1" :aria-labelledby="dialogLabel">
|
|
4
|
+
<slot />
|
|
5
|
+
</div>
|
|
5
6
|
</div>
|
|
6
7
|
</template>
|
|
7
8
|
|
|
8
|
-
<script>
|
|
9
|
-
|
|
10
|
-
name: 'MOverlay',
|
|
9
|
+
<script setup lang="ts">
|
|
10
|
+
import type { VNode } from 'vue';
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
/**
|
|
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
|
+
defineProps<{
|
|
16
|
+
/**
|
|
17
|
+
* Controls the visibility of the overlay.
|
|
18
|
+
*/
|
|
19
|
+
isVisible?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Accessible label for the overlay dialog.
|
|
22
|
+
*/
|
|
23
|
+
dialogLabel?: string;
|
|
24
|
+
}>();
|
|
25
|
+
|
|
26
|
+
defineSlots<{
|
|
27
|
+
/**
|
|
28
|
+
* Use this slot to insert a centered content inside the overlay
|
|
29
|
+
*/
|
|
30
|
+
default?: VNode;
|
|
31
|
+
}>();
|
|
23
32
|
</script>
|
|
24
33
|
|
|
25
|
-
<style lang="scss">
|
|
26
|
-
@
|
|
27
|
-
@import 'components/c.overlay';
|
|
34
|
+
<style lang="scss" scoped>
|
|
35
|
+
@use '@mozaic-ds/styles/components/overlay';
|
|
28
36
|
</style>
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import MQuantitySelector from './MQuantitySelector.vue';
|
|
4
|
+
|
|
5
|
+
describe('MQuantitySelector component', () => {
|
|
6
|
+
it('renders the component with default values', () => {
|
|
7
|
+
const wrapper = mount(MQuantitySelector, {
|
|
8
|
+
props: {
|
|
9
|
+
id: 'quantity-selector-1',
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const input = wrapper.find('input');
|
|
14
|
+
const increaseButton = wrapper.find(
|
|
15
|
+
'.mc-quantity-selector__button--increase',
|
|
16
|
+
);
|
|
17
|
+
const decreaseButton = wrapper.find(
|
|
18
|
+
'.mc-quantity-selector__button--decrease',
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
expect(input.exists()).toBe(true);
|
|
22
|
+
expect(input.element.value).toBe('1');
|
|
23
|
+
expect(input.attributes('min')).toBe('1');
|
|
24
|
+
expect(input.attributes('max')).toBe('100');
|
|
25
|
+
expect(input.attributes('step')).toBe('1');
|
|
26
|
+
|
|
27
|
+
expect(increaseButton.exists()).toBe(true);
|
|
28
|
+
expect(decreaseButton.exists()).toBe(true);
|
|
29
|
+
expect(increaseButton.attributes('disabled')).toBeUndefined();
|
|
30
|
+
expect(decreaseButton.attributes('disabled')).toBeDefined();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('emits an update when value changes via input', async () => {
|
|
34
|
+
const wrapper = mount(MQuantitySelector, {
|
|
35
|
+
props: {
|
|
36
|
+
id: 'quantity-selector-1',
|
|
37
|
+
modelValue: 5,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const input = wrapper.find('input');
|
|
42
|
+
await input.setValue(10);
|
|
43
|
+
|
|
44
|
+
const emittedValue = wrapper.emitted('update:modelValue');
|
|
45
|
+
expect(emittedValue).toBeTruthy();
|
|
46
|
+
expect(emittedValue?.[0]).toEqual([10]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('increments value when increase button is clicked', async () => {
|
|
50
|
+
const wrapper = mount(MQuantitySelector, {
|
|
51
|
+
props: {
|
|
52
|
+
id: 'quantity-selector-1',
|
|
53
|
+
modelValue: 5,
|
|
54
|
+
max: 10,
|
|
55
|
+
step: 1,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const increaseButton = wrapper.find(
|
|
60
|
+
'.mc-quantity-selector__button--increase',
|
|
61
|
+
);
|
|
62
|
+
await increaseButton.trigger('click');
|
|
63
|
+
|
|
64
|
+
const input = wrapper.find('input');
|
|
65
|
+
expect(input.element.value).toBe('6');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('decrements value when decrease button is clicked', async () => {
|
|
69
|
+
const wrapper = mount(MQuantitySelector, {
|
|
70
|
+
props: {
|
|
71
|
+
id: 'quantity-selector-1',
|
|
72
|
+
modelValue: 5,
|
|
73
|
+
min: 1,
|
|
74
|
+
step: 1,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const decreaseButton = wrapper.find(
|
|
79
|
+
'.mc-quantity-selector__button--decrease',
|
|
80
|
+
);
|
|
81
|
+
await decreaseButton.trigger('click');
|
|
82
|
+
|
|
83
|
+
const input = wrapper.find('input');
|
|
84
|
+
expect(input.element.value).toBe('4');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('does not increment beyond max value', async () => {
|
|
88
|
+
const wrapper = mount(MQuantitySelector, {
|
|
89
|
+
props: {
|
|
90
|
+
id: 'quantity-selector-1',
|
|
91
|
+
modelValue: 10,
|
|
92
|
+
max: 10,
|
|
93
|
+
step: 1,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const increaseButton = wrapper.find(
|
|
98
|
+
'.mc-quantity-selector__button--increase',
|
|
99
|
+
);
|
|
100
|
+
await increaseButton.trigger('click');
|
|
101
|
+
|
|
102
|
+
const input = wrapper.find('input');
|
|
103
|
+
expect(input.element.value).toBe('10');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('does not decrement below min value', async () => {
|
|
107
|
+
const wrapper = mount(MQuantitySelector, {
|
|
108
|
+
props: {
|
|
109
|
+
id: 'quantity-selector-1',
|
|
110
|
+
modelValue: 1,
|
|
111
|
+
min: 1,
|
|
112
|
+
step: 1,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const decreaseButton = wrapper.find(
|
|
117
|
+
'.mc-quantity-selector__button--decrease',
|
|
118
|
+
);
|
|
119
|
+
await decreaseButton.trigger('click');
|
|
120
|
+
|
|
121
|
+
const input = wrapper.find('input');
|
|
122
|
+
expect(input.element.value).toBe('1');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('disables input and buttons when disabled prop is true', async () => {
|
|
126
|
+
const wrapper = mount(MQuantitySelector, {
|
|
127
|
+
props: {
|
|
128
|
+
id: 'quantity-selector-1',
|
|
129
|
+
disabled: true,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const input = wrapper.find('input');
|
|
134
|
+
const increaseButton = wrapper.find(
|
|
135
|
+
'.mc-quantity-selector__button--increase',
|
|
136
|
+
);
|
|
137
|
+
const decreaseButton = wrapper.find(
|
|
138
|
+
'.mc-quantity-selector__button--decrease',
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
expect(input.attributes('disabled')).toBeDefined();
|
|
142
|
+
expect(increaseButton.attributes('disabled')).toBeDefined();
|
|
143
|
+
expect(decreaseButton.attributes('disabled')).toBeDefined();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('renders with custom size class', () => {
|
|
147
|
+
const wrapper = mount(MQuantitySelector, {
|
|
148
|
+
props: {
|
|
149
|
+
id: 'quantity-selector-1',
|
|
150
|
+
size: 's',
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const div = wrapper.find('.mc-quantity-selector');
|
|
155
|
+
expect(div.classes()).toContain('mc-quantity-selector--s');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('hides increase and decrease buttons when readonly is true', () => {
|
|
159
|
+
const wrapper = mount(MQuantitySelector, {
|
|
160
|
+
props: {
|
|
161
|
+
id: 'quantity-selector-1',
|
|
162
|
+
readonly: true,
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const increaseButton = wrapper.find(
|
|
167
|
+
'.mc-quantity-selector__button--increase',
|
|
168
|
+
);
|
|
169
|
+
const decreaseButton = wrapper.find(
|
|
170
|
+
'.mc-quantity-selector__button--decrease',
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
expect(increaseButton.exists()).toBe(false);
|
|
174
|
+
expect(decreaseButton.exists()).toBe(false);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('renders and tests aria-label and aria-invalid for accessibility', () => {
|
|
178
|
+
const wrapper = mount(MQuantitySelector, {
|
|
179
|
+
props: {
|
|
180
|
+
id: 'quantity-selector-1',
|
|
181
|
+
isInvalid: true,
|
|
182
|
+
ariaLabel: 'Select quantity',
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const input = wrapper.find('input');
|
|
187
|
+
expect(input.attributes('aria-label')).toBe('Select quantity');
|
|
188
|
+
expect(input.attributes('aria-invalid')).toBe('true');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('does not allow invalid input values (non-numeric)', async () => {
|
|
192
|
+
const wrapper = mount(MQuantitySelector, {
|
|
193
|
+
props: {
|
|
194
|
+
id: 'quantity-selector-1',
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const input = wrapper.find('input');
|
|
199
|
+
await input.setValue('abc');
|
|
200
|
+
expect(input.element.value).toBe('1');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('supports custom step values', async () => {
|
|
204
|
+
const wrapper = mount(MQuantitySelector, {
|
|
205
|
+
props: {
|
|
206
|
+
id: 'quantity-selector-1',
|
|
207
|
+
modelValue: 10,
|
|
208
|
+
step: 5,
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const increaseButton = wrapper.find(
|
|
213
|
+
'.mc-quantity-selector__button--increase',
|
|
214
|
+
);
|
|
215
|
+
await increaseButton.trigger('click');
|
|
216
|
+
|
|
217
|
+
const input = wrapper.find('input');
|
|
218
|
+
expect(input.element.value).toBe('15');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('renders custom increment and decrement labels', () => {
|
|
222
|
+
const wrapper = mount(MQuantitySelector, {
|
|
223
|
+
props: {
|
|
224
|
+
id: 'quantity-selector-1',
|
|
225
|
+
incrementlabel: 'Increase Quantity',
|
|
226
|
+
decrementLabel: 'Decrease Quantity',
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const increaseButton = wrapper.find(
|
|
231
|
+
'.mc-quantity-selector__button--increase',
|
|
232
|
+
);
|
|
233
|
+
const decreaseButton = wrapper.find(
|
|
234
|
+
'.mc-quantity-selector__button--decrease',
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
expect(increaseButton.text()).toBe('Increase Quantity');
|
|
238
|
+
expect(decreaseButton.text()).toBe('Decrease Quantity');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('correctly sets aria-controls', () => {
|
|
242
|
+
const wrapper = mount(MQuantitySelector, {
|
|
243
|
+
props: {
|
|
244
|
+
id: 'quantity-selector-1',
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const increaseButton = wrapper.find(
|
|
249
|
+
'.mc-quantity-selector__button--increase',
|
|
250
|
+
);
|
|
251
|
+
const decreaseButton = wrapper.find(
|
|
252
|
+
'.mc-quantity-selector__button--decrease',
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
expect(increaseButton.attributes('aria-controls')).toBe(
|
|
256
|
+
'quantity-selector-1',
|
|
257
|
+
);
|
|
258
|
+
expect(decreaseButton.attributes('aria-controls')).toBe(
|
|
259
|
+
'quantity-selector-1',
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3';
|
|
2
|
+
import { action } from '@storybook/addon-actions';
|
|
3
|
+
|
|
4
|
+
import MQuantitySelector from './MQuantitySelector.vue';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof MQuantitySelector> = {
|
|
7
|
+
title: 'Form Elements/Quantity Selector',
|
|
8
|
+
component: MQuantitySelector,
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component:
|
|
13
|
+
'The quantity selector is a form element used to enter or select a number. This type of input is best used when the user needs to choose the quantity of a selected item, like a product before adding to cart for example.',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
args: {
|
|
18
|
+
id: 'quantitySelectorId',
|
|
19
|
+
modelValue: 10,
|
|
20
|
+
min: 5,
|
|
21
|
+
max: 20,
|
|
22
|
+
},
|
|
23
|
+
argTypes: {
|
|
24
|
+
size: {
|
|
25
|
+
control: 'radio',
|
|
26
|
+
options: ['s', 'm'],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
render: (args) => ({
|
|
30
|
+
components: { MQuantitySelector },
|
|
31
|
+
setup() {
|
|
32
|
+
const handleUpdate = action('update:modelValue');
|
|
33
|
+
|
|
34
|
+
return { args, handleUpdate };
|
|
35
|
+
},
|
|
36
|
+
template: `
|
|
37
|
+
<MQuantitySelector
|
|
38
|
+
v-bind="args"
|
|
39
|
+
@update:modelValue="handleUpdate"
|
|
40
|
+
/>
|
|
41
|
+
`,
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
export default meta;
|
|
45
|
+
type Story = StoryObj<typeof MQuantitySelector>;
|
|
46
|
+
|
|
47
|
+
export const Default: Story = {};
|
|
48
|
+
|
|
49
|
+
export const Step: Story = {
|
|
50
|
+
args: {
|
|
51
|
+
id: 'stepId',
|
|
52
|
+
step: 5,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const Small: Story = {
|
|
57
|
+
args: {
|
|
58
|
+
id: 'smallId',
|
|
59
|
+
size: 's',
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const Disabled: Story = {
|
|
64
|
+
args: {
|
|
65
|
+
id: 'disableId',
|
|
66
|
+
disabled: true,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const ReadOnly: Story = {
|
|
71
|
+
args: {
|
|
72
|
+
id: 'readonlyId',
|
|
73
|
+
readonly: true,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const Invalid: Story = {
|
|
78
|
+
args: {
|
|
79
|
+
id: 'invalidId',
|
|
80
|
+
isInvalid: true,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const readOnly: Story = {
|
|
85
|
+
args: {
|
|
86
|
+
id: 'readonlyId',
|
|
87
|
+
readonly: true,
|
|
88
|
+
},
|
|
89
|
+
};
|