Commit bd2ee8bc authored by isstuev's avatar isstuev

sticky pagination

parent 9d3f151f
import clamp from 'lodash/clamp';
import throttle from 'lodash/throttle';
import React from 'react';
import isBrowser from 'lib/isBrowser';
const SCROLL_DIFF_THRESHOLD = 20;
export default function useScrollVisibility(direction: 'up' | 'down') {
const prevScrollPosition = React.useRef(isBrowser() ? window.pageYOffset : 0);
const [ isVisible, setVisibility ] = React.useState(true);
const handleScroll = React.useCallback(() => {
const currentScrollPosition = clamp(window.pageYOffset, 0, window.document.body.scrollHeight - window.innerHeight);
const scrollDiff = currentScrollPosition - prevScrollPosition.current;
if (Math.abs(scrollDiff) > SCROLL_DIFF_THRESHOLD) {
setVisibility(direction === 'up' ? scrollDiff < 0 : scrollDiff > 0);
}
prevScrollPosition.current = currentScrollPosition;
}, [ direction ]);
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 isVisible;
}
...@@ -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>
</> </>
); );
......
...@@ -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 { 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 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 useScrollVisibility from 'lib/hooks/useScrollVisibility';
const SCROLL_DIFF_THRESHOLD = 20;
interface Props { interface Props {
onChange: (event: ChangeEvent<HTMLInputElement>) => void; onChange: (event: ChangeEvent<HTMLInputElement>) => void;
...@@ -16,36 +12,12 @@ interface Props { ...@@ -16,36 +12,12 @@ interface Props {
const SearchBarMobile = ({ onChange, onSubmit }: Props) => { const SearchBarMobile = ({ onChange, onSubmit }: Props) => {
const prevScrollPosition = React.useRef(isBrowser() ? window.pageYOffset : 0); const isVisible = useScrollVisibility('up');
const [ isVisible, setVisibility ] = React.useState(true);
const searchIconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600'); const searchIconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600');
const inputBorderColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.200'); const inputBorderColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.200');
const bgColor = useColorModeValue('white', 'black'); const bgColor = useColorModeValue('white', 'black');
const handleScroll = React.useCallback(() => {
const currentScrollPosition = clamp(window.pageYOffset, 0, window.document.body.scrollHeight - window.innerHeight);
const scrollDiff = currentScrollPosition - prevScrollPosition.current;
if (Math.abs(scrollDiff) > SCROLL_DIFF_THRESHOLD) {
setVisibility(scrollDiff > 0 ? false : true);
}
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 ( return (
<chakra.form <chakra.form
noValidate noValidate
......
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';
...@@ -11,7 +11,7 @@ import DataFetchAlert from 'ui/shared/DataFetchAlert'; ...@@ -11,7 +11,7 @@ import DataFetchAlert from 'ui/shared/DataFetchAlert';
// import FilterInput from 'ui/shared/FilterInput'; // import FilterInput from 'ui/shared/FilterInput';
import Pagination from 'ui/shared/Pagination'; 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 TxsSorting from './TxsSorting';
...@@ -64,16 +64,10 @@ const TxsContent = ({ ...@@ -64,16 +64,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 +89,16 @@ const TxsContent = ({ ...@@ -95,47 +89,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 && 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 } 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 } from '@chakra-ui/react';
import React from 'react';
import type { Sort } from 'types/client/txs-sort';
import useIsMobile from 'lib/hooks/useIsMobile';
// import FilterInput from 'ui/shared/FilterInput';
import useScrollVisibility from 'lib/hooks/useScrollVisibility';
import Pagination from 'ui/shared/Pagination';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import SortButton from 'ui/shared/SortButton';
// import TxsFilters from './TxsFilters';
type Props = {
sorting: Sort;
paginationProps: PaginationProps;
}
const TxsHeader = ({ sorting, paginationProps }: Props) => {
const isVisible = useScrollVisibility('down');
const isMobile = useIsMobile(false);
return (
<Flex
backgroundColor="white"
mt={ -6 }
pt={ 6 }
pb={ 6 }
justifyContent="space-between"
width="100%"
position="sticky"
top={{ base: isVisible ? '56px' : '-56px', lg: 0 }}
transitionDuration="slow"
transitionProperty="top"
zIndex={{ base: 0, lg: 'docked' }}
>
<HStack>
{ /* api is not implemented */ }
{ /* <TxsFilters
filters={ filters }
onFiltersChange={ setFilters }
appliedFiltersNum={ 0 }
/> */ }
{ isMobile && (
<SortButton
// eslint-disable-next-line react/jsx-no-bind
handleSort={ () => {} }
isSortActive={ Boolean(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>
);
};
export default TxsHeader;
...@@ -17,7 +17,7 @@ type Props = { ...@@ -17,7 +17,7 @@ type Props = {
const TxsTable = ({ txs, sort, sorting }: Props) => { const TxsTable = ({ txs, sort, sorting }: Props) => {
return ( return (
<TableContainer width="100%" mt={ 6 }> <TableContainer width="100%">
<Table variant="simple" minWidth="810px" size="xs"> <Table variant="simple" minWidth="810px" size="xs">
<Thead> <Thead>
<Tr> <Tr>
......
...@@ -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 };
} }
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