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
51 lines
1.7 KiB
TypeScript
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>
|
|
);
|
|
}
|