chore: import upstream snapshot with attribution
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { ChevronDownIcon, HamburgerIcon } from "@/components/icons";
|
||||
import { Sheet, SheetTrigger, SheetContent, SheetTitle } from "@/components/ui/sheet";
|
||||
import type { DocsGroup } from "@/lib/types";
|
||||
|
||||
const COLLAPSE_STORAGE_KEY = "docs-sidebar-collapsed";
|
||||
|
||||
type SidebarNavProps = {
|
||||
groups: DocsGroup[];
|
||||
activeSlug: string;
|
||||
onNavigate?: () => void;
|
||||
};
|
||||
|
||||
function SidebarNav({ groups, activeSlug, onNavigate }: SidebarNavProps) {
|
||||
const initialCollapsed = new Set(
|
||||
groups
|
||||
.filter((g) => g.section !== "Start Here" && !g.items.some((i) => i.slug === activeSlug))
|
||||
.map((g) => g.section),
|
||||
);
|
||||
const [collapsed, setCollapsed] = useState<Set<string>>(initialCollapsed);
|
||||
const restored = useRef(false);
|
||||
|
||||
// Restore the user's expand/collapse choices so navigating between pages
|
||||
// does not reset sections the user opened. (The active section is always
|
||||
// shown expanded via the `hasActive` check below, regardless of this set.)
|
||||
useEffect(() => {
|
||||
if (restored.current) return;
|
||||
restored.current = true;
|
||||
try {
|
||||
const stored = sessionStorage.getItem(COLLAPSE_STORAGE_KEY);
|
||||
if (stored) setCollapsed(new Set(JSON.parse(stored) as string[]));
|
||||
} catch {}
|
||||
}, []);
|
||||
|
||||
function toggle(section: string) {
|
||||
setCollapsed((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(section)) next.delete(section);
|
||||
else next.add(section);
|
||||
try {
|
||||
sessionStorage.setItem(COLLAPSE_STORAGE_KEY, JSON.stringify([...next]));
|
||||
} catch {}
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className="px-3 py-4">
|
||||
{groups.map((group) => {
|
||||
const hasActive = group.items.some((item) => item.slug === activeSlug);
|
||||
const isCollapsed = collapsed.has(group.section) && !hasActive;
|
||||
return (
|
||||
<div key={group.section} className="mb-4">
|
||||
<button
|
||||
type="button"
|
||||
aria-expanded={!isCollapsed}
|
||||
onClick={() => toggle(group.section)}
|
||||
className="flex w-full cursor-pointer items-center justify-between rounded bg-transparent px-3 py-2 text-[0.6875rem] font-semibold uppercase tracking-[0.06em] text-muted transition hover:text-fg"
|
||||
>
|
||||
<span>{group.section}</span>
|
||||
<ChevronDownIcon className={`shrink-0 transition-transform ${isCollapsed ? "-rotate-90" : ""}`} />
|
||||
</button>
|
||||
<div
|
||||
className={`grid transition-[grid-template-rows] duration-200 ${
|
||||
isCollapsed ? "grid-rows-[0fr]" : "grid-rows-[1fr]"
|
||||
}`}
|
||||
>
|
||||
<div className="overflow-hidden">
|
||||
{group.items.map((item) => {
|
||||
const active = item.slug === activeSlug;
|
||||
return (
|
||||
<Link
|
||||
key={item.slug}
|
||||
href={item.path}
|
||||
aria-current={active ? "page" : undefined}
|
||||
onClick={onNavigate}
|
||||
className={`block rounded px-3 py-[0.1875rem] text-[0.8125rem] leading-[1.8] no-underline transition hover:text-fg ${
|
||||
active ? "font-medium text-fg" : "text-muted"
|
||||
}`}
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
type DocsSidebarShellProps = {
|
||||
groups: DocsGroup[];
|
||||
};
|
||||
|
||||
function activeDocForPath(groups: DocsGroup[], pathname: string) {
|
||||
const normalized = pathname.replace(/\/$/, "") || "/";
|
||||
return groups.flatMap((group) => group.items).find((item) => item.path === normalized);
|
||||
}
|
||||
|
||||
export function DocsSidebarShell({ groups }: DocsSidebarShellProps) {
|
||||
const pathname = usePathname();
|
||||
const activeDoc = activeDocForPath(groups, pathname);
|
||||
const activeSlug = activeDoc?.slug ?? "";
|
||||
const currentTitle = activeDoc?.title;
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetTrigger
|
||||
aria-label="Open table of contents"
|
||||
className="sticky top-14 z-40 flex w-full items-center justify-between border-b border-border bg-bg/80 px-6 py-3 backdrop-blur-sm focus:outline-none md:hidden"
|
||||
>
|
||||
<span className="text-sm font-medium text-fg">{currentTitle ?? "Documentation"}</span>
|
||||
<span className="flex h-8 w-8 items-center justify-center text-muted">
|
||||
<HamburgerIcon className="h-4 w-4" />
|
||||
</span>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="overflow-y-auto p-0" showCloseButton={false}>
|
||||
<SheetTitle className="px-6 pt-6">Table of Contents</SheetTitle>
|
||||
<SidebarNav groups={groups} activeSlug={activeSlug} onNavigate={() => setOpen(false)} />
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
||||
<aside
|
||||
aria-label="Documentation"
|
||||
className="hidden md:sticky md:top-14 md:block md:h-[calc(100vh-3.5rem)] md:w-60 md:overflow-y-auto"
|
||||
>
|
||||
<SidebarNav groups={groups} activeSlug={activeSlug} />
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user