{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "copy-button",
  "type": "registry:ui",
  "description": "Button that copies content to clipboard with feedback",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "button"
  ],
  "files": [
    {
      "path": "registry/default/ui/copy-button.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Check, Copy } from \"lucide-react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\n\nconst ICON_SPRING = {\n  type: \"spring\" as const,\n  duration: 0.3,\n  bounce: 0,\n}\n\nfunction iconMotion(reduceMotion: boolean) {\n  if (reduceMotion) {\n    return {\n      initial: { opacity: 1, scale: 1, filter: \"blur(0px)\" },\n      animate: { opacity: 1, scale: 1, filter: \"blur(0px)\" },\n      exit: { opacity: 1, scale: 1, filter: \"blur(0px)\" },\n      transition: { duration: 0 },\n    }\n  }\n  return {\n    initial: { opacity: 0, scale: 0.25, filter: \"blur(4px)\" },\n    animate: { opacity: 1, scale: 1, filter: \"blur(0px)\" },\n    exit: { opacity: 0, scale: 0.25, filter: \"blur(4px)\" },\n    transition: ICON_SPRING,\n  }\n}\n\nfunction legacyCopyToClipboard(value: string) {\n  const textArea = document.createElement(\"textarea\")\n  textArea.value = value\n  textArea.setAttribute(\"readonly\", \"\")\n  textArea.style.position = \"fixed\"\n  textArea.style.opacity = \"0\"\n  textArea.style.pointerEvents = \"none\"\n\n  document.body.appendChild(textArea)\n  textArea.focus()\n  textArea.select()\n  textArea.setSelectionRange(0, value.length)\n\n  let hasCopied = false\n  try {\n    hasCopied = document.execCommand(\"copy\")\n  } catch {\n    hasCopied = false\n  }\n\n  document.body.removeChild(textArea)\n  return hasCopied\n}\n\nexport async function copyToClipboardWithMeta(value: string) {\n  if (typeof window === \"undefined\") {\n    return false\n  }\n\n  if (!value) {\n    return false\n  }\n\n  let hasCopied = false\n\n  if (navigator.clipboard?.writeText) {\n    try {\n      await navigator.clipboard.writeText(value)\n      hasCopied = true\n    } catch {\n      hasCopied = legacyCopyToClipboard(value)\n    }\n  } else {\n    hasCopied = legacyCopyToClipboard(value)\n  }\n\n  if (!hasCopied) {\n    return false\n  }\n\n  return true\n}\n\nexport function CopyButton({\n  value,\n  className,\n  variant = \"ghost\",\n  layout = \"inline\",\n\n  ...props\n}: React.ComponentProps<typeof Button> & {\n  value: string\n  src?: string\n  /** `overlay` = absolute corner for code blocks; `inline` = normal flow next to text */\n  layout?: \"inline\" | \"overlay\"\n  tooltip?: string\n}) {\n  const [hasCopied, setHasCopied] = React.useState(false)\n  const reduceMotion = useReducedMotion()\n\n  React.useEffect(() => {\n    if (hasCopied) {\n      const timer = setTimeout(() => setHasCopied(false), 2000)\n      return () => clearTimeout(timer)\n    }\n  }, [hasCopied])\n\n  const motionProps = iconMotion(!!reduceMotion)\n\n  return (\n    <Button\n      aria-label={hasCopied ? \"Copied to clipboard\" : \"Copy to clipboard\"}\n      className={cn(\n        \"before:-inset-2 touch-manipulation before:absolute before:z-[-1] before:content-['']\",\n        \"motion-safe:active:scale-[0.96]\",\n        layout === \"overlay\" &&\n          \"absolute top-3 right-2 z-10 size-7 shrink-0 overflow-hidden bg-code focus-visible:opacity-100 [@media(hover:hover)]:hover:opacity-100\",\n        layout === \"inline\" && \"relative shrink-0 overflow-hidden rounded-xl\",\n        className\n      )}\n      data-copied={hasCopied}\n      data-slot=\"copy-button\"\n      onClick={async () => {\n        const ok = await copyToClipboardWithMeta(value)\n\n        if (ok) {\n          setHasCopied(true)\n        }\n      }}\n      size=\"icon\"\n      variant={variant}\n      {...props}\n    >\n      <span aria-atomic=\"true\" aria-live=\"polite\" className=\"sr-only\">\n        {hasCopied ? \"Copied\" : \"\"}\n      </span>\n      <span\n        aria-hidden=\"true\"\n        className=\"relative inline-flex size-4 items-center justify-center\"\n      >\n        <AnimatePresence initial={false} mode=\"sync\">\n          {hasCopied ? (\n            <motion.span\n              className=\"absolute inset-0 flex items-center justify-center\"\n              key=\"check\"\n              {...motionProps}\n            >\n              <Check className=\"size-4\" strokeWidth={2} />\n            </motion.span>\n          ) : (\n            <motion.span\n              className=\"absolute inset-0 flex items-center justify-center\"\n              key=\"copy\"\n              {...motionProps}\n            >\n              <Copy className=\"size-4\" strokeWidth={2} />\n            </motion.span>\n          )}\n        </AnimatePresence>\n      </span>\n    </Button>\n  )\n}\n",
      "type": "registry:ui"
    }
  ]
}