nandi/oripublic Fork 0
35061753be581c0ba47a3189520d64e7788c183d
Commits
Clone
git clone https://git.rickub.com/nandi/ori.git
git clone ssh://git@rickub.com/nandi/ori.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

forked from bots-garden/ori

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