1164841950
- Add a top-level "Import" entry to the sidebar and a global /import page, so you can start a tree from a GEDCOM without first creating an empty one. The import flow now picks its destination (new tree, or an existing one) — the tree-scoped page reuses the same <GedcomImport> with a fixed destination and keeps Export. - Extract the sidebar chrome into <AppShell> and give small screens a working menu: a hamburger opens the full sidebar as a slide-in drawer (it was just a logo + "Trees" link before). Used by both /trees and /import. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
57 lines
2.0 KiB
TypeScript
57 lines
2.0 KiB
TypeScript
"use client";
|
|
|
|
import { Menu } from "lucide-react";
|
|
import Link from "next/link";
|
|
import { useState } from "react";
|
|
|
|
import { AppSidebar } from "@/components/app-sidebar";
|
|
|
|
/**
|
|
* App chrome: a fixed sidebar on md+, and on small screens a top bar with a
|
|
* hamburger that opens the same sidebar as a slide-in drawer.
|
|
*/
|
|
export function AppShell({ children }: { children: React.ReactNode }) {
|
|
const [open, setOpen] = useState(false);
|
|
|
|
return (
|
|
<div className="flex min-h-screen">
|
|
<aside className="sticky top-0 hidden h-screen w-64 shrink-0 border-r border-[var(--border)] bg-[var(--surface)] md:flex md:flex-col">
|
|
<AppSidebar />
|
|
</aside>
|
|
|
|
<div className="flex min-w-0 flex-1 flex-col">
|
|
{/* Mobile top bar */}
|
|
<div className="flex items-center gap-3 border-b border-[var(--border)] bg-[var(--surface)] px-4 py-3 md:hidden">
|
|
<button
|
|
onClick={() => setOpen(true)}
|
|
aria-label="Open menu"
|
|
className="text-[var(--muted)] hover:text-[var(--foreground)]"
|
|
>
|
|
<Menu className="h-6 w-6" />
|
|
</button>
|
|
<Link href="/" aria-label="Provenance — home">
|
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
<img src="/provenance-logo-plain.svg" alt="Provenance" className="h-6 w-auto" />
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Mobile drawer */}
|
|
{open && (
|
|
<div className="fixed inset-0 z-50 md:hidden">
|
|
<div
|
|
className="absolute inset-0 bg-black/40"
|
|
onClick={() => setOpen(false)}
|
|
aria-hidden
|
|
/>
|
|
<aside className="absolute left-0 top-0 h-full w-72 max-w-[85%] border-r border-[var(--border)] bg-[var(--surface)] shadow-xl">
|
|
<AppSidebar onNavigate={() => setOpen(false)} />
|
|
</aside>
|
|
</div>
|
|
)}
|
|
|
|
<div className="mx-auto w-full max-w-4xl px-6 py-10 md:px-10">{children}</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|