{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "copy-button", "type": "registry:ui", "title": "Copy Button", "description": "A button with a copy to clipboard animation.", "dependencies": [ "motion", "lucide-react", "class-variance-authority" ], "files": [ { "path": "registry/buttons/copy/index.tsx", "content": "'use client';\n\nimport * as React from 'react';\nimport { AnimatePresence, HTMLMotionProps, motion } from 'motion/react';\nimport { CheckIcon, CopyIcon } from 'lucide-react';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/lib/utils';\n\nconst buttonVariants = cva(\n 'inline-flex items-center justify-center cursor-pointer rounded-md transition-colors disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',\n {\n variants: {\n variant: {\n default:\n 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',\n muted: 'bg-muted text-muted-foreground',\n destructive:\n 'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',\n outline:\n 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',\n secondary:\n 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',\n ghost:\n 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',\n },\n size: {\n default: 'size-8 rounded-lg [&_svg]:size-4',\n sm: 'size-6 [&_svg]:size-3',\n md: 'size-10 rounded-lg [&_svg]:size-5',\n lg: 'size-12 rounded-xl [&_svg]:size-6',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n);\n\ntype CopyButtonProps = Omit, 'children' | 'onCopy'> &\n VariantProps & {\n content?: string;\n delay?: number;\n onCopy?: (content: string) => void;\n isCopied?: boolean;\n onCopyChange?: (isCopied: boolean) => void;\n };\n\nfunction CopyButton({\n content,\n className,\n size,\n variant,\n delay = 3000,\n onClick,\n onCopy,\n isCopied,\n onCopyChange,\n ...props\n}: CopyButtonProps) {\n const [localIsCopied, setLocalIsCopied] = React.useState(isCopied ?? false);\n const Icon = localIsCopied ? CheckIcon : CopyIcon;\n\n React.useEffect(() => {\n setLocalIsCopied(isCopied ?? false);\n }, [isCopied]);\n\n const handleIsCopied = React.useCallback(\n (isCopied: boolean) => {\n setLocalIsCopied(isCopied);\n onCopyChange?.(isCopied);\n },\n [onCopyChange],\n );\n\n const handleCopy = React.useCallback(\n (e: React.MouseEvent) => {\n if (isCopied) return;\n if (content) {\n navigator.clipboard\n .writeText(content)\n .then(() => {\n handleIsCopied(true);\n setTimeout(() => handleIsCopied(false), delay);\n onCopy?.(content);\n })\n .catch((error) => {\n console.error('Error copying command', error);\n });\n }\n onClick?.(e);\n },\n [isCopied, content, delay, onClick, onCopy, handleIsCopied],\n );\n\n return (\n \n \n \n \n \n \n \n );\n}\n\nexport { CopyButton, buttonVariants, type CopyButtonProps };\n", "type": "registry:ui", "target": "components/animate-ui/buttons/copy.tsx" } ] }