feat(menu): collapsible category
This commit is contained in:
		
							parent
							
								
									849981d1ec
								
							
						
					
					
						commit
						b14cd71be9
					
				
							
								
								
									
										136
									
								
								src/components/CollapsibleToolMenu.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								src/components/CollapsibleToolMenu.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,136 @@ | ||||
| <template> | ||||
|   <div v-for="{ name, tools, isCollapsed } of menuOptions" :key="name"> | ||||
|     <n-text tag="div" depth="3" class="category-name" @click="toggleCategoryCollapse({ name })"> | ||||
|       <n-icon :component="ChevronRight" :class="{ rotated: isCollapsed }" size="16" /> | ||||
| 
 | ||||
|       <span> | ||||
|         {{ name }} | ||||
|       </span> | ||||
|     </n-text> | ||||
| 
 | ||||
|     <n-collapse-transition :show="!isCollapsed"> | ||||
|       <div class="menu-wrapper"> | ||||
|         <div class="toggle-bar" @click="toggleCategoryCollapse({ name })" /> | ||||
| 
 | ||||
|         <n-menu | ||||
|           class="menu" | ||||
|           :value="(route.name as string)" | ||||
|           :collapsed-width="64" | ||||
|           :collapsed-icon-size="22" | ||||
|           :options="tools" | ||||
|           :indent="8" | ||||
|           :default-expand-all="true" | ||||
|         /> | ||||
|       </div> | ||||
|     </n-collapse-transition> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts"> | ||||
| import type { Tool, ToolCategory } from '@/tools/tools.types'; | ||||
| import { ChevronRight } from '@vicons/tabler'; | ||||
| import { useStorage } from '@vueuse/core'; | ||||
| import { useThemeVars } from 'naive-ui'; | ||||
| import { toRefs, computed, h } from 'vue'; | ||||
| import { RouterLink, useRoute } from 'vue-router'; | ||||
| import MenuIconItem from './MenuIconItem.vue'; | ||||
| 
 | ||||
| const props = withDefaults(defineProps<{ toolsByCategory?: ToolCategory[] }>(), { toolsByCategory: () => [] }); | ||||
| const { toolsByCategory } = toRefs(props); | ||||
| const route = useRoute(); | ||||
| 
 | ||||
| const makeLabel = (tool: Tool) => () => h(RouterLink, { to: tool.path }, { default: () => tool.name }); | ||||
| const makeIcon = (tool: Tool) => () => h(MenuIconItem, { tool }); | ||||
| 
 | ||||
| const collapsedCategories = useStorage<Record<string, boolean>>( | ||||
|   'menu-tool-option:collapsed-categories', | ||||
|   {}, | ||||
|   undefined, | ||||
|   { | ||||
|     deep: true, | ||||
|     serializer: { | ||||
|       read: (v) => (v ? JSON.parse(v) : null), | ||||
|       write: (v) => JSON.stringify(v), | ||||
|     }, | ||||
|   }, | ||||
| ); | ||||
| 
 | ||||
| function toggleCategoryCollapse({ name }: { name: string }) { | ||||
|   collapsedCategories.value[name] = !collapsedCategories.value[name]; | ||||
| } | ||||
| 
 | ||||
| const menuOptions = computed(() => | ||||
|   toolsByCategory.value.map(({ name, components }) => ({ | ||||
|     name: name, | ||||
|     isCollapsed: collapsedCategories.value[name], | ||||
|     tools: components.map((tool) => ({ | ||||
|       label: makeLabel(tool), | ||||
|       icon: makeIcon(tool), | ||||
|       key: tool.name, | ||||
|     })), | ||||
|   })), | ||||
| ); | ||||
| 
 | ||||
| const themeVars = useThemeVars(); | ||||
| 
 | ||||
| console.log(themeVars.value); | ||||
| </script> | ||||
| 
 | ||||
