Commit 567a3a0e authored by Igor Stuev's avatar Igor Stuev Committed by GitHub

Merge pull request #282 from blockscout/sticky-pagination-new

Sticky pagination new
parents 9d3f151f 70cdf920
import clamp from 'lodash/clamp';
import throttle from 'lodash/throttle';
import React from 'react';
import isBrowser from 'lib/isBrowser';
const SCROLL_DIFF_THRESHOLD = 20;
type Directions = 'up' | 'down';
export default function useScrollDirection() {
const prevScrollPosition = React.useRef(isBrowser() ? window.pageYOffset : 0);
const [ scrollDirection, setDirection ] = React.useState<Directions>();
const handleScroll = React.useCallback(() => {
const currentScrollPosition = clamp(window.pageYOffset, 0, window.document.body.scrollHeight - window.innerHeight);
const scrollDiff = currentScrollPosition - prevScrollPosition.current;
if (window.pageYOffset === 0) {
setDirection(undefined);
} else if (Math.abs(scrollDiff) > SCROLL_DIFF_THRESHOLD) {
setDirection(scrollDiff < 0 ? 'up' : 'down');
}
prevScrollPosition.current = currentScrollPosition;
}, [ ]);
React.useEffect(() => {
const throttledHandleScroll = throttle(handleScroll, 300);
window.addEventListener('scroll', throttledHandleScroll);
return () => {
window.removeEventListener('scroll', throttledHandleScroll);
};
// replicate componentDidMount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return scrollDirection;
}
...@@ -16,10 +16,10 @@ const variantSimple = definePartsStyle((props) => { ...@@ -16,10 +16,10 @@ const variantSimple = definePartsStyle((props) => {
th: { th: {
border: 0, border: 0,
color: mode('gray.600', 'whiteAlpha.700')(props), color: mode('gray.600', 'whiteAlpha.700')(props),
backgroundColor: mode('blackAlpha.100', 'whiteAlpha.200')(props),
...transitionProps, ...transitionProps,
}, },
thead: { thead: {
backgroundColor: mode('blackAlpha.100', 'whiteAlpha.200')(props),
...transitionProps, ...transitionProps,
}, },
td: { td: {
...@@ -73,9 +73,6 @@ const variants = { ...@@ -73,9 +73,6 @@ const variants = {
}; };
const baseStyle = definePartsStyle({ const baseStyle = definePartsStyle({
thead: {
backgroundColor: 'gray.50',
},
th: { th: {
textTransform: 'none', textTransform: 'none',
fontFamily: 'body', fontFamily: 'body',
...@@ -83,6 +80,12 @@ const baseStyle = definePartsStyle({ ...@@ -83,6 +80,12 @@ const baseStyle = definePartsStyle({
overflow: 'hidden', overflow: 'hidden',
color: 'gray.500', color: 'gray.500',
letterSpacing: 'none', letterSpacing: 'none',
_first: {
borderTopLeftRadius: '8px',
},
_last: {
borderTopRightRadius: '8px',
},
}, },
td: { td: {
fontSize: 'md', fontSize: 'md',
...@@ -92,7 +95,7 @@ const baseStyle = definePartsStyle({ ...@@ -92,7 +95,7 @@ const baseStyle = definePartsStyle({
tableLayout: 'fixed', tableLayout: 'fixed',
borderTopLeftRadius: 'base', borderTopLeftRadius: 'base',
borderTopRightRadius: 'base', borderTopRightRadius: 'base',
overflow: 'hidden', overflow: 'unset',
fontVariant: 'normal', fontVariant: 'normal',
}, },
}); });
......
const zIndices = {
hide: -1,
auto: 'auto',
base: 0,
docked: 10,
dropdown: 1000,
sticky: 1100,
sticky1: 1101,
sticky2: 1102,
banner: 1200,
overlay: 1300,
modal: 1400,
popover: 1500,
skipLink: 1600,
toast: 1700,
tooltip: 1800,
};
export default zIndices;
...@@ -7,6 +7,7 @@ import breakpoints from './foundations/breakpoints'; ...@@ -7,6 +7,7 @@ import breakpoints from './foundations/breakpoints';
import colors from './foundations/colors'; import colors from './foundations/colors';
import transition from './foundations/transition'; import transition from './foundations/transition';
import typography from './foundations/typography'; import typography from './foundations/typography';
import zIndices from './foundations/zIndices';
import global from './global'; import global from './global';
const overrides = { const overrides = {
...@@ -20,6 +21,7 @@ const overrides = { ...@@ -20,6 +21,7 @@ const overrides = {
}, },
breakpoints, breakpoints,
transition, transition,
zIndices,
}; };
export default extendTheme(overrides); export default extendTheme(overrides);
import React from 'react';
const ScrollDirectionContext = React.createContext<'up' | 'down' | undefined>(undefined);
export default ScrollDirectionContext;
...@@ -4,7 +4,6 @@ import { ...@@ -4,7 +4,6 @@ import {
Tbody, Tbody,
Tr, Tr,
Th, Th,
TableContainer,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -21,26 +20,24 @@ interface Props { ...@@ -21,26 +20,24 @@ interface Props {
const ApiKeyTable = ({ data, onDeleteClick, onEditClick, limit }: Props) => { const ApiKeyTable = ({ data, onDeleteClick, onEditClick, limit }: Props) => {
return ( return (
<TableContainer width="100%"> <Table variant="simple" minWidth="600px">
<Table variant="simple" minWidth="600px"> <Thead>
<Thead> <Tr>
<Tr> <Th>{ `API key token (limit ${ limit } keys)` }</Th>
<Th>{ `API key token (limit ${ limit } keys)` }</Th> <Th width="108px"></Th>
<Th width="108px"></Th> </Tr>
</Tr> </Thead>
</Thead> <Tbody>
<Tbody> { data.map((item) => (
{ data.map((item) => ( <ApiKeyTableItem
<ApiKeyTableItem item={ item }
item={ item } key={ item.api_key }
key={ item.api_key } onDeleteClick={ onDeleteClick }
onDeleteClick={ onDeleteClick } onEditClick={ onEditClick }
onEditClick={ onEditClick } />
/> )) }
)) } </Tbody>
</Tbody> </Table>
</Table>
</TableContainer>
); );
}; };
......
...@@ -54,7 +54,7 @@ const BlocksContent = ({ type }: Props) => { ...@@ -54,7 +54,7 @@ const BlocksContent = ({ type }: Props) => {
<Show above="lg" key="content-desktop"><BlocksTable data={ data.items }/></Show> <Show above="lg" key="content-desktop"><BlocksTable data={ data.items }/></Show>
<Box mx={{ base: 0, lg: 6 }} my={{ base: 6, lg: 3 }}> <Box mx={{ base: 0, lg: 6 }} my={{ base: 6, lg: 3 }}>
{ /* eslint-disable-next-line react/jsx-no-bind */ } { /* eslint-disable-next-line react/jsx-no-bind */ }
<Pagination currentPage={ 1 } onNextPageClick={ () => {} } onPrevPageClick={ () => {} } hasNextPage/> <Pagination page={ 1 } onNextPageClick={ () => {} } onPrevPageClick={ () => {} } resetPage={ () => {} } hasNextPage/>
</Box> </Box>
</> </>
); );
......
import { Table, Thead, Tbody, Tr, Th, TableContainer } from '@chakra-ui/react'; import { Table, Tbody, Tr, Th } from '@chakra-ui/react';
import { AnimatePresence } from 'framer-motion'; import { AnimatePresence } from 'framer-motion';
import capitalize from 'lodash/capitalize'; import capitalize from 'lodash/capitalize';
import React from 'react'; import React from 'react';
...@@ -8,6 +8,7 @@ import type { Block } from 'types/api/block'; ...@@ -8,6 +8,7 @@ import type { Block } from 'types/api/block';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle'; import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import BlocksTableItem from 'ui/blocks/BlocksTableItem'; import BlocksTableItem from 'ui/blocks/BlocksTableItem';
import { default as Thead } from 'ui/shared/TheadSticky';
interface Props { interface Props {
data: Array<Block>; data: Array<Block>;
...@@ -16,27 +17,25 @@ interface Props { ...@@ -16,27 +17,25 @@ interface Props {
const BlocksTable = ({ data }: Props) => { const BlocksTable = ({ data }: Props) => {
return ( return (
<TableContainer width="100%" mt={ 8 }> <Table variant="simple" minWidth="1040px" size="md" fontWeight={ 500 } mt={ 8 }>
<Table variant="simple" minWidth="1040px" size="md" fontWeight={ 500 }> <Thead top={ 0 }>
<Thead> <Tr>
<Tr> <Th width="125px">Block</Th>
<Th width="125px">Block</Th> <Th width="120px">Size</Th>
<Th width="120px">Size</Th> <Th width="21%" minW="144px">{ capitalize(getNetworkValidatorTitle()) }</Th>
<Th width="21%" minW="144px">{ capitalize(getNetworkValidatorTitle()) }</Th> <Th width="64px" isNumeric>Txn</Th>
<Th width="64px" isNumeric>Txn</Th> <Th width="35%">Gas used</Th>
<Th width="35%">Gas used</Th> <Th width="22%">Reward { appConfig.network.currency.symbol }</Th>
<Th width="22%">Reward { appConfig.network.currency.symbol }</Th> <Th width="22%">Burnt fees { appConfig.network.currency.symbol }</Th>
<Th width="22%">Burnt fees { appConfig.network.currency.symbol }</Th> </Tr>
</Tr> </Thead>
</Thead> <Tbody>
<Tbody> <AnimatePresence initial={ false }>
<AnimatePresence initial={ false }> { /* TODO prop "enableTimeIncrement" should be set to false for second and later pages */ }
{ /* TODO prop "enableTimeIncrement" should be set to false for second and later pages */ } { data.map((item) => <BlocksTableItem key={ item.height } data={ item } enableTimeIncrement/>) }
{ data.map((item) => <BlocksTableItem key={ item.height } data={ item } enableTimeIncrement/>) } </AnimatePresence>
</AnimatePresence> </Tbody>
</Tbody> </Table>
</Table>
</TableContainer>
); );
}; };
......
...@@ -4,7 +4,6 @@ import { ...@@ -4,7 +4,6 @@ import {
Tbody, Tbody,
Tr, Tr,
Th, Th,
TableContainer,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -20,26 +19,24 @@ interface Props { ...@@ -20,26 +19,24 @@ interface Props {
const CustomAbiTable = ({ data, onDeleteClick, onEditClick }: Props) => { const CustomAbiTable = ({ data, onDeleteClick, onEditClick }: Props) => {
return ( return (
<TableContainer width="100%"> <Table variant="simple" minWidth="600px">
<Table variant="simple" minWidth="600px"> <Thead>
<Thead> <Tr>
<Tr> <Th>ABI for Smart contract address (0x...)</Th>
<Th>ABI for Smart contract address (0x...)</Th> <Th width="108px"></Th>
<Th width="108px"></Th> </Tr>
</Tr> </Thead>
</Thead> <Tbody>
<Tbody> { data.map((item) => (
{ data.map((item) => ( <CustomAbiTableItem
<CustomAbiTableItem item={ item }
item={ item } key={ item.id }
key={ item.id } onDeleteClick={ onDeleteClick }
onDeleteClick={ onDeleteClick } onEditClick={ onEditClick }
onEditClick={ onEditClick } />
/> )) }
)) } </Tbody>
</Tbody> </Table>
</Table>
</TableContainer>
); );
}; };
......
...@@ -19,7 +19,7 @@ const Transactions = () => { ...@@ -19,7 +19,7 @@ const Transactions = () => {
]; ];
return ( return (
<Page> <Page hideMobileHeaderOnScrollDown>
<Box h="100%"> <Box h="100%">
<PageTitle text="Transactions"/> <PageTitle text="Transactions"/>
<RoutedTabs tabs={ TABS }/> <RoutedTabs tabs={ TABS }/>
......
...@@ -4,7 +4,6 @@ import { ...@@ -4,7 +4,6 @@ import {
Tbody, Tbody,
Tr, Tr,
Th, Th,
TableContainer,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -20,27 +19,25 @@ interface Props { ...@@ -20,27 +19,25 @@ interface Props {
const AddressTagTable = ({ data, onDeleteClick, onEditClick }: Props) => { const AddressTagTable = ({ data, onDeleteClick, onEditClick }: Props) => {
return ( return (
<TableContainer width="100%"> <Table variant="simple" minWidth="600px">
<Table variant="simple" minWidth="600px"> <Thead>
<Thead> <Tr>
<Tr> <Th width="60%">Address</Th>
<Th width="60%">Address</Th> <Th width="40%">Private tag</Th>
<Th width="40%">Private tag</Th> <Th width="108px"></Th>
<Th width="108px"></Th> </Tr>
</Tr> </Thead>
</Thead> <Tbody>
<Tbody> { data.map((item: AddressTag) => (
{ data.map((item: AddressTag) => ( <AddressTagTableItem
<AddressTagTableItem item={ item }
item={ item } key={ item.id }
key={ item.id } onDeleteClick={ onDeleteClick }
onDeleteClick={ onDeleteClick } onEditClick={ onEditClick }
onEditClick={ onEditClick } />
/> )) }
)) } </Tbody>
</Tbody> </Table>
</Table>
</TableContainer>
); );
}; };
......
...@@ -4,7 +4,6 @@ import { ...@@ -4,7 +4,6 @@ import {
Tbody, Tbody,
Tr, Tr,
Th, Th,
TableContainer,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -20,27 +19,25 @@ interface Props { ...@@ -20,27 +19,25 @@ interface Props {
const AddressTagTable = ({ data, onDeleteClick, onEditClick }: Props) => { const AddressTagTable = ({ data, onDeleteClick, onEditClick }: Props) => {
return ( return (
<TableContainer width="100%"> <Table variant="simple" minWidth="600px">
<Table variant="simple" minWidth="600px"> <Thead>
<Thead> <Tr>
<Tr> <Th width="75%">Transaction</Th>
<Th width="75%">Transaction</Th> <Th width="25%">Private tag</Th>
<Th width="25%">Private tag</Th> <Th width="108px"></Th>
<Th width="108px"></Th> </Tr>
</Tr> </Thead>
</Thead> <Tbody>
<Tbody> { data.map((item) => (
{ data.map((item) => ( <TransactionTagTableItem
<TransactionTagTableItem item={ item }
item={ item } key={ item.id }
key={ item.id } onDeleteClick={ onDeleteClick }
onDeleteClick={ onDeleteClick } onEditClick={ onEditClick }
onEditClick={ onEditClick } />
/> )) }
)) } </Tbody>
</Tbody> </Table>
</Table>
</TableContainer>
); );
}; };
......
...@@ -4,7 +4,6 @@ import { ...@@ -4,7 +4,6 @@ import {
Tbody, Tbody,
Tr, Tr,
Th, Th,
TableContainer,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -20,28 +19,26 @@ interface Props { ...@@ -20,28 +19,26 @@ interface Props {
const PublicTagTable = ({ data, onEditClick, onDeleteClick }: Props) => { const PublicTagTable = ({ data, onEditClick, onDeleteClick }: Props) => {
return ( return (
<TableContainer width="100%"> <Table variant="simple" minWidth="600px">
<Table variant="simple" minWidth="600px"> <Thead>
<Thead> <Tr>
<Tr> <Th width="50%">Smart contract / Address (0x...)</Th>
<Th width="50%">Smart contract / Address (0x...)</Th> <Th width="25%">Public tag</Th>
<Th width="25%">Public tag</Th> <Th width="25%">Request status</Th>
<Th width="25%">Request status</Th> <Th width="108px"></Th>
<Th width="108px"></Th> </Tr>
</Tr> </Thead>
</Thead> <Tbody>
<Tbody> { data.map((item) => (
{ data.map((item) => ( <PublicTagTableItem
<PublicTagTableItem item={ item }
item={ item } key={ item.id }
key={ item.id } onDeleteClick={ onDeleteClick }
onDeleteClick={ onDeleteClick } onEditClick={ onEditClick }
onEditClick={ onEditClick } />
/> )) }
)) } </Tbody>
</Tbody> </Table>
</Table>
</TableContainer>
); );
}; };
......
...@@ -6,6 +6,8 @@ import { QueryKeys } from 'types/client/queries'; ...@@ -6,6 +6,8 @@ import { QueryKeys } from 'types/client/queries';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import useFetch from 'lib/hooks/useFetch'; import useFetch from 'lib/hooks/useFetch';
import useScrollDirection from 'lib/hooks/useScrollDirection';
import ScrollDirectionContext from 'ui/ScrollDirectionContext';
import PageContent from 'ui/shared/Page/PageContent'; import PageContent from 'ui/shared/Page/PageContent';
import Header from 'ui/snippets/header/Header'; import Header from 'ui/snippets/header/Header';
import NavigationDesktop from 'ui/snippets/navigation/NavigationDesktop'; import NavigationDesktop from 'ui/snippets/navigation/NavigationDesktop';
...@@ -13,27 +15,32 @@ import NavigationDesktop from 'ui/snippets/navigation/NavigationDesktop'; ...@@ -13,27 +15,32 @@ import NavigationDesktop from 'ui/snippets/navigation/NavigationDesktop';
interface Props { interface Props {
children: React.ReactNode; children: React.ReactNode;
wrapChildren?: boolean; wrapChildren?: boolean;
hideMobileHeaderOnScrollDown?: boolean;
} }
const Page = ({ children, wrapChildren = true }: Props) => { const Page = ({ children, wrapChildren = true, hideMobileHeaderOnScrollDown }: Props) => {
const fetch = useFetch(); const fetch = useFetch();
useQuery<unknown, unknown, unknown>([ QueryKeys.csrf ], async() => await fetch('/node-api/account/csrf'), { useQuery<unknown, unknown, unknown>([ QueryKeys.csrf ], async() => await fetch('/node-api/account/csrf'), {
enabled: Boolean(cookies.get(cookies.NAMES.API_TOKEN)), enabled: Boolean(cookies.get(cookies.NAMES.API_TOKEN)),
}); });
const directionContext = useScrollDirection();
const renderedChildren = wrapChildren ? ( const renderedChildren = wrapChildren ? (
<PageContent>{ children }</PageContent> <PageContent>{ children }</PageContent>
) : children; ) : children;
return ( return (
<Flex w="100%" minH="100vh" alignItems="stretch"> <ScrollDirectionContext.Provider value={ directionContext }>
<NavigationDesktop/> <Flex w="100%" minH="100vh" alignItems="stretch">
<Flex flexDir="column" width="100%"> <NavigationDesktop/>
<Header/> <Flex flexDir="column" width="100%">
{ renderedChildren } <Header hideOnScrollDown={ hideMobileHeaderOnScrollDown }/>
{ renderedChildren }
</Flex>
</Flex> </Flex>
</Flex> </ScrollDirectionContext.Provider>
); );
}; };
......
...@@ -3,77 +3,63 @@ import React from 'react'; ...@@ -3,77 +3,63 @@ import React from 'react';
import arrowIcon from 'icons/arrows/east-mini.svg'; import arrowIcon from 'icons/arrows/east-mini.svg';
type Props = { export type Props = {
currentPage: number; page: number;
maxPage?: number;
onNextPageClick: () => void; onNextPageClick: () => void;
onPrevPageClick: () => void; onPrevPageClick: () => void;
resetPage: () => void;
hasNextPage: boolean; hasNextPage: boolean;
hasPaginationParams?: boolean;
} }
const MAX_PAGE_DEFAULT = 50; const Pagination = ({ page, onNextPageClick, onPrevPageClick, resetPage, hasNextPage, hasPaginationParams }: Props) => {
const Pagination = ({ currentPage, maxPage, onNextPageClick, onPrevPageClick, hasNextPage }: Props) => { return (
const pageNumber = ( <Flex
<Flex alignItems="center"> fontSize="sm"
alignItems="center"
>
<Button <Button
variant="outline" variant="outline"
colorScheme="gray"
size="sm" size="sm"
isActive onClick={ resetPage }
borderWidth="1px" disabled={ !hasPaginationParams }
fontWeight={ 400 } mr={ 4 }
mr={ 3 }
h={ 8 }
> >
{ currentPage } First
</Button> </Button>
{ /* max page will be removed */ } <IconButton
of variant="outline"
onClick={ onPrevPageClick }
size="sm"
aria-label="Next page"
w="36px"
icon={ <Icon as={ arrowIcon } w={ 5 } h={ 5 }/> }
mr={ 6 }
disabled={ page === 1 }
/>
<Button <Button
variant="outline" variant="outline"
colorScheme="gray"
size="sm" size="sm"
width={ 8 } isActive
borderWidth="1px" borderWidth="1px"
fontWeight={ 400 } fontWeight={ 400 }
ml={ 3 } h={ 8 }
cursor="unset"
disabled={ hasPaginationParams && page === 1 }
> >
{ maxPage || MAX_PAGE_DEFAULT } { page }
</Button> </Button>
</Flex> <IconButton
); variant="outline"
onClick={ onNextPageClick }
return ( size="sm"
<Flex aria-label="Next page"
fontSize="sm" w="36px"
width={{ base: '100%', lg: 'auto' }} icon={ <Icon as={ arrowIcon } w={ 5 } h={ 5 } transform="rotate(180deg)"/> }
justifyContent={{ base: 'space-between', lg: 'unset' }} ml={ 6 }
alignItems="center" disabled={ !hasNextPage }
> />
<Flex alignItems="center" justifyContent="space-between" w={{ base: '100%', lg: 'auto' }}>
<IconButton
variant="outline"
onClick={ onPrevPageClick }
size="sm"
aria-label="Next page"
w="36px"
icon={ <Icon as={ arrowIcon } w={ 5 } h={ 5 }/> }
mr={ 8 }
disabled={ currentPage === 1 }
/>
{ pageNumber }
<IconButton
variant="outline"
onClick={ onNextPageClick }
size="sm"
aria-label="Next page"
w="36px"
icon={ <Icon as={ arrowIcon } w={ 5 } h={ 5 } transform="rotate(180deg)"/> }
ml={ 8 }
disabled={ !hasNextPage }
/>
</Flex>
{ /* not implemented yet */ } { /* not implemented yet */ }
{ /* <Flex alignItems="center" width="132px" ml={ 16 } display={{ base: 'none', lg: 'flex' }}> { /* <Flex alignItems="center" width="132px" ml={ 16 } display={{ base: 'none', lg: 'flex' }}>
Go to <Input w="84px" size="xs" ml={ 2 }/> Go to <Input w="84px" size="xs" ml={ 2 }/>
......
...@@ -20,6 +20,7 @@ const SortButton = ({ onClick, isActive, className }: Props) => { ...@@ -20,6 +20,7 @@ const SortButton = ({ onClick, isActive, className }: Props) => {
minWidth="36px" minWidth="36px"
onClick={ onClick } onClick={ onClick }
isActive={ isActive } isActive={ isActive }
display="flex"
className={ className } className={ className }
/> />
); );
......
import { Thead, useColorModeValue } from '@chakra-ui/react';
import type { TableHeadProps, PositionProps } from '@chakra-ui/react';
import throttle from 'lodash/throttle';
import React from 'react';
interface Props extends TableHeadProps {
top?: number;
children?: React.ReactNode;
}
const TheadSticky = ({ top, children, ...restProps }: Props) => {
const ref = React.useRef<HTMLTableSectionElement>(null);
const [ isSticky, setIsSticky ] = React.useState(false);
const handleScroll = React.useCallback(() => {
if (Number(ref.current?.getBoundingClientRect().y) <= (top || 0)) {
setIsSticky(true);
} else {
setIsSticky(false);
}
}, [ top ]);
React.useEffect(() => {
const throttledHandleScroll = throttle(handleScroll, 300);
window.addEventListener('scroll', throttledHandleScroll);
return () => {
window.removeEventListener('scroll', throttledHandleScroll);
};
}, [ handleScroll ]);
const props = {
...restProps,
position: 'sticky' as PositionProps['position'],
top: `${ top }px` || 0,
backgroundColor: useColorModeValue('white', 'black'),
boxShadow: isSticky ? 'md' : 'none',
zIndex: 1,
};
return (
<Thead { ...props } ref={ ref }>
{ children }
</Thead>
);
};
export default TheadSticky;
import { HStack, Box, Flex, useColorModeValue } from '@chakra-ui/react'; import { HStack, Box, Flex, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import ScrollDirectionContext from 'ui/ScrollDirectionContext';
import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo';
import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop'; import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop';
import ProfileMenuMobile from 'ui/snippets/profileMenu/ProfileMenuMobile'; import ProfileMenuMobile from 'ui/snippets/profileMenu/ProfileMenuMobile';
...@@ -9,46 +10,53 @@ import SearchBar from 'ui/snippets/searchBar/SearchBar'; ...@@ -9,46 +10,53 @@ import SearchBar from 'ui/snippets/searchBar/SearchBar';
import Burger from './Burger'; import Burger from './Burger';
import ColorModeToggler from './ColorModeToggler'; import ColorModeToggler from './ColorModeToggler';
const Header = () => { const Header = ({ hideOnScrollDown }: {hideOnScrollDown?: boolean}) => {
const bgColor = useColorModeValue('white', 'black'); const bgColor = useColorModeValue('white', 'black');
return ( return (
<> <ScrollDirectionContext.Consumer>
<Box bgColor={ bgColor } display={{ base: 'block', lg: 'none' }}> { (scrollDirection) => (
<Flex <>
as="header" <Box bgColor={ bgColor } display={{ base: 'block', lg: 'none' }}>
position="fixed" <Flex
top={ 0 } as="header"
left={ 0 } position="fixed"
paddingX={ 4 } top={ 0 }
paddingY={ 2 } left={ 0 }
bgColor={ bgColor } paddingX={ 4 }
width="100%" paddingY={ 2 }
alignItems="center" bgColor={ bgColor }
justifyContent="space-between" width="100%"
zIndex="sticky" alignItems="center"
> justifyContent="space-between"
<Burger/> zIndex="sticky2"
<NetworkLogo/> transitionProperty="box-shadow"
<ProfileMenuMobile/> transitionDuration="slow"
</Flex> boxShadow={ !hideOnScrollDown && scrollDirection === 'down' ? 'md' : 'none' }
<SearchBar/> >
</Box> <Burger/>
<HStack <NetworkLogo/>
as="header" <ProfileMenuMobile/>
width="100%" </Flex>
alignItems="center" <SearchBar withShadow={ !hideOnScrollDown }/>
justifyContent="center" </Box><HStack
gap={ 12 } as="header"
display={{ base: 'none', lg: 'flex' }} width="100%"
paddingX={ 12 } alignItems="center"
paddingTop={ 9 } justifyContent="center"
paddingBottom="52px" gap={ 12 }
> display={{ base: 'none', lg: 'flex' }}
<SearchBar/> paddingX={ 12 }
<ColorModeToggler/> paddingTop={ 9 }
<ProfileMenuDesktop/> paddingBottom="52px"
</HStack> >
</> <SearchBar/>
<ColorModeToggler/>
<ProfileMenuDesktop/>
</HStack>
</>
) }
</ScrollDirectionContext.Consumer>
); );
}; };
......
...@@ -6,7 +6,7 @@ import link from 'lib/link/link'; ...@@ -6,7 +6,7 @@ import link from 'lib/link/link';
import SearchBarDesktop from './SearchBarDesktop'; import SearchBarDesktop from './SearchBarDesktop';
import SearchBarMobile from './SearchBarMobile'; import SearchBarMobile from './SearchBarMobile';
const SearchBar = () => { const SearchBar = ({ withShadow }: {withShadow?: boolean}) => {
const [ value, setValue ] = React.useState(''); const [ value, setValue ] = React.useState('');
const handleChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => { const handleChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
...@@ -22,7 +22,7 @@ const SearchBar = () => { ...@@ -22,7 +22,7 @@ const SearchBar = () => {
return ( return (
<> <>
<SearchBarDesktop onChange={ handleChange } onSubmit={ handleSubmit }/> <SearchBarDesktop onChange={ handleChange } onSubmit={ handleSubmit }/>
<SearchBarMobile onChange={ handleChange } onSubmit={ handleSubmit }/> <SearchBarMobile onChange={ handleChange } onSubmit={ handleSubmit } withShadow={ withShadow }/>
</> </>
); );
}; };
......
import { InputGroup, Input, InputLeftElement, Icon, useColorModeValue, chakra } from '@chakra-ui/react'; import { InputGroup, Input, InputLeftElement, Icon, useColorModeValue, chakra } from '@chakra-ui/react';
import clamp from 'lodash/clamp';
import throttle from 'lodash/throttle'; import throttle from 'lodash/throttle';
import React from 'react'; import React from 'react';
import type { ChangeEvent, FormEvent } from 'react'; import type { ChangeEvent, FormEvent } from 'react';
import searchIcon from 'icons/search.svg'; import searchIcon from 'icons/search.svg';
import isBrowser from 'lib/isBrowser'; import ScrollDirectionContext from 'ui/ScrollDirectionContext';
const SCROLL_DIFF_THRESHOLD = 20; const TOP = 55;
interface Props { interface Props {
onChange: (event: ChangeEvent<HTMLInputElement>) => void; onChange: (event: ChangeEvent<HTMLInputElement>) => void;
onSubmit: (event: FormEvent<HTMLFormElement>) => void; onSubmit: (event: FormEvent<HTMLFormElement>) => void;
withShadow?: boolean;
} }
const SearchBarMobile = ({ onChange, onSubmit }: Props) => { const SearchBarMobile = ({ onChange, onSubmit, withShadow }: Props) => {
const prevScrollPosition = React.useRef(isBrowser() ? window.pageYOffset : 0); const [ isSticky, setIsSticky ] = React.useState(false);
const [ isVisible, setVisibility ] = React.useState(true);
const searchIconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600');
const inputBorderColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.200');
const bgColor = useColorModeValue('white', 'black');
const handleScroll = React.useCallback(() => { const handleScroll = React.useCallback(() => {
const currentScrollPosition = clamp(window.pageYOffset, 0, window.document.body.scrollHeight - window.innerHeight); if (window.pageYOffset !== 0) {
const scrollDiff = currentScrollPosition - prevScrollPosition.current; setIsSticky(true);
} else {
if (Math.abs(scrollDiff) > SCROLL_DIFF_THRESHOLD) { setIsSticky(false);
setVisibility(scrollDiff > 0 ? false : true);
} }
prevScrollPosition.current = currentScrollPosition;
}, []); }, []);
React.useEffect(() => { React.useEffect(() => {
...@@ -46,37 +38,46 @@ const SearchBarMobile = ({ onChange, onSubmit }: Props) => { ...@@ -46,37 +38,46 @@ const SearchBarMobile = ({ onChange, onSubmit }: Props) => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
const searchIconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600');
const inputBorderColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.200');
const bgColor = useColorModeValue('white', 'black');
return ( return (
<chakra.form <ScrollDirectionContext.Consumer>
noValidate { (scrollDirection) => (
onSubmit={ onSubmit } <chakra.form
paddingX={ 4 } noValidate
paddingTop={ 1 } onSubmit={ onSubmit }
paddingBottom={ 2 } paddingX={ 4 }
position="fixed" paddingTop={ 1 }
top="56px" paddingBottom={ 2 }
left="0" position="fixed"
zIndex="docked" top={ `${ TOP }px` }
bgColor={ bgColor } left="0"
transform={ isVisible ? 'translateY(0)' : 'translateY(-100%)' } zIndex="sticky1"
transitionProperty="transform" bgColor={ bgColor }
transitionDuration="slow" transform={ scrollDirection !== 'down' ? 'translateY(0)' : 'translateY(-100%)' }
display={{ base: 'block', lg: 'none' }} transitionProperty="transform,box-shadow"
w="100%" transitionDuration="slow"
> display={{ base: 'block', lg: 'none' }}
<InputGroup size="sm"> w="100%"
<InputLeftElement > boxShadow={ withShadow && scrollDirection !== 'down' && isSticky ? 'md' : 'none' }
<Icon as={ searchIcon } boxSize={ 4 } color={ searchIconColor }/> >
</InputLeftElement> <InputGroup size="sm">
<Input <InputLeftElement >
paddingInlineStart="38px" <Icon as={ searchIcon } boxSize={ 4 } color={ searchIconColor }/>
placeholder="Search by addresses / ... " </InputLeftElement>
ml="1px" <Input
onChange={ onChange } paddingInlineStart="38px"
borderColor={ inputBorderColor } placeholder="Search by addresses / ... "
/> ml="1px"
</InputGroup> onChange={ onChange }
</chakra.form> borderColor={ inputBorderColor }
/>
</InputGroup>
</chakra.form>
) }
</ScrollDirectionContext.Consumer>
); );
}; };
......
import { Table, Thead, Tbody, Tr, Th, TableContainer, Link, Icon } from '@chakra-ui/react'; import { Table, Tbody, Tr, Th, Link, Icon } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { InternalTransaction } from 'types/api/internalTransaction'; import type { InternalTransaction } from 'types/api/internalTransaction';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import arrowIcon from 'icons/arrows/east.svg'; import arrowIcon from 'icons/arrows/east.svg';
import { default as Thead } from 'ui/shared/TheadSticky';
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';
...@@ -18,35 +19,33 @@ const TxInternalsTable = ({ data, sort, onSortToggle }: Props) => { ...@@ -18,35 +19,33 @@ const TxInternalsTable = ({ data, sort, onSortToggle }: Props) => {
const sortIconTransform = sort?.includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)'; const sortIconTransform = sort?.includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)';
return ( return (
<TableContainer width="100%" mt={ 6 }> <Table variant="simple" size="sm" mt={ 6 }>
<Table variant="simple" size="sm"> <Thead top={ 0 }>
<Thead> <Tr>
<Tr> <Th width="28%">Type</Th>
<Th width="28%">Type</Th> <Th width="20%">From</Th>
<Th width="20%">From</Th> <Th width="24px" px={ 0 }/>
<Th width="24px" px={ 0 }/> <Th width="20%">To</Th>
<Th width="20%">To</Th> <Th width="16%" isNumeric>
<Th width="16%" isNumeric> <Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('value') } columnGap={ 1 }>
<Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('value') } columnGap={ 1 }> { sort?.includes('value') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> }
{ sort?.includes('value') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> }
Value { appConfig.network.currency.symbol } Value { appConfig.network.currency.symbol }
</Link> </Link>
</Th> </Th>
<Th width="16%" isNumeric> <Th width="16%" isNumeric>
<Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('gas-limit') } columnGap={ 1 }> <Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('gas-limit') } columnGap={ 1 }>
{ sort?.includes('gas-limit') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> } { sort?.includes('gas-limit') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> }
Gas limit { appConfig.network.currency.symbol } Gas limit { appConfig.network.currency.symbol }
</Link> </Link>
</Th> </Th>
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{ data.map((item) => ( { data.map((item) => (
<TxInternalsTableItem key={ item.transaction_hash } { ...item }/> <TxInternalsTableItem key={ item.transaction_hash } { ...item }/>
)) } )) }
</Tbody> </Tbody>
</Table> </Table>
</TableContainer>
); );
}; };
......
import { import {
Table, Table,
Thead,
Tbody, Tbody,
Tr, Tr,
Th, Th,
TableContainer,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import capitalize from 'lodash/capitalize'; import capitalize from 'lodash/capitalize';
import React from 'react'; import React from 'react';
...@@ -12,27 +10,26 @@ import React from 'react'; ...@@ -12,27 +10,26 @@ import React from 'react';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import { data } from 'data/txState'; import { data } from 'data/txState';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle'; import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import { default as Thead } from 'ui/shared/TheadSticky';
import TxStateTableItem from 'ui/tx/state/TxStateTableItem'; import TxStateTableItem from 'ui/tx/state/TxStateTableItem';
const TxStateTable = () => { const TxStateTable = () => {
return ( return (
<TableContainer width="100%" mt={ 6 }> <Table variant="simple" minWidth="950px" size="sm" w="auto" mt={ 6 }>
<Table variant="simple" minWidth="950px" size="sm" w="auto"> <Thead top={ 0 }>
<Thead> <Tr>
<Tr> <Th width="92px">Storage</Th>
<Th width="92px">Storage</Th> <Th width="146px">Address</Th>
<Th width="146px">Address</Th> <Th width="120px">{ capitalize(getNetworkValidatorTitle()) }</Th>
<Th width="120px">{ capitalize(getNetworkValidatorTitle()) }</Th> <Th width="33%" isNumeric>{ `After ${ appConfig.network.currency.symbol }` }</Th>
<Th width="33%" isNumeric>{ `After ${ appConfig.network.currency.symbol }` }</Th> <Th width="33%" isNumeric>{ `Before ${ appConfig.network.currency.symbol }` }</Th>
<Th width="33%" isNumeric>{ `Before ${ appConfig.network.currency.symbol }` }</Th> <Th width="33%" isNumeric>{ `State difference ${ appConfig.network.currency.symbol }` }</Th>
<Th width="33%" isNumeric>{ `State difference ${ appConfig.network.currency.symbol }` }</Th> </Tr>
</Tr> </Thead>
</Thead> <Tbody>
<Tbody> { data.map((item, index) => <TxStateTableItem txStateItem={ item } key={ index }/>) }
{ data.map((item, index) => <TxStateTableItem txStateItem={ item } key={ index }/>) } </Tbody>
</Tbody> </Table>
</Table>
</TableContainer>
); );
}; };
......
import { Alert, Box, HStack, Show, Button } from '@chakra-ui/react'; import { Alert, Box, Show } from '@chakra-ui/react';
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import type { TTxsFilters } from 'types/api/txsFilters'; import type { TTxsFilters } from 'types/api/txsFilters';
...@@ -6,15 +6,11 @@ import type { QueryKeys } from 'types/client/queries'; ...@@ -6,15 +6,11 @@ import type { QueryKeys } from 'types/client/queries';
import type { Sort } from 'types/client/txs-sort'; import type { Sort } from 'types/client/txs-sort';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import useIsMobile from 'lib/hooks/useIsMobile';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
// import FilterInput from 'ui/shared/FilterInput';
import Pagination from 'ui/shared/Pagination';
// import TxsFilters from './TxsFilters'; import TxsHeader from './TxsHeader';
import TxsSkeletonDesktop from './TxsSkeletonDesktop'; import TxsSkeletonDesktop from './TxsSkeletonDesktop';
import TxsSkeletonMobile from './TxsSkeletonMobile'; import TxsSkeletonMobile from './TxsSkeletonMobile';
import TxsSorting from './TxsSorting';
import TxsWithSort from './TxsWithSort'; import TxsWithSort from './TxsWithSort';
import useQueryWithPages from './useQueryWithPages'; import useQueryWithPages from './useQueryWithPages';
...@@ -64,16 +60,10 @@ const TxsContent = ({ ...@@ -64,16 +60,10 @@ const TxsContent = ({
data, data,
isLoading, isLoading,
isError, isError,
page, pagination,
onPrevPageClick,
onNextPageClick,
hasPagination,
resetPage,
} = useQueryWithPages(apiPath, queryName, stateFilter && { filter: stateFilter }); } = useQueryWithPages(apiPath, queryName, stateFilter && { filter: stateFilter });
// } = useQueryWithPages({ ...filters, filter: stateFilter, apiPath }); // } = useQueryWithPages({ ...filters, filter: stateFilter, apiPath });
const isMobile = useIsMobile(false);
if (isError) { if (isError) {
return <DataFetchAlert/>; return <DataFetchAlert/>;
} }
...@@ -95,47 +85,16 @@ const TxsContent = ({ ...@@ -95,47 +85,16 @@ const TxsContent = ({
content = <TxsWithSort txs={ txs } sorting={ sorting } sort={ sort }/>; content = <TxsWithSort txs={ txs } sorting={ sorting } sort={ sort }/>;
} }
const paginationProps = {
...pagination,
hasNextPage: data?.next_page_params !== undefined && data?.next_page_params !== null && Object.keys(data?.next_page_params).length > 0,
};
return ( return (
<> <>
{ showDescription && <Box mb={ 12 }>Only the first 10,000 elements are displayed</Box> } { showDescription && <Box mb={ 12 }>Only the first 10,000 elements are displayed</Box> }
<HStack mb={ 6 }> <TxsHeader sorting={ sorting } setSorting={ setSorting } paginationProps={ paginationProps }/>
{ /* api is not implemented */ }
{ /* <TxsFilters
filters={ filters }
onFiltersChange={ setFilters }
appliedFiltersNum={ 0 }
/> */ }
{ isMobile && (
<TxsSorting
// eslint-disable-next-line react/jsx-no-bind
isActive={ Boolean(sorting) }
setSorting={ setSorting }
sorting={ sorting }
/>
) }
{ /* api is not implemented */ }
{ /* <FilterInput
// eslint-disable-next-line react/jsx-no-bind
onChange={ () => {} }
maxW="360px"
size="xs"
placeholder="Search by addresses, hash, method..."
/> */ }
</HStack>
{ content } { content }
<Box mx={{ base: 0, lg: 6 }} my={{ base: 6, lg: 3 }}>
{ hasPagination ? (
<Pagination
currentPage={ page }
hasNextPage={ data?.next_page_params !== undefined && Object.keys(data?.next_page_params || {}).length > 0 }
onNextPageClick={ onNextPageClick }
onPrevPageClick={ onPrevPageClick }
/>
) :
// temporary button, waiting for new pagination mockups
<Button onClick={ resetPage }>Reset</Button>
}
</Box>
</> </>
); );
}; };
......
import { HStack, Flex, useColorModeValue } from '@chakra-ui/react';
import throttle from 'lodash/throttle';
import React, { useCallback } from 'react';
import type { Sort } from 'types/client/txs-sort';
import useIsMobile from 'lib/hooks/useIsMobile';
// import FilterInput from 'ui/shared/FilterInput';
import ScrollDirectionContext from 'ui/ScrollDirectionContext';
import Pagination from 'ui/shared/Pagination';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import TxsSorting from 'ui/txs/TxsSorting';
// import TxsFilters from './TxsFilters';
type Props = {
sorting: Sort;
setSorting: (val: Sort | ((val: Sort) => Sort)) => void;
paginationProps: PaginationProps;
}
const TOP_UP = 106;
const TOP_DOWN = 0;
const TxsHeader = ({ sorting, setSorting, paginationProps }: Props) => {
const [ isSticky, setIsSticky ] = React.useState(false);
const ref = React.useRef<HTMLDivElement>(null);
const isMobile = useIsMobile(false);
const handleScroll = useCallback(() => {
if (
Number(ref.current?.getBoundingClientRect().y) < TOP_UP + 5
) {
setIsSticky(true);
} else {
setIsSticky(false);
}
}, [ ]);
React.useEffect(() => {
const throttledHandleScroll = throttle(handleScroll, 300);
window.addEventListener('scroll', throttledHandleScroll);
return () => {
window.removeEventListener('scroll', throttledHandleScroll);
};
// replicate componentDidMount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ ]);
const bgColor = useColorModeValue('white', 'black');
return (
<ScrollDirectionContext.Consumer>
{ (scrollDirection) => (
<Flex
backgroundColor={ bgColor }
mt={ -6 }
pt={ 6 }
pb={ 6 }
mx={{ base: -4, lg: 0 }}
px={{ base: 4, lg: 0 }}
justifyContent="space-between"
width={{ base: '100vw', lg: 'unset' }}
position="sticky"
top={{ base: scrollDirection === 'down' ? `${ TOP_DOWN }px` : `${ TOP_UP }px`, lg: 0 }}
transitionProperty="top,box-shadow"
transitionDuration="slow"
zIndex={{ base: 'sticky2', lg: 'docked' }}
boxShadow={{ base: isSticky ? 'md' : 'none', lg: 'none' }}
ref={ ref }
>
<HStack>
{ /* api is not implemented */ }
{ /* <TxsFilters
filters={ filters }
onFiltersChange={ setFilters }
appliedFiltersNum={ 0 }
/> */ }
{ isMobile && (
<TxsSorting
isActive={ Boolean(sorting) }
setSorting={ setSorting }
sorting={ sorting }
/>
) }
{ /* api is not implemented */ }
{ /* <FilterInput
// eslint-disable-next-line react/jsx-no-bind
onChange={ () => {} }
maxW="360px"
size="xs"
placeholder="Search by addresses, hash, method..."
/> */ }
</HStack>
<Pagination { ...paginationProps }/>
</Flex>
) }
</ScrollDirectionContext.Consumer>
);
};
export default TxsHeader;
import { Link, Table, Thead, Tbody, Tr, Th, TableContainer, Icon } from '@chakra-ui/react'; import { Link, Table, Tbody, Tr, Th, Icon } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Transaction } from 'types/api/transaction'; import type { Transaction } from 'types/api/transaction';
...@@ -6,6 +6,7 @@ import type { Sort } from 'types/client/txs-sort'; ...@@ -6,6 +6,7 @@ import type { Sort } from 'types/client/txs-sort';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import rightArrowIcon from 'icons/arrows/east.svg'; import rightArrowIcon from 'icons/arrows/east.svg';
import TheadSticky from 'ui/shared/TheadSticky';
import TxsTableItem from './TxsTableItem'; import TxsTableItem from './TxsTableItem';
...@@ -17,44 +18,42 @@ type Props = { ...@@ -17,44 +18,42 @@ type Props = {
const TxsTable = ({ txs, sort, sorting }: Props) => { const TxsTable = ({ txs, sort, sorting }: Props) => {
return ( return (
<TableContainer width="100%" mt={ 6 }> <Table variant="simple" minWidth="810px" size="xs">
<Table variant="simple" minWidth="810px" size="xs"> <TheadSticky top={ 80 }>
<Thead> <Tr>
<Tr> <Th width="54px"></Th>
<Th width="54px"></Th> <Th width="20%">Type</Th>
<Th width="20%">Type</Th> <Th width="18%">Txn hash</Th>
<Th width="18%">Txn hash</Th> <Th width="15%">Method</Th>
<Th width="15%">Method</Th> <Th width="11%">Block</Th>
<Th width="11%">Block</Th> <Th width={{ xl: '128px', base: '66px' }}>From</Th>
<Th width={{ xl: '128px', base: '66px' }}>From</Th> <Th width={{ xl: '36px', base: '0' }}></Th>
<Th width={{ xl: '36px', base: '0' }}></Th> <Th width={{ xl: '128px', base: '66px' }}>To</Th>
<Th width={{ xl: '128px', base: '66px' }}>To</Th> <Th width="18%" isNumeric>
<Th width="18%" isNumeric> <Link onClick={ sort('val') } display="flex" justifyContent="end">
<Link onClick={ sort('val') } display="flex" justifyContent="end"> { sorting === 'val-asc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(-90deg)"/> }
{ sorting === 'val-asc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(-90deg)"/> } { sorting === 'val-desc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(90deg)"/> }
{ sorting === 'val-desc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(90deg)"/> } { `Value ${ appConfig.network.currency.symbol }` }
{ `Value ${ appConfig.network.currency.symbol }` } </Link>
</Link> </Th>
</Th> <Th width="18%" isNumeric pr={ 5 }>
<Th width="18%" isNumeric pr={ 5 }> <Link onClick={ sort('fee') } display="flex" justifyContent="end">
<Link onClick={ sort('fee') } display="flex" justifyContent="end"> { sorting === 'fee-asc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(-90deg)"/> }
{ sorting === 'fee-asc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(-90deg)"/> } { sorting === 'fee-desc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(90deg)"/> }
{ sorting === 'fee-desc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(90deg)"/> } { `Fee ${ appConfig.network.currency.symbol }` }
{ `Fee ${ appConfig.network.currency.symbol }` } </Link>
</Link> </Th>
</Th> </Tr>
</Tr> </TheadSticky>
</Thead> <Tbody>
<Tbody> { txs.map((item) => (
{ txs.map((item) => ( <TxsTableItem
<TxsTableItem key={ item.hash }
key={ item.hash } tx={ item }
tx={ item } />
/> )) }
)) } </Tbody>
</Tbody> </Table>
</Table>
</TableContainer>
); );
}; };
......
...@@ -63,6 +63,7 @@ export default function useQueryWithPages(apiPath: string, queryName: QueryKeys, ...@@ -63,6 +63,7 @@ export default function useQueryWithPages(apiPath: string, queryName: QueryKeys,
// we dont have pagination params for the first page // we dont have pagination params for the first page
let nextPageQuery: typeof router.query; let nextPageQuery: typeof router.query;
if (page === 2) { if (page === 2) {
queryClient.clear();
nextPageQuery = omit(router.query, PAGINATION_FIELDS); nextPageQuery = omit(router.query, PAGINATION_FIELDS);
} else { } else {
const nextPageParams = { ...pageParams[page - 2] }; const nextPageParams = { ...pageParams[page - 2] };
...@@ -75,16 +76,26 @@ export default function useQueryWithPages(apiPath: string, queryName: QueryKeys, ...@@ -75,16 +76,26 @@ export default function useQueryWithPages(apiPath: string, queryName: QueryKeys,
animateScroll.scrollToTop({ duration: 0 }); animateScroll.scrollToTop({ duration: 0 });
setPage(prev => prev - 1); setPage(prev => prev - 1);
}); });
}, [ router, page, pageParams ]); }, [ router, page, pageParams, queryClient ]);
const resetPage = useCallback(() => { const resetPage = useCallback(() => {
queryClient.clear(); queryClient.clear();
animateScroll.scrollToTop({ duration: 0 }); router.push({ pathname: router.pathname, query: omit(router.query, PAGINATION_FIELDS) }, undefined, { shallow: true }).then(() => {
router.push({ pathname: router.pathname, query: omit(router.query, PAGINATION_FIELDS) }, undefined, { shallow: true }); animateScroll.scrollToTop({ duration: 0 });
setPage(1);
setPageParams([ {} ]);
});
}, [ router, queryClient ]); }, [ router, queryClient ]);
// if there are pagination params on the initial page, we shouldn't show pagination const hasPaginationParams = Object.keys(currPageParams).length > 0;
const hasPagination = !(page === 1 && Object.keys(currPageParams).length > 0);
return { data, isError, isLoading, page, onNextPageClick, onPrevPageClick, hasPagination, resetPage }; const pagination = {
page,
onNextPageClick,
onPrevPageClick,
hasPaginationParams,
resetPage,
};
return { data, isError, isLoading, pagination };
} }
...@@ -4,7 +4,6 @@ import { ...@@ -4,7 +4,6 @@ import {
Tbody, Tbody,
Tr, Tr,
Th, Th,
TableContainer,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -20,28 +19,26 @@ interface Props { ...@@ -20,28 +19,26 @@ interface Props {
const WatchlistTable = ({ data, onDeleteClick, onEditClick }: Props) => { const WatchlistTable = ({ data, onDeleteClick, onEditClick }: Props) => {
return ( return (
<TableContainer width="100%"> <Table variant="simple" minWidth="600px">
<Table variant="simple" minWidth="600px"> <Thead>
<Thead> <Tr>
<Tr> <Th width="70%">Address</Th>
<Th width="70%">Address</Th> <Th width="30%">Private tag</Th>
<Th width="30%">Private tag</Th> <Th width="160px">Email notification</Th>
<Th width="160px">Email notification</Th> <Th width="108px"></Th>
<Th width="108px"></Th> </Tr>
</Tr> </Thead>
</Thead> <Tbody>
<Tbody> { data.map((item) => (
{ data.map((item) => ( <WatchlistTableItem
<WatchlistTableItem item={ item }
item={ item } key={ item.address_hash }
key={ item.address_hash } onDeleteClick={ onDeleteClick }
onDeleteClick={ onDeleteClick } onEditClick={ onEditClick }
onEditClick={ onEditClick } />
/> )) }
)) } </Tbody>
</Tbody> </Table>
</Table>
</TableContainer>
); );
}; };
......
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