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}
{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}
);
}