{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"chat-error","type":"registry:ui","title":"ChatError","description":"Quiet inline failure banner for a chat turn: the error, an optional detail line, and a retry button that disables itself while the retry is in flight","author":"fancy-ui <https://fancy-ui.rama.app>","categories":["ai-chat"],"registryDependencies":["https://fancy-ui.rama.app/r/internals-dom.json","https://fancy-ui.rama.app/r/sound.json","https://fancy-ui.rama.app/r/utils.json"],"files":[{"path":"react/src/components/chat-error/ChatError.tsx","content":"\"use client\";\n\nimport { forwardRef, type ReactNode } from \"react\";\nimport { cn } from \"../../utils\";\nimport { useSoundCue } from \"../../sound/use-sound\";\nimport \"./chat-error.css\";\n\n/**\n * Props for ChatError\n */\nexport interface ChatErrorProps {\n\t/** The failure line, e.g. \"Something went wrong\" */\n\tmessage?: string;\n\t/** Secondary muted line under the message, e.g. the error code */\n\tdetail?: string;\n\t/** Called when the retry button is pressed. The button only exists when this is set. */\n\tonRetry?: () => void;\n\t/** Label for the retry button */\n\tretryLabel?: string;\n\t/** Whether a retry is in flight: disables the button and marks the row busy */\n\tretrying?: boolean;\n\t/** Leading icon, replacing the default warning triangle */\n\ticon?: ReactNode;\n\t/** Rendered instead of the message and detail block */\n\tchildren?: ReactNode;\n\t/** Additional CSS classes */\n\tclassName?: string;\n\t/**\n\t * Plays the press cue through the sound controller when retry is\n\t * pressed. Off by default; only audible once the user has enabled\n\t * sound.\n\t */\n\tsound?: boolean;\n}\n\n/**\n * ChatError\n */\nexport const ChatError = forwardRef<HTMLDivElement, ChatErrorProps>(function ChatError(\n\t{\n\t\tmessage = \"Something went wrong\",\n\t\tdetail,\n\t\tonRetry,\n\t\tretryLabel = \"Retry\",\n\t\tretrying = false,\n\t\ticon,\n\t\tchildren,\n\t\tclassName,\n\t\tsound = false,\n\t},\n\tref\n) {\n\tconst playCue = useSoundCue(sound);\n\n\t// Mirrors Button's own guard: `retrying` disables the button natively, but\n\t// a synthetic click (or any dispatch that bypasses jsdom's disabled\n\t// handling) walks straight past that, so the handler checks again before\n\t// playing anything or calling out.\n\tfunction handleRetry() {\n\t\tif (retrying) return;\n\t\tplayCue(\"press\");\n\t\tonRetry?.();\n\t}\n\n\treturn (\n\t\t<div\n\t\t\tref={ref}\n\t\t\tclassName={cn(\n\t\t\t\t\"ft-error flex w-full items-start gap-2.5 rounded-md border px-3 py-2 text-sm\",\n\t\t\t\tclassName\n\t\t\t)}\n\t\t\trole=\"alert\"\n\t\t\taria-busy={retrying ? \"true\" : undefined}\n\t\t>\n\t\t\t{/*\n\t\t\t\tDecorative either way: the failure is carried by the message text, so the\n\t\t\t\ticon is hidden from assistive tech whether it is the default triangle or a\n\t\t\t\tcaller's own icon.\n\t\t\t*/}\n\t\t\t<span className=\"ft-error-icon mt-0.5 flex-none\" aria-hidden=\"true\">\n\t\t\t\t{icon ?? (\n\t\t\t\t\t<svg\n\t\t\t\t\t\tclassName=\"size-4\"\n\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\tstrokeWidth=\"2\"\n\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<path d=\"M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z\" />\n\t\t\t\t\t\t<path d=\"M12 9v4\" />\n\t\t\t\t\t\t<path d=\"M12 17h.01\" />\n\t\t\t\t\t</svg>\n\t\t\t\t)}\n\t\t\t</span>\n\n\t\t\t<div className=\"ft-error-body min-w-0 flex-1\">\n\t\t\t\t{children ?? (\n\t\t\t\t\t<>\n\t\t\t\t\t\t<p className=\"ft-error-message text-foreground\">{message}</p>\n\t\t\t\t\t\t{detail && <p className=\"ft-error-detail text-foreground/70 mt-0.5 text-xs\">{detail}</p>}\n\t\t\t\t\t</>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t{onRetry && (\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName=\"ft-error-retry text-foreground/80 hover:text-foreground hover:bg-foreground/5 -my-0.5 flex flex-none items-center gap-1.5 rounded px-2 py-1 text-xs font-medium transition-colors disabled:pointer-events-none disabled:opacity-60\"\n\t\t\t\t\tdisabled={retrying}\n\t\t\t\t\tonClick={handleRetry}\n\t\t\t\t>\n\t\t\t\t\t{retrying && <span className=\"ft-error-dot\" aria-hidden=\"true\"></span>}\n\t\t\t\t\t{retryLabel}\n\t\t\t\t</button>\n\t\t\t)}\n\t\t</div>\n\t);\n});\n","type":"registry:ui","target":"@ui/fancy/components/chat-error/ChatError.tsx"},{"path":"react/src/components/chat-error/chat-error.css","content":"/*\n * Every colour derives from `--ft-error-fg` at the point of use rather than\n * being redeclared on the root, so setting that one variable anywhere up the\n * tree — inline style, a theme class — retints the whole row without having\n * to win a specificity fight against these scoped rules. Unset, it falls\n * through to `--ft-status-error`, the failure colour this component family\n * shares, so a theme can speak once and be heard by all of them.\n */\n.ft-error {\n\tbackground: var(\n\t\t--ft-error-bg,\n\t\tcolor-mix(\n\t\t\tin oklab,\n\t\t\tvar(\n\t\t\t\t\t--ft-error-fg,\n\t\t\t\t\tvar(--ft-status-error, light-dark(oklch(0.5 0.19 25), oklch(0.7 0.18 25)))\n\t\t\t\t)\n\t\t\t\t8%,\n\t\t\ttransparent\n\t\t)\n\t);\n\tborder-color: var(\n\t\t--ft-error-border,\n\t\tcolor-mix(\n\t\t\tin oklab,\n\t\t\tvar(\n\t\t\t\t\t--ft-error-fg,\n\t\t\t\t\tvar(--ft-status-error, light-dark(oklch(0.5 0.19 25), oklch(0.7 0.18 25)))\n\t\t\t\t)\n\t\t\t\t20%,\n\t\t\ttransparent\n\t\t)\n\t);\n}\n\n.ft-error-icon {\n\tcolor: var(\n\t\t--ft-error-fg,\n\t\tvar(--ft-status-error, light-dark(oklch(0.5 0.19 25), oklch(0.7 0.18 25)))\n\t);\n}\n\n.ft-error-dot {\n\tdisplay: inline-block;\n\tflex: none;\n\twidth: 0.375rem;\n\theight: 0.375rem;\n\tborder-radius: 9999px;\n\tbackground: currentColor;\n\topacity: 0.75;\n}\n\n/*\n * Both animations live behind `no-preference`, so reduced motion is not a\n * degraded variant to keep in sync: the row simply appears, and the dot stays\n * a plain dot that still marks the retry as in flight.\n */\n@media (prefers-reduced-motion: no-preference) {\n\t.ft-error {\n\t\tanimation: ft-error-in 220ms cubic-bezier(0.4, 0, 0.2, 1);\n\t}\n\n\t.ft-error-dot {\n\t\tanimation: ft-error-pulse 1.1s ease-in-out infinite;\n\t}\n}\n\n@keyframes ft-error-in {\n\tfrom {\n\t\topacity: 0;\n\t\ttransform: translateY(-2px);\n\t}\n\tto {\n\t\topacity: 1;\n\t\ttransform: none;\n\t}\n}\n\n@keyframes ft-error-pulse {\n\t0%,\n\t100% {\n\t\topacity: 0.25;\n\t}\n\t50% {\n\t\topacity: 0.9;\n\t}\n}\n","type":"registry:ui","target":"@ui/fancy/components/chat-error/chat-error.css"},{"path":"react/src/components/chat-error/index.ts","content":"\"use client\";\n\nexport { ChatError } from \"./ChatError\";\nexport type { ChatErrorProps } from \"./ChatError\";\n","type":"registry:ui","target":"@ui/fancy/components/chat-error/index.ts"}],"meta":{"license":"MIT","package":"fancy-ui-react","version":"0.7.0","source":"https://fancy-ui.rama.app/docs/components/chat-error?framework=react"}}