1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
/**
* CopyButton provides a reusable copy-to-clipboard button with visual feedback.
* Shows a clipboard icon that changes to a checkmark on successful copy.
*
* @example
* <CopyButton content="const x = 42;" label="Copy code" />
*/
import { useState } from "react";
interface CopyButtonProps {
content: string;
label?: string;
className?: string;
}
export function CopyButton({
content,
label = "Copy",
className = "",
}: CopyButtonProps) {
const [copied, setCopied] = useState(false);
const handleCopy = async () => {
try {
// Use modern Clipboard API with fallback
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(content);
} else {
// Fallback for older browsers or non-HTTPS contexts
const textArea = document.createElement("textarea");
textArea.value = content;
textArea.style.position = "fixed";
textArea.style.left = "-999999px";
textArea.style.top = "-999999px";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
document.execCommand("copy");
textArea.remove();
}
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error("Failed to copy:", err);
}
};
return (
<button
type="button"
className={`copy-btn ${copied ? "copy-btn-copied" : ""} ${className}`}
onClick={handleCopy}
aria-label={copied ? "Copied!" : label}
title={copied ? "Copied!" : label}
>
{copied ? (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<polyline points="4,8 7,11 12,5" />
</svg>
) : (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<rect x="5" y="5" width="9" height="9" rx="1" />
<path d="M3 11V3a1 1 0 0 1 1-1h8" />
</svg>
)}
</button>
);
}
|