Commit 7025b758 authored by tom's avatar tom

advanced filters page

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