{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "copy-button", "title": "Copy Button", "author": "tusharvarshney ", "description": "Copy text to clipboard with visual, haptic, and audio feedback.", "dependencies": [ "motion", "@rexa-developer/tiks", "web-haptics" ], "registryDependencies": [ "button" ], "files": [ { "path": "src/registry/components/copy-button/copy-button.tsx", "content": "\"use client\"\n\nimport type { HTMLMotionProps, Variants } from \"motion/react\"\nimport { AnimatePresence, motion } from \"motion/react\"\nimport type { ComponentProps } from \"react\"\n\nimport { IconPlaceholder } from \"@/components/icon-placeholder\"\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 type CopyStateIconProps = {\n state: CopyState\n /** Custom icon for idle state. */\n idleIcon?: React.ReactNode\n /** Custom icon for done state. */\n doneIcon?: React.ReactNode\n /** Custom icon for error state. */\n errorIcon?: React.ReactNode\n}\n\nexport function CopyStateIcon({\n state,\n idleIcon,\n doneIcon,\n errorIcon,\n}: CopyStateIconProps) {\n return (\n \n {state === \"idle\" ? (\n \n {idleIcon ?? (\n \n )}\n \n ) : state === \"done\" ? (\n \n {doneIcon ?? (\n \n )}\n \n ) : state === \"error\" ? (\n \n {errorIcon ?? (\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} & Omit\n\nexport function CopyButton({\n size = \"icon\",\n children,\n text,\n idleIcon,\n doneIcon,\n errorIcon,\n onClick,\n onCopySuccess,\n onCopyError,\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", "target": "@components/copy-button.tsx" }, { "path": "src/hooks/use-copy-to-clipboard.ts", "content": "\"use client\"\n\nimport { useTiks } from \"@rexa-developer/tiks/react\"\nimport { useCallback, useRef, useState } from \"react\"\nimport { useWebHaptics } from \"web-haptics/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 resetTimeoutRef = useRef | null>(null)\n\n const { trigger: haptic } = useWebHaptics()\n const { success: tiksSuccess, error: tiksError } = useTiks()\n\n const copy = useCallback(\n async (text: string | (() => string)) => {\n // Clear any pending reset\n if (resetTimeoutRef.current) {\n clearTimeout(resetTimeoutRef.current)\n }\n\n try {\n const finalText = typeof text === \"function\" ? text() : text\n await navigator.clipboard.writeText(finalText)\n\n setState(\"done\")\n\n haptic(\"success\")\n tiksSuccess()\n\n onCopySuccess?.(finalText)\n } catch (error) {\n setState(\"error\")\n\n haptic(\"error\")\n tiksError()\n\n onCopyError?.(error instanceof Error ? error : new Error(\"Copy failed\"))\n } finally {\n // Schedule reset to idle\n resetTimeoutRef.current = setTimeout(() => {\n setState(\"idle\")\n }, resetDelay)\n }\n },\n [onCopySuccess, onCopyError, haptic, tiksSuccess, tiksError, resetDelay]\n )\n\n return { state, copy } as const\n}\n", "type": "registry:hook", "target": "@hooks/use-copy-to-clipboard.ts" } ], "type": "registry:component" }