Files
penguin-harness/packages/landing/src/components/copy-button.tsx
T
Yaowei ZhengandClaude Opus 4.8 45bfae6e94 Initialize repository with harness code and assets
Initial import of all source code, config, and README assets: the
packages workspace (cli, core, server, web, docs, landing, skills),
build scripts, tooling config, and CI workflows.

Includes the data-layout revision made on this branch: the local data
root defaults to ~/.penguin/data (PENGUIN_HOME still overrides; the
installer keeps its binaries in ~/.penguin), and every Agent lives
under <project>/agents/<agent>/ — path helpers, the three
agent-enumeration scans, the system prompt, built-in Skills, tests
and docs all follow the new layout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ihk8iQuo3kv2aPjAYEPuR
2026-07-19 14:06:53 +08:00

51 lines
1.7 KiB
TypeScript

/** Copy-to-clipboard button with a transient "copied" state. */
import { useEffect, useRef, useState } from "react";
import { S } from "../lib/strings";
import { CheckIcon, CopyIcon } from "./icons";
export function CopyButton({ text, className = "" }: { text: string; className?: string }) {
const [copied, setCopied] = useState(false);
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(
() => () => {
if (timer.current) clearTimeout(timer.current);
},
[],
);
const onCopy = async () => {
try {
await navigator.clipboard.writeText(text);
} catch {
// Clipboard API unavailable (e.g. non-secure context): fall back to a hidden textarea.
const ta = document.createElement("textarea");
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
ta.remove();
}
setCopied(true);
if (timer.current) clearTimeout(timer.current);
timer.current = setTimeout(() => setCopied(false), 1600);
};
return (
<button
type="button"
onClick={() => void onCopy()}
title={copied ? S.copy.copied : S.copy.copy}
aria-label={copied ? S.copy.copied : S.copy.copy}
className={`inline-flex items-center gap-1.5 rounded-md border border-gray-200 bg-white px-2 py-1 text-xs text-gray-600 transition-colors hover:bg-gray-50 hover:text-gray-900 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-100 ${className}`}
>
{copied ? (
<CheckIcon className="h-3.5 w-3.5 text-green-600 dark:text-green-400" />
) : (
<CopyIcon className="h-3.5 w-3.5" />
)}
<span>{copied ? S.copy.copied : S.copy.copy}</span>
</button>
);
}