import type { ReactNode } from "react" import { AlertTriangle } from "lucide-react" import { Card, CardContent, CardHeader } from "~/components/ui/card" import { Skeleton } from "~/components/ui/skeleton" type EmptyStateProps = { icon?: ReactNode title: string description?: ReactNode action?: ReactNode secondaryAction?: ReactNode /** When true, wrap the body in a bordered card frame. Default false — * a bare dashed surface that reads as "nothing here yet". */ framed?: boolean } /** Shared empty state. Centred icon disc + title + description + optional * actions, using theme tokens only. Pair with EmptyState → ErrorState → * content branching so a route never renders a bare blank surface. */ export function EmptyState({ icon, title, description, action, secondaryAction, framed = false, }: EmptyStateProps) { const body = (
{icon ? (
{icon}
) : null}

{title}

{description ? (

{description}

) : null}
{action || secondaryAction ? (
{action} {secondaryAction}
) : null}
) if (framed) { return ( {body} ) } return (
{body}
) } /** Shared error state — a soft destructive-tinted banner. Used both by * routes (data-fetch failures) and as the middle rung of the * skeleton → error → empty → content grammar. */ export function ErrorState({ title = "Something went wrong", message, action, }: { title?: string message: string action?: ReactNode }) { return (

{title}

{message}

{action ?
{action}
: null}
) } /** Card-shaped loading placeholder for card lists/grids. */ export function LoadingCards({ count = 3, variant = "row", }: { count?: number variant?: "row" | "grid" }) { const items = Array.from({ length: count }) return (
{items.map((_, i) => ( ))}
) }