import { cn } from "@/lib/utils"; export function PageShell({ className, ...props }: React.ComponentProps<"div">) { return (
); } export function PageHeader({ title, description, eyebrow, actions, }: { title: React.ReactNode; description?: React.ReactNode; eyebrow?: React.ReactNode; actions?: React.ReactNode; }) { return (
{/* The eyebrow labels the section; it is not a place for accent colour. */} {eyebrow && (
{eyebrow}
)} {/* Actions sit on the title's baseline, not the whole block's. Aligning them to the bottom of a two-line description is what left the buttons floating in the middle of empty space. */}

{title}

{/* shrink-0 only once there is room to shrink into. On a 360px screen a tab group plus a button is wider than the line, and refusing to shrink pushed the page sideways instead of wrapping. */} {actions && (
{actions}
)}
{description && (

{description}

)}
); } /** * A heading inside a tab, for a screen that holds several related lists rather * than one. Admin uses it because "people" is really three lists — the roster, * who has been invited, who has asked — and they were three tabs telling you to * go looking for the one you wanted. */ export function Section({ title, count, description, actions, children, }: { title: React.ReactNode; count?: number; description?: React.ReactNode; actions?: React.ReactNode; children: React.ReactNode; }) { return (

{title} {count !== undefined && ( {count} )}

{actions}
{description && (

{description}

)} {children}
); } /** A list that is empty, inside a Section. The dashed box of EmptyState is a * whole screen's worth of nothing; a section only needs a line. */ export function SectionEmpty({ children }: { children: React.ReactNode }) { return (

{children}

); } export function EmptyState({ icon: Icon, title, description, action, className, }: { icon: React.ComponentType<{ className?: string }>; title: string; description: string; action?: React.ReactNode; className?: string; }) { return (

{title}

{description}

{action &&
{action}
}
); }