Commit a51725d0 authored by tom's avatar tom

validators and coin balance tabs

parent 8600aeba
......@@ -353,6 +353,12 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
},
},
},
stat: {
indicator: {
up: { value: { _light: '{colors.green.500}', _dark: '{colors.green.400}' } },
down: { value: { _light: '{colors.red.600}', _dark: '{colors.red.400}' } },
},
},
heading: {
DEFAULT: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } },
},
......
......@@ -24,6 +24,7 @@ const styles = {
'&.address-entity_highlighted': {
_before: {
pr: 2,
width: `calc(100% + 6px + 8px)`,
},
},
},
......
......@@ -20,6 +20,7 @@ import { recipe as radiomark } from './radiomark.recipe';
import { recipe as select } from './select.recipe';
import { recipe as skeleton } from './skeleton.recipe';
import { recipe as spinner } from './spinner.recipe';
import { recipe as stat } from './stat.recipe';
import { recipe as switchRecipe } from './switch.recipe';
import { recipe as table } from './table.recipe';
import { recipe as tabs } from './tabs.recipe';
......@@ -55,6 +56,7 @@ export const slotRecipes = {
progressCircle,
radioGroup,
select,
stat,
'switch': switchRecipe,
table,
tabs,
......
......@@ -5,7 +5,7 @@ export const recipe = defineSlotRecipe({
base: {
content: {
outline: 0,
bg: 'popover,bg',
bg: 'popover.bg',
boxShadow: 'popover',
color: 'initial',
maxHeight: 'var(--available-height)',
......
import { defineSlotRecipe } from '@chakra-ui/react';
export const recipe = defineSlotRecipe({
slots: [ 'root', 'label', 'helpText', 'valueUnit', 'valueText', 'indicator' ],
base: {
root: {
display: 'flex',
flexDirection: 'column',
gap: '1',
position: 'relative',
flex: '1',
},
label: {
display: 'inline-flex',
gap: '1.5',
alignItems: 'center',
color: 'text',
textStyle: 'sm',
},
helpText: {
color: 'text',
textStyle: 'xs',
},
valueUnit: {
color: 'text',
textStyle: 'xs',
fontWeight: 'initial',
letterSpacing: 'initial',
},
valueText: {
verticalAlign: 'baseline',
fontWeight: 'semibold',
letterSpacing: 'normal',
fontFeatureSettings: 'initial',
fontVariantNumeric: 'initial',
display: 'inline-flex',
gap: '1',
},
indicator: {
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
marginEnd: 0,
'& :where(svg)': {
w: '1em',
h: '1em',
},
'&[data-type=up]': {
color: 'stat.indicator.up',
},
'&[data-type=down]': {
color: 'stat.indicator.down',
},
},
},
variants: {
orientation: {
horizontal: {
root: {
flexDirection: 'row',
alignItems: 'center',
},
},
},
positive: {
'true': {
valueText: {
color: 'stat.indicator.up',
},
},
'false': {
valueText: {
color: 'stat.indicator.down',
},
},
},
size: {
sm: {
valueText: {
textStyle: 'sm',
},
},
md: {
valueText: {
textStyle: 'md',
},
},
lg: {
valueText: {
textStyle: 'lg',
},
},
},
},
defaultVariants: {
size: 'md',
orientation: 'horizontal',
},
});
import { Hide, Show, Table, Tbody, Th, Tr } from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react';
......@@ -14,12 +14,12 @@ import useSocketMessage from 'lib/socket/useSocketMessage';
import { currencyUnits } from 'lib/units';
import { BLOCK } from 'stubs/block';
import { generateListStub } from 'stubs/utils';
import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import { default as Thead } from 'ui/shared/TheadSticky';
import AddressBlocksValidatedListItem from './blocksValidated/AddressBlocksValidatedListItem';
import AddressBlocksValidatedTableItem from './blocksValidated/AddressBlocksValidatedTableItem';
......@@ -104,19 +104,19 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled
const content = query.data?.items ? (
<>
<Hide below="lg" ssr={ false }>
<Table style={{ tableLayout: 'auto' }}>
<Thead top={ query.pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }>
<Tr>
<Th>Block</Th>
<Th>Age</Th>
<Th>Txn</Th>
<Th>Gas used</Th>
<Box hideBelow="lg">
<TableRoot style={{ tableLayout: 'auto' }}>
<TableHeaderSticky top={ query.pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }>
<TableRow>
<TableColumnHeader>Block</TableColumnHeader>
<TableColumnHeader>Age</TableColumnHeader>
<TableColumnHeader>Txn</TableColumnHeader>
<TableColumnHeader>Gas used</TableColumnHeader>
{ !config.UI.views.block.hiddenFields?.total_reward && !config.features.rollup.isEnabled &&
<Th isNumeric>Reward { currencyUnits.ether }</Th> }
</Tr>
</Thead>
<Tbody>
<TableColumnHeader isNumeric>Reward { currencyUnits.ether }</TableColumnHeader> }
</TableRow>
</TableHeaderSticky>
<TableBody>
<SocketNewItemsNotice.Desktop
url={ window.location.href }
num={ newItemsCount }
......@@ -132,10 +132,10 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled
isLoading={ query.isPlaceholderData }
/>
)) }
</Tbody>
</Table>
</Hide>
<Show below="lg" ssr={ false }>
</TableBody>
</TableRoot>
</Box>
<Box hideFrom="lg">
{ query.pagination.page === 1 && (
<SocketNewItemsNotice.Mobile
url={ window.location.href }
......@@ -153,7 +153,7 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled
isLoading={ query.isPlaceholderData }
/>
)) }
</Show>
</Box>
</>
) : null;
......@@ -166,11 +166,12 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled
return (
<DataListDisplay
isError={ query.isError }
items={ query.data?.items }
itemsNum={ query.data?.items.length }
emptyText="There are no validated blocks for this address."
content={ content }
actionBar={ actionBar }
/>
>
{ content }
</DataListDisplay>
);
};
......
......@@ -187,7 +187,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT, shouldRender =
return (
<>
{ !isMobile && (
<ActionBar mt={ -6 }>
<ActionBar>
{ filter }
{ currentAddress && csvExportLink }
<Pagination { ...addressTxsQuery.pagination } ml={ 8 }/>
......
......@@ -7,8 +7,8 @@ import type { Block } from 'types/api/block';
import config from 'configs/app';
import getBlockTotalReward from 'lib/block/getBlockTotalReward';
import { currencyUnits } from 'lib/units';
import { Skeleton } from 'toolkit/chakra/skeleton';
import BlockGasUsed from 'ui/shared/block/BlockGasUsed';
import Skeleton from 'ui/shared/chakra/Skeleton';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
......@@ -22,7 +22,7 @@ const AddressBlocksValidatedListItem = (props: Props) => {
const totalReward = getBlockTotalReward(props);
return (
<ListItemMobile rowGap={ 2 } isAnimated>
<ListItemMobile rowGap={ 2 }>
<Flex justifyContent="space-between" w="100%">
<BlockEntity
isLoading={ props.isLoading }
......@@ -34,32 +34,32 @@ const AddressBlocksValidatedListItem = (props: Props) => {
timestamp={ props.timestamp }
enableIncrement={ props.page === 1 }
isLoading={ props.isLoading }
color="text_secondary"
color="text.secondary"
display="inline-block"
/>
</Flex>
<Flex columnGap={ 2 } w="100%">
<Skeleton isLoaded={ !props.isLoading } fontWeight={ 500 } flexShrink={ 0 }>Txn</Skeleton>
<Skeleton isLoaded={ !props.isLoading } display="inline-block" color="Skeleton_secondary">
<Skeleton loading={ props.isLoading } fontWeight={ 500 } flexShrink={ 0 }>Txn</Skeleton>
<Skeleton loading={ props.isLoading } display="inline-block" color="Skeleton_secondary">
<span>{ props.transaction_count }</span>
</Skeleton>
</Flex>
<Flex columnGap={ 2 } w="100%">
<Skeleton isLoaded={ !props.isLoading } fontWeight={ 500 } flexShrink={ 0 }>Gas used</Skeleton>
<Skeleton isLoaded={ !props.isLoading }>
<Text color="text_secondary">{ BigNumber(props.gas_used || 0).toFormat() }</Text>
<Skeleton loading={ props.isLoading } fontWeight={ 500 } flexShrink={ 0 }>Gas used</Skeleton>
<Skeleton loading={ props.isLoading }>
<Text color="text.secondary">{ BigNumber(props.gas_used || 0).toFormat() }</Text>
</Skeleton>
<BlockGasUsed
gasUsed={ props.gas_used }
gasUsed={ props.gas_used || undefined }
gasLimit={ props.gas_limit }
isLoading={ props.isLoading }
/>
</Flex>
{ !config.UI.views.block.hiddenFields?.total_reward && !config.features.rollup.isEnabled && (
<Flex columnGap={ 2 } w="100%">
<Skeleton isLoaded={ !props.isLoading } fontWeight={ 500 } flexShrink={ 0 }>Reward { currencyUnits.ether }</Skeleton>
<Skeleton isLoaded={ !props.isLoading }>
<Text color="text_secondary">{ totalReward.toFixed() }</Text>
<Skeleton loading={ props.isLoading } fontWeight={ 500 } flexShrink={ 0 }>Reward { currencyUnits.ether }</Skeleton>
<Skeleton loading={ props.isLoading }>
<Text color="text.secondary">{ totalReward.toFixed() }</Text>
</Skeleton>
</Flex>
) }
......
import { Td, Tr, Flex } from '@chakra-ui/react';
import { Flex } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
......@@ -6,8 +6,9 @@ import type { Block } from 'types/api/block';
import config from 'configs/app';
import getBlockTotalReward from 'lib/block/getBlockTotalReward';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { TableCell, TableRow } from 'toolkit/chakra/table';
import BlockGasUsed from 'ui/shared/block/BlockGasUsed';
import Skeleton from 'ui/shared/chakra/Skeleton';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
......@@ -20,51 +21,50 @@ const AddressBlocksValidatedTableItem = (props: Props) => {
const totalReward = getBlockTotalReward(props);
return (
<Tr>
<Td>
<TableRow>
<TableCell>
<BlockEntity
isLoading={ props.isLoading }
number={ props.height }
noIcon
fontSize="sm"
lineHeight={ 5 }
textStyle="sm"
fontWeight={ 700 }
/>
</Td>
<Td>
</TableCell>
<TableCell>
<TimeAgoWithTooltip
timestamp={ props.timestamp }
enableIncrement={ props.page === 1 }
isLoading={ props.isLoading }
color="text_secondary"
color="text.secondary"
display="inline-block"
/>
</Td>
<Td>
<Skeleton isLoaded={ !props.isLoading } display="inline-block" fontWeight="500">
</TableCell>
<TableCell>
<Skeleton loading={ props.isLoading } display="inline-block" fontWeight="500">
<span>{ props.transaction_count }</span>
</Skeleton>
</Td>
<Td>
</TableCell>
<TableCell>
<Flex alignItems="center" columnGap={ 2 }>
<Skeleton isLoaded={ !props.isLoading } flexBasis="80px">
<Skeleton loading={ props.isLoading } flexBasis="80px">
{ BigNumber(props.gas_used || 0).toFormat() }
</Skeleton>
<BlockGasUsed
gasUsed={ props.gas_used }
gasUsed={ props.gas_used || undefined }
gasLimit={ props.gas_limit }
isLoading={ props.isLoading }
/>
</Flex>
</Td>
</TableCell>
{ !config.UI.views.block.hiddenFields?.total_reward && !config.features.rollup.isEnabled && (
<Td isNumeric>
<Skeleton isLoaded={ !props.isLoading } display="inline-block">
<TableCell isNumeric>
<Skeleton loading={ props.isLoading } display="inline-block">
<span>{ totalReward.toFixed() }</span>
</Skeleton>
</Td>
</TableCell>
) }
</Tr>
</TableRow>
);
};
......
......@@ -34,7 +34,7 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => {
isLoading={ isPending }
h="300px"
units={ currencyUnits.ether }
emptyText={ data?.days && `Insufficient data for the past ${ data.days } days` }
emptyText={ data?.days ? `Insufficient data for the past ${ data.days } days` : undefined }
/>
);
};
......
import { Hide, Show, Table, Tbody, Th, Tr } from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
......@@ -7,10 +7,10 @@ import type { PaginationParams } from 'ui/shared/pagination/types';
import type { ResourceError } from 'lib/api/resources';
import { currencyUnits } from 'lib/units';
import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import Pagination from 'ui/shared/pagination/Pagination';
import { default as Thead } from 'ui/shared/TheadSticky';
import AddressCoinBalanceListItem from './AddressCoinBalanceListItem';
import AddressCoinBalanceTableItem from './AddressCoinBalanceTableItem';
......@@ -25,18 +25,18 @@ const AddressCoinBalanceHistory = ({ query }: Props) => {
const content = query.data?.items ? (
<>
<Hide below="lg" ssr={ false }>
<Table>
<Thead top={ query.pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }>
<Tr>
<Th width="20%">Block</Th>
<Th width="20%">Txn</Th>
<Th width="20%">Age</Th>
<Th width="20%" isNumeric pr={ 1 }>Balance { currencyUnits.ether }</Th>
<Th width="20%" isNumeric>Delta</Th>
</Tr>
</Thead>
<Tbody>
<Box hideBelow="lg">
<TableRoot>
<TableHeaderSticky top={ query.pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }>
<TableRow>
<TableColumnHeader width="20%">Block</TableColumnHeader>
<TableColumnHeader width="20%">Txn</TableColumnHeader>
<TableColumnHeader width="20%">Age</TableColumnHeader>
<TableColumnHeader width="20%" isNumeric pr={ 1 }>Balance { currencyUnits.ether }</TableColumnHeader>
<TableColumnHeader width="20%" isNumeric>Delta</TableColumnHeader>
</TableRow>
</TableHeaderSticky>
<TableBody>
{ query.data.items.map((item, index) => (
<AddressCoinBalanceTableItem
key={ item.block_number + (query.isPlaceholderData ? String(index) : '') }
......@@ -45,10 +45,10 @@ const AddressCoinBalanceHistory = ({ query }: Props) => {
isLoading={ query.isPlaceholderData }
/>
)) }
</Tbody>
</Table>
</Hide>
<Show below="lg" ssr={ false }>
</TableBody>
</TableRoot>
</Box>
<Box hideFrom="lg">
{ query.data.items.map((item, index) => (
<AddressCoinBalanceListItem
key={ item.block_number + (query.isPlaceholderData ? String(index) : '') }
......@@ -57,7 +57,7 @@ const AddressCoinBalanceHistory = ({ query }: Props) => {
isLoading={ query.isPlaceholderData }
/>
)) }
</Show>
</Box>
</>
) : null;
......@@ -71,11 +71,12 @@ const AddressCoinBalanceHistory = ({ query }: Props) => {
<DataListDisplay
mt={ 8 }
isError={ query.isError }
items={ query.data?.items }
itemsNum={ query.data?.items.length }
emptyText="There is no coin balance history for this address."
content={ content }
actionBar={ actionBar }
/>
>
{ content }
</DataListDisplay>
);
};
......
import { Text, Stat, StatHelpText, StatArrow, Flex } from '@chakra-ui/react';
import { Stat, Flex } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
......@@ -6,7 +6,7 @@ import type { AddressCoinBalanceHistoryItem } from 'types/api/address';
import { WEI, ZERO } from 'lib/consts';
import { currencyUnits } from 'lib/units';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Skeleton } from 'toolkit/chakra/skeleton';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
......@@ -22,24 +22,22 @@ const AddressCoinBalanceListItem = (props: Props) => {
const isPositiveDelta = deltaBn.gte(ZERO);
return (
<ListItemMobile rowGap={ 2 } isAnimated>
<ListItemMobile rowGap={ 2 }>
<Flex justifyContent="space-between" w="100%">
<Skeleton isLoaded={ !props.isLoading } fontWeight={ 600 }>
<Skeleton loading={ props.isLoading } fontWeight={ 600 }>
{ BigNumber(props.value).div(WEI).dp(8).toFormat() } { currencyUnits.ether }
</Skeleton>
<Skeleton isLoaded={ !props.isLoading }>
<Stat flexGrow="0">
<StatHelpText display="flex" mb={ 0 } alignItems="center">
<StatArrow type={ isPositiveDelta ? 'increase' : 'decrease' } mr={ 2 }/>
<Text as="span" color={ isPositiveDelta ? 'green.500' : 'red.500' } fontWeight={ 600 }>
{ deltaBn.dp(8).toFormat() }
</Text>
</StatHelpText>
</Stat>
<Skeleton loading={ props.isLoading }>
<Stat.Root flexGrow="0" positive={ isPositiveDelta } size="sm">
{ isPositiveDelta ? <Stat.UpIndicator/> : <Stat.DownIndicator/> }
<Stat.ValueText fontWeight={ 600 }>
{ deltaBn.dp(8).toFormat() }
</Stat.ValueText>
</Stat.Root>
</Skeleton>
</Flex>
<Flex columnGap={ 2 } w="100%">
<Skeleton isLoaded={ !props.isLoading } fontWeight={ 500 } flexShrink={ 0 }>Block</Skeleton>
<Skeleton loading={ props.isLoading } fontWeight={ 500 } flexShrink={ 0 }>Block</Skeleton>
<BlockEntity
isLoading={ props.isLoading }
number={ props.block_number }
......@@ -49,7 +47,7 @@ const AddressCoinBalanceListItem = (props: Props) => {
</Flex>
{ props.transaction_hash && (
<Flex columnGap={ 2 } w="100%">
<Skeleton isLoaded={ !props.isLoading } fontWeight={ 500 } flexShrink={ 0 }>Txs</Skeleton>
<Skeleton loading={ props.isLoading } fontWeight={ 500 } flexShrink={ 0 }>Txs</Skeleton>
<TxEntity
hash={ props.transaction_hash }
isLoading={ props.isLoading }
......@@ -60,7 +58,7 @@ const AddressCoinBalanceListItem = (props: Props) => {
</Flex>
) }
<Flex columnGap={ 2 } w="100%">
<Skeleton isLoaded={ !props.isLoading } fontWeight={ 500 } flexShrink={ 0 }>Age</Skeleton>
<Skeleton loading={ props.isLoading } fontWeight={ 500 } flexShrink={ 0 }>Age</Skeleton>
<TimeAgoWithTooltip
timestamp={ props.block_timestamp }
enableIncrement={ props.page === 1 }
......
import { Td, Tr, Text, Stat, StatHelpText, StatArrow } from '@chakra-ui/react';
import { Stat } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { AddressCoinBalanceHistoryItem } from 'types/api/address';
import { WEI, ZERO } from 'lib/consts';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { TableCell, TableRow } from 'toolkit/chakra/table';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
......@@ -20,18 +21,16 @@ const AddressCoinBalanceTableItem = (props: Props) => {
const isPositiveDelta = deltaBn.gte(ZERO);
return (
<Tr>
<Td>
<TableRow>
<TableCell>
<BlockEntity
isLoading={ props.isLoading }
number={ props.block_number }
noIcon
fontSize="sm"
lineHeight={ 5 }
fontWeight={ 700 }
/>
</Td>
<Td>
</TableCell>
<TableCell>
{ props.transaction_hash && (
<TxEntity
hash={ props.transaction_hash }
......@@ -41,34 +40,32 @@ const AddressCoinBalanceTableItem = (props: Props) => {
maxW="150px"
/>
) }
</Td>
<Td>
</TableCell>
<TableCell>
<TimeAgoWithTooltip
timestamp={ props.block_timestamp }
enableIncrement={ props.page === 1 }
isLoading={ props.isLoading }
color="text_secondary"
color="text.secondary"
display="inline-block"
/>
</Td>
<Td isNumeric pr={ 1 }>
<Skeleton isLoaded={ !props.isLoading } color="text_secondary" display="inline-block">
</TableCell>
<TableCell isNumeric pr={ 1 }>
<Skeleton loading={ props.isLoading } color="text.secondary" display="inline-block">
<span>{ BigNumber(props.value).div(WEI).dp(8).toFormat() }</span>
</Skeleton>
</Td>
<Td isNumeric display="flex" justifyContent="end">
<Skeleton isLoaded={ !props.isLoading }>
<Stat flexGrow="0" lineHeight={ 5 }>
<StatHelpText display="flex" mb={ 0 } alignItems="center">
<StatArrow type={ isPositiveDelta ? 'increase' : 'decrease' } mr={ 2 }/>
<Text as="span" color={ isPositiveDelta ? 'green.500' : 'red.500' } fontWeight={ 600 }>
{ deltaBn.dp(8).toFormat() }
</Text>
</StatHelpText>
</Stat>
</TableCell>
<TableCell isNumeric display="flex" justifyContent="end">
<Skeleton loading={ props.isLoading }>
<Stat.Root flexGrow="0" size="sm" positive={ isPositiveDelta }>
{ isPositiveDelta ? <Stat.UpIndicator/> : <Stat.DownIndicator/> }
<Stat.ValueText fontWeight={ 600 }>
{ deltaBn.dp(8).toFormat() }
</Stat.ValueText>
</Stat.Root>
</Skeleton>
</Td>
</Tr>
</TableCell>
</TableRow>
);
};
......
......@@ -2,8 +2,8 @@ import { Box, Flex, HStack } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types';
import type { EntityTag } from 'ui/shared/EntityTags/types';
import type { RoutedTab } from 'ui/shared/Tabs/types';
import config from 'configs/app';
import getCheckedSummedAddress from 'lib/address/getCheckedSummedAddress';
......@@ -151,7 +151,7 @@ const AddressPageContent = () => {
Boolean(config.features.mudFramework.isEnabled && mudTablesCountQuery.data && mudTablesCountQuery.data > 0),
);
const tabs: Array<RoutedTab> = React.useMemo(() => {
const tabs: Array<TabItemRegular> = React.useMemo(() => {
return [
// config.features.mudFramework.isEnabled && mudTablesCountQuery.data && mudTablesCountQuery.data > 0 && {
// id: 'mud',
......@@ -213,19 +213,19 @@ const AddressPageContent = () => {
// count: addressTabsCountersQuery.data?.celo_election_rewards_count,
// component: <AddressEpochRewards scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
// } : undefined,
// {
// id: 'coin_balance_history',
// title: 'Coin balance history',
// component: <AddressCoinBalance shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
// },
// addressTabsCountersQuery.data?.validations_count ?
// {
// id: 'blocks_validated',
// title: `Blocks ${ getNetworkValidationActionText() }`,
// count: addressTabsCountersQuery.data?.validations_count,
// component: <AddressBlocksValidated scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
// } :
// undefined,
{
id: 'coin_balance_history',
title: 'Coin balance history',
component: <AddressCoinBalance shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
},
addressTabsCountersQuery.data?.validations_count ?
{
id: 'blocks_validated',
title: `Blocks ${ getNetworkValidationActionText() }`,
count: addressTabsCountersQuery.data?.validations_count,
component: <AddressBlocksValidated scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
} :
undefined,
// addressTabsCountersQuery.data?.logs_count ?
// {
// id: 'logs',
......
import { useColorModeValue, useToken } from '@chakra-ui/react';
import { useToken } from '@chakra-ui/react';
import * as d3 from 'd3';
import React from 'react';
import { useColorModeValue } from 'toolkit/chakra/color-mode';
interface Props extends Omit<React.SVGProps<SVGGElement>, 'scale'> {
type: 'left' | 'bottom';
scale: d3.ScaleTime<number, number> | d3.ScaleLinear<number, number>;
......@@ -14,8 +16,7 @@ interface Props extends Omit<React.SVGProps<SVGGElement>, 'scale'> {
const ChartAxis = ({ type, scale, ticks, tickFormatGenerator, noAnimation, anchorEl, ...props }: Props) => {
const ref = React.useRef<SVGGElement>(null);
const textColorToken = useColorModeValue('blackAlpha.600', 'whiteAlpha.500');
const textColor = useToken('colors', textColorToken);
const textColor = useToken('colors', useColorModeValue('blackAlpha.600', 'whiteAlpha.500'));
React.useEffect(() => {
if (!ref.current) {
......@@ -41,7 +42,7 @@ const ChartAxis = ({ type, scale, ticks, tickFormatGenerator, noAnimation, ancho
axisGroup.selectAll('text')
.attr('opacity', 1)
.attr('color', textColor)
.attr('font-size', '0.75rem');
.style('font-size', '12px');
}, [ scale, ticks, tickFormatGenerator, noAnimation, type, textColor ]);
React.useEffect(() => {
......
import {
IconButton,
MenuButton,
MenuItem,
MenuList,
useClipboard,
useColorModeValue,
VisuallyHidden,
} from '@chakra-ui/react';
import { useCopyToClipboard } from '@uidotdev/usehooks';
import domToImage from 'dom-to-image';
import React from 'react';
......@@ -16,8 +8,10 @@ import type { Resolution } from '@blockscout/stats-types';
import dayjs from 'lib/date/dayjs';
import isBrowser from 'lib/isBrowser';
import saveAsCSV from 'lib/saveAsCSV';
import Menu from 'ui/shared/chakra/Menu';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { useColorModeValue } from 'toolkit/chakra/color-mode';
import { IconButton } from 'toolkit/chakra/icon-button';
import { MenuContent, MenuItem, MenuRoot, MenuTrigger } from 'toolkit/chakra/menu';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import IconSvg from 'ui/shared/IconSvg';
import FullscreenChartModal from './FullscreenChartModal';
......@@ -52,19 +46,15 @@ const ChartMenu = ({
handleZoomReset,
}: Props) => {
const pngBackgroundColor = useColorModeValue('white', 'black');
const [ isFullscreen, setIsFullscreen ] = React.useState(false);
const fullscreenDialog = useDisclosure();
const { onCopy } = useClipboard(chartUrl ?? '');
const [ , copyToClipboard ] = useCopyToClipboard();
const isInBrowser = isBrowser();
const showChartFullscreen = React.useCallback(() => {
setIsFullscreen(true);
}, []);
const clearFullscreenChart = React.useCallback(() => {
setIsFullscreen(false);
}, []);
fullscreenDialog.onOpenChange({ open: true });
}, [ fullscreenDialog ]);
const handleFileSaveClick = React.useCallback(() => {
// wait for context menu to close
......@@ -111,6 +101,10 @@ const ChartMenu = ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const hasShare = isInBrowser && (window.navigator.share as any);
const handleCopy = React.useCallback(() => {
copyToClipboard(chartUrl ?? '');
}, [ chartUrl, copyToClipboard ]);
const handleShare = React.useCallback(async() => {
try {
await window.navigator.share({
......@@ -123,27 +117,17 @@ const ChartMenu = ({
return (
<>
<Menu>
<Skeleton isLoaded={ !isLoading } borderRadius="base">
<MenuButton
w="36px"
h="32px"
icon={ <IconSvg name="dots" boxSize={ 4 } transform="rotate(-90deg)"/> }
colorScheme="gray"
variant="simple"
as={ IconButton }
>
<VisuallyHidden>
Open chart options menu
</VisuallyHidden>
</MenuButton>
</Skeleton>
<MenuList>
<MenuRoot>
<MenuTrigger asChild>
<IconButton variant="dropdown" size="sm" aria-label="Open chart options menu" loading={ isLoading } borderRadius="base" borderWidth="0">
<IconSvg name="dots" boxSize={ 4 } transform="rotate(-90deg)"/>
</IconButton>
</MenuTrigger>
<MenuContent>
{ chartUrl && (
<MenuItem
display="flex"
alignItems="center"
onClick={ hasShare ? handleShare : onCopy }
value={ hasShare ? 'share' : 'copy' }
onClick={ hasShare ? handleShare : handleCopy }
closeOnSelect={ hasShare ? false : true }
>
<IconSvg name={ hasShare ? 'share' : 'copy' } boxSize={ 5 } mr={ 3 }/>
......@@ -151,38 +135,35 @@ const ChartMenu = ({
</MenuItem>
) }
<MenuItem
display="flex"
alignItems="center"
value="fullscreen"
onClick={ showChartFullscreen }
>
<IconSvg name="scope" boxSize={ 5 } mr={ 3 }/>
View fullscreen
</MenuItem>
<MenuItem
display="flex"
alignItems="center"
value="save-png"
onClick={ handleFileSaveClick }
>
<IconSvg name="files/image" boxSize={ 5 } mr={ 3 }/>
Save as PNG
</MenuItem>
<MenuItem
display="flex"
alignItems="center"
value="save-csv"
onClick={ handleSVGSavingClick }
>
<IconSvg name="files/csv" boxSize={ 5 } mr={ 3 }/>
Save as CSV
</MenuItem>
</MenuList>
</Menu>
{ items && isFullscreen && (
</MenuContent>
</MenuRoot>
{ items && (
<FullscreenChartModal
isOpen
open={ fullscreenDialog.open }
onOpenChange={ fullscreenDialog.onOpenChange }
items={ items }
title={ title }
description={ description }
onClose={ clearFullscreenChart }
units={ units }
resolution={ resolution }
zoomRange={ zoomRange }
......
import type { IconProps } from '@chakra-ui/react';
import { Icon, useColorModeValue } from '@chakra-ui/react';
import { Icon } from '@chakra-ui/react';
import React from 'react';
// eslint-disable-next-line no-restricted-imports
import logoIcon from 'icons/networks/logo-placeholder.svg';
const ChartWatermarkIcon = (props: IconProps) => {
const watermarkColor = useColorModeValue('link', 'white');
return (
<Icon
{ ...props }
......@@ -18,7 +17,7 @@ const ChartWatermarkIcon = (props: IconProps) => {
transform="translate(-50%, -50%)"
pointerEvents="none"
viewBox="0 0 114 20"
color={ watermarkColor }
color={{ _light: 'link', _dark: 'white' }}
/>
);
};
......
import {
chakra,
Flex,
IconButton,
Tooltip,
useColorModeValue,
} from '@chakra-ui/react';
import { chakra, Flex } from '@chakra-ui/react';
import NextLink from 'next/link';
import React, { useRef } from 'react';
......@@ -13,7 +7,9 @@ import type { TimeChartItem } from './types';
import { route, type Route } from 'nextjs-routes';
import config from 'configs/app';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { IconButton } from 'toolkit/chakra/icon-button';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { Tooltip } from 'toolkit/chakra/tooltip';
import IconSvg from 'ui/shared/IconSvg';
import ChartMenu from './ChartMenu';
......@@ -48,8 +44,6 @@ const ChartWidget = ({
const ref = useRef<HTMLDivElement>(null);
const { zoomRange, handleZoom, handleZoomReset } = useZoom();
const borderColor = useColorModeValue('gray.200', 'gray.600');
const hasItems = items && items.length > 2;
const content = (
......@@ -72,24 +66,22 @@ const ChartWidget = ({
flexDir="column"
alignItems="flex-start"
cursor={ href ? 'pointer' : 'default' }
_hover={ href ? { color: 'link_hovered' } : {} }
_hover={ href ? { color: 'link.primary.hovered' } : {} }
>
<Skeleton
isLoaded={ !isLoading }
loading={ isLoading }
fontWeight={ 600 }
size={{ base: 'xs', lg: 'sm' }}
textStyle="md"
>
<span>{ title }</span>
</Skeleton>
{ description && (
<Skeleton
isLoaded={ !isLoading }
loading={ isLoading }
color="text_secondary"
fontSize="xs"
mt={ 1 }
>
<span>{ description }</span>
</Skeleton>
......@@ -104,8 +96,8 @@ const ChartWidget = ({
flexDir="column"
padding={{ base: 3, lg: 4 }}
borderRadius="lg"
border="1px"
borderColor={ borderColor }
borderWidth="1px"
borderColor={{ _light: 'gray.200', _dark: 'gray.600' }}
className={ className }
>
<Flex columnGap={ 6 } mb={ 2 } alignItems="flex-start">
......@@ -115,18 +107,18 @@ const ChartWidget = ({
</NextLink>
) : chartHeader }
<Flex ml="auto" columnGap={ 2 }>
<Tooltip label="Reset zoom">
<Tooltip content="Reset zoom">
<IconButton
hidden={ !zoomRange }
aria-label="Reset zoom"
colorScheme="blue"
w={ 9 }
h={ 8 }
size="sm"
variant="outline"
onClick={ handleZoomReset }
icon={ <IconSvg name="repeat" w={ 4 } h={ 4 }/> }
/>
>
<IconSvg name="repeat" boxSize={ 4 }/>
</IconButton>
</Tooltip>
{ hasItems && (
......
import { Box, Center, Flex, Link, Text } from '@chakra-ui/react';
import { Box, Center, Flex, Text } from '@chakra-ui/react';
import React from 'react';
import type { TimeChartItem } from './types';
import type { Resolution } from '@blockscout/stats-types';
import { apos } from 'lib/html-entities';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';
import ChartWatermarkIcon from './ChartWatermarkIcon';
import ChartWidgetGraph from './ChartWidgetGraph';
......@@ -48,7 +49,7 @@ const ChartWidgetContent = ({
py={ 4 }
>
<Text
variant="secondary"
color="text.secondary"
fontSize="sm"
textAlign="center"
>
......@@ -60,13 +61,13 @@ const ChartWidgetContent = ({
}
if (isLoading) {
return <Skeleton flexGrow={ 1 } w="100%"/>;
return <Skeleton loading flexGrow={ 1 } w="100%"/>;
}
if (!hasItems) {
return (
<Center flexGrow={ 1 }>
<Text variant="secondary" fontSize="sm">{ emptyText || 'No data' }</Text>
<Text color="text.secondary" fontSize="sm">{ emptyText || 'No data' }</Text>
</Center>
);
}
......
......@@ -41,7 +41,7 @@ const ChartWidgetGraph = ({
zoomRange,
}: Props) => {
const isMobile = useIsMobile();
const color = useToken('colors', 'blue.200');
const [ color ] = useToken('colors', 'blue.200');
const chartId = `chart-${ title.split(' ').join('') }-${ isEnlarged ? 'fullscreen' : 'small' }`;
const overlayRef = React.useRef<SVGRectElement>(null);
......
import { Box, Button, Grid, Heading, Modal, ModalBody, ModalCloseButton, ModalContent, ModalOverlay, Text } from '@chakra-ui/react';
import { Grid, Text } from '@chakra-ui/react';
import React from 'react';
import type { TimeChartItem } from './types';
import type { Resolution } from '@blockscout/stats-types';
import { Button } from 'toolkit/chakra/button';
import { DialogBody, DialogContent, DialogHeader, DialogRoot } from 'toolkit/chakra/dialog';
import { Heading } from 'toolkit/chakra/heading';
import IconSvg from 'ui/shared/IconSvg';
import ChartWidgetContent from './ChartWidgetContent';
type Props = {
isOpen: boolean;
open: boolean;
onOpenChange: ({ open }: { open: boolean }) => void;
title: string;
description?: string;
items: Array<TimeChartItem>;
onClose: () => void;
units?: string;
resolution?: Resolution;
zoomRange?: [ Date, Date ];
......@@ -22,46 +25,35 @@ type Props = {
};
const FullscreenChartModal = ({
isOpen,
open,
onOpenChange,
title,
description,
items,
units,
onClose,
resolution,
zoomRange,
handleZoom,
handleZoomReset,
}: Props) => {
return (
<Modal
isOpen={ isOpen }
onClose={ onClose }
<DialogRoot
open={ open }
onOpenChange={ onOpenChange }
size="full"
isCentered
>
<ModalOverlay/>
<ModalContent>
<Box
mb={ 1 }
>
<Grid
gridColumnGap={ 2 }
>
<Heading
mb={ 1 }
size={{ base: 'xs', sm: 'md' }}
>
<DialogContent>
<DialogHeader/>
<DialogBody pt={ 6 } display="flex" flexDir="column">
<Grid gridColumnGap={ 2 } >
<Heading mb={ 1 } level="2">
{ title }
</Heading>
{ description && (
<Text
gridColumn={ 1 }
as="p"
variant="secondary"
color="text.secondary"
fontSize="xs"
>
{ description }
......@@ -70,8 +62,6 @@ const FullscreenChartModal = ({
{ Boolean(zoomRange) && (
<Button
leftIcon={ <IconSvg name="repeat" w={ 4 } h={ 4 }/> }
colorScheme="blue"
gridColumn={ 2 }
justifySelf="end"
alignSelf="top"
......@@ -80,18 +70,11 @@ const FullscreenChartModal = ({
variant="outline"
onClick={ handleZoomReset }
>
<IconSvg name="repeat" w={ 4 } h={ 4 }/>
Reset zoom
</Button>
) }
</Grid>
</Box>
<ModalCloseButton/>
<ModalBody
h="100%"
margin={{ bottom: 60 }}
>
<ChartWidgetContent
isEnlarged
items={ items }
......@@ -101,9 +84,9 @@ const FullscreenChartModal = ({
title={ title }
resolution={ resolution }
/>
</ModalBody>
</ModalContent>
</Modal>
</DialogBody>
</DialogContent>
</DialogRoot>
);
};
......
......@@ -71,7 +71,7 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement,
</TableCell>
<TableCell whiteSpace="nowrap">
{ tx.method && (
<Badge colorScheme={ tx.method === 'Multicall' ? 'teal' : 'gray' } loading={ isLoading } truncated>
<Badge colorPalette={ tx.method === 'Multicall' ? 'teal' : 'gray' } loading={ isLoading } truncated>
<span>{ tx.method }</span>
</Badge>
) }
......
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