Pagination.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import { PER_PAGE } from 'lib/constants';
  2. import usePagination from 'lib/hooks/usePagination';
  3. import { range } from 'lodash';
  4. import React from 'react';
  5. import PageControl from 'components/common/Pagination/PageControl';
  6. import useSearch from 'lib/hooks/useSearch';
  7. import * as S from './Pagination.styled';
  8. export interface PaginationProps {
  9. totalPages: number;
  10. }
  11. const NEIGHBOURS = 2;
  12. const Pagination: React.FC<PaginationProps> = ({ totalPages }) => {
  13. const { page, perPage, pathname } = usePagination();
  14. const [searchText] = useSearch();
  15. const currentPage = page || 1;
  16. const currentPerPage = perPage || PER_PAGE;
  17. const searchParam = searchText ? `&q=${searchText}` : '';
  18. const getPath = (newPage: number) =>
  19. `${pathname}?page=${Math.max(
  20. newPage,
  21. 1
  22. )}&perPage=${currentPerPage}${searchParam}`;
  23. const pages = React.useMemo(() => {
  24. // Total visible numbers: neighbours, current, first & last
  25. const totalNumbers = NEIGHBOURS * 2 + 3;
  26. // totalNumbers + `...`*2
  27. const totalBlocks = totalNumbers + 2;
  28. if (totalPages <= totalBlocks) {
  29. return range(1, totalPages + 1);
  30. }
  31. const startPage = Math.max(
  32. 2,
  33. Math.min(currentPage - NEIGHBOURS, totalPages)
  34. );
  35. const endPage = Math.min(
  36. totalPages - 1,
  37. Math.min(currentPage + NEIGHBOURS, totalPages)
  38. );
  39. let p = range(startPage, endPage + 1);
  40. const hasLeftSpill = startPage > 2;
  41. const hasRightSpill = totalPages - endPage > 1;
  42. const spillOffset = totalNumbers - (p.length + 1);
  43. switch (true) {
  44. case hasLeftSpill && !hasRightSpill: {
  45. p = [...range(startPage - spillOffset - 1, startPage - 1), ...p];
  46. break;
  47. }
  48. case !hasLeftSpill && hasRightSpill: {
  49. p = [...p, ...range(endPage + 1, endPage + spillOffset + 1)];
  50. break;
  51. }
  52. default:
  53. break;
  54. }
  55. return p;
  56. }, []);
  57. return (
  58. <S.Wrapper role="navigation" aria-label="pagination">
  59. {currentPage > 1 ? (
  60. <S.PaginationButton to={getPath(currentPage - 1)}>
  61. Previous
  62. </S.PaginationButton>
  63. ) : (
  64. <S.DisabledButton disabled>Previous</S.DisabledButton>
  65. )}
  66. {totalPages > 1 && (
  67. <ul>
  68. {!pages.includes(1) && (
  69. <PageControl
  70. page={1}
  71. current={currentPage === 1}
  72. url={getPath(1)}
  73. />
  74. )}
  75. {!pages.includes(2) && (
  76. <li>
  77. <span className="pagination-ellipsis">&hellip;</span>
  78. </li>
  79. )}
  80. {pages.map((p) => (
  81. <PageControl
  82. key={`page-${p}`}
  83. page={p}
  84. current={p === currentPage}
  85. url={getPath(p)}
  86. />
  87. ))}
  88. {!pages.includes(totalPages - 1) && (
  89. <li>
  90. <span className="pagination-ellipsis">&hellip;</span>
  91. </li>
  92. )}
  93. {!pages.includes(totalPages) && (
  94. <PageControl
  95. page={totalPages}
  96. current={currentPage === totalPages}
  97. url={getPath(totalPages)}
  98. />
  99. )}
  100. </ul>
  101. )}
  102. {currentPage < totalPages ? (
  103. <S.PaginationButton to={getPath(currentPage + 1)}>
  104. Next
  105. </S.PaginationButton>
  106. ) : (
  107. <S.DisabledButton disabled>Next</S.DisabledButton>
  108. )}
  109. </S.Wrapper>
  110. );
  111. };
  112. export default Pagination;