{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "copy-button",
  "title": "Copy Button",
  "description": "A button that copies text to the clipboard and briefly shows a check mark.",
  "dependencies": [
    "class-variance-authority",
    "lucide-react",
    "motion"
  ],
  "files": [
    {
      "path": "registry/tealmac/copy-button.tsx",
      "content": "// Adapted from Ballmac UI (https://github.com/vamsiy78/ballmac-ui), MIT License, Copyright (c) 2026 Ballmac.\n\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { AlertCircle, Check, Copy } from \"lucide-react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst copyButtonVariants = cva(\n  \"relative inline-flex shrink-0 items-center justify-center gap-1.5 rounded-md text-sm font-medium whitespace-nowrap outline-none transition-[color,background-color,border-color,box-shadow] duration-150 select-none focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 motion-reduce:transition-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        ghost: \"text-muted-foreground hover:bg-accent hover:text-foreground\",\n        outline:\n          \"border bg-background text-foreground shadow-xs hover:bg-accent\",\n        solid: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n      },\n      size: {\n        sm: \"h-7 text-xs [&_svg]:size-3.5\",\n        default: \"h-8 text-[13px] [&_svg]:size-4\",\n        lg: \"h-10 [&_svg]:size-4\",\n      },\n    },\n    defaultVariants: { variant: \"ghost\", size: \"default\" },\n  }\n)\n\n/** Writes text to the clipboard, falling back to a hidden textarea where the async API is blocked (insecure origins, some iframes). */\nasync function copyText(text: string): Promise<boolean> {\n  try {\n    await navigator.clipboard.writeText(text)\n    return true\n  } catch {\n    try {\n      const area = document.createElement(\"textarea\")\n      area.value = text\n      area.setAttribute(\"readonly\", \"\")\n      area.style.cssText =\n        \"position:fixed;top:0;left:0;opacity:0;pointer-events:none\"\n      document.body.appendChild(area)\n      area.select()\n      const ok = document.execCommand(\"copy\")\n      area.remove()\n      return ok\n    } catch {\n      return false\n    }\n  }\n}\n\ntype CopyButtonProps = Omit<\n  React.ComponentProps<\"button\">,\n  \"value\" | \"onCopy\" | \"children\"\n> &\n  VariantProps<typeof copyButtonVariants> & {\n    /** Text to copy. */\n    value?: string\n    /** Reads the text at click time instead, for content that changes (a selection, a form). May be async. */\n    getValue?: () => string | Promise<string>\n    /** Visible label next to the icon, such as \"Copy\". Leave out for an icon-only button. */\n    label?: string\n    /** Label shown after a successful copy. */\n    copiedLabel?: string\n    /** Accessible name of an icon-only button. */\n    ariaLabel?: string\n    /** How long the confirmation stays, in milliseconds. */\n    resetAfter?: number\n    /** Called with the copied text. */\n    onCopied?: (text: string) => void\n    /** Called when both clipboard routes fail. */\n    onError?: () => void\n  }\n\nfunction CopyButton({\n  value,\n  getValue,\n  label,\n  copiedLabel,\n  ariaLabel,\n  resetAfter = 1800,\n  onCopied,\n  onError,\n  variant,\n  size,\n  className,\n  onClick,\n  ...props\n}: CopyButtonProps) {\n  copiedLabel ??= \"Copied\"\n  ariaLabel ??= \"Copy to clipboard\"\n  const reduce = useReducedMotion()\n  const [state, setState] = React.useState<\"idle\" | \"copied\" | \"failed\">(\"idle\")\n  const timer = React.useRef<ReturnType<typeof setTimeout>>(undefined)\n  React.useEffect(() => () => clearTimeout(timer.current), [])\n\n  async function handleClick(event: React.MouseEvent<HTMLButtonElement>) {\n    onClick?.(event)\n    if (event.defaultPrevented) return\n    const text = getValue ? await getValue() : (value ?? \"\")\n    const ok = await copyText(text)\n    setState(ok ? \"copied\" : \"failed\")\n    if (ok) onCopied?.(text)\n    else onError?.()\n    clearTimeout(timer.current)\n    timer.current = setTimeout(() => setState(\"idle\"), resetAfter)\n  }\n\n  const word =\n    state === \"copied\"\n      ? copiedLabel\n      : state === \"failed\"\n        ? \"Copy failed\"\n        : label\n  const Icon =\n    state === \"copied\" ? Check : state === \"failed\" ? AlertCircle : Copy\n\n  return (\n    <>\n      <button\n        type=\"button\"\n        data-slot=\"copy-button\"\n        data-state={state}\n        aria-label={word ?? ariaLabel}\n        title={word ?? ariaLabel}\n        onClick={handleClick}\n        className={cn(\n          copyButtonVariants({ variant, size }),\n          label\n            ? { sm: \"px-2\", default: \"px-2.5\", lg: \"px-3.5\" }[size ?? \"default\"]\n            : { sm: \"w-7\", default: \"w-8\", lg: \"w-10\" }[size ?? \"default\"],\n          state === \"copied\" && \"text-foreground\",\n          className\n        )}\n        {...props}\n      >\n        <span\n          aria-hidden=\"true\"\n          className=\"relative flex items-center justify-center\"\n        >\n          <AnimatePresence mode=\"popLayout\" initial={false}>\n            <motion.span\n              key={state}\n              className=\"flex\"\n              initial={\n                reduce\n                  ? { opacity: 0 }\n                  : { opacity: 0, scale: 0.5, rotate: -45 }\n              }\n              animate={{ opacity: 1, scale: 1, rotate: 0 }}\n              exit={reduce ? { opacity: 0 } : { opacity: 0, scale: 0.5 }}\n              transition={{ type: \"spring\", stiffness: 520, damping: 30 }}\n            >\n              <Icon\n                className={cn(\n                  state === \"copied\" && \"text-chart-3\",\n                  state === \"failed\" && \"text-destructive\"\n                )}\n              />\n            </motion.span>\n          </AnimatePresence>\n        </span>\n        {label && (\n          <span data-label=\"\" aria-hidden=\"true\">\n            {word}\n          </span>\n        )}\n      </button>\n      <span className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n        {state === \"copied\"\n          ? \"Copied to clipboard\"\n          : state === \"failed\"\n            ? \"Copy failed\"\n            : \"\"}\n      </span>\n    </>\n  )\n}\n\nexport { CopyButton, copyButtonVariants, copyText, type CopyButtonProps }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}