diff --git a/packages/nextjs/app/blockexplorer/_components/AddressComponent.tsx b/packages/nextjs/app/blockexplorer/_components/AddressComponent.tsx index c0c14d60e..2c42eea97 100644 --- a/packages/nextjs/app/blockexplorer/_components/AddressComponent.tsx +++ b/packages/nextjs/app/blockexplorer/_components/AddressComponent.tsx @@ -19,7 +19,7 @@ export const AddressComponent = ({
-
+
Balance: diff --git a/packages/nextjs/app/blockexplorer/_components/TransactionsTable.tsx b/packages/nextjs/app/blockexplorer/_components/TransactionsTable.tsx index b91892cb8..118d912d6 100644 --- a/packages/nextjs/app/blockexplorer/_components/TransactionsTable.tsx +++ b/packages/nextjs/app/blockexplorer/_components/TransactionsTable.tsx @@ -44,14 +44,14 @@ export const TransactionsTable = ({ blocks, transactionReceipts }: TransactionsT {block.number?.toString()} {timeMined} -
+
{!receipt?.contractAddress ? ( - tx.to &&
+ tx.to &&
) : (
-
+
(Contract Creation)
)} diff --git a/packages/nextjs/app/blockexplorer/transaction/_components/TransactionComp.tsx b/packages/nextjs/app/blockexplorer/transaction/_components/TransactionComp.tsx index 848f2e99f..ed9b7ee78 100644 --- a/packages/nextjs/app/blockexplorer/transaction/_components/TransactionComp.tsx +++ b/packages/nextjs/app/blockexplorer/transaction/_components/TransactionComp.tsx @@ -64,7 +64,7 @@ const TransactionComp = ({ txHash }: { txHash: Hash }) => { From: -
+
@@ -73,11 +73,11 @@ const TransactionComp = ({ txHash }: { txHash: Hash }) => { {!receipt?.contractAddress ? ( - transaction.to &&
+ transaction.to &&
) : ( Contract Creation: -
+
)} diff --git a/packages/nextjs/app/debug/_components/contract/ContractUI.tsx b/packages/nextjs/app/debug/_components/contract/ContractUI.tsx index 31fcc7faa..8fcec9d38 100644 --- a/packages/nextjs/app/debug/_components/contract/ContractUI.tsx +++ b/packages/nextjs/app/debug/_components/contract/ContractUI.tsx @@ -48,7 +48,7 @@ export const ContractUI = ({ contractName, className = "" }: ContractUIProps) =>
{contractName} -
+
Balance: diff --git a/packages/nextjs/app/debug/_components/contract/utilsDisplay.tsx b/packages/nextjs/app/debug/_components/contract/utilsDisplay.tsx index 3148affdf..4c5ab2007 100644 --- a/packages/nextjs/app/debug/_components/contract/utilsDisplay.tsx +++ b/packages/nextjs/app/debug/_components/contract/utilsDisplay.tsx @@ -30,7 +30,7 @@ export const displayTxResult = ( if (typeof displayContent === "string") { if (isAddress(displayContent)) { - return
; + return
; } if (isHex(displayContent)) { diff --git a/packages/nextjs/components/scaffold-eth/Address.tsx b/packages/nextjs/components/scaffold-eth/Address.tsx deleted file mode 100644 index 6f299324c..000000000 --- a/packages/nextjs/components/scaffold-eth/Address.tsx +++ /dev/null @@ -1,141 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import Link from "next/link"; -import CopyToClipboard from "react-copy-to-clipboard"; -import { Address as AddressType, getAddress, isAddress } from "viem"; -import { hardhat } from "viem/chains"; -import { normalize } from "viem/ens"; -import { useEnsAvatar, useEnsName } from "wagmi"; -import { CheckCircleIcon, DocumentDuplicateIcon } from "@heroicons/react/24/outline"; -import { BlockieAvatar } from "~~/components/scaffold-eth"; -import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork"; -import { getBlockExplorerAddressLink } from "~~/utils/scaffold-eth"; - -type AddressProps = { - address?: AddressType; - disableAddressLink?: boolean; - format?: "short" | "long"; - size?: "xs" | "sm" | "base" | "lg" | "xl" | "2xl" | "3xl"; -}; - -const blockieSizeMap = { - xs: 6, - sm: 7, - base: 8, - lg: 9, - xl: 10, - "2xl": 12, - "3xl": 15, -}; - -/** - * Displays an address (or ENS) with a Blockie image and option to copy address. - */ -export const Address = ({ address, disableAddressLink, format, size = "base" }: AddressProps) => { - const [ens, setEns] = useState(); - const [ensAvatar, setEnsAvatar] = useState(); - const [addressCopied, setAddressCopied] = useState(false); - const checkSumAddress = address ? getAddress(address) : undefined; - - const { targetNetwork } = useTargetNetwork(); - - const { data: fetchedEns } = useEnsName({ - address: checkSumAddress, - chainId: 1, - query: { - enabled: isAddress(checkSumAddress ?? ""), - }, - }); - const { data: fetchedEnsAvatar } = useEnsAvatar({ - name: fetchedEns ? normalize(fetchedEns) : undefined, - chainId: 1, - query: { - enabled: Boolean(fetchedEns), - gcTime: 30_000, - }, - }); - - // We need to apply this pattern to avoid Hydration errors. - useEffect(() => { - setEns(fetchedEns); - }, [fetchedEns]); - - useEffect(() => { - setEnsAvatar(fetchedEnsAvatar); - }, [fetchedEnsAvatar]); - - // Skeleton UI - if (!checkSumAddress) { - return ( -
-
-
-
-
-
- ); - } - - if (!isAddress(checkSumAddress)) { - return Wrong address; - } - - const blockExplorerAddressLink = getBlockExplorerAddressLink(targetNetwork, checkSumAddress); - let displayAddress = checkSumAddress?.slice(0, 6) + "..." + checkSumAddress?.slice(-4); - - if (ens) { - displayAddress = ens; - } else if (format === "long") { - displayAddress = checkSumAddress; - } - - return ( -
-
- -
- {disableAddressLink ? ( - {displayAddress} - ) : targetNetwork.id === hardhat.id ? ( - - {displayAddress} - - ) : ( - - {displayAddress} - - )} - {addressCopied ? ( -
- ); -}; diff --git a/packages/nextjs/components/scaffold-eth/Address/Address.tsx b/packages/nextjs/components/scaffold-eth/Address/Address.tsx new file mode 100644 index 000000000..28011d51b --- /dev/null +++ b/packages/nextjs/components/scaffold-eth/Address/Address.tsx @@ -0,0 +1,187 @@ +"use client"; + +import { AddressCopyIcon } from "./AddressCopyIcon"; +import { AddressLinkWrapper } from "./AddressLinkWrapper"; +import { Address as AddressType, getAddress, isAddress } from "viem"; +import { normalize } from "viem/ens"; +import { useEnsAvatar, useEnsName } from "wagmi"; +import { BlockieAvatar } from "~~/components/scaffold-eth"; +import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork"; +import { getBlockExplorerAddressLink } from "~~/utils/scaffold-eth"; + +const textSizeMap = { + "3xs": "text-[10px]", + "2xs": "text-[11px]", + xs: "text-xs", + sm: "text-sm", + base: "text-base", + lg: "text-lg", + xl: "text-xl", + "2xl": "text-2xl", + "3xl": "text-3xl", + "4xl": "text-4xl", +} as const; + +const blockieSizeMap = { + "3xs": 4, + "2xs": 5, + xs: 6, + sm: 7, + base: 8, + lg: 9, + xl: 10, + "2xl": 12, + "3xl": 15, + "4xl": 17, + "5xl": 19, + "6xl": 21, + "7xl": 23, +} as const; + +const copyIconSizeMap = { + "3xs": "h-2.5 w-2.5", + "2xs": "h-3 w-3", + xs: "h-3.5 w-3.5", + sm: "h-4 w-4", + base: "h-[18px] w-[18px]", + lg: "h-5 w-5", + xl: "h-[22px] w-[22px]", + "2xl": "h-6 w-6", + "3xl": "h-[26px] w-[26px]", + "4xl": "h-7 w-7", +} as const; + +type SizeMap = typeof textSizeMap | typeof blockieSizeMap; + +const getNextSize = (sizeMap: T, currentSize: keyof T, step = 1): keyof T => { + const sizes = Object.keys(sizeMap) as Array; + const currentIndex = sizes.indexOf(currentSize); + const nextIndex = Math.min(currentIndex + step, sizes.length - 1); + return sizes[nextIndex]; +}; + +const getPrevSize = (sizeMap: T, currentSize: keyof T, step = 1): keyof T => { + const sizes = Object.keys(sizeMap) as Array; + const currentIndex = sizes.indexOf(currentSize); + const prevIndex = Math.max(currentIndex - step, 0); + return sizes[prevIndex]; +}; + +type AddressProps = { + address?: AddressType; + disableAddressLink?: boolean; + format?: "short" | "long"; + size?: "xs" | "sm" | "base" | "lg" | "xl" | "2xl" | "3xl"; + onlyEnsOrAddress?: boolean; +}; + +export const Address = ({ + address, + disableAddressLink, + format, + size = "base", + onlyEnsOrAddress = false, +}: AddressProps) => { + const checkSumAddress = address ? getAddress(address) : undefined; + + const { targetNetwork } = useTargetNetwork(); + + const { data: ens, isLoading: isEnsNameLoading } = useEnsName({ + address: checkSumAddress, + chainId: 1, + query: { + enabled: isAddress(checkSumAddress ?? ""), + }, + }); + const { data: ensAvatar } = useEnsAvatar({ + name: ens ? normalize(ens) : undefined, + chainId: 1, + query: { + enabled: Boolean(ens), + gcTime: 30_000, + }, + }); + + const shortAddress = checkSumAddress?.slice(0, 6) + "..." + checkSumAddress?.slice(-4); + const displayAddress = format === "long" ? checkSumAddress : shortAddress; + const displayEnsOrAddress = ens || displayAddress; + + const showSkeleton = !checkSumAddress || (!onlyEnsOrAddress && (ens || isEnsNameLoading)); + + const addressSize = showSkeleton && !onlyEnsOrAddress ? getPrevSize(textSizeMap, size, 2) : size; + const ensSize = getNextSize(textSizeMap, addressSize); + const blockieSize = showSkeleton && !onlyEnsOrAddress ? getNextSize(blockieSizeMap, addressSize, 4) : addressSize; + + if (!checkSumAddress) { + return ( +
+
+
+ {!onlyEnsOrAddress && ( +
+ 0x1234...56789 +
+ )} +
+ 0x1234...56789 +
+
+
+ ); + } + + if (!isAddress(checkSumAddress)) { + return Wrong address; + } + + const blockExplorerAddressLink = getBlockExplorerAddressLink(targetNetwork, checkSumAddress); + + return ( +
+
+ +
+
+ {showSkeleton && + (isEnsNameLoading ? ( +
+ {shortAddress} +
+ ) : ( + + + {ens} + + + ))} +
+ + + {onlyEnsOrAddress ? displayEnsOrAddress : displayAddress} + + + +
+
+
+ ); +}; diff --git a/packages/nextjs/components/scaffold-eth/Address/AddressCopyIcon.tsx b/packages/nextjs/components/scaffold-eth/Address/AddressCopyIcon.tsx new file mode 100644 index 000000000..48fbe1f0b --- /dev/null +++ b/packages/nextjs/components/scaffold-eth/Address/AddressCopyIcon.tsx @@ -0,0 +1,26 @@ +import { useState } from "react"; +import CopyToClipboard from "react-copy-to-clipboard"; +import { CheckCircleIcon, DocumentDuplicateIcon } from "@heroicons/react/24/outline"; + +export const AddressCopyIcon = ({ className, address }: { className?: string; address: string }) => { + const [addressCopied, setAddressCopied] = useState(false); + return ( + { + setAddressCopied(true); + setTimeout(() => { + setAddressCopied(false); + }, 800); + }} + > + + + ); +}; diff --git a/packages/nextjs/components/scaffold-eth/Address/AddressLinkWrapper.tsx b/packages/nextjs/components/scaffold-eth/Address/AddressLinkWrapper.tsx new file mode 100644 index 000000000..50e2ec0bb --- /dev/null +++ b/packages/nextjs/components/scaffold-eth/Address/AddressLinkWrapper.tsx @@ -0,0 +1,29 @@ +import Link from "next/link"; +import { hardhat } from "viem/chains"; +import { useTargetNetwork } from "~~/hooks/scaffold-eth"; + +type AddressLinkWrapperProps = { + children: React.ReactNode; + disableAddressLink?: boolean; + blockExplorerAddressLink: string; +}; + +export const AddressLinkWrapper = ({ + children, + disableAddressLink, + blockExplorerAddressLink, +}: AddressLinkWrapperProps) => { + const { targetNetwork } = useTargetNetwork(); + + return disableAddressLink ? ( + <>{children} + ) : ( + + {children} + + ); +}; diff --git a/packages/nextjs/components/scaffold-eth/Faucet.tsx b/packages/nextjs/components/scaffold-eth/Faucet.tsx index a55e7fb1f..569a923f1 100644 --- a/packages/nextjs/components/scaffold-eth/Faucet.tsx +++ b/packages/nextjs/components/scaffold-eth/Faucet.tsx @@ -98,7 +98,7 @@ export const Faucet = () => {
From: -
+
Available: diff --git a/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/AddressQRCodeModal.tsx b/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/AddressQRCodeModal.tsx index b5bb2efb8..a46356b10 100644 --- a/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/AddressQRCodeModal.tsx +++ b/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/AddressQRCodeModal.tsx @@ -22,7 +22,7 @@ export const AddressQRCodeModal = ({ address, modalId }: AddressQRCodeModalProps
-
+
diff --git a/packages/nextjs/components/scaffold-eth/index.tsx b/packages/nextjs/components/scaffold-eth/index.tsx index bf1e8a749..333cdf74b 100644 --- a/packages/nextjs/components/scaffold-eth/index.tsx +++ b/packages/nextjs/components/scaffold-eth/index.tsx @@ -1,4 +1,4 @@ -export * from "./Address"; +export * from "./Address/Address"; export * from "./Balance"; export * from "./BlockieAvatar"; export * from "./Faucet";