@mozaic-ds/vue 2.19.1 → 2.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mozaic-vue.css +1 -1
- package/dist/mozaic-vue.d.ts +512 -296
- package/dist/mozaic-vue.js +1952 -1678
- 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 +6 -3
- package/src/components/Catalog.scss +84 -0
- package/src/components/Catalog.stories.ts +146 -0
- package/src/components/GettingStarted.mdx +258 -2
- package/src/components/Migration.mdx +4135 -532
- package/src/components/Welcome.mdx +1 -1
- package/src/components/carousel/MCarousel.spec.ts +174 -55
- package/src/components/carousel/MCarousel.vue +106 -33
- package/src/components/checkboxgroup/MCheckboxGroup.vue +2 -9
- package/src/components/combobox/MCombobox.spec.ts +144 -0
- package/src/components/combobox/MCombobox.stories.ts +2 -1
- package/src/components/combobox/MCombobox.vue +68 -38
- package/src/components/combobox/README.md +6 -0
- package/src/components/datatable/DataTable.stories.ts +2 -2
- package/src/components/datatable/DataTableCells.stories.ts +1 -0
- package/src/components/datatable/DataTableEmpty.stories.ts +1 -0
- package/src/components/datatable/DataTableExpandable.stories.ts +1 -0
- package/src/components/datatable/DataTableNested.stories.ts +1 -0
- package/src/components/datatable/DataTablePageable.stories.ts +194 -0
- package/src/components/datatable/DataTableSelectable.stories.ts +1 -0
- package/src/components/datatable/DataTableSortable.stories.ts +1 -0
- package/src/components/datepicker/MDatepicker.stories.ts +2 -0
- package/src/components/datepicker/MDatepicker.vue +17 -11
- package/src/components/divider/README.md +2 -0
- package/src/components/drawer/MDrawer.vue +7 -5
- package/src/components/link/MLink.stories.ts +18 -0
- package/src/components/link/MLink.vue +20 -4
- package/src/components/link/README.md +2 -1
- package/src/components/optionListbox/MOptionListbox.spec.ts +78 -1
- package/src/components/optionListbox/MOptionListbox.types.ts +7 -0
- package/src/components/optionListbox/MOptionListbox.vue +31 -217
- package/src/components/optionListbox/README.md +2 -0
- package/src/components/optionListbox/use-listbox-navigation.composable.ts +147 -0
- package/src/components/optionListbox/use-listbox-selection.composable.ts +149 -0
- package/src/components/pageheader/MPageHeader.spec.ts +17 -3
- package/src/components/pageheader/MPageHeader.stories.ts +37 -41
- package/src/components/pageheader/MPageHeader.vue +26 -16
- package/src/components/pageheader/README.md +2 -1
- package/src/components/pageheadercontext/MPageHeaderContext.spec.ts +155 -0
- package/src/components/pageheadercontext/MPageHeaderContext.vue +126 -0
- package/src/components/pageheadercontext/README.md +33 -0
- package/src/components/pageheaderfeature/MPageHeaderFeature.spec.ts +39 -0
- package/src/components/pageheaderfeature/MPageHeaderFeature.vue +40 -0
- package/src/components/pageheaderfeature/README.md +17 -0
- package/src/components/radiogroup/MRadioGroup.stories.ts +6 -0
- package/src/components/radiogroup/MRadioGroup.vue +2 -9
- package/src/components/sidebar/MSidebar.const.ts +2 -0
- package/src/components/sidebar/MSidebar.stories.ts +33 -0
- package/src/components/sidebar/MSidebar.vue +10 -1
- package/src/components/sidebar/README.md +1 -0
- package/src/components/sidebar/stories/DefaultCase.stories.vue +12 -11
- package/src/components/sidebar/use-floating-item.composable.ts +1 -1
- package/src/components/sidebarexpandableitem/MSidebarExpandableItem.vue +13 -7
- package/src/components/sidebarexpandableitem/README.md +2 -1
- package/src/components/sidebarfooter/MSidebarFooter.vue +16 -9
- package/src/components/sidebarfooter/_MSidebarFooterMenu.vue +10 -2
- package/src/components/sidebarnavitem/MSidebarNavItem.vue +36 -5
- package/src/components/sidebarnavitem/README.md +1 -0
- package/src/components/sidebarshortcutitem/MSidebarShortcutItem.vue +24 -3
- package/src/components/sidebarshortcutitem/README.md +3 -1
- package/src/components/sidebarshortcuts/MSidebarShortcuts.vue +6 -1
- package/src/components/sidebarshortcuts/README.md +12 -0
- package/src/components/statusbadge/MStatusBadge.stories.ts +5 -1
- package/src/components/statusbadge/MStatusBadge.vue +7 -1
- package/src/components/statusbadge/MstatusBadge.spec.ts +31 -0
- package/src/components/statusbadge/README.md +1 -0
- package/src/components/statusmessage/MStatusMessage.spec.ts +4 -4
- package/src/components/statusmessage/MStatusMessage.vue +5 -10
- package/src/components/statusnotification/MStatusNotification.spec.ts +9 -9
- package/src/components/statusnotification/MStatusNotification.vue +9 -8
- package/src/components/tabs/MTabs.stories.ts +30 -1
- package/src/components/tabs/MTabs.vue +12 -3
- package/src/components/tabs/Mtabs.spec.ts +19 -0
- package/src/components/tabs/README.md +1 -0
- package/src/components/tileselectable/MTileSelectable.spec.ts +69 -0
- package/src/components/tileselectable/MTileSelectable.stories.ts +8 -2
- package/src/components/tileselectable/MTileSelectable.vue +18 -1
- package/src/components/tileselectable/README.md +1 -0
- package/src/main.ts +3 -0
- package/src/utils/debounce.ts +15 -0
- package/src/components/BrandPresets.mdx +0 -146
- package/src/components/DarkMode.mdx +0 -115
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { defineComponent } from 'vue';
|
|
4
|
+
import MPageHeaderFeature from './MPageHeaderFeature.vue';
|
|
5
|
+
|
|
6
|
+
const IconStub = defineComponent({
|
|
7
|
+
name: 'IconStub',
|
|
8
|
+
template: '<svg class="icon-stub" />',
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('MPageHeaderFeature', () => {
|
|
12
|
+
it('renders label and icon', () => {
|
|
13
|
+
const wrapper = mount(MPageHeaderFeature, {
|
|
14
|
+
props: {
|
|
15
|
+
label: 'Search',
|
|
16
|
+
icon: IconStub,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
expect(wrapper.find('.mc-page-header__feature-item').exists()).toBe(true);
|
|
21
|
+
expect(wrapper.find('.mc-page-header__feature-item-icon').exists()).toBe(
|
|
22
|
+
true,
|
|
23
|
+
);
|
|
24
|
+
expect(wrapper.text()).toContain('Search');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('emits action when clicked', async () => {
|
|
28
|
+
const wrapper = mount(MPageHeaderFeature, {
|
|
29
|
+
props: {
|
|
30
|
+
label: 'Help',
|
|
31
|
+
icon: IconStub,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await wrapper.find('.mc-page-header__feature-item').trigger('click');
|
|
36
|
+
|
|
37
|
+
expect(wrapper.emitted()).toHaveProperty('action');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
type="button"
|
|
4
|
+
class="mc-page-header__feature-item"
|
|
5
|
+
@click="emit('action')"
|
|
6
|
+
>
|
|
7
|
+
<component :is="props.icon" class="mc-page-header__feature-item-icon" />
|
|
8
|
+
|
|
9
|
+
<span>{{ props.label }}</span>
|
|
10
|
+
</button>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup lang="ts">
|
|
14
|
+
import { type Component } from 'vue';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The Page Header Feature component renders a clickable feature item in the page header action area.
|
|
18
|
+
*/
|
|
19
|
+
const props = defineProps<{
|
|
20
|
+
/**
|
|
21
|
+
* Text label displayed for the feature item.
|
|
22
|
+
*/
|
|
23
|
+
label: string;
|
|
24
|
+
/**
|
|
25
|
+
* Icon component displayed next to the feature label.
|
|
26
|
+
*/
|
|
27
|
+
icon: Component;
|
|
28
|
+
}>();
|
|
29
|
+
|
|
30
|
+
const emit = defineEmits<{
|
|
31
|
+
/**
|
|
32
|
+
* Emits when the feature item is clicked.
|
|
33
|
+
*/
|
|
34
|
+
(on: 'action'): void;
|
|
35
|
+
}>();
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<style lang="scss" scoped>
|
|
39
|
+
@use '@mozaic-ds/styles/components/page-header';
|
|
40
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# MPageHeaderFeature
|
|
2
|
+
|
|
3
|
+
The Page Header Feature component renders a clickable feature item in the page header action area.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Props
|
|
7
|
+
|
|
8
|
+
| Name | Description | Type | Default |
|
|
9
|
+
| --- | --- | --- | --- |
|
|
10
|
+
| `label*` | Text label displayed for the feature item. | `string` | - |
|
|
11
|
+
| `icon*` | Icon component displayed next to the feature label. | `Component` | - |
|
|
12
|
+
|
|
13
|
+
## Events
|
|
14
|
+
|
|
15
|
+
| Name | Description | Type |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| `action` | Emits when the feature item is clicked. | [] |
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="mc-
|
|
2
|
+
<div class="mc-field__content" :class="classObjectContainer">
|
|
3
3
|
<MRadio
|
|
4
4
|
v-for="option in options"
|
|
5
5
|
:id="option.id"
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
:is-invalid="isInvalid"
|
|
9
9
|
:name="name"
|
|
10
10
|
class="mc-field__item"
|
|
11
|
-
:class="classObjectItem"
|
|
12
11
|
:model-value="modelValue === option.value"
|
|
13
12
|
:disabled="option.disabled"
|
|
14
13
|
@update:model-value="
|
|
@@ -56,13 +55,7 @@ const props = defineProps<{
|
|
|
56
55
|
|
|
57
56
|
const classObjectContainer = computed(() => {
|
|
58
57
|
return {
|
|
59
|
-
'mc-
|
|
60
|
-
};
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const classObjectItem = computed(() => {
|
|
64
|
-
return {
|
|
65
|
-
'mc-field__container--inline__item': props.inline,
|
|
58
|
+
'mc-field__content--inline': props.inline,
|
|
66
59
|
};
|
|
67
60
|
});
|
|
68
61
|
|
|
@@ -4,3 +4,5 @@ export const EXPANDED_SIDEBAR_KEY = Symbol() as InjectionKey<Ref<boolean>>;
|
|
|
4
4
|
export const TOGGLE_SIDEBAR_KEY = Symbol() as InjectionKey<() => void>;
|
|
5
5
|
export const SUB_ITEM_KEY = Symbol() as InjectionKey<boolean>;
|
|
6
6
|
export const STACKED_SHORTCUTS_KEY = Symbol() as InjectionKey<boolean>;
|
|
7
|
+
export const FOOTER_ITEM_KEY = Symbol() as InjectionKey<boolean>;
|
|
8
|
+
export const HIDE_MENU_KEY = Symbol() as InjectionKey<() => void>;
|
|
@@ -70,6 +70,17 @@ export const Default: Story = {};
|
|
|
70
70
|
|
|
71
71
|
export const ExpandOnly: Story = {
|
|
72
72
|
render: () => ({
|
|
73
|
+
setup() {
|
|
74
|
+
const handleClose = action('close');
|
|
75
|
+
const handleUpdateModelValue = action('update:modelValue');
|
|
76
|
+
const handleLogOut = action('log-out');
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
handleClose,
|
|
80
|
+
handleUpdateModelValue,
|
|
81
|
+
handleLogOut,
|
|
82
|
+
};
|
|
83
|
+
},
|
|
73
84
|
components: { WithExpandOnlyStory },
|
|
74
85
|
template: `
|
|
75
86
|
<WithExpandOnlyStory
|
|
@@ -83,6 +94,17 @@ export const ExpandOnly: Story = {
|
|
|
83
94
|
|
|
84
95
|
export const ProfileInfoOnly: Story = {
|
|
85
96
|
render: () => ({
|
|
97
|
+
setup() {
|
|
98
|
+
const handleClose = action('close');
|
|
99
|
+
const handleUpdateModelValue = action('update:modelValue');
|
|
100
|
+
const handleLogOut = action('log-out');
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
handleClose,
|
|
104
|
+
handleUpdateModelValue,
|
|
105
|
+
handleLogOut,
|
|
106
|
+
};
|
|
107
|
+
},
|
|
86
108
|
components: { WithProfileInfoOnlyStory },
|
|
87
109
|
template: `
|
|
88
110
|
<WithProfileInfoOnlyStory
|
|
@@ -96,6 +118,17 @@ export const ProfileInfoOnly: Story = {
|
|
|
96
118
|
|
|
97
119
|
export const SingleLevel: Story = {
|
|
98
120
|
render: () => ({
|
|
121
|
+
setup() {
|
|
122
|
+
const handleClose = action('close');
|
|
123
|
+
const handleUpdateModelValue = action('update:modelValue');
|
|
124
|
+
const handleLogOut = action('log-out');
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
handleClose,
|
|
128
|
+
handleUpdateModelValue,
|
|
129
|
+
handleLogOut,
|
|
130
|
+
};
|
|
131
|
+
},
|
|
99
132
|
components: { WithSingleLevelStory },
|
|
100
133
|
template: `
|
|
101
134
|
<WithSingleLevelStory
|
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
`mc-sidebar--${expanded ? 'expanded' : 'collapsed'}`,
|
|
6
6
|
]"
|
|
7
7
|
>
|
|
8
|
-
<div
|
|
8
|
+
<div
|
|
9
|
+
:class="{
|
|
10
|
+
'mc-sidebar__wrapper': true,
|
|
11
|
+
'mc-sidebar__wrapper--divider': props.divider,
|
|
12
|
+
}"
|
|
13
|
+
>
|
|
9
14
|
<slot name="header" />
|
|
10
15
|
|
|
11
16
|
<slot name="shortcuts" />
|
|
@@ -55,6 +60,10 @@ const props = defineProps<{
|
|
|
55
60
|
* the state of the expanded sidebar, bound via v-model.
|
|
56
61
|
*/
|
|
57
62
|
modelValue: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Whether to display a right border on the sidebar.
|
|
65
|
+
*/
|
|
66
|
+
divider?: boolean;
|
|
58
67
|
}>();
|
|
59
68
|
|
|
60
69
|
const emit = defineEmits<{
|
|
@@ -8,6 +8,7 @@ A sidebar is a vertical navigation component that provides quick access to key s
|
|
|
8
8
|
| Name | Description | Type | Default |
|
|
9
9
|
| --- | --- | --- | --- |
|
|
10
10
|
| `modelValue*` | the state of the expanded sidebar, bound via v-model. | `boolean` | - |
|
|
11
|
+
| `divider` | Whether to display a right border on the sidebar. | `boolean` | - |
|
|
11
12
|
|
|
12
13
|
## Slots
|
|
13
14
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<MSidebar
|
|
3
3
|
v-model="expanded"
|
|
4
|
+
divider
|
|
4
5
|
@update:model-value="emit('update:modelValue')"
|
|
5
6
|
@close="emit('close')"
|
|
6
7
|
>
|
|
@@ -96,25 +97,25 @@ const shortcutItems = [
|
|
|
96
97
|
];
|
|
97
98
|
|
|
98
99
|
const navigationItems = [
|
|
99
|
-
{ label: 'Get Started',
|
|
100
|
+
{ label: 'Get Started', to: 'get-started', icon: Release24 },
|
|
100
101
|
{
|
|
101
102
|
label: 'Design Tokens',
|
|
102
103
|
icon: Sample24,
|
|
103
104
|
items: [
|
|
104
|
-
{ label: 'Subsection menu label 1',
|
|
105
|
-
{ label: 'Subsection menu label 2',
|
|
106
|
-
{ label: 'Subsection menu label 2',
|
|
107
|
-
{ label: 'Subsection menu label 2',
|
|
105
|
+
{ label: 'Subsection menu label 1', to: 'subsection-1', locked: true },
|
|
106
|
+
{ label: 'Subsection menu label 2', to: 'subsection-2', external: true },
|
|
107
|
+
{ label: 'Subsection menu label 2', to: 'subsection-3' },
|
|
108
|
+
{ label: 'Subsection menu label 2', to: 'subsection-4' },
|
|
108
109
|
],
|
|
109
110
|
},
|
|
110
|
-
{ label: 'Styles',
|
|
111
|
-
{ label: 'Components',
|
|
111
|
+
{ label: 'Styles', to: 'styles', icon: Palette24 },
|
|
112
|
+
{ label: 'Components', to: 'components', icon: Pantone24 },
|
|
112
113
|
];
|
|
113
114
|
|
|
114
115
|
const footerMenuItems = [
|
|
115
|
-
{ label: 'Action 01', icon: Admin24,
|
|
116
|
-
{ label: 'Action 02', icon: Admin24,
|
|
117
|
-
{ label: 'Action 03', icon: Admin24,
|
|
118
|
-
{ label: 'Action 04', icon: Admin24,
|
|
116
|
+
{ label: 'Action 01', icon: Admin24, to: 'action-01' },
|
|
117
|
+
{ label: 'Action 02', icon: Admin24, to: 'action-02' },
|
|
118
|
+
{ label: 'Action 03', icon: Admin24, to: 'action-03' },
|
|
119
|
+
{ label: 'Action 04', icon: Admin24, to: 'action-04' },
|
|
119
120
|
];
|
|
120
121
|
</script>
|
|
@@ -73,7 +73,7 @@ export const useFloatingItem = (
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
function showFloatingItem() {
|
|
76
|
-
floatingItem.value?.classList.
|
|
76
|
+
floatingItem.value?.classList.remove('mc-sidebar__floating-item--hidden');
|
|
77
77
|
floatingItemIsDisplayed.value = !floatingItem.value?.classList.contains(
|
|
78
78
|
'mc-sidebar__floating-item--hidden',
|
|
79
79
|
);
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
<ChevronDown20 />
|
|
12
12
|
</summary>
|
|
13
13
|
<ul class="mc-sidebar__sublist">
|
|
14
|
-
<slot name="default" />
|
|
14
|
+
<slot name="default" v-bind="{ close: hideFloatingItem }" />
|
|
15
15
|
</ul>
|
|
16
16
|
</details>
|
|
17
17
|
</li>
|
|
18
18
|
|
|
19
|
-
<div v-else class="mc-sidebar__item">
|
|
19
|
+
<div v-else class="mc-sidebar__item mc-sidebar__item--collapsed">
|
|
20
20
|
<button
|
|
21
21
|
ref="trigger"
|
|
22
22
|
class="mc-sidebar__trigger mc-sidebar__link"
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
<div class="mc-sidebar__listbox">
|
|
48
48
|
<h3 class="mc-sidebar__listbox-title">{{ props.menuLabel }}</h3>
|
|
49
49
|
<ul class="mc-sidebar__list">
|
|
50
|
-
<slot name="default" />
|
|
50
|
+
<slot name="default" v-bind="{ close: hideFloatingItem }" />
|
|
51
51
|
</ul>
|
|
52
52
|
</div>
|
|
53
53
|
</div>
|
|
@@ -65,7 +65,11 @@ import {
|
|
|
65
65
|
} from 'vue';
|
|
66
66
|
import { ChevronDown20 } from '@mozaic-ds/icons-vue';
|
|
67
67
|
import { useFloatingItem } from '../sidebar/use-floating-item.composable';
|
|
68
|
-
import {
|
|
68
|
+
import {
|
|
69
|
+
SUB_ITEM_KEY,
|
|
70
|
+
EXPANDED_SIDEBAR_KEY,
|
|
71
|
+
HIDE_MENU_KEY,
|
|
72
|
+
} from '../sidebar/MSidebar.const';
|
|
69
73
|
/**
|
|
70
74
|
* SidebarExpandableItem is a component that renders a sidebar item which can either expand to show a nested submenu or display a floating menu when the sidebar is collapsed. It supports optional icons, keyboard navigation, and accessibility attributes, and allows for custom submenu items via slots.
|
|
71
75
|
*/
|
|
@@ -87,12 +91,11 @@ const props = defineProps<{
|
|
|
87
91
|
defineSlots<{
|
|
88
92
|
/**
|
|
89
93
|
* Contains one or more MSidebarNavItem components used to render the submenu items.
|
|
94
|
+
* The slot scope provides a `close` function that can be called to hide the floating menu.
|
|
90
95
|
*/
|
|
91
|
-
default: VNode[];
|
|
96
|
+
default(props: { close: () => void }): VNode[];
|
|
92
97
|
}>();
|
|
93
98
|
|
|
94
|
-
const expanded = inject(EXPANDED_SIDEBAR_KEY);
|
|
95
|
-
|
|
96
99
|
const id = useId();
|
|
97
100
|
|
|
98
101
|
const trigger = useTemplateRef('trigger');
|
|
@@ -106,6 +109,9 @@ const {
|
|
|
106
109
|
onListboxKeydown,
|
|
107
110
|
} = useFloatingItem(trigger, listbox);
|
|
108
111
|
|
|
112
|
+
const expanded = inject(EXPANDED_SIDEBAR_KEY);
|
|
113
|
+
|
|
114
|
+
provide(HIDE_MENU_KEY, hideFloatingItem);
|
|
109
115
|
provide(SUB_ITEM_KEY, true);
|
|
110
116
|
</script>
|
|
111
117
|
|
|
@@ -15,7 +15,8 @@ SidebarExpandableItem is a component that renders a sidebar item which can eithe
|
|
|
15
15
|
|
|
16
16
|
| Name | Description |
|
|
17
17
|
| --- | --- |
|
|
18
|
-
| `default` | Contains one or more MSidebarNavItem components used to render the submenu items.
|
|
18
|
+
| `default` | Contains one or more MSidebarNavItem components used to render the submenu items.
|
|
19
|
+
The slot scope provides a `close` function that can be called to hide the floating menu. |
|
|
19
20
|
|
|
20
21
|
## Dependencies
|
|
21
22
|
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
:hideFloatingItem="hideFloatingItem"
|
|
40
40
|
@log-out="emit('log-out')"
|
|
41
41
|
>
|
|
42
|
-
<slot />
|
|
42
|
+
<slot v-bind="{ close: hideFloatingItem }" />
|
|
43
43
|
</MSidebarFooterMenu>
|
|
44
44
|
</component>
|
|
45
45
|
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
:hideFloatingItem="hideFloatingItem"
|
|
78
78
|
@log-out="emit('log-out')"
|
|
79
79
|
>
|
|
80
|
-
<slot />
|
|
80
|
+
<slot v-bind="{ close: hideFloatingItem }" />
|
|
81
81
|
</MSidebarFooterMenu>
|
|
82
82
|
</button>
|
|
83
83
|
</div>
|
|
@@ -96,6 +96,8 @@ import {
|
|
|
96
96
|
} from '@mozaic-ds/icons-vue';
|
|
97
97
|
import {
|
|
98
98
|
EXPANDED_SIDEBAR_KEY,
|
|
99
|
+
FOOTER_ITEM_KEY,
|
|
100
|
+
HIDE_MENU_KEY,
|
|
99
101
|
SUB_ITEM_KEY,
|
|
100
102
|
TOGGLE_SIDEBAR_KEY,
|
|
101
103
|
} from '../sidebar/MSidebar.const';
|
|
@@ -136,8 +138,9 @@ withDefaults(
|
|
|
136
138
|
defineSlots<{
|
|
137
139
|
/**
|
|
138
140
|
* Contains one or more MSidebarNavItem components used to render the profile items.
|
|
141
|
+
* The slot scope provides a `close` function that can be called to hide the floating menu.
|
|
139
142
|
*/
|
|
140
|
-
default: VNode[];
|
|
143
|
+
default(props: { close: () => void }): VNode[];
|
|
141
144
|
}>();
|
|
142
145
|
|
|
143
146
|
const emit = defineEmits<{
|
|
@@ -147,11 +150,6 @@ const emit = defineEmits<{
|
|
|
147
150
|
(on: 'log-out'): void;
|
|
148
151
|
}>();
|
|
149
152
|
|
|
150
|
-
provide(SUB_ITEM_KEY, true);
|
|
151
|
-
|
|
152
|
-
const toggleSidebar = inject(TOGGLE_SIDEBAR_KEY);
|
|
153
|
-
const expanded = inject(EXPANDED_SIDEBAR_KEY);
|
|
154
|
-
|
|
155
153
|
const trigger = useTemplateRef('trigger');
|
|
156
154
|
const listbox = useTemplateRef('listbox');
|
|
157
155
|
|
|
@@ -171,6 +169,13 @@ const {
|
|
|
171
169
|
position: 'top',
|
|
172
170
|
});
|
|
173
171
|
|
|
172
|
+
provide(SUB_ITEM_KEY, true);
|
|
173
|
+
provide(FOOTER_ITEM_KEY, true);
|
|
174
|
+
provide(HIDE_MENU_KEY, hideFloatingItem);
|
|
175
|
+
|
|
176
|
+
const toggleSidebar = inject(TOGGLE_SIDEBAR_KEY);
|
|
177
|
+
const expanded = inject(EXPANDED_SIDEBAR_KEY);
|
|
178
|
+
|
|
174
179
|
const desktopTriggerAttrs = computed(() => {
|
|
175
180
|
if (isMobile.value) return {};
|
|
176
181
|
|
|
@@ -182,7 +187,9 @@ const desktopTriggerAttrs = computed(() => {
|
|
|
182
187
|
return expanded?.value
|
|
183
188
|
? {
|
|
184
189
|
...baseAttrs,
|
|
185
|
-
onClick:
|
|
190
|
+
onClick: floatingItemIsDisplayed.value
|
|
191
|
+
? hideFloatingItem
|
|
192
|
+
: showFloatingItem,
|
|
186
193
|
}
|
|
187
194
|
: {
|
|
188
195
|
...baseAttrs,
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
'mc-sidebar__floating-item mc-sidebar__floating-item--hidden': true,
|
|
9
9
|
'mc-sidebar__floating-item--expanded': expanded,
|
|
10
10
|
}"
|
|
11
|
+
@click.stop
|
|
11
12
|
v-bind="
|
|
12
13
|
expanded
|
|
13
14
|
? {}
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
<slot name="default" />
|
|
24
25
|
</ul>
|
|
25
26
|
|
|
26
|
-
<MDivider class="mc-sidebar__footer-menu-divider" />
|
|
27
|
+
<MDivider v-if="hasNavItems" class="mc-sidebar__footer-menu-divider" />
|
|
27
28
|
|
|
28
29
|
<MSidebarNavItem
|
|
29
30
|
tag="div"
|
|
@@ -36,7 +37,7 @@
|
|
|
36
37
|
</template>
|
|
37
38
|
|
|
38
39
|
<script setup lang="ts">
|
|
39
|
-
import { useTemplateRef } from 'vue';
|
|
40
|
+
import { computed, useSlots, useTemplateRef, type VNode } from 'vue';
|
|
40
41
|
import MDivider from '../divider/MDivider.vue';
|
|
41
42
|
import MSidebarNavItem from '../sidebarnavitem/MSidebarNavItem.vue';
|
|
42
43
|
import { LogOut24 } from '@mozaic-ds/icons-vue';
|
|
@@ -54,6 +55,13 @@ const emit = defineEmits<{
|
|
|
54
55
|
|
|
55
56
|
const container = useTemplateRef('container');
|
|
56
57
|
|
|
58
|
+
const slots = useSlots();
|
|
59
|
+
|
|
60
|
+
const hasNavItems = computed(() => {
|
|
61
|
+
const content = slots.default?.() ?? [];
|
|
62
|
+
return content.some((vnode: VNode) => vnode.children?.length);
|
|
63
|
+
});
|
|
64
|
+
|
|
57
65
|
defineExpose({
|
|
58
66
|
container,
|
|
59
67
|
});
|
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
}"
|
|
9
9
|
>
|
|
10
10
|
<component
|
|
11
|
-
:is="
|
|
11
|
+
:is="itemTag"
|
|
12
12
|
ref="trigger"
|
|
13
13
|
:href="href"
|
|
14
|
+
:to="to"
|
|
14
15
|
:target="external ? '_blank' : '_self'"
|
|
15
16
|
:class="{
|
|
16
17
|
'mc-sidebar__link': true,
|
|
17
|
-
'mc-sidebar__link--selected': active,
|
|
18
|
+
'mc-sidebar__link--selected': active && !isFooterItem,
|
|
18
19
|
'mc-sidebar__link--locked': locked,
|
|
19
20
|
}"
|
|
20
21
|
:aria-label="label"
|
|
@@ -22,7 +23,8 @@
|
|
|
22
23
|
@focus="showFloatingItem"
|
|
23
24
|
@mouseleave="hideFloatingItem"
|
|
24
25
|
@blur="hideFloatingItem"
|
|
25
|
-
@click="
|
|
26
|
+
@click="handleClick"
|
|
27
|
+
v-bind="itemTag === 'button' ? { type: 'button' } : {}"
|
|
26
28
|
>
|
|
27
29
|
<component :is="icon" class="mc-sidebar__icon" />
|
|
28
30
|
|
|
@@ -47,6 +49,7 @@
|
|
|
47
49
|
<script setup lang="ts">
|
|
48
50
|
import { Lock20, ExternalLink20 } from '@mozaic-ds/icons-vue';
|
|
49
51
|
import {
|
|
52
|
+
computed,
|
|
50
53
|
inject,
|
|
51
54
|
useId,
|
|
52
55
|
useTemplateRef,
|
|
@@ -54,12 +57,17 @@ import {
|
|
|
54
57
|
type ShallowRef,
|
|
55
58
|
} from 'vue';
|
|
56
59
|
import MTooltip from '../tooltip/MTooltip.vue';
|
|
57
|
-
import {
|
|
60
|
+
import {
|
|
61
|
+
SUB_ITEM_KEY,
|
|
62
|
+
EXPANDED_SIDEBAR_KEY,
|
|
63
|
+
FOOTER_ITEM_KEY,
|
|
64
|
+
HIDE_MENU_KEY,
|
|
65
|
+
} from '../sidebar/MSidebar.const';
|
|
58
66
|
import { useFloatingItem } from '../sidebar/use-floating-item.composable';
|
|
59
67
|
/**
|
|
60
68
|
* SidebarNavItem is a component that renders a sidebar navigation item with optional icon support. It handles active, locked, and external states, shows tooltips or floating items when the sidebar is collapsed, and supports click events.
|
|
61
69
|
*/
|
|
62
|
-
defineProps<{
|
|
70
|
+
const props = defineProps<{
|
|
63
71
|
tag?: string;
|
|
64
72
|
/**
|
|
65
73
|
* The text label displayed for the navigation item.
|
|
@@ -73,6 +81,10 @@ defineProps<{
|
|
|
73
81
|
* The URL the navigation item links to.
|
|
74
82
|
*/
|
|
75
83
|
href?: string;
|
|
84
|
+
/**
|
|
85
|
+
* If defined, the item will act as a router-link to the specified route.
|
|
86
|
+
*/
|
|
87
|
+
to?: string;
|
|
76
88
|
/**
|
|
77
89
|
* Marks the item as locked. Shows a lock icon and prevents interaction.
|
|
78
90
|
*/
|
|
@@ -93,6 +105,9 @@ const id = useId();
|
|
|
93
105
|
|
|
94
106
|
const expanded = inject(EXPANDED_SIDEBAR_KEY);
|
|
95
107
|
const isSubItem = inject(SUB_ITEM_KEY, false);
|
|
108
|
+
const isFooterItem = inject(FOOTER_ITEM_KEY, false);
|
|
109
|
+
|
|
110
|
+
const hideFooterMenu = inject(HIDE_MENU_KEY);
|
|
96
111
|
|
|
97
112
|
const trigger = useTemplateRef(
|
|
98
113
|
'trigger',
|
|
@@ -106,6 +121,22 @@ const { hideFloatingItem, showFloatingItem } = useFloatingItem(
|
|
|
106
121
|
allowItemHover: false,
|
|
107
122
|
},
|
|
108
123
|
);
|
|
124
|
+
|
|
125
|
+
const itemTag = computed(() => {
|
|
126
|
+
if (props.href) return 'a';
|
|
127
|
+
if (props.to) return 'router-link';
|
|
128
|
+
return 'button';
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
function handleClick(event: MouseEvent) {
|
|
132
|
+
if (props.locked) {
|
|
133
|
+
event.preventDefault();
|
|
134
|
+
event.stopPropagation();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
emit('click', event);
|
|
138
|
+
hideFooterMenu?.();
|
|
139
|
+
}
|
|
109
140
|
</script>
|
|
110
141
|
|
|
111
142
|
<style lang="scss" scoped>
|
|
@@ -11,6 +11,7 @@ SidebarNavItem is a component that renders a sidebar navigation item with option
|
|
|
11
11
|
| `label*` | The text label displayed for the navigation item. | `string` | - |
|
|
12
12
|
| `icon` | Optional icon component displayed alongside the label. | `Component` | - |
|
|
13
13
|
| `href` | The URL the navigation item links to. | `string` | - |
|
|
14
|
+
| `to` | If defined, the item will act as a router-link to the specified route. | `string` | - |
|
|
14
15
|
| `locked` | Marks the item as locked. Shows a lock icon and prevents interaction. | `boolean` | - |
|
|
15
16
|
| `external` | Indicates the link is external. Opens in a new tab and shows an external link icon. | `boolean` | - |
|
|
16
17
|
| `active` | Marks the item as active, applying the active styling. | `boolean` | - |
|
|
@@ -8,14 +8,27 @@
|
|
|
8
8
|
]"
|
|
9
9
|
role="menuitem"
|
|
10
10
|
>
|
|
11
|
-
<
|
|
11
|
+
<component
|
|
12
|
+
:is="props.to ? 'router-link' : 'a'"
|
|
13
|
+
:target="props.external ? '_blank' : '_self'"
|
|
14
|
+
class="mc-sidebar__shortcut-link"
|
|
15
|
+
v-bind="
|
|
16
|
+
props.to
|
|
17
|
+
? {
|
|
18
|
+
to: props.to,
|
|
19
|
+
}
|
|
20
|
+
: {
|
|
21
|
+
href: props.href,
|
|
22
|
+
}
|
|
23
|
+
"
|
|
24
|
+
>
|
|
12
25
|
<component
|
|
13
26
|
v-if="props.icon"
|
|
14
27
|
:is="props.icon"
|
|
15
28
|
class="mc-sidebar__shortcut-icon"
|
|
16
29
|
/>
|
|
17
30
|
<span class="mc-sidebar__shortcut-label">{{ props.label }}</span>
|
|
18
|
-
</
|
|
31
|
+
</component>
|
|
19
32
|
</li>
|
|
20
33
|
</template>
|
|
21
34
|
|
|
@@ -36,7 +49,15 @@ const props = defineProps<{
|
|
|
36
49
|
/**
|
|
37
50
|
* The URL the shortcut item links to.
|
|
38
51
|
*/
|
|
39
|
-
href
|
|
52
|
+
href?: string;
|
|
53
|
+
/**
|
|
54
|
+
* If defined, the item will act as a router-link to the specified route.
|
|
55
|
+
*/
|
|
56
|
+
to?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Indicates the link is external and opens in a new tab.
|
|
59
|
+
*/
|
|
60
|
+
external?: boolean;
|
|
40
61
|
/**
|
|
41
62
|
* Optional icon component displayed alongside the label.
|
|
42
63
|
*/
|
|
@@ -8,7 +8,9 @@ SidebarShortcutItem is a component that renders a sidebar shortcut link with an
|
|
|
8
8
|
| Name | Description | Type | Default |
|
|
9
9
|
| --- | --- | --- | --- |
|
|
10
10
|
| `label*` | The text label displayed for the shortcut item. | `string` | - |
|
|
11
|
-
| `href
|
|
11
|
+
| `href` | The URL the shortcut item links to. | `string` | - |
|
|
12
|
+
| `to` | If defined, the item will act as a router-link to the specified route. | `string` | - |
|
|
13
|
+
| `external` | Indicates the link is external and opens in a new tab. | `boolean` | - |
|
|
12
14
|
| `icon` | Optional icon component displayed alongside the label. | `Component` | - |
|
|
13
15
|
|
|
14
16
|
## Dependencies
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<section v-if="expanded">
|
|
2
|
+
<section v-if="expanded" class="mc-sidebar__shortcuts">
|
|
3
3
|
<nav role="navigation" aria-label="shortcut items">
|
|
4
4
|
<ul
|
|
5
5
|
:class="{
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
<slot name="default" />
|
|
13
13
|
</ul>
|
|
14
14
|
</nav>
|
|
15
|
+
|
|
16
|
+
<MDivider />
|
|
15
17
|
</section>
|
|
16
18
|
|
|
17
19
|
<div v-else class="mc-sidebar__shortcut-wrapper">
|
|
@@ -32,6 +34,8 @@
|
|
|
32
34
|
<ViewGridX424 />
|
|
33
35
|
</button>
|
|
34
36
|
|
|
37
|
+
<MDivider />
|
|
38
|
+
|
|
35
39
|
<div
|
|
36
40
|
ref="listbox"
|
|
37
41
|
:id="`listbox-${id}`"
|
|
@@ -54,6 +58,7 @@
|
|
|
54
58
|
</template>
|
|
55
59
|
|
|
56
60
|
<script setup lang="ts">
|
|
61
|
+
import MDivider from '../divider/MDivider.vue';
|
|
57
62
|
import { ViewGridX424 } from '@mozaic-ds/icons-vue';
|
|
58
63
|
import { inject, provide, useId, useTemplateRef, type VNode } from 'vue';
|
|
59
64
|
import { useFloatingItem } from '../sidebar/use-floating-item.composable';
|