diff --git a/src/components/FileTree.tsx b/src/components/FileTree.tsx new file mode 100644 index 0000000..e9e7e81 --- /dev/null +++ b/src/components/FileTree.tsx @@ -0,0 +1,107 @@ +import { Component, createSignal, Show } from "solid-js"; +import { type FileNode, type TocNode } from "../data-loader"; +import { useNavigate } from "@solidjs/router"; + +/** + * 文件树节点组件 + */ +export const FileTreeNode: Component<{ + node: FileNode; + currentPath: string; + pathHeadings: Record; + depth: number; + onClose: () => void; +}> = (props) => { + const navigate = useNavigate(); + const [isExpanded, setIsExpanded] = createSignal(true); + const isDir = !!props.node.children; + const isActive = props.currentPath === props.node.path; + + const handleClick = () => { + if (isDir) { + setIsExpanded(!isExpanded()); + } else { + navigate(props.node.path); + props.onClose(); + } + }; + + const indent = props.depth * 12; + + return ( +
+
+ + {isExpanded() ? "📂" : "📁"} + + + 📄 + + {props.node.name} +
+ +
+ {props.node.children!.map((child) => ( + + ))} +
+
+
+ ); +}; + +/** + * 标题节点组件 + */ +export const HeadingNode: Component<{ + node: TocNode; + basePath: string; + depth: number; +}> = (props) => { + const navigate = useNavigate(); + const anchor = props.node.title.toLowerCase().replace(/\s+/g, "-"); + const href = `${props.basePath}#${anchor}`; + + const handleClick = (e: MouseEvent) => { + e.preventDefault(); + navigate(href); + }; + + const indent = props.depth * 12; + + return ( +
+ + {props.node.title} + + +
+ {props.node.children!.map((child) => ( + + ))} +
+
+
+ ); +}; diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 51fdcd0..72026cc 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,115 +1,13 @@ import { Component, createSignal, onMount, Show } from "solid-js"; import { generateToc, type FileNode, type TocNode } from "../data-loader"; -import { useLocation, useNavigate } from "@solidjs/router"; +import { useLocation } from "@solidjs/router"; +import { FileTreeNode, HeadingNode } from "./FileTree"; interface SidebarProps { isOpen: boolean; onClose: () => void; } -/** - * 文件树节点组件 - */ -const FileTreeNode: Component<{ - node: FileNode; - currentPath: string; - pathHeadings: Record; - depth: number; -}> = (props) => { - const navigate = useNavigate(); - const [isExpanded, setIsExpanded] = createSignal(true); - const isDir = !!props.node.children; - const isActive = props.currentPath === props.node.path; - - const handleClick = () => { - if (isDir) { - setIsExpanded(!isExpanded()); - } else { - navigate(props.node.path); - props.onClose(); - } - }; - - const indent = props.depth * 12; - - return ( -
-
- - {isExpanded() ? "📂" : "📁"} - - - 📄 - - {props.node.name} -
- -
- {props.node.children!.map((child) => ( - - ))} -
-
-
- ); -}; - -/** - * 标题节点组件 - */ -const HeadingNode: Component<{ - node: TocNode; - basePath: string; - depth: number; -}> = (props) => { - const navigate = useNavigate(); - const anchor = props.node.title.toLowerCase().replace(/\s+/g, "-"); - const href = `${props.basePath}#${anchor}`; - - const handleClick = (e: MouseEvent) => { - e.preventDefault(); - navigate(href); - }; - - const indent = props.depth * 12; - - return ( -
- - {props.node.title} - - -
- {props.node.children!.map((child) => ( - - ))} -
-
-
- ); -}; - /** * 侧边栏组件 */ diff --git a/src/components/index.ts b/src/components/index.ts index bf2a490..4280ff2 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -8,6 +8,7 @@ export { Article } from './Article'; export type { ArticleProps } from './Article'; export { Sidebar } from './Sidebar'; export type { SidebarProps } from './Sidebar'; +export { FileTreeNode, HeadingNode } from './FileTree'; // 导出数据类型 export type { DiceProps } from './dice';