aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/CollapsibleToolMenu.vue136
-rw-r--r--src/layouts/base.layout.vue38
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>