Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55207beff5 | ||
|
|
e98a344b95 | ||
|
|
d553c3e04c | ||
|
|
e2da2758cc | ||
|
|
3ab70498e6 |
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "litek",
|
"name": "litek",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.16",
|
"version": "0.0.18",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "npm run generate:sitemap && tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"generate:sitemap": "tsx scripts/generate-sitemap.ts",
|
"generate:sitemap": "tsx scripts/generate-sitemap.ts",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { ChevronRight } from "lucide-react";
|
||||||
|
|
||||||
import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarMenuButton, SidebarMenuItem, SidebarMenu, SidebarMenuSub, SidebarMenuSubItem, SidebarMenuSubButton } from "@/components/ui/sidebar";
|
import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarMenuButton, SidebarMenuItem, SidebarMenu, SidebarMenuSub, SidebarMenuSubItem, SidebarMenuSubButton } from "@/components/ui/sidebar";
|
||||||
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@/components/ui/collapsible";
|
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@/components/ui/collapsible";
|
||||||
import { tools, type Tool } from "@/components/tool";
|
import { tools, type Tool } from "@/components/tool";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { ModeToggle } from "@/components/theme/toggle";
|
|
||||||
import { Button } from "../ui/button";
|
|
||||||
import { ChevronRight } from "lucide-react";
|
|
||||||
|
|
||||||
export const AppSidebar = () => {
|
export const AppSidebar = () => {
|
||||||
// 递归构建完整路径
|
// 递归构建完整路径
|
||||||
@@ -13,6 +13,46 @@ export const AppSidebar = () => {
|
|||||||
return `/tool/${pathSegments.join("/")}`;
|
return `/tool/${pathSegments.join("/")}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 递归渲染子菜单内容
|
||||||
|
const renderSubMenuContent = (child: Tool, currentPaths: string[]): ReactNode => {
|
||||||
|
if (child.children) {
|
||||||
|
// 子菜单内的可折叠项
|
||||||
|
return (
|
||||||
|
<Collapsible defaultOpen={false} className="group/collapsible">
|
||||||
|
<CollapsibleTrigger asChild>
|
||||||
|
<SidebarMenuSubButton>
|
||||||
|
{child.icon}
|
||||||
|
<span>{child.name}</span>
|
||||||
|
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
||||||
|
</SidebarMenuSubButton>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
<CollapsibleContent>
|
||||||
|
<SidebarMenuSub>
|
||||||
|
{child.children.map((subChild) => (
|
||||||
|
<SidebarMenuSubItem key={subChild.name}>
|
||||||
|
{renderSubMenuContent(subChild, [...currentPaths, child.path])}
|
||||||
|
</SidebarMenuSubItem>
|
||||||
|
))}
|
||||||
|
</SidebarMenuSub>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</Collapsible>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 叶子节点
|
||||||
|
return (
|
||||||
|
<SidebarMenuSubButton asChild>
|
||||||
|
<Link
|
||||||
|
to={buildFullPath([...currentPaths, child.path])}
|
||||||
|
title={child.description}
|
||||||
|
>
|
||||||
|
{child.icon}
|
||||||
|
<span>{child.name}</span>
|
||||||
|
</Link>
|
||||||
|
</SidebarMenuSubButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// 递归渲染菜单项
|
// 递归渲染菜单项
|
||||||
const renderMenuItem = (tool: Tool, parentPaths: string[] = []): ReactNode => {
|
const renderMenuItem = (tool: Tool, parentPaths: string[] = []): ReactNode => {
|
||||||
const currentPaths = [...parentPaths, tool.path];
|
const currentPaths = [...parentPaths, tool.path];
|
||||||
@@ -20,12 +60,11 @@ export const AppSidebar = () => {
|
|||||||
if (tool.children) {
|
if (tool.children) {
|
||||||
// 有子菜单的项目
|
// 有子菜单的项目
|
||||||
return (
|
return (
|
||||||
|
<SidebarMenuItem key={tool.name}>
|
||||||
<Collapsible
|
<Collapsible
|
||||||
key={tool.name}
|
|
||||||
defaultOpen={false}
|
defaultOpen={false}
|
||||||
className="group/collapsible"
|
className="group/collapsible"
|
||||||
>
|
>
|
||||||
<SidebarMenuItem>
|
|
||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
<SidebarMenuButton tooltip={tool.description}>
|
<SidebarMenuButton tooltip={tool.description}>
|
||||||
{tool.icon}
|
{tool.icon}
|
||||||
@@ -37,25 +76,13 @@ export const AppSidebar = () => {
|
|||||||
<SidebarMenuSub>
|
<SidebarMenuSub>
|
||||||
{tool.children.map((child) => (
|
{tool.children.map((child) => (
|
||||||
<SidebarMenuSubItem key={child.name}>
|
<SidebarMenuSubItem key={child.name}>
|
||||||
{child.children ? (
|
{renderSubMenuContent(child, currentPaths)}
|
||||||
renderMenuItem(child, currentPaths)
|
|
||||||
) : (
|
|
||||||
<SidebarMenuSubButton asChild>
|
|
||||||
<Link
|
|
||||||
to={buildFullPath([...currentPaths, child.path])}
|
|
||||||
title={child.description}
|
|
||||||
>
|
|
||||||
{child.icon}
|
|
||||||
<span>{child.name}</span>
|
|
||||||
</Link>
|
|
||||||
</SidebarMenuSubButton>
|
|
||||||
)}
|
|
||||||
</SidebarMenuSubItem>
|
</SidebarMenuSubItem>
|
||||||
))}
|
))}
|
||||||
</SidebarMenuSub>
|
</SidebarMenuSub>
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</SidebarMenuItem>
|
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
|
</SidebarMenuItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,10 +115,9 @@ export const AppSidebar = () => {
|
|||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
<SidebarFooter className="flex flex-row justify-between items-center gap-2">
|
<SidebarFooter className="flex flex-row justify-between items-center gap-2">
|
||||||
<Button variant="link">
|
<SidebarMenuButton variant="outline" asChild>
|
||||||
<a href="mailto:litek@mail.typist.cc">need more tools?</a>
|
<a href="mailto:litek@mail.typist.cc">need more tools?</a>
|
||||||
</Button>
|
</SidebarMenuButton>
|
||||||
<ModeToggle />
|
|
||||||
</SidebarFooter>
|
</SidebarFooter>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { Outlet } from "react-router-dom";
|
|||||||
import { ThemeProvider } from "@/components/theme/provider"
|
import { ThemeProvider } from "@/components/theme/provider"
|
||||||
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
|
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
|
||||||
import { AppSidebar } from "@/components/sidebar";
|
import { AppSidebar } from "@/components/sidebar";
|
||||||
|
import { ModeToggle } from "@/components/theme/toggle";
|
||||||
|
|
||||||
import { useSEO } from "@/hooks/use-seo";
|
import { useSEO } from "@/hooks/use-seo";
|
||||||
|
|
||||||
export const Layout: FC = () => {
|
export const Layout: FC = () => {
|
||||||
@@ -17,7 +19,7 @@ export const Layout: FC = () => {
|
|||||||
<div className="p-4 flex flex-col w-full h-[100vh] overflow-hidden">
|
<div className="p-4 flex flex-col w-full h-[100vh] overflow-hidden">
|
||||||
<nav className="flex items-center justify-between">
|
<nav className="flex items-center justify-between">
|
||||||
<SidebarTrigger className="size-10" />
|
<SidebarTrigger className="size-10" />
|
||||||
<div role="actions" />
|
<ModeToggle />
|
||||||
</nav>
|
</nav>
|
||||||
<main className="flex-1 overflow-auto p-4 overflow-hidden">
|
<main className="flex-1 overflow-auto p-4 overflow-hidden">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
|||||||
Reference in New Issue
Block a user