aboutsummaryrefslogtreecommitdiff
path: root/packages/ui/copyable-cell.tsx
blob: 6b2dbc89ecc214bee7aa2d37724c82a17aac9bee (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
"use client";

import { cn } from "@lib/utils";
import { Label1Regular } from "@ui/text/label/label-1-regular";
import { AnimatePresence, motion } from "motion/react";
import * as React from "react";

interface CopyableCellProps extends React.HTMLAttributes<HTMLDivElement> {
	value: string;
	displayValue?: React.ReactNode;
}

export function CopyableCell({
	value,
	displayValue,
	className,
	children,
	...props
}: CopyableCellProps) {
	const [hasCopied, setHasCopied] = React.useState(false);

	React.useEffect(() => {
		if (hasCopied) {
			const timeout = setTimeout(() => {
				setHasCopied(false);
			}, 2000);
			return () => clearTimeout(timeout);
		}
	}, [hasCopied]);

	const handleCopy = async (e: React.MouseEvent) => {
		e.stopPropagation();
		try {
			await navigator.clipboard.writeText(value);
			setHasCopied(true);
		} catch (err) {
			console.error("Failed to copy:", err);
		}
	};

	return (
		// biome-ignore lint/a11y/noStaticElementInteractions: shadcn
		// biome-ignore lint/a11y/useKeyWithClickEvents: shadcn
		<div
			className={cn(
				"cursor-pointer transition-colors duration-200",
				"hover:bg-zinc-800/50 hover:text-zinc-50",
				"rounded px-2 py-1 -mx-2 -my-1",
				"relative",
				className,
			)}
			onClick={handleCopy}
			{...props}
		>
			<AnimatePresence mode="wait">
				{hasCopied ? (
					<Label1Regular asChild className="block">
						<motion.span
							animate={{ opacity: 1, y: 0 }}
							exit={{ opacity: 0, y: -10 }}
							initial={{ opacity: 0, y: 10 }}
							key="copied"
							transition={{ duration: 0.2 }}
						>
							Copied!
						</motion.span>
					</Label1Regular>
				) : (
					<Label1Regular asChild>
						<motion.div
							animate={{ opacity: 1, y: 0 }}
							exit={{ opacity: 0, y: 10 }}
							initial={{ opacity: 0, y: -10 }}
							key="content"
							transition={{ duration: 0.2 }}
						>
							{displayValue || children || value}
						</motion.div>
					</Label1Regular>
				)}
			</AnimatePresence>
		</div>
	);
}