Commit add0e9bb authored by tom's avatar tom

stats pages

parent 8fb28abe
...@@ -13,14 +13,14 @@ import config from 'configs/app'; ...@@ -13,14 +13,14 @@ import config from 'configs/app';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
// const Chart = dynamic(() => import('ui/pages/Chart'), { ssr: false }); const Chart = dynamic(() => import('ui/pages/Chart'), { ssr: false });
const pathname: Route['pathname'] = '/stats/[id]'; const pathname: Route['pathname'] = '/stats/[id]';
const Page: NextPage<Props<typeof pathname>> = (props: Props<typeof pathname>) => { const Page: NextPage<Props<typeof pathname>> = (props: Props<typeof pathname>) => {
return ( return (
<PageNextJs pathname={ pathname } query={ props.query } apiData={ props.apiData }> <PageNextJs pathname={ pathname } query={ props.query } apiData={ props.apiData }>
{ /* <Chart/> */ } <Chart/>
</PageNextJs> </PageNextJs>
); );
}; };
......
...@@ -3,12 +3,12 @@ import React from 'react'; ...@@ -3,12 +3,12 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
// import Stats from 'ui/pages/Stats'; import Stats from 'ui/pages/Stats';
const Page: NextPage = () => { const Page: NextPage = () => {
return ( return (
<PageNextJs pathname="/stats"> <PageNextJs pathname="/stats">
{ /* <Stats/> */ } <Stats/>
</PageNextJs> </PageNextJs>
); );
}; };
......
...@@ -13,6 +13,7 @@ export interface TagProps extends ChakraTag.RootProps { ...@@ -13,6 +13,7 @@ export interface TagProps extends ChakraTag.RootProps {
closable?: boolean; closable?: boolean;
truncated?: boolean; truncated?: boolean;
loading?: boolean; loading?: boolean;
selected?: boolean;
} }
export const Tag = React.forwardRef<HTMLSpanElement, TagProps>( export const Tag = React.forwardRef<HTMLSpanElement, TagProps>(
...@@ -26,6 +27,7 @@ export const Tag = React.forwardRef<HTMLSpanElement, TagProps>( ...@@ -26,6 +27,7 @@ export const Tag = React.forwardRef<HTMLSpanElement, TagProps>(
children, children,
truncated = false, truncated = false,
loading, loading,
selected,
...rest ...rest
} = props; } = props;
...@@ -37,7 +39,11 @@ export const Tag = React.forwardRef<HTMLSpanElement, TagProps>( ...@@ -37,7 +39,11 @@ export const Tag = React.forwardRef<HTMLSpanElement, TagProps>(
return ( return (
<Skeleton loading={ loading } asChild> <Skeleton loading={ loading } asChild>
<ChakraTag.Root ref={ ref } { ...rest }> <ChakraTag.Root
ref={ ref }
{ ...(selected && { 'data-selected': true }) }
{ ...rest }
>
{ startElement && ( { startElement && (
<ChakraTag.StartElement>{ startElement }</ChakraTag.StartElement> <ChakraTag.StartElement>{ startElement }</ChakraTag.StartElement>
) } ) }
......
...@@ -358,6 +358,13 @@ const semanticTokens: ThemingConfig['semanticTokens'] = { ...@@ -358,6 +358,13 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
bg: { value: { _light: '{colors.gray.100}', _dark: '{colors.gray.800}' } }, bg: { value: { _light: '{colors.gray.100}', _dark: '{colors.gray.800}' } },
fg: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, fg: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } },
}, },
select: {
bg: {
DEFAULT: { value: { _light: '{colors.gray.100}', _dark: '{colors.gray.800}' } },
selected: { value: { _light: '{colors.blue.500}', _dark: '{colors.blue.900}' } },
},
fg: { value: { _light: '{colors.gray.500}', _dark: '{colors.whiteAlpha.800}' } },
},
}, },
closeTrigger: { closeTrigger: {
color: { value: { _light: '{colors.gray.400}', _dark: '{colors.gray.500}' } }, color: { value: { _light: '{colors.gray.400}', _dark: '{colors.gray.500}' } },
......
...@@ -79,6 +79,20 @@ export const recipe = defineSlotRecipe({ ...@@ -79,6 +79,20 @@ export const recipe = defineSlotRecipe({
textStyle: 'sm', textStyle: 'sm',
}, },
}, },
lg: {
root: {
px: '6px',
py: '6px',
minH: '8',
gap: '1',
'--tag-avatar-size': 'spacing.4',
'--tag-element-size': 'spacing.3',
'--tag-element-offset': '0px',
},
label: {
textStyle: 'sm',
},
},
}, },
variant: { variant: {
...@@ -104,6 +118,28 @@ export const recipe = defineSlotRecipe({ ...@@ -104,6 +118,28 @@ export const recipe = defineSlotRecipe({
}, },
}, },
}, },
select: {
root: {
cursor: 'pointer',
bgColor: 'tag.root.select.bg',
color: 'tag.root.select.fg',
'&:not([data-loading], [aria-busy=true])': {
bgColor: 'tag.root.select.bg',
},
_hover: {
color: 'blue.400',
opacity: 0.76,
},
_selected: {
bgColor: 'tag.root.select.bg.selected',
color: 'whiteAlpha.800',
_hover: {
color: 'whiteAlpha.800',
opacity: 0.76,
},
},
},
},
}, },
}, },
......
import { Button, Flex, Link, Text } from '@chakra-ui/react'; import { createListCollection, Flex, Text } from '@chakra-ui/react';
import type { NextRouter } from 'next/router'; import type { NextRouter } from 'next/router';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -15,8 +15,11 @@ import isBrowser from 'lib/isBrowser'; ...@@ -15,8 +15,11 @@ import isBrowser from 'lib/isBrowser';
import * as metadata from 'lib/metadata'; import * as metadata from 'lib/metadata';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { Button } from 'toolkit/chakra/button';
import { IconButton } from 'toolkit/chakra/icon-button';
import { SelectContent, SelectControl, SelectItem, SelectRoot, SelectValueText } from 'toolkit/chakra/select';
import { Skeleton } from 'toolkit/chakra/skeleton';
import isCustomAppError from 'ui/shared/AppError/isCustomAppError'; import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
import Skeleton from 'ui/shared/chakra/Skeleton';
import ChartIntervalSelect from 'ui/shared/chart/ChartIntervalSelect'; import ChartIntervalSelect from 'ui/shared/chart/ChartIntervalSelect';
import ChartMenu from 'ui/shared/chart/ChartMenu'; import ChartMenu from 'ui/shared/chart/ChartMenu';
import ChartWidgetContent from 'ui/shared/chart/ChartWidgetContent'; import ChartWidgetContent from 'ui/shared/chart/ChartWidgetContent';
...@@ -25,7 +28,6 @@ import useZoom from 'ui/shared/chart/useZoom'; ...@@ -25,7 +28,6 @@ import useZoom from 'ui/shared/chart/useZoom';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import Select from 'ui/shared/select/Select';
import { STATS_RESOLUTIONS } from 'ui/stats/constants'; import { STATS_RESOLUTIONS } from 'ui/stats/constants';
const DEFAULT_RESOLUTION = Resolution.DAY; const DEFAULT_RESOLUTION = Resolution.DAY;
...@@ -108,11 +110,11 @@ const Chart = () => { ...@@ -108,11 +110,11 @@ const Chart = () => {
); );
}, [ setIntervalState, router ]); }, [ setIntervalState, router ]);
const onResolutionChange = React.useCallback((resolution: Resolution) => { const onResolutionChange = React.useCallback(({ value }: { value: Array<string> }) => {
setResolution(resolution); setResolution(value[0] as Resolution);
router.push({ router.push({
pathname: router.pathname, pathname: router.pathname,
query: { ...router.query, resolution }, query: { ...router.query, resolution: value[0] },
}, },
undefined, undefined,
{ shallow: true }, { shallow: true },
...@@ -121,7 +123,7 @@ const Chart = () => { ...@@ -121,7 +123,7 @@ const Chart = () => {
const handleReset = React.useCallback(() => { const handleReset = React.useCallback(() => {
handleZoomReset(); handleZoomReset();
onResolutionChange(DEFAULT_RESOLUTION); onResolutionChange({ value: [ DEFAULT_RESOLUTION ] });
}, [ handleZoomReset, onResolutionChange ]); }, [ handleZoomReset, onResolutionChange ]);
const { items, info, lineQuery } = useChartQuery(id, resolution, interval); const { items, info, lineQuery } = useChartQuery(id, resolution, interval);
...@@ -155,22 +157,24 @@ const Chart = () => { ...@@ -155,22 +157,24 @@ const Chart = () => {
const shareButton = ( const shareButton = (
<Button <Button
leftIcon={ <IconSvg name="share" w={ 4 } h={ 4 }/> }
colorScheme="blue"
size="sm" size="sm"
variant="outline" variant="outline"
onClick={ onShare } onClick={ onShare }
ml={ 6 } ml={ 6 }
loadingSkeleton={ lineQuery.isPlaceholderData }
> >
<IconSvg name="share" w={ 4 } h={ 4 }/>
Share Share
</Button> </Button>
); );
const resolutionOptions = React.useMemo(() => { const resolutionCollection = React.useMemo(() => {
const resolutions = lineQuery.data?.info?.resolutions || []; const resolutions = lineQuery.data?.info?.resolutions || [];
return STATS_RESOLUTIONS const items = STATS_RESOLUTIONS
.filter((resolution) => resolutions.includes(resolution.id)) .filter((resolution) => resolutions.includes(resolution.id))
.map((resolution) => ({ value: resolution.id, label: resolution.title })); .map((resolution) => ({ value: resolution.id, label: resolution.title }));
return createListCollection({ items });
}, [ lineQuery.data?.info?.resolutions ]); }, [ lineQuery.data?.info?.resolutions ]);
return ( return (
...@@ -194,21 +198,32 @@ const Chart = () => { ...@@ -194,21 +198,32 @@ const Chart = () => {
(!info && lineQuery.data?.info?.resolutions && lineQuery.data?.info?.resolutions.length > 1) (!info && lineQuery.data?.info?.resolutions && lineQuery.data?.info?.resolutions.length > 1)
) && ( ) && (
<Flex alignItems="center" gap={ 3 }> <Flex alignItems="center" gap={ 3 }>
<Skeleton isLoaded={ !isInfoLoading }> <Skeleton loading={ isInfoLoading }>
{ isMobile ? 'Res.' : 'Resolution' } { isMobile ? 'Res.' : 'Resolution' }
</Skeleton> </Skeleton>
<Select <SelectRoot
options={ resolutionOptions } collection={ resolutionCollection }
defaultValue={ defaultResolution } variant="outline"
onChange={ onResolutionChange } defaultValue={ [ defaultResolution ] }
isLoading={ isInfoLoading } onValueChange={ onResolutionChange }
w={{ base: 'fit-content', lg: '160px' }} w={{ base: 'fit-content', lg: '160px' }}
fontWeight={ 600 } >
/> <SelectControl loading={ isInfoLoading }>
<SelectValueText placeholder="Select resolution"/>
</SelectControl>
<SelectContent>
{ resolutionCollection.items.map((item) => (
<SelectItem item={ item } key={ item.value }>
{ item.label }
</SelectItem>
)) }
</SelectContent>
</SelectRoot>
</Flex> </Flex>
) } ) }
{ (Boolean(zoomRange)) && ( { (Boolean(zoomRange)) && (
<Link <Button
variant="link"
onClick={ handleReset } onClick={ handleReset }
display="flex" display="flex"
alignItems="center" alignItems="center"
...@@ -216,7 +231,7 @@ const Chart = () => { ...@@ -216,7 +231,7 @@ const Chart = () => {
> >
<IconSvg name="repeat" w={ 5 } h={ 5 }/> <IconSvg name="repeat" w={ 5 } h={ 5 }/>
{ !isMobile && 'Reset' } { !isMobile && 'Reset' }
</Link> </Button>
) } ) }
</Flex> </Flex>
<Flex alignItems="center" gap={ 3 }> <Flex alignItems="center" gap={ 3 }>
...@@ -225,23 +240,23 @@ const Chart = () => { ...@@ -225,23 +240,23 @@ const Chart = () => {
{ !isMobile && (isInBrowser && ((window.navigator.share as any) ? { !isMobile && (isInBrowser && ((window.navigator.share as any) ?
shareButton : shareButton :
( (
<IconButton variant="outline" size="sm" asChild p={ 1 }>
<CopyToClipboard <CopyToClipboard
text={ config.app.baseUrl + router.asPath } text={ config.app.baseUrl + router.asPath }
size={ 5 }
type="link" type="link"
variant="outline" boxSize={ 8 }
colorScheme="blue" color="button.outline.fg"
display="flex" ml={ 0 }
borderRadius="8px" borderRadius="base"
width={ 8 }
height={ 8 }
/> />
</IconButton>
) )
)) } )) }
{ (hasItems || lineQuery.isPlaceholderData) && ( { (hasItems || lineQuery.isPlaceholderData) && (
<ChartMenu <ChartMenu
items={ items } items={ items }
title={ info?.title || '' } title={ info?.title || '' }
description={ info?.description || '' }
isLoading={ lineQuery.isPlaceholderData } isLoading={ lineQuery.isPlaceholderData }
chartRef={ ref } chartRef={ ref }
resolution={ resolution } resolution={ resolution }
......
import { Box, Heading, Icon } from '@chakra-ui/react'; import { Box, Icon } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
// This icon doesn't work properly when it is in the sprite // This icon doesn't work properly when it is in the sprite
// Probably because of radial gradient // Probably because of radial gradient
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import emptySearchResultIcon from 'icons/empty_search_result.svg'; import emptySearchResultIcon from 'icons/empty_search_result.svg';
import { Heading } from 'toolkit/chakra/heading';
interface Props { interface Props {
text: string | React.JSX.Element; text: string | React.JSX.Element;
...@@ -26,7 +27,7 @@ const EmptySearchResult = ({ text }: Props) => { ...@@ -26,7 +27,7 @@ const EmptySearchResult = ({ text }: Props) => {
mb={{ base: 4, sm: 6 }} mb={{ base: 4, sm: 6 }}
/> />
<Heading as="h4" size="sm" mb={ 2 }> <Heading level="3" mb={ 2 }>
No results No results
</Heading> </Heading>
......
import type { TagProps } from '@chakra-ui/react'; import { createListCollection } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { StatsInterval, StatsIntervalIds } from 'types/client/stats'; import type { StatsInterval, StatsIntervalIds } from 'types/client/stats';
import type { SelectOption } from 'toolkit/chakra/select'; import { SelectContent, SelectControl, SelectItem, SelectRoot, SelectValueText } from 'toolkit/chakra/select';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Skeleton } from 'toolkit/chakra/skeleton';
import Select from 'ui/shared/select/Select'; import type { TagProps } from 'toolkit/chakra/tag';
import TagGroupSelect from 'ui/shared/tagGroupSelect/TagGroupSelect'; import TagGroupSelect from 'ui/shared/tagGroupSelect/TagGroupSelect';
import { STATS_INTERVALS } from 'ui/stats/constants'; import { STATS_INTERVALS } from 'ui/stats/constants';
const intervalList = Object.keys(STATS_INTERVALS).map((id: string) => ({ const intervalCollection = createListCollection({
items: Object.keys(STATS_INTERVALS).map((id: string) => ({
value: id, value: id,
label: STATS_INTERVALS[id as StatsIntervalIds].title, label: STATS_INTERVALS[id as StatsIntervalIds].shortTitle,
})) as Array<SelectOption>; })),
});
const intervalListShort = Object.keys(STATS_INTERVALS).map((id: string) => ({ const intervalListShort = Object.keys(STATS_INTERVALS).map((id: string) => ({
id: id, id: id,
...@@ -27,21 +29,35 @@ type Props = { ...@@ -27,21 +29,35 @@ type Props = {
}; };
const ChartIntervalSelect = ({ interval, onIntervalChange, isLoading, selectTagSize }: Props) => { const ChartIntervalSelect = ({ interval, onIntervalChange, isLoading, selectTagSize }: Props) => {
const handleItemSelect = React.useCallback(({ value }: { value: Array<string> }) => {
onIntervalChange(value[0] as StatsIntervalIds);
}, [ onIntervalChange ]);
return ( return (
<> <>
<Skeleton display={{ base: 'none', lg: 'flex' }} borderRadius="base" isLoaded={ !isLoading }> <Skeleton hideBelow="lg" borderRadius="base" loading={ isLoading }>
<TagGroupSelect<StatsIntervalIds> items={ intervalListShort } onChange={ onIntervalChange } value={ interval } tagSize={ selectTagSize }/> <TagGroupSelect<StatsIntervalIds> items={ intervalListShort } onChange={ onIntervalChange } value={ interval } tagSize={ selectTagSize }/>
</Skeleton> </Skeleton>
<Select <SelectRoot
options={ intervalList } collection={ intervalCollection }
defaultValue={ interval } variant="outline"
onChange={ onIntervalChange } defaultValue={ [ interval ] }
isLoading={ isLoading } onValueChange={ handleItemSelect }
w={{ base: '100%', lg: '136px' }} hideFrom="lg"
display={{ base: 'flex', lg: 'none' }} w="100%"
flexShrink={ 0 } >
fontWeight={ 600 } <SelectControl loading={ isLoading }>
/> <SelectValueText placeholder="Select interval"/>
</SelectControl>
<SelectContent>
{ intervalCollection.items.map((item) => (
<SelectItem item={ item } key={ item.value }>
{ item.label }
</SelectItem>
)) }
</SelectContent>
</SelectRoot>
</> </>
); );
}; };
......
...@@ -130,7 +130,7 @@ const ChartMenu = ({ ...@@ -130,7 +130,7 @@ const ChartMenu = ({
onClick={ hasShare ? handleShare : handleCopy } onClick={ hasShare ? handleShare : handleCopy }
closeOnSelect={ hasShare ? false : true } closeOnSelect={ hasShare ? false : true }
> >
<IconSvg name={ hasShare ? 'share' : 'copy' } boxSize={ 5 } mr={ 3 }/> <IconSvg name={ hasShare ? 'share' : 'copy' } boxSize={ 5 }/>
{ hasShare ? 'Share' : 'Copy link' } { hasShare ? 'Share' : 'Copy link' }
</MenuItem> </MenuItem>
) } ) }
...@@ -138,21 +138,21 @@ const ChartMenu = ({ ...@@ -138,21 +138,21 @@ const ChartMenu = ({
value="fullscreen" value="fullscreen"
onClick={ showChartFullscreen } onClick={ showChartFullscreen }
> >
<IconSvg name="scope" boxSize={ 5 } mr={ 3 }/> <IconSvg name="scope" boxSize={ 5 }/>
View fullscreen View fullscreen
</MenuItem> </MenuItem>
<MenuItem <MenuItem
value="save-png" value="save-png"
onClick={ handleFileSaveClick } onClick={ handleFileSaveClick }
> >
<IconSvg name="files/image" boxSize={ 5 } mr={ 3 }/> <IconSvg name="files/image" boxSize={ 5 }/>
Save as PNG Save as PNG
</MenuItem> </MenuItem>
<MenuItem <MenuItem
value="save-csv" value="save-csv"
onClick={ handleSVGSavingClick } onClick={ handleSVGSavingClick }
> >
<IconSvg name="files/csv" boxSize={ 5 } mr={ 3 }/> <IconSvg name="files/csv" boxSize={ 5 }/>
Save as CSV Save as CSV
</MenuItem> </MenuItem>
</MenuContent> </MenuContent>
......
...@@ -40,12 +40,13 @@ const FullscreenChartModal = ({ ...@@ -40,12 +40,13 @@ const FullscreenChartModal = ({
<DialogRoot <DialogRoot
open={ open } open={ open }
onOpenChange={ onOpenChange } onOpenChange={ onOpenChange }
size="full" // FIXME: with size="full" the chart will not be expanded to the full height of the modal
size="cover"
> >
<DialogContent> <DialogContent>
<DialogHeader/> <DialogHeader/>
<DialogBody pt={ 6 } display="flex" flexDir="column"> <DialogBody pt={ 6 } display="flex" flexDir="column">
<Grid gridColumnGap={ 2 } > <Grid gridColumnGap={ 2 } mb={ 4 }>
<Heading mb={ 1 } level="2"> <Heading mb={ 1 } level="2">
{ title } { title }
</Heading> </Heading>
...@@ -54,7 +55,7 @@ const FullscreenChartModal = ({ ...@@ -54,7 +55,7 @@ const FullscreenChartModal = ({
<Text <Text
gridColumn={ 1 } gridColumn={ 1 }
color="text.secondary" color="text.secondary"
fontSize="xs" textStyle="sm"
> >
{ description } { description }
</Text> </Text>
......
import type { TagProps } from '@chakra-ui/react'; import { HStack } from '@chakra-ui/react';
import { HStack, Tag } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { TagProps } from 'toolkit/chakra/tag';
import { Tag } from 'toolkit/chakra/tag';
type Props<T extends string> = { type Props<T extends string> = {
items: Array<{ id: T; title: string }>; items: Array<{ id: T; title: string }>;
tagSize?: TagProps['size']; tagSize?: TagProps['size'];
...@@ -42,7 +44,7 @@ const TagGroupSelect = <T extends string>({ items, value, isMulti, onChange, tag ...@@ -42,7 +44,7 @@ const TagGroupSelect = <T extends string>({ items, value, isMulti, onChange, tag
variant="select" variant="select"
key={ item.id } key={ item.id }
data-id={ item.id } data-id={ item.id }
data-selected={ isSelected } selected={ isSelected }
fontWeight={ 500 } fontWeight={ 500 }
onClick={ onItemClick } onClick={ onItemClick }
size={ tagSize } size={ tagSize }
......
...@@ -19,6 +19,22 @@ const TagShowcase = () => { ...@@ -19,6 +19,22 @@ const TagShowcase = () => {
<Sample label="variant: clickable"> <Sample label="variant: clickable">
<Tag variant="clickable">My tag</Tag> <Tag variant="clickable">My tag</Tag>
</Sample> </Sample>
<Sample label="variant: select">
<Tag variant="select">Default</Tag>
<Tag variant="select" selected>Selected</Tag>
</Sample>
</SamplesStack>
</Section>
<Section>
<SectionHeader>Size</SectionHeader>
<SamplesStack>
<Sample label="size: md">
<Tag size="md">My tag</Tag>
</Sample>
<Sample label="size: lg">
<Tag size="lg">My tag</Tag>
</Sample>
</SamplesStack> </SamplesStack>
</Section> </Section>
......
import { Box, Grid, Heading, List, ListItem } from '@chakra-ui/react'; import { Box, Grid } from '@chakra-ui/react';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import type * as stats from '@blockscout/stats-types'; import type * as stats from '@blockscout/stats-types';
...@@ -6,7 +6,8 @@ import type { StatsIntervalIds } from 'types/client/stats'; ...@@ -6,7 +6,8 @@ import type { StatsIntervalIds } from 'types/client/stats';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { apos } from 'lib/html-entities'; import { apos } from 'lib/html-entities';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Heading } from 'toolkit/chakra/heading';
import { Skeleton } from 'toolkit/chakra/skeleton';
import EmptySearchResult from 'ui/shared/EmptySearchResult'; import EmptySearchResult from 'ui/shared/EmptySearchResult';
import GasInfoTooltip from 'ui/shared/gas/GasInfoTooltip'; import GasInfoTooltip from 'ui/shared/gas/GasInfoTooltip';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
...@@ -61,18 +62,18 @@ const ChartsWidgetsList = ({ filterQuery, isError, isPlaceholderData, charts, in ...@@ -61,18 +62,18 @@ const ChartsWidgetsList = ({ filterQuery, isError, isPlaceholderData, charts, in
<ChartsLoadingErrorAlert/> <ChartsLoadingErrorAlert/>
) } ) }
<List ref={ sectionRef }> <section ref={ sectionRef }>
{ {
charts?.map((section) => ( charts?.map((section) => (
<ListItem <Box
key={ section.id } key={ section.id }
mb={ 8 } mb={ 8 }
_last={{ _last={{
marginBottom: 0, marginBottom: 0,
}} }}
> >
<Skeleton isLoaded={ !isPlaceholderData } mb={ 4 } display="inline-flex" alignItems="center" columnGap={ 2 } id={ section.id }> <Skeleton loading={ isPlaceholderData } mb={ 4 } display="inline-flex" alignItems="center" columnGap={ 2 } id={ section.id }>
<Heading size="md" id={ section.id }> <Heading level="2" id={ section.id }>
{ section.title } { section.title }
</Heading> </Heading>
{ section.id === 'gas' && homeStatsQuery.data && homeStatsQuery.data.gas_prices && ( { section.id === 'gas' && homeStatsQuery.data && homeStatsQuery.data.gas_prices && (
...@@ -100,10 +101,10 @@ const ChartsWidgetsList = ({ filterQuery, isError, isPlaceholderData, charts, in ...@@ -100,10 +101,10 @@ const ChartsWidgetsList = ({ filterQuery, isError, isPlaceholderData, charts, in
/> />
)) } )) }
</Grid> </Grid>
</ListItem> </Box>
)) ))
} }
</List> </section>
</Box> </Box>
); );
}; };
......
import { Grid, GridItem } from '@chakra-ui/react'; import { createListCollection, Grid, GridItem } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type * as stats from '@blockscout/stats-types'; import type * as stats from '@blockscout/stats-types';
import type { StatsIntervalIds } from 'types/client/stats'; import type { StatsIntervalIds } from 'types/client/stats';
import { SelectContent, SelectControl, SelectItem, SelectRoot, SelectValueText } from 'toolkit/chakra/select';
import ChartIntervalSelect from 'ui/shared/chart/ChartIntervalSelect'; import ChartIntervalSelect from 'ui/shared/chart/ChartIntervalSelect';
import FilterInput from 'ui/shared/filters/FilterInput'; import FilterInput from 'ui/shared/filters/FilterInput';
import Select from 'ui/shared/select/Select';
type Props = { type Props = {
sections?: Array<stats.LineChartSection>; sections?: Array<stats.LineChartSection>;
...@@ -30,13 +30,19 @@ const StatsFilters = ({ ...@@ -30,13 +30,19 @@ const StatsFilters = ({
initialFilterValue, initialFilterValue,
}: Props) => { }: Props) => {
const options = React.useMemo(() => { const collection = React.useMemo(() => {
return [ return createListCollection({
items: [
{ value: 'all', label: 'All stats' }, { value: 'all', label: 'All stats' },
...(sections || []).map((section) => ({ value: section.id, label: section.title })), ...(sections || []).map((section) => ({ value: section.id, label: section.title })),
]; ],
});
}, [ sections ]); }, [ sections ]);
const handleItemSelect = React.useCallback(({ value }: { value: Array<string> }) => {
onSectionChange(value[0]);
}, [ onSectionChange ]);
return ( return (
<Grid <Grid
gap={{ base: 2, lg: 6 }} gap={{ base: 2, lg: 6 }}
...@@ -52,14 +58,24 @@ const StatsFilters = ({ ...@@ -52,14 +58,24 @@ const StatsFilters = ({
w={{ base: '100%', lg: 'auto' }} w={{ base: '100%', lg: 'auto' }}
area="section" area="section"
> >
<Select <SelectRoot
options={ options } collection={ collection }
defaultValue={ currentSection } variant="outline"
onChange={ onSectionChange } defaultValue={ [ currentSection ] }
isLoading={ isLoading } onValueChange={ handleItemSelect }
w={{ base: '100%', lg: '136px' }} w={{ base: '100%', lg: '136px' }}
fontWeight={ 600 } >
/> <SelectControl loading={ isLoading }>
<SelectValueText placeholder="Select section"/>
</SelectControl>
<SelectContent>
{ collection.items.map((item) => (
<SelectItem item={ item } key={ item.value }>
{ item.label }
</SelectItem>
)) }
</SelectContent>
</SelectRoot>
</GridItem> </GridItem>
<GridItem <GridItem
...@@ -75,11 +91,11 @@ const StatsFilters = ({ ...@@ -75,11 +91,11 @@ const StatsFilters = ({
> >
<FilterInput <FilterInput
key={ initialFilterValue } key={ initialFilterValue }
isLoading={ isLoading } loading={ isLoading }
onChange={ onFilterInputChange } onChange={ onFilterInputChange }
placeholder="Find chart, metric..." placeholder="Find chart, metric..."
initialValue={ initialFilterValue } initialValue={ initialFilterValue }
size="xs" size="sm"
/> />
</GridItem> </GridItem>
</Grid> </Grid>
......
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