Commit 7025b758 authored by tom's avatar tom

advanced filters page

parent 7e97c811
...@@ -37,6 +37,7 @@ const RESTRICTED_MODULES = { ...@@ -37,6 +37,7 @@ const RESTRICTED_MODULES = {
'Tag', 'Switch', 'Image', 'Popover', 'PopoverTrigger', 'PopoverContent', 'PopoverBody', 'PopoverFooter', 'Tag', 'Switch', 'Image', 'Popover', 'PopoverTrigger', 'PopoverContent', 'PopoverBody', 'PopoverFooter',
'DrawerRoot', 'DrawerBody', 'DrawerContent', 'DrawerOverlay', 'DrawerBackdrop', 'DrawerTrigger', 'Drawer', 'DrawerRoot', 'DrawerBody', 'DrawerContent', 'DrawerOverlay', 'DrawerBackdrop', 'DrawerTrigger', 'Drawer',
'Alert', 'AlertIcon', 'AlertTitle', 'AlertDescription', 'Alert', 'AlertIcon', 'AlertTitle', 'AlertDescription',
'Select', 'SelectRoot', 'SelectControl', 'SelectContent', 'SelectItem', 'SelectValueText',
'Heading', 'Badge', 'Tabs', 'Show', 'Hide', 'Checkbox', 'CheckboxGroup', 'Heading', 'Badge', 'Tabs', 'Show', 'Hide', 'Checkbox', 'CheckboxGroup',
'Table', 'TableRoot', 'TableBody', 'TableHeader', 'TableRow', 'TableCell', 'Table', 'TableRoot', 'TableBody', 'TableHeader', 'TableRow', 'TableCell',
'Menu', 'MenuRoot', 'MenuTrigger', 'MenuContent', 'MenuItem', 'MenuTriggerItem', 'MenuRadioItemGroup', 'MenuContextTrigger', 'Menu', 'MenuRoot', 'MenuTrigger', 'MenuContent', 'MenuItem', 'MenuTriggerItem', 'MenuRadioItemGroup', 'MenuContextTrigger',
......
...@@ -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 AdvancedFilter from 'ui/pages/AdvancedFilter'; import AdvancedFilter from 'ui/pages/AdvancedFilter';
const Page: NextPage = () => { const Page: NextPage = () => {
return ( return (
<PageNextJs pathname="/advanced-filter"> <PageNextJs pathname="/advanced-filter">
{ /* <AdvancedFilter/> */ } <AdvancedFilter/>
</PageNextJs> </PageNextJs>
); );
}; };
......
...@@ -9,18 +9,18 @@ import config from 'configs/app'; ...@@ -9,18 +9,18 @@ import config from 'configs/app';
const validatorsFeature = config.features.validators; const validatorsFeature = config.features.validators;
// const ValidatorDetails = dynamic(() => { const ValidatorDetails = dynamic(() => {
// if (validatorsFeature.isEnabled && validatorsFeature.chainType === 'zilliqa') { if (validatorsFeature.isEnabled && validatorsFeature.chainType === 'zilliqa') {
// return import('ui/pages/ValidatorZilliqa'); return import('ui/pages/ValidatorZilliqa');
// } }
// throw new Error('Validators feature is not enabled.'); throw new Error('Validators feature is not enabled.');
// }, { ssr: false }); }, { ssr: false });
const Page: NextPage<Props> = (props) => { const Page: NextPage<Props> = (props) => {
return ( return (
<PageNextJs pathname="/validators/[id]" query={ props.query }> <PageNextJs pathname="/validators/[id]" query={ props.query }>
{ /* <ValidatorDetails/> */ } <ValidatorDetails/>
</PageNextJs> </PageNextJs>
); );
}; };
......
...@@ -31,7 +31,7 @@ const semanticTokens = { ...@@ -31,7 +31,7 @@ const semanticTokens = {
'default': 'red.500', 'default': 'red.500',
_dark: 'red.500', _dark: 'red.500',
}, },
dialog.bg: { dialog_bg: {
'default': 'white', 'default': 'white',
_dark: 'gray.900', _dark: 'gray.900',
}, },
......
import type { Checkbox as ArkCheckbox } from '@ark-ui/react/checkbox'; import type { Checkbox as ArkCheckbox } from '@ark-ui/react/checkbox';
import type { HTMLChakraProps } from '@chakra-ui/react';
import { Checkbox as ChakraCheckbox, CheckboxGroup as ChakraCheckboxGroup } from '@chakra-ui/react'; import { Checkbox as ChakraCheckbox, CheckboxGroup as ChakraCheckboxGroup } from '@chakra-ui/react';
import * as React from 'react'; import * as React from 'react';
...@@ -25,7 +26,7 @@ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>( ...@@ -25,7 +26,7 @@ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
}, },
); );
export interface CheckboxGroupProps extends ArkCheckbox.GroupProps { export interface CheckboxGroupProps extends HTMLChakraProps<'div', ArkCheckbox.GroupProps> {
orientation?: 'vertical' | 'horizontal'; orientation?: 'vertical' | 'horizontal';
} }
......
'use client'; 'use client';
import type { CollectionItem } from '@chakra-ui/react'; import type { CollectionItem, ListCollection } from '@chakra-ui/react';
import { Select as ChakraSelect, Portal, useSelectContext } from '@chakra-ui/react'; import { Select as ChakraSelect, Portal, useSelectContext } from '@chakra-ui/react';
import * as React from 'react'; import * as React from 'react';
...@@ -173,3 +173,33 @@ export const SelectItemGroup = React.forwardRef< ...@@ -173,3 +173,33 @@ export const SelectItemGroup = React.forwardRef<
export const SelectLabel = ChakraSelect.Label; export const SelectLabel = ChakraSelect.Label;
export const SelectItemText = ChakraSelect.ItemText; export const SelectItemText = ChakraSelect.ItemText;
export interface SelectProps extends SelectRootProps {
collection: ListCollection<CollectionItem>;
placeholder: string;
portalled?: boolean;
}
// TODO @tom2drum refactor selects
export const Select = React.forwardRef<HTMLDivElement, SelectProps>((props, ref) => {
const { collection, placeholder, portalled = true, ...rest } = props;
return (
<SelectRoot
ref={ ref }
collection={ collection }
variant="outline"
{ ...rest }
>
<SelectControl>
<SelectValueText placeholder={ placeholder }/>
</SelectControl>
<SelectContent portalled={ portalled }>
{ collection.items.map((item) => (
<SelectItem item={ item } key={ item.value }>
{ item.label }
</SelectItem>
)) }
</SelectContent>
</SelectRoot>
);
});
import type { TokenDefinition } from '@chakra-ui/react/dist/types/styled-system/types'; import type { ThemingConfig } from '@chakra-ui/react';
export const radii: TokenDefinition['radii'] = { import type { ExcludeUndefined } from 'types/utils';
export const radii: ExcludeUndefined<ThemingConfig['tokens']>['radii'] = {
none: { value: '0' }, none: { value: '0' },
sm: { value: '4px' }, sm: { value: '4px' },
base: { value: '8px' }, base: { value: '8px' },
......
import type { TokenDefinition } from '@chakra-ui/react/dist/types/styled-system/types'; import type { ThemingConfig } from '@chakra-ui/react';
export const zIndex: TokenDefinition['zIndex'] = { import type { ExcludeUndefined } from 'types/utils';
export const zIndex: ExcludeUndefined<ThemingConfig['tokens']>['zIndex'] = {
hide: { value: -1 }, hide: { value: -1 },
auto: { value: 'auto' }, auto: { value: 'auto' },
base: { value: 0 }, base: { value: 0 },
......
...@@ -26,6 +26,7 @@ export const recipe = defineSlotRecipe({ ...@@ -26,6 +26,7 @@ export const recipe = defineSlotRecipe({
label: { label: {
fontWeight: 'normal', fontWeight: 'normal',
userSelect: 'none', userSelect: 'none',
flexGrow: 1,
_disabled: { _disabled: {
opacity: 'control.disabled', opacity: 'control.disabled',
}, },
......
import {
Button,
Grid,
PopoverTrigger,
PopoverContent,
PopoverBody,
useDisclosure,
Checkbox,
} from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { ChangeEvent } from 'react';
import { Button } from 'toolkit/chakra/button';
import { Checkbox, CheckboxGroup } from 'toolkit/chakra/checkbox';
import { PopoverBody, PopoverContent, PopoverRoot, PopoverTrigger } from 'toolkit/chakra/popover';
import type { ColumnsIds } from 'ui/advancedFilter/constants'; import type { ColumnsIds } from 'ui/advancedFilter/constants';
import { TABLE_COLUMNS } from 'ui/advancedFilter/constants'; import { TABLE_COLUMNS } from 'ui/advancedFilter/constants';
import Popover from 'ui/shared/chakra/Popover';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
interface Props { interface Props {
...@@ -21,46 +13,47 @@ interface Props { ...@@ -21,46 +13,47 @@ interface Props {
} }
const ColumnsButton = ({ columns, onChange }: Props) => { const ColumnsButton = ({ columns, onChange }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure(); const handleValueChange = React.useCallback((value: Array<string>) => {
const newCols = value.reduce((acc, key) => {
const onCheckboxClick = React.useCallback((event: ChangeEvent<HTMLInputElement>) => { acc[key as ColumnsIds] = true;
const newCols = { ...columns }; return acc;
const id = event.target.id as ColumnsIds; }, {} as Record<ColumnsIds, boolean>);
newCols[id] = event.target.checked;
onChange(newCols); onChange(newCols);
}, [ onChange, columns ]); }, [ onChange ]);
return ( return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy> <PopoverRoot>
<PopoverTrigger> <PopoverTrigger>
<Button <Button
onClick={ onToggle } variant="dropdown"
variant="outline"
colorScheme="gray"
size="sm" size="sm"
leftIcon={ <IconSvg name="columns" boxSize={ 5 } color="inherit"/> }
> >
<IconSvg name="columns" boxSize={ 5 } color="inherit"/>
Columns Columns
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent> <PopoverContent>
<PopoverBody px={ 4 } py={ 6 } display="flex" flexDir="column" rowGap={ 5 }> <PopoverBody px={ 4 } py={ 6 } display="flex" flexDir="column" rowGap={ 5 }>
<Grid gridTemplateColumns="160px 160px" gap={ 3 }> <CheckboxGroup
defaultValue={ Object.keys(columns).filter((key) => columns[key as ColumnsIds]) }
onValueChange={ handleValueChange }
display="grid"
gridTemplateColumns="160px 160px"
gap={ 3 }
>
{ TABLE_COLUMNS.map(col => ( { TABLE_COLUMNS.map(col => (
<Checkbox <Checkbox
key={ col.id } key={ col.id }
defaultChecked={ columns[col.id] } value={ col.id }
onChange={ onCheckboxClick } size="md"
id={ col.id }
size="lg"
> >
{ col.id === 'or_and' ? 'And/Or' : col.name } { col.id === 'or_and' ? 'And/Or' : col.name }
</Checkbox> </Checkbox>
)) } )) }
</Grid> </CheckboxGroup>
</PopoverBody> </PopoverBody>
</PopoverContent> </PopoverContent>
</Popover> </PopoverRoot>
); );
}; };
......
import { Button } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { AdvancedFilterParams } from 'types/api/advancedFilter'; import type { AdvancedFilterParams } from 'types/api/advancedFilter';
...@@ -7,7 +6,8 @@ import config from 'configs/app'; ...@@ -7,7 +6,8 @@ import config from 'configs/app';
import buildUrl from 'lib/api/buildUrl'; import buildUrl from 'lib/api/buildUrl';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import downloadBlob from 'lib/downloadBlob'; import downloadBlob from 'lib/downloadBlob';
import useToast from 'lib/hooks/useToast'; import { Button } from 'toolkit/chakra/button';
import { toaster } from 'toolkit/chakra/toaster';
import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha'; import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha';
import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha'; import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha';
...@@ -17,7 +17,6 @@ type Props = { ...@@ -17,7 +17,6 @@ type Props = {
const ExportCSV = ({ filters }: Props) => { const ExportCSV = ({ filters }: Props) => {
const recaptcha = useReCaptcha(); const recaptcha = useReCaptcha();
const toast = useToast();
const [ isLoading, setIsLoading ] = React.useState(false); const [ isLoading, setIsLoading ] = React.useState(false);
const handleExportCSV = React.useCallback(async() => { const handleExportCSV = React.useCallback(async() => {
...@@ -49,18 +48,14 @@ const ExportCSV = ({ filters }: Props) => { ...@@ -49,18 +48,14 @@ const ExportCSV = ({ filters }: Props) => {
downloadBlob(blob, fileName); downloadBlob(blob, fileName);
} catch (error) { } catch (error) {
toast({ toaster.error({
position: 'top-right',
title: 'Error', title: 'Error',
description: (error as Error)?.message || 'Something went wrong. Try again later.', description: (error as Error)?.message || 'Something went wrong. Try again later.',
status: 'error',
variant: 'subtle',
isClosable: true,
}); });
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
}, [ toast, filters, recaptcha ]); }, [ filters, recaptcha ]);
if (!config.services.reCaptchaV2.siteKey) { if (!config.services.reCaptchaV2.siteKey) {
return null; return null;
...@@ -71,7 +66,7 @@ const ExportCSV = ({ filters }: Props) => { ...@@ -71,7 +66,7 @@ const ExportCSV = ({ filters }: Props) => {
<Button <Button
onClick={ handleExportCSV } onClick={ handleExportCSV }
variant="outline" variant="outline"
isLoading={ isLoading } loading={ isLoading }
size="sm" size="sm"
mr={ 3 } mr={ 3 }
> >
......
...@@ -34,7 +34,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc ...@@ -34,7 +34,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc
<TableColumnFilterWrapper <TableColumnFilterWrapper
columnName="Type" columnName="Type"
isLoading={ isLoading } isLoading={ isLoading }
isActive={ Boolean(value && value.length) } selected={ Boolean(value && value.length) }
> >
<TypeFilter { ...commonProps } value={ value }/> <TypeFilter { ...commonProps } value={ value }/>
</TableColumnFilterWrapper> </TableColumnFilterWrapper>
...@@ -46,7 +46,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc ...@@ -46,7 +46,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc
<TableColumnFilterWrapper <TableColumnFilterWrapper
columnName="Method" columnName="Method"
isLoading={ isLoading } isLoading={ isLoading }
isActive={ Boolean(value && value.length) } selected={ Boolean(value && value.length) }
w="350px" w="350px"
> >
<MethodFilter { ...commonProps } value={ value }/> <MethodFilter { ...commonProps } value={ value }/>
...@@ -59,7 +59,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc ...@@ -59,7 +59,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc
<TableColumnFilterWrapper <TableColumnFilterWrapper
columnName="Age" columnName="Age"
isLoading={ isLoading } isLoading={ isLoading }
isActive={ Boolean(value.from || value.to || value.age) } selected={ Boolean(value.from || value.to || value.age) }
w="382px" w="382px"
> >
<AgeFilter { ...commonProps } value={ value }/> <AgeFilter { ...commonProps } value={ value }/>
...@@ -71,7 +71,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc ...@@ -71,7 +71,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc
<TableColumnFilterWrapper <TableColumnFilterWrapper
columnName="And/Or" columnName="And/Or"
isLoading={ isLoading } isLoading={ isLoading }
isActive={ false } selected={ false }
w="106px" w="106px"
value={ filters.address_relation === 'and' ? 'AND' : 'OR' } value={ filters.address_relation === 'and' ? 'AND' : 'OR' }
> >
...@@ -88,7 +88,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc ...@@ -88,7 +88,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc
<TableColumnFilterWrapper <TableColumnFilterWrapper
columnName="Address from" columnName="Address from"
isLoading={ isLoading } isLoading={ isLoading }
isActive={ Boolean(value.length) } selected={ Boolean(value.length) }
w="480px" w="480px"
> >
<AddressFilter { ...commonProps } type="from" value={ value }/> <AddressFilter { ...commonProps } type="from" value={ value }/>
...@@ -105,7 +105,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc ...@@ -105,7 +105,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc
<TableColumnFilterWrapper <TableColumnFilterWrapper
columnName="Address to" columnName="Address to"
isLoading={ isLoading } isLoading={ isLoading }
isActive={ Boolean(value.length) } selected={ Boolean(value.length) }
w="480px" w="480px"
> >
<AddressFilter { ...commonProps } type="to" value={ value }/> <AddressFilter { ...commonProps } type="to" value={ value }/>
...@@ -118,7 +118,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc ...@@ -118,7 +118,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc
<TableColumnFilterWrapper <TableColumnFilterWrapper
columnName="Amount" columnName="Amount"
isLoading={ isLoading } isLoading={ isLoading }
isActive={ Boolean(value.from || value.to) } selected={ Boolean(value.from || value.to) }
w="382px" w="382px"
> >
<AmountFilter { ...commonProps } value={ value }/> <AmountFilter { ...commonProps } value={ value }/>
...@@ -145,7 +145,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc ...@@ -145,7 +145,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc
<TableColumnFilterWrapper <TableColumnFilterWrapper
columnName="Asset" columnName="Asset"
isLoading={ isLoading } isLoading={ isLoading }
isActive={ Boolean(value.length) } selected={ Boolean(value.length) }
w="382px" w="382px"
> >
<AssetFilter { ...commonProps } value={ value }/> <AssetFilter { ...commonProps } value={ value }/>
......
...@@ -5,10 +5,10 @@ import type { AdvancedFilterResponseItem } from 'types/api/advancedFilter'; ...@@ -5,10 +5,10 @@ import type { AdvancedFilterResponseItem } from 'types/api/advancedFilter';
import config from 'configs/app'; import config from 'configs/app';
import getCurrencyValue from 'lib/getCurrencyValue'; import getCurrencyValue from 'lib/getCurrencyValue';
import { Badge } from 'toolkit/chakra/badge';
import { Skeleton } from 'toolkit/chakra/skeleton';
import type { ColumnsIds } from 'ui/advancedFilter/constants'; import type { ColumnsIds } from 'ui/advancedFilter/constants';
import AddressFromToIcon from 'ui/shared/address/AddressFromToIcon'; import AddressFromToIcon from 'ui/shared/address/AddressFromToIcon';
import Skeleton from 'ui/shared/chakra/Skeleton';
import Tag from 'ui/shared/chakra/Tag';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity';
...@@ -31,10 +31,10 @@ const ItemByColumn = ({ item, column, isLoading }: Props) => { ...@@ -31,10 +31,10 @@ const ItemByColumn = ({ item, column, isLoading }: Props) => {
if (!type) { if (!type) {
return null; return null;
} }
return <Tag isLoading={ isLoading }>{ type.name }</Tag>; return <Badge loading={ isLoading }>{ type.name }</Badge>;
} }
case 'method': case 'method':
return item.method ? <Tag isLoading={ isLoading } isTruncated colorScheme="gray">{ item.method }</Tag> : null; return item.method ? <Badge loading={ isLoading } truncated>{ item.method }</Badge> : null;
case 'age': case 'age':
return <TimeAgoWithTooltip timestamp={ item.timestamp } isLoading={ isLoading } color="text_secondary" fontWeight={ 400 }/>; return <TimeAgoWithTooltip timestamp={ item.timestamp } isLoading={ isLoading } color="text_secondary" fontWeight={ 400 }/>;
case 'from': case 'from':
...@@ -63,18 +63,18 @@ const ItemByColumn = ({ item, column, isLoading }: Props) => { ...@@ -63,18 +63,18 @@ const ItemByColumn = ({ item, column, isLoading }: Props) => {
); );
case 'amount': { case 'amount': {
if (item.token?.type === 'ERC-721') { if (item.token?.type === 'ERC-721') {
return <Skeleton isLoaded={ !isLoading }>1</Skeleton>; return <Skeleton loading={ isLoading }>1</Skeleton>;
} }
if (item.total) { if (item.total) {
return ( return (
<Skeleton isLoaded={ !isLoading }> <Skeleton loading={ isLoading }>
{ getCurrencyValue({ value: item.total?.value, decimals: item.total.decimals, accuracy: 8 }).valueStr } { getCurrencyValue({ value: item.total?.value, decimals: item.total.decimals, accuracy: 8 }).valueStr }
</Skeleton> </Skeleton>
); );
} }
if (item.value) { if (item.value) {
return ( return (
<Skeleton isLoaded={ !isLoading }> <Skeleton loading={ isLoading }>
{ getCurrencyValue({ value: item.value, decimals: config.chain.currency.decimals.toString(), accuracy: 8 }).valueStr } { getCurrencyValue({ value: item.value, decimals: config.chain.currency.decimals.toString(), accuracy: 8 }).valueStr }
</Skeleton> </Skeleton>
); );
...@@ -84,9 +84,9 @@ const ItemByColumn = ({ item, column, isLoading }: Props) => { ...@@ -84,9 +84,9 @@ const ItemByColumn = ({ item, column, isLoading }: Props) => {
case 'asset': case 'asset':
return item.token ? return item.token ?
<TokenEntity token={ item.token } isLoading={ isLoading } fontWeight={ 700 } onlySymbol noCopy/> : <TokenEntity token={ item.token } isLoading={ isLoading } fontWeight={ 700 } onlySymbol noCopy/> :
<Skeleton isLoaded={ !isLoading } fontWeight={ 700 }>{ config.chain.currency.symbol }</Skeleton>; <Skeleton loading={ isLoading } fontWeight={ 700 }>{ config.chain.currency.symbol }</Skeleton>;
case 'fee': case 'fee':
return <Skeleton isLoaded={ !isLoading }>{ item.fee ? getCurrencyValue({ value: item.fee, accuracy: 8 }).valueStr : '-' }</Skeleton>; return <Skeleton loading={ isLoading }>{ item.fee ? getCurrencyValue({ value: item.fee, accuracy: 8 }).valueStr : '-' }</Skeleton>;
default: default:
return null; return null;
} }
......
...@@ -40,7 +40,7 @@ export const TABLE_COLUMNS: Array<TxTableColumn> = [ ...@@ -40,7 +40,7 @@ export const TABLE_COLUMNS: Array<TxTableColumn> = [
{ {
id: 'or_and', id: 'or_and',
name: '', name: '',
width: '60px', width: '65px',
}, },
{ {
id: 'to', id: 'to',
......
import { Flex, Select, Input, InputGroup, InputRightElement, VStack, IconButton } from '@chakra-ui/react'; import { createListCollection, Flex, VStack } from '@chakra-ui/react';
import { isEqual } from 'es-toolkit'; import { isEqual } from 'es-toolkit';
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import React from 'react'; import React from 'react';
import type { AdvancedFilterParams } from 'types/api/advancedFilter'; import type { AdvancedFilterParams } from 'types/api/advancedFilter';
import { IconButton } from 'toolkit/chakra/icon-button';
import { Input } from 'toolkit/chakra/input';
import { InputGroup } from 'toolkit/chakra/input-group';
import { Select } from 'toolkit/chakra/select';
import ClearButton from 'ui/shared/ClearButton'; import ClearButton from 'ui/shared/ClearButton';
import TableColumnFilter from 'ui/shared/filters/TableColumnFilter'; import TableColumnFilter from 'ui/shared/filters/TableColumnFilter';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
...@@ -16,6 +20,13 @@ const FILTER_PARAM_FROM_EXCLUDE = 'from_address_hashes_to_exclude'; ...@@ -16,6 +20,13 @@ const FILTER_PARAM_FROM_EXCLUDE = 'from_address_hashes_to_exclude';
export type AddressFilterMode = 'include' | 'exclude'; export type AddressFilterMode = 'include' | 'exclude';
const collection = createListCollection({
items: [
{ label: 'Include', value: 'include' },
{ label: 'Exclude', value: 'exclude' },
],
});
type Value = Array<{ address: string; mode: AddressFilterMode }>; type Value = Array<{ address: string; mode: AddressFilterMode }>;
type Props = { type Props = {
...@@ -24,14 +35,13 @@ type Props = { ...@@ -24,14 +35,13 @@ type Props = {
columnName: string; columnName: string;
type: 'from' | 'to'; type: 'from' | 'to';
isLoading?: boolean; isLoading?: boolean;
onClose?: () => void;
}; };
type InputProps = { type InputProps = {
address?: string; address?: string;
mode?: AddressFilterMode; mode?: AddressFilterMode;
isLast: boolean; isLast: boolean;
onModeChange: (event: ChangeEvent<HTMLSelectElement>) => void; onModeChange: ({ value }: { value: Array<string> }) => void;
onChange: (event: ChangeEvent<HTMLInputElement>) => void; onChange: (event: ChangeEvent<HTMLInputElement>) => void;
onClear: () => void; onClear: () => void;
onAddFieldClick: () => void; onAddFieldClick: () => void;
...@@ -50,22 +60,19 @@ const AddressFilterInput = ({ address, mode, onModeChange, onChange, onClear, is ...@@ -50,22 +60,19 @@ const AddressFilterInput = ({ address, mode, onModeChange, onChange, onClear, is
return ( return (
<Flex alignItems="center" w="100%"> <Flex alignItems="center" w="100%">
<Select <Select
size="xs" collection={ collection }
borderRadius="base" placeholder="Select mode"
value={ mode || 'include' } defaultValue={ [ mode || 'include' ] }
onChange={ onModeChange } onValueChange={ onModeChange }
minW="105px" portalled={ false }
w="105px" w="105px"
mr={ 3 } mr={ 3 }
/>
<InputGroup
flexGrow={ 1 }
endElement={ <ClearButton onClick={ onClear } isDisabled={ !address }/> }
> >
<option value="include">Include</option> <Input value={ address } onChange={ onChange } placeholder="Smart contract / Address (0x...)*" size="sm" autoComplete="off"/>
<option value="exclude">Exclude</option>
</Select>
<InputGroup size="xs" flexGrow={ 1 }>
<Input value={ address } onChange={ onChange } placeholder="Smart contract / Address (0x...)*" size="xs" autoComplete="off"/>
<InputRightElement>
<ClearButton onClick={ onClear } isDisabled={ !address }/>
</InputRightElement>
</InputGroup> </InputGroup>
{ isLast && ( { isLast && (
<IconButton <IconButton
...@@ -76,8 +83,9 @@ const AddressFilterInput = ({ address, mode, onModeChange, onChange, onClear, is ...@@ -76,8 +83,9 @@ const AddressFilterInput = ({ address, mode, onModeChange, onChange, onClear, is
h="30px" h="30px"
ml={ 2 } ml={ 2 }
onClick={ onAddFieldClick } onClick={ onAddFieldClick }
icon={ <IconSvg name="plus" w="20px" h="20px"/> } >
/> <IconSvg name="plus" w="20px" h="20px"/>
</IconButton>
) } ) }
</Flex> </Flex>
); );
...@@ -85,14 +93,13 @@ const AddressFilterInput = ({ address, mode, onModeChange, onChange, onClear, is ...@@ -85,14 +93,13 @@ const AddressFilterInput = ({ address, mode, onModeChange, onChange, onClear, is
const emptyItem = { address: '', mode: 'include' as AddressFilterMode }; const emptyItem = { address: '', mode: 'include' as AddressFilterMode };
const AddressFilter = ({ type, value = [], handleFilterChange, onClose }: Props) => { const AddressFilter = ({ type, value = [], handleFilterChange }: Props) => {
const [ currentValue, setCurrentValue ] = const [ currentValue, setCurrentValue ] =
React.useState<Array<AddressFilter>>([ ...value, emptyItem ]); React.useState<Array<AddressFilter>>([ ...value, emptyItem ]);
const handleModeSelectChange = React.useCallback((index: number) => (event: React.ChangeEvent<HTMLSelectElement>) => { const handleModeSelectChange = React.useCallback((index: number) => ({ value }: { value: Array<string> }) => {
const value = event.target.value as AddressFilterMode;
setCurrentValue(prev => { setCurrentValue(prev => {
prev[index] = { ...prev[index], mode: value }; prev[index] = { ...prev[index], mode: value[0] as AddressFilterMode };
return [ ...prev ]; return [ ...prev ];
}); });
}, []); }, []);
...@@ -138,7 +145,6 @@ const AddressFilter = ({ type, value = [], handleFilterChange, onClose }: Props) ...@@ -138,7 +145,6 @@ const AddressFilter = ({ type, value = [], handleFilterChange, onClose }: Props)
isTouched={ !isEqual(currentValue.filter(i => i.address).map(addressFilterToKey).sort(), value.map(addressFilterToKey).sort()) } isTouched={ !isEqual(currentValue.filter(i => i.address).map(addressFilterToKey).sort(), value.map(addressFilterToKey).sort()) }
onFilter={ onFilter } onFilter={ onFilter }
onReset={ onReset } onReset={ onReset }
onClose={ onClose }
hasReset hasReset
> >
<VStack gap={ 2 }> <VStack gap={ 2 }>
......
import { Radio, RadioGroup, Stack, Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { type AdvancedFilterParams } from 'types/api/advancedFilter'; import { type AdvancedFilterParams } from 'types/api/advancedFilter';
import { Radio, RadioGroup } from 'toolkit/chakra/radio';
const FILTER_PARAM = 'address_relation'; const FILTER_PARAM = 'address_relation';
type Value = 'or' | 'and'; type Value = 'or' | 'and';
...@@ -18,18 +20,16 @@ type Props = { ...@@ -18,18 +20,16 @@ type Props = {
}; };
const AddressRelationFilter = ({ value = DEFAULT_VALUE, handleFilterChange, onClose }: Props) => { const AddressRelationFilter = ({ value = DEFAULT_VALUE, handleFilterChange, onClose }: Props) => {
const onFilter = React.useCallback((val: Value) => { const onFilter = React.useCallback(({ value }: { value: string }) => {
onClose && onClose(); onClose && onClose();
handleFilterChange(FILTER_PARAM, val); handleFilterChange(FILTER_PARAM, value as Value);
}, [ handleFilterChange, onClose ]); }, [ handleFilterChange, onClose ]);
return ( return (
<Box w="120px"> <Box w="120px">
<RadioGroup onChange={ onFilter } value={ value }> <RadioGroup onValueChange={ onFilter } value={ value } orientation="vertical">
<Stack direction="column"> <Radio value="or">OR</Radio>
<Radio value="or">OR</Radio> <Radio value="and">AND</Radio>
<Radio value="and">AND</Radio>
</Stack>
</RadioGroup> </RadioGroup>
</Box> </Box>
); );
......
import { Flex, Input, Text } from '@chakra-ui/react'; import { Flex, Text } from '@chakra-ui/react';
import { isEqual } from 'es-toolkit'; import { isEqual } from 'es-toolkit';
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import React from 'react'; import React from 'react';
...@@ -7,6 +7,8 @@ import { ADVANCED_FILTER_AGES, type AdvancedFilterAge, type AdvancedFilterParams ...@@ -7,6 +7,8 @@ import { ADVANCED_FILTER_AGES, type AdvancedFilterAge, type AdvancedFilterParams
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import { ndash } from 'lib/html-entities'; import { ndash } from 'lib/html-entities';
import { Input } from 'toolkit/chakra/input';
import { PopoverCloseTriggerWrapper } from 'toolkit/chakra/popover';
import TableColumnFilter from 'ui/shared/filters/TableColumnFilter'; import TableColumnFilter from 'ui/shared/filters/TableColumnFilter';
import TagGroupSelect from 'ui/shared/tagGroupSelect/TagGroupSelect'; import TagGroupSelect from 'ui/shared/tagGroupSelect/TagGroupSelect';
...@@ -48,8 +50,8 @@ const AgeFilter = ({ value = defaultValue, handleFilterChange, onClose }: Props) ...@@ -48,8 +50,8 @@ const AgeFilter = ({ value = defaultValue, handleFilterChange, onClose }: Props)
const to = dayjs().toISOString(); const to = dayjs().toISOString();
handleFilterChange(FILTER_PARAM_TO, to); handleFilterChange(FILTER_PARAM_TO, to);
handleFilterChange(FILTER_PARAM_AGE, age); handleFilterChange(FILTER_PARAM_AGE, age);
onClose && onClose(); onClose?.();
}, [ onClose, handleFilterChange ]); }, [ handleFilterChange, onClose ]);
const onReset = React.useCallback(() => setCurrentValue(defaultValue), []); const onReset = React.useCallback(() => setCurrentValue(defaultValue), []);
...@@ -76,15 +78,16 @@ const AgeFilter = ({ value = defaultValue, handleFilterChange, onClose }: Props) ...@@ -76,15 +78,16 @@ const AgeFilter = ({ value = defaultValue, handleFilterChange, onClose }: Props)
isTouched={ value.age ? value.age !== currentValue.age : !isEqual(currentValue, value) } isTouched={ value.age ? value.age !== currentValue.age : !isEqual(currentValue, value) }
onFilter={ onFilter } onFilter={ onFilter }
onReset={ onReset } onReset={ onReset }
onClose={ onClose }
hasReset hasReset
> >
<Flex gap={ 3 }> <Flex gap={ 3 }>
<TagGroupSelect<AdvancedFilterAge> <PopoverCloseTriggerWrapper>
items={ ADVANCED_FILTER_AGES.map(val => ({ id: val, title: val })) } <TagGroupSelect<AdvancedFilterAge>
onChange={ onPresetChange } items={ ADVANCED_FILTER_AGES.map(val => ({ id: val, title: val })) }
value={ currentValue.age || undefined } onChange={ onPresetChange }
/> value={ currentValue.age || undefined }
/>
</PopoverCloseTriggerWrapper>
</Flex> </Flex>
<Flex mt={ 3 }> <Flex mt={ 3 }>
<Input <Input
...@@ -92,7 +95,7 @@ const AgeFilter = ({ value = defaultValue, handleFilterChange, onClose }: Props) ...@@ -92,7 +95,7 @@ const AgeFilter = ({ value = defaultValue, handleFilterChange, onClose }: Props)
onChange={ handleFromChange } onChange={ handleFromChange }
placeholder="From" placeholder="From"
type="date" type="date"
size="xs" size="sm"
/> />
<Text mx={ 3 }>{ ndash }</Text> <Text mx={ 3 }>{ ndash }</Text>
<Input <Input
...@@ -100,7 +103,7 @@ const AgeFilter = ({ value = defaultValue, handleFilterChange, onClose }: Props) ...@@ -100,7 +103,7 @@ const AgeFilter = ({ value = defaultValue, handleFilterChange, onClose }: Props)
onChange={ handleToChange } onChange={ handleToChange }
placeholder="To" placeholder="To"
type="date" type="date"
size="xs" size="sm"
/> />
</Flex> </Flex>
</TableColumnFilter> </TableColumnFilter>
......
import { Flex, Input, Tag, Text } from '@chakra-ui/react'; import { Flex, Text } from '@chakra-ui/react';
import { isEqual } from 'es-toolkit'; import { isEqual } from 'es-toolkit';
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import React from 'react'; import React from 'react';
...@@ -6,6 +6,9 @@ import React from 'react'; ...@@ -6,6 +6,9 @@ import React from 'react';
import type { AdvancedFilterParams } from 'types/api/advancedFilter'; import type { AdvancedFilterParams } from 'types/api/advancedFilter';
import { ndash } from 'lib/html-entities'; import { ndash } from 'lib/html-entities';
import { Input } from 'toolkit/chakra/input';
import { PopoverCloseTriggerWrapper } from 'toolkit/chakra/popover';
import { Tag } from 'toolkit/chakra/tag';
import TableColumnFilter from 'ui/shared/filters/TableColumnFilter'; import TableColumnFilter from 'ui/shared/filters/TableColumnFilter';
const FILTER_PARAM_FROM = 'amount_from'; const FILTER_PARAM_FROM = 'amount_from';
...@@ -44,10 +47,9 @@ type AmountValue = { from?: string; to?: string }; ...@@ -44,10 +47,9 @@ type AmountValue = { from?: string; to?: string };
type Props = { type Props = {
value?: AmountValue; value?: AmountValue;
handleFilterChange: (filed: keyof AdvancedFilterParams, value?: string) => void; handleFilterChange: (filed: keyof AdvancedFilterParams, value?: string) => void;
onClose?: () => void;
}; };
const AmountFilter = ({ value = {}, handleFilterChange, onClose }: Props) => { const AmountFilter = ({ value = {}, handleFilterChange }: Props) => {
const [ currentValue, setCurrentValue ] = React.useState<AmountValue>(value || defaultValue); const [ currentValue, setCurrentValue ] = React.useState<AmountValue>(value || defaultValue);
const handleFromChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => { const handleFromChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
...@@ -69,8 +71,7 @@ const AmountFilter = ({ value = {}, handleFilterChange, onClose }: Props) => { ...@@ -69,8 +71,7 @@ const AmountFilter = ({ value = {}, handleFilterChange, onClose }: Props) => {
const to = (event.currentTarget as HTMLDivElement).getAttribute('data-id') as string; const to = (event.currentTarget as HTMLDivElement).getAttribute('data-id') as string;
handleFilterChange(FILTER_PARAM_FROM, ''); handleFilterChange(FILTER_PARAM_FROM, '');
handleFilterChange(FILTER_PARAM_TO, to); handleFilterChange(FILTER_PARAM_TO, to);
onClose && onClose(); }, [ handleFilterChange ]);
}, [ handleFilterChange, onClose ]);
return ( return (
<TableColumnFilter <TableColumnFilter
...@@ -79,25 +80,26 @@ const AmountFilter = ({ value = {}, handleFilterChange, onClose }: Props) => { ...@@ -79,25 +80,26 @@ const AmountFilter = ({ value = {}, handleFilterChange, onClose }: Props) => {
isTouched={ !isEqual(currentValue, value) } isTouched={ !isEqual(currentValue, value) }
onFilter={ onFilter } onFilter={ onFilter }
onReset={ onReset } onReset={ onReset }
onClose={ onClose }
hasReset hasReset
> >
<Flex gap={ 3 }> <PopoverCloseTriggerWrapper>
{ PRESETS.map(preset => ( <Flex gap={ 3 }>
<Tag { PRESETS.map(preset => (
key={ preset.value } <Tag
data-id={ preset.value } key={ preset.value }
onClick={ onPresetClick } data-id={ preset.value }
variant="select" onClick={ onPresetClick }
> variant="select"
{ preset.name } >
</Tag> { preset.name }
)) } </Tag>
</Flex> )) }
</Flex>
</PopoverCloseTriggerWrapper>
<Flex mt={ 3 } alignItems="center"> <Flex mt={ 3 } alignItems="center">
<Input value={ currentValue.from } onChange={ handleFromChange } placeholder="From" type="number" size="xs"/> <Input value={ currentValue.from } onChange={ handleFromChange } placeholder="From" type="number" size="sm"/>
<Text mx={ 3 }>{ ndash }</Text> <Text mx={ 3 }>{ ndash }</Text>
<Input value={ currentValue.to } onChange={ handleToChange } placeholder="To" type="number" size="xs"/> <Input value={ currentValue.to } onChange={ handleToChange } placeholder="To" type="number" size="sm"/>
</Flex> </Flex>
</TableColumnFilter> </TableColumnFilter>
); );
......
import { Flex, Checkbox, CheckboxGroup, Text, Spinner, Select } from '@chakra-ui/react'; import { Flex, Text, Spinner, createListCollection } from '@chakra-ui/react';
import { isEqual } from 'es-toolkit'; import { isEqual } from 'es-toolkit';
import React from 'react'; import React from 'react';
...@@ -7,7 +7,9 @@ import type { TokenInfo } from 'types/api/token'; ...@@ -7,7 +7,9 @@ import type { TokenInfo } from 'types/api/token';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import useDebounce from 'lib/hooks/useDebounce'; import useDebounce from 'lib/hooks/useDebounce';
import Tag from 'ui/shared/chakra/Tag'; import { Checkbox, CheckboxGroup } from 'toolkit/chakra/checkbox';
import { Select } from 'toolkit/chakra/select';
import { Tag } from 'toolkit/chakra/tag';
import ClearButton from 'ui/shared/ClearButton'; import ClearButton from 'ui/shared/ClearButton';
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity'; import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
import FilterInput from 'ui/shared/filters/FilterInput'; import FilterInput from 'ui/shared/filters/FilterInput';
...@@ -23,6 +25,13 @@ const NAME_PARAM_EXCLUDE = 'token_contract_symbols_to_exclude'; ...@@ -23,6 +25,13 @@ const NAME_PARAM_EXCLUDE = 'token_contract_symbols_to_exclude';
export type AssetFilterMode = 'include' | 'exclude'; export type AssetFilterMode = 'include' | 'exclude';
const collection = createListCollection({
items: [
{ label: 'Include', value: 'include' },
{ label: 'Exclude', value: 'exclude' },
],
});
// add native token // add native token
type Value = Array<{ token: TokenInfo; mode: AssetFilterMode }>; type Value = Array<{ token: TokenInfo; mode: AssetFilterMode }>;
...@@ -31,10 +40,9 @@ type Props = { ...@@ -31,10 +40,9 @@ type Props = {
handleFilterChange: (filed: keyof AdvancedFilterParams, val: Array<string>) => void; handleFilterChange: (filed: keyof AdvancedFilterParams, val: Array<string>) => void;
columnName: string; columnName: string;
isLoading?: boolean; isLoading?: boolean;
onClose?: () => void;
}; };
const AssetFilter = ({ value = [], handleFilterChange, onClose }: Props) => { const AssetFilter = ({ value = [], handleFilterChange }: Props) => {
const [ currentValue, setCurrentValue ] = React.useState<Value>([ ...value ]); const [ currentValue, setCurrentValue ] = React.useState<Value>([ ...value ]);
const [ searchTerm, setSearchTerm ] = React.useState<string>(''); const [ searchTerm, setSearchTerm ] = React.useState<string>('');
const debouncedSearchTerm = useDebounce(searchTerm, 300); const debouncedSearchTerm = useDebounce(searchTerm, 300);
...@@ -43,11 +51,10 @@ const AssetFilter = ({ value = [], handleFilterChange, onClose }: Props) => { ...@@ -43,11 +51,10 @@ const AssetFilter = ({ value = [], handleFilterChange, onClose }: Props) => {
setSearchTerm(value); setSearchTerm(value);
}, []); }, []);
const handleModeSelectChange = React.useCallback((index: number) => (event: React.ChangeEvent<HTMLSelectElement>) => { const handleModeSelectChange = React.useCallback((index: number) => ({ value }: { value: Array<string> }) => {
const value = event.target.value as AssetFilterMode;
setCurrentValue(prev => { setCurrentValue(prev => {
const newValue = [ ...prev ]; const newValue = [ ...prev ];
newValue[index] = { ...prev[index], mode: value }; newValue[index] = { ...prev[index], mode: value[0] as AssetFilterMode };
return newValue; return newValue;
}); });
}, []); }, []);
...@@ -88,11 +95,10 @@ const AssetFilter = ({ value = [], handleFilterChange, onClose }: Props) => { ...@@ -88,11 +95,10 @@ const AssetFilter = ({ value = [], handleFilterChange, onClose }: Props) => {
isTouched={ !isEqual(currentValue.map(i => JSON.stringify(i)).sort(), value.map(i => JSON.stringify(i)).sort()) } isTouched={ !isEqual(currentValue.map(i => JSON.stringify(i)).sort(), value.map(i => JSON.stringify(i)).sort()) }
onFilter={ onFilter } onFilter={ onFilter }
onReset={ onReset } onReset={ onReset }
onClose={ onClose }
hasReset hasReset
> >
<FilterInput <FilterInput
size="xs" size="sm"
onChange={ onSearchChange } onChange={ onSearchChange }
placeholder="Token name or symbol" placeholder="Token name or symbol"
initialValue={ searchTerm } initialValue={ searchTerm }
...@@ -100,17 +106,15 @@ const AssetFilter = ({ value = [], handleFilterChange, onClose }: Props) => { ...@@ -100,17 +106,15 @@ const AssetFilter = ({ value = [], handleFilterChange, onClose }: Props) => {
{ !searchTerm && currentValue.map((item, index) => ( { !searchTerm && currentValue.map((item, index) => (
<Flex key={ item.token.address } alignItems="center"> <Flex key={ item.token.address } alignItems="center">
<Select <Select
size="xs" size="sm"
borderRadius="base" value={ [ item.mode ] }
value={ item.mode } onValueChange={ handleModeSelectChange(index) }
onChange={ handleModeSelectChange(index) } collection={ collection }
placeholder="Select mode"
minW="105px" minW="105px"
w="105px" w="105px"
mr={ 3 } mr={ 3 }
> />
<option value="include">Include</option>
<option value="exclude">Exclude</option>
</Select>
<TokenEntity.default token={ item.token } noLink noCopy flexGrow={ 1 }/> <TokenEntity.default token={ item.token } noLink noCopy flexGrow={ 1 }/>
<ClearButton onClick={ handleRemove(index) }/> <ClearButton onClick={ handleRemove(index) }/>
</Flex> </Flex>
...@@ -139,25 +143,19 @@ const AssetFilter = ({ value = [], handleFilterChange, onClose }: Props) => { ...@@ -139,25 +143,19 @@ const AssetFilter = ({ value = [], handleFilterChange, onClose }: Props) => {
{ searchTerm && tokensQuery.data && !tokensQuery.data?.items.length && <Text>No tokens found</Text> } { searchTerm && tokensQuery.data && !tokensQuery.data?.items.length && <Text>No tokens found</Text> }
{ searchTerm && tokensQuery.data && Boolean(tokensQuery.data?.items.length) && ( { searchTerm && tokensQuery.data && Boolean(tokensQuery.data?.items.length) && (
<Flex display="flex" flexDir="column" rowGap={ 3 } maxH="250px" overflowY="scroll" mt={ 3 } ml="-4px"> <Flex display="flex" flexDir="column" rowGap={ 3 } maxH="250px" overflowY="scroll" mt={ 3 } ml="-4px">
<CheckboxGroup value={ currentValue.map(i => i.token.address) }> <CheckboxGroup value={ currentValue.map(i => i.token.address) } orientation="vertical">
{ tokensQuery.data.items.map(token => ( { tokensQuery.data.items.map(token => (
<Flex key={ token.address }> <Checkbox
<Checkbox key={ token.address }
value={ token.address } value={ token.address }
id={ token.address } id={ token.address }
onChange={ onTokenClick(token) } onChange={ onTokenClick(token) }
overflow="hidden" overflow="hidden"
w="100%" w="100%"
pl={ 1 } pl={ 1 }
sx={{ >
'.chakra-checkbox__label': { <TokenEntity.default token={ token } noLink noCopy/>
flexGrow: 1, </Checkbox>
},
}}
>
<TokenEntity.default token={ token } noLink noCopy/>
</Checkbox>
</Flex>
)) } )) }
</CheckboxGroup> </CheckboxGroup>
</Flex> </Flex>
......
import { Flex, Checkbox, CheckboxGroup, Spinner, chakra } from '@chakra-ui/react'; import { Flex, Spinner, chakra } from '@chakra-ui/react';
import { isEqual, differenceBy } from 'es-toolkit'; import { isEqual, differenceBy } from 'es-toolkit';
import type { ChangeEvent } from 'react';
import React from 'react'; import React from 'react';
import type { AdvancedFilterMethodInfo, AdvancedFilterParams } from 'types/api/advancedFilter'; import type { AdvancedFilterMethodInfo, AdvancedFilterParams } from 'types/api/advancedFilter';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import useDebounce from 'lib/hooks/useDebounce'; import useDebounce from 'lib/hooks/useDebounce';
import Tag from 'ui/shared/chakra/Tag'; import { Badge } from 'toolkit/chakra/badge';
import { Checkbox, CheckboxGroup } from 'toolkit/chakra/checkbox';
import FilterInput from 'ui/shared/filters/FilterInput'; import FilterInput from 'ui/shared/filters/FilterInput';
import TableColumnFilter from 'ui/shared/filters/TableColumnFilter'; import TableColumnFilter from 'ui/shared/filters/TableColumnFilter';
...@@ -19,10 +19,9 @@ const NAMES_PARAM = 'methods_names'; ...@@ -19,10 +19,9 @@ const NAMES_PARAM = 'methods_names';
type Props = { type Props = {
value?: Array<AdvancedFilterMethodInfo>; value?: Array<AdvancedFilterMethodInfo>;
handleFilterChange: (filed: keyof AdvancedFilterParams, val: Array<string>) => void; handleFilterChange: (filed: keyof AdvancedFilterParams, val: Array<string>) => void;
onClose?: () => void;
}; };
const MethodFilter = ({ value = [], handleFilterChange, onClose }: Props) => { const MethodFilter = ({ value = [], handleFilterChange }: Props) => {
const [ currentValue, setCurrentValue ] = React.useState<Array<AdvancedFilterMethodInfo>>([ ...value ]); const [ currentValue, setCurrentValue ] = React.useState<Array<AdvancedFilterMethodInfo>>([ ...value ]);
const [ searchTerm, setSearchTerm ] = React.useState<string>(''); const [ searchTerm, setSearchTerm ] = React.useState<string>('');
const debouncedSearchTerm = useDebounce(searchTerm, 300); const debouncedSearchTerm = useDebounce(searchTerm, 300);
...@@ -43,9 +42,10 @@ const MethodFilter = ({ value = [], handleFilterChange, onClose }: Props) => { ...@@ -43,9 +42,10 @@ const MethodFilter = ({ value = [], handleFilterChange, onClose }: Props) => {
} }
}, [ methodsQuery.data, value, methodsList ]); }, [ methodsQuery.data, value, methodsList ]);
const handleChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => { const handleChange: React.FormEventHandler<HTMLLabelElement> = React.useCallback((event) => {
const checked = event.target.checked; const checked = (event.target as HTMLInputElement).checked;
const id = event.target.id as string | typeof RESET_VALUE; const id = event.currentTarget.getAttribute('data-id');
if (id === RESET_VALUE) { if (id === RESET_VALUE) {
setCurrentValue([]); setCurrentValue([]);
setMethodsList(methodsQuery.data || []); setMethodsList(methodsQuery.data || []);
...@@ -75,11 +75,10 @@ const MethodFilter = ({ value = [], handleFilterChange, onClose }: Props) => { ...@@ -75,11 +75,10 @@ const MethodFilter = ({ value = [], handleFilterChange, onClose }: Props) => {
isTouched={ !isEqual(currentValue.map(i => JSON.stringify(i)).sort(), value.map(i => JSON.stringify(i)).sort()) } isTouched={ !isEqual(currentValue.map(i => JSON.stringify(i)).sort(), value.map(i => JSON.stringify(i)).sort()) }
onFilter={ onFilter } onFilter={ onFilter }
onReset={ onReset } onReset={ onReset }
onClose={ onClose }
hasReset hasReset
> >
<FilterInput <FilterInput
size="xs" size="sm"
onChange={ onSearchChange } onChange={ onSearchChange }
placeholder="Find by function name/ method ID" placeholder="Find by function name/ method ID"
mb={ 3 } mb={ 3 }
...@@ -89,26 +88,23 @@ const MethodFilter = ({ value = [], handleFilterChange, onClose }: Props) => { ...@@ -89,26 +88,23 @@ const MethodFilter = ({ value = [], handleFilterChange, onClose }: Props) => {
{ Boolean(searchTerm) && methodsQuery.data?.length === 0 && <span>No results found.</span> } { Boolean(searchTerm) && methodsQuery.data?.length === 0 && <span>No results found.</span> }
{ methodsQuery.data && ( { methodsQuery.data && (
// added negative margin because of checkbox focus styles & overflow hidden // added negative margin because of checkbox focus styles & overflow hidden
<Flex display="flex" flexDir="column" rowGap={ 3 } maxH="250px" overflowY="scroll" ml="-4px"> <Flex display="flex" flexDir="column" rowGap={ 3 } maxH="250px" overflowY="scroll">
<CheckboxGroup value={ currentValue.length ? currentValue.map(i => i.method_id) : [ RESET_VALUE ] }> <CheckboxGroup
value={ currentValue.length ? currentValue.map(i => i.method_id) : [ ] }
orientation="vertical"
>
{ (searchTerm ? methodsQuery.data : (methodsList || [])).map(method => ( { (searchTerm ? methodsQuery.data : (methodsList || [])).map(method => (
<Checkbox <Checkbox
key={ method.method_id } key={ method.method_id }
value={ method.method_id } value={ method.method_id }
id={ method.method_id } data-id={ method.method_id }
onChange={ handleChange } onChange={ handleChange }
pl={ 1 }
sx={{
'.chakra-checkbox__label': {
flexGrow: 1,
},
}}
> >
<Flex justifyContent="space-between" alignItems="center" id={ method.method_id }> <Flex justifyContent="space-between" alignItems="center" id={ method.method_id }>
<chakra.span overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis">{ method.name || method.method_id }</chakra.span> <chakra.span overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis">{ method.name || method.method_id }</chakra.span>
<Tag colorScheme="gray" isTruncated ml={ 2 }> <Badge colorPalette="gray" truncated ml="auto">
{ method.method_id } { method.method_id }
</Tag> </Badge>
</Flex> </Flex>
</Checkbox> </Checkbox>
)) } )) }
......
import { Flex, Checkbox, CheckboxGroup } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import { isEqual, without } from 'es-toolkit'; import { isEqual, without } from 'es-toolkit';
import type { ChangeEvent } from 'react';
import React from 'react'; import React from 'react';
import type { AdvancedFilterParams, AdvancedFilterType } from 'types/api/advancedFilter'; import type { AdvancedFilterParams, AdvancedFilterType } from 'types/api/advancedFilter';
import { Checkbox, CheckboxGroup } from 'toolkit/chakra/checkbox';
import TableColumnFilter from 'ui/shared/filters/TableColumnFilter'; import TableColumnFilter from 'ui/shared/filters/TableColumnFilter';
import { ADVANCED_FILTER_TYPES_WITH_ALL } from '../constants'; import { ADVANCED_FILTER_TYPES_WITH_ALL } from '../constants';
...@@ -14,48 +14,49 @@ const RESET_VALUE = 'all'; ...@@ -14,48 +14,49 @@ const RESET_VALUE = 'all';
const FILTER_PARAM = 'transaction_types'; const FILTER_PARAM = 'transaction_types';
type Props = { type Props = {
value?: Array<AdvancedFilterType>; value?: Array<AdvancedFilterType | typeof RESET_VALUE>;
handleFilterChange: (filed: keyof AdvancedFilterParams, value: Array<AdvancedFilterType>) => void; handleFilterChange: (filed: keyof AdvancedFilterParams, value: Array<AdvancedFilterType>) => void;
onClose?: () => void;
}; };
const TypeFilter = ({ value = [], handleFilterChange, onClose }: Props) => { const TypeFilter = ({ value = [ RESET_VALUE ], handleFilterChange }: Props) => {
const [ currentValue, setCurrentValue ] = React.useState<Array<AdvancedFilterType>>([ ...value ]); const [ currentValue, setCurrentValue ] = React.useState<Array<AdvancedFilterType | typeof RESET_VALUE>>([ ...value ]);
const handleChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => { const handleChange = React.useCallback((value: Array<string>) => {
const checked = event.target.checked; setCurrentValue((prev) => {
const id = event.target.id as AdvancedFilterType | typeof RESET_VALUE; if (value.length === 0) {
if (id === RESET_VALUE) { return [ RESET_VALUE ];
setCurrentValue([]); }
} else {
setCurrentValue(prev => checked ? [ ...prev, id ] : without(prev, id)); const diff = value.filter(item => !prev.includes(item));
} if (diff.includes(RESET_VALUE)) {
return [ RESET_VALUE ];
}
return without(value as Array<AdvancedFilterType>, RESET_VALUE);
});
}, []); }, []);
const onReset = React.useCallback(() => setCurrentValue([]), []); const onReset = React.useCallback(() => setCurrentValue([ RESET_VALUE ]), []);
const onFilter = React.useCallback(() => { const onFilter = React.useCallback(() => {
handleFilterChange(FILTER_PARAM, currentValue); handleFilterChange(FILTER_PARAM, currentValue.filter(item => item !== RESET_VALUE));
}, [ handleFilterChange, currentValue ]); }, [ handleFilterChange, currentValue ]);
return ( return (
<TableColumnFilter <TableColumnFilter
title="Type of transfer" title="Type of transfer"
isFilled={ currentValue.length > 0 } isFilled={ !(currentValue.length === 1 && currentValue[0] === RESET_VALUE) }
isTouched={ !isEqual(currentValue.sort(), value.sort()) } isTouched={ !isEqual(currentValue.sort(), value.sort()) }
onFilter={ onFilter } onFilter={ onFilter }
onReset={ onReset } onReset={ onReset }
onClose={ onClose }
hasReset hasReset
> >
<Flex display="flex" flexDir="column" rowGap={ 3 }> <Flex display="flex" flexDir="column" rowGap={ 3 }>
<CheckboxGroup value={ currentValue.length ? currentValue : [ RESET_VALUE ] }> <CheckboxGroup value={ currentValue } onValueChange={ handleChange } orientation="vertical">
{ ADVANCED_FILTER_TYPES_WITH_ALL.map(type => ( { ADVANCED_FILTER_TYPES_WITH_ALL.map(type => (
<Checkbox <Checkbox
key={ type.id } key={ type.id }
value={ type.id } value={ type.id }
id={ type.id }
onChange={ handleChange }
> >
{ type.name } { type.name }
</Checkbox> </Checkbox>
......
import { import {
Table,
Tbody,
Tr,
Th,
Td,
Thead,
Box, Box,
Text, Text,
Tag,
TagCloseButton,
chakra, chakra,
Flex, Flex,
TagLabel,
HStack, HStack,
Link,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { omit } from 'es-toolkit'; import { omit } from 'es-toolkit';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
...@@ -31,6 +21,9 @@ import getValuesArrayFromQuery from 'lib/getValuesArrayFromQuery'; ...@@ -31,6 +21,9 @@ import getValuesArrayFromQuery from 'lib/getValuesArrayFromQuery';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { ADVANCED_FILTER_ITEM } from 'stubs/advancedFilter'; import { ADVANCED_FILTER_ITEM } from 'stubs/advancedFilter';
import { generateListStub } from 'stubs/utils'; import { generateListStub } from 'stubs/utils';
import { Link } from 'toolkit/chakra/link';
import { TableBody, TableCell, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import { Tag } from 'toolkit/chakra/tag';
import ColumnsButton from 'ui/advancedFilter/ColumnsButton'; import ColumnsButton from 'ui/advancedFilter/ColumnsButton';
import type { ColumnsIds } from 'ui/advancedFilter/constants'; import type { ColumnsIds } from 'ui/advancedFilter/constants';
import { TABLE_COLUMNS } from 'ui/advancedFilter/constants'; import { TABLE_COLUMNS } from 'ui/advancedFilter/constants';
...@@ -145,12 +138,12 @@ const AdvancedFilter = () => { ...@@ -145,12 +138,12 @@ const AdvancedFilter = () => {
const content = ( const content = (
<AddressHighlightProvider> <AddressHighlightProvider>
<Box maxW="100%" overflowX="scroll" whiteSpace="nowrap"> <Box maxW="100%" overflowX="scroll" whiteSpace="nowrap">
<Table style={{ tableLayout: 'fixed' }} minWidth="950px" w="100%"> <TableRoot tableLayout="fixed" minWidth="950px" w="100%">
<Thead w="100%" display="table"> <TableHeaderSticky>
<Tr> <TableRow>
{ columnsToShow.map(column => { { columnsToShow.map(column => {
return ( return (
<Th <TableColumnHeader
key={ column.id } key={ column.id }
isNumeric={ column.isNumeric } isNumeric={ column.isNumeric }
minW={ column.width } minW={ column.width }
...@@ -167,33 +160,45 @@ const AdvancedFilter = () => { ...@@ -167,33 +160,45 @@ const AdvancedFilter = () => {
searchParams={ data?.search_params } searchParams={ data?.search_params }
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
/> />
</Th> </TableColumnHeader>
); );
}) } }) }
</Tr> </TableRow>
</Thead> </TableHeaderSticky>
<Tbody w="100%" display="table"> <TableBody>
{ data?.items.map((item, index) => ( { data?.items.map((item, index) => (
<Tr key={ item.hash + String(index) }> <TableRow key={ item.hash + String(index) }>
{ columnsToShow.map(column => ( { columnsToShow.map(column => {
<Td const textAlign = (() => {
key={ item.hash + column.id } if (column.id === 'or_and') {
isNumeric={ column.isNumeric } return 'center';
minW={ column.width } }
maxW={ column.width } if (column.isNumeric) {
w={ column.width } return 'right';
wordBreak="break-word" }
whiteSpace="nowrap" return 'start';
overflow="hidden" })();
textAlign={ column.id === 'or_and' ? 'center' : 'start' }
> return (
<ItemByColumn item={ item } column={ column.id } isLoading={ isPlaceholderData }/> <TableCell
</Td> key={ item.hash + column.id }
)) } isNumeric={ column.isNumeric }
</Tr> minW={ column.width }
maxW={ column.width }
w={ column.width }
wordBreak="break-word"
whiteSpace="nowrap"
overflow="hidden"
textAlign={ textAlign }
>
<ItemByColumn item={ item } column={ column.id } isLoading={ isPlaceholderData }/>
</TableCell>
);
}) }
</TableRow>
)) } )) }
</Tbody> </TableBody>
</Table> </TableRoot>
</Box> </Box>
</AddressHighlightProvider> </AddressHighlightProvider>
); );
...@@ -223,42 +228,36 @@ const AdvancedFilter = () => { ...@@ -223,42 +228,36 @@ const AdvancedFilter = () => {
</Flex> </Flex>
<HStack gap={ 2 } flexWrap="wrap" mb={ 6 }> <HStack gap={ 2 } flexWrap="wrap" mb={ 6 }>
{ filterTags.map(t => ( { filterTags.map(t => (
<Tag key={ t.name } colorScheme="blue" display="inline-flex"> <Tag key={ t.name } colorScheme="blue" display="inline-flex" onClose={ onClearFilter(t.key) } closable>
<TagLabel> <chakra.span color="text_secondary">{ t.name }: </chakra.span>
<chakra.span color="text_secondary">{ t.name }: </chakra.span> <chakra.span color="text">{ t.value }</chakra.span>
<chakra.span color="text">{ t.value }</chakra.span>
</TagLabel>
<TagCloseButton onClick={ onClearFilter(t.key) }/>
</Tag> </Tag>
)) } )) }
{ filterTags.length === 0 && ( { filterTags.length === 0 && (
<> <>
<Tag colorScheme="blue" display="inline-flex"> <Tag colorScheme="blue" display="inline-flex">
<TagLabel> <chakra.span color="text_secondary">Type: </chakra.span>
<chakra.span color="text_secondary">Type: </chakra.span> <chakra.span color="text">All</chakra.span>
<chakra.span color="text">All</chakra.span>
</TagLabel>
</Tag> </Tag>
<Tag colorScheme="blue" display="inline-flex"> <Tag colorScheme="blue" display="inline-flex">
<TagLabel> <chakra.span color="text_secondary">Age: </chakra.span>
<chakra.span color="text_secondary">Age: </chakra.span> <chakra.span color="text">7d</chakra.span>
<chakra.span color="text">7d</chakra.span>
</TagLabel>
</Tag> </Tag>
</> </>
) } ) }
</HStack> </HStack>
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
items={ data?.items } itemsNum={ data?.items.length }
emptyText="There are no transactions." emptyText="There are no transactions."
content={ content }
actionBar={ actionBar } actionBar={ actionBar }
filterProps={{ filterProps={{
hasActiveFilters: Object.values(filters).some(Boolean), hasActiveFilters: Object.values(filters).some(Boolean),
emptyFilteredText: 'No match found for current filter', emptyFilteredText: 'No match found for current filter',
}} }}
/> >
{ content }
</DataListDisplay>
</> </>
); );
}; };
......
...@@ -43,7 +43,7 @@ const RawInputData = ({ hex, rightSlot: rightSlotProp, defaultDataType = 'Hex', ...@@ -43,7 +43,7 @@ const RawInputData = ({ hex, rightSlot: rightSlotProp, defaultDataType = 'Hex',
mr="auto" mr="auto"
> >
<SelectControl loading={ isLoading }> <SelectControl loading={ isLoading }>
<SelectValueText placeholder="Select framework"/> <SelectValueText placeholder="Select type"/>
</SelectControl> </SelectControl>
<SelectContent> <SelectContent>
{ collection.items.map((item) => ( { collection.items.map((item) => (
......
import { Box, useColorModeValue } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { useColorModeValue } from 'toolkit/chakra/color-mode';
interface Props { interface Props {
isVerified: boolean; isVerified: boolean;
} }
......
import type { As } from '@chakra-ui/react';
import { chakra } from '@chakra-ui/react'; import { chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -60,21 +59,20 @@ export interface EntityProps extends EntityBase.EntityBaseProps { ...@@ -60,21 +59,20 @@ export interface EntityProps extends EntityBase.EntityBaseProps {
id: string; id: string;
} }
const UserOpEntity = (props: EntityProps) => { const ValidatorEntity = (props: EntityProps) => {
const partsProps = distributeEntityProps(props); const partsProps = distributeEntityProps(props);
const content = <Content { ...partsProps.content }/>;
return ( return (
<Container { ...partsProps.container }> <Container { ...partsProps.container }>
<Icon { ...partsProps.icon }/> <Icon { ...partsProps.icon }/>
<Link { ...partsProps.link }> { props.noLink ? content : <Link { ...partsProps.link }>{ content }</Link> }
<Content { ...partsProps.content }/>
</Link>
<Copy { ...partsProps.copy }/> <Copy { ...partsProps.copy }/>
</Container> </Container>
); );
}; };
export default React.memo(chakra<As, EntityProps>(UserOpEntity)); export default React.memo(chakra(ValidatorEntity));
export { export {
Container, Container,
......
...@@ -24,7 +24,7 @@ const TableColumnFilter = ({ title, isFilled, isTouched, hasReset, onFilter, onR ...@@ -24,7 +24,7 @@ const TableColumnFilter = ({ title, isFilled, isTouched, hasReset, onFilter, onR
}, [ onFilter ]); }, [ onFilter ]);
return ( return (
<> <>
<Flex alignItems="center" justifyContent="space-between"> <Flex alignItems="center" justifyContent="space-between" columnGap={ 6 }>
<Text color="text.secondary" fontWeight="600">{ title }</Text> <Text color="text.secondary" fontWeight="600">{ title }</Text>
{ hasReset && ( { hasReset && (
<Button <Button
......
...@@ -19,6 +19,7 @@ const TableColumnFilterWrapper = ({ columnName, className, children, isLoading, ...@@ -19,6 +19,7 @@ const TableColumnFilterWrapper = ({ columnName, className, children, isLoading,
<PopoverRoot> <PopoverRoot>
<PopoverTrigger> <PopoverTrigger>
<Button <Button
display="inline-flex"
aria-label={ `filter by ${ columnName }` } aria-label={ `filter by ${ columnName }` }
variant="dropdown" variant="dropdown"
borderWidth="0" borderWidth="0"
...@@ -28,16 +29,9 @@ const TableColumnFilterWrapper = ({ columnName, className, children, isLoading, ...@@ -28,16 +29,9 @@ const TableColumnFilterWrapper = ({ columnName, className, children, isLoading,
selected={ selected } selected={ selected }
borderRadius="4px" borderRadius="4px"
size="sm" size="sm"
textStyle="sm"
fontWeight={ 500 } fontWeight={ 500 }
padding={ 0 } padding={ 0 }
css={{
'span:only-child': {
mx: 0,
},
'span:not(:only-child)': {
mr: '2px',
},
}}
> >
<IconSvg name="filter" w="19px" h="19px"/> <IconSvg name="filter" w="19px" h="19px"/>
{ Boolean(value) && <chakra.span>{ value }</chakra.span> } { Boolean(value) && <chakra.span>{ value }</chakra.span> }
......
...@@ -19,7 +19,7 @@ type Props<T extends string> = { ...@@ -19,7 +19,7 @@ type Props<T extends string> = {
} }
); );
const TagGroupSelect = <T extends string>({ items, value, isMulti, onChange, tagSize }: Props<T>) => { const TagGroupSelect = <T extends string>({ items, value, isMulti, onChange, tagSize, ...rest }: Props<T>) => {
const onItemClick = React.useCallback((event: React.SyntheticEvent) => { const onItemClick = React.useCallback((event: React.SyntheticEvent) => {
const itemValue = (event.currentTarget as HTMLDivElement).getAttribute('data-id') as T; const itemValue = (event.currentTarget as HTMLDivElement).getAttribute('data-id') as T;
if (isMulti) { if (isMulti) {
...@@ -36,7 +36,7 @@ const TagGroupSelect = <T extends string>({ items, value, isMulti, onChange, tag ...@@ -36,7 +36,7 @@ const TagGroupSelect = <T extends string>({ items, value, isMulti, onChange, tag
}, [ isMulti, onChange, value ]); }, [ isMulti, onChange, value ]);
return ( return (
<HStack> <HStack { ...rest }>
{ items.map(item => { { items.map(item => {
const isSelected = isMulti ? value.includes(item.id) : value === item.id; const isSelected = isMulti ? value.includes(item.id) : value === item.id;
return ( return (
......
...@@ -47,6 +47,15 @@ const TagShowcase = () => { ...@@ -47,6 +47,15 @@ const TagShowcase = () => {
</SamplesStack> </SamplesStack>
</Section> </Section>
<Section>
<SectionHeader>Closable</SectionHeader>
<SamplesStack>
<Sample label="closable: true">
<Tag closable>My tag</Tag>
</Sample>
</SamplesStack>
</Section>
<Section> <Section>
<SectionHeader>Loading</SectionHeader> <SectionHeader>Loading</SectionHeader>
<SamplesStack> <SamplesStack>
......
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