"use client"; import { useState } from "react"; // Put something on the clipboard, and fall back visibly when that is not // allowed. // // navigator.clipboard needs a secure context. localhost counts, so the happy // path is the normal one here -- but a copy that silently does nothing is the // worst possible failure for a button whose entire job is "now paste this // somewhere else". So on failure the text is shown in a selectable box instead // of an error, which is still one keystroke from done. type Props = { /** Literal text, or a URL to fetch it from (used for the big digests). */ text?: string; url?: string; label?: string; title?: string; className?: string; }; export default function CopyButton({ text, url, label = "copy", title, className }: Props) { const [state, setState] = useState<"idle" | "busy" | "done" | "failed">("idle"); const [fallback, setFallback] = useState(null); const run = async () => { setState("busy"); setFallback(null); try { let body = text ?? ""; if (url) { const r = await fetch(url, { cache: "no-store" }); if (!r.ok) throw new Error(await r.text()); body = await r.text(); } await navigator.clipboard.writeText(body); setState("done"); setTimeout(() => setState("idle"), 1600); } catch { // Re-fetch is cheap and avoids holding a large digest in state for the // whole page life just in case this path is taken. try { const body = url ? await (await fetch(url, { cache: "no-store" })).text() : (text ?? ""); setFallback(body); } catch { setFallback(null); } setState("failed"); } }; return ( <> {fallback !== null && (

the clipboard was refused — select and copy: