nandi/oripublic Fork 0
00224b167266e7d496672adfe1e60fcffeab7e63
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

Add copy-paste functionality for code blocks and completion widgets 7895c1d · on 00224b167266e7d496672adfe1e60fcffeab7e63 · k33g · 21h ago
CopyButton.tsx · 90 lines · 2.1 KBTypeScript Blame HistoryRaw
 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>
	);
}