Commit 16408afe authored by tom's avatar tom

address tokens tab

parent e4a5483f
...@@ -82,11 +82,12 @@ export interface ButtonGroupRadioProps extends Omit<ChakraButtonGroupProps, 'chi ...@@ -82,11 +82,12 @@ export interface ButtonGroupRadioProps extends Omit<ChakraButtonGroupProps, 'chi
onChange?: (value: string) => void; onChange?: (value: string) => void;
defaultValue?: string; defaultValue?: string;
loading?: boolean; loading?: boolean;
equalWidth?: boolean;
} }
export const ButtonGroupRadio = React.forwardRef<HTMLDivElement, ButtonGroupRadioProps>( export const ButtonGroupRadio = React.forwardRef<HTMLDivElement, ButtonGroupRadioProps>(
function ButtonGroupRadio(props, ref) { function ButtonGroupRadio(props, ref) {
const { children, onChange, variant = 'segmented', defaultValue, loading = false, ...rest } = props; const { children, onChange, variant = 'segmented', defaultValue, loading = false, equalWidth = false, ...rest } = props;
const firstChildValue = React.useMemo(() => { const firstChildValue = React.useMemo(() => {
const firstChild = Array.isArray(children) ? children[0] : undefined; const firstChild = Array.isArray(children) ? children[0] : undefined;
...@@ -109,11 +110,17 @@ export const ButtonGroupRadio = React.forwardRef<HTMLDivElement, ButtonGroupRadi ...@@ -109,11 +110,17 @@ export const ButtonGroupRadio = React.forwardRef<HTMLDivElement, ButtonGroupRadi
}); });
}); });
const childrenLength = React.Children.count(children);
return ( return (
<Skeleton loading={ loading }> <Skeleton loading={ loading }>
<ChakraButtonGroup <ChakraButtonGroup
ref={ ref } ref={ ref }
gap={ 0 } gap={ 0 }
{ ...(equalWidth ? {
display: 'grid',
gridTemplateColumns: `repeat(${ childrenLength }, 1fr)`,
} : {}) }
{ ...rest } { ...rest }
> >
{ clonedChildren } { clonedChildren }
......
...@@ -10,19 +10,21 @@ export interface ImageProps extends ChakraImageProps { ...@@ -10,19 +10,21 @@ export interface ImageProps extends ChakraImageProps {
export const Image = React.forwardRef<HTMLImageElement, ImageProps>( export const Image = React.forwardRef<HTMLImageElement, ImageProps>(
function Image(props, ref) { function Image(props, ref) {
const { fallback, src, ...rest } = props; const { fallback, src, onLoad, onError, ...rest } = props;
const [ loading, setLoading ] = React.useState(true); const [ loading, setLoading ] = React.useState(true);
const [ error, setError ] = React.useState(false); const [ error, setError ] = React.useState(false);
const handleLoadError = React.useCallback(() => { const handleLoadError = React.useCallback((event: React.SyntheticEvent<HTMLImageElement>) => {
setError(true); setError(true);
setLoading(false); setLoading(false);
}, []); onError?.(event);
}, [ onError ]);
const handleLoadSuccess = React.useCallback(() => { const handleLoadSuccess = React.useCallback((event: React.SyntheticEvent<HTMLImageElement>) => {
setLoading(false); setLoading(false);
}, []); onLoad?.(event);
}, [ onLoad ]);
if (!src && fallback) { if (!src && fallback) {
return fallback; return fallback;
......
import { Box, HStack } from '@chakra-ui/react'; import { Box, chakra, HStack } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -15,12 +15,13 @@ import getQueryParamString from 'lib/router/getQueryParamString'; ...@@ -15,12 +15,13 @@ import getQueryParamString from 'lib/router/getQueryParamString';
import { NFT_TOKEN_TYPE_IDS } from 'lib/token/tokenTypes'; import { NFT_TOKEN_TYPE_IDS } from 'lib/token/tokenTypes';
import { ADDRESS_TOKEN_BALANCE_ERC_20, ADDRESS_NFT_1155, ADDRESS_COLLECTION } from 'stubs/address'; import { ADDRESS_TOKEN_BALANCE_ERC_20, ADDRESS_NFT_1155, ADDRESS_COLLECTION } from 'stubs/address';
import { generateListStub } from 'stubs/utils'; import { generateListStub } from 'stubs/utils';
import { Button, ButtonGroupRadio } from 'toolkit/chakra/button';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import PopoverFilter from 'ui/shared/filters/PopoverFilter'; import PopoverFilter from 'ui/shared/filters/PopoverFilter';
import TokenTypeFilter from 'ui/shared/filters/TokenTypeFilter'; import TokenTypeFilter from 'ui/shared/filters/TokenTypeFilter';
import IconSvg from 'ui/shared/IconSvg';
import Pagination from 'ui/shared/pagination/Pagination'; import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import RadioButtonGroup from 'ui/shared/radioButtonGroup/RadioButtonGroup';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
import AddressCollections from './tokens/AddressCollections'; import AddressCollections from './tokens/AddressCollections';
import AddressNFTs from './tokens/AddressNFTs'; import AddressNFTs from './tokens/AddressNFTs';
...@@ -96,9 +97,9 @@ const AddressTokens = ({ shouldRender = true, isQueryEnabled = true }: Props) => ...@@ -96,9 +97,9 @@ const AddressTokens = ({ shouldRender = true, isQueryEnabled = true }: Props) =>
filters: { type: tokenTypes }, filters: { type: tokenTypes },
}); });
const handleNFTsDisplayTypeChange = React.useCallback((val: TNftDisplayType) => { const handleNFTsDisplayTypeChange = React.useCallback((val: string) => {
cookies.set(cookies.NAMES.ADDRESS_NFT_DISPLAY_TYPE, val); cookies.set(cookies.NAMES.ADDRESS_NFT_DISPLAY_TYPE, val);
setNftDisplayType(val); setNftDisplayType(val as TNftDisplayType);
}, []); }, []);
const handleTokenTypesChange = React.useCallback((value: Array<NFTTokenType>) => { const handleTokenTypesChange = React.useCallback((value: Array<NFTTokenType>) => {
...@@ -131,15 +132,20 @@ const AddressTokens = ({ shouldRender = true, isQueryEnabled = true }: Props) => ...@@ -131,15 +132,20 @@ const AddressTokens = ({ shouldRender = true, isQueryEnabled = true }: Props) =>
]; ];
const nftDisplayTypeRadio = ( const nftDisplayTypeRadio = (
<RadioButtonGroup<TNftDisplayType> <ButtonGroupRadio
onChange={ handleNFTsDisplayTypeChange }
defaultValue={ nftDisplayType } defaultValue={ nftDisplayType }
name="type" onChange={ handleNFTsDisplayTypeChange }
options={ [ equalWidth
{ title: 'By collection', value: 'collection', icon: 'collection', onlyIcon: isMobile }, >
{ title: 'List', value: 'list', icon: 'apps', onlyIcon: isMobile }, <Button value="collection" size="sm" px={ 3 }>
] } <IconSvg name="collection" boxSize={ 5 }/>
/> <chakra.span hideBelow="lg">By collection</chakra.span>
</Button>
<Button value="list" size="sm" px={ 3 }>
<IconSvg name="apps" boxSize={ 5 }/>
<chakra.span hideBelow="lg">List</chakra.span>
</Button>
</ButtonGroupRadio>
); );
let pagination: PaginationParams | undefined; let pagination: PaginationParams | undefined;
...@@ -158,7 +164,7 @@ const AddressTokens = ({ shouldRender = true, isQueryEnabled = true }: Props) => ...@@ -158,7 +164,7 @@ const AddressTokens = ({ shouldRender = true, isQueryEnabled = true }: Props) =>
const rightSlot = ( const rightSlot = (
<> <>
<HStack spacing={ 3 }> <HStack gap={ 3 }>
{ isNftTab && (hasNftData || hasActiveFilters) && nftDisplayTypeRadio } { isNftTab && (hasNftData || hasActiveFilters) && nftDisplayTypeRadio }
{ isNftTab && (hasNftData || hasActiveFilters) && nftTypeFilter } { isNftTab && (hasNftData || hasActiveFilters) && nftTypeFilter }
</HStack> </HStack>
...@@ -173,10 +179,9 @@ const AddressTokens = ({ shouldRender = true, isQueryEnabled = true }: Props) => ...@@ -173,10 +179,9 @@ const AddressTokens = ({ shouldRender = true, isQueryEnabled = true }: Props) =>
<Box ref={ scrollRef }></Box> <Box ref={ scrollRef }></Box>
<RoutedTabs <RoutedTabs
tabs={ tabs } tabs={ tabs }
variant="outline" variant="secondary"
colorScheme="gray"
size="sm" size="sm"
tabListProps={ isMobile ? TAB_LIST_PROPS_MOBILE : TAB_LIST_PROPS } listProps={ isMobile ? TAB_LIST_PROPS_MOBILE : TAB_LIST_PROPS }
rightSlot={ rightSlot } rightSlot={ rightSlot }
rightSlotProps={ tab !== 'tokens_erc20' && !isMobile ? { flexGrow: 1, display: 'flex', justifyContent: 'space-between', ml: 8 } : {} } rightSlotProps={ tab !== 'tokens_erc20' && !isMobile ? { flexGrow: 1, display: 'flex', justifyContent: 'space-between', ml: 8 } : {} }
stickyEnabled={ !isMobile } stickyEnabled={ !isMobile }
......
...@@ -5,11 +5,11 @@ import { route } from 'nextjs-routes'; ...@@ -5,11 +5,11 @@ import { route } from 'nextjs-routes';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { apos } from 'lib/html-entities'; import { apos } from 'lib/html-entities';
import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';
import ActionBar from 'ui/shared/ActionBar'; import ActionBar from 'ui/shared/ActionBar';
import Skeleton from 'ui/shared/chakra/Skeleton';
import DataListDisplay from 'ui/shared/DataListDisplay'; import DataListDisplay from 'ui/shared/DataListDisplay';
import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import LinkInternal from 'ui/shared/links/LinkInternal';
import NftFallback from 'ui/shared/nft/NftFallback'; import NftFallback from 'ui/shared/nft/NftFallback';
import Pagination from 'ui/shared/pagination/Pagination'; import Pagination from 'ui/shared/pagination/Pagination';
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
...@@ -56,12 +56,12 @@ const AddressCollections = ({ collectionsQuery, address, hasActiveFilters }: Pro ...@@ -56,12 +56,12 @@ const AddressCollections = ({ collectionsQuery, address, hasActiveFilters }: Pro
noCopy noCopy
fontWeight="600" fontWeight="600"
/> />
<Skeleton isLoaded={ !isPlaceholderData } mr={ 3 }> <Skeleton loading={ isPlaceholderData } mr={ 3 }>
<Text variant="secondary" whiteSpace="pre">{ ` - ${ Number(item.amount).toLocaleString() } item${ Number(item.amount) > 1 ? 's' : '' }` }</Text> <Text color="text.secondary" whiteSpace="pre">{ ` - ${ Number(item.amount).toLocaleString() } item${ Number(item.amount) > 1 ? 's' : '' }` }</Text>
</Skeleton> </Skeleton>
<LinkInternal href={ collectionUrl } isLoading={ isPlaceholderData }> <Link href={ collectionUrl } loading={ isPlaceholderData }>
<Skeleton isLoaded={ !isPlaceholderData }>View in collection</Skeleton> View in collection
</LinkInternal> </Link>
</Flex> </Flex>
<Grid <Grid
w="100%" w="100%"
...@@ -83,16 +83,16 @@ const AddressCollections = ({ collectionsQuery, address, hasActiveFilters }: Pro ...@@ -83,16 +83,16 @@ const AddressCollections = ({ collectionsQuery, address, hasActiveFilters }: Pro
); );
}) } }) }
{ hasOverload && ( { hasOverload && (
<LinkInternal display="flex" href={ collectionUrl }> <Link href={ collectionUrl }>
<NFTItemContainer display="flex" alignItems="center" justifyContent="center" flexDirection="column" minH="248px"> <NFTItemContainer display="flex" alignItems="center" justifyContent="center" flexDirection="column" minH="248px">
<HStack gap={ 2 } mb={ 3 }> <HStack gap={ 2 } mb={ 3 }>
<NftFallback bgColor="unset" w="30px" h="30px" boxSize="30px" p={ 0 }/> <NftFallback bgColor={{ _light: 'unset', _dark: 'unset' }} w="30px" h="30px" boxSize="30px" p={ 0 }/>
<NftFallback bgColor="unset" w="30px" h="30px" boxSize="30px" p={ 0 }/> <NftFallback bgColor={{ _light: 'unset', _dark: 'unset' }} w="30px" h="30px" boxSize="30px" p={ 0 }/>
<NftFallback bgColor="unset" w="30px" h="30px" boxSize="30px" p={ 0 }/> <NftFallback bgColor={{ _light: 'unset', _dark: 'unset' }} w="30px" h="30px" boxSize="30px" p={ 0 }/>
</HStack> </HStack>
View all NFTs View all NFTs
</NFTItemContainer> </NFTItemContainer>
</LinkInternal> </Link>
) } ) }
</Grid> </Grid>
</Box> </Box>
...@@ -102,15 +102,16 @@ const AddressCollections = ({ collectionsQuery, address, hasActiveFilters }: Pro ...@@ -102,15 +102,16 @@ const AddressCollections = ({ collectionsQuery, address, hasActiveFilters }: Pro
return ( return (
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
items={ data?.items } itemsNum={ data?.items?.length }
emptyText="There are no tokens of selected type." emptyText="There are no tokens of selected type."
content={ content }
actionBar={ actionBar } actionBar={ actionBar }
filterProps={{ filterProps={{
emptyFilteredText: `Couldn${ apos }t find any token that matches your query.`, emptyFilteredText: `Couldn${ apos }t find any token that matches your query.`,
hasActiveFilters, hasActiveFilters,
}} }}
/> >
{ content }
</DataListDisplay>
); );
}; };
......
...@@ -51,15 +51,16 @@ const AddressNFTs = ({ tokensQuery, hasActiveFilters }: Props) => { ...@@ -51,15 +51,16 @@ const AddressNFTs = ({ tokensQuery, hasActiveFilters }: Props) => {
return ( return (
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
items={ data?.items } itemsNum={ data?.items?.length }
emptyText="There are no tokens of selected type." emptyText="There are no tokens of selected type."
content={ content }
actionBar={ actionBar } actionBar={ actionBar }
filterProps={{ filterProps={{
emptyFilteredText: `Couldn${ apos }t find any token that matches your query.`, emptyFilteredText: `Couldn${ apos }t find any token that matches your query.`,
hasActiveFilters, hasActiveFilters,
}} }}
/> >
{ content }
</DataListDisplay>
); );
}; };
......
import { Show, Hide } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
...@@ -27,24 +27,27 @@ const ERC20Tokens = ({ tokensQuery }: Props) => { ...@@ -27,24 +27,27 @@ const ERC20Tokens = ({ tokensQuery }: Props) => {
const content = data?.items ? ( const content = data?.items ? (
<> <>
<Hide below="lg" ssr={ false }><ERC20TokensTable data={ data.items } top={ pagination.isVisible ? 72 : 0 } isLoading={ isPlaceholderData }/></Hide> <Box hideBelow="lg"><ERC20TokensTable data={ data.items } top={ pagination.isVisible ? 72 : 0 } isLoading={ isPlaceholderData }/></Box>
<Show below="lg" ssr={ false }>{ data.items.map((item, index) => ( <Box hideFrom="lg">{ data.items.map((item, index) => (
<ERC20TokensListItem <ERC20TokensListItem
key={ item.token.address + (isPlaceholderData ? index : '') } key={ item.token.address + (isPlaceholderData ? index : '') }
{ ...item } { ...item }
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
/> />
)) }</Show></> )) }
</Box>
</>
) : null; ) : null;
return ( return (
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
items={ data?.items } itemsNum={ data?.items.length }
emptyText="There are no tokens of selected type." emptyText="There are no tokens of selected type."
content={ content }
actionBar={ actionBar } actionBar={ actionBar }
/> >
{ content }
</DataListDisplay>
); );
}; };
......
...@@ -4,8 +4,8 @@ import React from 'react'; ...@@ -4,8 +4,8 @@ import React from 'react';
import type { AddressTokenBalance } from 'types/api/address'; import type { AddressTokenBalance } from 'types/api/address';
import getCurrencyValue from 'lib/getCurrencyValue'; import getCurrencyValue from 'lib/getCurrencyValue';
import { Skeleton } from 'toolkit/chakra/skeleton';
import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet'; import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet';
import Skeleton from 'ui/shared/chakra/Skeleton';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
...@@ -40,23 +40,23 @@ const ERC20TokensListItem = ({ token, value, isLoading }: Props) => { ...@@ -40,23 +40,23 @@ const ERC20TokensListItem = ({ token, value, isLoading }: Props) => {
<AddressAddToWallet token={ token } ml={ 2 } isLoading={ isLoading }/> <AddressAddToWallet token={ token } ml={ 2 } isLoading={ isLoading }/>
</Flex> </Flex>
{ token.exchange_rate !== undefined && token.exchange_rate !== null && ( { token.exchange_rate !== undefined && token.exchange_rate !== null && (
<HStack spacing={ 3 }> <HStack gap={ 3 }>
<Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>Price</Skeleton> <Skeleton loading={ isLoading } fontSize="sm" fontWeight={ 500 }>Price</Skeleton>
<Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary"> <Skeleton loading={ isLoading } fontSize="sm" color="text_secondary">
<span>{ `$${ Number(token.exchange_rate).toLocaleString() }` }</span> <span>{ `$${ Number(token.exchange_rate).toLocaleString() }` }</span>
</Skeleton> </Skeleton>
</HStack> </HStack>
) } ) }
<HStack spacing={ 3 } alignItems="baseline"> <HStack gap={ 3 } alignItems="baseline">
<Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>Quantity</Skeleton> <Skeleton loading={ isLoading } fontSize="sm" fontWeight={ 500 }>Quantity</Skeleton>
<Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary" whiteSpace="pre-wrap" wordBreak="break-word"> <Skeleton loading={ isLoading } fontSize="sm" color="text_secondary" whiteSpace="pre-wrap" wordBreak="break-word">
<span>{ tokenQuantity }</span> <span>{ tokenQuantity }</span>
</Skeleton> </Skeleton>
</HStack> </HStack>
{ tokenValue !== undefined && ( { tokenValue !== undefined && (
<HStack spacing={ 3 } alignItems="baseline"> <HStack gap={ 3 } alignItems="baseline">
<Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>Value</Skeleton> <Skeleton loading={ isLoading } fontSize="sm" fontWeight={ 500 }>Value</Skeleton>
<Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary" whiteSpace="pre-wrap" wordBreak="break-word"> <Skeleton loading={ isLoading } fontSize="sm" color="text_secondary" whiteSpace="pre-wrap" wordBreak="break-word">
<span>${ tokenValue }</span> <span>${ tokenValue }</span>
</Skeleton> </Skeleton>
</HStack> </HStack>
......
import { Table, Tbody, Tr, Th } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { AddressTokenBalance } from 'types/api/address'; import type { AddressTokenBalance } from 'types/api/address';
import { default as Thead } from 'ui/shared/TheadSticky'; import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import ERC20TokensTableItem from './ERC20TokensTableItem'; import ERC20TokensTableItem from './ERC20TokensTableItem';
...@@ -15,22 +14,22 @@ interface Props { ...@@ -15,22 +14,22 @@ interface Props {
const ERC20TokensTable = ({ data, top, isLoading }: Props) => { const ERC20TokensTable = ({ data, top, isLoading }: Props) => {
return ( return (
<Table> <TableRoot>
<Thead top={ top }> <TableHeaderSticky top={ top }>
<Tr> <TableRow>
<Th width="30%">Asset</Th> <TableColumnHeader width="30%">Asset</TableColumnHeader>
<Th width="30%">Contract address</Th> <TableColumnHeader width="30%">Contract address</TableColumnHeader>
<Th width="10%" isNumeric>Price</Th> <TableColumnHeader width="10%" isNumeric>Price</TableColumnHeader>
<Th width="15%" isNumeric>Quantity</Th> <TableColumnHeader width="15%" isNumeric>Quantity</TableColumnHeader>
<Th width="15%" isNumeric>Value</Th> <TableColumnHeader width="15%" isNumeric>Value</TableColumnHeader>
</Tr> </TableRow>
</Thead> </TableHeaderSticky>
<Tbody> <TableBody>
{ data.map((item, index) => ( { data.map((item, index) => (
<ERC20TokensTableItem key={ item.token.address + (isLoading ? index : '') } { ...item } isLoading={ isLoading }/> <ERC20TokensTableItem key={ item.token.address + (isLoading ? index : '') } { ...item } isLoading={ isLoading }/>
)) } )) }
</Tbody> </TableBody>
</Table> </TableRoot>
); );
}; };
......
import { Tr, Td, Flex } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { AddressTokenBalance } from 'types/api/address'; import type { AddressTokenBalance } from 'types/api/address';
import getCurrencyValue from 'lib/getCurrencyValue'; import getCurrencyValue from 'lib/getCurrencyValue';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { TableCell, TableRow } from 'toolkit/chakra/table';
import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet'; import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet';
import Skeleton from 'ui/shared/chakra/Skeleton';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity';
...@@ -23,10 +24,8 @@ const ERC20TokensTableItem = ({ ...@@ -23,10 +24,8 @@ const ERC20TokensTableItem = ({
} = getCurrencyValue({ value: value, exchangeRate: token.exchange_rate, decimals: token.decimals, accuracy: 8, accuracyUsd: 2 }); } = getCurrencyValue({ value: value, exchangeRate: token.exchange_rate, decimals: token.decimals, accuracy: 8, accuracyUsd: 2 });
return ( return (
<Tr <TableRow role="group" >
role="group" <TableCell verticalAlign="middle">
>
<Td verticalAlign="middle">
<TokenEntity <TokenEntity
token={ token } token={ token }
isLoading={ isLoading } isLoading={ isLoading }
...@@ -34,8 +33,8 @@ const ERC20TokensTableItem = ({ ...@@ -34,8 +33,8 @@ const ERC20TokensTableItem = ({
jointSymbol jointSymbol
fontWeight="700" fontWeight="700"
/> />
</Td> </TableCell>
<Td verticalAlign="middle"> <TableCell verticalAlign="middle">
<Flex alignItems="center" width="150px" justifyContent="space-between"> <Flex alignItems="center" width="150px" justifyContent="space-between">
<AddressEntity <AddressEntity
address={{ hash: token.address }} address={{ hash: token.address }}
...@@ -45,23 +44,23 @@ const ERC20TokensTableItem = ({ ...@@ -45,23 +44,23 @@ const ERC20TokensTableItem = ({
/> />
<AddressAddToWallet token={ token } ml={ 4 } isLoading={ isLoading } opacity="0" _groupHover={{ opacity: 1 }}/> <AddressAddToWallet token={ token } ml={ 4 } isLoading={ isLoading } opacity="0" _groupHover={{ opacity: 1 }}/>
</Flex> </Flex>
</Td> </TableCell>
<Td isNumeric verticalAlign="middle"> <TableCell isNumeric verticalAlign="middle">
<Skeleton isLoaded={ !isLoading } display="inline-block"> <Skeleton loading={ isLoading } display="inline-block">
{ token.exchange_rate && `$${ Number(token.exchange_rate).toLocaleString() }` } { token.exchange_rate && `$${ Number(token.exchange_rate).toLocaleString() }` }
</Skeleton> </Skeleton>
</Td> </TableCell>
<Td isNumeric verticalAlign="middle"> <TableCell isNumeric verticalAlign="middle">
<Skeleton isLoaded={ !isLoading } display="inline"> <Skeleton loading={ isLoading } display="inline">
{ tokenQuantity } { tokenQuantity }
</Skeleton> </Skeleton>
</Td> </TableCell>
<Td isNumeric verticalAlign="middle"> <TableCell isNumeric verticalAlign="middle">
<Skeleton isLoaded={ !isLoading } display="inline"> <Skeleton loading={ isLoading } display="inline">
{ tokenValue && `$${ tokenValue }` } { tokenValue && `$${ tokenValue }` }
</Skeleton> </Skeleton>
</Td> </TableCell>
</Tr> </TableRow>
); );
}; };
......
import { Tag, Flex, Text, Link, LightMode } from '@chakra-ui/react'; import { Flex, Text } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { AddressNFT } from 'types/api/address'; import type { AddressNFT } from 'types/api/address';
...@@ -7,7 +7,9 @@ import { route } from 'nextjs-routes'; ...@@ -7,7 +7,9 @@ import { route } from 'nextjs-routes';
import getCurrencyValue from 'lib/getCurrencyValue'; import getCurrencyValue from 'lib/getCurrencyValue';
import { getTokenTypeName } from 'lib/token/tokenTypes'; import { getTokenTypeName } from 'lib/token/tokenTypes';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { Tag } from 'toolkit/chakra/tag';
import NftEntity from 'ui/shared/entities/nft/NftEntity'; import NftEntity from 'ui/shared/entities/nft/NftEntity';
import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import NftMedia from 'ui/shared/nft/NftMedia'; import NftMedia from 'ui/shared/nft/NftMedia';
...@@ -24,10 +26,10 @@ const NFTItem = ({ token, value, isLoading, withTokenLink, ...tokenInstance }: P ...@@ -24,10 +26,10 @@ const NFTItem = ({ token, value, isLoading, withTokenLink, ...tokenInstance }: P
return ( return (
<NFTItemContainer position="relative"> <NFTItemContainer position="relative">
<Skeleton isLoaded={ !isLoading }> <Skeleton loading={ isLoading } className="light">
<LightMode><Tag background="gray.50" zIndex={ 1 } position="absolute" top="18px" right="18px">{ getTokenTypeName(token.type) }</Tag></LightMode> <Tag background="gray.50" zIndex={ 1 } position="absolute" top="18px" right="18px">{ getTokenTypeName(token.type) }</Tag>
</Skeleton> </Skeleton>
<Link href={ isLoading ? undefined : tokenInstanceLink }> <Link href={ isLoading ? undefined : tokenInstanceLink } display="inline">
<NftMedia <NftMedia
mb="18px" mb="18px"
data={ tokenInstance } data={ tokenInstance }
...@@ -37,13 +39,13 @@ const NFTItem = ({ token, value, isLoading, withTokenLink, ...tokenInstance }: P ...@@ -37,13 +39,13 @@ const NFTItem = ({ token, value, isLoading, withTokenLink, ...tokenInstance }: P
</Link> </Link>
<Flex justifyContent="space-between" w="100%" flexWrap="wrap"> <Flex justifyContent="space-between" w="100%" flexWrap="wrap">
<Flex ml={ 1 } overflow="hidden"> <Flex ml={ 1 } overflow="hidden">
<Text whiteSpace="pre" variant="secondary">ID# </Text> <Text whiteSpace="pre" color="text.secondary">ID# </Text>
<NftEntity hash={ token.address } id={ tokenInstance.id } isLoading={ isLoading } noIcon/> <NftEntity hash={ token.address } id={ tokenInstance.id } isLoading={ isLoading } noIcon/>
</Flex> </Flex>
<Skeleton isLoaded={ !isLoading } overflow="hidden" ml={ 1 }> <Skeleton loading={ isLoading } overflow="hidden" ml={ 1 }>
{ valueResult && ( { valueResult && (
<Flex> <Flex>
<Text variant="secondary" whiteSpace="pre">Qty </Text> <Text color="text.secondary" whiteSpace="pre">Qty </Text>
<Text overflow="hidden" wordBreak="break-all">{ valueResult }</Text> <Text overflow="hidden" wordBreak="break-all">{ valueResult }</Text>
</Flex> </Flex>
) } ) }
......
import { Box, useColorModeValue, chakra } from '@chakra-ui/react'; import { Box, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
type Props = { type Props = {
...@@ -11,7 +11,7 @@ const NFTItemContainer = ({ children, className }: Props) => { ...@@ -11,7 +11,7 @@ const NFTItemContainer = ({ children, className }: Props) => {
<Box <Box
w={{ base: '100%', lg: '210px' }} w={{ base: '100%', lg: '210px' }}
border="1px solid" border="1px solid"
borderColor={ useColorModeValue('blackAlpha.100', 'whiteAlpha.200') } borderColor={{ _light: 'blackAlpha.100', _dark: 'whiteAlpha.200' }}
borderRadius="12px" borderRadius="12px"
p="10px" p="10px"
fontSize="sm" fontSize="sm"
......
import { Box, Flex, Text, useColorModeValue } from '@chakra-ui/react'; import { Box, Flex, Text } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Skeleton } from 'toolkit/chakra/skeleton';
type Props = { type Props = {
name: string; name: string;
value: string; value: string;
...@@ -12,16 +13,14 @@ type Props = { ...@@ -12,16 +13,14 @@ type Props = {
const TokenBalancesItem = ({ name, icon, value, valueSecondary, isLoading }: Props) => { const TokenBalancesItem = ({ name, icon, value, valueSecondary, isLoading }: Props) => {
const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
return ( return (
<Box px="12px" py="10px" bgColor={ bgColor } borderRadius="base"> <Box px="12px" py="10px" bgColor={{ _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' }} borderRadius="base">
<Text variant="secondary" fontSize="xs" fontWeight={ 500 } mb={ 1 }>{ name }</Text> <Text color="text.secondary" textStyle="xs" fontWeight={ 500 } mb={ 1 }>{ name }</Text>
<Flex alignItems="center"> <Flex alignItems="center">
{ icon } { icon }
<Skeleton isLoaded={ !isLoading } fontWeight="500" whiteSpace="pre-wrap" wordBreak="break-word" display="flex" ml={ 2 }> <Skeleton loading={ isLoading } fontWeight="500" whiteSpace="pre-wrap" wordBreak="break-word" display="flex" ml={ 2 }>
{ value } { value }
{ Boolean(valueSecondary) && <Text color="text_secondary"> ({ valueSecondary })</Text> } { Boolean(valueSecondary) && <Text color="text.secondary"> ({ valueSecondary })</Text> }
</Skeleton> </Skeleton>
</Flex> </Flex>
</Box> </Box>
......
...@@ -194,13 +194,13 @@ const AddressPageContent = () => { ...@@ -194,13 +194,13 @@ const AddressPageContent = () => {
count: addressTabsCountersQuery.data?.token_transfers_count, count: addressTabsCountersQuery.data?.token_transfers_count,
component: <AddressTokenTransfers scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>, component: <AddressTokenTransfers scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
}, },
// { {
// id: 'tokens', id: 'tokens',
// title: 'Tokens', title: 'Tokens',
// count: addressTabsCountersQuery.data?.token_balances_count, count: addressTabsCountersQuery.data?.token_balances_count,
// component: <AddressTokens shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>, component: <AddressTokens shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
// subTabs: TOKEN_TABS, subTabs: TOKEN_TABS,
// }, },
// { // {
// id: 'internal_txns', // id: 'internal_txns',
// title: 'Internal txns', // title: 'Internal txns',
......
...@@ -54,13 +54,6 @@ const TokenTypeFilter = <T extends TokenType | NFTTokenType>({ nftOnly, onChange ...@@ -54,13 +54,6 @@ const TokenTypeFilter = <T extends TokenType | NFTTokenType>({ nftOnly, onChange
</Fieldset.Content> </Fieldset.Content>
</CheckboxGroup> </CheckboxGroup>
</Fieldset.Root> </Fieldset.Root>
{ /* <CheckboxGroup size="lg" onChange={ handleChange } value={ value }>
{ (nftOnly ? NFT_TOKEN_TYPE_IDS : TOKEN_TYPE_IDS).map((id) => (
<Checkbox key={ id } value={ id }>
<Text fontSize="md">{ TOKEN_TYPES[id] }</Text>
</Checkbox>
)) }
</CheckboxGroup> */ }
</> </>
); );
}; };
......
import { useColorModeValue, chakra } from '@chakra-ui/react'; import { chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
...@@ -9,8 +9,8 @@ const NftFallback = ({ className }: { className?: string }) => { ...@@ -9,8 +9,8 @@ const NftFallback = ({ className }: { className?: string }) => {
className={ className } className={ className }
name="nft_shield" name="nft_shield"
p="50px" p="50px"
color={ useColorModeValue('blackAlpha.500', 'whiteAlpha.500') } color={{ _light: 'blackAlpha.500', _dark: 'whiteAlpha.500' }}
bgColor={ useColorModeValue('blackAlpha.50', 'whiteAlpha.50') } bgColor={{ _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' }}
/> />
); );
}; };
......
...@@ -14,6 +14,7 @@ const NftHtml = ({ src, onLoad, onError, onClick }: Props) => { ...@@ -14,6 +14,7 @@ const NftHtml = ({ src, onLoad, onError, onClick }: Props) => {
return ( return (
<LinkOverlay <LinkOverlay
onClick={ onClick } onClick={ onClick }
h="100%"
{ ...mediaStyleProps } { ...mediaStyleProps }
> >
<chakra.iframe <chakra.iframe
......
...@@ -5,13 +5,13 @@ import NftMediaFullscreenModal from './NftMediaFullscreenModal'; ...@@ -5,13 +5,13 @@ import NftMediaFullscreenModal from './NftMediaFullscreenModal';
interface Props { interface Props {
src: string; src: string;
isOpen: boolean; open: boolean;
onClose: () => void; onOpenChange: ({ open }: { open: boolean }) => void;
} }
const NftHtmlFullscreen = ({ src, isOpen, onClose }: Props) => { const NftHtmlFullscreen = ({ src, open, onOpenChange }: Props) => {
return ( return (
<NftMediaFullscreenModal isOpen={ isOpen } onClose={ onClose }> <NftMediaFullscreenModal open={ open } onOpenChange={ onOpenChange }>
<chakra.iframe <chakra.iframe
w="90vw" w="90vw"
h="90vh" h="90vh"
......
import { Image } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { Image } from 'toolkit/chakra/image';
import { mediaStyleProps } from './utils'; import { mediaStyleProps } from './utils';
interface Props { interface Props {
......
import {
Image,
} from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { Image } from 'toolkit/chakra/image';
import NftMediaFullscreenModal from './NftMediaFullscreenModal'; import NftMediaFullscreenModal from './NftMediaFullscreenModal';
interface Props { interface Props {
src: string; src: string;
isOpen: boolean; open: boolean;
onClose: () => void; onOpenChange: ({ open }: { open: boolean }) => void;
} }
const NftImageFullscreen = ({ src, isOpen, onClose }: Props) => { const NftImageFullscreen = ({ src, open, onOpenChange }: Props) => {
const imgRef = React.useRef<HTMLImageElement>(null); const imgRef = React.useRef<HTMLImageElement>(null);
const [ hasDimensions, setHasDimensions ] = React.useState<boolean>(true); const [ hasDimensions, setHasDimensions ] = React.useState<boolean>(true);
...@@ -22,7 +21,7 @@ const NftImageFullscreen = ({ src, isOpen, onClose }: Props) => { ...@@ -22,7 +21,7 @@ const NftImageFullscreen = ({ src, isOpen, onClose }: Props) => {
}, [ ]); }, [ ]);
return ( return (
<NftMediaFullscreenModal isOpen={ isOpen } onClose={ onClose }> <NftMediaFullscreenModal open={ open } onOpenChange={ onOpenChange }>
<Image <Image
src={ src } src={ src }
alt="Token instance image" alt="Token instance image"
......
import { AspectRatio, chakra, useDisclosure } from '@chakra-ui/react'; import { AspectRatio, Box, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { useInView } from 'react-intersection-observer'; import { useInView } from 'react-intersection-observer';
import type { TokenInstance } from 'types/api/token'; import type { TokenInstance } from 'types/api/token';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Skeleton } from 'toolkit/chakra/skeleton';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import NftFallback from './NftFallback'; import NftFallback from './NftFallback';
import NftHtml from './NftHtml'; import NftHtml from './NftHtml';
...@@ -48,7 +49,7 @@ const NftMedia = ({ data, className, isLoading, withFullscreen, autoplayVideo }: ...@@ -48,7 +49,7 @@ const NftMedia = ({ data, className, isLoading, withFullscreen, autoplayVideo }:
setIsLoadingError(true); setIsLoadingError(true);
}, []); }, []);
const { isOpen, onOpen, onClose } = useDisclosure(); const { open, onOpen, onOpenChange } = useDisclosure();
const content = (() => { const content = (() => {
if (isLoading) { if (isLoading) {
...@@ -94,8 +95,8 @@ const NftMedia = ({ data, className, isLoading, withFullscreen, autoplayVideo }: ...@@ -94,8 +95,8 @@ const NftMedia = ({ data, className, isLoading, withFullscreen, autoplayVideo }:
} }
const props = { const props = {
isOpen, open,
onClose, onOpenChange,
}; };
switch (mediaInfo.mediaType) { switch (mediaInfo.mediaType) {
...@@ -119,18 +120,20 @@ const NftMedia = ({ data, className, isLoading, withFullscreen, autoplayVideo }: ...@@ -119,18 +120,20 @@ const NftMedia = ({ data, className, isLoading, withFullscreen, autoplayVideo }:
ratio={ 1 / 1 } ratio={ 1 / 1 }
overflow="hidden" overflow="hidden"
borderRadius="md" borderRadius="md"
objectFit="contain"
isolation="isolate" isolation="isolate"
sx={{
'&>img, &>video': {
objectFit: 'contain',
},
}}
> >
<> <>
{ content } <Box
css={{
'& > img, & > video': {
objectFit: 'contain',
},
}}
>
{ content }
</Box>
{ modal } { modal }
{ isMediaLoading && <Skeleton position="absolute" left={ 0 } top={ 0 } w="100%" h="100%" zIndex="1"/> } { isMediaLoading && <Skeleton loading position="absolute" left={ 0 } top={ 0 } w="100%" h="100%" zIndex="1"/> }
</> </>
</AspectRatio> </AspectRatio>
); );
......
import {
Modal,
ModalContent,
ModalCloseButton,
ModalOverlay,
} from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { DialogContent, DialogRoot } from 'toolkit/chakra/dialog';
interface Props { interface Props {
isOpen: boolean; open: boolean;
onClose: () => void; onOpenChange: ({ open }: { open: boolean }) => void;
children: React.ReactNode; children: React.ReactNode;
} }
const NftMediaFullscreenModal = ({ isOpen, onClose, children }: Props) => { const NftMediaFullscreenModal = ({ open, onOpenChange, children }: Props) => {
return ( return (
<Modal isOpen={ isOpen } onClose={ onClose } motionPreset="none"> <DialogRoot open={ open } onOpenChange={ onOpenChange } motionPreset="none">
<ModalOverlay/> <DialogContent w="unset" maxW="100vw" p={ 0 } background="none" boxShadow="none">
<ModalContent w="unset" maxW="100vw" p={ 0 } background="none" boxShadow="none">
<ModalCloseButton position="fixed" top={{ base: 2.5, lg: 8 }} right={{ base: 2.5, lg: 8 }} color="whiteAlpha.800"/>
{ children } { children }
</ModalContent> </DialogContent>
</Modal> </DialogRoot>
); );
}; };
......
...@@ -6,13 +6,13 @@ import { videoPlayProps } from './utils'; ...@@ -6,13 +6,13 @@ import { videoPlayProps } from './utils';
interface Props { interface Props {
src: string; src: string;
isOpen: boolean; open: boolean;
onClose: () => void; onOpenChange: ({ open }: { open: boolean }) => void;
} }
const NftVideoFullscreen = ({ src, isOpen, onClose }: Props) => { const NftVideoFullscreen = ({ src, open, onOpenChange }: Props) => {
return ( return (
<NftMediaFullscreenModal isOpen={ isOpen } onClose={ onClose }> <NftMediaFullscreenModal open={ open } onOpenChange={ onOpenChange }>
<chakra.video <chakra.video
{ ...videoPlayProps } { ...videoPlayProps }
src={ src } src={ src }
......
import { Box } from '@chakra-ui/react';
import React from 'react';
import { test, expect } from 'playwright/lib';
import RadioButtonGroupTest from './specs/RadioButtonGroupTest';
test('radio button group', async({ render }) => {
const component = await render(
<Box w="400px" p="10px">
<RadioButtonGroupTest/>
</Box>,
);
await expect(component).toHaveScreenshot();
});
import { chakra, Flex, useRadioGroup } from '@chakra-ui/react';
import type { ChakraProps, UseRadioProps } from '@chakra-ui/react';
import React from 'react';
import { Button, ButtonGroup } from 'toolkit/chakra/button';
import { Skeleton } from 'toolkit/chakra/skeleton';
import type { IconName } from 'ui/shared/IconSvg';
import IconSvg from 'ui/shared/IconSvg';
// TODO @tom2drum remove this component
type RadioItemProps = {
title: string;
icon?: IconName;
onlyIcon?: false;
contentAfter?: React.ReactNode;
} | {
title: string;
icon: IconName;
onlyIcon: true;
};
type RadioButtonProps = UseRadioProps & RadioItemProps;
const RadioButton = (props: RadioButtonProps) => {
// const { getInputProps, getRadioProps } = useRadio(props);
// const input = getInputProps();
// const checkbox = getRadioProps();
if (props.onlyIcon) {
return (
<Button
as="label"
aria-label={ props.title }
variant="radio_group"
selected={ props.isChecked }
>
{ /* <input { ...input }/> */ }
<Flex
// { ...checkbox }
>
<IconSvg name={ props.icon } boxSize={ 5 }/>
</Flex>
</Button>
);
}
return (
<Button
as="label"
leftIcon={ props.icon ? <IconSvg name={ props.icon } boxSize={ 5 } mr={ -1 }/> : undefined }
variant="radio_group"
selected={ props.isChecked }
>
{ /* <input { ...input }/> */ }
<Flex
alignItems="center"
// { ...checkbox }
>
{ props.title }
{ props.contentAfter }
</Flex>
</Button>
);
};
type RadioButtonGroupProps<T extends string> = {
onChange: ({ value }: { value: T }) => void;
name: string;
defaultValue: string;
options: Array<{ value: T } & RadioItemProps>;
autoWidth?: boolean;
className?: string;
isLoading?: boolean;
};
const RadioButtonGroup = <T extends string>({ onChange, name, defaultValue, options, autoWidth = false, className, isLoading }: RadioButtonGroupProps<T>) => {
const { getRootProps } = useRadioGroup({ name, defaultValue, onValueChange: onChange });
const root = getRootProps();
return (
<Skeleton loading={ isLoading }>
<ButtonGroup
{ ...root }
className={ className }
isAttached
size="sm"
display="grid"
gridTemplateColumns={ `repeat(${ options.length }, ${ autoWidth ? 'auto' : '1fr' })` }
>
{ options.map((option) => {
// const props = getRadioProps({ value: option.value });
return <RadioButton key={ option.value } { ...option }/>;
}) }
</ButtonGroup>
</Skeleton>
);
};
const WrappedRadioButtonGroup = chakra(RadioButtonGroup);
type WrappedComponent = <T extends string>(props: RadioButtonGroupProps<T> & ChakraProps) => React.JSX.Element;
export default React.memo(WrappedRadioButtonGroup) as WrappedComponent;
import React from 'react';
import RadioButtonGroup from '../RadioButtonGroup';
type Test = 'v1' | 'v2' | 'v3';
const RadioButtonGroupTest = () => {
return (
<RadioButtonGroup<Test>
// eslint-disable-next-line react/jsx-no-bind
onChange={ () => {} }
defaultValue="v1"
name="test"
options={ [
{ value: 'v1', title: 'test option 1', icon: 'clock', onlyIcon: false },
{ value: 'v2', title: 'test 2', onlyIcon: false },
{ value: 'v2', title: 'test 2', icon: 'clock', onlyIcon: true },
] }
/>
);
};
export default RadioButtonGroupTest;
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