Files

101 lines
3.1 KiB
TypeScript

"use client";
import type { ReactNode } from "react";
import { AppNavSheet } from "@/components/navigation/app-nav-sheet";
import { PageTopbar, type ParentCrumb } from "@/components/page-topbar";
import { useHomeAffordance } from "@/hooks/use-home-affordance";
import { cn } from "@/lib/utils";
type PageShellProps = {
// Forwarded verbatim to PageTopbar.
title: string;
subtitle?: string;
icon?: ReactNode;
backHref?: string;
backLabel?: string;
parents?: ParentCrumb[];
titleSlot?: ReactNode;
center?: ReactNode;
leading?: ReactNode;
leftActions?: ReactNode;
actions?: ReactNode;
overflowActions?: ReactNode;
/** Raw menu items shown only when the topbar action budget is exceeded. */
overflowMenuItems?: ReactNode;
/** Primary action retained beside the overflow menu at narrow widths. */
overflowPrimaryAction?: ReactNode;
className?: string;
showStatusTrigger?: boolean;
/** `data-testid` on the topbar header (e2e anchors like `office-topbar`). */
topbarTestId?: string;
/** In-section nav for the phone sheet: settings tree, office sections. */
pageNav?: ReactNode | ((close: () => void) => ReactNode);
/** Manifest destination ids `pageNav` already covers. See `AppNavSections`. */
navOmitDestinations?: string[];
/**
* Off for a surface whose own page *is* the navigation — Settings, where
* `/settings` renders the tree on a phone, so a sheet over it would offer the
* list the user is already looking at.
*/
showNavTrigger?: boolean;
/** "none" for children that own their own scrolling (dockview, full-bleed). */
scroll?: "auto" | "none";
contentClassName?: string;
/** `data-testid` on the scroll container (e2e anchors like `settings-scroll-container`). */
contentTestId?: string;
children: ReactNode;
};
/**
* The one page chrome for top-level routes: `PageTopbar` with the app nav
* trigger following the page actions, the home affordance resolved by
* `useHomeAffordance` (instead of each shell hand-rolling an escape hatch), and
* a scroll container below.
*
* Renders no `<main>` — `AppShell` already owns that landmark; Settings used to
* nest a second one.
*/
export function PageShell({
leading,
pageNav,
navOmitDestinations,
showNavTrigger = true,
scroll = "auto",
contentClassName,
contentTestId,
children,
...topbarProps
}: PageShellProps) {
const home = useHomeAffordance();
const { topbarTestId, ...forwarded } = topbarProps;
return (
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
<PageTopbar
{...forwarded}
testId={topbarTestId}
homeAffordance={home.mode}
homeHref={home.href}
leading={leading}
actions={
<>
{forwarded.actions}
{showNavTrigger && (
<AppNavSheet pageNav={pageNav} omitDestinations={navOmitDestinations} />
)}
</>
}
/>
{scroll === "none" ? (
children
) : (
<div
data-testid={contentTestId}
className={cn("min-h-0 min-w-0 flex-1 overflow-y-auto", contentClassName)}
>
{children}
</div>
)}
</div>
);
}