{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "copy-button",
  "title": "Copy Button",
  "description": "Click-to-copy utility with confirmation feedback and a useCopyToClipboard hook.",
  "dependencies": [
    "@vllnt/ui@^0.3.0",
    "lucide-react"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/copy-button/copy-button.tsx",
      "content": "\"use client\";\n\nimport {\n  type ComponentPropsWithoutRef,\n  type MouseEvent as ReactMouseEvent,\n  type ReactElement,\n  type ReactNode,\n  useCallback,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\";\n\nimport { Check, Copy } from \"lucide-react\";\n\nimport { cn } from \"@vllnt/ui\";\nimport { Button } from \"@vllnt/ui\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@vllnt/ui\";\n\nconst FALLBACK_TIMEOUT_MS = 2000;\n\n/**\n * Options for {@link useCopyToClipboard}.\n *\n * @public\n */\nexport type UseCopyToClipboardOptions = {\n  /** Milliseconds the `copied` flag stays true after a successful copy. */\n  timeout?: number;\n};\n\n/**\n * Return shape for {@link useCopyToClipboard}.\n *\n * @public\n */\nexport type UseCopyToClipboardResult = {\n  /** True for `timeout` ms after the most recent successful copy. */\n  copied: boolean;\n  /** Writes `value` to the clipboard. Resolves to `true` on success. */\n  copy: (value: string) => Promise<boolean>;\n  /** Clears the `copied` flag and pending timer. */\n  reset: () => void;\n};\n\n/**\n * React hook that copies arbitrary strings to the clipboard with a transient\n * `copied` flag suitable for visual feedback.\n *\n * @example\n * ```tsx\n * const { copied, copy } = useCopyToClipboard()\n * <button onClick={() => copy(apiKey)}>{copied ? \"Copied!\" : \"Copy\"}</button>\n * ```\n *\n * @public\n */\nexport function useCopyToClipboard(\n  options: UseCopyToClipboardOptions = {},\n): UseCopyToClipboardResult {\n  const { timeout = FALLBACK_TIMEOUT_MS } = options;\n  const [copied, setCopied] = useState(false);\n  const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n  useEffect(() => {\n    return () => {\n      if (timerRef.current !== undefined) {\n        clearTimeout(timerRef.current);\n      }\n    };\n  }, []);\n\n  const reset = useCallback(() => {\n    if (timerRef.current !== undefined) clearTimeout(timerRef.current);\n    setCopied(false);\n  }, []);\n\n  const copy = useCallback(\n    async (value: string): Promise<boolean> => {\n      try {\n        if (\n          typeof navigator === \"undefined\" ||\n          typeof navigator.clipboard?.writeText !== \"function\"\n        ) {\n          return false;\n        }\n        await navigator.clipboard.writeText(value);\n        if (timerRef.current !== undefined) clearTimeout(timerRef.current);\n        setCopied(true);\n        timerRef.current = setTimeout(() => {\n          setCopied(false);\n        }, timeout);\n        return true;\n      } catch {\n        return false;\n      }\n    },\n    [timeout],\n  );\n\n  return { copied, copy, reset };\n}\n\n/**\n * Visual variant for {@link CopyButton}.\n *\n * - `icon`   — compact icon-style button (default), suitable for toolbars.\n * - `inline` — small button sized to sit next to short inline text.\n * - `button` — full button with text label, suitable for primary CTAs.\n *\n * @public\n */\nexport type CopyButtonVariant = \"button\" | \"icon\" | \"inline\";\n\ntype ButtonElementProps = ComponentPropsWithoutRef<\"button\">;\n\n/**\n * Props for {@link CopyButton}.\n *\n * @public\n */\nexport type CopyButtonProps = {\n  /** Tooltip + announcement text after a successful copy. Defaults to `\"Copied!\"`. */\n  copiedLabel?: string;\n  /** Class name forwarded to the rendered icon. */\n  iconClassName?: string;\n  /** Tooltip + accessible label before copy. Defaults to `\"Copy\"`. */\n  label?: string;\n  /** Set to `false` to suppress the tooltip. */\n  showTooltip?: boolean;\n  /** Milliseconds the success state persists. Defaults to 2000. */\n  timeout?: number;\n  /** String to write to the clipboard when clicked. */\n  value: string;\n  /** Visual variant. */\n  variant?: CopyButtonVariant;\n} & Omit<ButtonElementProps, \"value\">;\n\nfunction CopyIcon({\n  className,\n  copied,\n  size,\n}: {\n  className?: string;\n  copied: boolean;\n  size: \"sm\" | \"xs\";\n}): ReactNode {\n  const Icon = copied ? Check : Copy;\n  const sizeClass = size === \"xs\" ? \"size-3\" : \"size-4\";\n  return <Icon aria-hidden=\"true\" className={cn(sizeClass, className)} />;\n}\n\ntype TriggerProps = Omit<\n  CopyButtonProps,\n  \"copiedLabel\" | \"showTooltip\" | \"timeout\"\n> & {\n  accessibleLabel: string;\n  copied: boolean;\n  copiedText: string;\n  onClickHandler: (event: ReactMouseEvent<HTMLButtonElement>) => void;\n};\n\nconst ButtonTrigger = ({\n  accessibleLabel,\n  className,\n  copied,\n  copiedText,\n  iconClassName,\n  label = \"Copy\",\n  onClick: _onClick,\n  onClickHandler,\n  ref,\n  value: _value,\n  variant = \"icon\",\n  ...rest\n}: TriggerProps & { ref?: React.Ref<HTMLButtonElement> }) => {\n  if (variant === \"button\") {\n    return (\n      <Button\n        aria-label={accessibleLabel}\n        className={cn(className)}\n        onClick={onClickHandler}\n        ref={ref}\n        size=\"sm\"\n        type=\"button\"\n        variant=\"outline\"\n        {...rest}\n      >\n        <CopyIcon className={iconClassName} copied={copied} size=\"sm\" />\n        <span>{copied ? copiedText : label}</span>\n      </Button>\n    );\n  }\n\n  if (variant === \"inline\") {\n    return (\n      <Button\n        aria-label={accessibleLabel}\n        className={cn(\n          \"size-6 align-middle text-muted-foreground hover:text-foreground\",\n          className,\n        )}\n        onClick={onClickHandler}\n        ref={ref}\n        size=\"icon\"\n        type=\"button\"\n        variant=\"ghost\"\n        {...rest}\n      >\n        <CopyIcon className={iconClassName} copied={copied} size=\"xs\" />\n      </Button>\n    );\n  }\n\n  return (\n    <Button\n      aria-label={accessibleLabel}\n      className={cn(\"size-8\", className)}\n      onClick={onClickHandler}\n      ref={ref}\n      size=\"icon\"\n      type=\"button\"\n      variant=\"ghost\"\n      {...rest}\n    >\n      <CopyIcon className={iconClassName} copied={copied} size=\"sm\" />\n    </Button>\n  );\n};\nButtonTrigger.displayName = \"CopyButton.Trigger\";\n\n/**\n * Click-to-copy button with confirmation feedback.\n *\n * Composes {@link Button} and {@link Tooltip}. Copies `value` to the clipboard\n * via the async Clipboard API and announces success to assistive tech via a\n * visually hidden status region.\n *\n * @example\n * ```tsx\n * <CopyButton value={apiKey} />\n * <CopyButton value={url} variant=\"button\" label=\"Copy link\" />\n * <span>ID: usr_42 <CopyButton value=\"usr_42\" variant=\"inline\" /></span>\n * ```\n *\n * @public\n */\nexport const CopyButton = ({\n  \"aria-label\": ariaLabelOverride,\n  copiedLabel = \"Copied!\",\n  label = \"Copy\",\n  onClick,\n  ref,\n  showTooltip = true,\n  timeout = FALLBACK_TIMEOUT_MS,\n  value,\n  ...rest\n}: CopyButtonProps & { ref?: React.Ref<HTMLButtonElement> }) => {\n  const { copied, copy } = useCopyToClipboard({ timeout });\n\n  const handleClick = useCallback(\n    (event: ReactMouseEvent<HTMLButtonElement>) => {\n      onClick?.(event);\n      if (event.defaultPrevented) return;\n      void copy(value);\n    },\n    [copy, onClick, value],\n  );\n\n  const accessibleLabel = ariaLabelOverride ?? (copied ? copiedLabel : label);\n  const tooltipText = copied ? copiedLabel : label;\n\n  const trigger: ReactElement = (\n    <ButtonTrigger\n      {...rest}\n      accessibleLabel={accessibleLabel}\n      copied={copied}\n      copiedText={copiedLabel}\n      label={label}\n      onClickHandler={handleClick}\n      ref={ref}\n      value={value}\n    />\n  );\n\n  const liveRegion = (\n    <span aria-live=\"polite\" className=\"sr-only\" role=\"status\">\n      {copied ? copiedLabel : \"\"}\n    </span>\n  );\n\n  if (!showTooltip) {\n    return (\n      <>\n        {trigger}\n        {liveRegion}\n      </>\n    );\n  }\n\n  return (\n    <TooltipProvider delayDuration={200}>\n      <Tooltip>\n        <TooltipTrigger asChild>{trigger}</TooltipTrigger>\n        <TooltipContent>{tooltipText}</TooltipContent>\n      </Tooltip>\n      {liveRegion}\n    </TooltipProvider>\n  );\n};\nCopyButton.displayName = \"CopyButton\";\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component",
  "version": "0.3.0",
  "stability": "stable"
}
