{ "name": "result", "type": "registry:ui", "title": "Result", "description": "The whole-page outcome — not found, forbidden, server error, done. Announced on render, so a client-side route change to an error page is not silent for a screen reader.", "category": "Feedback", "dependencies": [ "lucide-react@^1.39.0" ], "registryDependencies": [ "lib-motion", "lib-styles", "lib-utils" ], "files": [ { "path": "components/ui/result.tsx", "type": "registry:ui", "content": "import type { ComponentProps, ReactNode } from 'react'\nimport { CheckCircle2, Construction, FileQuestion, Lock, ServerCrash, XCircle } from 'lucide-react'\nimport { enterFade } from '@/lib/motion'\nimport { radius } from '@/lib/styles'\nimport { cn } from '@/lib/utils'\n\n/**\n * The whole-page outcome: not found, forbidden, server error, done.\n *\n * **Distinct from `Empty`, and the difference matters.** An empty state means\n * \"this worked, there is nothing here yet\" and its job is to invite a first\n * action. A result means \"your request ended this way\" — it reports a status,\n * often one the user cannot fix, and its job is to say what happened and where\n * to go next. Using an empty state for a 403 tells someone to create something\n * they are not allowed to see.\n *\n * **Every status offers a way out.** A 404 with no link is a dead end that\n * sends people to the back button; the `actions` slot is where the escape goes,\n * and the presets set an icon and tone but never invent the copy — only you\n * know whether \"Go back\" or \"Contact support\" is right.\n *\n * The status is announced: errors get `role=\"alert\"`, everything else\n * `role=\"status\"`, so a client-side route change to an error page is not\n * silent for a screen reader — which is the failure mode of every SPA that\n * renders one of these without saying anything.\n */\nexport type ResultStatus = 'success' | 'error' | 'warning' | 'info' | '404' | '403' | '500'\n\ntype ResultProps = Omit, 'title'> & {\n status?: ResultStatus\n title: ReactNode\n description?: ReactNode\n /** Buttons or links. A result with no way onward is a dead end. */\n actions?: ReactNode\n /** Replaces the preset icon. */\n icon?: ReactNode\n /** Stack traces, request ids — anything a support ticket would want. */\n details?: ReactNode\n compact?: boolean\n}\n\nconst PRESETS: Record = {\n success: { icon: , tint: 'text-[var(--green-soft-foreground)]', alert: false },\n error: { icon: , tint: 'text-[var(--destructive)]', alert: true },\n warning: { icon: , tint: 'text-[var(--amber-soft-foreground)]', alert: true },\n info: { icon: , tint: 'text-[var(--blue-soft-foreground)]', alert: false },\n '404': { icon: , tint: 'text-muted-foreground', alert: true },\n '403': { icon: , tint: 'text-[var(--amber-soft-foreground)]', alert: true },\n '500': { icon: , tint: 'text-[var(--destructive)]', alert: true },\n}\n\nfunction Result({\n status = 'info',\n title,\n description,\n actions,\n icon,\n details,\n compact = false,\n className,\n ...props\n}: ResultProps) {\n const preset = PRESETS[status]\n\n return (\n \n \n {icon ?? preset.icon}\n \n\n {/* The numeric statuses read as a code first; the sentence is the title. */}\n {(status === '404' || status === '403' || status === '500') && (\n

{status}

\n )}\n\n

\n {title}\n

\n\n {description && (\n

{description}

\n )}\n\n {actions &&
{actions}
}\n\n {details && (\n
\n \n Details\n \n \n {details}\n \n
\n )}\n \n )\n}\n\nexport { Result }\nexport type { ResultProps }\n" } ] }