Commit 9d6a512c authored by tom goriunov's avatar tom goriunov Committed by GitHub

collapse long token name (#994)

* fix tokens list, token transfers and token select items

* fix tx details view

* delete trimTokenSymbol utility

* fix max token supply

* truncate page title

* change verified token badge

* change api host (for preview purpose)

* fix tests

* update screenshots
parent cec71aa0
......@@ -77,7 +77,7 @@ frontend:
NEXT_PUBLIC_FEATURED_NETWORKS:
_default: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-goerli.json
NEXT_PUBLIC_API_HOST:
_default: blockscout-main.k8s-dev.blockscout.com
_default: eth-goerli.blockscout.com
NEXT_PUBLIC_STATS_API_HOST:
_default: https://stats-test.k8s-dev.blockscout.com/
NEXT_PUBLIC_VISUALIZE_API_HOST:
......
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.522 2.624a2 2 0 0 1 2.957 0l1.02 1.12a2 2 0 0 0 1.572.651l1.514-.07a2 2 0 0 1 2.091 2.09l-.07 1.514a2 2 0 0 0 .65 1.572l1.12 1.02a2 2 0 0 1 0 2.958l-1.12 1.02a2 2 0 0 0-.65 1.572l.07 1.514a2 2 0 0 1-2.091 2.091l-1.514-.07a2 2 0 0 0-1.572.65l-1.02 1.12a2 2 0 0 1-2.957 0l-1.02-1.12a2 2 0 0 0-1.573-.65l-1.513.07a2 2 0 0 1-2.091-2.091l.07-1.514a2 2 0 0 0-.65-1.572l-1.121-1.02a2 2 0 0 1 0-2.957l1.12-1.02a2 2 0 0 0 .651-1.573l-.07-1.513a2 2 0 0 1 2.09-2.091l1.514.07a2 2 0 0 0 1.572-.65l1.02-1.121Z" stroke="currentColor" stroke-width="1.5"/>
<path d="m9 12 2 2 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
import appConfig from 'configs/app/config';
// import appConfig from 'configs/app/config';
// FIXME
// I was not able to figure out how to send CORS with credentials from localhost
// unsuccessfully tried different ways, even custom local dev domain
// so for local development we have to use next.js api as proxy server
export default function isNeedProxy() {
return appConfig.host === 'localhost' && appConfig.host !== appConfig.api.host;
// eslint-disable-next-line no-restricted-properties
if (process.env.NEXT_PUBLIC_APP_INSTANCE === 'pw') {
return false;
}
return true;
}
// some tokens could have symbols like "ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY"
// so in some cases we trim it to max 10 symbols
export default function trimTokenSymbol(symbol: string | null) {
if (!symbol) {
return '';
}
if (symbol.length <= 7) {
return symbol;
}
return symbol.slice(0, 7) + '...';
}
......@@ -184,7 +184,7 @@ export const withActionsUniswap: Transaction = {
address1: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6',
amount0: '7143.488560357232097378',
amount1: '10',
symbol0: 'XFT',
symbol0: 'Ring ding ding daa baa Baa aramba baa bom baa barooumba Wh-wha-what&#39;s going on-on? Ding, ding This is the Crazy Frog Ding, ding Bem',
symbol1: 'Ether',
},
protocol: 'uniswap_v3',
......
import { Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Address } from 'types/api/address';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import LinkInternal from 'ui/shared/LinkInternal';
import TokenSnippet from 'ui/shared/TokenSnippet/TokenSnippet';
interface Props {
data: Pick<Address, 'name' | 'token' | 'is_contract'>;
......@@ -15,18 +13,13 @@ interface Props {
const AddressNameInfo = ({ data, isLoading }: Props) => {
if (data.token) {
const symbol = data.token.symbol ? ` (${ trimTokenSymbol(data.token.symbol) })` : '';
return (
<DetailsInfoItem
title="Token name"
hint="Token name and symbol"
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading }>
<LinkInternal href={ route({ pathname: '/token/[hash]', query: { hash: data.token.address } }) }>
{ data.token.name || 'Unnamed token' }{ symbol }
</LinkInternal>
</Skeleton>
<TokenSnippet data={ data.token } isLoading={ isLoading } hideIcon/>
</DetailsInfoItem>
);
}
......
......@@ -3,9 +3,8 @@ import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import HashStringShorten from 'ui/shared/HashStringShorten';
import TokenLogo from 'ui/shared/TokenLogo';
import TokenSnippet from 'ui/shared/TokenSnippet/TokenSnippet';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
import type { TokenEnhancedData } from '../utils/tokenUtils';
......@@ -19,15 +18,28 @@ const TokenSelectItem = ({ data }: Props) => {
switch (data.token.type) {
case 'ERC-20': {
const tokenDecimals = Number(data.token.decimals) || 18;
const text = `${ BigNumber(data.value).dividedBy(10 ** tokenDecimals).toFormat(2) } ${ data.token.symbol }`;
return (
<>
<span >{ BigNumber(data.value).dividedBy(10 ** tokenDecimals).toFormat(2) } { trimTokenSymbol(data.token.symbol) }</span>
{ data.token.exchange_rate && <span >@{ Number(data.token.exchange_rate).toLocaleString() }</span> }
<TruncatedTextTooltip label={ text }>
<chakra.span textOverflow="ellipsis" overflow="hidden">
{ text }
</chakra.span>
</TruncatedTextTooltip>
{ data.token.exchange_rate && <chakra.span ml={ 2 }>@{ Number(data.token.exchange_rate).toLocaleString() }</chakra.span> }
</>
);
}
case 'ERC-721': {
return <chakra.span textOverflow="ellipsis" overflow="hidden">{ BigNumber(data.value).toFormat() } { data.token.symbol }</chakra.span>;
const text = `${ BigNumber(data.value).toFormat() } ${ data.token.symbol }`;
return (
<TruncatedTextTooltip label={ text }>
<chakra.span textOverflow="ellipsis" overflow="hidden">
{ text }
</chakra.span>
</TruncatedTextTooltip>
);
}
case 'ERC-1155': {
return (
......@@ -64,9 +76,8 @@ const TokenSelectItem = ({ data }: Props) => {
as="a"
href={ url }
>
<Flex alignItems="center" w="100%">
<TokenLogo data={ data.token } boxSize={ 6 }/>
<Text fontWeight={ 700 } ml={ 2 }>{ data.token.name || <HashStringShorten hash={ data.token.address }/> }</Text>
<Flex alignItems="center" w="100%" overflow="hidden">
<TokenSnippet data={ data.token } hideSymbol fontWeight={ 700 } isDisabled/>
{ data.usd && <Text fontWeight={ 700 } ml="auto">${ data.usd.toFormat(2) }</Text> }
</Flex>
<Flex alignItems="center" justifyContent="space-between" w="100%" whiteSpace="nowrap">
......
import { Box, Icon } from '@chakra-ui/react';
import { Box, Icon, Tooltip } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React, { useEffect } from 'react';
......@@ -10,6 +10,7 @@ import type { RoutedTab } from 'ui/shared/Tabs/types';
import appConfig from 'configs/app/config';
import iconSuccess from 'icons/status/success.svg';
import iconVerifiedToken from 'icons/verified_token.svg';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/contexts/app';
import useContractTabs from 'lib/hooks/useContractTabs';
......@@ -17,7 +18,6 @@ import useIsMobile from 'lib/hooks/useIsMobile';
import getQueryParamString from 'lib/router/getQueryParamString';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import * as addressStubs from 'stubs/address';
import * as tokenStubs from 'stubs/token';
import { generateListStub } from 'stubs/utils';
......@@ -197,7 +197,7 @@ const TokenPageContent = () => {
pagination = inventoryQuery.pagination;
}
const tokenSymbolText = tokenQuery.data?.symbol ? ` (${ trimTokenSymbol(tokenQuery.data.symbol) })` : '';
const tokenSymbolText = tokenQuery.data?.symbol ? ` (${ tokenQuery.data.symbol })` : '';
const tabListProps = React.useCallback(({ isSticky, activeTabIndex }: { isSticky: boolean; activeTabIndex: number }) => {
if (isMobile) {
......@@ -225,30 +225,39 @@ const TokenPageContent = () => {
};
}, [ appProps.referrer ]);
const tags = (
<EntityTags
data={ contractQuery.data }
isLoading={ tokenQuery.isPlaceholderData || contractQuery.isPlaceholderData }
tagsBefore={ [
tokenQuery.data ? { label: tokenQuery.data?.type, display_name: tokenQuery.data?.type } : undefined,
] }
tagsAfter={
verifiedInfoQuery.data?.projectSector ?
[ { label: verifiedInfoQuery.data.projectSector, display_name: verifiedInfoQuery.data.projectSector } ] :
undefined
}
contentAfter={
<NetworkExplorers type="token" pathParam={ hashString } ml="auto" hideText={ isMobile }/>
}
flexGrow={ 1 }
/>
const titleContentAfter = (
<>
{ verifiedInfoQuery.data?.tokenAddress && (
<Tooltip label={ `Information on this token has been verified by ${ appConfig.network.name }` }>
<Box boxSize={ 6 }>
<Icon as={ iconVerifiedToken } color="green.500" boxSize={ 6 } cursor="pointer"/>
</Box>
</Tooltip>
) }
<EntityTags
data={ contractQuery.data }
isLoading={ tokenQuery.isPlaceholderData || contractQuery.isPlaceholderData }
tagsBefore={ [
tokenQuery.data ? { label: tokenQuery.data?.type, display_name: tokenQuery.data?.type } : undefined,
] }
tagsAfter={
verifiedInfoQuery.data?.projectSector ?
[ { label: verifiedInfoQuery.data.projectSector, display_name: verifiedInfoQuery.data.projectSector } ] :
undefined
}
contentAfter={
<NetworkExplorers type="token" pathParam={ hashString } ml="auto" hideText={ isMobile }/>
}
flexGrow={ 1 }
/>
</>
);
return (
<>
<TextAd mb={ 6 }/>
<PageTitle
title={ `${ tokenQuery.data?.name || 'Unnamed' }${ tokenSymbolText } token` }
title={ `${ tokenQuery.data?.name || 'Unnamed token' }${ tokenSymbolText }` }
isLoading={ tokenQuery.isPlaceholderData }
backLink={ backLink }
beforeTitle={ (
......@@ -256,18 +265,10 @@ const TokenPageContent = () => {
data={ tokenQuery.data }
boxSize={ 6 }
isLoading={ tokenQuery.isPlaceholderData }
display="inline-block"
mr={ 2 }
my={{ base: 'auto', lg: tokenQuery.isPlaceholderData ? 2 : 'auto' }}
verticalAlign={{ base: undefined, lg: tokenQuery.isPlaceholderData ? 'text-bottom' : undefined }}
/>
) }
afterTitle={
verifiedInfoQuery.data?.tokenAddress ?
<Icon as={ iconSuccess } color="green.500" boxSize={ 4 } verticalAlign="top"/> :
<Box boxSize={ 4 } display="inline-block"/>
}
contentAfter={ tags }
contentAfter={ titleContentAfter }
/>
<TokenContractInfo tokenQuery={ tokenQuery } contractQuery={ contractQuery }/>
<TokenVerifiedInfo verifiedInfoQuery={ verifiedInfoQuery } isVerifiedInfoEnabled={ isVerifiedInfoEnabled }/>
......
......@@ -10,7 +10,6 @@ import txIcon from 'icons/transactions.svg';
import highlightText from 'lib/highlightText';
import * as mixpanel from 'lib/mixpanel/index';
import { saveToRecentKeywords } from 'lib/recentSearchKeywords';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
......@@ -39,10 +38,10 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
const firstRow = (() => {
switch (data.type) {
case 'token': {
const name = data.name + (data.symbol ? ` (${ trimTokenSymbol(data.symbol) })` : '');
const name = data.name + (data.symbol ? ` (${ data.symbol })` : '');
return (
<Flex alignItems="flex-start">
<Flex alignItems="flex-start" overflow="hidden">
<TokenLogo boxSize={ 6 } data={ data } flexShrink={ 0 } isLoading={ isLoading }/>
<LinkInternal
ml={ 2 }
......@@ -51,8 +50,15 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
wordBreak="break-all"
isLoading={ isLoading }
onClick={ handleLinkClick }
overflow="hidden"
>
<Skeleton isLoaded={ !isLoading } dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }}/>
<Skeleton
isLoaded={ !isLoading }
dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }}
whiteSpace="nowrap"
overflow="hidden"
textOverflow="ellipsis"
/>
</LinkInternal>
</Flex>
);
......
......@@ -10,7 +10,6 @@ import txIcon from 'icons/transactions.svg';
import highlightText from 'lib/highlightText';
import * as mixpanel from 'lib/mixpanel/index';
import { saveToRecentKeywords } from 'lib/recentSearchKeywords';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
......@@ -38,7 +37,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
const content = (() => {
switch (data.type) {
case 'token': {
const name = data.name + (data.symbol ? ` (${ trimTokenSymbol(data.symbol) })` : '');
const name = data.name + (data.symbol ? ` (${ data.symbol })` : '');
return (
<>
......@@ -50,10 +49,17 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
href={ route({ pathname: '/token/[hash]', query: { hash: data.address } }) }
fontWeight={ 700 }
wordBreak="break-all"
overflow="hidden"
isLoading={ isLoading }
onClick={ handleLinkClick }
>
<Skeleton isLoaded={ !isLoading } dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }}/>
<Skeleton
isLoaded={ !isLoading }
overflow="hidden"
textOverflow="ellipsis"
whiteSpace="nowrap"
dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }}
/>
</LinkInternal>
</Flex>
</Td>
......
......@@ -37,6 +37,7 @@ const AdditionalInfoButton = ({ isOpen, onClick, className, isLoading }: Props,
h="24px"
onClick={ onClick }
cursor="pointer"
flexShrink={ 0 }
>
<Icon
as={ infoIcon }
......
import { Heading, Flex, Tooltip, Icon, Link, chakra, Box, Skeleton } from '@chakra-ui/react';
import { Heading, Flex, Tooltip, Icon, Link, chakra, Skeleton, useDisclosure } from '@chakra-ui/react';
import _debounce from 'lodash/debounce';
import React from 'react';
import eastArrowIcon from 'icons/arrows/east.svg';
import useIsMobile from 'lib/hooks/useIsMobile';
import TextAd from 'ui/shared/ad/TextAd';
import LinkInternal from 'ui/shared/LinkInternal';
......@@ -18,6 +20,8 @@ type Props = {
withTextAd?: boolean;
}
const TEXT_MAX_LINES = 1;
const BackLink = (props: BackLinkProp & { isLoading?: boolean }) => {
if (!props) {
return null;
......@@ -49,6 +53,42 @@ const BackLink = (props: BackLinkProp & { isLoading?: boolean }) => {
};
const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoading, afterTitle, beforeTitle }: Props) => {
const tooltip = useDisclosure();
const isMobile = useIsMobile();
const [ isTextTruncated, setIsTextTruncated ] = React.useState(false);
const headingRef = React.useRef<HTMLHeadingElement>(null);
const textRef = React.useRef<HTMLSpanElement>(null);
const updatedTruncateState = React.useCallback(() => {
if (!headingRef.current || !textRef.current) {
return;
}
const headingRect = headingRef.current.getBoundingClientRect();
const textRect = textRef.current.getBoundingClientRect();
if ((TEXT_MAX_LINES + 1) * headingRect.height <= textRect.height) {
setIsTextTruncated(true);
} else {
setIsTextTruncated(false);
}
}, []);
React.useEffect(() => {
if (!isLoading) {
updatedTruncateState();
}
}, [ isLoading, updatedTruncateState ]);
React.useEffect(() => {
const handleResize = _debounce(updatedTruncateState, 1000);
window.addEventListener('resize', handleResize);
return function cleanup() {
window.removeEventListener('resize', handleResize);
};
}, [ updatedTruncateState ]);
return (
<Flex
className={ className }
......@@ -59,26 +99,46 @@ const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoa
columnGap={ 3 }
alignItems="center"
>
<Box h={{ base: 'auto', lg: isLoading ? 10 : 'auto' }}>
<Flex h={{ base: 'auto', lg: isLoading ? 10 : 'auto' }} maxW="100%" alignItems="center">
{ backLink && <BackLink { ...backLink } isLoading={ isLoading }/> }
{ beforeTitle }
<Skeleton
isLoaded={ !isLoading }
display={{ base: 'inline', lg: isLoading ? 'inline-block' : 'inline' }}
verticalAlign={ isLoading ? 'super' : undefined }
overflow="hidden"
>
<Heading
as="h1"
size="lg"
display="inline"
wordBreak="break-word"
w="100%"
<Tooltip
label={ title }
isOpen={ tooltip.isOpen }
onClose={ tooltip.onClose }
maxW={{ base: 'calc(100vw - 32px)', lg: '500px' }}
closeOnScroll={ isMobile ? true : false }
isDisabled={ !isTextTruncated }
>
{ title }
</Heading>
<Heading
ref={ headingRef }
as="h1"
size="lg"
whiteSpace="normal"
wordBreak="break-all"
style={{
WebkitLineClamp: TEXT_MAX_LINES,
WebkitBoxOrient: 'vertical',
display: '-webkit-box',
}}
overflow="hidden"
textOverflow="ellipsis"
onMouseEnter={ tooltip.onOpen }
onMouseLeave={ tooltip.onClose }
onClick={ isMobile ? tooltip.onToggle : undefined }
>
<span ref={ textRef }>
{ title }
</span>
</Heading>
</Tooltip>
</Skeleton>
{ afterTitle }
</Box>
</Flex>
{ contentAfter }
{ withTextAd && <TextAd order={{ base: -1, lg: 100 }} mb={{ base: 6, lg: 0 }} ml="auto" w={{ base: '100%', lg: 'auto' }}/> }
</Flex>
......
......@@ -3,7 +3,7 @@ import React from 'react';
import type { TokenInfo } from 'types/api/token';
import iconSuccess from 'icons/status/success.svg';
import iconVerifiedToken from 'icons/verified_token.svg';
import useIsMobile from 'lib/hooks/useIsMobile';
import EntityTags from 'ui/shared/EntityTags';
import NetworkExplorers from 'ui/shared/NetworkExplorers';
......@@ -33,22 +33,24 @@ const DefaultView = () => {
};
const contentAfter = (
<EntityTags
tagsBefore={ [
{ label: 'example', display_name: 'Example label' },
] }
contentAfter={ <NetworkExplorers type="token" pathParam="token-hash" ml="auto" hideText={ isMobile }/> }
flexGrow={ 1 }
/>
<>
<Icon as={ iconVerifiedToken } color="green.500" boxSize={ 6 } cursor="pointer"/>
<EntityTags
tagsBefore={ [
{ label: 'example', display_name: 'Example label' },
] }
contentAfter={ <NetworkExplorers type="token" pathParam="token-hash" ml="auto" hideText={ isMobile }/> }
flexGrow={ 1 }
/>
</>
);
return (
<PageTitle
title="Shavukha Token (SHVKH) token"
beforeTitle={ (
<TokenLogo data={ tokenData } boxSize={ 6 } display="inline-block" mr={ 2 }/>
<TokenLogo data={ tokenData } boxSize={ 6 } mr={ 2 }/>
) }
afterTitle={ <Icon as={ iconSuccess } color="green.500" boxSize={ 4 } verticalAlign="top"/> }
backLink={ backLink }
contentAfter={ contentAfter }
/>
......
......@@ -4,9 +4,8 @@ import React from 'react';
import type { TokenInfo } from 'types/api/token';
import iconSuccess from 'icons/status/success.svg';
import iconVerifiedToken from 'icons/verified_token.svg';
import useIsMobile from 'lib/hooks/useIsMobile';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import { publicTag, privateTag, watchlistName } from 'mocks/address/tag';
import EntityTags from 'ui/shared/EntityTags';
import NetworkExplorers from 'ui/shared/NetworkExplorers';
......@@ -31,33 +30,35 @@ const LongNameAndManyTags = () => {
};
const contentAfter = (
<EntityTags
data={{
private_tags: [ privateTag ],
public_tags: [ publicTag ],
watchlist_names: [ watchlistName ],
}}
tagsBefore={ [
{ label: 'example', display_name: 'Example with long name' },
] }
tagsAfter={ [
{ label: 'after_1', display_name: 'Another tag' },
{ label: 'after_2', display_name: 'And yet more' },
] }
contentAfter={ <NetworkExplorers type="token" pathParam="token-hash" ml="auto" hideText={ isMobile }/> }
flexGrow={ 1 }
/>
<>
<Icon as={ iconVerifiedToken } color="green.500" boxSize={ 6 } cursor="pointer"/>
<EntityTags
data={{
private_tags: [ privateTag ],
public_tags: [ publicTag ],
watchlist_names: [ watchlistName ],
}}
tagsBefore={ [
{ label: 'example', display_name: 'Example with long name' },
] }
tagsAfter={ [
{ label: 'after_1', display_name: 'Another tag' },
{ label: 'after_2', display_name: 'And yet more' },
] }
contentAfter={ <NetworkExplorers type="token" pathParam="token-hash" ml="auto" hideText={ isMobile }/> }
flexGrow={ 1 }
/>
</>
);
const tokenSymbolText = ` (${ trimTokenSymbol(tokenData.symbol) })`;
const tokenSymbolText = ` (${ tokenData.symbol })`;
return (
<PageTitle
title={ `${ tokenData?.name }${ tokenSymbolText } token` }
beforeTitle={ (
<TokenLogo data={ tokenData } boxSize={ 6 } display="inline-block" mr={ 2 }/>
<TokenLogo data={ tokenData } boxSize={ 6 } mr={ 2 }/>
) }
afterTitle={ <Icon as={ iconSuccess } color="green.500" boxSize={ 4 } verticalAlign="top"/> }
contentAfter={ contentAfter }
/>
);
......
import { Flex, chakra, Skeleton } from '@chakra-ui/react';
import type { StyleProps } from '@chakra-ui/react';
import { Flex, chakra, Skeleton, Box, shouldForwardProp } from '@chakra-ui/react';
import React from 'react';
import type { TokenInfo } from 'types/api/token';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import AddressLink from 'ui/shared/address/AddressLink';
import TokenLogo from 'ui/shared/TokenLogo';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
interface Props {
data?: Pick<TokenInfo, 'address' | 'icon_url' | 'name' | 'symbol'>;
......@@ -14,20 +15,53 @@ interface Props {
isDisabled?: boolean;
isLoading?: boolean;
hideSymbol?: boolean;
hideIcon?: boolean;
maxW: StyleProps['maxW'];
}
const TokenSnippet = ({ data, className, logoSize = 6, isDisabled, hideSymbol, isLoading }: Props) => {
const TokenSnippet = ({ data, className, logoSize = 6, isDisabled, hideSymbol, hideIcon, isLoading, maxW }: Props) => {
const withSymbol = data && data.symbol && !hideSymbol;
const columnGap = 2;
return (
<Flex className={ className } alignItems="center" columnGap={ 2 } w="100%">
<TokenLogo boxSize={ logoSize } data={ data } isLoading={ isLoading }/>
<AddressLink hash={ data?.address || '' } alias={ data?.name || 'Unnamed token' } type="token" isDisabled={ isDisabled } isLoading={ isLoading }/>
{ data?.symbol && !hideSymbol && (
<Skeleton isLoaded={ !isLoading } color="text_secondary">
<span>({ trimTokenSymbol(data.symbol) })</span>
<Flex className={ className } alignItems="center" columnGap={ columnGap } w="100%" overflow="hidden">
{ !hideIcon && <TokenLogo boxSize={ logoSize } data={ data } isLoading={ isLoading }/> }
<AddressLink
flexShrink={ 0 }
hash={ data?.address || '' }
alias={ data?.name || 'Unnamed token' }
type="token"
isDisabled={ isDisabled }
isLoading={ isLoading }
maxW={ withSymbol ?
`calc(80% - ${ (logoSize + columnGap * 2) * 4 }px)` :
`calc(${ maxW || '100%' } - ${ (logoSize + columnGap) * 4 }px)`
}
overflow="hidden"
textOverflow="ellipsis"
/>
{ withSymbol && (
<Skeleton isLoaded={ !isLoading } color="text_secondary" maxW="20%" display="flex">
<div>(</div>
<TruncatedTextTooltip label={ data.symbol || '' }>
<Box overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap" wordBreak="break-all">{ data.symbol }</Box>
</TruncatedTextTooltip>
<div>)</div>
</Skeleton>
) }
</Flex>
);
};
export default chakra(TokenSnippet);
export default chakra(TokenSnippet, {
shouldForwardProp: (prop) => {
const isChakraProp = !shouldForwardProp(prop);
if (isChakraProp && prop !== 'maxW') {
return false;
}
return true;
},
});
......@@ -54,8 +54,8 @@ const TokenTransferListItem = ({
return (
<ListItemMobile rowGap={ 3 } isAnimated>
<Flex w="100%" justifyContent="space-between">
<Flex flexWrap="wrap" rowGap={ 1 } mr={ showTxInfo && txHash ? 2 : 0 } columnGap={ 2 }>
<TokenSnippet data={ token } w="auto" maxW="calc(100% - 140px)" hideSymbol isLoading={ isLoading }/>
<Flex flexWrap="wrap" rowGap={ 1 } mr={ showTxInfo && txHash ? 2 : 0 } columnGap={ 2 } overflow="hidden">
<TokenSnippet data={ token } w="auto" hideSymbol isLoading={ isLoading }/>
<Tag flexShrink={ 0 } isLoading={ isLoading }>{ token.type }</Tag>
<Tag colorScheme="orange" isLoading={ isLoading }>{ getTokenTransferTypeText(type) }</Tag>
</Flex>
......
......@@ -60,7 +60,7 @@ const TruncatedTextTooltip = ({ children, label }: Props) => {
);
if (isTruncated) {
return <Tooltip label={ label }>{ modifiedChildren }</Tooltip>;
return <Tooltip label={ label } maxW={{ base: '100vw', lg: '400px' }}>{ modifiedChildren }</Tooltip>;
}
return modifiedChildren;
......
......@@ -61,7 +61,7 @@ const AddressLink = (props: Props) => {
const content = (() => {
if (alias) {
const text = <Box overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap">{ alias }</Box>;
if (type === 'token') {
if (type === 'token' || type === 'address_token') {
return (
<TruncatedTextTooltip label={ alias }>
{ text }
......
import { Box, Flex, Grid, Link, Skeleton } from '@chakra-ui/react';
import { Box, Grid, Link, Skeleton } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import BigNumber from 'bignumber.js';
import { useRouter } from 'next/router';
......@@ -13,8 +13,7 @@ import { TOKEN_COUNTERS } from 'stubs/token';
import type { TokenTabs } from 'ui/pages/Token';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
interface Props {
tokenQuery: UseQueryResult<TokenInfo>;
}
......@@ -120,13 +119,16 @@ const TokenDetails = ({ tokenQuery }: Props) => {
whiteSpace="pre-wrap"
isLoading={ tokenQuery.isPlaceholderData }
>
<Skeleton isLoaded={ !tokenQuery.isPlaceholderData }>
<Flex w="100%">
<Box whiteSpace="nowrap" overflow="hidden">
<HashStringShortenDynamic hash={ totalSupplyValue || '0' }/>
<Skeleton isLoaded={ !tokenQuery.isPlaceholderData } w="100%" display="flex">
<TruncatedTextTooltip label={ totalSupplyValue || '0' }>
<Box overflow="hidden" textOverflow="ellipsis" flexShrink={ 0 } maxW="80%" whiteSpace="nowrap">
{ totalSupplyValue || '0' }
</Box>
<Box flexShrink={ 0 }> { symbol || '' }</Box>
</Flex>
</TruncatedTextTooltip>
<Box flexShrink={ 0 }> </Box>
<TruncatedTextTooltip label={ symbol || '' }>
<Box overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap">{ symbol || '' }</Box>
</TruncatedTextTooltip>
</Skeleton>
</DetailsInfoItem>
<DetailsInfoItem
......
......@@ -7,7 +7,6 @@ import type { TokenTransfer } from 'types/api/tokenTransfer';
import eastArrowIcon from 'icons/arrows/east.svg';
import transactionIcon from 'icons/transactions.svg';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
......@@ -16,6 +15,7 @@ import Tag from 'ui/shared/chakra/Tag';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
import TokenTransferNft from 'ui/shared/TokenTransfer/TokenTransferNft';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
type Props = TokenTransfer & { tokenId?: string; isLoading?: boolean };
......@@ -92,7 +92,13 @@ const TokenTransferListItem = ({
<Skeleton isLoaded={ !isLoading } color="text_secondary">
<span>{ value }</span>
</Skeleton>
<Skeleton isLoaded={ !isLoading }>{ trimTokenSymbol(token.symbol) }</Skeleton>
{ token.symbol && (
<TruncatedTextTooltip label={ token.symbol }>
<Skeleton isLoaded={ !isLoading } overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap">
{ token.symbol }
</Skeleton>
</TruncatedTextTooltip>
) }
</Flex>
) }
{ 'token_id' in total && (token.type === 'ERC-721' || token.type === 'ERC-1155') && (
......
import { Table, Tbody, Tr, Th } from '@chakra-ui/react';
import { Table, Tbody, Tr, Th, Box } from '@chakra-ui/react';
import React from 'react';
import type { TokenInfo } from 'types/api/token';
import type { TokenTransfer } from 'types/api/tokenTransfer';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import { default as Thead } from 'ui/shared/TheadSticky';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
import TokenTransferTableItem from 'ui/token/TokenTransfer/TokenTransferTableItem';
interface Props {
......@@ -33,8 +33,13 @@ const TokenTransferTable = ({ data, top, showSocketInfo, socketInfoAlert, socket
<Th width="36px" px={ 0 }/>
<Th width="218px" >To</Th>
{ (tokenType === 'ERC-721' || tokenType === 'ERC-1155') && <Th width="20%" isNumeric={ tokenType === 'ERC-721' }>Token ID</Th> }
{ (tokenType === 'ERC-20' || tokenType === 'ERC-1155') &&
<Th width="20%" isNumeric whiteSpace="nowrap">Value { trimTokenSymbol(token?.symbol || '') }</Th> }
{ (tokenType === 'ERC-20' || tokenType === 'ERC-1155') && (
<Th width="20%" isNumeric whiteSpace="nowrap">
<TruncatedTextTooltip label={ `Value ${ token?.symbol }` }>
<Box overflow="hidden" textOverflow="ellipsis">Value { token?.symbol }</Box>
</TruncatedTextTooltip>
</Th>
) }
</Tr>
</Thead>
<Tbody>
......
......@@ -15,13 +15,13 @@ type Props = {
const TokensTable = ({ items, page, isLoading }: Props) => {
return (
<Table style={{ tableLayout: 'auto' }}>
<Table>
<Thead top={ 80 }>
<Tr>
<Th>Token</Th>
<Th isNumeric>Price</Th>
<Th isNumeric>On-chain market cap</Th>
<Th isNumeric>Holders</Th>
<Th w="50%">Token</Th>
<Th isNumeric w="15%">Price</Th>
<Th isNumeric w="20%">On-chain market cap</Th>
<Th isNumeric w="15%">Holders</Th>
</Tr>
</Thead>
<Tbody>
......
......@@ -53,7 +53,7 @@ const TokensTableItem = ({
>
{ (page - 1) * PAGE_SIZE + index + 1 }
</Skeleton>
<Box>
<Box overflow="hidden">
<Flex alignItems="center">
<TokenLogo data={ token } boxSize={ 6 } mr={ 2 } isLoading={ isLoading }/>
<AddressLink fontSize="sm" fontWeight="700" hash={ address } type="token" alias={ tokenString } isLoading={ isLoading }/>
......
......@@ -20,7 +20,7 @@ const NftTokenTransferSnippet = ({ value, token, tokenId }: Props) => {
const url = route({ pathname: '/token/[hash]/instance/[id]', query: { hash: token.address, id: tokenId } });
return (
<Flex alignItems="center" columnGap={ 3 } rowGap={ 2 } flexWrap="wrap">
<Flex alignItems="center" columnGap={ 3 } rowGap={ 2 } flexWrap={{ base: 'wrap', lg: 'nowrap' }}>
<Text fontWeight={ 500 } as="span">For { num } token ID:</Text>
<Box display="inline-flex" alignItems="center">
<Icon as={ nftIcon } boxSize={ 6 } mr={ 1 }/>
......@@ -29,7 +29,7 @@ const NftTokenTransferSnippet = ({ value, token, tokenId }: Props) => {
</Link>
</Box>
{ token.name ? (
<TokenSnippet data={ token } w="auto" logoSize={ 5 } columnGap={ 1 }/>
<TokenSnippet data={ token } logoSize={ 5 } columnGap={ 1 }/>
) : (
<AddressLink hash={ token.address } truncation="constant" type="token"/>
) }
......
......@@ -42,6 +42,7 @@ import LinkInternal from 'ui/shared/LinkInternal';
import LogDecodedInputData from 'ui/shared/logs/LogDecodedInputData';
import RawInputData from 'ui/shared/RawInputData';
import TextSeparator from 'ui/shared/TextSeparator';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
import TxStatus from 'ui/shared/TxStatus';
import Utilization from 'ui/shared/Utilization/Utilization';
import TxDetailsActions from 'ui/tx/details/TxDetailsActions';
......@@ -240,7 +241,7 @@ const TxDetails = () => {
{ toAddress ? (
<>
{ data.to && data.to.hash ? (
<Address alignItems="center">
<Address alignItems="center" flexShrink={ 0 } w={{ base: '100%', lg: 'auto' }}>
<AddressIcon address={ toAddress } isLoading={ isPlaceholderData }/>
<AddressLink type="address" ml={ 2 } hash={ toAddress.hash } isLoading={ isPlaceholderData }/>
{ executionSuccessBadge }
......@@ -248,7 +249,7 @@ const TxDetails = () => {
<CopyToClipboard text={ toAddress.hash } isLoading={ isPlaceholderData }/>
</Address>
) : (
<Flex width={{ base: '100%', lg: 'auto' }} whiteSpace="pre" alignItems="center">
<Flex width={{ base: '100%', lg: 'auto' }} whiteSpace="pre" alignItems="center" flexShrink={ 0 }>
<span>[Contract </span>
<AddressLink type="address" hash={ toAddress.hash }/>
<span> created]</span>
......@@ -257,7 +258,11 @@ const TxDetails = () => {
<CopyToClipboard text={ toAddress.hash }/>
</Flex>
) }
{ toAddress.name && <Text>{ toAddress.name }</Text> }
{ toAddress.name && (
<TruncatedTextTooltip label={ toAddress.name }>
<chakra.span overflow="hidden" textOverflow="ellipsis">{ toAddress.name }</chakra.span>
</TruncatedTextTooltip>
) }
{ addressToTags.length > 0 && (
<Flex columnGap={ 3 }>
{ addressToTags }
......
......@@ -64,6 +64,9 @@ const TxDetailsAction = ({ action }: Props) => {
columnGap={ 1 }
logoSize={ 5 }
isDisabled={ data.symbol0 === 'Ether' }
hideSymbol
maxW="200px"
flexShrink={ 0 }
/>
<chakra.span color="text_secondary">{ type === 'swap' ? 'For' : 'And' }: </chakra.span>
......@@ -75,6 +78,9 @@ const TxDetailsAction = ({ action }: Props) => {
columnGap={ 1 }
logoSize={ 5 }
isDisabled={ data.symbol1 === 'Ether' }
hideSymbol
maxW="200px"
flexShrink={ 0 }
/>
<chakra.span color="text_secondary">{ text1 } </chakra.span>
......@@ -105,7 +111,6 @@ const TxDetailsAction = ({ action }: Props) => {
columnGap={ 1 }
logoSize={ 5 }
rowGap={ 2 }
flexWrap="wrap"
/>
<chakra.span> to </chakra.span>
<AddressLink hash={ data.to } type="address" truncation="constant"/>
......
......@@ -34,7 +34,7 @@ const TxDetailsActions = ({ actions }: Props) => {
>
<Flex
flexDirection="column"
alignItems="flex-start"
alignItems="stretch"
rowGap={ 5 }
w="100%"
maxH="200px"
......
......@@ -64,17 +64,18 @@ const TxDetailsTokenTransfer = ({ data }: Props) => {
return (
<Flex
alignItems="center"
flexWrap="wrap"
flexWrap={{ base: 'wrap', lg: 'nowrap' }}
columnGap={ 3 }
rowGap={ 3 }
flexDir="row"
w="100%"
>
<Flex alignItems="center">
<AddressLink type="address" fontWeight="500" hash={ data.from.hash } truncation="constant"/>
<Icon as={ rightArrowIcon } boxSize={ 6 } mx={ 2 } color="gray.500"/>
<AddressLink type="address" fontWeight="500" hash={ data.to.hash } truncation="constant"/>
</Flex>
<Flex flexDir="column" rowGap={ 5 }>
<Flex flexDir="column" rowGap={ 5 } w="100%" overflow="hidden">
{ content }
</Flex>
</Flex>
......
......@@ -51,6 +51,7 @@ const TxDetailsTokenTransfers = ({ data, txHash }: Props) => {
alignItems="flex-start"
rowGap={ 5 }
w="100%"
overflow="hidden"
>
{ items.slice(0, VISIBLE_ITEMS_NUM).map((item, index) => <TxDetailsTokenTransfer key={ index } data={ item }/>) }
</Flex>
......
......@@ -19,7 +19,7 @@ interface Props {
const TxStateTable = ({ data, isLoading, top }: Props) => {
return (
<Table variant="simple" minWidth="1000px" size="sm" w="auto">
<Table variant="simple" minWidth="1000px" size="sm" w="100%">
<Thead top={ top }>
<Tr>
<Th width="140px">Type</Th>
......
......@@ -8,7 +8,6 @@ import appConfig from 'configs/app/config';
import { ZERO_ADDRESS } from 'lib/consts';
import { nbsp, space } from 'lib/html-entities';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import AddressLink from 'ui/shared/address/AddressLink';
import Tag from 'ui/shared/chakra/Tag';
import TokenTransferNft from 'ui/shared/TokenTransfer/TokenTransferNft';
......@@ -58,8 +57,16 @@ export function getStateElements(data: TxStateChange, isLoading?: boolean) {
const changeSign = beforeBn.lte(afterBn) ? '+' : '-';
return {
before: <Skeleton isLoaded={ !isLoading } display="inline-block">{ beforeBn.toFormat() } { appConfig.network.currency.symbol }</Skeleton>,
after: <Skeleton isLoaded={ !isLoading } display="inline-block">{ afterBn.toFormat() } { appConfig.network.currency.symbol }</Skeleton>,
before: (
<Skeleton isLoaded={ !isLoading } wordBreak="break-all" display="inline-block">
{ beforeBn.toFormat() } { appConfig.network.currency.symbol }
</Skeleton>
),
after: (
<Skeleton isLoaded={ !isLoading } wordBreak="break-all" display="inline-block">
{ afterBn.toFormat() } { appConfig.network.currency.symbol }
</Skeleton>
),
change: (
<Skeleton isLoaded={ !isLoading } display="inline-block" color={ changeColor }>
<span>{ changeSign }{ nbsp }{ differenceBn.abs().toFormat() }</span>
......@@ -73,7 +80,7 @@ export function getStateElements(data: TxStateChange, isLoading?: boolean) {
<AddressLink
type="token"
hash={ data.token.address }
alias={ trimTokenSymbol(data.token?.symbol || data.token.address) }
alias={ data.token?.symbol || data.token.address }
isLoading={ isLoading }
/>
);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment