Skip to content
File

Blob: src/client/components/ui/copyable.tsx

typescript54 lines
1import { Check, Copy } from "lucide-react";
2import { useState } from "react";
3 
4import { toast } from "@/client/lib/toast";
5 
6type Props = {
7 value: string;
8 copyValue?: string;
9 label?: string;
10 mono?: boolean;
11};
12 
13export function Copyable({ value, copyValue, label, mono = true }: Props) {
14 const [copied, setCopied] = useState(false);
15 const handleCopy = () => {
16 void navigator.clipboard.writeText(copyValue ?? value).then(
17 () => {
18 setCopied(true);
19 window.setTimeout(() => setCopied(false), 1400);
20 toast.success(`Copied${label ? ` ${label}` : ""}.`);
21 },
22 () => toast.error(`Could not copy${label ? ` ${label}` : ""}.`),
23 );
24 };
25 
26 return (
27 <div className="group relative rounded-xl border border-zinc-700/60 bg-zinc-900/60">
28 <div
29 className={`min-w-0 break-all px-3 py-2 pr-24 text-sm leading-relaxed text-zinc-200 ${mono ? "font-mono" : ""}`}
30 >
31 {value}
32 </div>
33 <button
34 type="button"
35 onClick={handleCopy}
36 aria-label={`Copy${label ? ` ${label}` : ""}`}
37 className="absolute top-1.5 right-1.5 inline-flex min-w-[5rem] items-center justify-center gap-1.5 rounded-lg border border-zinc-700/60 bg-zinc-800/90 px-2.5 py-1 text-xs text-zinc-200 transition-colors hover:bg-zinc-700/80"
38 >
39 {copied ? (
40 <>
41 <Check className="h-3.5 w-3.5 text-emerald-400" strokeWidth={2.25} aria-hidden="true" />
42 Copied
43 </>
44 ) : (
45 <>
46 <Copy className="h-3.5 w-3.5" strokeWidth={2} aria-hidden="true" />
47 Copy
48 </>
49 )}
50 </button>
51 </div>
52 );
53}