@mozaic-ds/vue 1.0.0-rc.3 → 2.2.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/LICENSE +51 -0
- package/README.md +76 -77
- package/dist/mozaic-vue.css +1 -1
- package/dist/mozaic-vue.d.ts +1718 -0
- package/dist/mozaic-vue.js +1971 -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 +78 -51
- package/src/components/Contributing.mdx +118 -0
- package/src/components/GettingStarted.mdx +45 -0
- package/src/components/Introduction.mdx +100 -0
- package/src/components/Support.mdx +18 -0
- package/src/components/breadcrumb/MBreadcrumb.spec.ts +105 -0
- package/src/components/breadcrumb/MBreadcrumb.stories.ts +82 -0
- package/src/components/breadcrumb/MBreadcrumb.vue +52 -55
- package/src/components/button/MButton.spec.ts +191 -0
- package/src/components/button/MButton.stories.ts +59 -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/datepicker/MDatepicker.spec.ts +95 -0
- package/src/components/datepicker/MDatepicker.stories.ts +75 -0
- package/src/components/datepicker/MDatepicker.vue +114 -0
- 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.spec.ts +166 -0
- package/src/components/field/MField.stories.ts +369 -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 +416 -0
- package/src/components/fieldgroup/MFieldGroup.vue +79 -0
- package/src/components/flag/MFlag.spec.ts +46 -0
- package/src/components/flag/MFlag.stories.ts +46 -0
- package/src/components/flag/MFlag.vue +28 -39
- package/src/components/iconbutton/MIconButton.spec.ts +108 -0
- package/src/components/iconbutton/MIconButton.stories.ts +74 -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 +89 -0
- package/src/components/link/MLink.vue +86 -120
- package/src/components/loader/MLoader.spec.ts +104 -0
- package/src/components/loader/MLoader.stories.ts +43 -0
- package/src/components/loader/MLoader.vue +66 -55
- 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 +111 -159
- package/src/components/numberbadge/MNumberBadge.spec.ts +56 -0
- package/src/components/numberbadge/MNumberBadge.stories.ts +48 -0
- package/src/components/numberbadge/MNumberBadge.vue +45 -0
- package/src/components/overlay/MOverlay.spec.ts +51 -0
- package/src/components/overlay/MOverlay.stories.ts +35 -0
- package/src/components/overlay/MOverlay.vue +27 -19
- package/src/components/pagination/MPagination.spec.ts +123 -0
- package/src/components/pagination/MPagination.stories.ts +83 -0
- package/src/components/pagination/MPagination.vue +120 -140
- package/src/components/passwordinput/MPasswordInput.spec.ts +104 -0
- package/src/components/passwordinput/MPasswordInput.stories.ts +75 -0
- package/src/components/passwordinput/MPasswordInput.vue +126 -77
- package/src/components/pincode/MPincode.spec.ts +126 -0
- package/src/components/pincode/MPincode.stories.ts +68 -0
- package/src/components/pincode/MPincode.vue +148 -0
- package/src/components/quantityselector/MQuantitySelector.spec.ts +262 -0
- package/src/components/quantityselector/MQuantitySelector.stories.ts +89 -0
- package/src/components/quantityselector/MQuantitySelector.vue +159 -148
- 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/MstatusBadge.spec.ts +16 -0
- package/src/components/statusdot/MStatusDot.spec.ts +51 -0
- package/src/components/statusdot/MStatusDot.stories.ts +48 -0
- package/src/components/statusdot/MStatusDot.vue +36 -0
- package/src/components/statusnotification/MStatusNotification.spec.ts +103 -0
- package/src/components/statusnotification/MStatusNotification.stories.ts +89 -0
- package/src/components/statusnotification/MStatusNotification.vue +106 -0
- 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.spec.ts +112 -0
- package/src/components/textarea/MTextArea.stories.ts +67 -0
- package/src/components/textarea/MTextArea.vue +81 -43
- package/src/components/textinput/MTextInput.spec.ts +121 -0
- package/src/components/textinput/MTextInput.stories.ts +107 -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/togglegroup/MToggleGroup.spec.ts +78 -0
- package/src/components/togglegroup/MToggleGroup.stories.ts +61 -0
- package/src/components/togglegroup/MToggleGroup.vue +97 -0
- package/src/components/tooltip/MTooltip.spec.ts +47 -0
- package/src/components/tooltip/MTooltip.stories.ts +59 -0
- package/src/components/tooltip/MTooltip.vue +49 -32
- package/src/components/usingIcons.mdx +35 -0
- package/src/components/usingPresets.mdx +128 -0
- package/src/main.ts +33 -0
- package/dist/demo.html +0 -1
- package/dist/mozaic-vue.adeo.css +0 -47
- package/dist/mozaic-vue.adeo.umd.js +0 -31341
- package/dist/mozaic-vue.common.js +0 -31331
- package/dist/mozaic-vue.common.js.map +0 -1
- package/dist/mozaic-vue.umd.js +0 -31342
- 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 -380
- package/src/components/autocomplete/index.js +0 -7
- package/src/components/badge/MBadge.vue +0 -43
- package/src/components/badge/index.js +0 -7
- 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 -163
- 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/dropdown/MDropdown.vue +0 -317
- package/src/components/dropdown/index.js +0 -7
- 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/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 -136
- package/src/components/icon/index.js +0 -7
- package/src/components/index.js +0 -44
- 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 -146
- package/src/components/listbox/MListBoxActions.vue +0 -251
- package/src/components/listbox/index.js +0 -12
- package/src/components/loader/index.js +0 -7
- 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/index.js +0 -7
- package/src/components/passwordinput/index.js +0 -7
- package/src/components/phonenumber/MPhoneNumber.vue +0 -398
- 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 -119
- 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 -111
- package/src/components/stepper/index.js +0 -7
- package/src/components/tabs/MTab.vue +0 -204
- package/src/components/tabs/index.js +0 -7
- package/src/components/tags/MTag.vue +0 -175
- 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/index.js +0 -7
- package/src/index.js +0 -63
- package/src/shims-tsx.d.ts +0 -13
- package/src/shims.vue.d.ts +0 -4
- package/src/tokens/adeo/android/colors.xml +0 -452
- package/src/tokens/adeo/android/font_dimens.xml +0 -18
- package/src/tokens/adeo/css/_variables.scss +0 -446
- package/src/tokens/adeo/css/root.scss +0 -448
- package/src/tokens/adeo/ios/StyleDictionaryColor.h +0 -460
- package/src/tokens/adeo/ios/StyleDictionaryColor.m +0 -472
- package/src/tokens/adeo/ios/StyleDictionaryColor.swift +0 -455
- 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 -544
- package/src/tokens/adeo/js/tokensObject.js +0 -11733
- package/src/tokens/adeo/scss/_tokens.scss +0 -1522
- package/src/utils/mozaicClasses.js +0 -16
- package/src/utils/theme.validator.js +0 -19
- package/types/index.d.ts +0 -104
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import MPasswordInput from './MPasswordInput.vue';
|
|
4
|
+
|
|
5
|
+
describe('MPasswordInput.vue', () => {
|
|
6
|
+
it('renders correctly with default props', () => {
|
|
7
|
+
const wrapper = mount(MPasswordInput, {
|
|
8
|
+
props: {
|
|
9
|
+
id: 'password-input',
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const input = wrapper.find('input');
|
|
14
|
+
const toggleButton = wrapper.find('button[role="switch"]');
|
|
15
|
+
|
|
16
|
+
expect(input.exists()).toBe(true);
|
|
17
|
+
expect(input.attributes('type')).toBe('password');
|
|
18
|
+
|
|
19
|
+
expect(toggleButton.exists()).toBe(true);
|
|
20
|
+
expect(toggleButton.text()).toBe('Show');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('toggles password visibility on button click', async () => {
|
|
24
|
+
const wrapper = mount(MPasswordInput, {
|
|
25
|
+
props: {
|
|
26
|
+
id: 'password-input',
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const toggleButton = wrapper.find('button[role="switch"]');
|
|
31
|
+
const input = wrapper.find('input');
|
|
32
|
+
|
|
33
|
+
expect(input.attributes('type')).toBe('password');
|
|
34
|
+
|
|
35
|
+
await toggleButton.trigger('click');
|
|
36
|
+
|
|
37
|
+
expect(input.attributes('type')).toBe('text');
|
|
38
|
+
expect(toggleButton.text()).toBe('Hide');
|
|
39
|
+
|
|
40
|
+
await toggleButton.trigger('click');
|
|
41
|
+
|
|
42
|
+
expect(input.attributes('type')).toBe('password');
|
|
43
|
+
expect(toggleButton.text()).toBe('Show');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should update modelValue when input value changes', async () => {
|
|
47
|
+
const wrapper = mount(MPasswordInput, {
|
|
48
|
+
props: {
|
|
49
|
+
id: 'password-input',
|
|
50
|
+
modelValue: '',
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const input = wrapper.find('input');
|
|
55
|
+
await input.setValue('newPassword123');
|
|
56
|
+
|
|
57
|
+
expect(wrapper.emitted()['update:modelValue']).toBeTruthy();
|
|
58
|
+
expect(wrapper.emitted()['update:modelValue'][0]).toEqual([
|
|
59
|
+
'newPassword123',
|
|
60
|
+
]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('clears the input when the clear button is clicked', async () => {
|
|
64
|
+
const wrapper = mount(MPasswordInput, {
|
|
65
|
+
props: {
|
|
66
|
+
id: 'password-input',
|
|
67
|
+
modelValue: 'somePassword',
|
|
68
|
+
isClearable: true,
|
|
69
|
+
clearLabel: 'Clear content',
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const clearButton = wrapper.find('.mc-controls-options__button');
|
|
74
|
+
|
|
75
|
+
await clearButton.trigger('click');
|
|
76
|
+
|
|
77
|
+
expect(wrapper.emitted()['update:modelValue']).toBeTruthy();
|
|
78
|
+
expect(wrapper.emitted()['update:modelValue'][0]).toEqual(['']);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('disables the input when disabled prop is passed', () => {
|
|
82
|
+
const wrapper = mount(MPasswordInput, {
|
|
83
|
+
props: {
|
|
84
|
+
id: 'password-input',
|
|
85
|
+
disabled: true,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const input = wrapper.find('input');
|
|
90
|
+
|
|
91
|
+
expect(input.attributes('disabled')).toBeDefined();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('applies invalid state when isInvalid prop is true', () => {
|
|
95
|
+
const wrapper = mount(MPasswordInput, {
|
|
96
|
+
props: {
|
|
97
|
+
id: 'password-input',
|
|
98
|
+
isInvalid: true,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
expect(wrapper.classes()).toContain('is-invalid');
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
|
+
import { action } from 'storybook/actions';
|
|
3
|
+
|
|
4
|
+
import MPasswordInput from './MPasswordInput.vue';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof MPasswordInput> = {
|
|
7
|
+
title: 'Form Elements/PasswordInput',
|
|
8
|
+
component: MPasswordInput,
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component:
|
|
13
|
+
'A password input is a specialized input field used to securely enter and manage passwords. It typically masks the characters entered to protect sensitive information from being seen. It includes a toggle button to show or hide the password, improving usability while maintaining security. Password inputs are commonly used in login forms, account creation, and authentication flows.<br><br> To put a label, requierement text, help text or to apply a valid or invalid message, the examples are available in the [Field section](/docs/form-elements-field--docs#input).',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
args: {
|
|
18
|
+
id: 'passwordInputId',
|
|
19
|
+
placeholder: 'Enter your password',
|
|
20
|
+
},
|
|
21
|
+
render: (args) => ({
|
|
22
|
+
components: { MPasswordInput },
|
|
23
|
+
setup() {
|
|
24
|
+
const handleUpdate = action('update:modelValue');
|
|
25
|
+
|
|
26
|
+
return { args, handleUpdate };
|
|
27
|
+
},
|
|
28
|
+
template: `
|
|
29
|
+
<MPasswordInput
|
|
30
|
+
v-bind="args"
|
|
31
|
+
@update:modelValue="handleUpdate"
|
|
32
|
+
/>
|
|
33
|
+
`,
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
export default meta;
|
|
37
|
+
type Story = StoryObj<typeof MPasswordInput>;
|
|
38
|
+
|
|
39
|
+
export const WithValue: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
id: 'withValueId',
|
|
42
|
+
modelValue: 'Magic-word-123',
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const Default: Story = {};
|
|
47
|
+
|
|
48
|
+
export const Clearable: Story = {
|
|
49
|
+
args: {
|
|
50
|
+
id: 'clearableId',
|
|
51
|
+
modelValue: 'Magic-word-123',
|
|
52
|
+
isClearable: true,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const Disabled: Story = {
|
|
57
|
+
args: {
|
|
58
|
+
id: 'disableId',
|
|
59
|
+
disabled: true,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const ReadOnly: Story = {
|
|
64
|
+
args: {
|
|
65
|
+
id: 'readonlyId',
|
|
66
|
+
readonly: true,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const Invalid: Story = {
|
|
71
|
+
args: {
|
|
72
|
+
id: 'invalidId',
|
|
73
|
+
isInvalid: true,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
@@ -1,97 +1,146 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="mc-password-input" :class="
|
|
2
|
+
<div class="mc-password-input mc-text-input" :class="classObject">
|
|
3
3
|
<input
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
:
|
|
4
|
+
class="mc-password-input__control mc-text-input__control"
|
|
5
|
+
v-model="modelValue"
|
|
6
|
+
:id="id"
|
|
7
|
+
:type="inputType"
|
|
8
|
+
:name="name"
|
|
9
|
+
:placeholder="placeholder"
|
|
10
|
+
:disabled="disabled"
|
|
8
11
|
:aria-invalid="isInvalid"
|
|
9
|
-
|
|
12
|
+
:readonly="readonly"
|
|
13
|
+
v-bind="$attrs"
|
|
14
|
+
@input="
|
|
15
|
+
emit('update:modelValue', ($event.target as HTMLInputElement).value)
|
|
16
|
+
"
|
|
10
17
|
/>
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
<div v-if="isClearable && modelValue" class="mc-controls-options">
|
|
19
|
+
<button class="mc-controls-options__button" @click="clearValue">
|
|
20
|
+
<CrossCircleFilled24
|
|
21
|
+
class="mc-controls-options__icon"
|
|
22
|
+
aria-hidden="true"
|
|
23
|
+
/>
|
|
24
|
+
<span class="mc-controls-options__label">{{ clearLabel }}</span>
|
|
25
|
+
</button>
|
|
26
|
+
</div>
|
|
27
|
+
<MButton
|
|
13
28
|
ref="button"
|
|
14
|
-
type="button"
|
|
15
|
-
class="mc-password-input__button"
|
|
16
29
|
role="switch"
|
|
17
|
-
:aria-
|
|
18
|
-
|
|
30
|
+
:aria-checked="ariaChecked"
|
|
31
|
+
:disabled="disabled"
|
|
32
|
+
@click="toggleVisibility"
|
|
33
|
+
size="s"
|
|
34
|
+
ghost
|
|
19
35
|
>
|
|
20
|
-
{{
|
|
21
|
-
</
|
|
36
|
+
{{ isVisible ? buttonLabel.hide : buttonLabel.show }}
|
|
37
|
+
</MButton>
|
|
22
38
|
</div>
|
|
23
39
|
</template>
|
|
24
40
|
|
|
25
|
-
<script>
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
<script setup lang="ts">
|
|
42
|
+
import { computed, ref } from 'vue';
|
|
43
|
+
import CrossCircleFilled24 from '@mozaic-ds/icons-vue/src/components/CrossCircleFilled24/CrossCircleFilled24.vue';
|
|
44
|
+
import MButton from '../button/MButton.vue';
|
|
28
45
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
/**
|
|
47
|
+
* A password input is a specialized input field used to securely enter and manage passwords. It typically masks the characters entered to protect sensitive information from being seen. It includes a toggle button to show or hide the password, improving usability while maintaining security. Password inputs are commonly used in login forms, account creation, and authentication flows.
|
|
48
|
+
*/
|
|
49
|
+
const props = withDefaults(
|
|
50
|
+
defineProps<{
|
|
51
|
+
/**
|
|
52
|
+
* A unique identifier for the password input element, used to associate the label with the form element.
|
|
53
|
+
*/
|
|
54
|
+
id: string;
|
|
55
|
+
/**
|
|
56
|
+
* The name attribute for the password input element, typically used for form submission.
|
|
57
|
+
*/
|
|
58
|
+
name?: string;
|
|
59
|
+
/**
|
|
60
|
+
* The current value of the password input field.
|
|
61
|
+
*/
|
|
62
|
+
modelValue?: string | number;
|
|
63
|
+
/**
|
|
64
|
+
* A placeholder text to show in the password input when it is empty.
|
|
65
|
+
*/
|
|
66
|
+
placeholder?: string;
|
|
67
|
+
/**
|
|
68
|
+
* If `true`, applies an invalid state to the password input.
|
|
69
|
+
*/
|
|
70
|
+
isInvalid?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* If `true`, disables the password input, making it non-interactive.
|
|
73
|
+
*/
|
|
74
|
+
disabled?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* If `true`, the password input is read-only (cannot be edited).
|
|
77
|
+
*/
|
|
78
|
+
readonly?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* If `true`, a clear button will appear when the password input has a value.
|
|
81
|
+
*/
|
|
82
|
+
isClearable?: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* The label text for the clear button
|
|
85
|
+
*/
|
|
86
|
+
clearLabel?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Labels of the button displayed when showing or hiding the password
|
|
89
|
+
*/
|
|
90
|
+
buttonLabel?: {
|
|
91
|
+
show: string;
|
|
92
|
+
hide: string;
|
|
93
|
+
};
|
|
94
|
+
}>(),
|
|
95
|
+
{
|
|
96
|
+
clearLabel: 'Clear content',
|
|
97
|
+
buttonLabel: () => ({ show: 'Show', hide: 'Hide' }),
|
|
33
98
|
},
|
|
99
|
+
);
|
|
34
100
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
default: false,
|
|
39
|
-
},
|
|
40
|
-
value: {
|
|
41
|
-
type: [String, Number],
|
|
42
|
-
default: null,
|
|
43
|
-
},
|
|
44
|
-
isValid: {
|
|
45
|
-
type: Boolean,
|
|
46
|
-
default: false,
|
|
47
|
-
},
|
|
48
|
-
isInvalid: {
|
|
49
|
-
type: Boolean,
|
|
50
|
-
default: false,
|
|
51
|
-
},
|
|
52
|
-
buttonLabel: {
|
|
53
|
-
type: Object,
|
|
54
|
-
default: function () {
|
|
55
|
-
return { show: 'Show', hide: 'Hide' };
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
},
|
|
101
|
+
const classObject = computed(() => ({
|
|
102
|
+
'is-invalid': props.isInvalid,
|
|
103
|
+
}));
|
|
59
104
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
};
|
|
64
|
-
},
|
|
105
|
+
// Local state management
|
|
106
|
+
const modelValue = ref(props.modelValue);
|
|
107
|
+
const isVisible = ref(false);
|
|
65
108
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
'is-invalid': this.isInvalid,
|
|
74
|
-
};
|
|
75
|
-
},
|
|
76
|
-
setAriaPressed() {
|
|
77
|
-
return this.isVisible ? 'true' : 'false';
|
|
78
|
-
},
|
|
79
|
-
setButtonLabel() {
|
|
80
|
-
return this.isVisible ? this.buttonLabel.hide : this.buttonLabel.show;
|
|
81
|
-
},
|
|
82
|
-
},
|
|
109
|
+
/**
|
|
110
|
+
* Clear the input value.
|
|
111
|
+
*/
|
|
112
|
+
const clearValue = () => {
|
|
113
|
+
modelValue.value = '';
|
|
114
|
+
emit('update:modelValue', '');
|
|
115
|
+
};
|
|
83
116
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
},
|
|
117
|
+
/**
|
|
118
|
+
* Toggle the visibility of the password.
|
|
119
|
+
*/
|
|
120
|
+
const toggleVisibility = () => {
|
|
121
|
+
isVisible.value = !isVisible.value;
|
|
90
122
|
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Compute the input type based on visibility state.
|
|
126
|
+
*/
|
|
127
|
+
const inputType = computed(() => (isVisible.value ? 'text' : 'password'));
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Aria attributes for accessibility.
|
|
131
|
+
*/
|
|
132
|
+
const ariaChecked = computed(() => (isVisible.value ? 'true' : 'false'));
|
|
133
|
+
|
|
134
|
+
const emit = defineEmits<{
|
|
135
|
+
/**
|
|
136
|
+
* Emits when the input value changes, updating the `modelValue` prop.
|
|
137
|
+
*/
|
|
138
|
+
(on: 'update:modelValue', value: string | number): void;
|
|
139
|
+
}>();
|
|
91
140
|
</script>
|
|
92
141
|
|
|
93
|
-
<style lang="scss">
|
|
94
|
-
@
|
|
95
|
-
@
|
|
96
|
-
@
|
|
142
|
+
<style lang="scss" scoped>
|
|
143
|
+
@use '@mozaic-ds/styles/components/controls-options';
|
|
144
|
+
@use '@mozaic-ds/styles/components/text-input';
|
|
145
|
+
@use '@mozaic-ds/styles/components/password-input';
|
|
97
146
|
</style>
|
|
@@ -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
|
+
};
|