{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "copy-button", "title": "Copy Button", "author": "shakil-ahmed-billal ", "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\"\r\n\r\nimport { CheckIcon, CircleXIcon, CopyIcon } from \"lucide-react\"\r\nimport type { HTMLMotionProps, Variants } from \"motion/react\"\r\nimport { AnimatePresence, motion } from \"motion/react\"\r\nimport type { ComponentProps } from \"react\"\r\n\r\nimport { Button } from \"@/components/ui/button\"\r\nimport type { CopyState } from \"@/hooks/use-copy-to-clipboard\"\r\nimport { useCopyToClipboard } from \"@/hooks/use-copy-to-clipboard\"\r\n\r\nexport const motionIconVariants: Variants = {\r\n initial: { opacity: 0, scale: 0.8, filter: \"blur(2px)\" },\r\n animate: { opacity: 1, scale: 1, filter: \"blur(0px)\" },\r\n exit: { opacity: 0, scale: 0.8 },\r\n}\r\n\r\nexport const motionIconProps: HTMLMotionProps<\"span\"> = {\r\n variants: motionIconVariants,\r\n initial: \"initial\",\r\n animate: \"animate\",\r\n exit: \"exit\",\r\n transition: { duration: 0.15, ease: \"easeOut\" },\r\n}\r\n\r\nexport type CopyStateIconProps = {\r\n state: CopyState\r\n /**\r\n * Custom icon for idle state.\r\n * @defaultValue CopyIcon\r\n * */\r\n idleIcon?: React.ReactNode\r\n /**\r\n * Custom icon for done state.\r\n * @defaultValue CheckIcon\r\n * */\r\n doneIcon?: React.ReactNode\r\n /**\r\n * Custom icon for error state.\r\n * @defaultValue CircleXIcon\r\n * */\r\n errorIcon?: React.ReactNode\r\n}\r\n\r\nexport function CopyStateIcon({\r\n state,\r\n idleIcon,\r\n doneIcon,\r\n errorIcon,\r\n}: CopyStateIconProps) {\r\n return (\r\n \r\n {state === \"idle\" ? (\r\n \r\n {idleIcon ?? }\r\n \r\n ) : state === \"done\" ? (\r\n \r\n {doneIcon ?? }\r\n \r\n ) : state === \"error\" ? (\r\n \r\n {errorIcon ?? }\r\n \r\n ) : null}\r\n \r\n )\r\n}\r\n\r\nexport type CopyButtonProps = ComponentProps & {\r\n /** The text to copy, or a function that returns the text. */\r\n text: string | (() => string)\r\n /** Called with the copied text on successful copy. */\r\n onCopySuccess?: (text: string) => void\r\n /** Called with the error if the copy operation fails. */\r\n onCopyError?: (error: Error) => void\r\n} & Pick\r\n\r\nexport function CopyButton({\r\n size = \"icon\",\r\n children,\r\n text,\r\n idleIcon,\r\n doneIcon,\r\n errorIcon,\r\n onClick,\r\n onCopySuccess,\r\n onCopyError,\r\n ...props\r\n}: CopyButtonProps) {\r\n const { state, copy } = useCopyToClipboard({\r\n onCopySuccess,\r\n onCopyError,\r\n })\r\n\r\n return (\r\n {\r\n copy(text)\r\n onClick?.(e)\r\n }}\r\n aria-label=\"Copy\"\r\n {...props}\r\n >\r\n \r\n {children}\r\n \r\n )\r\n}\r\n", "type": "registry:component" }, { "path": "src/hooks/use-copy-to-clipboard.ts", "content": "\"use client\"\r\n\r\nimport { useCallback, useRef, useState } from \"react\"\r\n\r\nexport type CopyState = \"idle\" | \"done\" | \"error\"\r\n\r\nexport type UseCopyToClipboardOptions = {\r\n onCopySuccess?: (text: string) => void\r\n onCopyError?: (error: Error) => void\r\n resetDelay?: number\r\n}\r\n\r\nexport function useCopyToClipboard({\r\n onCopySuccess,\r\n onCopyError,\r\n resetDelay = 1500,\r\n}: UseCopyToClipboardOptions = {}) {\r\n const [state, setState] = useState(\"idle\")\r\n const resetTimeoutRef = useRef | null>(null)\r\n\r\n const copy = useCallback(\r\n async (text: string | (() => string)) => {\r\n // Clear any pending reset\r\n if (resetTimeoutRef.current) {\r\n clearTimeout(resetTimeoutRef.current)\r\n }\r\n\r\n try {\r\n const finalText = typeof text === \"function\" ? text() : text\r\n await navigator.clipboard.writeText(finalText)\r\n setState(\"done\")\r\n onCopySuccess?.(finalText)\r\n } catch (error) {\r\n setState(\"error\")\r\n onCopyError?.(error instanceof Error ? error : new Error(\"Copy failed\"))\r\n } finally {\r\n // Schedule reset to idle\r\n resetTimeoutRef.current = setTimeout(() => {\r\n setState(\"idle\")\r\n }, resetDelay)\r\n }\r\n },\r\n [onCopyError, onCopySuccess, resetDelay]\r\n )\r\n\r\n return { state, copy } as const\r\n}\r\n", "type": "registry:hook" } ], "type": "registry:component" }