copy-button.tsx77 lines · main
1'use client';
2
3import { useCallback, useState } from 'react';
4
5import { CopyIcon } from './copy';
6
7interface CopyButtonProps {
8 /** The exact text written to the clipboard. */
9 value: string;
10 /** Accessible label / tooltip in the idle state. */
11 label?: string;
12 /** Icon size in px. */
13 size?: number;
14 /** Show a text label next to the icon (button style) instead of icon-only. */
15 showLabel?: boolean;
16 className?: string;
17}
18
19/**
20 * One-click copy with honest feedback: clicking copies `value` and flashes a
21 * thick green check for ~2s so the user can SEE it worked, then reverts to the
22 * copy icon. Reused for non-secret identifiers (endpoint, project id) and for
23 * a freshly-minted API key while its plaintext is still on screen.
24 */
25export function CopyButton({
26 value,
27 label = 'copy',
28 size = 14,
29 showLabel = false,
30 className = '',
31}: CopyButtonProps) {
32 const [copied, setCopied] = useState(false);
33
34 const onClick = useCallback(() => {
35 void navigator.clipboard
36 .writeText(value)
37 .then(() => {
38 setCopied(true);
39 setTimeout(() => setCopied(false), 2000);
40 })
41 .catch(() => undefined);
42 }, [value]);
43
44 return (
45 <button
46 type="button"
47 onClick={onClick}
48 aria-label={copied ? 'copied' : label}
49 title={copied ? 'copied!' : label}
50 className={`inline-flex shrink-0 items-center gap-1.5 rounded-md p-1 text-[var(--color-text-subtle)] transition hover:text-[var(--color-text)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--color-primary)] ${className}`}
51 >
52 {copied ? (
53 <svg
54 width={size}
55 height={size}
56 viewBox="0 0 24 24"
57 fill="none"
58 stroke="currentColor"
59 strokeWidth="3"
60 strokeLinecap="round"
61 strokeLinejoin="round"
62 className="text-[var(--color-primary)]"
63 aria-hidden="true"
64 >
65 <path d="M20 6 9 17l-5-5" />
66 </svg>
67 ) : (
68 <CopyIcon size={size} aria-hidden="true" />
69 )}
70 {showLabel ? (
71 <span className={`font-mono text-xs ${copied ? 'text-[var(--color-primary)]' : ''}`}>
72 {copied ? 'copied!' : label}
73 </span>
74 ) : null}
75 </button>
76 );
77}