Skip to content

Commit

Permalink
feat: add copy button for accounts (#7197)
Browse files Browse the repository at this point in the history
* add copy button for accounts

* move the logic into the hook

* Update useCopyToClipboard.tsx
  • Loading branch information
reallybeard authored Jun 24, 2024
1 parent cc0e5ac commit f4d3afa
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 8 deletions.
49 changes: 49 additions & 0 deletions src/components/InlineCopyButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { CheckIcon, CopyIcon } from '@chakra-ui/icons'
import { Flex, IconButton } from '@chakra-ui/react'
import type { PropsWithChildren } from 'react'
import React, { useCallback } from 'react'
import { useTranslate } from 'react-polyglot'
import { useCopyToClipboard } from 'hooks/useCopyToClipboard'

import { TooltipWithTouch } from './TooltipWithTouch'

type InlineCopyButtonProps = {
value: string
isDisabled?: boolean
} & PropsWithChildren

const copyIcon = <CopyIcon />
const checkIcon = <CheckIcon />

export const InlineCopyButton: React.FC<InlineCopyButtonProps> = ({
value,
children,
isDisabled,
}) => {
const translate = useTranslate()
const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 })

const handleCopyClick = useCallback(() => {
copyToClipboard(value)
}, [copyToClipboard, value])

// Hide the copy button if it is disabled
if (isDisabled) return <>{children}</>

return (
<Flex gap={2} alignItems='center'>
{children}
<TooltipWithTouch label={translate(isCopied ? 'common.copied' : 'common.copy')}>
<IconButton
icon={isCopied ? checkIcon : copyIcon}
colorScheme={isCopied ? 'green' : 'gray'}
size='sm'
variant='ghost'
fontSize='xl'
aria-label='Copy value'
onClick={handleCopyClick}
/>
</TooltipWithTouch>
</Flex>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslate } from 'react-polyglot'
import { accountManagement } from 'react-queries/queries/accountManagement'
import { Amount } from 'components/Amount/Amount'
import { InlineCopyButton } from 'components/InlineCopyButton'
import { RawText } from 'components/Text'
import { WalletActions } from 'context/WalletProvider/actions'
import {
Expand Down Expand Up @@ -78,11 +79,13 @@ const TableRowAccount = forwardRef<TableRowAccountProps, 'div'>(({ asset, accoun
return (
<>
<Td fontWeight='bold'>
<Tooltip label={pubkey} isDisabled={isUtxoAccount}>
<div ref={ref}>
<RawText>{accountLabel}</RawText>
</div>
</Tooltip>
<InlineCopyButton value={pubkey} isDisabled={isUtxoAccount}>
<Tooltip label={pubkey} isDisabled={isUtxoAccount}>
<div ref={ref}>
<RawText>{accountLabel}</RawText>
</div>
</Tooltip>
</InlineCopyButton>
</Td>
<Td textAlign='right'>
{isLoading ? (
Expand Down
36 changes: 36 additions & 0 deletions src/hooks/useCopyToClipboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useState } from 'react'

export interface useCopyToClipboardProps {
timeout?: number
}

export function useCopyToClipboard({ timeout = 2000 }: useCopyToClipboardProps) {
const [isCopied, setIsCopied] = useState<boolean>(false)
const [isCopying, setIsCopying] = useState<boolean>(false)

const copyToClipboard = (value: string) => {
if (typeof window === 'undefined' || !navigator.clipboard?.writeText) {
return
}

if (!value) {
return
}

// Prevent race condition if the user clicks copy multiple times
if (isCopying) return
setIsCopying(true)

void navigator.clipboard.writeText(value).then(() => {
setIsCopied(true)

setTimeout(() => {
// Reset the state after the timeout
setIsCopying(false)
setIsCopied(false)
}, timeout)
})
}

return { isCopied, copyToClipboard }
}
18 changes: 15 additions & 3 deletions src/pages/Accounts/components/AccountNumberRow.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ArrowDownIcon, ArrowUpIcon } from '@chakra-ui/icons'
import { ArrowDownIcon, ArrowUpIcon, CheckIcon, CopyIcon } from '@chakra-ui/icons'
import type { ButtonProps } from '@chakra-ui/react'
import {
Avatar,
Expand All @@ -15,15 +15,16 @@ import {
Stack,
useDisclosure,
} from '@chakra-ui/react'
import type { AccountId, ChainId } from '@shapeshiftoss/caip'
import { useMemo } from 'react'
import { type AccountId, type ChainId, fromAccountId } from '@shapeshiftoss/caip'
import { useCallback, useMemo } from 'react'
import { MdOutlineMoreVert } from 'react-icons/md'
import { RiWindow2Line } from 'react-icons/ri'
import { useTranslate } from 'react-polyglot'
import { useSelector } from 'react-redux'
import { Amount } from 'components/Amount/Amount'
import { NestedList } from 'components/NestedList'
import { RawText } from 'components/Text'
import { useCopyToClipboard } from 'hooks/useCopyToClipboard'
import { getAccountTitle } from 'lib/utils/accounts'
import { isUtxoAccountId, isUtxoChainId } from 'lib/utils/utxo'
import {
Expand Down Expand Up @@ -51,6 +52,8 @@ const mdOutlineMoreVertIcon = <MdOutlineMoreVert />
const riWindow2LineIcon = <RiWindow2Line />
const arrowDownIcon = <ArrowDownIcon />
const arrowUpIcon = <ArrowUpIcon />
const copyIcon = <CopyIcon />
const checkIcon = <CheckIcon />

const UtxoAccountEntries: React.FC<UtxoAccountEntriesProps> = ({ accountIds, chainId }) => {
const feeAsset = useAppSelector(s => selectFeeAssetByChainId(s, chainId))
Expand Down Expand Up @@ -108,6 +111,7 @@ export const AccountNumberRow: React.FC<AccountNumberRowProps> = ({
...buttonProps
}) => {
const { isOpen, onToggle } = useDisclosure()
const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 })
const translate = useTranslate()
const assets = useSelector(selectAssets)
const accountId = useMemo(() => accountIds[0], [accountIds]) // all accountIds belong to the same chain
Expand Down Expand Up @@ -148,6 +152,11 @@ export const AccountNumberRow: React.FC<AccountNumberRowProps> = ({

const fontFamily = useMemo(() => (!isUtxoChainId(chainId) ? 'monospace' : ''), [chainId])

const handleCopyClick = useCallback(() => {
const account = fromAccountId(accountId).account
copyToClipboard(account)
}, [accountId, copyToClipboard])

return (
<ListItem>
<Flex p={0} flexDir='row' display='flex' gap={2} alignItems='center'>
Expand Down Expand Up @@ -198,6 +207,9 @@ export const AccountNumberRow: React.FC<AccountNumberRowProps> = ({
<MenuItem onClick={onToggle} icon={isOpen ? arrowUpIcon : arrowDownIcon}>
{translate(isOpen ? 'accounts.hideAssets' : 'accounts.showAssets')}
</MenuItem>
<MenuItem onClick={handleCopyClick} icon={isCopied ? checkIcon : copyIcon}>
{translate(isCopied ? 'common.copied' : 'common.copy')}
</MenuItem>
</MenuGroup>
</MenuList>
</Menu>
Expand Down

0 comments on commit f4d3afa

Please sign in to comment.