| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273 |
- import { useEffect } from 'react';
- import type { ReactNode } from 'react';
- import { useLocation } from 'react-router-dom';
- import { NavTree } from './NavTree';
- import { Breadcrumb } from './Breadcrumb';
- import { ThemeToggle } from './ThemeToggle';
- import { findTrail } from './nav-utils';
- interface Props {
- children: ReactNode;
- }
- /** 品牌 mark:漏斗三阶 glyph(与 favicon 一致),mint 方块内白色。 */
- function BrandGlyph() {
- return (
- <svg viewBox="0 0 32 32" className="size-4" fill="currentColor" aria-hidden>
- <rect x="7" y="9" width="18" height="3.6" rx="1.8" />
- <rect x="9.5" y="14.6" width="13" height="3.6" rx="1.8" />
- <rect x="12" y="20.2" width="8" height="3.6" rx="1.8" />
- </svg>
- );
- }
- /** Shell: top bar + left nav (L1/L2/L3 tree) + content area (docs/02 §9). */
- export function AppLayout({ children }: Props) {
- const { pathname } = useLocation();
- // 页面标题随路由变:「<当前页> · HS-Data」。
- useEffect(() => {
- const trail = findTrail(pathname);
- const leaf = trail[trail.length - 1];
- document.title = leaf
- ? `${leaf.label} · HS Data`
- : 'HS Data · 数据平台';
- }, [pathname]);
- return (
- <div className="h-screen overflow-hidden flex flex-col bg-background text-foreground">
- {/* 顶栏白、侧栏浅 slate —— 低反差,顶 ≠ 左 */}
- <header className="h-14 flex items-center gap-2.5 px-5 border-b border-border bg-card">
- <span className="grid size-7 shrink-0 place-items-center rounded-md bg-primary text-primary-foreground shadow-sm">
- <BrandGlyph />
- </span>
- <span className="text-[15px] font-bold tracking-tight">
- HS <span className="text-primary">Data</span>
- </span>
- <span className="h-4 w-px bg-border" />
- <span className="text-xs text-muted-foreground">数据平台</span>
- <div className="ml-auto flex items-center gap-2">
- <span className="flex items-center gap-1.5 text-xs text-muted-foreground">
- <span className="size-1.5 rounded-full bg-primary" />
- <span>MVP</span>
- </span>
- <ThemeToggle />
- </div>
- </header>
- <div className="flex flex-1 min-h-0">
- <aside className="w-56 shrink-0 overflow-y-auto border-r border-border bg-sidebar text-sidebar-foreground">
- <NavTree />
- </aside>
- {/* scrollbar-gutter: stable —— 始终预留滚动条宽度,内容增高出现滚动条时不再横向抖动 */}
- <main className="flex-1 min-w-0 overflow-auto [scrollbar-gutter:stable]">
- <div className="p-6 flex flex-col gap-4">
- <Breadcrumb />
- {children}
- </div>
- </main>
- </div>
- </div>
- );
- }
- export default AppLayout;
|