import type { ReactNode } from "react"; import { cn } from "~/lib/utils"; /** Fades a stack of ghost rows out toward the title. Shared by every page's ghost. */ export const PAGE_EMPTY_GHOST_OPACITY = ["opacity-70", "opacity-45", "opacity-25"]; /** * Page empties accept an icon, faded ghostRows, or a custom ghost. Use fill to center content * in the remaining scroll area when the page has no centered container. */ export function PageEmptyState({ fill = false, align = "center", icon, ghostRows = 0, ghost, title, description, children, }: { fill?: boolean; align?: "center" | "start"; icon?: ReactNode; ghostRows?: number; ghost?: ReactNode; title: ReactNode; description: ReactNode; children?: ReactNode; }) { return (
{ghost !== undefined ? (
{ghost}
) : ghostRows > 0 ? (
{Array.from({ length: ghostRows }, (_, index) => (
))}
) : icon !== undefined ? ( {icon} ) : null}

{title}

{description}

{children}
); } /** A row of equal hint tiles under an empty state, optionally under a small heading. */ export function PageEmptyTiles({ title, narrow = false, children, }: { title?: string; /** Match the 420px text column instead of the full empty state. */ narrow?: boolean; children: ReactNode; }) { return (
{/* Settings section rhythm: the title starts on the tile edge in a 28px row, 8px above the tiles, which sit 12px apart like the other settings tile grids. */} {title ? (

{title}

) : null}
{children}
); } /** `aside` sits at the tile's top right; children pin to its bottom. */ export function PageEmptyTile({ icon, title, description, aside, children, }: { icon: ReactNode; title: ReactNode; description: ReactNode; aside?: ReactNode; children?: ReactNode; }) { return ( // The transparent border matches the settings cards and tiles, so text sits 13px in.
{aside ? ( {icon} {aside} ) : null} {aside ? null : icon} {title} {description} {children ?
{children}
: null}
); }