Skip to content

Commit 252e734

Browse files
authored
feat: copy addresses and ids (#84)
1 parent 418216d commit 252e734

17 files changed

+560
-338
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ dist-ssr
1919
*.njsproj
2020
*.sln
2121
*.sw?
22+
.idea
Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,38 @@
1+
import { CopyButton } from '@/features/common/components/copy-button'
12
import { cn } from '@/features/common/utils'
23
import { TemplatedNavLink } from '@/features/routing/components/templated-nav-link/templated-nav-link'
34
import { Urls } from '@/routes/urls'
45
import { ellipseAddress } from '@/utils/ellipse-address'
56
import { fixedForwardRef } from '@/utils/fixed-forward-ref'
6-
import { PropsWithChildren } from 'react'
7+
import { PropsWithChildren, useCallback } from 'react'
8+
import { toast } from 'react-toastify'
79

810
type Props = PropsWithChildren<{
911
address: string
1012
short?: boolean
1113
className?: string
14+
showCopyButton?: boolean
1215
}>
1316

1417
export const AccountLink = fixedForwardRef(
15-
({ address, short, className, children, ...rest }: Props, ref?: React.LegacyRef<HTMLAnchorElement>) => {
18+
({ address, short, className, children, showCopyButton, ...rest }: Props, ref?: React.LegacyRef<HTMLAnchorElement>) => {
19+
const copyClipboard = useCallback(async () => {
20+
await navigator.clipboard.writeText(address)
21+
toast.success('Address copied to clipboard')
22+
}, [address])
1623
return (
17-
<TemplatedNavLink
18-
className={cn(!children && 'text-primary underline', className)}
19-
urlTemplate={Urls.Explore.Account.ByAddress}
20-
urlParams={{ address }}
21-
ref={ref}
22-
{...rest}
23-
>
24-
{children ? children : short ? <abbr title={address}>{ellipseAddress(address)}</abbr> : address}
25-
</TemplatedNavLink>
24+
<div className={cn('inline-flex gap-2 items-center')}>
25+
<TemplatedNavLink
26+
className={cn(!children && 'text-primary underline', className)}
27+
urlTemplate={Urls.Explore.Account.ByAddress}
28+
urlParams={{ address }}
29+
ref={ref}
30+
{...rest}
31+
>
32+
{children ? children : short ? <abbr title={address}>{ellipseAddress(address)}</abbr> : address}
33+
</TemplatedNavLink>
34+
{showCopyButton && <CopyButton onClick={copyClipboard} />}
35+
</div>
2636
)
2737
}
2838
)

src/features/assets/components/asset-link.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import { cn } from '@/features/common/utils'
22
import { TemplatedNavLink } from '@/features/routing/components/templated-nav-link/templated-nav-link'
33
import { Urls } from '@/routes/urls'
4-
import { PropsWithChildren } from 'react'
4+
import { PropsWithChildren, useCallback } from 'react'
55
import { AssetSummary } from '../models'
66
import { AsyncMaybeAtom } from '@/features/common/data/types'
77
import { RenderInlineAsyncAtom } from '@/features/common/components/render-inline-async-atom'
8+
import { CopyButton } from '@/features/common/components/copy-button.tsx'
9+
import { toast } from 'react-toastify'
810

911
type CommonProps = {
1012
className?: string
13+
showCopyButton?: boolean
1114
}
1215

1316
type AssetIdLinkProps = PropsWithChildren<
@@ -30,8 +33,13 @@ type AssetLinkProps = PropsWithChildren<
3033
>
3134

3235
function Link(props: AssetIdLinkProps | AssetIdAndNameLinkProps) {
36+
const copyClipboard = useCallback(async () => {
37+
await navigator.clipboard.writeText(props.assetId.toString())
38+
toast.success('Asset ID copied to clipboard')
39+
}, [props.assetId])
40+
3341
return (
34-
<span>
42+
<div className={cn('inline-flex gap-2 items-center')}>
3543
<TemplatedNavLink
3644
className={cn(!props.children && 'text-primary underline', props.className)}
3745
urlTemplate={Urls.Explore.Asset.ById}
@@ -40,7 +48,8 @@ function Link(props: AssetIdLinkProps | AssetIdAndNameLinkProps) {
4048
{props.children ? props.children : props.assetId}
4149
</TemplatedNavLink>
4250
{'assetName' in props && props.assetName && ` (${props.assetName})`}
43-
</span>
51+
{props.showCopyButton && <CopyButton onClick={copyClipboard} />}
52+
</div>
4453
)
4554
}
4655

src/features/common/components/button.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ const buttonVariants = cva(
1515
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
1616
ghost: 'hover:bg-accent hover:text-accent-foreground',
1717
link: 'text-primary underline-offset-4 hover:underline',
18+
['no-style']: '',
1819
},
1920
size: {
2021
default: 'h-10 px-4 py-2',
2122
sm: 'h-9 rounded-md px-3',
2223
lg: 'h-11 rounded-md px-8',
2324
icon: 'size-10',
25+
['sm-icon']: 'size-4',
2426
},
2527
},
2628
defaultVariants: {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { CopyIcon } from 'lucide-react'
2+
import { Button } from './button'
3+
4+
type Props = {
5+
onClick: () => void
6+
}
7+
8+
export const copyButtonLabel = 'Copy'
9+
10+
export function CopyButton({ onClick }: Props) {
11+
return (
12+
<Button onClick={onClick} variant="no-style" size="sm-icon" aria-label={copyButtonLabel} className="hover:bg-transparent">
13+
<CopyIcon className="size-4" />
14+
</Button>
15+
)
16+
}

0 commit comments

Comments
 (0)