188 lines
5.7 KiB
TypeScript
188 lines
5.7 KiB
TypeScript
"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 = <T extends SizeMap>(sizeMap: T, currentSize: keyof T, step = 1): keyof T => {
|
|
const sizes = Object.keys(sizeMap) as Array<keyof T>;
|
|
const currentIndex = sizes.indexOf(currentSize);
|
|
const nextIndex = Math.min(currentIndex + step, sizes.length - 1);
|
|
return sizes[nextIndex];
|
|
};
|
|
|
|
const getPrevSize = <T extends SizeMap>(sizeMap: T, currentSize: keyof T, step = 1): keyof T => {
|
|
const sizes = Object.keys(sizeMap) as Array<keyof T>;
|
|
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 (
|
|
<div className="flex items-center">
|
|
<div
|
|
className="shrink-0 skeleton rounded-full"
|
|
style={{
|
|
width: (blockieSizeMap[blockieSize] * 24) / blockieSizeMap["base"],
|
|
height: (blockieSizeMap[blockieSize] * 24) / blockieSizeMap["base"],
|
|
}}
|
|
></div>
|
|
<div className="flex flex-col space-y-1">
|
|
{!onlyEnsOrAddress && (
|
|
<div className={`ml-1.5 skeleton rounded-lg font-bold ${textSizeMap[ensSize]}`}>
|
|
<span className="invisible">0x1234...56789</span>
|
|
</div>
|
|
)}
|
|
<div className={`ml-1.5 skeleton rounded-lg ${textSizeMap[addressSize]}`}>
|
|
<span className="invisible">0x1234...56789</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!isAddress(checkSumAddress)) {
|
|
return <span className="text-error">Wrong address</span>;
|
|
}
|
|
|
|
const blockExplorerAddressLink = getBlockExplorerAddressLink(targetNetwork, checkSumAddress);
|
|
|
|
return (
|
|
<div className="flex items-center shrink-0">
|
|
<div className="shrink-0">
|
|
<BlockieAvatar
|
|
address={checkSumAddress}
|
|
ensImage={ensAvatar}
|
|
size={(blockieSizeMap[blockieSize] * 24) / blockieSizeMap["base"]}
|
|
/>
|
|
</div>
|
|
<div className="flex flex-col">
|
|
{showSkeleton &&
|
|
(isEnsNameLoading ? (
|
|
<div className={`ml-1.5 skeleton rounded-lg font-bold ${textSizeMap[ensSize]}`}>
|
|
<span className="invisible">{shortAddress}</span>
|
|
</div>
|
|
) : (
|
|
<span className={`ml-1.5 ${textSizeMap[ensSize]} font-bold`}>
|
|
<AddressLinkWrapper
|
|
disableAddressLink={disableAddressLink}
|
|
blockExplorerAddressLink={blockExplorerAddressLink}
|
|
>
|
|
{ens}
|
|
</AddressLinkWrapper>
|
|
</span>
|
|
))}
|
|
<div className="flex">
|
|
<span className={`ml-1.5 ${textSizeMap[addressSize]} font-normal`}>
|
|
<AddressLinkWrapper
|
|
disableAddressLink={disableAddressLink}
|
|
blockExplorerAddressLink={blockExplorerAddressLink}
|
|
>
|
|
{onlyEnsOrAddress ? displayEnsOrAddress : displayAddress}
|
|
</AddressLinkWrapper>
|
|
</span>
|
|
<AddressCopyIcon
|
|
className={`ml-1 ${copyIconSizeMap[addressSize]} cursor-pointer`}
|
|
address={checkSumAddress}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|