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