| <style scoped lang="less"> | ||||
| .category-name { | ||||
|   font-size: 0.93em; | ||||
|   padding: 12px 0 0px 0; | ||||
|   cursor: pointer; | ||||
| 
 | ||||
|   display: flex; | ||||
|   flex-direction: row; | ||||
|   align-items: center; | ||||
|   .n-icon { | ||||
|     transition: transform ease 0.5s; | ||||
|     transform: rotate(90deg); | ||||
|     margin: 0 10px 0 7px; | ||||
|     opacity: 0.5; | ||||
| 
 | ||||
|     &.rotated { | ||||
|       transform: rotate(0deg); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .menu-wrapper { | ||||
|   display: flex; | ||||
|   flex-direction: row; | ||||
|   .menu { | ||||
|     flex: 1; | ||||
|     margin-bottom: 5px; | ||||
| 
 | ||||
|     ::v-deep(.n-menu-item-content::before) { | ||||
|       left: 0; | ||||
|       right: 13px; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .toggle-bar { | ||||
|     width: 25px; | ||||
|     opacity: 0.1; | ||||
|     transition: opacity ease 0.2s; | ||||
|     position: relative; | ||||
|     cursor: pointer; | ||||
| 
 | ||||
|     &::before { | ||||
|       width: 2px; | ||||
|       height: 100%; | ||||
|       content: ' '; | ||||
|       background-color: v-bind('themeVars.textColor3'); | ||||
|       border-radius: 2px; | ||||
|       position: absolute; | ||||
|       top: 0; | ||||
|       left: 14.5px; | ||||
|     } | ||||
| 
 | ||||
|     &:hover { | ||||
|       opacity: 0.5; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </style> | ||||
| @ -1,5 +1,5 @@ | ||||
| <script lang="ts" setup> | ||||
| import { NIcon, useThemeVars, type MenuGroupOption } from 'naive-ui'; | ||||
| import { NIcon, useThemeVars, type MenuGroupOption, type MenuOption } from 'naive-ui'; | ||||
| import { computed, h } from 'vue'; | ||||
| import { RouterLink, useRoute } from 'vue-router'; | ||||
| import { Heart, Menu2, Home2 } from '@vicons/tabler'; | ||||
| @ -7,9 +7,10 @@ import { toolsByCategory } from '@/tools'; | ||||
| import { useStyleStore } from '@/stores/style.store'; | ||||
| import { config } from '@/config'; | ||||
| import MenuIconItem from '@/components/MenuIconItem.vue'; | ||||
| import type { Tool } from '@/tools/tools.types'; | ||||
| import type { Tool, ToolCategory } from '@/tools/tools.types'; | ||||
| import { useToolStore } from '@/tools/tools.store'; | ||||
| import { useTracker } from '@/modules/tracker/tracker.services'; | ||||
| import CollapsibleToolMenu from '@/components/CollapsibleToolMenu.vue'; | ||||
| import SearchBar from '../components/SearchBar.vue'; | ||||
| import HeroGradient from '../assets/hero-gradient.svg?component'; | ||||
| import MenuLayout from '../components/MenuLayout.vue'; | ||||
| @ -21,30 +22,14 @@ const styleStore = useStyleStore(); | ||||
| const version = config.app.version; | ||||
| const commitSha = config.app.lastCommitSha.slice(0, 7); | ||||
| 
 | ||||
| const makeLabel = (tool: Tool) => () => h(RouterLink, { to: tool.path }, { default: () => tool.name }); | ||||
| const makeIcon = (tool: Tool) => () => h(MenuIconItem, { tool }); | ||||
| 
 | ||||
| const { tracker } = useTracker(); | ||||
| 
 | ||||
| const toolStore = useToolStore(); | ||||
| 
 | ||||
| const menuOptions = computed<MenuGroupOption[]>(() => | ||||
|   [ | ||||
|     ...(toolStore.favoriteTools.length > 0 | ||||
|       ? [{ name: 'Your favorite tools', components: toolStore.favoriteTools }] | ||||
|       : []), | ||||
|     ...toolsByCategory, | ||||
|   ].map((category) => ({ | ||||
|     label: category.name, | ||||
|     key: category.name, | ||||
|     type: 'group', | ||||
|     children: category.components.map((tool) => ({ | ||||
|       label: makeLabel(tool), | ||||
|       icon: makeIcon(tool), | ||||
|       key: tool.name, | ||||
|     })), | ||||
|   })), | ||||
| ); | ||||
| const tools = computed<ToolCategory[]>(() => [ | ||||
|   ...(toolStore.favoriteTools.length > 0 ? [{ name: 'Your favorite tools', components: toolStore.favoriteTools }] : []), | ||||
|   ...toolsByCategory, | ||||
| ]); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
| @ -64,14 +49,7 @@ const menuOptions = computed<MenuGroupOption[]>(() => | ||||
|           <navbar-buttons /> | ||||
|         </n-space> | ||||
| 
 | ||||
|         <n-menu | ||||
|           class="menu" | ||||
|           :value="(route.name as string)" | ||||
|           :collapsed-width="64" | ||||
|           :collapsed-icon-size="22" | ||||
|           :options="menuOptions" | ||||
|           :indent="20" | ||||
|         /> | ||||
|         <collapsible-tool-menu :tools-by-category="tools" /> | ||||
| 
 | ||||
|         <div class="footer"> | ||||
|           <div> | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user