feat: reactive sidebar
This commit is contained in:
+108
-54
@@ -6,12 +6,80 @@ import { FileTreeNode, HeadingNode } from "./FileTree";
|
||||
interface SidebarProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
isDesktop?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 侧边栏组件
|
||||
* 侧边栏内容组件
|
||||
*/
|
||||
export const Sidebar: Component<SidebarProps> = (props) => {
|
||||
const SidebarContent: Component<{
|
||||
fileTree: FileNode[];
|
||||
pathHeadings: Record<string, TocNode[]>;
|
||||
currentPath: string;
|
||||
onClose: () => void;
|
||||
}> = (props) => {
|
||||
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) => {
|
||||
const location = useLocation();
|
||||
const [fileTree, setFileTree] = createSignal<FileNode[]>([]);
|
||||
const [pathHeadings, setPathHeadings] = createSignal<
|
||||
@@ -25,72 +93,58 @@ export const Sidebar: Component<SidebarProps> = (props) => {
|
||||
setPathHeadings(toc.pathHeadings);
|
||||
});
|
||||
|
||||
// 响应式获取当前文件的标题列表
|
||||
const currentFileHeadings = createMemo(() => {
|
||||
const pathname = location.pathname;
|
||||
return pathHeadings()[pathname] || pathHeadings()[`${pathname}.md`] || [];
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 遮罩层 */}
|
||||
<div
|
||||
class={`fixed inset-0 bg-black/50 z-40 transition-opacity duration-300 ease-in-out ${
|
||||
class={`fixed inset-0 bg-black/50 z-40 transition-opacity duration-300 ease-in-out md:hidden ${
|
||||
props.isOpen ? "opacity-100" : "opacity-0 pointer-events-none"
|
||||
}`}
|
||||
onClick={props.onClose}
|
||||
/>
|
||||
{/* 侧边栏 */}
|
||||
<aside
|
||||
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 ${
|
||||
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 ${
|
||||
props.isOpen ? "translate-x-0" : "-translate-x-full"
|
||||
}`}
|
||||
>
|
||||
<div class="p-4">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-lg font-bold text-gray-900">目录</h2>
|
||||
<button
|
||||
onClick={props.onClose}
|
||||
class="text-gray-500 hover:text-gray-700"
|
||||
title="关闭"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 文件树 */}
|
||||
<div class="mb-4">
|
||||
<h3 class="text-xs font-semibold text-gray-500 uppercase mb-2 px-2">
|
||||
文件
|
||||
</h3>
|
||||
{fileTree().map((node) => (
|
||||
<FileTreeNode
|
||||
node={node}
|
||||
currentPath={location.pathname}
|
||||
pathHeadings={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>
|
||||
<SidebarContent
|
||||
fileTree={fileTree()}
|
||||
pathHeadings={pathHeadings()}
|
||||
currentPath={location.pathname}
|
||||
onClose={props.onClose}
|
||||
/>
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 桌面端固定侧边栏
|
||||
*/
|
||||
export const DesktopSidebar: Component = () => {
|
||||
const location = useLocation();
|
||||
const [fileTree, setFileTree] = createSignal<FileNode[]>([]);
|
||||
const [pathHeadings, setPathHeadings] = createSignal<
|
||||
Record<string, TocNode[]>
|
||||
>({});
|
||||
|
||||
// 加载目录数据
|
||||
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={() => {}}
|
||||
isDesktop={true}
|
||||
/>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user