main
vue 183 lines 3.82 KB
Raw
1 <template>
2 <nav class="nav" :class="[{ collapsed }]">
3 <n-menu
4 ref="menu"
5 v-model:value="selectedKey"
6 :options="menuOptions"
7 :collapsed
8 :indent="18"
9 :mode
10 accordion
11 :collapsed-width
12 :dropdown-props="{
13 scrollable: true,
14 menuProps: () => ({
15 class: 'main-nav'
16 })
17 }"
18 :expanded-keys
19 @update:expanded-keys="handleUpdateExpandedKeys"
20 />
21 </nav>
22 </template>
23
24 <script lang="ts" setup>
25 import type { MenuInst } from "naive-ui"
26 import type { MenuMixedOption } from "naive-ui/es/menu/src/interface"
27 import type { RouteRecordNormalized } from "vue-router"
28 import _uniq from "lodash/uniq"
29 import { NMenu } from "naive-ui"
30 import { computed, onBeforeMount, ref } from "vue"
31 import { useRoute, useRouter } from "vue-router"
32 import { useThemeStore } from "@/stores/theme"
33 import getItems from "./items"
34
35 const { collapsed = false } = defineProps<{
36 collapsed?: boolean
37 mode?: "horizontal" | "vertical"
38 }>()
39
40 const route = useRoute()
41 const router = useRouter()
42 const selectedKey = ref<string | null>(null)
43 const menu = ref<MenuInst | null>(null)
44 const expandedKeys = ref<string[] | undefined>(undefined)
45
46 const themeStore = useThemeStore()
47
48 const menuOptions = computed<MenuMixedOption[]>(() => getItems())
49 const collapsedWidth = computed<number>(() => themeStore.sidebar.closeWidth)
50 const sidebarCollapsed = computed<boolean>(() => themeStore.sidebar.collapsed)
51
52 function setMenuKey(matched: RouteRecordNormalized[]) {
53 for (const match of matched) {
54 if (match.name && typeof match.name === "string") {
55 selectedKey.value = match.name?.toString() || null
56 if (selectedKey.value) {
57 menu.value?.showOption(selectedKey.value)
58 }
59 }
60 }
61 }
62
63 onBeforeMount(() => {
64 setMenuKey(route.matched)
65
66 router.afterEach(route => {
67 if (route?.matched?.length) {
68 setMenuKey(route.matched)
69
70 if (window.innerWidth <= 700 && !sidebarCollapsed.value) {
71 themeStore.closeSidebar()
72 }
73 }
74 })
75 })
76
77 // handler to simulate the accordion behavior in a specific submenu
78 function handleUpdateExpandedKeys(value: string[]) {
79 const submenu = "components"
80
81 if (value?.length && value.includes(submenu)) {
82 const lastKey = value.pop()
83 if (lastKey) {
84 expandedKeys.value = _uniq([submenu, lastKey])
85 }
86 } else {
87 expandedKeys.value = undefined
88 }
89 }
90 </script>
91
92 <style lang="scss" scoped>
93 .nav {
94 &.collapsed {
95 pointer-events: none;
96 }
97
98 :deep() {
99 .n-menu-item-group {
100 .n-menu-item-group-title {
101 white-space: nowrap;
102 overflow: hidden;
103 text-overflow: ellipsis;
104 }
105 }
106
107 .n-submenu-children {
108 --dash-width: 8px;
109 --dash-height: 2px;
110 --dash-offset: 29px;
111
112 position: relative;
113
114 &::before {
115 content: "";
116 display: block;
117 background-color: var(--border-color);
118 width: var(--dash-height);
119 position: absolute;
120 top: 0px;
121 bottom: 20px;
122 left: var(--dash-offset);
123 }
124
125 .n-menu-item-content {
126 &::after {
127 content: "";
128 display: block;
129 background-color: var(--border-color);
130 width: var(--dash-width);
131 height: var(--dash-height);
132 position: absolute;
133 z-index: -1;
134 top: calc(50% - calc(ar(--dash-height) / 2));
135 left: calc(var(--dash-offset) + var(--dash-height));
136 }
137 }
138
139 .n-menu-item-group {
140 .n-menu-item-group-title {
141 padding-left: 44px !important;
142 }
143 }
144
145 .n-submenu-children {
146 &::before {
147 display: none;
148 }
149 .n-menu-item-content {
150 &::after {
151 width: calc(var(--dash-width) * 3);
152 background: repeating-linear-gradient(
153 90deg,
154 var(--border-color) 0px,
155 var(--border-color) 5px,
156 transparent 5px,
157 transparent 8px
158 );
159 }
160 }
161 }
162 }
163
164 .n-menu--horizontal {
165 .n-menu-item-content {
166 .n-menu-item-content-header {
167 overflow: initial;
168 }
169 }
170 }
171 }
172 }
173
174 .direction-rtl {
175 .nav {
176 :deep() {
177 .n-submenu-children {
178 --dash-offset: 25px;
179 }
180 }
181 }
182 }
183 </style>