@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,126 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import MPincode from './MPincode.vue';
|
|
4
|
+
|
|
5
|
+
describe('MPincode component', () => {
|
|
6
|
+
if (typeof ClipboardEvent === 'undefined') {
|
|
7
|
+
global.ClipboardEvent = class ClipboardEvent extends Event {
|
|
8
|
+
clipboardData: DataTransfer;
|
|
9
|
+
constructor(type: string, eventInitDict?: ClipboardEventInit) {
|
|
10
|
+
super(type, eventInitDict);
|
|
11
|
+
this.clipboardData =
|
|
12
|
+
(eventInitDict && eventInitDict.clipboardData) || new DataTransfer();
|
|
13
|
+
}
|
|
14
|
+
// eslint-disable-next-line
|
|
15
|
+
} as any;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
it('renders correct number of input fields based on length', () => {
|
|
19
|
+
const wrapper = mount(MPincode, {
|
|
20
|
+
props: {
|
|
21
|
+
id: 'otp',
|
|
22
|
+
length: 6,
|
|
23
|
+
modelValue: '',
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const inputs = wrapper.findAll('input');
|
|
28
|
+
expect(inputs).toHaveLength(6);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('updates modelValue as user types', async () => {
|
|
32
|
+
const wrapper = mount(MPincode, {
|
|
33
|
+
props: {
|
|
34
|
+
id: 'otp',
|
|
35
|
+
length: 4,
|
|
36
|
+
modelValue: '',
|
|
37
|
+
'onUpdate:modelValue': vi.fn(),
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const inputs = wrapper.findAll('input');
|
|
42
|
+
await inputs[0].setValue('1');
|
|
43
|
+
await inputs[1].setValue('2');
|
|
44
|
+
await inputs[2].setValue('3');
|
|
45
|
+
await inputs[3].setValue('4');
|
|
46
|
+
|
|
47
|
+
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
|
|
48
|
+
const emitted = wrapper.emitted('update:modelValue');
|
|
49
|
+
const lastEmitted = emitted?.[emitted.length - 1][0];
|
|
50
|
+
expect(lastEmitted).toBe('1234');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('moves focus to next input on input', async () => {
|
|
54
|
+
const wrapper = mount(MPincode, {
|
|
55
|
+
attachTo: document.body, // Needed for focus
|
|
56
|
+
props: {
|
|
57
|
+
id: 'otp',
|
|
58
|
+
length: 4,
|
|
59
|
+
modelValue: '',
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const inputs = wrapper.findAll('input');
|
|
64
|
+
await inputs[0].setValue('5');
|
|
65
|
+
expect(document.activeElement).toBe(inputs[1].element);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('moves focus to previous input on backspace if current is empty', async () => {
|
|
69
|
+
const wrapper = mount(MPincode, {
|
|
70
|
+
attachTo: document.body,
|
|
71
|
+
props: {
|
|
72
|
+
id: 'otp',
|
|
73
|
+
length: 4,
|
|
74
|
+
modelValue: '',
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const inputs = wrapper.findAll('input');
|
|
79
|
+
await inputs[0].setValue('5');
|
|
80
|
+
await inputs[1].element.focus();
|
|
81
|
+
const event = new KeyboardEvent('keydown', { key: 'Backspace' });
|
|
82
|
+
inputs[1].element.dispatchEvent(event);
|
|
83
|
+
await wrapper.vm.$nextTick();
|
|
84
|
+
|
|
85
|
+
expect(document.activeElement).toBe(inputs[0].element);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('renders invalid class when isInvalid is true', () => {
|
|
89
|
+
const wrapper = mount(MPincode, {
|
|
90
|
+
props: {
|
|
91
|
+
id: 'otp',
|
|
92
|
+
isInvalid: true,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
expect(wrapper.classes()).toContain('is-invalid');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('disables inputs when disabled is true', () => {
|
|
100
|
+
const wrapper = mount(MPincode, {
|
|
101
|
+
props: {
|
|
102
|
+
id: 'otp',
|
|
103
|
+
disabled: true,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const inputs = wrapper.findAll('input');
|
|
108
|
+
for (const input of inputs) {
|
|
109
|
+
expect(input.attributes('disabled')).toBeDefined();
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('makes inputs readonly when readonly is true', () => {
|
|
114
|
+
const wrapper = mount(MPincode, {
|
|
115
|
+
props: {
|
|
116
|
+
id: 'otp',
|
|
117
|
+
readonly: true,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const inputs = wrapper.findAll('input');
|
|
122
|
+
for (const input of inputs) {
|
|
123
|
+
expect(input.attributes('readonly')).toBeDefined();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
|
+
import { action } from 'storybook/actions';
|
|
3
|
+
|
|
4
|
+
import MPincode from './MPincode.vue';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof MPincode> = {
|
|
7
|
+
title: 'Form Elements/Pincode',
|
|
8
|
+
component: MPincode,
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component:
|
|
13
|
+
'A pincode input is a specialized input field used to enter short numeric codes, such as verification codes, security PINs, or authentication tokens. It typically separates each digit into individual fields to improve readability and ease of entry. This component is commonly used in two-factor authentication (2FA), password recovery, and secure access flows, ensuring a structured and user-friendly experience.',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
args: {
|
|
18
|
+
id: 'pincodeId',
|
|
19
|
+
ariaLabel: 'enter your code',
|
|
20
|
+
},
|
|
21
|
+
render: (args) => ({
|
|
22
|
+
components: { MPincode },
|
|
23
|
+
setup() {
|
|
24
|
+
const handleUpdate = action('update:modelValue');
|
|
25
|
+
|
|
26
|
+
return { args, handleUpdate };
|
|
27
|
+
},
|
|
28
|
+
template: `
|
|
29
|
+
<MPincode
|
|
30
|
+
v-bind="args"
|
|
31
|
+
@update:modelValue="handleUpdate"
|
|
32
|
+
/>
|
|
33
|
+
`,
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
export default meta;
|
|
37
|
+
type Story = StoryObj<typeof MPincode>;
|
|
38
|
+
|
|
39
|
+
export const WithValue: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
id: 'valueId',
|
|
42
|
+
modelValue: '123098',
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const Default: Story = {};
|
|
47
|
+
|
|
48
|
+
export const Disabled: Story = {
|
|
49
|
+
args: {
|
|
50
|
+
id: 'disableId',
|
|
51
|
+
disabled: true,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const ReadOnly: Story = {
|
|
56
|
+
args: {
|
|
57
|
+
id: 'readonlyId',
|
|
58
|
+
modelValue: '123098',
|
|
59
|
+
readonly: true,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const Invalid: Story = {
|
|
64
|
+
args: {
|
|
65
|
+
id: 'invalidId',
|
|
66
|
+
isInvalid: true,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="mc-pincode-input" :class="classObject" @paste="onPaste">
|
|
3
|
+
<input
|
|
4
|
+
v-for="(digit, index) in otp"
|
|
5
|
+
:key="index"
|
|
6
|
+
:id="`pincodeItem${index}`"
|
|
7
|
+
:ref="(el) => setInputRef(el, index)"
|
|
8
|
+
type="text"
|
|
9
|
+
inputmode="numeric"
|
|
10
|
+
maxlength="1"
|
|
11
|
+
pattern="\d*"
|
|
12
|
+
autocomplete="one-time-code"
|
|
13
|
+
:name="name || `pincode-${id}`"
|
|
14
|
+
class="mc-pincode-input__control"
|
|
15
|
+
:disabled="disabled"
|
|
16
|
+
:readonly="readonly"
|
|
17
|
+
:value="digit"
|
|
18
|
+
v-bind="$attrs"
|
|
19
|
+
@input="(e) => onInput(e, index)"
|
|
20
|
+
@keydown.backspace="(e) => onBackspace(e, index)"
|
|
21
|
+
@keydown="(e) => onKeyDown(e, index)"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<script setup lang="ts">
|
|
27
|
+
import { ref, computed, watch, nextTick, type ComponentPublicInstance } from 'vue';
|
|
28
|
+
/**
|
|
29
|
+
* A pincode input is a specialized input field used to enter short numeric codes, such as verification codes, security PINs, or authentication tokens. It typically separates each digit into individual fields to improve readability and ease of entry. This component is commonly used in two-factor authentication (2FA), password recovery, and secure access flows, ensuring a structured and user-friendly experience.
|
|
30
|
+
*/
|
|
31
|
+
const props = withDefaults(
|
|
32
|
+
defineProps<{
|
|
33
|
+
/**
|
|
34
|
+
* A unique identifier for the pincode element, used to associate the label with the form element.
|
|
35
|
+
*/
|
|
36
|
+
id: string;
|
|
37
|
+
/**
|
|
38
|
+
* The number of input displayed in the pincode element.
|
|
39
|
+
*/
|
|
40
|
+
length?: 4 | 5 | 6;
|
|
41
|
+
/**
|
|
42
|
+
* The name attribute for the pincode element, typically used for form submission.
|
|
43
|
+
*/
|
|
44
|
+
name?: string;
|
|
45
|
+
/**
|
|
46
|
+
* The current value of the pincode field.
|
|
47
|
+
*/
|
|
48
|
+
modelValue?: string | number;
|
|
49
|
+
/**
|
|
50
|
+
* If `true`, applies an invalid state to the pincode.
|
|
51
|
+
*/
|
|
52
|
+
isInvalid?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* If `true`, disables the pincode, making it non-interactive.
|
|
55
|
+
*/
|
|
56
|
+
disabled?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* If `true`, the pincode is read-only (cannot be edited).
|
|
59
|
+
*/
|
|
60
|
+
readonly?: boolean;
|
|
61
|
+
}>(),
|
|
62
|
+
{
|
|
63
|
+
length: 6,
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const classObject = computed(() => {
|
|
68
|
+
return {
|
|
69
|
+
'is-invalid': props.isInvalid,
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const emit = defineEmits<{
|
|
74
|
+
(on: 'update:modelValue', value: string): void;
|
|
75
|
+
}>();
|
|
76
|
+
|
|
77
|
+
const otp = ref<string[]>(Array(props.length).fill(''));
|
|
78
|
+
const inputRefs = ref<(HTMLInputElement | null)[]>([]);
|
|
79
|
+
|
|
80
|
+
const setInputRef = (el: Element | ComponentPublicInstance | null, index: number) => {
|
|
81
|
+
inputRefs.value[index] = el as HTMLInputElement | null;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
watch(
|
|
85
|
+
() => props.modelValue,
|
|
86
|
+
(value) => {
|
|
87
|
+
const str = String(value ?? '');
|
|
88
|
+
otp.value = Array.from({ length: props.length }, (_, i) => str[i] ?? '');
|
|
89
|
+
},
|
|
90
|
+
{ immediate: true },
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const focusInput = (index: number) => {
|
|
94
|
+
nextTick(() => inputRefs.value[index]?.focus());
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const onInput = (e: Event, index: number) => {
|
|
98
|
+
const val = (e.target as HTMLInputElement).value.replace(/\D/g, '');
|
|
99
|
+
if (val) {
|
|
100
|
+
otp.value[index] = val[0];
|
|
101
|
+
emit('update:modelValue', otp.value.join(''));
|
|
102
|
+
if (index + 1 < props.length) focusInput(index + 1);
|
|
103
|
+
} else {
|
|
104
|
+
otp.value[index] = '';
|
|
105
|
+
emit('update:modelValue', otp.value.join(''));
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const onKeyDown = (e: KeyboardEvent, index: number) => {
|
|
110
|
+
if (e.key === 'ArrowLeft' && index > 0) {
|
|
111
|
+
focusInput(index - 1);
|
|
112
|
+
} else if (e.key === 'ArrowRight' && index < props.length - 1) {
|
|
113
|
+
focusInput(index + 1);
|
|
114
|
+
} else if (e.key === 'Backspace') {
|
|
115
|
+
onBackspace(e, index);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const onBackspace = (e: KeyboardEvent, index: number) => {
|
|
120
|
+
if (otp.value[index] === '' && index > 0) {
|
|
121
|
+
otp.value[index - 1] = '';
|
|
122
|
+
emit('update:modelValue', otp.value.join(''));
|
|
123
|
+
focusInput(index - 1);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const onPaste = (e: ClipboardEvent) => {
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
const pasted = e.clipboardData?.getData('text') ?? '';
|
|
130
|
+
const digits = pasted.replace(/\D/g, '').slice(0, props.length).split('');
|
|
131
|
+
otp.value = Array.from({ length: props.length }, (_, i) => digits[i] ?? '');
|
|
132
|
+
emit('update:modelValue', otp.value.join(''));
|
|
133
|
+
focusInput(Math.min(digits.length, props.length - 1));
|
|
134
|
+
};
|
|
135
|
+
</script>
|
|
136
|
+
|
|
137
|
+
<style lang="scss" scoped>
|
|
138
|
+
@use '@mozaic-ds/styles/components/pincode-input';
|
|
139
|
+
</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 MQuantitySelector from './MQuantitySelector.vue';
|
|
5
5
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils';
|
|
2
2
|
import { describe, it, expect } from 'vitest';
|
|
3
3
|
import MStatusNotification from './MStatusNotification.vue';
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
4
|
+
import InfoCircleFilled32 from '@mozaic-ds/icons-vue/src/components/InfoCircleFilled32/InfoCircleFilled32.vue';
|
|
5
|
+
import WarningCircleFilled32 from '@mozaic-ds/icons-vue/src/components/WarningCircleFilled32/WarningCircleFilled32.vue';
|
|
6
|
+
import CrossCircleFilled32 from '@mozaic-ds/icons-vue/src/components/CrossCircleFilled32/CrossCircleFilled32.vue';
|
|
7
|
+
import CheckCircleFilled32 from '@mozaic-ds/icons-vue/src/components/CheckCircleFilled32/CheckCircleFilled32.vue';
|
|
8
8
|
|
|
9
9
|
describe('MStatusNotification.vue', () => {
|
|
10
10
|
it('should render correctly with the default props', () => {
|
|
@@ -17,7 +17,7 @@ describe('MStatusNotification.vue', () => {
|
|
|
17
17
|
|
|
18
18
|
expect(wrapper.text()).toContain('Test Title');
|
|
19
19
|
expect(wrapper.text()).toContain('Test Description');
|
|
20
|
-
expect(wrapper.findComponent(
|
|
20
|
+
expect(wrapper.findComponent(InfoCircleFilled32).exists()).toBe(true); // Default icon is InfoCircle32
|
|
21
21
|
expect(wrapper.classes()).toContain('mc-status-notification');
|
|
22
22
|
});
|
|
23
23
|
|
|
@@ -29,7 +29,9 @@ describe('MStatusNotification.vue', () => {
|
|
|
29
29
|
status: 'success',
|
|
30
30
|
},
|
|
31
31
|
});
|
|
32
|
-
expect(wrapperSuccess.findComponent(
|
|
32
|
+
expect(wrapperSuccess.findComponent(CheckCircleFilled32).exists()).toBe(
|
|
33
|
+
true,
|
|
34
|
+
);
|
|
33
35
|
|
|
34
36
|
const wrapperWarning = mount(MStatusNotification, {
|
|
35
37
|
props: {
|
|
@@ -38,7 +40,9 @@ describe('MStatusNotification.vue', () => {
|
|
|
38
40
|
status: 'warning',
|
|
39
41
|
},
|
|
40
42
|
});
|
|
41
|
-
expect(wrapperWarning.findComponent(
|
|
43
|
+
expect(wrapperWarning.findComponent(WarningCircleFilled32).exists()).toBe(
|
|
44
|
+
true,
|
|
45
|
+
);
|
|
42
46
|
|
|
43
47
|
const wrapperError = mount(MStatusNotification, {
|
|
44
48
|
props: {
|
|
@@ -47,7 +51,7 @@ describe('MStatusNotification.vue', () => {
|
|
|
47
51
|
status: 'error',
|
|
48
52
|
},
|
|
49
53
|
});
|
|
50
|
-
expect(wrapperError.findComponent(
|
|
54
|
+
expect(wrapperError.findComponent(CrossCircleFilled32).exists()).toBe(true);
|
|
51
55
|
});
|
|
52
56
|
|
|
53
57
|
it('should show the close button if closable prop is true', () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/vue3';
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
2
|
import MStatusNotification from './MStatusNotification.vue';
|
|
3
|
-
import { action } from '
|
|
3
|
+
import { action } from 'storybook/actions';
|
|
4
4
|
import MButton from '../button/MButton.vue';
|
|
5
5
|
import MLink from '../link/MLink.vue';
|
|
6
6
|
import ArrowNext20 from '@mozaic-ds/icons-vue/src/components/ArrowNext20/ArrowNext20.vue';
|
|
@@ -21,13 +21,6 @@ const meta: Meta<typeof MStatusNotification> = {
|
|
|
21
21
|
'This is a title, be concise and use the description message to give details.',
|
|
22
22
|
description: 'Description message.',
|
|
23
23
|
},
|
|
24
|
-
argTypes: {
|
|
25
|
-
$slots: {
|
|
26
|
-
table: {
|
|
27
|
-
disable: true,
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
24
|
render: (args) => ({
|
|
32
25
|
components: { MStatusNotification, MButton, MLink, ArrowNext20 },
|
|
33
26
|
setup() {
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
<script setup lang="ts">
|
|
35
35
|
import { computed, type VNode } from 'vue';
|
|
36
36
|
import Cross20 from '@mozaic-ds/icons-vue/src/components/Cross20/Cross20.vue';
|
|
37
|
-
import
|
|
38
|
-
import
|
|
39
|
-
import
|
|
40
|
-
import
|
|
37
|
+
import InfoCircleFilled32 from '@mozaic-ds/icons-vue/src/components/InfoCircleFilled32/InfoCircleFilled32.vue';
|
|
38
|
+
import WarningCircleFilled32 from '@mozaic-ds/icons-vue/src/components/WarningCircleFilled32/WarningCircleFilled32.vue';
|
|
39
|
+
import CrossCircleFilled32 from '@mozaic-ds/icons-vue/src/components/CrossCircleFilled32/CrossCircleFilled32.vue';
|
|
40
|
+
import CheckCircleFilled32 from '@mozaic-ds/icons-vue/src/components/CheckCircleFilled32/CheckCircleFilled32.vue';
|
|
41
41
|
/**
|
|
42
42
|
* A Status Notification is used to draw the user’s attention to important information that needs to be acknowledged. It often provides feedback on a process, highlights a status update, or alerts users about an issue. Notifications are typically triggered by user actions or system events and are designed to be easily noticeable while maintaining a non-intrusive experience.
|
|
43
43
|
*/
|
|
@@ -82,14 +82,14 @@ const classObject = computed(() => {
|
|
|
82
82
|
const iconComponent = computed(() => {
|
|
83
83
|
switch (props.status) {
|
|
84
84
|
case 'success':
|
|
85
|
-
return
|
|
85
|
+
return CheckCircleFilled32;
|
|
86
86
|
case 'warning':
|
|
87
|
-
return
|
|
87
|
+
return WarningCircleFilled32;
|
|
88
88
|
case 'error':
|
|
89
|
-
return
|
|
89
|
+
return CrossCircleFilled32;
|
|
90
90
|
case 'info':
|
|
91
91
|
default:
|
|
92
|
-
return
|
|
92
|
+
return InfoCircleFilled32;
|
|
93
93
|
}
|
|
94
94
|
});
|
|
95
95
|
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
|
+
import { action } from 'storybook/actions';
|
|
3
|
+
|
|
4
|
+
import Mtabs from './MTabs.vue';
|
|
5
|
+
import ChevronRight24 from '@mozaic-ds/icons-vue/src/components/ChevronRight24/ChevronRight24.vue';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Mtabs> = {
|
|
8
|
+
title: 'Navigation/Tabs',
|
|
9
|
+
component: Mtabs,
|
|
10
|
+
parameters: {
|
|
11
|
+
docs: {
|
|
12
|
+
description: {
|
|
13
|
+
component:
|
|
14
|
+
'Tabs are a navigation component that allows users to switch between different sections within the same context. They help organize content efficiently by displaying only one section at a time, reducing clutter and improving accessibility. Tabs can include icons, labels, and notification badges to provide additional context. They are commonly used in dashboards, product management, and settings interfaces.',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
args: {
|
|
19
|
+
tabs: [
|
|
20
|
+
{
|
|
21
|
+
label: 'Label',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
label: 'Label',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
label: 'Label',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
label: 'Label',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
render: (args) => ({
|
|
35
|
+
components: { Mtabs, ChevronRight24 },
|
|
36
|
+
setup() {
|
|
37
|
+
const handleUpdate = action('update:modelValue');
|
|
38
|
+
|
|
39
|
+
return { args, handleUpdate };
|
|
40
|
+
},
|
|
41
|
+
template: `
|
|
42
|
+
<Mtabs
|
|
43
|
+
v-bind="args"
|
|
44
|
+
@update:modelValue="handleUpdate"
|
|
45
|
+
></Mtabs>
|
|
46
|
+
`,
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
49
|
+
export default meta;
|
|
50
|
+
type Story = StoryObj<typeof Mtabs>;
|
|
51
|
+
|
|
52
|
+
export const Default: Story = {};
|
|
53
|
+
|
|
54
|
+
export const Icons: Story = {
|
|
55
|
+
args: {
|
|
56
|
+
tabs: [
|
|
57
|
+
{
|
|
58
|
+
label: 'Label',
|
|
59
|
+
icon: ChevronRight24,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
label: 'Label',
|
|
63
|
+
icon: ChevronRight24,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
label: 'Label',
|
|
67
|
+
icon: ChevronRight24,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
label: 'Label',
|
|
71
|
+
icon: ChevronRight24,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const Centered: Story = {
|
|
78
|
+
args: { centered: true },
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const NoDivider: Story = {
|
|
82
|
+
args: { divider: false },
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const Disabled: Story = {
|
|
86
|
+
args: {
|
|
87
|
+
tabs: [
|
|
88
|
+
{
|
|
89
|
+
label: 'Label',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
label: 'Label',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
label: 'Label',
|
|
96
|
+
disabled: true,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
label: 'Label',
|
|
100
|
+
disabled: true,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
};
|