learning_ai_notes/web/src/components/AppShell.tsx

70 lines
2.1 KiB
TypeScript

"use client";
import { type ReactNode, useState, useCallback, useEffect } from "react";
import { usePathname } from "next/navigation";
import { Sidebar } from "@/components/Sidebar";
export function AppShell({
title,
description,
actions,
children,
}: {
title: string;
description: string;
actions?: ReactNode;
children: ReactNode;
}) {
const [sidebarOpen, setSidebarOpen] = useState(false);
const pathname = usePathname();
useEffect(() => {
setSidebarOpen(false);
}, [pathname]);
const toggle = useCallback(() => setSidebarOpen((o) => !o), []);
const close = useCallback(() => setSidebarOpen(false), []);
return (
<div className="app-shell">
<a href="#main-content" className="skip-link">
Skip to main content
</a>
<button
className="sidebar-toggle"
onClick={toggle}
aria-label={sidebarOpen ? "Close menu" : "Open menu"}
type="button"
>
{sidebarOpen ? "\u2715" : "\u2630"}
</button>
<div
className={`sidebar-overlay${sidebarOpen ? " open" : ""}`}
onClick={close}
aria-hidden="true"
/>
<Sidebar open={sidebarOpen} />
<main id="main-content" className="main-panel" tabIndex={-1} aria-labelledby="page-title">
<div className="page-grid">
<header
className="surface-card"
style={{ padding: "var(--nl-space-6)", display: "flex", justifyContent: "space-between", gap: "var(--nl-space-4)", alignItems: "start", flexWrap: "wrap" }}
>
<div style={{ display: "grid", gap: "var(--nl-space-2)" }}>
<h1
id="page-title"
style={{ margin: 0, fontFamily: "var(--nl-font-display)", fontSize: "var(--nl-fs-2xl)", fontWeight: 700 }}
>
{title}
</h1>
<div style={{ color: "var(--nl-text-secondary)", maxWidth: 720 }}>{description}</div>
</div>
{actions ? <div aria-label="Page actions">{actions}</div> : null}
</header>
{children}
</div>
</main>
</div>
);
}