ttrpg-tools/src/components/Sidebar.tsx

149 lines
4.1 KiB
TypeScript
Raw Normal View History

2026-02-26 15:10:03 +08:00
import { Component, createMemo, createSignal, onMount, Show } from "solid-js";
2026-02-26 14:24:48 +08:00
import { generateToc, type FileNode, type TocNode } from "../data-loader";
2026-02-26 15:04:17 +08:00
import { useLocation } from "@solidjs/router";
import { FileTreeNode, HeadingNode } from "./FileTree";
2026-02-26 14:24:48 +08:00
2026-02-27 12:34:55 +08:00
export interface SidebarProps {
2026-02-26 14:24:48 +08:00
isOpen: boolean;
onClose: () => void;
}
2026-02-27 12:34:55 +08:00
interface SidebarContentProps {
2026-02-26 15:22:40 +08:00
fileTree: FileNode[];
pathHeadings: Record<string, TocNode[]>;
currentPath: string;
onClose: () => void;
2026-02-27 12:34:55 +08:00
isDesktop?: boolean;
}
/**
*
*/
const SidebarContent: Component<SidebarContentProps> = (props) => {
2026-02-26 15:22:40 +08:00
const location = useLocation();
// 响应式获取当前文件的标题列表
const currentFileHeadings = createMemo(() => {
const pathname = location.pathname;
return props.pathHeadings[pathname] || props.pathHeadings[`${pathname}.md`] || [];
});
return (
<div class="p-4">
<div class="flex items-center justify-between mb-4">
<h2 class="text-lg font-bold text-gray-900"></h2>
<Show when={!props.isDesktop}>
<button
onClick={props.onClose}
class="text-gray-500 hover:text-gray-700"
title="关闭"
>
</button>
</Show>
</div>
{/* 文件树 */}
<div class="mb-4">
<h3 class="text-xs font-semibold text-gray-500 uppercase mb-2 px-2">
</h3>
{props.fileTree.map((node) => (
<FileTreeNode
node={node}
currentPath={props.currentPath}
pathHeadings={props.pathHeadings}
depth={0}
onClose={props.onClose}
/>
))}
</div>
{/* 当前文件标题 */}
<Show when={currentFileHeadings().length > 0}>
<div>
<h3 class="text-xs font-semibold text-gray-500 uppercase mb-2 px-2">
</h3>
{currentFileHeadings().map((node) => (
<HeadingNode
node={node}
basePath={location.pathname}
depth={0}
/>
))}
</div>
</Show>
</div>
);
};
/**
*
*/
export const MobileSidebar: Component<SidebarProps> = (props) => {
2026-02-26 14:24:48 +08:00
const location = useLocation();
const [fileTree, setFileTree] = createSignal<FileNode[]>([]);
2026-02-27 12:34:55 +08:00
const [pathHeadings, setPathHeadings] = createSignal<Record<string, TocNode[]>>({});
2026-02-26 14:24:48 +08:00
2026-02-26 15:10:03 +08:00
// 加载目录数据
2026-02-26 14:30:09 +08:00
onMount(async () => {
const toc = await generateToc();
2026-02-26 14:24:48 +08:00
setFileTree(toc.fileTree);
setPathHeadings(toc.pathHeadings);
});
return (
2026-02-26 15:02:28 +08:00
<>
2026-02-26 14:24:48 +08:00
{/* 遮罩层 */}
<div
2026-02-26 15:22:40 +08:00
class={`fixed inset-0 bg-black/50 z-40 transition-opacity duration-300 ease-in-out md:hidden ${
2026-02-26 15:02:28 +08:00
props.isOpen ? "opacity-100" : "opacity-0 pointer-events-none"
}`}
2026-02-26 14:24:48 +08:00
onClick={props.onClose}
/>
{/* 侧边栏 */}
2026-02-26 15:02:28 +08:00
<aside
2026-02-26 15:22:40 +08:00
class={`fixed top-0 left-0 h-full w-64 bg-white shadow-lg z-50 overflow-y-auto transform transition-transform duration-300 ease-in-out md:hidden ${
2026-02-26 15:02:28 +08:00
props.isOpen ? "translate-x-0" : "-translate-x-full"
}`}
>
2026-02-26 15:22:40 +08:00
<SidebarContent
fileTree={fileTree()}
pathHeadings={pathHeadings()}
currentPath={location.pathname}
onClose={props.onClose}
/>
2026-02-26 14:24:48 +08:00
</aside>
2026-02-26 15:02:28 +08:00
</>
2026-02-26 14:24:48 +08:00
);
};
2026-02-26 15:22:40 +08:00
/**
*
*/
2026-02-27 12:34:55 +08:00
export const DesktopSidebar: Component<{}> = () => {
2026-02-26 15:22:40 +08:00
const location = useLocation();
const [fileTree, setFileTree] = createSignal<FileNode[]>([]);
2026-02-27 12:34:55 +08:00
const [pathHeadings, setPathHeadings] = createSignal<Record<string, TocNode[]>>({});
2026-02-26 15:22:40 +08:00
// 加载目录数据
onMount(async () => {
const toc = await generateToc();
setFileTree(toc.fileTree);
setPathHeadings(toc.pathHeadings);
});
return (
<aside class="hidden md:block fixed top-0 left-0 h-full w-64 bg-white shadow-lg z-30 overflow-y-auto pt-16">
<SidebarContent
fileTree={fileTree()}
pathHeadings={pathHeadings()}
currentPath={location.pathname}
onClose={() => {}}
2026-02-27 12:34:55 +08:00
isDesktop
2026-02-26 15:22:40 +08:00
/>
</aside>
);
};