{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"copy-button","title":"Copy Button","description":"A button that copies text to the clipboard with optional feedback.","files":[{"path":"ui/copy-button.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { IconCheck, IconCopy } from \"@tabler/icons-react\";\nimport * as React from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\ntype CopyButtonClickEvent = Parameters<\n  NonNullable<React.ComponentProps<typeof Button>[\"onClick\"]>\n>[0];\n\ntype CopyButtonProps = Omit<React.ComponentProps<typeof Button>, \"children\" | \"value\"> & {\n  value: string | (() => string);\n  showLabel?: boolean;\n  copyLabel?: string;\n  copiedLabel?: string;\n  resetDelay?: number;\n  onCopied?: (value: string, event: CopyButtonClickEvent) => void;\n  onCopyError?: (error: unknown, event: CopyButtonClickEvent) => void;\n};\n\nfunction CopyButton({\n  value,\n  showLabel = false,\n  copyLabel = \"Copy to clipboard\",\n  copiedLabel = \"Copied\",\n  resetDelay = 2000,\n  variant = \"ghost\",\n  size,\n  className,\n  disabled,\n  onClick,\n  onCopied,\n  onCopyError,\n  ...props\n}: CopyButtonProps) {\n  const resetTimeoutRef = React.useRef<number | undefined>(undefined);\n  const [copied, setCopied] = React.useState(false);\n  const buttonSize: React.ComponentProps<typeof Button>[\"size\"] =\n    size ?? (showLabel ? \"sm\" : \"icon-sm\");\n\n  React.useEffect(() => {\n    return () => window.clearTimeout(resetTimeoutRef.current);\n  }, []);\n\n  const handleClick = React.useCallback(\n    async (event: CopyButtonClickEvent) => {\n      onClick?.(event);\n\n      if (event.defaultPrevented || copied || disabled) {\n        return;\n      }\n\n      try {\n        const textToCopy = typeof value === \"function\" ? value() : value;\n\n        await copyTextToClipboard(textToCopy);\n        window.clearTimeout(resetTimeoutRef.current);\n        setCopied(true);\n        resetTimeoutRef.current = window.setTimeout(() => setCopied(false), resetDelay);\n        onCopied?.(textToCopy, event);\n      } catch (error) {\n        setCopied(false);\n        onCopyError?.(error, event);\n      }\n    },\n    [copied, disabled, onClick, onCopied, onCopyError, resetDelay, value],\n  );\n\n  return (\n    <Button\n      type=\"button\"\n      variant={variant}\n      size={buttonSize}\n      disabled={disabled}\n      aria-label={copied ? copiedLabel : copyLabel}\n      className={cn(\"shrink-0\", copied && \"cursor-default\", className)}\n      onClick={(event) => {\n        void handleClick(event);\n      }}\n      {...props}\n    >\n      {copied ? (\n        <IconCheck aria-hidden=\"true\" data-icon={showLabel ? \"inline-start\" : undefined} />\n      ) : (\n        <IconCopy aria-hidden=\"true\" data-icon={showLabel ? \"inline-start\" : undefined} />\n      )}\n      {showLabel && <span>{copied ? copiedLabel : copyLabel}</span>}\n    </Button>\n  );\n}\n\nasync function copyTextToClipboard(value: string) {\n  if (navigator.clipboard?.writeText) {\n    await navigator.clipboard.writeText(value);\n    return;\n  }\n\n  const textArea = document.createElement(\"textarea\");\n\n  textArea.value = value;\n  textArea.setAttribute(\"readonly\", \"\");\n  textArea.style.position = \"fixed\";\n  textArea.style.inset = \"0 auto auto -9999px\";\n  document.body.append(textArea);\n  textArea.select();\n\n  const copied = document.execCommand(\"copy\");\n\n  textArea.remove();\n\n  if (!copied) {\n    throw new Error(\"Copy command was rejected.\");\n  }\n}\n\nexport { CopyButton, type CopyButtonProps };","target":"@ui/copy-button.tsx"}],"type":"registry:ui","dependencies":["@tabler/icons-react"],"registryDependencies":["button"]}