diff --git a/src/components/InlineCopyButton.tsx b/src/components/InlineCopyButton.tsx new file mode 100644 index 00000000000..d3938e1cafc --- /dev/null +++ b/src/components/InlineCopyButton.tsx @@ -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 = +const checkIcon = + +export const InlineCopyButton: React.FC = ({ + 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 ( + + {children} + + + + + ) +} diff --git a/src/components/ManageAccountsDrawer/components/ImportAccounts.tsx b/src/components/ManageAccountsDrawer/components/ImportAccounts.tsx index a74f368ea71..10e063e03d3 100644 --- a/src/components/ManageAccountsDrawer/components/ImportAccounts.tsx +++ b/src/components/ManageAccountsDrawer/components/ImportAccounts.tsx @@ -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 { @@ -78,11 +79,13 @@ const TableRowAccount = forwardRef(({ asset, accoun return ( <> - -
- {accountLabel} -
-
+ + +
+ {accountLabel} +
+
+
{isLoading ? ( diff --git a/src/hooks/useCopyToClipboard.tsx b/src/hooks/useCopyToClipboard.tsx new file mode 100644 index 00000000000..53806afcb15 --- /dev/null +++ b/src/hooks/useCopyToClipboard.tsx @@ -0,0 +1,36 @@ +import { useState } from 'react' + +export interface useCopyToClipboardProps { + timeout?: number +} + +export function useCopyToClipboard({ timeout = 2000 }: useCopyToClipboardProps) { + const [isCopied, setIsCopied] = useState(false) + const [isCopying, setIsCopying] = useState(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 } +} diff --git a/src/pages/Accounts/components/AccountNumberRow.tsx b/src/pages/Accounts/components/AccountNumberRow.tsx index fad7fbe7d04..12bc56af5c6 100644 --- a/src/pages/Accounts/components/AccountNumberRow.tsx +++ b/src/pages/Accounts/components/AccountNumberRow.tsx @@ -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, @@ -15,8 +15,8 @@ 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' @@ -24,6 +24,7 @@ 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 { @@ -51,6 +52,8 @@ const mdOutlineMoreVertIcon = const riWindow2LineIcon = const arrowDownIcon = const arrowUpIcon = +const copyIcon = +const checkIcon = const UtxoAccountEntries: React.FC = ({ accountIds, chainId }) => { const feeAsset = useAppSelector(s => selectFeeAssetByChainId(s, chainId)) @@ -108,6 +111,7 @@ export const AccountNumberRow: React.FC = ({ ...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 @@ -148,6 +152,11 @@ export const AccountNumberRow: React.FC = ({ const fontFamily = useMemo(() => (!isUtxoChainId(chainId) ? 'monospace' : ''), [chainId]) + const handleCopyClick = useCallback(() => { + const account = fromAccountId(accountId).account + copyToClipboard(account) + }, [accountId, copyToClipboard]) + return ( @@ -198,6 +207,9 @@ export const AccountNumberRow: React.FC = ({ {translate(isOpen ? 'accounts.hideAssets' : 'accounts.showAssets')} + + {translate(isCopied ? 'common.copied' : 'common.copy')} +