File
Blob: src/client/components/ui/copyable.tsx
| 1 | import { Check, Copy } from "lucide-react"; |
| 2 | import { useState } from "react"; |
| 3 | |
| 4 | import { toast } from "@/client/lib/toast"; |
| 5 | |
| 6 | type Props = { |
| 7 | value: string; |
| 8 | copyValue?: string; |
| 9 | label?: string; |
| 10 | mono?: boolean; |
| 11 | }; |
| 12 | |
| 13 | export 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 | } |