| 449 | }; |
| 450 | |
| 451 | export const CodeBlockCopyButton = ({ |
| 452 | onCopy, |
| 453 | onError, |
| 454 | timeout = 2000, |
| 455 | children, |
| 456 | className, |
| 457 | ...props |
| 458 | }: CodeBlockCopyButtonProps) => { |
| 459 | const [isCopied, setIsCopied] = useState(false); |
| 460 | const timeoutRef = useRef<number>(0); |
| 461 | const { code } = useContext(CodeBlockContext); |
| 462 | |
| 463 | const copyToClipboard = useCallback(async () => { |
| 464 | if (typeof window === "undefined" || !navigator?.clipboard?.writeText) { |
| 465 | onError?.(new Error("Clipboard API not available")); |
| 466 | return; |
| 467 | } |
| 468 | |
| 469 | try { |
| 470 | if (!isCopied) { |
| 471 | await navigator.clipboard.writeText(code); |
| 472 | setIsCopied(true); |
| 473 | onCopy?.(); |
| 474 | timeoutRef.current = window.setTimeout( |
| 475 | () => setIsCopied(false), |
| 476 | timeout |
| 477 | ); |
| 478 | } |
| 479 | } catch (error) { |
| 480 | onError?.(error as Error); |
| 481 | } |
| 482 | }, [code, onCopy, onError, timeout, isCopied]); |
| 483 | |
| 484 | useEffect( |
| 485 | () => () => { |
| 486 | window.clearTimeout(timeoutRef.current); |
| 487 | }, |
| 488 | [] |
| 489 | ); |
| 490 | |
| 491 | const Icon = isCopied ? CheckIcon : CopyIcon; |
| 492 | |
| 493 | return ( |
| 494 | <Button |
| 495 | className={cn("shrink-0", className)} |
| 496 | onClick={copyToClipboard} |
| 497 | size="icon" |
| 498 | variant="ghost" |
| 499 | {...props} |
| 500 | > |
| 501 | {children ?? <Icon size={14} />} |
| 502 | </Button> |
| 503 | ); |
| 504 | }; |
| 505 | |
| 506 | export type CodeBlockLanguageSelectorProps = ComponentProps<typeof Select>; |
| 507 | |