Skip to content

Commit cf435cd

Browse files
committed
feat: add copy address component
1 parent 1e20424 commit cf435cd

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
@use "@pythnetwork/component-library/theme";
2+
3+
.copyAddress {
4+
position: relative;
5+
6+
.icon {
7+
position: absolute;
8+
right: 0;
9+
top: 0;
10+
display: none;
11+
12+
&:has(.copied) {
13+
display: block;
14+
}
15+
}
16+
17+
&:hover {
18+
.icon {
19+
display: block;
20+
}
21+
}
22+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"use client"
2+
3+
import { CheckSquare, Copy } from "@phosphor-icons/react";
4+
import { Link } from "@pythnetwork/component-library/unstyled/Link";
5+
import { useRef, useState } from "react";
6+
import { useClipboard, useKeyboard, usePress } from 'react-aria';
7+
8+
import TruncateToMiddle from "../TruncateToMiddle";
9+
import styles from "./index.module.scss";
10+
11+
12+
13+
const CopyAddress = ({ address, url }: { address: string; url?: string }) => {
14+
const [copied, setCopied] = useState<boolean>(false);
15+
const timeout = useRef<NodeJS.Timeout | undefined>(undefined);
16+
17+
const showCopied = () => {
18+
setCopied(true);
19+
if(timeout.current) clearTimeout(timeout.current);
20+
timeout.current = setTimeout(() => {
21+
setCopied(false);
22+
timeout.current = undefined;
23+
}, 2000);
24+
}
25+
26+
const { clipboardProps } = useClipboard({
27+
getItems() {
28+
showCopied();
29+
return [{
30+
'text/plain': address
31+
}];
32+
}
33+
});
34+
35+
const { pressProps } = usePress({
36+
onPress: () => {
37+
void writeClipboardText(address);
38+
}
39+
});
40+
41+
const { keyboardProps } = useKeyboard({
42+
onKeyUp: (e) => {
43+
if(e.key === "Enter") {
44+
void writeClipboardText(address);
45+
}
46+
}
47+
});
48+
49+
async function writeClipboardText(text: string) {
50+
try {
51+
await navigator.clipboard.writeText(text);
52+
showCopied();
53+
} catch {
54+
// Silent fail - clipboard operations may not be supported in all environments
55+
}
56+
}
57+
58+
return (
59+
<div
60+
tabIndex={0}
61+
onKeyDown={(e) => {
62+
if (e.key === 'Enter' || e.key === ' ') {
63+
e.preventDefault();
64+
void writeClipboardText(address);
65+
}
66+
}}
67+
onClick={(e) => {
68+
e.preventDefault();
69+
void writeClipboardText(address);
70+
}}
71+
{...clipboardProps}
72+
{...pressProps}
73+
{...keyboardProps}
74+
className={styles.copyAddress}
75+
role="button"
76+
aria-label="Copy address to clipboard"
77+
>
78+
{url ? (
79+
<Link href={url}>
80+
<TruncateToMiddle text={address} />
81+
</Link>
82+
) : (
83+
<TruncateToMiddle text={address} />
84+
)}
85+
<div className={styles.icon}>
86+
{copied ? <CheckSquare size={24} className={styles.copied} /> : <Copy size={24} /> }
87+
</div>
88+
</div>
89+
);
90+
};
91+
92+
export default CopyAddress;

0 commit comments

Comments
 (0)