{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "copy-button", "title": "Copy Button", "author": "siby369 ", "description": "Copy text to clipboard with visual feedback and animation.", "dependencies": [ "lucide-react", "motion" ], "registryDependencies": [ "button" ], "files": [ { "path": "src/registry/components/copy-button/copy-button.tsx", "content": "\"use client\"\n\nimport { CheckIcon, CircleXIcon, CopyIcon } from \"lucide-react\"\nimport type { HTMLMotionProps, Variants } from \"motion/react\"\nimport { AnimatePresence, motion } from \"motion/react\"\nimport type { ComponentProps } from \"react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport type { CopyState } from \"@/hooks/use-copy-to-clipboard\"\nimport { useCopyToClipboard } from \"@/hooks/use-copy-to-clipboard\"\n\nexport const motionIconVariants: Variants = {\n initial: { opacity: 0, scale: 0.8, filter: \"blur(2px)\" },\n animate: { opacity: 1, scale: 1, filter: \"blur(0px)\" },\n exit: { opacity: 0, scale: 0.8 },\n}\n\nexport const motionIconProps: HTMLMotionProps<\"span\"> = {\n variants: motionIconVariants,\n initial: \"initial\",\n animate: \"animate\",\n exit: \"exit\",\n transition: { duration: 0.15, ease: \"easeOut\" },\n}\n\nexport function CopyStateIcon({ state }: { state: CopyState }) {\n return (\n \n {state === \"idle\" ? (\n \n \n \n ) : state === \"done\" ? (\n \n \n \n ) : state === \"error\" ? (\n \n \n \n ) : null}\n \n )\n}\n\nexport type CopyButtonProps = ComponentProps & {\n /** The text to copy, or a function that returns the text. */\n text: string | (() => string)\n /** Called with the copied text on successful copy. */\n onCopySuccess?: (text: string) => void\n /** Called with the error if the copy operation fails. */\n onCopyError?: (error: Error) => void\n}\n\nexport function CopyButton({\n size = \"icon\",\n children,\n text,\n onCopySuccess,\n onCopyError,\n onClick,\n ...props\n}: CopyButtonProps) {\n const { state, copy } = useCopyToClipboard({\n onCopySuccess,\n onCopyError,\n })\n\n return (\n {\n copy(text)\n onClick?.(e)\n }}\n aria-label=\"Copy\"\n {...props}\n >\n \n {children}\n \n )\n}\n", "type": "registry:component" }, { "path": "src/hooks/use-copy-to-clipboard.ts", "content": "\"use client\"\n\nimport { useState, useTransition } from \"react\"\n\nexport type CopyState = \"idle\" | \"done\" | \"error\"\n\nexport type UseCopyToClipboardOptions = {\n onCopySuccess?: (text: string) => void\n onCopyError?: (error: Error) => void\n resetDelay?: number\n}\n\nexport function useCopyToClipboard({\n onCopySuccess,\n onCopyError,\n resetDelay = 1500,\n}: UseCopyToClipboardOptions = {}) {\n const [state, setState] = useState(\"idle\")\n const [, startTransition] = useTransition()\n\n const copy = (text: string | (() => string)) => {\n startTransition(async () => {\n try {\n const finalText = typeof text === \"function\" ? text() : text\n await navigator.clipboard.writeText(finalText)\n setState(\"done\")\n onCopySuccess?.(finalText)\n } catch (error) {\n setState(\"error\")\n onCopyError?.(error instanceof Error ? error : new Error(\"Copy failed\"))\n } finally {\n await new Promise((resolve) => setTimeout(resolve, resetDelay))\n setState(\"idle\")\n }\n })\n }\n\n return { state, copy } as const\n}\n", "type": "registry:hook" } ], "type": "registry:component" }