Skip to content
File

Blob: src/client/sites/islands/copy-code-button.tsx

typescript48 lines
1import { useCallback, useEffect, useRef, useState } from "react";
2import { copyTextToClipboard } from "@/shared/browser/use-copy-feedback";
3import { CodeBlockCopyButtonChrome, getCodeCopyButtonLabel } from "@/shared/editor/presentation/code-block";
4 
5const COPY_RESET_MS = 2000;
6 
7export function CopyCodeButton() {
8 const buttonRef = useRef<HTMLButtonElement>(null);
9 const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
10 const [copied, setCopied] = useState(false);
11 
12 useEffect(() => {
13 return () => {
14 if (timerRef.current) clearTimeout(timerRef.current);
15 };
16 }, []);
17 
18 const handleClick = useCallback(() => {
19 const text = readNearestCodeText(buttonRef.current);
20 void copyTextToClipboard(text).then(
21 () => {
22 if (timerRef.current) clearTimeout(timerRef.current);
23 setCopied(true);
24 timerRef.current = setTimeout(() => setCopied(false), COPY_RESET_MS);
25 },
26 () => {
27 // Clipboard unavailable; keep the button in its idle state.
28 },
29 );
30 }, []);
31 
32 return (
33 <CodeBlockCopyButtonChrome
34 ref={buttonRef}
35 copied={copied}
36 aria-label={getCodeCopyButtonLabel(copied)}
37 onClick={handleClick}
38 />
39 );
40}
41 
42function readNearestCodeText(button: HTMLButtonElement | null): string {
43 if (!button) return "";
44 const wrapper = button.closest(".tiptap-code-block-wrapper");
45 const code = wrapper?.querySelector(".tiptap-code-block-content");
46 return code?.textContent ?? "";
47}