Commit 99cb9b03 authored by tom's avatar tom

sortable table column header

parent 1bf8899f
'use client';
import type { HTMLChakraProps, RecipeProps } from '@chakra-ui/react';
import { createRecipeContext } from '@chakra-ui/react';
export interface LinkButtonProps
extends HTMLChakraProps<'a', RecipeProps<'button'>> {}
const { withContext } = createRecipeContext({ key: 'button' });
// TODO @tom2drum style and use this component
// Replace "a" with your framework's link component
export const LinkButton = withContext<HTMLAnchorElement, LinkButtonProps>('a');
...@@ -2,6 +2,10 @@ import { Table as ChakraTable } from '@chakra-ui/react'; ...@@ -2,6 +2,10 @@ import { Table as ChakraTable } from '@chakra-ui/react';
import { throttle } from 'es-toolkit'; import { throttle } from 'es-toolkit';
import * as React from 'react'; import * as React from 'react';
import IconSvg from 'ui/shared/IconSvg';
import { Link } from './link';
export const TableRoot = ChakraTable.Root; export const TableRoot = ChakraTable.Root;
export const TableBody = ChakraTable.Body; export const TableBody = ChakraTable.Body;
export const TableHeader = ChakraTable.Header; export const TableHeader = ChakraTable.Header;
...@@ -27,6 +31,40 @@ export const TableColumnHeader = (props: TableColumnHeaderProps) => { ...@@ -27,6 +31,40 @@ export const TableColumnHeader = (props: TableColumnHeaderProps) => {
return <ChakraTable.ColumnHeader textAlign={ isNumeric ? 'right' : undefined } { ...rest }/>; return <ChakraTable.ColumnHeader textAlign={ isNumeric ? 'right' : undefined } { ...rest }/>;
}; };
export interface TableColumnHeaderSortableProps<F extends string> extends TableColumnHeaderProps {
sortField: F;
sortValue: string;
onSortToggle: (sortField: F) => void;
disabled?: boolean;
}
export const TableColumnHeaderSortable = <F extends string>(props: TableColumnHeaderSortableProps<F>) => {
const { sortField, sortValue, onSortToggle, children, disabled, ...rest } = props;
const handleSortToggle = React.useCallback(() => {
onSortToggle(sortField);
}, [ onSortToggle, sortField ]);
return (
<TableColumnHeader { ...rest }>
<Link onClick={ disabled ? undefined : handleSortToggle } position="relative">
{ sortValue.includes(sortField) && (
<IconSvg
name="arrows/east"
w={ 4 }
h="100%"
transform={ sortValue.toLowerCase().includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)' }
position="absolute"
left={ -5 }
top={ 0 }
/>
) }
{ children }
</Link>
</TableColumnHeader>
);
};
export interface TableHeaderProps extends ChakraTable.HeaderProps { export interface TableHeaderProps extends ChakraTable.HeaderProps {
top?: number; top?: number;
} }
......
...@@ -210,6 +210,9 @@ export const recipe = defineRecipe({ ...@@ -210,6 +210,9 @@ export const recipe = defineRecipe({
bg: 'transparent', bg: 'transparent',
color: 'link.primary.hover', color: 'link.primary.hover',
}, },
_disabled: {
color: 'text.secondary',
},
}, },
}, },
size: { size: {
......
...@@ -15,9 +15,6 @@ export const recipe = defineRecipe({ ...@@ -15,9 +15,6 @@ export const recipe = defineRecipe({
textDecoration: 'none', textDecoration: 'none',
color: 'link.primary.hover', color: 'link.primary.hover',
}, },
_disabled: {
color: 'text.secondary',
},
}, },
secondary: { secondary: {
color: 'link.secondary', color: 'link.secondary',
......
...@@ -2,11 +2,11 @@ import { ...@@ -2,11 +2,11 @@ import {
chakra, chakra,
Flex, Flex,
Text, Text,
Link,
Button,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { Button } from 'toolkit/chakra/button';
import ColumnFilterWrapper from './ColumnFilterWrapper'; import ColumnFilterWrapper from './ColumnFilterWrapper';
type Props = { type Props = {
...@@ -40,20 +40,17 @@ const ColumnFilterContent = ({ title, isFilled, onFilter, onReset, onClose, chil ...@@ -40,20 +40,17 @@ const ColumnFilterContent = ({ title, isFilled, onFilter, onReset, onClose, chil
<> <>
<Flex alignItems="center" justifyContent="space-between" mb={ 3 }> <Flex alignItems="center" justifyContent="space-between" mb={ 3 }>
<Text color="text_secondary" fontWeight="600">{ title }</Text> <Text color="text_secondary" fontWeight="600">{ title }</Text>
<Link <Button
variant="link"
onClick={ onReset } onClick={ onReset }
cursor={ isFilled ? 'pointer' : 'unset' } disabled={ !isFilled }
opacity={ isFilled ? 1 : 0.2 }
_hover={{
color: isFilled ? 'link_hovered' : 'none',
}}
> >
Reset Reset
</Link> </Button>
</Flex> </Flex>
{ children } { children }
<Button <Button
isDisabled={ !isFilled } disabled={ !isFilled }
mt={ 4 } mt={ 4 }
onClick={ onFilterClick } onClick={ onFilterClick }
w="fit-content" w="fit-content"
......
...@@ -31,11 +31,10 @@ const NameDomainHistory = ({ domain }: Props) => { ...@@ -31,11 +31,10 @@ const NameDomainHistory = ({ domain }: Props) => {
}, },
}); });
const handleSortToggle = React.useCallback((event: React.MouseEvent) => { const handleSortToggle = React.useCallback((field: SortField) => {
if (isPlaceholderData) { if (isPlaceholderData) {
return; return;
} }
const field = (event.currentTarget as HTMLDivElement).getAttribute('data-field') as SortField | undefined;
if (field) { if (field) {
setSort(getNextSortValue(field)); setSort(getNextSortValue(field));
......
...@@ -2,46 +2,35 @@ import React from 'react'; ...@@ -2,46 +2,35 @@ import React from 'react';
import type * as bens from '@blockscout/bens-types'; import type * as bens from '@blockscout/bens-types';
import { Link } from 'toolkit/chakra/link'; import { TableBody, TableColumnHeader, TableColumnHeaderSortable, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import IconSvg from 'ui/shared/IconSvg';
import NameDomainHistoryTableItem from './NameDomainHistoryTableItem'; import NameDomainHistoryTableItem from './NameDomainHistoryTableItem';
import type { Sort } from './utils'; import type { SortField, Sort } from './utils';
import { sortFn } from './utils'; import { sortFn } from './utils';
interface Props { interface Props {
history: bens.ListDomainEventsResponse | undefined; history: bens.ListDomainEventsResponse | undefined;
domain: bens.DetailedDomain | undefined; domain: bens.DetailedDomain | undefined;
isLoading?: boolean; isLoading?: boolean;
sort: Sort | undefined; sort: Sort;
onSortToggle: (event: React.MouseEvent) => void; onSortToggle: (field: SortField) => void;
} }
const NameDomainHistoryTable = ({ history, domain, isLoading, sort, onSortToggle }: Props) => { const NameDomainHistoryTable = ({ history, domain, isLoading, sort, onSortToggle }: Props) => {
const sortIconTransform = sort?.includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)';
return ( return (
<TableRoot> <TableRoot>
<TableHeaderSticky top={ 0 }> <TableHeaderSticky top={ 0 }>
<TableRow> <TableRow>
<TableColumnHeader width="25%">Txn hash</TableColumnHeader> <TableColumnHeader width="25%">Txn hash</TableColumnHeader>
<TableColumnHeader width="25%" pl={ 9 }> <TableColumnHeaderSortable
<Link display="flex" alignItems="center" justifyContent="flex-start" position="relative" data-field="timestamp" onClick={ onSortToggle }> width="25%"
{ sort?.includes('timestamp') && ( pl={ 9 }
<IconSvg sortField="timestamp"
name="arrows/east" sortValue={ sort }
boxSize={ 4 } onSortToggle={ onSortToggle }
transform={ sortIconTransform } >
color="link.primary" Age
position="absolute" </TableColumnHeaderSortable>
left={ -5 }
top={ 0 }
/>
) }
<span>Age</span>
</Link>
</TableColumnHeader>
<TableColumnHeader width="25%">From</TableColumnHeader> <TableColumnHeader width="25%">From</TableColumnHeader>
<TableColumnHeader width="25%">Method</TableColumnHeader> <TableColumnHeader width="25%">Method</TableColumnHeader>
</TableRow> </TableRow>
......
...@@ -6,9 +6,9 @@ import type { EnsDomainLookupFiltersOptions } from 'types/api/ens'; ...@@ -6,9 +6,9 @@ import type { EnsDomainLookupFiltersOptions } from 'types/api/ens';
import type { PaginationParams } from 'ui/shared/pagination/types'; import type { PaginationParams } from 'ui/shared/pagination/types';
import useIsInitialLoading from 'lib/hooks/useIsInitialLoading'; import useIsInitialLoading from 'lib/hooks/useIsInitialLoading';
import { Button } from 'toolkit/chakra/button';
import { Checkbox } from 'toolkit/chakra/checkbox'; import { Checkbox } from 'toolkit/chakra/checkbox';
import { Image } from 'toolkit/chakra/image'; import { Image } from 'toolkit/chakra/image';
import { Link } from 'toolkit/chakra/link';
import ActionBar from 'ui/shared/ActionBar'; import ActionBar from 'ui/shared/ActionBar';
import FilterInput from 'ui/shared/filters/FilterInput'; import FilterInput from 'ui/shared/filters/FilterInput';
import PopoverFilter from 'ui/shared/filters/PopoverFilter'; import PopoverFilter from 'ui/shared/filters/PopoverFilter';
...@@ -87,12 +87,13 @@ const NameDomainsActionBar = ({ ...@@ -87,12 +87,13 @@ const NameDomainsActionBar = ({
<> <>
<Flex justifyContent="space-between" textStyle="sm" mb={ 3 }> <Flex justifyContent="space-between" textStyle="sm" mb={ 3 }>
<Text fontWeight={ 600 } color="text.secondary">Protocol</Text> <Text fontWeight={ 600 } color="text.secondary">Protocol</Text>
<Link <Button
variant="link"
onClick={ handleProtocolReset } onClick={ handleProtocolReset }
disabled={ protocolsFilterValue.length === 0 } disabled={ protocolsFilterValue.length === 0 }
> >
Reset Reset
</Link> </Button>
</Flex> </Flex>
<Fieldset.Root> <Fieldset.Root>
<CheckboxGroup defaultValue={ protocolsFilterValue } onValueChange={ onProtocolsFilterChange } value={ protocolsFilterValue } name="token_type"> <CheckboxGroup defaultValue={ protocolsFilterValue } onValueChange={ onProtocolsFilterChange } value={ protocolsFilterValue } name="token_type">
......
...@@ -2,46 +2,35 @@ import React from 'react'; ...@@ -2,46 +2,35 @@ import React from 'react';
import type * as bens from '@blockscout/bens-types'; import type * as bens from '@blockscout/bens-types';
import { Link } from 'toolkit/chakra/link'; import { TableBody, TableColumnHeader, TableColumnHeaderSortable, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
import IconSvg from 'ui/shared/IconSvg';
import NameDomainsTableItem from './NameDomainsTableItem'; import NameDomainsTableItem from './NameDomainsTableItem';
import { type Sort } from './utils'; import type { SortField, Sort } from './utils';
interface Props { interface Props {
data: bens.LookupDomainNameResponse | undefined; data: bens.LookupDomainNameResponse | undefined;
isLoading?: boolean; isLoading?: boolean;
sort: Sort; sort: Sort;
onSortToggle: (event: React.MouseEvent) => void; onSortToggle: (field: SortField) => void;
} }
const NameDomainsTable = ({ data, isLoading, sort, onSortToggle }: Props) => { const NameDomainsTable = ({ data, isLoading, sort, onSortToggle }: Props) => {
const sortIconTransform = sort?.toLowerCase().includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)';
return ( return (
<TableRoot> <TableRoot>
<TableHeaderSticky top={ ACTION_BAR_HEIGHT_DESKTOP }> <TableHeaderSticky top={ ACTION_BAR_HEIGHT_DESKTOP }>
<TableRow> <TableRow>
<TableColumnHeader width="25%">Domain</TableColumnHeader> <TableColumnHeader width="25%">Domain</TableColumnHeader>
<TableColumnHeader width="25%">Address</TableColumnHeader> <TableColumnHeader width="25%">Address</TableColumnHeader>
<TableColumnHeader width="25%" pl={ 9 }> <TableColumnHeaderSortable
<Link display="flex" alignItems="center" justifyContent="flex-start" position="relative" data-field="registration_date" onClick={ onSortToggle }> width="25%"
{ sort?.includes('registration_date') && ( pl={ 9 }
<IconSvg sortField="registration_date"
name="arrows/east" sortValue={ sort }
boxSize={ 4 } onSortToggle={ onSortToggle }
transform={ sortIconTransform } >
color="link.primary" Registered on
position="absolute" </TableColumnHeaderSortable>
left={ -5 }
top={ 0 }
/>
) }
<span>Registered on</span>
</Link>
</TableColumnHeader>
<TableColumnHeader width="25%">Expiration date</TableColumnHeader> <TableColumnHeader width="25%">Expiration date</TableColumnHeader>
</TableRow> </TableRow>
</TableHeaderSticky> </TableHeaderSticky>
......
...@@ -21,12 +21,22 @@ import PinInputShowcase from 'ui/showcases/PinInput'; ...@@ -21,12 +21,22 @@ import PinInputShowcase from 'ui/showcases/PinInput';
import ProgressCircleShowcase from 'ui/showcases/ProgressCircle'; import ProgressCircleShowcase from 'ui/showcases/ProgressCircle';
import RadioShowcase from 'ui/showcases/Radio'; import RadioShowcase from 'ui/showcases/Radio';
import SelectShowcase from 'ui/showcases/Select'; import SelectShowcase from 'ui/showcases/Select';
import TableShowcase from 'ui/showcases/Table';
import TabsShowcase from 'ui/showcases/Tabs'; import TabsShowcase from 'ui/showcases/Tabs';
import TagShowcase from 'ui/showcases/Tag'; import TagShowcase from 'ui/showcases/Tag';
import TextareaShowcase from 'ui/showcases/Textarea'; import TextareaShowcase from 'ui/showcases/Textarea';
import ToastShowcase from 'ui/showcases/Toast'; import ToastShowcase from 'ui/showcases/Toast';
import TooltipShowcase from 'ui/showcases/Tooltip'; import TooltipShowcase from 'ui/showcases/Tooltip';
// Drawer
// CloseButton
// IconButton
// EmptyState ?
// Rating
// Switch
// ToggleTip
// Popover
const tabs = [ const tabs = [
{ label: 'Accordion', value: 'accordion', component: <AccordionsShowcase/> }, { label: 'Accordion', value: 'accordion', component: <AccordionsShowcase/> },
{ label: 'Alert', value: 'alert', component: <AlertShowcase/> }, { label: 'Alert', value: 'alert', component: <AlertShowcase/> },
...@@ -44,6 +54,7 @@ const tabs = [ ...@@ -44,6 +54,7 @@ const tabs = [
{ label: 'Radio', value: 'radio', component: <RadioShowcase/> }, { label: 'Radio', value: 'radio', component: <RadioShowcase/> },
{ label: 'Pin input', value: 'pin-input', component: <PinInputShowcase/> }, { label: 'Pin input', value: 'pin-input', component: <PinInputShowcase/> },
{ label: 'Select', value: 'select', component: <SelectShowcase/> }, { label: 'Select', value: 'select', component: <SelectShowcase/> },
{ label: 'Table', value: 'table', component: <TableShowcase/> },
{ label: 'Tabs', value: 'tabs', component: <TabsShowcase/> }, { label: 'Tabs', value: 'tabs', component: <TabsShowcase/> },
{ label: 'Tag', value: 'tag', component: <TagShowcase/> }, { label: 'Tag', value: 'tag', component: <TagShowcase/> },
{ label: 'Textarea', value: 'textarea', component: <TextareaShowcase/> }, { label: 'Textarea', value: 'textarea', component: <TextareaShowcase/> },
......
...@@ -110,11 +110,10 @@ const NameDomains = () => { ...@@ -110,11 +110,10 @@ const NameDomains = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ isAddressSearch ]); }, [ isAddressSearch ]);
const handleSortToggle = React.useCallback((event: React.MouseEvent) => { const handleSortToggle = React.useCallback((field: SortField) => {
if (isLoading) { if (isLoading) {
return; return;
} }
const field = (event.currentTarget as HTMLDivElement).getAttribute('data-field') as SortField | undefined;
if (field) { if (field) {
setSort((prevValue) => { setSort((prevValue) => {
......
...@@ -6,7 +6,6 @@ import { ...@@ -6,7 +6,6 @@ import {
import React from 'react'; import React from 'react';
import { Button } from 'toolkit/chakra/button'; import { Button } from 'toolkit/chakra/button';
import { Link } from 'toolkit/chakra/link';
import { PopoverCloseTriggerWrapper } from 'toolkit/chakra/popover'; import { PopoverCloseTriggerWrapper } from 'toolkit/chakra/popover';
type Props = { type Props = {
...@@ -28,16 +27,13 @@ const TableColumnFilter = ({ title, isFilled, isTouched, hasReset, onFilter, onR ...@@ -28,16 +27,13 @@ const TableColumnFilter = ({ title, isFilled, isTouched, hasReset, onFilter, onR
<Flex alignItems="center" justifyContent="space-between"> <Flex alignItems="center" justifyContent="space-between">
<Text color="text.secondary" fontWeight="600">{ title }</Text> <Text color="text.secondary" fontWeight="600">{ title }</Text>
{ hasReset && ( { hasReset && (
<Link <Button
variant="link"
onClick={ onReset } onClick={ onReset }
cursor={ isFilled ? 'pointer' : 'unset' } disabled={ !isFilled }
opacity={ isFilled ? 1 : 0.2 }
_hover={{
color: isFilled ? 'link_hovered' : 'none',
}}
> >
Reset Reset
</Link> </Button>
) } ) }
</Flex> </Flex>
{ children } { children }
......
...@@ -4,8 +4,8 @@ import React from 'react'; ...@@ -4,8 +4,8 @@ import React from 'react';
import type { NFTTokenType, TokenType } from 'types/api/token'; import type { NFTTokenType, TokenType } from 'types/api/token';
import { TOKEN_TYPES, TOKEN_TYPE_IDS, NFT_TOKEN_TYPE_IDS } from 'lib/token/tokenTypes'; import { TOKEN_TYPES, TOKEN_TYPE_IDS, NFT_TOKEN_TYPE_IDS } from 'lib/token/tokenTypes';
import { Button } from 'toolkit/chakra/button';
import { Checkbox } from 'toolkit/chakra/checkbox'; import { Checkbox } from 'toolkit/chakra/checkbox';
import { Link } from 'toolkit/chakra/link';
type Props<T extends TokenType | NFTTokenType> = { type Props<T extends TokenType | NFTTokenType> = {
onChange: (nextValue: Array<T>) => void; onChange: (nextValue: Array<T>) => void;
...@@ -32,12 +32,13 @@ const TokenTypeFilter = <T extends TokenType | NFTTokenType>({ nftOnly, onChange ...@@ -32,12 +32,13 @@ const TokenTypeFilter = <T extends TokenType | NFTTokenType>({ nftOnly, onChange
<> <>
<Flex justifyContent="space-between" fontSize="sm"> <Flex justifyContent="space-between" fontSize="sm">
<Text fontWeight={ 600 } color="text.secondary">Type</Text> <Text fontWeight={ 600 } color="text.secondary">Type</Text>
<Link <Button
variant="link"
onClick={ handleReset } onClick={ handleReset }
disabled={ value.length === 0 } disabled={ value.length === 0 }
> >
Reset Reset
</Link> </Button>
</Flex> </Flex>
<Fieldset.Root> <Fieldset.Root>
<CheckboxGroup defaultValue={ defaultValue } onValueChange={ handleChange } value={ value } name="token_type"> <CheckboxGroup defaultValue={ defaultValue } onValueChange={ handleChange } value={ value } name="token_type">
......
...@@ -26,9 +26,8 @@ const TxWatchListTags = ({ tx, isLoading }: Props) => { ...@@ -26,9 +26,8 @@ const TxWatchListTags = ({ tx, isLoading }: Props) => {
<Badge <Badge
key={ tag.label } key={ tag.label }
loading={ isLoading } loading={ isLoading }
truncate truncated
// TODO @tom2drum check these styles maxW={{ base: '115px', lg: 'initial' }}
// maxW={{ base: '115px', lg: 'initial' }}
colorPalette="gray" colorPalette="gray"
> >
{ tag.display_name } { tag.display_name }
......
...@@ -4,7 +4,7 @@ import { IconButton } from 'toolkit/chakra/icon-button'; ...@@ -4,7 +4,7 @@ import { IconButton } from 'toolkit/chakra/icon-button';
import { MenuContent, MenuItem, MenuRoot, MenuTrigger } from 'toolkit/chakra/menu'; import { MenuContent, MenuItem, MenuRoot, MenuTrigger } from 'toolkit/chakra/menu';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import { Section, Container, SectionHeader, SamplesStack, Sample, SectionSubHeader } from './parts'; import { Section, Container, SectionHeader, SamplesStack, Sample } from './parts';
const MenuShowcase = () => { const MenuShowcase = () => {
...@@ -44,11 +44,6 @@ const MenuShowcase = () => { ...@@ -44,11 +44,6 @@ const MenuShowcase = () => {
</Sample> </Sample>
</SamplesStack> </SamplesStack>
</Section> </Section>
<Section>
<SectionHeader>Examples</SectionHeader>
<SectionSubHeader>Example 1</SectionSubHeader>
</Section>
</Container> </Container>
); );
}; };
......
...@@ -23,8 +23,6 @@ const txSortingOptions = createListCollection({ ...@@ -23,8 +23,6 @@ const txSortingOptions = createListCollection({
items: SORT_OPTIONS, items: SORT_OPTIONS,
}); });
// TODO @tom2drum + tanya: select with search
const SelectShowcase = () => { const SelectShowcase = () => {
const [ hasActiveFilter, setHasActiveFilter ] = React.useState(false); const [ hasActiveFilter, setHasActiveFilter ] = React.useState(false);
...@@ -52,8 +50,8 @@ const SelectShowcase = () => { ...@@ -52,8 +50,8 @@ const SelectShowcase = () => {
</SelectRoot> </SelectRoot>
</Sample> </Sample>
<Sample label="variant: filter"> <Sample label="variant: filter">
<SelectRoot collection={ frameworks } variant="filter" multiple> <SelectRoot collection={ frameworks } variant="filter">
<SelectControl w="200px"> <SelectControl w="200px" noIndicator>
<SelectValueText placeholder="Select framework"/> <SelectValueText placeholder="Select framework"/>
</SelectControl> </SelectControl>
<SelectContent> <SelectContent>
......
import { Box } from '@chakra-ui/react';
import React from 'react';
import { TableColumnHeader, TableHeaderSticky, TableRoot, TableRow, TableCell, TableBody, TableColumnHeaderSortable } from 'toolkit/chakra/table';
import getNextSortValue from 'ui/shared/sort/getNextSortValue';
import { Section, Container, SectionHeader, SamplesStack, Sample } from './parts';
const ITEMS = [
{ id: 1, name: 'Laptop', category: 'Electronics', price: 999.99 },
{ id: 2, name: 'Coffee Maker', category: 'Home Appliances', price: 49.99 },
{ id: 3, name: 'Desk Chair', category: 'Furniture', price: 150.0 },
{ id: 4, name: 'Smartphone', category: 'Electronics', price: 799.99 },
{ id: 5, name: 'Headphones', category: 'Accessories', price: 199.99 },
];
type Item = typeof ITEMS[number];
type SortField = 'category' | 'price';
type SortValue = 'category-asc' | 'category-desc' | 'price-asc' | 'price-desc' | 'default';
const SORT_SEQUENCE: Record<SortField, Array<SortValue>> = {
category: [ 'category-desc', 'category-asc', 'default' ],
price: [ 'price-desc', 'price-asc', 'default' ],
};
const TableShowcase = () => {
const [ sort, setSort ] = React.useState<SortValue>('default');
const handleSortToggle = React.useCallback((sortField: string) => {
const value = getNextSortValue<SortField, SortValue>(SORT_SEQUENCE, sortField as SortField)(sort);
setSort(value);
}, [ sort, setSort ]);
const sortFn = (a: Item, b: Item) => {
if (sort === 'category-asc') {
return a.category.localeCompare(b.category);
}
if (sort === 'category-desc') {
return b.category.localeCompare(a.category);
}
if (sort === 'price-asc') {
return a.price - b.price;
}
if (sort === 'price-desc') {
return b.price - a.price;
}
return 0;
};
return (
<Container value="table">
<Section>
<SectionHeader>Variant</SectionHeader>
<SamplesStack >
<Sample label="variant: line">
<TableRoot>
<TableHeaderSticky>
<TableRow>
<TableColumnHeader>Product</TableColumnHeader>
<TableColumnHeaderSortable
sortField="category"
sortValue={ sort }
onSortToggle={ handleSortToggle }
>
Category
</TableColumnHeaderSortable>
<TableColumnHeaderSortable
sortField="price"
sortValue={ sort }
onSortToggle={ handleSortToggle }
isNumeric
>
Price
</TableColumnHeaderSortable>
</TableRow>
</TableHeaderSticky>
<TableBody>
{ ITEMS.slice().sort(sortFn).map((item) => (
<TableRow key={ item.id }>
<TableCell>{ item.name }</TableCell>
<TableCell>{ item.category }</TableCell>
<TableCell isNumeric>{ item.price }</TableCell>
</TableRow>
)) }
</TableBody>
</TableRoot>
<Box h="1000px"/>
</Sample>
</SamplesStack>
</Section>
</Container>
);
};
export default React.memo(TableShowcase);
...@@ -2,8 +2,8 @@ import { CheckboxGroup, Text, Flex, useCheckboxGroup, chakra, Fieldset } from '@ ...@@ -2,8 +2,8 @@ import { CheckboxGroup, Text, Flex, useCheckboxGroup, chakra, Fieldset } from '@
import React from 'react'; import React from 'react';
import config from 'configs/app'; import config from 'configs/app';
import { Button } from 'toolkit/chakra/button';
import { Checkbox } from 'toolkit/chakra/checkbox'; import { Checkbox } from 'toolkit/chakra/checkbox';
import { Link } from 'toolkit/chakra/link';
const feature = config.features.bridgedTokens; const feature = config.features.bridgedTokens;
...@@ -36,12 +36,13 @@ const TokensBridgedChainsFilter = ({ onChange, defaultValue }: Props) => { ...@@ -36,12 +36,13 @@ const TokensBridgedChainsFilter = ({ onChange, defaultValue }: Props) => {
<> <>
<Flex justifyContent="space-between" fontSize="sm"> <Flex justifyContent="space-between" fontSize="sm">
<Text fontWeight={ 600 } color="text.secondary">Show bridged tokens from</Text> <Text fontWeight={ 600 } color="text.secondary">Show bridged tokens from</Text>
<Link <Button
variant="link"
onClick={ handleReset } onClick={ handleReset }
disabled={ value.length === 0 } disabled={ value.length === 0 }
> >
Reset Reset
</Link> </Button>
</Flex> </Flex>
<Fieldset.Root> <Fieldset.Root>
<CheckboxGroup defaultValue={ defaultValue } onValueChange={ handleChange } value={ value } name="bridged_token_chain"> <CheckboxGroup defaultValue={ defaultValue } onValueChange={ handleChange } value={ value } name="bridged_token_chain">
......
...@@ -83,12 +83,11 @@ const TxInternals = ({ txQuery }: Props) => { ...@@ -83,12 +83,11 @@ const TxInternals = ({ txQuery }: Props) => {
// }, []); // }, []);
const handleSortToggle = React.useCallback((field: SortField) => { const handleSortToggle = React.useCallback((field: SortField) => {
return () => {
if (isPlaceholderData) { if (isPlaceholderData) {
return; return;
} }
setSort(getNextSortValue(field)); setSort(getNextSortValue(field));
};
}, [ isPlaceholderData ]); }, [ isPlaceholderData ]);
if (!txQuery.isPlaceholderData && !txQuery.isError && !txQuery.data?.status) { if (!txQuery.isPlaceholderData && !txQuery.isError && !txQuery.data?.status) {
......
...@@ -12,7 +12,7 @@ interface Props { ...@@ -12,7 +12,7 @@ interface Props {
isLoading?: boolean; isLoading?: boolean;
} }
const TxInternalsTable = ({ data, top, isLoading }: Props) => { const TxBlobsTable = ({ data, top, isLoading }: Props) => {
return ( return (
<TableRoot> <TableRoot>
...@@ -32,4 +32,4 @@ const TxInternalsTable = ({ data, top, isLoading }: Props) => { ...@@ -32,4 +32,4 @@ const TxInternalsTable = ({ data, top, isLoading }: Props) => {
); );
}; };
export default TxInternalsTable; export default TxBlobsTable;
...@@ -4,23 +4,19 @@ import type { InternalTransaction } from 'types/api/internalTransaction'; ...@@ -4,23 +4,19 @@ import type { InternalTransaction } from 'types/api/internalTransaction';
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight'; import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
import { currencyUnits } from 'lib/units'; import { currencyUnits } from 'lib/units';
import { Link } from 'toolkit/chakra/link'; import { TableBody, TableColumnHeader, TableColumnHeaderSortable, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import IconSvg from 'ui/shared/IconSvg';
import TxInternalsTableItem from 'ui/tx/internals/TxInternalsTableItem'; import TxInternalsTableItem from 'ui/tx/internals/TxInternalsTableItem';
import type { Sort, SortField } from 'ui/tx/internals/utils'; import type { Sort, SortField } from 'ui/tx/internals/utils';
interface Props { interface Props {
data: Array<InternalTransaction>; data: Array<InternalTransaction>;
sort: Sort | undefined; sort: Sort;
onSortToggle: (field: SortField) => () => void; onSortToggle: (field: SortField) => void;
top: number; top: number;
isLoading?: boolean; isLoading?: boolean;
} }
const TxInternalsTable = ({ data, sort, onSortToggle, top, isLoading }: Props) => { const TxInternalsTable = ({ data, sort, onSortToggle, top, isLoading }: Props) => {
const sortIconTransform = sort?.includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)';
return ( return (
<AddressHighlightProvider> <AddressHighlightProvider>
<TableRoot> <TableRoot>
...@@ -28,18 +24,24 @@ const TxInternalsTable = ({ data, sort, onSortToggle, top, isLoading }: Props) = ...@@ -28,18 +24,24 @@ const TxInternalsTable = ({ data, sort, onSortToggle, top, isLoading }: Props) =
<TableRow> <TableRow>
<TableColumnHeader width="28%">Type</TableColumnHeader> <TableColumnHeader width="28%">Type</TableColumnHeader>
<TableColumnHeader width="40%">From/To</TableColumnHeader> <TableColumnHeader width="40%">From/To</TableColumnHeader>
<TableColumnHeader width="16%" isNumeric> <TableColumnHeaderSortable
<Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('value') } columnGap={ 1 }> width="16%"
{ sort?.includes('value') && <IconSvg name="arrows/east" boxSize={ 4 } transform={ sortIconTransform }/> } isNumeric
sortField="value"
sortValue={ sort }
onSortToggle={ onSortToggle }
>
Value { currencyUnits.ether } Value { currencyUnits.ether }
</Link> </TableColumnHeaderSortable>
</TableColumnHeader> <TableColumnHeaderSortable
<TableColumnHeader width="16%" isNumeric> width="16%"
<Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('gas-limit') } columnGap={ 1 }> isNumeric
{ sort?.includes('gas-limit') && <IconSvg name="arrows/east" boxSize={ 4 } transform={ sortIconTransform }/> } sortField="gas-limit"
sortValue={ sort }
onSortToggle={ onSortToggle }
>
Gas limit { currencyUnits.ether } Gas limit { currencyUnits.ether }
</Link> </TableColumnHeaderSortable>
</TableColumnHeader>
</TableRow> </TableRow>
</TableHeaderSticky> </TableHeaderSticky>
<TableBody> <TableBody>
......
...@@ -25,7 +25,6 @@ type Props = ...@@ -25,7 +25,6 @@ type Props =
className?: string; className?: string;
}; };
// TODO @tom2drum fix other popovers
const TxAdditionalInfo = ({ hash, tx, isMobile, isLoading, className }: Props) => { const TxAdditionalInfo = ({ hash, tx, isMobile, isLoading, className }: Props) => {
const content = hash !== undefined ? <TxAdditionalInfoContainer hash={ hash }/> : <TxAdditionalInfoContent tx={ tx }/>; const content = hash !== undefined ? <TxAdditionalInfoContainer hash={ hash }/> : <TxAdditionalInfoContent tx={ tx }/>;
......
...@@ -60,7 +60,7 @@ const TxsContent = ({ ...@@ -60,7 +60,7 @@ const TxsContent = ({
}: Props) => { }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const onSortToggle = React.useCallback((field: TransactionsSortingField) => () => { const onSortToggle = React.useCallback((field: TransactionsSortingField) => {
const value = getNextSortValue<TransactionsSortingField, TransactionsSortingValue>(SORT_SEQUENCE, field)(sort); const value = getNextSortValue<TransactionsSortingField, TransactionsSortingValue>(SORT_SEQUENCE, field)(sort);
setSorting(value); setSorting(value);
}, [ sort, setSorting ]); }, [ sort, setSorting ]);
...@@ -84,8 +84,8 @@ const TxsContent = ({ ...@@ -84,8 +84,8 @@ const TxsContent = ({
<Box hideBelow="lg"> <Box hideBelow="lg">
<TxsTable <TxsTable
txs={ itemsWithTranslation } txs={ itemsWithTranslation }
sort={ onSortToggle } sort={ sort }
sorting={ sort } onSortToggle={ onSortToggle }
showBlockInfo={ showBlockInfo } showBlockInfo={ showBlockInfo }
showSocketInfo={ showSocketInfo } showSocketInfo={ showSocketInfo }
socketInfoAlert={ socketInfoAlert } socketInfoAlert={ socketInfoAlert }
......
...@@ -10,8 +10,9 @@ test('base view +@dark-mode', async({ render }) => { ...@@ -10,8 +10,9 @@ test('base view +@dark-mode', async({ render }) => {
const component = await render( const component = await render(
<TxsTable <TxsTable
txs={ [ txMock.base, txMock.withWatchListNames ] } txs={ [ txMock.base, txMock.withWatchListNames ] }
sort="default"
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
sort={ () => () => {} } onSortToggle={ () => {} }
top={ 0 } top={ 0 }
showBlockInfo showBlockInfo
showSocketInfo={ false } showSocketInfo={ false }
...@@ -30,8 +31,9 @@ test.describe('screen xl', () => { ...@@ -30,8 +31,9 @@ test.describe('screen xl', () => {
const component = await render( const component = await render(
<TxsTable <TxsTable
txs={ [ txMock.base, txMock.withWatchListNames ] } txs={ [ txMock.base, txMock.withWatchListNames ] }
sort="default"
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
sort={ () => () => {} } onSortToggle={ () => {} }
top={ 0 } top={ 0 }
showBlockInfo showBlockInfo
showSocketInfo={ false } showSocketInfo={ false }
......
...@@ -7,17 +7,15 @@ import { AddressHighlightProvider } from 'lib/contexts/addressHighlight'; ...@@ -7,17 +7,15 @@ import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
import useInitialList from 'lib/hooks/useInitialList'; import useInitialList from 'lib/hooks/useInitialList';
import useLazyRenderedList from 'lib/hooks/useLazyRenderedList'; import useLazyRenderedList from 'lib/hooks/useLazyRenderedList';
import { currencyUnits } from 'lib/units'; import { currencyUnits } from 'lib/units';
import { Link } from 'toolkit/chakra/link'; import { TableBody, TableColumnHeader, TableColumnHeaderSortable, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import IconSvg from 'ui/shared/IconSvg';
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice'; import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import TxsTableItem from './TxsTableItem'; import TxsTableItem from './TxsTableItem';
type Props = { type Props = {
txs: Array<Transaction>; txs: Array<Transaction>;
sort: (field: TransactionsSortingField) => () => void; sort: TransactionsSortingValue;
sorting?: TransactionsSortingValue; onSortToggle: (field: TransactionsSortingField) => void;
top: number; top: number;
showBlockInfo: boolean; showBlockInfo: boolean;
showSocketInfo: boolean; showSocketInfo: boolean;
...@@ -31,7 +29,7 @@ type Props = { ...@@ -31,7 +29,7 @@ type Props = {
const TxsTable = ({ const TxsTable = ({
txs, txs,
sort, sort,
sorting, onSortToggle,
top, top,
showBlockInfo, showBlockInfo,
showSocketInfo, showSocketInfo,
...@@ -62,32 +60,38 @@ const TxsTable = ({ ...@@ -62,32 +60,38 @@ const TxsTable = ({
<TableColumnHeader width="160px">Type</TableColumnHeader> <TableColumnHeader width="160px">Type</TableColumnHeader>
<TableColumnHeader width="20%">Method</TableColumnHeader> <TableColumnHeader width="20%">Method</TableColumnHeader>
{ showBlockInfo && ( { showBlockInfo && (
<TableColumnHeader width="18%"> <TableColumnHeaderSortable
<Link onClick={ isLoading ? undefined : sort('block_number') } display="flex" alignItems="center"> width="18%"
{ sorting === 'block_number-asc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(-90deg)"/> } sortField="block_number"
{ sorting === 'block_number-desc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(90deg)"/> } sortValue={ sort }
onSortToggle={ onSortToggle }
>
Block Block
</Link> </TableColumnHeaderSortable>
</TableColumnHeader>
) } ) }
<TableColumnHeader width="224px">From/To</TableColumnHeader> <TableColumnHeader width="224px">From/To</TableColumnHeader>
{ !config.UI.views.tx.hiddenFields?.value && ( { !config.UI.views.tx.hiddenFields?.value && (
<TableColumnHeader width="20%" isNumeric> <TableColumnHeaderSortable
<Link onClick={ isLoading ? undefined : sort('value') } display="flex" alignItems="center" justifyContent="end"> width="20%"
{ sorting === 'value-asc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(-90deg)"/> } isNumeric
{ sorting === 'value-desc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(90deg)"/> } sortField="value"
sortValue={ sort }
onSortToggle={ onSortToggle }
>
{ `Value ${ currencyUnits.ether }` } { `Value ${ currencyUnits.ether }` }
</Link> </TableColumnHeaderSortable>
</TableColumnHeader>
) } ) }
{ !config.UI.views.tx.hiddenFields?.tx_fee && ( { !config.UI.views.tx.hiddenFields?.tx_fee && (
<TableColumnHeader width="20%" isNumeric pr={ 5 }> <TableColumnHeaderSortable
<Link onClick={ isLoading ? undefined : sort('fee') } display="flex" alignItems="center" justifyContent="end"> width="20%"
{ sorting === 'fee-asc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(-90deg)"/> } isNumeric
{ sorting === 'fee-desc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(90deg)"/> } pr={ 5 }
sortField="fee"
sortValue={ sort }
onSortToggle={ onSortToggle }
>
{ `Fee${ feeCurrency }` } { `Fee${ feeCurrency }` }
</Link> </TableColumnHeaderSortable>
</TableColumnHeader>
) } ) }
</TableRow> </TableRow>
</TableHeaderSticky> </TableHeaderSticky>
......
import React from 'react'; import React from 'react';
import type { VerifiedContract } from 'types/api/contracts'; import type { VerifiedContract } from 'types/api/contracts';
import type { VerifiedContractsSorting, VerifiedContractsSortingField, VerifiedContractsSortingValue } from 'types/api/verifiedContracts'; import type { VerifiedContractsSortingField, VerifiedContractsSortingValue } from 'types/api/verifiedContracts';
import { currencyUnits } from 'lib/units'; import { currencyUnits } from 'lib/units';
import { Link } from 'toolkit/chakra/link'; import { TableBody, TableColumnHeader, TableColumnHeaderSortable, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
import IconSvg from 'ui/shared/IconSvg';
import getNextSortValue from 'ui/shared/sort/getNextSortValue'; import getNextSortValue from 'ui/shared/sort/getNextSortValue';
import { SORT_SEQUENCE } from 'ui/verifiedContracts/utils'; import { SORT_SEQUENCE } from 'ui/verifiedContracts/utils';
...@@ -21,9 +19,7 @@ interface Props { ...@@ -21,9 +19,7 @@ interface Props {
} }
const VerifiedContractsTable = ({ data, sort, setSorting, isLoading }: Props) => { const VerifiedContractsTable = ({ data, sort, setSorting, isLoading }: Props) => {
const sortIconTransform = sort?.includes('asc' as VerifiedContractsSorting['order']) ? 'rotate(-90deg)' : 'rotate(90deg)'; const onSortToggle = React.useCallback((field: VerifiedContractsSortingField) => {
const onSortToggle = React.useCallback((field: VerifiedContractsSortingField) => () => {
const value = getNextSortValue<VerifiedContractsSortingField, VerifiedContractsSortingValue>(SORT_SEQUENCE, field)(sort); const value = getNextSortValue<VerifiedContractsSortingField, VerifiedContractsSortingValue>(SORT_SEQUENCE, field)(sort);
setSorting({ value: [ value ] }); setSorting({ value: [ value ] });
}, [ sort, setSorting ]); }, [ sort, setSorting ]);
...@@ -33,24 +29,26 @@ const VerifiedContractsTable = ({ data, sort, setSorting, isLoading }: Props) => ...@@ -33,24 +29,26 @@ const VerifiedContractsTable = ({ data, sort, setSorting, isLoading }: Props) =>
<TableHeaderSticky top={ ACTION_BAR_HEIGHT_DESKTOP }> <TableHeaderSticky top={ ACTION_BAR_HEIGHT_DESKTOP }>
<TableRow> <TableRow>
<TableColumnHeader width="50%">Contract</TableColumnHeader> <TableColumnHeader width="50%">Contract</TableColumnHeader>
<TableColumnHeader width="130px" isNumeric> <TableColumnHeaderSortable
<Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ isLoading ? undefined : onSortToggle('balance') } columnGap={ 1 }> width="130px"
{ sort?.includes('balance') && <IconSvg name="arrows/east" boxSize={ 4 } transform={ sortIconTransform }/> } isNumeric
sortField="balance"
sortValue={ sort }
onSortToggle={ onSortToggle }
disabled={ isLoading }
>
Balance { currencyUnits.ether } Balance { currencyUnits.ether }
</Link> </TableColumnHeaderSortable>
</TableColumnHeader> <TableColumnHeaderSortable
<TableColumnHeader width="130px" isNumeric> width="130px"
<Link isNumeric
display="flex" sortField="transactions_count"
alignItems="center" sortValue={ sort }
justifyContent="flex-end" onSortToggle={ onSortToggle }
onClick={ isLoading ? undefined : onSortToggle('transactions_count') } disabled={ isLoading }
columnGap={ 1 }
> >
{ sort?.includes('transactions_count') && <IconSvg name="arrows/east" boxSize={ 4 } transform={ sortIconTransform }/> }
Txs Txs
</Link> </TableColumnHeaderSortable>
</TableColumnHeader>
<TableColumnHeader width="50%">Language / Compiler version</TableColumnHeader> <TableColumnHeader width="50%">Language / Compiler version</TableColumnHeader>
<TableColumnHeader width="80px">Settings</TableColumnHeader> <TableColumnHeader width="80px">Settings</TableColumnHeader>
<TableColumnHeader width="150px">Verified</TableColumnHeader> <TableColumnHeader width="150px">Verified</TableColumnHeader>
......
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