diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/components/CollapsibleToolMenu.vue | 136 | ||||
-rw-r--r-- | src/layouts/base.layout.vue | 38 |
2 files changed, 144 insertions, 30 deletions
diff --git a/src/components/CollapsibleToolMenu.vue b/src/components/CollapsibleToolMenu.vue new file mode 100644 index 0000000..ac54fde --- /dev/null +++ b/src/components/CollapsibleToolMenu.vue @@ -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> diff --git a/src/layouts/base.layout.vue b/src/layouts/base.layout.vue index 383a0b5..5b35f9d 100644 --- a/src/layouts/base.layout.vue +++ b/src/layouts/base.layout.vue @@ -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> |