@mozaic-ds/vue 2.13.0 → 2.14.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 +1088 -378
- package/dist/mozaic-vue.js +2662 -1854
- package/dist/mozaic-vue.js.map +1 -1
- package/dist/mozaic-vue.umd.cjs +5 -5
- package/dist/mozaic-vue.umd.cjs.map +1 -1
- package/package.json +4 -4
- package/src/components/actionlistbox/MActionListbox.spec.ts +53 -59
- package/src/components/actionlistbox/MActionListbox.stories.ts +22 -1
- package/src/components/actionlistbox/MActionListbox.vue +91 -28
- package/src/components/actionlistbox/README.md +15 -0
- package/src/components/breadcrumb/MBreadcrumb.vue +5 -0
- package/src/components/button/README.md +4 -0
- package/src/components/checkbox/README.md +2 -0
- package/src/components/divider/README.md +4 -0
- package/src/components/iconbutton/MIconButton.stories.ts +12 -0
- package/src/components/iconbutton/MIconButton.vue +13 -1
- package/src/components/iconbutton/README.md +27 -0
- package/src/components/loader/README.md +2 -0
- package/src/components/navigationindicator/MNavigationIndicator.spec.ts +152 -0
- package/src/components/navigationindicator/MNavigationIndicator.stories.ts +41 -0
- package/src/components/navigationindicator/MNavigationIndicator.vue +132 -0
- package/src/components/navigationindicator/README.md +37 -0
- package/src/components/pageheader/MPageHeader.spec.ts +142 -0
- package/src/components/pageheader/MPageHeader.stories.ts +125 -0
- package/src/components/pageheader/MPageHeader.vue +133 -0
- package/src/components/pageheader/README.md +46 -0
- package/src/components/popover/MPopover.spec.ts +106 -0
- package/src/components/popover/MPopover.stories.ts +126 -0
- package/src/components/popover/MPopover.vue +131 -0
- package/src/components/popover/README.md +42 -0
- package/src/components/radio/README.md +2 -0
- package/src/components/select/MSelect.spec.ts +2 -1
- package/src/components/select/MSelect.vue +30 -25
- package/src/components/sidebar/MSidebar.const.ts +6 -0
- package/src/components/sidebar/MSidebar.spec.ts +110 -0
- package/src/components/sidebar/MSidebar.stories.ts +108 -0
- package/src/components/sidebar/MSidebar.vue +124 -0
- package/src/components/sidebar/README.md +59 -0
- package/src/components/sidebar/stories/DefaultCase.stories.vue +120 -0
- package/src/components/sidebar/stories/README.md +27 -0
- package/src/components/sidebar/stories/WithExpandOnly.stories.vue +112 -0
- package/src/components/sidebar/stories/WithProfileInfoOnly.stories.vue +119 -0
- package/src/components/sidebar/stories/WithSingleLevel.stories.vue +98 -0
- package/src/components/sidebar/use-floating-item.composable.ts +135 -0
- package/src/components/sidebar/use-floating-item.spec.ts +251 -0
- package/src/components/sidebarexpandableitem/MSidebarExpandableItem.spec.ts +151 -0
- package/src/components/sidebarexpandableitem/MSidebarExpandableItem.vue +113 -0
- package/src/components/sidebarexpandableitem/README.md +36 -0
- package/src/components/sidebarfooter/MSidebarFooter.spec.ts +276 -0
- package/src/components/sidebarfooter/MSidebarFooter.vue +201 -0
- package/src/components/sidebarfooter/README.md +52 -0
- package/src/components/sidebarfooter/_MSidebarFooterMenu.vue +64 -0
- package/src/components/sidebarheader/MSidebarHeader.vue +36 -0
- package/src/components/sidebarheader/README.md +31 -0
- package/src/components/sidebarnavitem/MSidebarNavItem.spec.ts +127 -0
- package/src/components/sidebarnavitem/MSidebarNavItem.vue +113 -0
- package/src/components/sidebarnavitem/README.md +56 -0
- package/src/components/sidebarshortcutitem/MSidebarShortcutItem.spec.ts +59 -0
- package/src/components/sidebarshortcutitem/MSidebarShortcutItem.vue +52 -0
- package/src/components/sidebarshortcutitem/README.md +32 -0
- package/src/components/sidebarshortcuts/MSidebarShortcuts.spec.ts +87 -0
- package/src/components/sidebarshortcuts/MSidebarShortcuts.vue +101 -0
- package/src/components/sidebarshortcuts/README.md +36 -0
- package/src/components/statusbadge/README.md +12 -0
- package/src/components/textinput/MTextInput.stories.ts +13 -1
- package/src/components/textinput/MTextInput.vue +12 -0
- package/src/components/textinput/README.md +3 -1
- package/src/components/tile/MTile.spec.ts +61 -0
- package/src/components/tile/MTile.stories.ts +102 -0
- package/src/components/tile/MTile.vue +68 -0
- package/src/components/tile/README.md +19 -0
- package/src/components/tileclickable/MTileClickable.spec.ts +130 -0
- package/src/components/tileclickable/MTileClickable.stories.ts +60 -0
- package/src/components/tileclickable/MTileClickable.vue +106 -0
- package/src/components/tileclickable/README.md +30 -0
- package/src/components/tileexpandable/MTileExpandable.spec.ts +121 -0
- package/src/components/tileexpandable/MTileExpandable.stories.ts +50 -0
- package/src/components/tileexpandable/MTileExpandable.vue +131 -0
- package/src/components/tileexpandable/README.md +36 -0
- package/src/components/tileselectable/MTileSelectable.spec.ts +177 -0
- package/src/components/tileselectable/MTileSelectable.stories.ts +55 -0
- package/src/components/tileselectable/MTileSelectable.vue +142 -0
- package/src/components/tileselectable/README.md +44 -0
- package/src/components/toaster/README.md +1 -1
- package/src/components/tooltip/MTooltip.vue +5 -0
- package/src/components/tooltip/README.md +16 -1
- package/src/main.ts +12 -2
- package/src/utils/use-is-mobile.composable.ts +20 -0
- package/src/utils/use-is-mobile.spec.ts +70 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { defineComponent } from 'vue';
|
|
4
|
+
import MSidebarShortcutItem from './MSidebarShortcutItem.vue';
|
|
5
|
+
import {
|
|
6
|
+
EXPANDED_SIDEBAR_KEY,
|
|
7
|
+
STACKED_SHORTCUTS_KEY,
|
|
8
|
+
} from '../sidebar/MSidebar.const';
|
|
9
|
+
|
|
10
|
+
const TestIcon = defineComponent({
|
|
11
|
+
name: 'TestIcon',
|
|
12
|
+
template: '<svg data-testid="test-icon"></svg>',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('MSidebarShortcutItem', () => {
|
|
16
|
+
it('renders label and href', () => {
|
|
17
|
+
const wrapper = mount(MSidebarShortcutItem, {
|
|
18
|
+
props: { label: 'Dashboard', href: '/dashboard' },
|
|
19
|
+
global: {
|
|
20
|
+
provide: {
|
|
21
|
+
[EXPANDED_SIDEBAR_KEY]: true,
|
|
22
|
+
[STACKED_SHORTCUTS_KEY]: true,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
const link = wrapper.find('a.mc-sidebar__shortcut-link');
|
|
27
|
+
expect(link.exists()).toBe(true);
|
|
28
|
+
expect(link.attributes('href')).toBe('/dashboard');
|
|
29
|
+
expect(wrapper.text()).toContain('Dashboard');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('renders icon when provided', () => {
|
|
33
|
+
const wrapper = mount(MSidebarShortcutItem, {
|
|
34
|
+
props: { label: 'WithIcon', href: '#', icon: TestIcon },
|
|
35
|
+
global: {
|
|
36
|
+
provide: {
|
|
37
|
+
[EXPANDED_SIDEBAR_KEY]: true,
|
|
38
|
+
[STACKED_SHORTCUTS_KEY]: true,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
expect(wrapper.find('.mc-sidebar__shortcut-icon').exists()).toBe(true);
|
|
43
|
+
expect(wrapper.find('[data-testid="test-icon"]').exists()).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('does not render icon when not provided', () => {
|
|
47
|
+
const wrapper = mount(MSidebarShortcutItem, {
|
|
48
|
+
props: { label: 'NoIcon', href: '#' },
|
|
49
|
+
global: {
|
|
50
|
+
provide: {
|
|
51
|
+
[EXPANDED_SIDEBAR_KEY]: true,
|
|
52
|
+
[STACKED_SHORTCUTS_KEY]: true,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
expect(wrapper.find('.mc-sidebar__shortcut-icon').exists()).toBe(false);
|
|
57
|
+
expect(wrapper.find('svg').exists()).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<li
|
|
3
|
+
:class="[
|
|
4
|
+
'mc-sidebar__shortcut-item',
|
|
5
|
+
!stacked && expanded
|
|
6
|
+
? 'mc-sidebar__shortcut-item--grid'
|
|
7
|
+
: 'mc-sidebar__shortcut-item--stacked',
|
|
8
|
+
]"
|
|
9
|
+
role="menuitem"
|
|
10
|
+
>
|
|
11
|
+
<a :href="props.href" class="mc-sidebar__shortcut-link">
|
|
12
|
+
<component
|
|
13
|
+
v-if="props.icon"
|
|
14
|
+
:is="props.icon"
|
|
15
|
+
class="mc-sidebar__shortcut-icon"
|
|
16
|
+
/>
|
|
17
|
+
<span class="mc-sidebar__shortcut-label">{{ props.label }}</span>
|
|
18
|
+
</a>
|
|
19
|
+
</li>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script setup lang="ts">
|
|
23
|
+
import { inject, type Component } from 'vue';
|
|
24
|
+
import {
|
|
25
|
+
EXPANDED_SIDEBAR_KEY,
|
|
26
|
+
STACKED_SHORTCUTS_KEY,
|
|
27
|
+
} from '../sidebar/MSidebar.const';
|
|
28
|
+
/**
|
|
29
|
+
* SidebarShortcutItem is a component that renders a sidebar shortcut link with an optional icon and label, providing quick access to a specific URL.
|
|
30
|
+
*/
|
|
31
|
+
const props = defineProps<{
|
|
32
|
+
/**
|
|
33
|
+
* The text label displayed for the shortcut item.
|
|
34
|
+
*/
|
|
35
|
+
label: string;
|
|
36
|
+
/**
|
|
37
|
+
* The URL the shortcut item links to.
|
|
38
|
+
*/
|
|
39
|
+
href: string;
|
|
40
|
+
/**
|
|
41
|
+
* Optional icon component displayed alongside the label.
|
|
42
|
+
*/
|
|
43
|
+
icon?: Component;
|
|
44
|
+
}>();
|
|
45
|
+
|
|
46
|
+
const stacked = inject(STACKED_SHORTCUTS_KEY);
|
|
47
|
+
const expanded = inject(EXPANDED_SIDEBAR_KEY);
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<style lang="scss" scoped>
|
|
51
|
+
@use '@mozaic-ds/styles/components/sidebar';
|
|
52
|
+
</style>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# MSidebarShortcutItem
|
|
2
|
+
|
|
3
|
+
SidebarShortcutItem is a component that renders a sidebar shortcut link with an optional icon and label, providing quick access to a specific URL.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Props
|
|
7
|
+
|
|
8
|
+
| Name | Description | Type | Default |
|
|
9
|
+
| --- | --- | --- | --- |
|
|
10
|
+
| `label*` | The text label displayed for the shortcut item. | `string` | - |
|
|
11
|
+
| `href*` | The URL the shortcut item links to. | `string` | - |
|
|
12
|
+
| `icon` | Optional icon component displayed alongside the label. | `Component` | - |
|
|
13
|
+
|
|
14
|
+
## Dependencies
|
|
15
|
+
|
|
16
|
+
### Used By
|
|
17
|
+
|
|
18
|
+
- [DefaultCase.stories](../sidebar/stories)
|
|
19
|
+
- [WithExpandOnly.stories](../sidebar/stories)
|
|
20
|
+
- [WithProfileInfoOnly.stories](../sidebar/stories)
|
|
21
|
+
- [WithSingleLevel.stories](../sidebar/stories)
|
|
22
|
+
|
|
23
|
+
### Graph
|
|
24
|
+
|
|
25
|
+
```mermaid
|
|
26
|
+
graph TD;
|
|
27
|
+
DefaultCase.stories --> MSidebarShortcutItem
|
|
28
|
+
WithExpandOnly.stories --> MSidebarShortcutItem
|
|
29
|
+
WithProfileInfoOnly.stories --> MSidebarShortcutItem
|
|
30
|
+
WithSingleLevel.stories --> MSidebarShortcutItem
|
|
31
|
+
style MSidebarShortcutItem fill:#008240,stroke:#333,stroke-width:4px
|
|
32
|
+
```
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import { nextTick, ref } from 'vue';
|
|
4
|
+
|
|
5
|
+
const floatingItemIsDisplayed = ref(false);
|
|
6
|
+
const showFloatingItem = vi.fn(() => (floatingItemIsDisplayed.value = true));
|
|
7
|
+
const hideFloatingItem = vi.fn(() => (floatingItemIsDisplayed.value = false));
|
|
8
|
+
const onTriggerKeydown = vi.fn();
|
|
9
|
+
const onListboxKeydown = vi.fn();
|
|
10
|
+
|
|
11
|
+
vi.mock('../sidebar/use-floating-item.composable', () => {
|
|
12
|
+
return {
|
|
13
|
+
useFloatingItem: () => ({
|
|
14
|
+
floatingItemIsDisplayed,
|
|
15
|
+
showFloatingItem,
|
|
16
|
+
hideFloatingItem,
|
|
17
|
+
onTriggerKeydown,
|
|
18
|
+
onListboxKeydown,
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
import MSidebarShortcuts from './MSidebarShortcuts.vue';
|
|
24
|
+
import { EXPANDED_SIDEBAR_KEY } from '../sidebar/MSidebar.const';
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
vi.clearAllMocks();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('MSidebarShortcuts', () => {
|
|
31
|
+
it('renders expanded view when expanded is true', () => {
|
|
32
|
+
const wrapper = mount(MSidebarShortcuts, {
|
|
33
|
+
props: { menuLabel: 'Shortcuts' },
|
|
34
|
+
global: {
|
|
35
|
+
provide: { [EXPANDED_SIDEBAR_KEY as symbol]: true },
|
|
36
|
+
components: { ViewGridX424: { template: '<svg />' } },
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
expect(wrapper.find('section').exists()).toBe(true);
|
|
41
|
+
expect(wrapper.find('.mc-sidebar__trigger').exists()).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('renders trigger when collapsed and responds to interactions', async () => {
|
|
45
|
+
const wrapper = mount(MSidebarShortcuts, {
|
|
46
|
+
props: { menuLabel: 'Shortcuts' },
|
|
47
|
+
attachTo: document.body,
|
|
48
|
+
global: {
|
|
49
|
+
provide: { [EXPANDED_SIDEBAR_KEY as symbol]: false },
|
|
50
|
+
components: { ViewGridX424: { template: '<svg />' } },
|
|
51
|
+
stubs: {
|
|
52
|
+
Teleport: true,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const trigger = wrapper.find('.mc-sidebar__trigger');
|
|
58
|
+
expect(trigger.exists()).toBe(true);
|
|
59
|
+
|
|
60
|
+
await trigger.trigger('mouseenter');
|
|
61
|
+
expect(showFloatingItem).toHaveBeenCalled();
|
|
62
|
+
await nextTick();
|
|
63
|
+
expect(trigger.attributes('aria-expanded')).toBe('true');
|
|
64
|
+
|
|
65
|
+
await trigger.trigger('mouseleave');
|
|
66
|
+
expect(hideFloatingItem).toHaveBeenCalled();
|
|
67
|
+
await nextTick();
|
|
68
|
+
expect(trigger.attributes('aria-expanded')).toBe('false');
|
|
69
|
+
|
|
70
|
+
await trigger.trigger('focus');
|
|
71
|
+
expect(showFloatingItem).toHaveBeenCalled();
|
|
72
|
+
|
|
73
|
+
await trigger.trigger('blur');
|
|
74
|
+
expect(hideFloatingItem).toHaveBeenCalled();
|
|
75
|
+
|
|
76
|
+
await trigger.trigger('keydown', { key: 'ArrowDown' });
|
|
77
|
+
expect(onTriggerKeydown).toHaveBeenCalled();
|
|
78
|
+
|
|
79
|
+
const floating = wrapper.find('.mc-sidebar__floating-item');
|
|
80
|
+
expect(floating.exists()).toBe(true);
|
|
81
|
+
await floating.trigger('keydown', { key: 'Escape' });
|
|
82
|
+
expect(onListboxKeydown).toHaveBeenCalled();
|
|
83
|
+
|
|
84
|
+
await floating.trigger('mouseleave');
|
|
85
|
+
expect(hideFloatingItem).toHaveBeenCalled();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section v-if="expanded">
|
|
3
|
+
<nav role="navigation" aria-label="shortcut items">
|
|
4
|
+
<ul
|
|
5
|
+
:class="{
|
|
6
|
+
'mc-sidebar__shortcut-list': true,
|
|
7
|
+
'mc-sidebar__shortcut-list--stacked': props.stacked,
|
|
8
|
+
'mc-sidebar__shortcut-list--grid': !props.stacked,
|
|
9
|
+
}"
|
|
10
|
+
role="menu"
|
|
11
|
+
>
|
|
12
|
+
<slot name="default" />
|
|
13
|
+
</ul>
|
|
14
|
+
</nav>
|
|
15
|
+
</section>
|
|
16
|
+
|
|
17
|
+
<div v-else class="mc-sidebar__shortcut-wrapper">
|
|
18
|
+
<button
|
|
19
|
+
ref="trigger"
|
|
20
|
+
:id="`trigger-${id}`"
|
|
21
|
+
class="mc-sidebar__shortcut-item mc-sidebar__trigger"
|
|
22
|
+
aria-label="Sidebar shortcuts"
|
|
23
|
+
aria-haspopup="menu"
|
|
24
|
+
:aria-expanded="floatingItemIsDisplayed"
|
|
25
|
+
:aria-controls="`listbox-${id}`"
|
|
26
|
+
@mouseenter="showFloatingItem"
|
|
27
|
+
@focus="showFloatingItem"
|
|
28
|
+
@keydown="onTriggerKeydown"
|
|
29
|
+
@mouseleave="hideFloatingItem"
|
|
30
|
+
@blur="hideFloatingItem"
|
|
31
|
+
>
|
|
32
|
+
<ViewGridX424 />
|
|
33
|
+
</button>
|
|
34
|
+
|
|
35
|
+
<div
|
|
36
|
+
ref="listbox"
|
|
37
|
+
:id="`listbox-${id}`"
|
|
38
|
+
tabindex="0"
|
|
39
|
+
role="menu"
|
|
40
|
+
class="mc-sidebar__floating-item mc-sidebar__floating-item--hidden"
|
|
41
|
+
@keydown="onListboxKeydown"
|
|
42
|
+
@mouseleave="hideFloatingItem"
|
|
43
|
+
@blur="hideFloatingItem"
|
|
44
|
+
>
|
|
45
|
+
<div class="mc-sidebar__listbox">
|
|
46
|
+
<ul
|
|
47
|
+
class="mc-sidebar__shortcut-list mc-sidebar__shortcut-list--floating mc-sidebar__shortcut-list mc-sidebar__shortcut-list--stacked"
|
|
48
|
+
>
|
|
49
|
+
<slot name="default" />
|
|
50
|
+
</ul>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</template>
|
|
55
|
+
|
|
56
|
+
<script setup lang="ts">
|
|
57
|
+
import { ViewGridX424 } from '@mozaic-ds/icons-vue';
|
|
58
|
+
import { inject, provide, useId, useTemplateRef, type VNode } from 'vue';
|
|
59
|
+
import { useFloatingItem } from '../sidebar/use-floating-item.composable';
|
|
60
|
+
import {
|
|
61
|
+
EXPANDED_SIDEBAR_KEY,
|
|
62
|
+
STACKED_SHORTCUTS_KEY,
|
|
63
|
+
} from '../sidebar/MSidebar.const';
|
|
64
|
+
/**
|
|
65
|
+
* SidebarShortcuts is a component that displays a list of sidebar shortcut items. It supports stacked or grid layouts, shows a floating overlay when the sidebar is collapsed, and allows custom shortcut items via slots.
|
|
66
|
+
*/
|
|
67
|
+
const props = defineProps<{
|
|
68
|
+
/**
|
|
69
|
+
* Determines the layout of the sidebar shortcuts. When true, the shortcuts are displayed in a vertical stacked list. When false or not provided, the shortcuts are arranged in a grid layout.
|
|
70
|
+
*/
|
|
71
|
+
stacked?: boolean;
|
|
72
|
+
}>();
|
|
73
|
+
|
|
74
|
+
defineSlots<{
|
|
75
|
+
/**
|
|
76
|
+
* Slot for sidebar shortcut items. Should contain one or more `MSidebarShortcutItem` components.
|
|
77
|
+
*/
|
|
78
|
+
default: VNode[];
|
|
79
|
+
}>();
|
|
80
|
+
|
|
81
|
+
const expanded = inject(EXPANDED_SIDEBAR_KEY);
|
|
82
|
+
|
|
83
|
+
const id = useId();
|
|
84
|
+
|
|
85
|
+
const trigger = useTemplateRef('trigger');
|
|
86
|
+
const listbox = useTemplateRef('listbox');
|
|
87
|
+
|
|
88
|
+
const {
|
|
89
|
+
floatingItemIsDisplayed,
|
|
90
|
+
hideFloatingItem,
|
|
91
|
+
showFloatingItem,
|
|
92
|
+
onTriggerKeydown,
|
|
93
|
+
onListboxKeydown,
|
|
94
|
+
} = useFloatingItem(trigger, listbox);
|
|
95
|
+
|
|
96
|
+
provide(STACKED_SHORTCUTS_KEY, props.stacked);
|
|
97
|
+
</script>
|
|
98
|
+
|
|
99
|
+
<style lang="scss" scoped>
|
|
100
|
+
@use '@mozaic-ds/styles/components/sidebar';
|
|
101
|
+
</style>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# MSidebarShortcuts
|
|
2
|
+
|
|
3
|
+
SidebarShortcuts is a component that displays a list of sidebar shortcut items. It supports stacked or grid layouts, shows a floating overlay when the sidebar is collapsed, and allows custom shortcut items via slots.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Props
|
|
7
|
+
|
|
8
|
+
| Name | Description | Type | Default |
|
|
9
|
+
| --- | --- | --- | --- |
|
|
10
|
+
| `stacked` | Determines the layout of the sidebar shortcuts. When true, the shortcuts are displayed in a vertical stacked list. When false or not provided, the shortcuts are arranged in a grid layout. | `boolean` | - |
|
|
11
|
+
|
|
12
|
+
## Slots
|
|
13
|
+
|
|
14
|
+
| Name | Description |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| `default` | Slot for sidebar shortcut items. Should contain one or more `MSidebarShortcutItem` components. |
|
|
17
|
+
|
|
18
|
+
## Dependencies
|
|
19
|
+
|
|
20
|
+
### Used By
|
|
21
|
+
|
|
22
|
+
- [DefaultCase.stories](../sidebar/stories)
|
|
23
|
+
- [WithExpandOnly.stories](../sidebar/stories)
|
|
24
|
+
- [WithProfileInfoOnly.stories](../sidebar/stories)
|
|
25
|
+
- [WithSingleLevel.stories](../sidebar/stories)
|
|
26
|
+
|
|
27
|
+
### Graph
|
|
28
|
+
|
|
29
|
+
```mermaid
|
|
30
|
+
graph TD;
|
|
31
|
+
DefaultCase.stories --> MSidebarShortcuts
|
|
32
|
+
WithExpandOnly.stories --> MSidebarShortcuts
|
|
33
|
+
WithProfileInfoOnly.stories --> MSidebarShortcuts
|
|
34
|
+
WithSingleLevel.stories --> MSidebarShortcuts
|
|
35
|
+
style MSidebarShortcuts fill:#008240,stroke:#333,stroke-width:4px
|
|
36
|
+
```
|
|
@@ -23,3 +23,15 @@ graph TD;
|
|
|
23
23
|
MStatusBadge --> MStatusDot
|
|
24
24
|
style MStatusBadge fill:#008240,stroke:#333,stroke-width:4px
|
|
25
25
|
```
|
|
26
|
+
|
|
27
|
+
### Used By
|
|
28
|
+
|
|
29
|
+
- [MPageHeader](../pageheader)
|
|
30
|
+
|
|
31
|
+
### Graph
|
|
32
|
+
|
|
33
|
+
```mermaid
|
|
34
|
+
graph TD;
|
|
35
|
+
MPageHeader --> MStatusBadge
|
|
36
|
+
style MStatusBadge fill:#008240,stroke:#333,stroke-width:4px
|
|
37
|
+
```
|
|
@@ -33,7 +33,7 @@ const meta: Meta<typeof MTextInput> = {
|
|
|
33
33
|
return { args, modelValue, handleUpdate };
|
|
34
34
|
},
|
|
35
35
|
template: `
|
|
36
|
-
<MTextInput
|
|
36
|
+
<MTextInput
|
|
37
37
|
v-bind="{ ...args, modelValue }"
|
|
38
38
|
@update:modelValue="handleUpdate"
|
|
39
39
|
/>
|
|
@@ -111,3 +111,15 @@ export const Invalid: Story = {
|
|
|
111
111
|
isInvalid: true,
|
|
112
112
|
},
|
|
113
113
|
};
|
|
114
|
+
|
|
115
|
+
export const WithPrefix = {
|
|
116
|
+
args: {
|
|
117
|
+
prefix: 'Prefix',
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const WithSuffix = {
|
|
122
|
+
args: {
|
|
123
|
+
suffix: 'Suffix',
|
|
124
|
+
},
|
|
125
|
+
};
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
<slot name="icon" />
|
|
5
5
|
</span>
|
|
6
6
|
|
|
7
|
+
<span v-if="prefix" class="mc-text-input__addon">{{ prefix }}</span>
|
|
8
|
+
|
|
7
9
|
<input
|
|
8
10
|
:id="id"
|
|
9
11
|
class="mc-text-input__control"
|
|
@@ -20,6 +22,8 @@
|
|
|
20
22
|
"
|
|
21
23
|
/>
|
|
22
24
|
|
|
25
|
+
<span v-if="suffix" class="mc-text-input__addon">{{ suffix }}</span>
|
|
26
|
+
|
|
23
27
|
<div v-if="isClearable && modelValue" class="mc-controls-options">
|
|
24
28
|
<button
|
|
25
29
|
type="button"
|
|
@@ -96,6 +100,14 @@ const props = withDefaults(
|
|
|
96
100
|
* The label text for the clear button.
|
|
97
101
|
*/
|
|
98
102
|
clearLabel?: string;
|
|
103
|
+
/**
|
|
104
|
+
* Displays additional text inside the component, before the input.
|
|
105
|
+
*/
|
|
106
|
+
prefix?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Displays additional text inside the component, after the input.
|
|
109
|
+
*/
|
|
110
|
+
suffix?: string;
|
|
99
111
|
}>(),
|
|
100
112
|
{
|
|
101
113
|
inputType: 'text',
|
|
@@ -11,13 +11,15 @@ A text input is a single-line input that allows users to enter and edit short te
|
|
|
11
11
|
| `name` | The name attribute for the input element, typically used for form submission. | `string` | - |
|
|
12
12
|
| `modelValue` | The current value of the input field. | `string` `number` | - |
|
|
13
13
|
| `placeholder` | A placeholder text to show in the input when it is empty. | `string` | - |
|
|
14
|
-
| `inputType` | Defines the type of input. | `"number"` `"text"` `"
|
|
14
|
+
| `inputType` | Defines the type of input. | `"number"` `"text"` `"search"` `"date"` `"email"` `"password"` `"tel"` | `"text"` |
|
|
15
15
|
| `isInvalid` | If `true`, applies an invalid state to the input. | `boolean` | - |
|
|
16
16
|
| `disabled` | If `true`, disables the input, making it non-interactive. | `boolean` | - |
|
|
17
17
|
| `size` | Determines the size of the input. | `"s"` `"m"` | `"m"` |
|
|
18
18
|
| `readonly` | If `true`, the input is read-only (cannot be edited). | `boolean` | - |
|
|
19
19
|
| `isClearable` | If `true`, a clear button will appear when the input has a value. | `boolean` | - |
|
|
20
20
|
| `clearLabel` | The label text for the clear button. | `string` | `"clear content"` |
|
|
21
|
+
| `prefix` | Displays additional text inside the component, before the input. | `string` | - |
|
|
22
|
+
| `suffix` | Displays additional text inside the component, after the input. | `string` | - |
|
|
21
23
|
|
|
22
24
|
## Slots
|
|
23
25
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import MTile from './MTile.vue';
|
|
4
|
+
|
|
5
|
+
describe('MTile', () => {
|
|
6
|
+
it('does not apply appearance modifier when appearance is primary', () => {
|
|
7
|
+
const wrapper = mount(MTile, {
|
|
8
|
+
props: {
|
|
9
|
+
appearance: 'primary',
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
expect(wrapper.classes()).not.toContain('mc-tile--primary');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('applies appearance modifier when appearance is not primary', () => {
|
|
17
|
+
const wrapper = mount(MTile, {
|
|
18
|
+
props: {
|
|
19
|
+
appearance: 'secondary',
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
expect(wrapper.classes()).toContain('mc-tile--secondary');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('applies bordered class when bordered is true', () => {
|
|
27
|
+
const wrapper = mount(MTile, {
|
|
28
|
+
props: {
|
|
29
|
+
bordered: true,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
expect(wrapper.classes()).toContain('mc-tile--bordered');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('does not apply bordered class when bordered is false', () => {
|
|
37
|
+
const wrapper = mount(MTile, {
|
|
38
|
+
props: {
|
|
39
|
+
bordered: false,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(wrapper.classes()).not.toContain('mc-tile--bordered');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('does not render details container when slot is missing', () => {
|
|
47
|
+
const wrapper = mount(MTile);
|
|
48
|
+
|
|
49
|
+
expect(wrapper.find('.mc-tile__content').exists()).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('renders details container when slot is provided', () => {
|
|
53
|
+
const wrapper = mount(MTile, {
|
|
54
|
+
slots: {
|
|
55
|
+
details: '<span>Details</span>',
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(wrapper.find('.mc-tile__content').exists()).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
|
+
import MTile from './MTile.vue';
|
|
3
|
+
import MIconButton from '../iconbutton/MIconButton.vue';
|
|
4
|
+
import {
|
|
5
|
+
OptionsVertical24,
|
|
6
|
+
Copy20,
|
|
7
|
+
ArrowTopRight20,
|
|
8
|
+
Download20,
|
|
9
|
+
Trash20,
|
|
10
|
+
} from '@mozaic-ds/icons-vue';
|
|
11
|
+
import MActionListbox from '../actionlistbox/MActionListbox.vue';
|
|
12
|
+
|
|
13
|
+
const meta: Meta<typeof MTile> = {
|
|
14
|
+
title: 'Content/Tile/Base',
|
|
15
|
+
component: MTile,
|
|
16
|
+
parameters: {
|
|
17
|
+
docs: {
|
|
18
|
+
description: {
|
|
19
|
+
component:
|
|
20
|
+
'A tile is a container component used to group related content and actions within a structured layout. It provides a clickable or static area that can display text, images, icons, or interactive elements. Tiles are commonly used to present key information, navigate to detailed views, or trigger specific actions in dashboards, cards, and grid-based layouts. Their adaptable design allows them to accommodate various content types while maintaining a consistent and organized interface.',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
args: {
|
|
25
|
+
appearance: 'primary',
|
|
26
|
+
bordered: true,
|
|
27
|
+
default: `
|
|
28
|
+
<div class="placeholder-slot">
|
|
29
|
+
Insert a form element here to replace this slot.
|
|
30
|
+
</div>
|
|
31
|
+
`,
|
|
32
|
+
},
|
|
33
|
+
render: (args) => ({
|
|
34
|
+
components: {
|
|
35
|
+
MTile,
|
|
36
|
+
MIconButton,
|
|
37
|
+
OptionsVertical24,
|
|
38
|
+
MActionListbox,
|
|
39
|
+
Copy20,
|
|
40
|
+
ArrowTopRight20,
|
|
41
|
+
Download20,
|
|
42
|
+
Trash20,
|
|
43
|
+
},
|
|
44
|
+
setup() {
|
|
45
|
+
const items = [
|
|
46
|
+
{
|
|
47
|
+
label: 'Duplicate',
|
|
48
|
+
icon: Copy20,
|
|
49
|
+
disabled: true,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
label: 'Move to...',
|
|
53
|
+
icon: ArrowTopRight20,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
label: 'Download',
|
|
57
|
+
icon: Download20,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
label: 'Delete',
|
|
61
|
+
icon: Trash20,
|
|
62
|
+
appearance: 'danger',
|
|
63
|
+
divider: true,
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
return { args, items };
|
|
67
|
+
},
|
|
68
|
+
template: `
|
|
69
|
+
<MTile v-bind="args">
|
|
70
|
+
${args.default}
|
|
71
|
+
<template v-if="args.action" #action>
|
|
72
|
+
${args.action}
|
|
73
|
+
</template>
|
|
74
|
+
</MTile>
|
|
75
|
+
`,
|
|
76
|
+
}),
|
|
77
|
+
};
|
|
78
|
+
export default meta;
|
|
79
|
+
type Story = StoryObj<typeof MTile>;
|
|
80
|
+
|
|
81
|
+
export const Default: Story = {};
|
|
82
|
+
|
|
83
|
+
export const WithAction: Story = {
|
|
84
|
+
args: {
|
|
85
|
+
action: `
|
|
86
|
+
<MActionListbox position="left" :items="items">
|
|
87
|
+
<template #activator="{id}">
|
|
88
|
+
<MIconButton
|
|
89
|
+
:popovertarget="id"
|
|
90
|
+
ghost
|
|
91
|
+
class="mc-tile__action-button"
|
|
92
|
+
aria-label="Action button"
|
|
93
|
+
>
|
|
94
|
+
<template #icon>
|
|
95
|
+
<OptionsVertical24 />
|
|
96
|
+
</template>
|
|
97
|
+
</MIconButton>
|
|
98
|
+
</template>
|
|
99
|
+
</MActionListbox>
|
|
100
|
+
`,
|
|
101
|
+
},
|
|
102
|
+
};
|