{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "response-actions",
  "title": "Response Actions",
  "description": "The row under an answer: copy it, ask again, and rate it.",
  "dependencies": [
    "lucide-react@^1.31.0"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/default/response-actions/response-actions.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Check, Clipboard, RotateCcw, ThumbsDown, ThumbsUp } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport type ResponseFeedback = \"up\" | \"down\" | null\n\nexport type ResponseActionsProps = React.HTMLAttributes<HTMLDivElement> & {\n  /** Copies this text. The copy control is absent without it. */\n  copyText?: string\n  onRetry?: () => void\n  retryLabel?: string\n  /** Turns the rating controls on. Omit to leave them out. */\n  onFeedbackChange?: (feedback: ResponseFeedback) => void\n  feedback?: ResponseFeedback\n  defaultFeedback?: ResponseFeedback\n  label?: string\n}\n\nexport function ResponseActions({\n  copyText,\n  onRetry,\n  retryLabel = \"Try again\",\n  onFeedbackChange,\n  feedback,\n  defaultFeedback = null,\n  label = \"Response actions\",\n  children,\n  className,\n  ...rootProps\n}: ResponseActionsProps) {\n  const [copyState, setCopyState] = React.useState<\"idle\" | \"copied\" | \"error\">(\n    \"idle\"\n  )\n  const [ownFeedback, setOwnFeedback] =\n    React.useState<ResponseFeedback>(defaultFeedback)\n  const timer = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n\n  React.useEffect(\n    () => () => {\n      if (timer.current) clearTimeout(timer.current)\n    },\n    []\n  )\n\n  const controlled = feedback !== undefined\n  const current = controlled ? feedback : ownFeedback\n  const rateable = onFeedbackChange !== undefined || controlled\n\n  async function copy() {\n    if (copyText === undefined) return\n    try {\n      await navigator.clipboard.writeText(copyText)\n      setCopyState(\"copied\")\n    } catch {\n      // Denied permission, an insecure context, or a sandboxed frame.\n      setCopyState(\"error\")\n    }\n    if (timer.current) clearTimeout(timer.current)\n    timer.current = setTimeout(() => setCopyState(\"idle\"), 1400)\n  }\n\n  function rate(next: Exclude<ResponseFeedback, null>) {\n    const value = current === next ? null : next\n    if (!controlled) setOwnFeedback(value)\n    onFeedbackChange?.(value)\n  }\n\n  const button =\n    \"text-muted-foreground hover:bg-muted hover:text-foreground aria-pressed:text-foreground inline-flex size-8 items-center justify-center rounded-md transition-colors duration-150 motion-reduce:transition-none\"\n\n  return (\n    <div\n      data-slot=\"response-actions\"\n      role=\"group\"\n      aria-label={label}\n      className={cn(\"flex items-center gap-0.5\", className)}\n      {...rootProps}\n    >\n      {copyText !== undefined ? (\n        <button\n          type=\"button\"\n          data-slot=\"response-actions-copy\"\n          aria-label={copyState === \"copied\" ? \"Copied\" : \"Copy response\"}\n          className={button}\n          onClick={copy}\n        >\n          {copyState === \"copied\" ? (\n            <Check aria-hidden=\"true\" size={14} />\n          ) : (\n            <Clipboard aria-hidden=\"true\" size={14} />\n          )}\n        </button>\n      ) : null}\n\n      {onRetry ? (\n        <button\n          type=\"button\"\n          aria-label={retryLabel}\n          className={button}\n          onClick={onRetry}\n        >\n          <RotateCcw aria-hidden=\"true\" size={14} />\n        </button>\n      ) : null}\n\n      {rateable ? (\n        <>\n          <button\n            type=\"button\"\n            aria-label=\"Good response\"\n            aria-pressed={current === \"up\"}\n            className={cn(button, current === \"up\" && \"bg-muted\")}\n            onClick={() => rate(\"up\")}\n          >\n            <ThumbsUp aria-hidden=\"true\" size={14} />\n          </button>\n          <button\n            type=\"button\"\n            aria-label=\"Bad response\"\n            aria-pressed={current === \"down\"}\n            className={cn(button, current === \"down\" && \"bg-muted\")}\n            onClick={() => rate(\"down\")}\n          >\n            <ThumbsDown aria-hidden=\"true\" size={14} />\n          </button>\n        </>\n      ) : null}\n\n      {children}\n\n      <span aria-live=\"polite\" className=\"sr-only\">\n        {copyState === \"copied\"\n          ? \"Response copied to clipboard.\"\n          : copyState === \"error\"\n            ? \"Response could not be copied.\"\n            : \"\"}\n      </span>\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "ai",
    "chat",
    "agent",
    "feedback"
  ],
  "type": "registry:ui"
}