@mozaic-ds/vue 2.18.0 → 2.19.1
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 +961 -2085
- package/dist/mozaic-vue.js +1253 -1143
- package/dist/mozaic-vue.js.map +1 -1
- package/dist/mozaic-vue.umd.cjs +6 -6
- package/dist/mozaic-vue.umd.cjs.map +1 -1
- package/package.json +8 -6
- package/src/components/BrandPresets.mdx +20 -2
- package/src/components/accordionlist/MAccordionList.figma.ts +16 -16
- package/src/components/accordionlist/MAccordionList.stories.ts +1 -1
- package/src/components/accordionlistitem/MAccordionListItem.figma.ts +9 -5
- package/src/components/accordionlistitem/MAccordionListItem.vue +4 -1
- package/src/components/accordionlistitem/README.md +2 -0
- package/src/components/actionbottombar/MActionBottomBar.figma.ts +7 -7
- package/src/components/actionlistbox/MActionListbox.figma.ts +11 -11
- package/src/components/actionlistbox/MActionListbox.spec.ts +113 -0
- package/src/components/actionlistbox/MActionListbox.vue +63 -5
- package/src/components/avatar/MAvatar.figma.ts +5 -5
- package/src/components/breadcrumb/MBreadcrumb.figma.ts +7 -7
- package/src/components/breadcrumb/MBreadcrumb.vue +1 -1
- package/src/components/builtinmenu/MBuiltInMenu.figma.ts +8 -5
- package/src/components/builtinmenu/MBuiltInMenu.spec.ts +3 -1
- package/src/components/button/MButton.figma.ts +21 -6
- package/src/components/button/MButton.spec.ts +26 -0
- package/src/components/button/MButton.vue +2 -0
- package/src/components/callout/MCallout.figma.ts +7 -7
- package/src/components/callout/MCallout.stories.ts +0 -3
- package/src/components/callout/MCallout.vue +4 -3
- package/src/components/callout/README.md +2 -2
- package/src/components/carousel/MCarousel.figma.ts +10 -10
- package/src/components/carousel/MCarousel.spec.ts +26 -2
- package/src/components/carousel/MCarousel.vue +10 -4
- package/src/components/checkbox/MCheckbox.figma.ts +10 -10
- package/src/components/checkboxgroup/MCheckboxGroup.figma.ts +7 -7
- package/src/components/checklistmenu/MCheckListMenu.figma.ts +8 -8
- package/src/components/circularprogressbar/MCircularProgressbar.figma.ts +7 -3
- package/src/components/combobox/MCombobox.figma.ts +10 -10
- package/src/components/combobox/MCombobox.vue +7 -0
- package/src/components/container/MContainer.figma.ts +5 -5
- package/src/components/datatable/DataTable.stories.ts +33 -7
- package/src/components/datatable/DataTableCells.stories.ts +2 -2
- package/src/components/datatable/DataTableEmpty.stories.ts +2 -2
- package/src/components/datatable/DataTableExpandable.stories.ts +2 -2
- package/src/components/datatable/DataTableNested.stories.ts +1 -1
- package/src/components/datatable/DataTableSelectable.stories.ts +2 -3
- package/src/components/datatable/DataTableSortable.stories.ts +1 -1
- package/src/components/datepicker/MDatepicker.figma.ts +3 -3
- package/src/components/divider/MDivider.figma.ts +3 -3
- package/src/components/drawer/MDrawer.figma.ts +13 -13
- package/src/components/drawer/MDrawer.spec.ts +102 -3
- package/src/components/drawer/MDrawer.vue +73 -14
- package/src/components/field/MField.figma.ts +9 -5
- package/src/components/field/MField.vue +1 -0
- package/src/components/fileuploader/MFileUploader.figma.ts +3 -3
- package/src/components/fileuploader/MFileUploader.vue +2 -2
- package/src/components/fileuploaderitem/MFileUploaderItem.figma.ts +7 -3
- package/src/components/fileuploaderitem/MFileUploaderItem.vue +2 -7
- package/src/components/flag/MFlag.figma.ts +3 -3
- package/src/components/iconbutton/MIconButton.figma.ts +16 -16
- package/src/components/iconbutton/MIconButton.spec.ts +15 -0
- package/src/components/iconbutton/MIconButton.vue +1 -0
- package/src/components/kpiitem/MKpiItem.figma.ts +8 -3
- package/src/components/kpiitem/MKpiItem.spec.ts +12 -0
- package/src/components/kpiitem/MKpiItem.vue +7 -1
- package/src/components/linearprogressbarbuffer/MLinearProgressbarBuffer.figma.ts +6 -3
- package/src/components/linearprogressbarpercentage/MLinearProgressbarPercentage.figma.ts +5 -3
- package/src/components/link/MLink.figma.ts +5 -5
- package/src/components/loader/MLoader.figma.ts +3 -3
- package/src/components/loadingoverlay/MLoadingOverlay.figma.ts +3 -3
- package/src/components/modal/MModal.figma.ts +12 -12
- package/src/components/modal/MModal.spec.ts +115 -3
- package/src/components/modal/MModal.vue +91 -11
- package/src/components/modal/README.md +1 -1
- package/src/components/navigationindicator/MNavigationIndicator.figma.ts +7 -3
- package/src/components/numberbadge/MNumberBadge.figma.ts +7 -3
- package/src/components/optionListbox/MOptionListbox.figma.ts +10 -10
- package/src/components/overlay/MOverlay.figma.ts +5 -5
- package/src/components/overlay/MOverlay.spec.ts +1 -1
- package/src/components/overlay/MOverlay.vue +1 -1
- package/src/components/pageheader/MPageHeader.figma.ts +3 -3
- package/src/components/pagination/MPagination.figma.ts +10 -10
- package/src/components/passwordinput/MPasswordInput.figma.ts +9 -9
- package/src/components/phonenumber/MPhoneNumber.figma.ts +9 -9
- package/src/components/phonenumber/MPhoneNumber.spec.ts +6 -2
- package/src/components/phonenumber/MPhoneNumber.vue +21 -15
- package/src/components/pincode/MPincode.figma.ts +9 -9
- package/src/components/popover/MPopover.figma.ts +15 -15
- package/src/components/quantityselector/MQuantitySelector.figma.ts +12 -12
- package/src/components/radio/MRadio.figma.ts +9 -9
- package/src/components/radiogroup/MRadioGroup.figma.ts +7 -7
- package/src/components/segmentedcontrol/MSegmentedControl.figma.ts +8 -8
- package/src/components/select/MSelect.figma.ts +11 -11
- package/src/components/sidebar/MSidebar.figma.ts +8 -8
- package/src/components/sidebar/MSidebar.vue +1 -0
- package/src/components/sidebarexpandableitem/MSidebarExpandableItem.figma.ts +8 -5
- package/src/components/sidebarexpandableitem/MSidebarExpandableItem.spec.ts +12 -0
- package/src/components/sidebarexpandableitem/MSidebarExpandableItem.vue +1 -0
- package/src/components/sidebarfooter/MSidebarFooter.figma.ts +7 -3
- package/src/components/sidebarheader/MSidebarHeader.figma.ts +3 -3
- package/src/components/sidebarnavitem/MSidebarNavItem.figma.ts +3 -3
- package/src/components/sidebarshortcutitem/MSidebarShortcutItem.figma.ts +3 -3
- package/src/components/starrating/MStarRating.figma.ts +7 -7
- package/src/components/statusbadge/MStatusBadge.figma.ts +3 -3
- package/src/components/statusdot/MStatusDot.figma.ts +3 -3
- package/src/components/statusmessage/MStatusMessage.figma.ts +3 -3
- package/src/components/statusnotification/MStatusNotification.figma.ts +7 -7
- package/src/components/stepperbottombar/MStepperBottomBar.figma.ts +3 -3
- package/src/components/steppercompact/MStepperCompact.figma.ts +8 -3
- package/src/components/steppercompact/MStepperCompact.spec.ts +9 -0
- package/src/components/steppercompact/MStepperCompact.vue +1 -1
- package/src/components/stepperinline/MStepperInline.figma.ts +6 -3
- package/src/components/stepperinline/MStepperInline.spec.ts +11 -0
- package/src/components/stepperinline/MStepperInline.stories.ts +5 -1
- package/src/components/stepperinline/MStepperInline.vue +1 -1
- package/src/components/stepperstacked/MStepperStacked.figma.ts +6 -3
- package/src/components/stepperstacked/MStepperStacked.spec.ts +13 -0
- package/src/components/stepperstacked/MStepperStacked.vue +1 -0
- package/src/components/tabs/MTabs.figma.ts +8 -8
- package/src/components/tag/MTag.figma.ts +3 -3
- package/src/components/textarea/MTextArea.figma.ts +8 -8
- package/src/components/textinput/MTextInput.figma.ts +12 -12
- package/src/components/textinput/MTextInput.vue +2 -2
- package/src/components/textinput/README.md +1 -1
- package/src/components/tile/MTile.figma.ts +5 -5
- package/src/components/tileclickable/MTileClickable.figma.ts +6 -6
- package/src/components/tileexpandable/MTileExpandable.figma.ts +6 -6
- package/src/components/tileselectable/MTileSelectable.figma.ts +9 -5
- package/src/components/toaster/MToaster.figma.ts +3 -3
- package/src/components/toggle/MToggle.figma.ts +9 -9
- package/src/components/toggle/MToggle.vue +1 -1
- package/src/components/togglegroup/MToggleGroup.figma.ts +7 -7
- package/src/components/tooltip/MTooltip.figma.ts +10 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mozaic-ds/vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.19.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Mozaic-Vue is the Vue.js implementation of ADEO Design system",
|
|
6
6
|
"author": "ADEO - ADEO Design system",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"*.d.ts"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@mozaic-ds/styles": "^2.
|
|
46
|
+
"@mozaic-ds/styles": "^2.23.0",
|
|
47
47
|
"@mozaic-ds/web-fonts": "^1.65.0",
|
|
48
48
|
"postcss-scss": "^4.0.9"
|
|
49
49
|
},
|
|
@@ -51,9 +51,10 @@
|
|
|
51
51
|
"vue": "^3.5.13"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@commitlint/cli": "^
|
|
55
|
-
"@commitlint/config-conventional": "^
|
|
54
|
+
"@commitlint/cli": "^21.0.1",
|
|
55
|
+
"@commitlint/config-conventional": "^21.0.1",
|
|
56
56
|
"@figma/code-connect": "^1.4.1",
|
|
57
|
+
"@microsoft/api-extractor": "^7.58.7",
|
|
57
58
|
"@mozaic-ds/css-dev-tools": "1.75.0",
|
|
58
59
|
"@mozaic-ds/datatable-vue": "^1.0.0",
|
|
59
60
|
"@mozaic-ds/icons-vue": "^2.5.0",
|
|
@@ -68,6 +69,7 @@
|
|
|
68
69
|
"@vitest/eslint-plugin": "^1.1.38",
|
|
69
70
|
"@vue/eslint-config-prettier": "^10.2.0",
|
|
70
71
|
"@vue/eslint-config-typescript": "^14.5.0",
|
|
72
|
+
"@vue/language-core": "^3.3.2",
|
|
71
73
|
"@vue/test-utils": "^2.4.6",
|
|
72
74
|
"dotenv-cli": "^11.0.0",
|
|
73
75
|
"eslint": "^10.0.2",
|
|
@@ -78,7 +80,7 @@
|
|
|
78
80
|
"husky": "^9.1.7",
|
|
79
81
|
"jsdom": "^29.0.0",
|
|
80
82
|
"libphonenumber-js": "^1.12.23",
|
|
81
|
-
"lint-staged": "^
|
|
83
|
+
"lint-staged": "^17.0.5",
|
|
82
84
|
"mdx-mermaid": "^2.0.3",
|
|
83
85
|
"mermaid": "^11.5.0",
|
|
84
86
|
"npm-run-all": "^4.1.5",
|
|
@@ -88,8 +90,8 @@
|
|
|
88
90
|
"storybook": "^10.0.4",
|
|
89
91
|
"storybook-addon-tag-badges": "^3.0.2",
|
|
90
92
|
"typescript": "^6.0.3",
|
|
93
|
+
"unplugin-dts": "^1.0.1",
|
|
91
94
|
"vite": "^8.0.0",
|
|
92
|
-
"vite-plugin-dts": "^4.5.3",
|
|
93
95
|
"vitest": "^4.0.7",
|
|
94
96
|
"vue": "^3.5.13",
|
|
95
97
|
"vue-component-meta": "^3.0.8",
|
|
@@ -80,16 +80,34 @@ The table below summarises which font to use depending on the brand.
|
|
|
80
80
|
</tr>
|
|
81
81
|
</table>
|
|
82
82
|
|
|
83
|
+
For example, here is how to include the Roboto font in your HTML for the Adeo brand:
|
|
84
|
+
|
|
85
|
+
<Source
|
|
86
|
+
language="html"
|
|
87
|
+
dark
|
|
88
|
+
code={`
|
|
89
|
+
<head>
|
|
90
|
+
<meta charset="UTF-8" />
|
|
91
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
92
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
93
|
+
<link
|
|
94
|
+
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap"
|
|
95
|
+
rel="stylesheet"
|
|
96
|
+
/>
|
|
97
|
+
</head>
|
|
98
|
+
`}
|
|
99
|
+
/>
|
|
100
|
+
|
|
83
101
|
From there, we can update the main style sheet of your project, in order to import the right font.
|
|
84
102
|
|
|
85
103
|
<Source
|
|
86
104
|
language='css'
|
|
87
105
|
dark
|
|
88
106
|
code={`
|
|
89
|
-
@use
|
|
107
|
+
@use '@mozaic-ds/tokens/adeo/theme' as *;
|
|
90
108
|
|
|
91
109
|
body {
|
|
92
|
-
|
|
110
|
+
font-family: var(--font-family, 'Roboto', sans-serif);
|
|
93
111
|
}
|
|
94
112
|
`} />
|
|
95
113
|
|
|
@@ -22,22 +22,22 @@ figma.connect(
|
|
|
22
22
|
},
|
|
23
23
|
example: ({ appearance }) =>
|
|
24
24
|
html`<script setup>
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
import { MAccordionList, MAccordionListItem } from '@mozaic-ds/vue';
|
|
26
|
+
</script>
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
28
|
+
<MAccordionList appearance=${appearance}>
|
|
29
|
+
<MAccordionListItem id="1" title="Content title">
|
|
30
|
+
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</p>
|
|
31
|
+
</MAccordionListItem>
|
|
32
|
+
<MAccordionListItem id="2" title="Content title">
|
|
33
|
+
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</p>
|
|
34
|
+
</MAccordionListItem>
|
|
35
|
+
<MAccordionListItem id="3" title="Content title">
|
|
36
|
+
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</p>
|
|
37
|
+
</MAccordionListItem>
|
|
38
|
+
<MAccordionListItem id="4" title="Content title">
|
|
39
|
+
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</p>
|
|
40
|
+
</MAccordionListItem>
|
|
41
|
+
</MAccordionList>`,
|
|
42
42
|
},
|
|
43
43
|
);
|
|
@@ -51,7 +51,7 @@ const meta: Meta<typeof MAccordionList> = {
|
|
|
51
51
|
return { args, Wrench32, Project32, Sharpening32, handleUpdate };
|
|
52
52
|
},
|
|
53
53
|
template: `
|
|
54
|
-
<MAccordionList v-bind="args" @update:modelValue="handleUpdate">
|
|
54
|
+
<MAccordionList v-bind="args" v-model="args.modelValue" @update:modelValue="handleUpdate">
|
|
55
55
|
${args.default}
|
|
56
56
|
</MAccordionList>
|
|
57
57
|
`,
|
|
@@ -16,12 +16,16 @@ figma.connect(
|
|
|
16
16
|
},
|
|
17
17
|
example: ({ title, subtitle }) =>
|
|
18
18
|
html`<script setup>
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
import { MAccordionListItem } from '@mozaic-ds/vue';
|
|
20
|
+
</script>
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
<MAccordionListItem
|
|
23
|
+
id="accordion-1"
|
|
24
|
+
title=${title}
|
|
25
|
+
subtitle=${subtitle}
|
|
26
|
+
>
|
|
27
|
+
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</p>
|
|
28
|
+
</MAccordionListItem>`,
|
|
25
29
|
imports: ["import { MAccordionListItem } from '@mozaic-ds/vue'"],
|
|
26
30
|
},
|
|
27
31
|
);
|
|
@@ -23,7 +23,10 @@
|
|
|
23
23
|
</div>
|
|
24
24
|
<div
|
|
25
25
|
:id="`content-${id}`"
|
|
26
|
-
class="
|
|
26
|
+
:class="{
|
|
27
|
+
'mc-accordion__content': true,
|
|
28
|
+
'mc-accordion__content--open': open,
|
|
29
|
+
}"
|
|
27
30
|
:inert="!open || undefined"
|
|
28
31
|
:aria-labelledby="`accordion-${id}`"
|
|
29
32
|
role="region"
|
|
@@ -15,6 +15,8 @@ If no ID is provided, a unique one is generated automatically. | `string` | - |
|
|
|
15
15
|
| `subtitle` | An optional secondary heading displayed below the title. It provides additional context or detail about the content of the accordion item. | `string` | - |
|
|
16
16
|
| `content` | The main content of the accordion item. This is the information revealed when the accordion is expanded, typically containing text, HTML, or other elements. | `string` | - |
|
|
17
17
|
| `icon` | Icon component to display before the item title. | `Component` | - |
|
|
18
|
+
| `tag` | Heading level for the accordion trigger (h2–h6). Adjust to match the
|
|
19
|
+
heading hierarchy of the page where the accordion is used. | `"h2"` `"h1"` `"h3"` `"h4"` `"h5"` `"h6"` | `"h2"` |
|
|
18
20
|
|
|
19
21
|
## Slots
|
|
20
22
|
|
|
@@ -12,13 +12,13 @@ figma.connect(
|
|
|
12
12
|
},
|
|
13
13
|
example: ({ shadow }) =>
|
|
14
14
|
html`<script setup>
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
import { MActionBottomBar, MButton } from '@mozaic-ds/vue';
|
|
16
|
+
</script>
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
<MActionBottomBar shadow=${shadow}>
|
|
19
|
+
<template #right>
|
|
20
|
+
<MButton size="s" appearance="accent">Save</MButton>
|
|
21
|
+
</template>
|
|
22
|
+
</MActionBottomBar>`,
|
|
23
23
|
},
|
|
24
24
|
);
|
|
@@ -10,21 +10,21 @@ figma.connect(
|
|
|
10
10
|
props: {},
|
|
11
11
|
example: () =>
|
|
12
12
|
html`<script setup>
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
import { MActionListbox, MButton } from '@mozaic-ds/vue';
|
|
14
|
+
import { Copy20, Download20, Trash20 } from '@mozaic-ds/icons-vue';
|
|
15
|
+
</script>
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
<MActionListbox
|
|
18
|
+
title="Listbox title"
|
|
19
|
+
:items="[
|
|
20
20
|
{ id: '1', label: 'Duplicate', icon: Copy20 },
|
|
21
21
|
{ id: '2', label: 'Download', icon: Download20 },
|
|
22
22
|
{ id: '3', label: 'Delete', icon: Trash20, appearance: 'danger', divider: true },
|
|
23
23
|
]"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
>
|
|
25
|
+
<template #activator="{ id }">
|
|
26
|
+
<MButton :popovertarget="id">Open menu</MButton>
|
|
27
|
+
</template>
|
|
28
|
+
</MActionListbox>`,
|
|
29
29
|
},
|
|
30
30
|
);
|
|
@@ -135,4 +135,117 @@ describe('MActionListbox', () => {
|
|
|
135
135
|
await actions[1].trigger('click');
|
|
136
136
|
expect(wrapper.emitted('action')?.[1][0]).toBe('move');
|
|
137
137
|
});
|
|
138
|
+
|
|
139
|
+
it('has role="menu" on the list and role="menuitem" on each button', () => {
|
|
140
|
+
const wrapper = mountComponent();
|
|
141
|
+
expect(wrapper.find('ul.mc-action-list').attributes('role')).toBe('menu');
|
|
142
|
+
const menuItems = wrapper.findAll('button[role="menuitem"]');
|
|
143
|
+
expect(menuItems.length).toBe(items.length);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('keyboard navigation', () => {
|
|
147
|
+
function mountAttached(props = {}) {
|
|
148
|
+
const div = document.createElement('div');
|
|
149
|
+
document.body.appendChild(div);
|
|
150
|
+
const wrapper = mount(MActionListbox, {
|
|
151
|
+
props: { items, ...props },
|
|
152
|
+
attachTo: div,
|
|
153
|
+
global: { components: { MDivider, MIconButton, Cross24 } },
|
|
154
|
+
});
|
|
155
|
+
return wrapper;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
it('ArrowDown moves focus to the next item', async () => {
|
|
159
|
+
const wrapper = mountAttached();
|
|
160
|
+
const buttons = wrapper.findAll('button[role="menuitem"]');
|
|
161
|
+
await buttons[0].element.focus();
|
|
162
|
+
await wrapper
|
|
163
|
+
.find('ul[role="menu"]')
|
|
164
|
+
.trigger('keydown', { key: 'ArrowDown' });
|
|
165
|
+
expect(document.activeElement).toBe(buttons[1].element);
|
|
166
|
+
wrapper.unmount();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('ArrowDown wraps from last to first item', async () => {
|
|
170
|
+
const wrapper = mountAttached();
|
|
171
|
+
const buttons = wrapper.findAll('button[role="menuitem"]');
|
|
172
|
+
await buttons[buttons.length - 1].element.focus();
|
|
173
|
+
await wrapper
|
|
174
|
+
.find('ul[role="menu"]')
|
|
175
|
+
.trigger('keydown', { key: 'ArrowDown' });
|
|
176
|
+
expect(document.activeElement).toBe(buttons[0].element);
|
|
177
|
+
wrapper.unmount();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('ArrowUp moves focus to the previous item', async () => {
|
|
181
|
+
const wrapper = mountAttached();
|
|
182
|
+
const buttons = wrapper.findAll('button[role="menuitem"]');
|
|
183
|
+
await buttons[1].element.focus();
|
|
184
|
+
await wrapper
|
|
185
|
+
.find('ul[role="menu"]')
|
|
186
|
+
.trigger('keydown', { key: 'ArrowUp' });
|
|
187
|
+
expect(document.activeElement).toBe(buttons[0].element);
|
|
188
|
+
wrapper.unmount();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('ArrowUp wraps from first to last item', async () => {
|
|
192
|
+
const wrapper = mountAttached();
|
|
193
|
+
const buttons = wrapper.findAll('button[role="menuitem"]');
|
|
194
|
+
await buttons[0].element.focus();
|
|
195
|
+
await wrapper
|
|
196
|
+
.find('ul[role="menu"]')
|
|
197
|
+
.trigger('keydown', { key: 'ArrowUp' });
|
|
198
|
+
expect(document.activeElement).toBe(buttons[buttons.length - 1].element);
|
|
199
|
+
wrapper.unmount();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('Home moves focus to the first item', async () => {
|
|
203
|
+
const wrapper = mountAttached();
|
|
204
|
+
const buttons = wrapper.findAll('button[role="menuitem"]');
|
|
205
|
+
await buttons[2].element.focus();
|
|
206
|
+
await wrapper.find('ul[role="menu"]').trigger('keydown', { key: 'Home' });
|
|
207
|
+
expect(document.activeElement).toBe(buttons[0].element);
|
|
208
|
+
wrapper.unmount();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('End moves focus to the last item', async () => {
|
|
212
|
+
const wrapper = mountAttached();
|
|
213
|
+
const buttons = wrapper.findAll('button[role="menuitem"]');
|
|
214
|
+
await buttons[0].element.focus();
|
|
215
|
+
await wrapper.find('ul[role="menu"]').trigger('keydown', { key: 'End' });
|
|
216
|
+
expect(document.activeElement).toBe(buttons[buttons.length - 1].element);
|
|
217
|
+
wrapper.unmount();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('Escape emits "close"', async () => {
|
|
221
|
+
const wrapper = mountAttached();
|
|
222
|
+
await wrapper
|
|
223
|
+
.find('ul[role="menu"]')
|
|
224
|
+
.trigger('keydown', { key: 'Escape' });
|
|
225
|
+
expect(wrapper.emitted('close')).toBeTruthy();
|
|
226
|
+
wrapper.unmount();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('disabled items are skipped during ArrowDown navigation', async () => {
|
|
230
|
+
const itemsWithDisabled = [
|
|
231
|
+
{ label: 'First' },
|
|
232
|
+
{ label: 'Disabled', disabled: true },
|
|
233
|
+
{ label: 'Third' },
|
|
234
|
+
];
|
|
235
|
+
const wrapper = mount(MActionListbox, {
|
|
236
|
+
props: { items: itemsWithDisabled },
|
|
237
|
+
attachTo: document.body,
|
|
238
|
+
global: { components: { MDivider, MIconButton, Cross24 } },
|
|
239
|
+
});
|
|
240
|
+
const enabledButtons = wrapper.findAll(
|
|
241
|
+
'button[role="menuitem"]:not([disabled])',
|
|
242
|
+
);
|
|
243
|
+
await enabledButtons[0].element.focus();
|
|
244
|
+
await wrapper
|
|
245
|
+
.find('ul[role="menu"]')
|
|
246
|
+
.trigger('keydown', { key: 'ArrowDown' });
|
|
247
|
+
expect(document.activeElement).toBe(enabledButtons[1].element);
|
|
248
|
+
wrapper.unmount();
|
|
249
|
+
});
|
|
250
|
+
});
|
|
138
251
|
});
|
|
@@ -9,9 +9,12 @@
|
|
|
9
9
|
ref="popover"
|
|
10
10
|
class="mc-listbox__content"
|
|
11
11
|
v-bind="$slots.activator ? { id, popover: '' } : {}"
|
|
12
|
+
@toggle="onPopoverToggle"
|
|
12
13
|
>
|
|
13
14
|
<div class="mc-listbox__header">
|
|
14
|
-
<h3 v-if="title" class="mc-listbox__title">
|
|
15
|
+
<h3 v-if="title" :id="`${id}-title`" class="mc-listbox__title">
|
|
16
|
+
{{ title }}
|
|
17
|
+
</h3>
|
|
15
18
|
<MIconButton
|
|
16
19
|
class="mc-listbox__close"
|
|
17
20
|
ghost
|
|
@@ -24,7 +27,15 @@
|
|
|
24
27
|
</MIconButton>
|
|
25
28
|
</div>
|
|
26
29
|
<div class="mc-listbox__body">
|
|
27
|
-
<ul
|
|
30
|
+
<ul
|
|
31
|
+
ref="menuEl"
|
|
32
|
+
class="mc-action-list"
|
|
33
|
+
role="menu"
|
|
34
|
+
tabindex="-1"
|
|
35
|
+
:aria-label="title || undefined"
|
|
36
|
+
:aria-labelledby="title ? `${id}-title` : undefined"
|
|
37
|
+
@keydown="onMenuKeydown"
|
|
38
|
+
>
|
|
28
39
|
<template v-for="(item, index) in items" :key="`item-${index}`">
|
|
29
40
|
<MDivider
|
|
30
41
|
v-if="item.divider"
|
|
@@ -40,17 +51,20 @@
|
|
|
40
51
|
'mc-action-list__element--disabled': item.disabled,
|
|
41
52
|
},
|
|
42
53
|
]"
|
|
43
|
-
role="
|
|
54
|
+
role="presentation"
|
|
44
55
|
>
|
|
45
56
|
<button
|
|
46
57
|
type="button"
|
|
58
|
+
role="menuitem"
|
|
47
59
|
class="mc-action-list__button"
|
|
60
|
+
:disabled="item.disabled || undefined"
|
|
48
61
|
@click="emit('action', item?.id || index)"
|
|
49
62
|
>
|
|
50
63
|
<component
|
|
51
64
|
v-if="item.icon"
|
|
52
65
|
class="mc-action-list__icon"
|
|
53
66
|
:is="item.icon"
|
|
67
|
+
aria-hidden="true"
|
|
54
68
|
/>
|
|
55
69
|
<p class="mc-action-list__text">{{ item.label }}</p>
|
|
56
70
|
</button>
|
|
@@ -65,7 +79,13 @@
|
|
|
65
79
|
</template>
|
|
66
80
|
|
|
67
81
|
<script setup lang="ts">
|
|
68
|
-
import {
|
|
82
|
+
import {
|
|
83
|
+
nextTick,
|
|
84
|
+
useId,
|
|
85
|
+
useTemplateRef,
|
|
86
|
+
type Component,
|
|
87
|
+
type VNode,
|
|
88
|
+
} from 'vue';
|
|
69
89
|
import MIconButton from '../iconbutton/MIconButton.vue';
|
|
70
90
|
import MDivider from '../divider/MDivider.vue';
|
|
71
91
|
import { Cross24 } from '@mozaic-ds/icons-vue';
|
|
@@ -137,8 +157,46 @@ const slots = defineSlots<{
|
|
|
137
157
|
}>();
|
|
138
158
|
|
|
139
159
|
const id = useId();
|
|
140
|
-
|
|
141
160
|
const popover = useTemplateRef('popover');
|
|
161
|
+
const menuEl = useTemplateRef('menuEl');
|
|
162
|
+
|
|
163
|
+
function getMenuItems(): HTMLButtonElement[] {
|
|
164
|
+
return Array.from(
|
|
165
|
+
menuEl.value?.querySelectorAll<HTMLButtonElement>(
|
|
166
|
+
'button[role="menuitem"]:not(:disabled)',
|
|
167
|
+
) ?? [],
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function onMenuKeydown(e: KeyboardEvent) {
|
|
172
|
+
const items = getMenuItems();
|
|
173
|
+
if (!items.length) return;
|
|
174
|
+
const current = items.findIndex((el) => el === document.activeElement);
|
|
175
|
+
|
|
176
|
+
if (e.key === 'ArrowDown') {
|
|
177
|
+
e.preventDefault();
|
|
178
|
+
items[(current + 1) % items.length].focus();
|
|
179
|
+
} else if (e.key === 'ArrowUp') {
|
|
180
|
+
e.preventDefault();
|
|
181
|
+
items[(current - 1 + items.length) % items.length].focus();
|
|
182
|
+
} else if (e.key === 'Home') {
|
|
183
|
+
e.preventDefault();
|
|
184
|
+
items[0].focus();
|
|
185
|
+
} else if (e.key === 'End') {
|
|
186
|
+
e.preventDefault();
|
|
187
|
+
items[items.length - 1].focus();
|
|
188
|
+
} else if (e.key === 'Escape') {
|
|
189
|
+
close();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function onPopoverToggle(e: ToggleEvent) {
|
|
194
|
+
if (e.newState === 'open') {
|
|
195
|
+
nextTick(() => getMenuItems()[0]?.focus());
|
|
196
|
+
} else {
|
|
197
|
+
document.querySelector<HTMLElement>(`[popovertarget="${id}"]`)?.focus();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
142
200
|
|
|
143
201
|
function close() {
|
|
144
202
|
emit('close');
|
|
@@ -21,11 +21,11 @@ figma.connect(
|
|
|
21
21
|
},
|
|
22
22
|
example: ({ size }) =>
|
|
23
23
|
html`<script setup>
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
import { MAvatar } from '@mozaic-ds/vue';
|
|
25
|
+
</script>
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
<MAvatar size=${size}>
|
|
28
|
+
<img src="/placeholder.png" alt="Avatar" />
|
|
29
|
+
</MAvatar>`,
|
|
30
30
|
},
|
|
31
31
|
);
|
|
@@ -15,17 +15,17 @@ figma.connect(
|
|
|
15
15
|
},
|
|
16
16
|
example: ({ appearance }) =>
|
|
17
17
|
html`<script setup>
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
import { MBreadcrumb } from '@mozaic-ds/vue';
|
|
19
|
+
</script>
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
<MBreadcrumb
|
|
22
|
+
appearance=${appearance}
|
|
23
|
+
:links="[
|
|
24
24
|
{ label: 'Home', href: '#' },
|
|
25
25
|
{ label: 'Level 01', href: '#' },
|
|
26
26
|
{ label: 'Current Page', href: '#' },
|
|
27
27
|
]"
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
aria-label="breadcrumb"
|
|
29
|
+
></MBreadcrumb>`,
|
|
30
30
|
},
|
|
31
31
|
);
|
|
@@ -9,15 +9,18 @@ figma.connect(
|
|
|
9
9
|
{
|
|
10
10
|
props: {
|
|
11
11
|
outlined: figma.enum('is outlined', {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
true: true,
|
|
13
|
+
false: false,
|
|
14
14
|
}),
|
|
15
15
|
},
|
|
16
16
|
example: ({ outlined }) =>
|
|
17
17
|
html`<script setup>
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
import { MBuiltInMenu } from '@mozaic-ds/vue';
|
|
19
|
+
</script>
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
<MBuiltInMenu
|
|
22
|
+
:items="[{ label: 'Label' }, { label: 'Label' }]"
|
|
23
|
+
outlined=${outlined}
|
|
24
|
+
/>`,
|
|
22
25
|
},
|
|
23
26
|
);
|
|
@@ -120,7 +120,9 @@ describe('MBuiltInMenu', () => {
|
|
|
120
120
|
const wrapper = mount(MBuiltInMenu, {
|
|
121
121
|
props: { items, label: 'Settings navigation' },
|
|
122
122
|
});
|
|
123
|
-
expect(wrapper.find('nav').attributes('aria-label')).toBe(
|
|
123
|
+
expect(wrapper.find('nav').attributes('aria-label')).toBe(
|
|
124
|
+
'Settings navigation',
|
|
125
|
+
);
|
|
124
126
|
});
|
|
125
127
|
|
|
126
128
|
it('sets aria-hidden on ChevronRight and item icons', () => {
|
|
@@ -29,13 +29,28 @@ figma.connect(
|
|
|
29
29
|
outlined: figma.enum('Type', { Outlined: true }),
|
|
30
30
|
ghost: figma.enum('Type', { Ghost: true }),
|
|
31
31
|
},
|
|
32
|
-
example: ({
|
|
32
|
+
example: ({
|
|
33
|
+
label,
|
|
34
|
+
appearance,
|
|
35
|
+
size,
|
|
36
|
+
disabled,
|
|
37
|
+
isLoading,
|
|
38
|
+
outlined,
|
|
39
|
+
ghost,
|
|
40
|
+
}) =>
|
|
33
41
|
html`<script setup>
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
import { MButton } from '@mozaic-ds/vue';
|
|
43
|
+
</script>
|
|
36
44
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
<MButton
|
|
46
|
+
appearance=${appearance}
|
|
47
|
+
size=${size}
|
|
48
|
+
disabled=${disabled}
|
|
49
|
+
:is-loading=${isLoading}
|
|
50
|
+
outlined=${outlined}
|
|
51
|
+
ghost=${ghost}
|
|
52
|
+
>
|
|
53
|
+
${label}
|
|
54
|
+
</MButton>`,
|
|
40
55
|
},
|
|
41
56
|
);
|
|
@@ -188,4 +188,30 @@ describe('MButton component', () => {
|
|
|
188
188
|
expect(label.exists()).toBe(true);
|
|
189
189
|
expect(label.text()).toBe('Normal Button');
|
|
190
190
|
});
|
|
191
|
+
|
|
192
|
+
it('sets aria-busy="true" when isLoading is true', () => {
|
|
193
|
+
const wrapper = mount(MButton, {
|
|
194
|
+
props: { isLoading: true },
|
|
195
|
+
slots: { default: 'Loading' },
|
|
196
|
+
});
|
|
197
|
+
expect(wrapper.find('button').attributes('aria-busy')).toBe('true');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('does not set aria-busy when isLoading is false', () => {
|
|
201
|
+
const wrapper = mount(MButton, {
|
|
202
|
+
props: { isLoading: false },
|
|
203
|
+
slots: { default: 'Normal' },
|
|
204
|
+
});
|
|
205
|
+
expect(wrapper.find('button').attributes('aria-busy')).toBeUndefined();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('sets aria-hidden on the loader wrapper', () => {
|
|
209
|
+
const wrapper = mount(MButton, {
|
|
210
|
+
props: { isLoading: true },
|
|
211
|
+
slots: { default: 'Loading' },
|
|
212
|
+
});
|
|
213
|
+
const loaderWrapper = wrapper.find('.mc-button__icon[aria-hidden]');
|
|
214
|
+
expect(loaderWrapper.exists()).toBe(true);
|
|
215
|
+
expect(loaderWrapper.attributes('aria-hidden')).toBe('true');
|
|
216
|
+
});
|
|
191
217
|
});
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
:class="classObject"
|
|
5
5
|
:disabled="disabled"
|
|
6
6
|
:type="type"
|
|
7
|
+
:aria-busy="isLoading || undefined"
|
|
7
8
|
>
|
|
8
9
|
<span
|
|
9
10
|
v-if="$slots.icon && iconPosition == 'left' && !isLoading"
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
v-if="isLoading"
|
|
16
17
|
class="mc-button__icon"
|
|
17
18
|
:style="{ position: 'absolute' }"
|
|
19
|
+
aria-hidden="true"
|
|
18
20
|
>
|
|
19
21
|
<MLoader :style="{ color: 'currentColor' }" size="s" />
|
|
20
22
|
</span>
|