Pagination.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. "use client";
  2. import { createUrl } from "@/utils/helper";
  3. import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/outline";
  4. import clsx from "clsx";
  5. import { usePathname, useSearchParams, useRouter } from "next/navigation";
  6. export default function Pagination({
  7. itemsPerPage,
  8. itemsTotal,
  9. currentPage,
  10. nextCursor,
  11. prevCursor,
  12. }: {
  13. itemsPerPage: number;
  14. itemsTotal: number;
  15. currentPage: number;
  16. nextCursor?: string;
  17. prevCursor?: string;
  18. }) {
  19. const router = useRouter();
  20. const pathname = usePathname();
  21. const currentParams = useSearchParams();
  22. const pageCount = Math.ceil(itemsTotal / itemsPerPage);
  23. const handlePageClick = (page: number) => {
  24. if (page < 0 || page >= pageCount) return;
  25. const params = new URLSearchParams(currentParams.toString());
  26. params.set("page", String(page + 1));
  27. if (page === currentPage + 1 && nextCursor) {
  28. params.set("cursor", nextCursor);
  29. params.delete("before");
  30. } else if (page === currentPage - 1 && prevCursor) {
  31. params.set("before", prevCursor);
  32. params.delete("cursor");
  33. } else {
  34. params.delete("cursor");
  35. params.delete("before");
  36. }
  37. const newUrl = createUrl(pathname, params);
  38. router.replace(newUrl);
  39. };
  40. const renderDots = () => {
  41. const dots = [];
  42. const maxVisiblePages = 5;
  43. if (pageCount <= maxVisiblePages) {
  44. for (let i = 0; i < pageCount; i++) {
  45. dots.push(renderPageButton(i));
  46. }
  47. } else {
  48. const halfMax = Math.floor(maxVisiblePages / 2);
  49. const start = Math.max(0, currentPage - halfMax);
  50. const end = Math.min(pageCount, start + maxVisiblePages);
  51. if (start > 0) {
  52. dots.push(renderPageButton(0));
  53. if (start > 1) {
  54. dots.push(
  55. <li key="dot-start" className="pagination-dot">...</li>
  56. );
  57. }
  58. }
  59. for (let i = start; i < end; i++) {
  60. dots.push(renderPageButton(i));
  61. }
  62. if (end < pageCount) {
  63. if (end < pageCount - 1) {
  64. dots.push(
  65. <li key="dot-end" className="pagination-dot">...</li>
  66. );
  67. }
  68. dots.push(renderPageButton(pageCount - 1));
  69. }
  70. }
  71. return dots;
  72. };
  73. const renderPageButton = (pageIndex: number) => (
  74. <li
  75. key={pageIndex}
  76. onClick={() => handlePageClick(pageIndex)}
  77. className="rounded-sm cursor-pointer"
  78. >
  79. <button
  80. className={`
  81. flex h-10 w-10 items-center justify-center text-lg rounded-sm duration-300 cursor-pointer
  82. ${pageIndex === currentPage
  83. ? "border !border-gray-300 dark:border-gray-700"
  84. : "text-gray-500 dark:!text-gray-400 hover:border-gray-500 dark:hover:border-gray-700"
  85. }
  86. `}
  87. aria-label={`Goto Page ${pageIndex + 1}`}
  88. aria-current={pageIndex === currentPage}
  89. >
  90. {pageIndex + 1}
  91. </button>
  92. </li>
  93. );
  94. return (
  95. <ul
  96. className="mx-auto h-10 gap-x-2 text-base flex justify-center"
  97. role="navigation"
  98. aria-label="Pagination"
  99. >
  100. <li
  101. key="prev"
  102. onClick={() => handlePageClick(currentPage - 1)}
  103. className={clsx(
  104. "cursor-pointer rounded-lg hover:text-gray-700",
  105. currentPage > 0 ? "block cursor-pointer" : ""
  106. )}
  107. >
  108. <button
  109. className={clsx(
  110. "ml-0 flex h-10 items-center justify-center px-2 leading-tight",
  111. currentPage > 0 ? "cursor-pointer" : "cursor-not-allowed"
  112. )}
  113. aria-label="Previous page"
  114. disabled={currentPage <= 0}
  115. >
  116. <ChevronLeftIcon className="h-5" />
  117. </button>
  118. </li>
  119. {renderDots()}
  120. <li
  121. key="next"
  122. onClick={() => handlePageClick(currentPage + 1)}
  123. className={clsx(
  124. "rounded-lg hover:text-gray-700",
  125. currentPage < pageCount - 1 ? "block cursor-pointer" : ""
  126. )}
  127. >
  128. <button
  129. className={clsx(
  130. "ml-0 flex h-10 items-center justify-center px-2 leading-tight",
  131. currentPage < pageCount - 1 ? "cursor-pointer" : "cursor-not-allowed"
  132. )}
  133. aria-label="Next page"
  134. disabled={currentPage >= pageCount - 1}
  135. >
  136. <ChevronRightIcon className="h-5" />
  137. </button>
  138. </li>
  139. </ul>
  140. );
  141. }