Commit 1ece2b7f authored by tom goriunov's avatar tom goriunov Committed by GitHub

Replace system selectors with Blockscout selectors style (#2451)

* migrate from native select to the custom one

* migrate popover radio filter to select component

* migrate stats menu to select

* migrate Sort component

* screenshot updates
parent b1b4d81d
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 320" shape-rendering="crispEdges">
<rect width="100%" height="100%" fill="#d5d7e1"/>
<path fill="#caeff9" d="M90 210h140v10H90zM90 220h140v10H90zM90 230h140v10H90zM90 240h140v10H90zM90 250h20v10H90zM120 250h110v10H120zM90 260h20v10H90zM120 260h110v10H120zM90 270h20v10H90zM120 270h110v10H120zM90 280h20v10H90zM120 280h110v10H120zM90 290h20v10H90zM120 290h110v10H120zM90 300h20v10H90zM120 300h110v10H120zM90 310h20v10H90zM120 310h110v10H120z"/>
<path fill="#caeff9" d="M90 210h140v10H90zm0 10h140v10H90zm0 10h140v10H90zm0 10h140v10H90zm0 10h20v10H90zm30 0h110v10H120zm-30 10h20v10H90zm30 0h110v10H120zm-30 10h20v10H90zm30 0h110v10H120zm-30 10h20v10H90zm30 0h110v10H120zm-30 10h20v10H90zm30 0h110v10H120zm-30 10h20v10H90zm30 0h110v10H120zm-30 10h20v10H90zm30 0h110v10H120z"/>
<path fill="#9f21a0" d="M200 230h30v10h-30z"/>
<path fill="#d22209" d="M160 240h40v10h-40z"/>
<path fill="#9f21a0" d="M200 240h30v10h-30z"/>
......@@ -13,7 +13,7 @@
<path fill="#ffc925" d="M90 270h20v10H90z"/>
<path fill="#fe500c" d="M120 270h40v10h-40z"/>
<path fill="#ffc925" d="M90 280h20v10H90z"/>
<path fill="#ffc110" d="M160 30h80v10h-80zM140 40h50v10h-50z"/>
<path fill="#ffc110" d="M160 30h80v10h-80zm-20 10h50v10h-50z"/>
<path fill="#fff0ee" d="M190 40h40v10h-40z"/>
<path fill="#ffc110" d="M230 40h20v10h-20zM130 50h50v10h-50z"/>
<path fill="#fff0ee" d="M180 50h20v10h-20z"/>
......@@ -29,17 +29,17 @@
<path fill="#fff0ee" d="M190 70h40v10h-40z"/>
<path fill="#ffc110" d="M230 70h20v10h-20z"/>
<path fill="#fe500c" d="M250 70h30v10h-30z"/>
<path fill="#ffc110" d="M50 80h20v10H50zM140 80h100v10H140z"/>
<path fill="#ffc110" d="M50 80h20v10H50zm90 0h100v10H140z"/>
<path fill="#fe500c" d="M240 80h30v10h-30z"/>
<path fill="#ffc110" d="M50 90h50v10H50zM150 90h100v10H150zM40 100h220v10H40zM40 110h230v10H40zM40 120h240v10H40zM40 130h240v10H40zM40 140h240v10H40zM50 150h230v10H50zM50 160h230v10H50zM60 170h220v10H60zM60 180h110v10H60z"/>
<path fill="#ffc110" d="M50 90h50v10H50zm100 0h100v10H150zM40 100h220v10H40zm0 10h230v10H40zm0 10h240v10H40zm0 10h240v10H40zm0 10h240v10H40zm10 10h230v10H50zm0 10h230v10H50zm10 10h220v10H60zm0 10h110v10H60z"/>
<path fill="#d08b11" d="M170 180h10v10h-10z"/>
<path fill="#ffc110" d="M180 180h90v10h-90zM70 190h70v10H70z"/>
<path fill="#d08b11" d="M140 190h30v10h-30z"/>
<path fill="#ffc110" d="M170 190h90v10h-90zM90 200h160v10H90z"/>
<path fill="#ff638d" d="M100 110h60v10h-60zM170 110h60v10h-60zM100 120h10v10h-10z"/>
<path fill="#ffc110" d="M170 190h90v10h-90zm-80 10h160v10H90z"/>
<path fill="#ff638d" d="M100 110h60v10h-60zm70 0h60v10h-60zm-70 10h10v10h-10z"/>
<path fill="#fff" d="M110 120h20v10h-20z"/>
<path d="M130 120h20v10h-20z"/>
<path fill="#ff638d" d="M150 120h10v10h-10zM170 120h10v10h-10z"/>
<path fill="#ff638d" d="M150 120h10v10h-10zm20 0h10v10h-10z"/>
<path fill="#fff" d="M180 120h20v10h-20z"/>
<path d="M200 120h20v10h-20z"/>
<path fill="#ff638d" d="M220 120h10v10h-10zM70 130h40v10H70z"/>
......@@ -54,11 +54,11 @@
<path fill="#ff638d" d="M150 140h30v10h-30z"/>
<path fill="#fff" d="M180 140h20v10h-20z"/>
<path d="M200 140h20v10h-20z"/>
<path fill="#ff638d" d="M220 140h10v10h-10zM70 150h10v10H70zM100 150h10v10h-10z"/>
<path fill="#ff638d" d="M220 140h10v10h-10zM70 150h10v10H70zm30 0h10v10h-10z"/>
<path fill="#fff" d="M110 150h20v10h-20z"/>
<path d="M130 150h20v10h-20z"/>
<path fill="#ff638d" d="M150 150h10v10h-10zM170 150h10v10h-10z"/>
<path fill="#ff638d" d="M150 150h10v10h-10zm20 0h10v10h-10z"/>
<path fill="#fff" d="M180 150h20v10h-20z"/>
<path d="M200 150h20v10h-20z"/>
<path fill="#ff638d" d="M220 150h10v10h-10zM100 160h60v10h-60zM170 160h60v10h-60z"/>
<path fill="#ff638d" d="M220 150h10v10h-10zm-120 10h60v10h-60zm70 0h60v10h-60z"/>
</svg>
......@@ -24,6 +24,7 @@ import ContractDetailsInfo from './info/ContractDetailsInfo';
import useContractDetailsTabs from './useContractDetailsTabs';
const TAB_LIST_PROPS = { flexWrap: 'wrap', rowGap: 2 };
const LEFT_SLOT_PROPS = { w: { base: '100%', lg: 'auto' } };
type Props = {
addressHash: string;
......@@ -117,6 +118,7 @@ const ContractDetails = ({ addressHash, channel, mainContractQuery }: Props) =>
size="sm"
leftSlot={ addressSelector }
tabListProps={ TAB_LIST_PROPS }
leftSlotProps={ LEFT_SLOT_PROPS }
/>
) : (
<>
......
import { chakra, Flex, Select, Skeleton } from '@chakra-ui/react';
import { chakra, Flex, Skeleton } from '@chakra-ui/react';
import React from 'react';
import { route } from 'nextjs-routes';
......@@ -6,6 +6,7 @@ import { route } from 'nextjs-routes';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import LinkNewTab from 'ui/shared/links/LinkNewTab';
import Select from 'ui/shared/select/Select';
export interface Item {
address: string;
......@@ -23,13 +24,17 @@ interface Props {
const ContractSourceAddressSelector = ({ className, selectedItem, onItemSelect, items, isLoading, label }: Props) => {
const handleItemSelect = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
const nextOption = items.find(({ address }) => address === event.target.value);
const handleItemSelect = React.useCallback((value: string) => {
const nextOption = items.find(({ address }) => address === value);
if (nextOption) {
onItemSelect(nextOption);
}
}, [ items, onItemSelect ]);
const options = React.useMemo(() => {
return items.map(({ address, name }) => ({ label: name || address, value: address }));
}, [ items ]);
if (isLoading) {
return <Skeleton h={ 6 } w={{ base: '300px', lg: '500px' }} className={ className }/>;
}
......@@ -53,19 +58,14 @@ const ContractSourceAddressSelector = ({ className, selectedItem, onItemSelect,
<Flex columnGap={ 3 } rowGap={ 2 } alignItems="center" className={ className }>
<chakra.span fontWeight={ 500 } fontSize="sm">{ label }</chakra.span>
<Select
size="xs"
value={ selectedItem.address }
options={ options }
name="contract-source-address"
defaultValue={ options[0].value }
onChange={ handleItemSelect }
w="auto"
isLoading={ isLoading }
maxW={{ base: '180px', lg: 'none' }}
fontWeight={ 600 }
borderRadius="base"
>
{ items.map((item) => (
<option key={ item.address } value={ item.address }>
{ item.name }
</option>
)) }
</Select>
/>
<Flex columnGap={ 2 } alignItems="center">
<CopyToClipboard text={ selectedItem.address } ml={ 0 }/>
<LinkNewTab
......
......@@ -12,7 +12,8 @@ test('text', async({ render }) => {
const data = '0xE2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A280E2A3A4E2A1B6E2A0BFE2A0BFE2A0B7E2A3B6E2A384E2A080E2A080E2A080E2A080E2A0800AE2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A3B0E2A1BFE2A081E2A080E2A080E2A280E2A380E2A180E2A099E2A3B7E2A180E2A080E2A080E2A0800AE2A080E2A080E2A080E2A180E2A080E2A080E2A080E2A080E2A080E2A2A0E2A3BFE2A081E2A080E2A080E2A080E2A098E2A0BFE2A083E2A080E2A2B8E2A3BFE2A3BFE2A3BFE2A3BF0AE2A080E2A3A0E2A1BFE2A09BE2A2B7E2A3A6E2A180E2A080E2A080E2A088E2A3BFE2A184E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A3B8E2A3BFE2A3BFE2A3BFE2A09F0AE2A2B0E2A1BFE2A081E2A080E2A080E2A099E2A2BFE2A3A6E2A3A4E2A3A4E2A3BCE2A3BFE2A384E2A080E2A080E2A080E2A080E2A080E2A2B4E2A19FE2A09BE2A08BE2A081E2A0800AE2A3BFE2A087E2A080E2A080E2A080E2A080E2A080E2A089E2A089E2A089E2A089E2A089E2A081E2A080E2A080E2A080E2A080E2A080E2A088E2A3BFE2A180E2A080E2A080E2A0800AE2A3BFE2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A2B9E2A187E2A080E2A080E2A0800AE2A3BFE2A186E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A3BCE2A187E2A080E2A080E2A0800AE2A0B8E2A3B7E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A2A0E2A1BFE2A080E2A080E2A080E2A0800AE2A080E2A0B9E2A3B7E2A3A4E2A380E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A380E2A3B0E2A1BFE2A081E2A080E2A080E2A080E2A0800AE2A080E2A080E2A080E2A089E2A099E2A09BE2A0BFE2A0B6E2A3B6E2A3B6E2A3B6E2A3B6E2A3B6E2A0B6E2A0BFE2A09FE2A09BE2A089E2A080E2A080E2A080E2A080E2A080E2A080';
const component = await render(<BlobData hash="0x01" data={ data }/>);
await expect(component).toHaveScreenshot();
await component.locator('select').selectOption('UTF-8');
await component.getByRole('button', { name: 'Raw' }).click();
await component.getByText('UTF-8').click();
await expect(component).toHaveScreenshot();
});
......@@ -21,7 +22,8 @@ test('image', async({ render }) => {
const data = '0x89504E470D0A1A0A0000000D494844520000003C0000003C0403000000C8D2C4410000000467414D410000B18F0BFC6105000000017352474200AECE1CE900000027504C54454C69712B6CB02A6CB02B6CB02B6CB02B6CB02B6CB02B6CB02B6CB02B6CB02B6CB02B6CB02B6CB0F4205A540000000C74524E5300ED2F788CD91B99475C09B969CFA99D0000004F7A5458745261772070726F66696C65207479706520697074630000789CE3CA2C2849E6520003230B2E630B1323134B9314031320448034C3640323B35420CBD8D4C8C4CCC41CC407CB8048A04A2E0028950EE32A226D1F0000000970485973000084DF000084DF0195C81C33000000F24944415438CB636000018E983367CE482780D90CDA40F6991D0C4820152472A60ACCE6DA03629F4E40929E03961602B39964C09C0624691B24690E88F48461215D03160903B3D962C01C07842C2758C341A80643B0B40484C3646C6C5C78E6E016171723A8E215262EEE31670E161B1B7731304C05AB155EC08002C0D172E6F80206884DBB50651938CF4003FE0CBA4390E3C56064482F53525252C329CD562A2828283A0197340B22AAB0494332C311FCD2C747A547A58996C69998D8F12745B68DA0846C85331B2CEAE8E8681A81D91F8B348C4605D0527B02A4283FA88026CD05163EAAC0900ED21EC9800EC0C2110C002BBA9FE999B920330000000049454E44AE426082';
const component = await render(<BlobData hash="0x01" data={ data }/>);
await expect(component).toHaveScreenshot();
await component.locator('select').selectOption('Base64');
await component.getByRole('button', { name: 'Image' }).click();
await component.getByText('Base64').click();
await expect(component).toHaveScreenshot();
});
......
import { Flex, GridItem, Select, Skeleton, Button } from '@chakra-ui/react';
import { Flex, GridItem, Skeleton, Button } from '@chakra-ui/react';
import React from 'react';
import * as blobUtils from 'lib/blob';
......@@ -10,12 +10,18 @@ import hexToBytes from 'lib/hexToBytes';
import hexToUtf8 from 'lib/hexToUtf8';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import RawDataSnippet from 'ui/shared/RawDataSnippet';
import Select from 'ui/shared/select/Select';
import BlobDataImage from './BlobDataImage';
const FORMATS = [ 'Image', 'Raw', 'UTF-8', 'Base64' ] as const;
const FORMATS = [
{ label: 'Image', value: 'Image' as const },
{ label: 'Raw', value: 'Raw' as const },
{ label: 'UTF-8', value: 'UTF-8' as const },
{ label: 'Base64', value: 'Base64' as const },
];
type Format = typeof FORMATS[number];
type Format = typeof FORMATS[number]['value'];
interface Props {
data: string;
......@@ -34,7 +40,7 @@ const BlobData = ({ data, isLoading, hash }: Props) => {
}, [ data, isLoading ]);
const isImage = guessedType?.mime?.startsWith('image/');
const formats = isImage ? FORMATS : FORMATS.filter((format) => format !== 'Image');
const formats = isImage ? FORMATS : FORMATS.filter((format) => format.value !== 'Image');
React.useEffect(() => {
if (isImage) {
......@@ -42,10 +48,6 @@ const BlobData = ({ data, isLoading, hash }: Props) => {
}
}, [ isImage ]);
const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
setFormat(event.target.value as Format);
}, []);
const handleDownloadButtonClick = React.useCallback(() => {
const fileBlob = (() => {
switch (format) {
......@@ -104,16 +106,13 @@ const BlobData = ({ data, isLoading, hash }: Props) => {
</Skeleton>
<Skeleton ml={ 5 } isLoaded={ !isLoading }>
<Select
size="xs"
borderRadius="base"
value={ format }
onChange={ handleSelectChange }
w="auto"
>
{ formats.map((format) => (
<option key={ format } value={ format }>{ format }</option>
)) }
</Select>
options={ formats }
name="format"
defaultValue={ format }
onChange={ setFormat }
isLoading={ isLoading }
w="95px"
/>
</Skeleton>
<Skeleton ml="auto" mr={ 3 } isLoaded={ !isLoading }>
<Button
......
import type { NextRouter } from 'next/router';
import type { SelectOption } from 'ui/shared/select/types';
import config from 'configs/app';
import getQueryParamString from 'lib/router/getQueryParamString';
import removeQueryParam from 'lib/router/removeQueryParam';
import type { TOption } from 'ui/shared/sort/Option';
const feature = config.features.marketplace;
export type SortValue = 'rating_score' | 'rating_count' | 'security_score';
export const SORT_OPTIONS: Array<TOption<SortValue>> = [
{ title: 'Default', id: undefined },
(feature.isEnabled && feature.rating) && { title: 'Top rated', id: 'rating_score' },
(feature.isEnabled && feature.rating) && { title: 'Most rated', id: 'rating_count' },
(feature.isEnabled && feature.securityReportsUrl) && { title: 'Security score', id: 'security_score' },
].filter(Boolean) as Array<TOption<SortValue>>;
export const SORT_OPTIONS: Array<SelectOption<SortValue>> = [
{ label: 'Default', value: undefined },
(feature.isEnabled && feature.rating) && { label: 'Top rated', value: 'rating_score' },
(feature.isEnabled && feature.rating) && { label: 'Most rated', value: 'rating_count' },
(feature.isEnabled && feature.securityReportsUrl) && { label: 'Security score', value: 'security_score' },
].filter(Boolean) as Array<SelectOption<SortValue>>;
export function getAppUrl(url: string | undefined, router: NextRouter) {
if (!url) {
......
import type { EnsLookupSorting } from 'types/api/ens';
import type { SelectOption } from 'ui/shared/select/types';
import getNextSortValueShared from 'ui/shared/sort/getNextSortValue';
import type { TOption } from 'ui/shared/sort/Option';
export type SortField = EnsLookupSorting['sort'];
export type Sort = `${ EnsLookupSorting['sort'] }-${ EnsLookupSorting['order'] }`;
export const SORT_OPTIONS: Array<TOption<Sort>> = [
{ title: 'Default', id: undefined },
{ title: 'Registered on descending', id: 'registration_date-DESC' },
{ title: 'Registered on ascending', id: 'registration_date-ASC' },
export const SORT_OPTIONS: Array<SelectOption<Sort>> = [
{ label: 'Default', value: undefined },
{ label: 'Registered on descending', value: 'registration_date-DESC' },
{ label: 'Registered on ascending', value: 'registration_date-ASC' },
];
const SORT_SEQUENCE: Record<SortField, Array<Sort | undefined>> = {
......
......@@ -18,13 +18,14 @@ import getQueryParamString from 'lib/router/getQueryParamString';
import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
import ChartIntervalSelect from 'ui/shared/chart/ChartIntervalSelect';
import ChartMenu from 'ui/shared/chart/ChartMenu';
import ChartResolutionSelect from 'ui/shared/chart/ChartResolutionSelect';
import ChartWidgetContent from 'ui/shared/chart/ChartWidgetContent';
import useChartQuery from 'ui/shared/chart/useChartQuery';
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;
......@@ -68,8 +69,9 @@ const Chart = () => {
const id = getQueryParamString(router.query.id);
const intervalFromQuery = getIntervalFromQuery(router);
const resolutionFromQuery = getResolutionFromQuery(router);
const defaultResolution = resolutionFromQuery || DEFAULT_RESOLUTION;
const [ intervalState, setIntervalState ] = React.useState<StatsIntervalIds | undefined>(intervalFromQuery);
const [ resolution, setResolution ] = React.useState<Resolution>(resolutionFromQuery || DEFAULT_RESOLUTION);
const [ resolution, setResolution ] = React.useState<Resolution>(defaultResolution);
const { zoomRange, handleZoom, handleZoomReset } = useZoom();
const interval = intervalState || getIntervalByResolution(resolution);
......@@ -163,6 +165,13 @@ const Chart = () => {
</Button>
);
const resolutionOptions = React.useMemo(() => {
const resolutions = lineQuery.data?.info?.resolutions || [];
return STATS_RESOLUTIONS
.filter((resolution) => resolutions.includes(resolution.id))
.map((resolution) => ({ value: resolution.id, label: resolution.title }));
}, [ lineQuery.data?.info?.resolutions ]);
return (
<>
<PageTitle
......@@ -174,7 +183,7 @@ const Chart = () => {
withTextAd
/>
<Flex alignItems="center" justifyContent="space-between">
<Flex alignItems="center" gap={{ base: 3, lg: 6 }} maxW="100%" overflow="hidden">
<Flex alignItems="center" gap={{ base: 3, lg: 6 }} maxW="100%">
<Flex alignItems="center" gap={ 3 }>
{ !isMobile && <Text>Period</Text> }
<ChartIntervalSelect interval={ interval } onIntervalChange={ onIntervalChange }/>
......@@ -187,11 +196,13 @@ const Chart = () => {
<Skeleton isLoaded={ !isInfoLoading }>
{ isMobile ? 'Res.' : 'Resolution' }
</Skeleton>
<ChartResolutionSelect
resolution={ resolution }
onResolutionChange={ onResolutionChange }
resolutions={ lineQuery.data?.info?.resolutions || [] }
<Select
options={ resolutionOptions }
defaultValue={ defaultResolution }
onChange={ onResolutionChange }
isLoading={ isInfoLoading }
w={{ base: 'fit-content', lg: '160px' }}
fontWeight={ 600 }
/>
</Flex>
) }
......
import { Select, Skeleton } from '@chakra-ui/react';
import React from 'react';
import hexToUtf8 from 'lib/hexToUtf8';
import RawDataSnippet from 'ui/shared/RawDataSnippet';
import Select from 'ui/shared/select/Select';
export type DataType = 'Hex' | 'UTF-8';
const OPTIONS: Array<DataType> = [ 'Hex', 'UTF-8' ];
const OPTIONS = [
{ label: 'Hex', value: 'Hex' as const },
{ label: 'UTF-8', value: 'UTF-8' as const },
];
export type DataType = (typeof OPTIONS)[number]['value'];
interface Props {
hex: string;
......@@ -18,17 +22,17 @@ interface Props {
const RawInputData = ({ hex, rightSlot: rightSlotProp, defaultDataType = 'Hex', isLoading, minHeight }: Props) => {
const [ selectedDataType, setSelectedDataType ] = React.useState<DataType>(defaultDataType);
const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedDataType(event.target.value as DataType);
}, []);
const rightSlot = (
<>
<Skeleton isLoaded={ !isLoading } borderRadius="base" w="auto" mr="auto">
<Select size="xs" borderRadius="base" value={ selectedDataType } onChange={ handleSelectChange }>
{ OPTIONS.map((option) => <option key={ option } value={ option }>{ option }</option>) }
</Select>
</Skeleton>
<Select
options={ OPTIONS }
name="data-type"
defaultValue={ defaultDataType }
onChange={ setSelectedDataType }
isLoading={ isLoading }
w="90px"
mr="auto"
/>
{ rightSlotProp }
</>
);
......
......@@ -3,15 +3,16 @@ import { Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { StatsInterval, StatsIntervalIds } from 'types/client/stats';
import type { SelectOption } from 'ui/shared/select/types';
import Select from 'ui/shared/select/Select';
import TagGroupSelect from 'ui/shared/tagGroupSelect/TagGroupSelect';
import { STATS_INTERVALS } from 'ui/stats/constants';
import StatsDropdownMenu from 'ui/stats/StatsDropdownMenu';
const intervalList = Object.keys(STATS_INTERVALS).map((id: string) => ({
id: id,
title: STATS_INTERVALS[id as StatsIntervalIds].title,
})) as Array<StatsInterval>;
value: id,
label: STATS_INTERVALS[id as StatsIntervalIds].title,
})) as Array<SelectOption>;
const intervalListShort = Object.keys(STATS_INTERVALS).map((id: string) => ({
id: id,
......@@ -31,13 +32,16 @@ const ChartIntervalSelect = ({ interval, onIntervalChange, isLoading, selectTagS
<Skeleton display={{ base: 'none', lg: 'flex' }} borderRadius="base" isLoaded={ !isLoading }>
<TagGroupSelect<StatsIntervalIds> items={ intervalListShort } onChange={ onIntervalChange } value={ interval } tagSize={ selectTagSize }/>
</Skeleton>
<Skeleton display={{ base: 'block', lg: 'none' }} borderRadius="base" isLoaded={ !isLoading }>
<StatsDropdownMenu
items={ intervalList }
selectedId={ interval }
onSelect={ onIntervalChange }
/>
</Skeleton>
<Select
options={ intervalList }
defaultValue={ interval }
onChange={ onIntervalChange }
isLoading={ isLoading }
w={{ base: '100%', lg: '136px' }}
display={{ base: 'flex', lg: 'none' }}
flexShrink={ 0 }
fontWeight={ 600 }
/>
</>
);
};
......
import { Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { Resolution } from '@blockscout/stats-types';
import { STATS_RESOLUTIONS } from 'ui/stats/constants';
import StatsDropdownMenu from 'ui/stats/StatsDropdownMenu';
type Props = {
resolution: Resolution;
resolutions: Array<string>;
onResolutionChange: (resolution: Resolution) => void;
isLoading?: boolean;
};
const ChartResolutionSelect = ({ resolution, resolutions, onResolutionChange, isLoading }: Props) => {
return (
<Skeleton borderRadius="base" isLoaded={ !isLoading } w={{ base: 'auto', lg: '160px' }}>
<StatsDropdownMenu
items={ STATS_RESOLUTIONS.filter(r => resolutions.includes(r.id)) }
selectedId={ resolution }
onSelect={ onResolutionChange }
/>
</Skeleton>
);
};
export default React.memo(ChartResolutionSelect);
import {
PopoverTrigger,
PopoverContent,
PopoverBody,
useDisclosure,
useRadio,
Box,
useRadioGroup,
useColorModeValue,
} from '@chakra-ui/react';
import React from 'react';
import Popover from 'ui/shared/chakra/Popover';
import FilterButton from 'ui/shared/filters/FilterButton';
import IconSvg from 'ui/shared/IconSvg';
// OPTION
export interface TOption {
value: string;
label: string;
}
type OptionProps = ReturnType<ReturnType<typeof useRadioGroup>['getRadioProps']>;
const Option = (props: OptionProps) => {
const { getInputProps, getRadioProps } = useRadio(props);
const input = getInputProps();
const checkbox = getRadioProps();
const bgColorHover = useColorModeValue('blue.50', 'whiteAlpha.100');
return (
<Box
as="label"
px={ 4 }
py={ 2 }
cursor="pointer"
display="flex"
columnGap={ 3 }
alignItems="center"
_hover={{
bgColor: bgColorHover,
}}
>
<input { ...input }/>
<Box { ...checkbox }>
{ props.children }
</Box>
{ props.isChecked && <IconSvg name="check" boxSize={ 4 }/> }
</Box>
);
};
// FILTER
import type { SelectOption } from 'ui/shared/select/types';
import FilterButton from 'ui/shared/filters/FilterButton';
import Select from 'ui/shared/select/Select';
interface Props {
name: string;
options: Array<TOption>;
options: Array<SelectOption>;
hasActiveFilter: boolean;
defaultValue?: string;
isLoading?: boolean;
......@@ -63,39 +14,22 @@ interface Props {
}
const PopoverFilterRadio = ({ name, hasActiveFilter, options, isLoading, onChange, defaultValue }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure();
const { getRootProps, getRadioProps } = useRadioGroup({
name,
defaultValue,
onChange,
});
const root = getRootProps();
return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
<PopoverTrigger>
<Select
options={ options }
name={ name }
defaultValue={ defaultValue }
onChange={ onChange }
>
{ ({ isOpen, onToggle }) => (
<FilterButton
isActive={ isOpen }
onClick={ onToggle }
appliedFiltersNum={ hasActiveFilter ? 1 : 0 }
isLoading={ isLoading }
/>
</PopoverTrigger>
<PopoverContent w="fit-content" minW="150px">
<PopoverBody { ...root } py={ 2 } px={ 0 } display="flex" flexDir="column">
{ options.map((option) => {
const radio = getRadioProps({ value: option.value });
return (
<Option key={ option.value } { ...radio }>
{ option.label }
</Option>
);
}) }
</PopoverBody>
</PopoverContent>
</Popover>
) }
</Select>
);
};
......
import { PopoverTrigger, chakra, useDisclosure, useRadioGroup } from '@chakra-ui/react';
import React from 'react';
import type { SelectOption } from './types';
import Popover from 'ui/shared/chakra/Popover';
import SelectButton from './SelectButton';
import SelectContent from './SelectContent';
interface InjectedProps<Value extends string> {
isOpen: boolean;
onToggle: () => void;
value: Value;
}
export interface Props<Value extends string> {
className?: string;
isLoading?: boolean;
options: Array<SelectOption<Value>>;
name: string;
defaultValue?: Value;
onChange: (value: Value) => void;
children?: (props: InjectedProps<Value>) => React.ReactNode;
}
const Select = <Value extends string>({ className, isLoading, options, name, defaultValue, onChange, children }: Props<Value>) => {
const { isOpen, onToggle, onClose } = useDisclosure();
const handleChange = React.useCallback((value: Value) => {
onChange(value);
onClose();
}, [ onChange, onClose ]);
const { value, getRootProps, getRadioProps, setValue } = useRadioGroup({
name,
defaultValue,
onChange: handleChange,
});
React.useEffect(() => {
if (defaultValue) {
setValue(defaultValue);
}
}, [ defaultValue, setValue ]);
return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
<PopoverTrigger>
{ children?.({ isOpen, onToggle, value: value as Value }) || (
<SelectButton
className={ className }
onClick={ onToggle }
isOpen={ isOpen }
isLoading={ isLoading }
label={ options.find((option) => option.value === value)?.label || String(value) }
/>
) }
</PopoverTrigger>
<SelectContent options={ options } getRootProps={ getRootProps } getRadioProps={ getRadioProps } value={ value }/>
</Popover>
);
};
export default React.memo(chakra(Select));
import { Box, Button, Skeleton } from '@chakra-ui/react';
import React from 'react';
import IconSvg from 'ui/shared/IconSvg';
interface Props {
className?: string;
onClick: () => void;
isOpen: boolean;
isLoading?: boolean;
label: string;
}
const SelectButton = ({ className, onClick, isOpen, isLoading, label }: Props, ref: React.Ref<HTMLButtonElement>) => {
if (isLoading) {
return <Skeleton className={ className } h={ 8 } borderRadius="base" flexShrink={ 0 }/>;
}
return (
<Button
ref={ ref }
className={ className }
variant="outline"
size="sm"
colorScheme="gray"
fontWeight="500"
lineHeight={ 5 }
display="flex"
alignItems="center"
justifyContent="space-between"
columnGap={ 1 }
onClick={ onClick }
isActive={ isOpen }
pl={ 2 }
pr={ 1 }
py={ 1 }
flexShrink={ 0 }
>
<Box maxW="calc(100% - 20px)" overflow="hidden" textOverflow="ellipsis">{ label }</Box>
<IconSvg name="arrows/east-mini" boxSize={ 5 } transform={ isOpen ? 'rotate(90deg)' : 'rotate(-90deg)' } flexShrink={ 0 }/>
</Button>
);
};
export default React.forwardRef(SelectButton);
import type { useRadioGroup } from '@chakra-ui/react';
import { PopoverBody, PopoverContent } from '@chakra-ui/react';
import React from 'react';
import type { SelectOption as TSelectOption } from './types';
import SelectOption from './SelectOption';
interface Props {
options: Array<TSelectOption>;
getRootProps: ReturnType<typeof useRadioGroup>['getRootProps'];
getRadioProps: ReturnType<typeof useRadioGroup>['getRadioProps'];
value: string | number;
}
const SelectContent = ({ options, getRootProps, getRadioProps, value }: Props) => {
const root = getRootProps();
return (
<PopoverContent w="fit-content" minW="150px">
<PopoverBody { ...root } py={ 2 } px={ 0 } display="flex" flexDir="column">
{ options.map((option) => {
const radio = getRadioProps({ value: option.value });
return (
<SelectOption key={ option.value } { ...radio } isChecked={ radio.isChecked || (!option.value && !value) }>
{ option.label }
</SelectOption>
);
}) }
</PopoverBody>
</PopoverContent>
);
};
export default React.memo(SelectContent);
import {
useRadio,
Box,
useColorModeValue,
} from '@chakra-ui/react';
import type { useRadioGroup } from '@chakra-ui/react';
import type { UseRadioProps } from '@chakra-ui/react';
import { Box, useRadio, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import IconSvg from 'ui/shared/IconSvg';
import IconSvg from '../IconSvg';
export interface TOption<Sort extends string> {
id: Sort | undefined;
title: string;
interface Props extends UseRadioProps {
children: React.ReactNode;
}
type OptionProps = ReturnType<ReturnType<typeof useRadioGroup>['getRadioProps']>;
const Option = (props: OptionProps) => {
const SelectOption = (props: Props) => {
const { getInputProps, getRadioProps } = useRadio(props);
const input = getInputProps();
......@@ -35,13 +28,13 @@ const Option = (props: OptionProps) => {
bgColor: bgColorHover,
}}
>
{ props.isChecked ? <IconSvg name="check" boxSize={ 5 }/> : <Box boxSize={ 5 }/> }
<input { ...input }/>
<Box { ...checkbox }>
{ props.children }
</Box>
{ props.isChecked && <IconSvg name="check" boxSize={ 4 } color="blue.600"/> }
</Box>
);
};
export default Option;
export default React.memo(SelectOption);
export interface SelectOption<Value extends string = string> {
value: Value | undefined;
label: string;
}
import {
PopoverTrigger,
PopoverContent,
PopoverBody,
useDisclosure,
useRadioGroup,
chakra,
} from '@chakra-ui/react';
import { chakra } from '@chakra-ui/react';
import React from 'react';
import type { SelectOption } from 'ui/shared/select/types';
import useIsMobile from 'lib/hooks/useIsMobile';
import Popover from 'ui/shared/chakra/Popover';
import Select, { type Props as SelectProps } from 'ui/shared/select/Select';
import SortButtonDesktop from './ButtonDesktop';
import SortButtonMobile from './ButtonMobile';
import Option from './Option';
import type { TOption } from './Option';
interface Props<Sort extends string> {
name: string;
options: Array<TOption<Sort>>;
defaultValue?: Sort;
isLoading?: boolean;
onChange: (value: Sort | undefined) => void;
}
type Props<Value extends string> = Omit<SelectProps<Value>, 'children'>;
const Sort = <Sort extends string>({ name, options, isLoading, onChange, defaultValue }: Props<Sort>) => {
const isMobile = useIsMobile(false);
const { isOpen, onToggle, onClose } = useDisclosure();
const handleChange = (value: Sort) => {
onChange(value);
onClose();
};
const { value, getRootProps, getRadioProps } = useRadioGroup({
name,
defaultValue,
onChange: handleChange,
});
const root = getRootProps();
return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
<PopoverTrigger>
{ isMobile ? (
<SortButtonMobile isActive={ isOpen || Boolean(value) } onClick={ onToggle } isLoading={ isLoading }/>
) : (
<SortButtonDesktop isActive={ isOpen } isLoading={ isLoading } onClick={ onToggle }>
{ options.find((option: TOption<Sort>) => option.id === value || (!option.id && !value))?.title }
</SortButtonDesktop>
) }
</PopoverTrigger>
<PopoverContent w="fit-content" minW="165px">
<PopoverBody { ...root } py={ 2 } px={ 0 } display="flex" flexDir="column">
{ options.map((option, index) => {
const radio = getRadioProps({ value: option.id });
return (
<Option key={ index } { ...radio } isChecked={ radio.isChecked || (!option.id && !value) }>
{ option.title }
</Option>
);
}) }
</PopoverBody>
</PopoverContent>
</Popover>
<Select
options={ options }
name={ name }
defaultValue={ defaultValue }
onChange={ onChange }
>
{ ({ isOpen, onToggle, value }) => {
return (
isMobile ? (
<SortButtonMobile isActive={ isOpen || Boolean(value) } onClick={ onToggle } isLoading={ isLoading }/>
) : (
<SortButtonDesktop isActive={ isOpen } isLoading={ isLoading } onClick={ onToggle }>
{ options.find((option: SelectOption<Sort>) => option.value === value || (!option.value && !value))?.label }
</SortButtonDesktop>
)
);
} }
</Select>
);
};
......
import type { Query } from 'nextjs-routes';
import type { SelectOption } from 'ui/shared/select/types';
import type { TOption } from 'ui/shared/sort/Option';
import type { Query } from 'nextjs-routes';
export default function getSortValueFromQuery<SortValue extends string>(query: Query, sortOptions: Array<TOption<SortValue>>) {
export default function getSortValueFromQuery<SortValue extends string>(query: Query, sortOptions: Array<SelectOption<SortValue>>) {
if (!query.sort || !query.order) {
return undefined;
}
const str = query.sort + '-' + query.order;
if (sortOptions.map(option => option.id).includes(str as SortValue)) {
if (sortOptions.map(option => option.value).includes(str as SortValue)) {
return str as SortValue;
}
}
import { Box, Button, MenuButton, MenuItemOption, MenuList, MenuOptionGroup, chakra } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import Menu from 'ui/shared/chakra/Menu';
import IconSvg from 'ui/shared/IconSvg';
type Props<T extends string> = {
items: ReadonlyArray<{ id: T; title: string }>;
selectedId: T;
onSelect: (id: T) => void;
};
export function StatsDropdownMenu<T extends string>({ items, selectedId, onSelect }: Props<T>) {
const selectedCategory = items.find(category => category.id === selectedId);
const handleSelection = useCallback((id: string | Array<string>) => {
const selectedId = Array.isArray(id) ? id[0] : id;
onSelect(selectedId as T);
}, [ onSelect ]);
return (
<Menu
>
<MenuButton
as={ Button }
size="sm"
variant="outline"
colorScheme="gray"
w="100%"
>
<Box
as="span"
display="flex"
alignItems="center"
>
<chakra.span
whiteSpace="nowrap"
overflow="hidden"
textOverflow="ellipsis"
>
{ selectedCategory?.title }
</chakra.span>
<IconSvg transform="rotate(-90deg)" ml="auto" name="arrows/east-mini" w={ 5 } h={ 5 }/>
</Box>
</MenuButton>
<MenuList zIndex={ 3 }>
<MenuOptionGroup
value={ selectedId }
type="radio"
onChange={ handleSelection }
>
{ items.map((item) => (
<MenuItemOption
key={ item.id }
value={ item.id }
>
{ item.title }
</MenuItemOption>
)) }
</MenuOptionGroup>
</MenuList>
</Menu>
);
}
export default StatsDropdownMenu;
import { Grid, GridItem, Skeleton } from '@chakra-ui/react';
import { Grid, GridItem } from '@chakra-ui/react';
import React from 'react';
import type * as stats from '@blockscout/stats-types';
......@@ -6,8 +6,7 @@ import type { StatsIntervalIds } from 'types/client/stats';
import ChartIntervalSelect from 'ui/shared/chart/ChartIntervalSelect';
import FilterInput from 'ui/shared/filters/FilterInput';
import StatsDropdownMenu from './StatsDropdownMenu';
import Select from 'ui/shared/select/Select';
type Props = {
sections?: Array<stats.LineChartSection>;
......@@ -30,10 +29,13 @@ const StatsFilters = ({
isLoading,
initialFilterValue,
}: Props) => {
const sectionsList = [ {
id: 'all',
title: 'All stats',
}, ... (sections || []) ];
const options = React.useMemo(() => {
return [
{ value: 'all', label: 'All stats' },
...(sections || []).map((section) => ({ value: section.id, label: section.title })),
];
}, [ sections ]);
return (
<Grid
......@@ -50,13 +52,14 @@ const StatsFilters = ({
w={{ base: '100%', lg: 'auto' }}
area="section"
>
{ isLoading ? <Skeleton w={{ base: '100%', lg: '103px' }} h="32px" borderRadius="base"/> : (
<StatsDropdownMenu
items={ sectionsList }
selectedId={ currentSection }
onSelect={ onSectionChange }
/>
) }
<Select
options={ options }
defaultValue={ currentSection }
onChange={ onSectionChange }
isLoading={ isLoading }
w={{ base: '100%', lg: '136px' }}
fontWeight={ 600 }
/>
</GridItem>
<GridItem
......
......@@ -23,6 +23,7 @@ test('base view +@mobile', async({ render }) => {
test('raw view', async({ render }) => {
const component = await render(<TokenInstanceMetadata data={ tokenInstanceMock.withRichMetadata.metadata }/>);
await component.locator('select').selectOption('JSON');
await component.getByRole('button', { name: 'Table' }).click();
await component.getByText('JSON').click();
await expect(component).toHaveScreenshot();
});
import { Alert, Box, Flex, Select, chakra } from '@chakra-ui/react';
import { Alert, Box, Flex, chakra } from '@chakra-ui/react';
import React from 'react';
import type { TokenInstance } from 'types/api/token';
......@@ -6,11 +6,17 @@ import type { TokenInstance } from 'types/api/token';
import ContentLoader from 'ui/shared/ContentLoader';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import RawDataSnippet from 'ui/shared/RawDataSnippet';
import Select from 'ui/shared/select/Select';
import { useMetadataUpdateContext } from './contexts/metadataUpdate';
import MetadataAccordion from './metadata/MetadataAccordion';
type Format = 'JSON' | 'Table';
const OPTIONS = [
{ label: 'Table', value: 'Table' as const },
{ label: 'JSON', value: 'JSON' as const },
];
type Format = (typeof OPTIONS)[number]['value'];
interface Props {
data: TokenInstance['metadata'] | undefined;
......@@ -22,10 +28,6 @@ const TokenInstanceMetadata = ({ data, isPlaceholderData }: Props) => {
const { status: refetchStatus } = useMetadataUpdateContext() || {};
const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
setFormat(event.target.value as Format);
}, []);
if (isPlaceholderData || refetchStatus === 'WAITING_FOR_RESPONSE') {
return <ContentLoader/>;
}
......@@ -48,10 +50,14 @@ const TokenInstanceMetadata = ({ data, isPlaceholderData }: Props) => {
) }
<Flex alignItems="center" mb={ 6 }>
<chakra.span fontWeight={ 500 }>Metadata</chakra.span>
<Select size="xs" borderRadius="base" value={ format } onChange={ handleSelectChange } w="auto" ml={ 5 }>
<option value="Table">Table</option>
<option value="JSON">JSON</option>
</Select>
<Select
options={ OPTIONS }
name="metadata-format"
defaultValue="Table"
onChange={ setFormat }
w="85px"
ml={ 5 }
/>
{ format === 'JSON' && <CopyToClipboard text={ JSON.stringify(data) } ml="auto"/> }
</Flex>
{ content }
......
import type { TokenType } from 'types/api/token';
import type { TokensSortingValue } from 'types/api/tokens';
import type { SelectOption } from 'ui/shared/select/types';
import config from 'configs/app';
import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery';
import { TOKEN_TYPE_IDS } from 'lib/token/tokenTypes';
import type { TOption } from 'ui/shared/sort/Option';
export const SORT_OPTIONS: Array<TOption<TokensSortingValue>> = [
{ title: 'Default', id: undefined },
{ title: 'Price ascending', id: 'fiat_value-asc' },
{ title: 'Price descending', id: 'fiat_value-desc' },
{ title: 'Holders ascending', id: 'holder_count-asc' },
{ title: 'Holders descending', id: 'holder_count-desc' },
{ title: 'On-chain market cap ascending', id: 'circulating_market_cap-asc' },
{ title: 'On-chain market cap descending', id: 'circulating_market_cap-desc' },
export const SORT_OPTIONS: Array<SelectOption<TokensSortingValue>> = [
{ label: 'Default', value: undefined },
{ label: 'Price ascending', value: 'fiat_value-asc' },
{ label: 'Price descending', value: 'fiat_value-desc' },
{ label: 'Holders ascending', value: 'holder_count-asc' },
{ label: 'Holders descending', value: 'holder_count-desc' },
{ label: 'On-chain market cap ascending', value: 'circulating_market_cap-asc' },
{ label: 'On-chain market cap descending', value: 'circulating_market_cap-desc' },
];
export const getTokenFilterValue = (getFilterValuesFromQuery<TokenType>).bind(null, TOKEN_TYPE_IDS);
......
......@@ -2,19 +2,19 @@ import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
import type { TransactionsSortingValue, TxsResponse } from 'types/api/transaction';
import type { SelectOption } from 'ui/shared/select/types';
import type { ResourceError } from 'lib/api/resources';
import * as cookies from 'lib/cookies';
import type { TOption } from 'ui/shared/sort/Option';
import sortTxs from './sortTxs';
export const SORT_OPTIONS: Array<TOption<TransactionsSortingValue>> = [
{ title: 'Default', id: undefined },
{ title: 'Value ascending', id: 'value-asc' },
{ title: 'Value descending', id: 'value-desc' },
{ title: 'Fee ascending', id: 'fee-asc' },
{ title: 'Fee descending', id: 'fee-desc' },
export const SORT_OPTIONS: Array<SelectOption<TransactionsSortingValue>> = [
{ label: 'Default', value: undefined },
{ label: 'Value ascending', value: 'value-asc' },
{ label: 'Value descending', value: 'value-desc' },
{ label: 'Fee ascending', value: 'fee-asc' },
{ label: 'Fee descending', value: 'fee-desc' },
];
type SortingValue = TransactionsSortingValue | undefined;
......
......@@ -2,13 +2,12 @@ import type {
ValidatorsBlackfortSortingValue,
ValidatorsBlackfortSortingField,
} from 'types/api/validators';
import type { SelectOption } from 'ui/shared/select/types';
import type { TOption } from 'ui/shared/sort/Option';
export const VALIDATORS_BLACKFORT_SORT_OPTIONS: Array<TOption<ValidatorsBlackfortSortingValue>> = [
{ title: 'Default', id: undefined },
{ title: 'Address descending', id: 'address_hash-desc' },
{ title: 'Address ascending', id: 'address_hash-asc' },
export const VALIDATORS_BLACKFORT_SORT_OPTIONS: Array<SelectOption<ValidatorsBlackfortSortingValue>> = [
{ label: 'Default', value: undefined },
{ label: 'Address descending', value: 'address_hash-desc' },
{ label: 'Address ascending', value: 'address_hash-asc' },
];
export const VALIDATORS_BLACKFORT_SORT_SEQUENCE: Record<ValidatorsBlackfortSortingField, Array<ValidatorsBlackfortSortingValue | undefined>> = {
......
......@@ -2,15 +2,14 @@ import type {
ValidatorsStabilitySortingValue,
ValidatorsStabilitySortingField,
} from 'types/api/validators';
import type { SelectOption } from 'ui/shared/select/types';
import type { TOption } from 'ui/shared/sort/Option';
export const VALIDATORS_STABILITY_SORT_OPTIONS: Array<TOption<ValidatorsStabilitySortingValue>> = [
{ title: 'Default', id: undefined },
{ title: 'Status descending', id: 'state-desc' },
{ title: 'Status ascending', id: 'state-asc' },
{ title: 'Blocks validated descending', id: 'blocks_validated-desc' },
{ title: 'Blocks validated ascending', id: 'blocks_validated-asc' },
export const VALIDATORS_STABILITY_SORT_OPTIONS: Array<SelectOption<ValidatorsStabilitySortingValue>> = [
{ label: 'Default', value: undefined },
{ label: 'Status descending', value: 'state-desc' },
{ label: 'Status ascending', value: 'state-asc' },
{ label: 'Blocks validated descending', value: 'blocks_validated-desc' },
{ label: 'Blocks validated ascending', value: 'blocks_validated-asc' },
];
export const VALIDATORS_STABILITY_SORT_SEQUENCE: Record<ValidatorsStabilitySortingField, Array<ValidatorsStabilitySortingValue | undefined>> = {
......
import type { VerifiedContractsSortingValue, VerifiedContractsSortingField } from 'types/api/verifiedContracts';
import type { SelectOption } from 'ui/shared/select/types';
import type { TOption } from 'ui/shared/sort/Option';
export const SORT_OPTIONS: Array<TOption<VerifiedContractsSortingValue>> = [
{ title: 'Default', id: undefined },
{ title: 'Balance descending', id: 'balance-desc' },
{ title: 'Balance ascending', id: 'balance-asc' },
{ title: 'Txs count descending', id: 'txs_count-desc' },
{ title: 'Txs count ascending', id: 'txs_count-asc' },
export const SORT_OPTIONS: Array<SelectOption<VerifiedContractsSortingValue>> = [
{ label: 'Default', value: undefined },
{ label: 'Balance descending', value: 'balance-desc' },
{ label: 'Balance ascending', value: 'balance-asc' },
{ label: 'Txs count descending', value: 'txs_count-desc' },
{ label: 'Txs count ascending', value: 'txs_count-asc' },
];
export const SORT_SEQUENCE: Record<VerifiedContractsSortingField, Array<VerifiedContractsSortingValue | undefined>> = {
......
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