'use client' import * as React from 'react' import { cn } from '../../internal/utils' import { Button, type ButtonProps } from '../button/button' const CHECK_PATH = 'M4.3 12.55 L9.25 17.5 L19.7 6.5' const ICON_SVG_PROPS = { xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true, } satisfies React.SVGProps type CopyButtonValue = string | React.RefObject | (() => string | Promise) async function resolveValue(value: CopyButtonValue): Promise { if (typeof value === 'string') return value if (typeof value === 'function') return await value() const element = value.current if (!element) throw new Error('CopyButton: the `value` ref is not attached to an element') if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) return element.value return element.textContent ?? '' } async function copyTextToClipboard(text: string): Promise { if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(text) return } const textarea = document.createElement('textarea') textarea.value = text textarea.setAttribute('readonly', '') textarea.style.position = 'fixed' textarea.style.opacity = '0' document.body.appendChild(textarea) textarea.select() try { if (!document.execCommand('copy')) throw new Error('Copying to clipboard is not supported') } finally { textarea.remove() } } interface CopyButtonProps extends Omit { /** **Required.** What to copy: a string, an element ref (its value/textContent), or a (possibly async) getter. */ value: CopyButtonValue /** * How long (ms) the copied state lasts before reverting. * @default 2000 */ timeout?: number /** * Accessible name (and tooltip via `title`) in the idle state. * @default 'Copy' */ label?: string /** * Accessible name after a successful copy; a string child also swaps to this. * @default 'Copied' */ copiedLabel?: string /** Called with the copied text on success. */ onCopy?: (value: string) => void /** Called if reading the value or writing to the clipboard fails. */ onCopyError?: (error: unknown) => void } function CopyButton({ value, timeout = 2000, label = 'Copy', copiedLabel = 'Copied', onCopy, onCopyError, onClick, variant = 'ghost', size = 'icon-sm', className, children, ...props }: CopyButtonProps) { const [copied, setCopied] = React.useState(false) const resetRef = React.useRef | undefined>(undefined) React.useEffect(() => () => clearTimeout(resetRef.current), []) const handleClick: ButtonProps['onClick'] = async (event) => { onClick?.(event) if (event.defaultPrevented) return let text: string try { text = await resolveValue(value) await copyTextToClipboard(text) } catch (error) { onCopyError?.(error) return } onCopy?.(text) setCopied(true) clearTimeout(resetRef.current) resetRef.current = setTimeout(() => setCopied(false), timeout) } return ( ) } export { CopyButton } export type { CopyButtonProps }