feat(ui): make desktop sidebar collapsible
Add a header toggle and a panel close button so the desktop file sidebar collapses with a width transition, mirroring the journal panel. - split onClose (close button) from onNavigate (file selection) so clicking a file no longer collapses the sidebar on desktop - give the aside h-full so the file tree and outline keep bounded scroll regions inside the new wrapper - swap the aside shadow for a gray right border to match the journal
This commit is contained in:
+27
-2
@@ -11,6 +11,8 @@ import {
|
|||||||
TbOutlineMenu2,
|
TbOutlineMenu2,
|
||||||
TbOutlineClipboardList,
|
TbOutlineClipboardList,
|
||||||
TbOutlineHelpCircle,
|
TbOutlineHelpCircle,
|
||||||
|
TbOutlineLayoutSidebarLeftCollapse,
|
||||||
|
TbOutlineLayoutSidebarLeftExpand,
|
||||||
} from "solid-icons/tb";
|
} from "solid-icons/tb";
|
||||||
import { useJournalStream } from "./components/stores/journalStream";
|
import { useJournalStream } from "./components/stores/journalStream";
|
||||||
|
|
||||||
@@ -51,6 +53,7 @@ const App: Component = () => {
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const stream = useJournalStream();
|
const stream = useJournalStream();
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = createSignal(false);
|
const [isSidebarOpen, setIsSidebarOpen] = createSignal(false);
|
||||||
|
const [isDesktopSidebarOpen, setIsDesktopSidebarOpen] = createSignal(true);
|
||||||
const [isDocOpen, setIsDocOpen] = createSignal(false);
|
const [isDocOpen, setIsDocOpen] = createSignal(false);
|
||||||
const [isDataSourceOpen, setIsDataSourceOpen] = createSignal(false);
|
const [isDataSourceOpen, setIsDataSourceOpen] = createSignal(false);
|
||||||
const [isJournalOpen, setIsJournalOpen] = createSignal(false);
|
const [isJournalOpen, setIsJournalOpen] = createSignal(false);
|
||||||
@@ -98,7 +101,7 @@ const App: Component = () => {
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header class="shrink-0 h-16 bg-white shadow z-30">
|
<header class="shrink-0 h-16 bg-white shadow z-30">
|
||||||
<div class="h-full max-w-4xl mx-auto px-4 flex items-center gap-4">
|
<div class="h-full max-w-4xl mx-auto px-4 flex items-center gap-4">
|
||||||
{/* 仅在移动端显示菜单按钮 */}
|
{/* 移动端:打开抽屉式侧边栏 */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsSidebarOpen(true)}
|
onClick={() => setIsSidebarOpen(true)}
|
||||||
class="md:hidden text-gray-600 hover:text-gray-900 p-1 rounded hover:bg-gray-100"
|
class="md:hidden text-gray-600 hover:text-gray-900 p-1 rounded hover:bg-gray-100"
|
||||||
@@ -106,6 +109,18 @@ const App: Component = () => {
|
|||||||
>
|
>
|
||||||
<TbOutlineMenu2 class="inline-block align-[-0.125em]" />
|
<TbOutlineMenu2 class="inline-block align-[-0.125em]" />
|
||||||
</button>
|
</button>
|
||||||
|
{/* 桌面端:折叠/展开固定侧边栏 */}
|
||||||
|
<button
|
||||||
|
onClick={() => setIsDesktopSidebarOpen((v) => !v)}
|
||||||
|
class="hidden md:block text-gray-600 hover:text-gray-900 p-1 rounded hover:bg-gray-100"
|
||||||
|
title={isDesktopSidebarOpen() ? "收起目录" : "展开目录"}
|
||||||
|
>
|
||||||
|
{isDesktopSidebarOpen() ? (
|
||||||
|
<TbOutlineLayoutSidebarLeftCollapse class="inline-block align-[-0.125em]" />
|
||||||
|
) : (
|
||||||
|
<TbOutlineLayoutSidebarLeftExpand class="inline-block align-[-0.125em]" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
<h1 class="text-2xl font-bold text-gray-900">Tabletop Toolkit</h1>
|
<h1 class="text-2xl font-bold text-gray-900">Tabletop Toolkit</h1>
|
||||||
<div class="flex-1" />
|
<div class="flex-1" />
|
||||||
<button
|
<button
|
||||||
@@ -150,12 +165,22 @@ const App: Component = () => {
|
|||||||
|
|
||||||
{/* Body: sidebar + main + journal */}
|
{/* Body: sidebar + main + journal */}
|
||||||
<div class="flex flex-1 min-h-0">
|
<div class="flex flex-1 min-h-0">
|
||||||
{/* 桌面端固定侧边栏 (flex child) */}
|
{/* 桌面端侧边栏 — flex child,可折叠 */}
|
||||||
|
<div
|
||||||
|
class="hidden md:block md:shrink-0 md:overflow-hidden md:transition-all md:duration-300"
|
||||||
|
classList={{
|
||||||
|
"md:w-64": isDesktopSidebarOpen(),
|
||||||
|
"md:w-0": !isDesktopSidebarOpen(),
|
||||||
|
}}
|
||||||
|
inert={!isDesktopSidebarOpen()}
|
||||||
|
>
|
||||||
<DesktopSidebar
|
<DesktopSidebar
|
||||||
fileTree={fileTree()}
|
fileTree={fileTree()}
|
||||||
pathHeadings={pathHeadings()}
|
pathHeadings={pathHeadings()}
|
||||||
onDataSourceOpen={() => setIsDataSourceOpen(true)}
|
onDataSourceOpen={() => setIsDataSourceOpen(true)}
|
||||||
|
onClose={() => setIsDesktopSidebarOpen(false)}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Main content — the scrollable region */}
|
{/* Main content — the scrollable region */}
|
||||||
<ScrollContainerCtx.Provider value={() => mainRef}>
|
<ScrollContainerCtx.Provider value={() => mainRef}>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ interface SidebarContentProps {
|
|||||||
pathHeadings: Record<string, TocNode[]>;
|
pathHeadings: Record<string, TocNode[]>;
|
||||||
currentPath: string;
|
currentPath: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
isDesktop?: boolean;
|
onNavigate?: () => void;
|
||||||
onDataSourceOpen?: () => void;
|
onDataSourceOpen?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +60,6 @@ const SidebarContent: Component<SidebarContentProps> = (props) => {
|
|||||||
>
|
>
|
||||||
<TbOutlineFolderOpen class="inline-block align-[-0.125em]" />
|
<TbOutlineFolderOpen class="inline-block align-[-0.125em]" />
|
||||||
</button>
|
</button>
|
||||||
<Show when={!props.isDesktop}>
|
|
||||||
<button
|
<button
|
||||||
onClick={props.onClose}
|
onClick={props.onClose}
|
||||||
class="text-gray-500 hover:text-gray-700"
|
class="text-gray-500 hover:text-gray-700"
|
||||||
@@ -68,7 +67,6 @@ const SidebarContent: Component<SidebarContentProps> = (props) => {
|
|||||||
>
|
>
|
||||||
<TbOutlineX class="inline-block align-[-0.125em]" />
|
<TbOutlineX class="inline-block align-[-0.125em]" />
|
||||||
</button>
|
</button>
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,7 +82,7 @@ const SidebarContent: Component<SidebarContentProps> = (props) => {
|
|||||||
currentPath={props.currentPath}
|
currentPath={props.currentPath}
|
||||||
pathHeadings={props.pathHeadings}
|
pathHeadings={props.pathHeadings}
|
||||||
depth={0}
|
depth={0}
|
||||||
onClose={props.onClose}
|
onClose={() => props.onNavigate?.()}
|
||||||
isHidden={isFileHidden}
|
isHidden={isFileHidden}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@@ -151,6 +149,7 @@ export const MobileSidebar: Component<SidebarProps> = (props) => {
|
|||||||
pathHeadings={pathHeadings()}
|
pathHeadings={pathHeadings()}
|
||||||
currentPath={location.pathname}
|
currentPath={location.pathname}
|
||||||
onClose={props.onClose}
|
onClose={props.onClose}
|
||||||
|
onNavigate={props.onClose}
|
||||||
onDataSourceOpen={props.onDataSourceOpen}
|
onDataSourceOpen={props.onDataSourceOpen}
|
||||||
/>
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -164,6 +163,7 @@ export const MobileSidebar: Component<SidebarProps> = (props) => {
|
|||||||
export const DesktopSidebar: Component<{
|
export const DesktopSidebar: Component<{
|
||||||
fileTree?: FileNode[];
|
fileTree?: FileNode[];
|
||||||
pathHeadings?: Record<string, TocNode[]>;
|
pathHeadings?: Record<string, TocNode[]>;
|
||||||
|
onClose: () => void;
|
||||||
onDataSourceOpen?: () => void;
|
onDataSourceOpen?: () => void;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -183,13 +183,12 @@ export const DesktopSidebar: Component<{
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside class="hidden md:flex flex-col w-64 shrink-0 bg-white shadow-lg overflow-hidden">
|
<aside class="hidden md:flex flex-col h-full w-64 shrink-0 bg-white border-r border-gray-200 overflow-hidden">
|
||||||
<SidebarContent
|
<SidebarContent
|
||||||
fileTree={fileTree()}
|
fileTree={fileTree()}
|
||||||
pathHeadings={pathHeadings()}
|
pathHeadings={pathHeadings()}
|
||||||
currentPath={location.pathname}
|
currentPath={location.pathname}
|
||||||
onClose={() => {}}
|
onClose={props.onClose}
|
||||||
isDesktop
|
|
||||||
onDataSourceOpen={props.onDataSourceOpen}
|
onDataSourceOpen={props.onDataSourceOpen}
|
||||||
/>
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
Reference in New Issue
Block a user