import { ArrowPathIcon } from "@heroicons/react/24/solid"; import clsx from "clsx"; import { useEffect, useState } from "react"; import { Pagination as ShadCnPagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, } from "../ui/pagination"; import * as React from "react"; import { ChevronDoubleLeftIcon, ChevronDoubleRightIcon } from "@heroicons/react/16/solid"; type PaginationItemWrapperProps = { /** * Whether a page is currently loading. */ isLoadingPage: boolean; /** * The children to render. */ children: React.ReactNode; }; function PaginationItemWrapper({ isLoadingPage, children }: PaginationItemWrapperProps) { return ( {children} ); } type Props = { /** * If true, the pagination will be rendered as a mobile-friendly pagination. */ mobilePagination: boolean; /** * The current page. */ page: number; /** * The total number of pages. */ totalPages: number; /** * The page to show a loading icon on. */ loadingPage: number | undefined; /** * Callback function that is called when the user clicks on a page number. */ onPageChange: (page: number) => void; /** * Optional callback to generate the URL for each page. */ generatePageUrl?: (page: number) => string; }; export default function Pagination({ mobilePagination, page, totalPages, loadingPage, onPageChange, generatePageUrl, }: Props) { totalPages = Math.round(totalPages); const isLoading = loadingPage !== undefined; const [currentPage, setCurrentPage] = useState(page); useEffect(() => { setCurrentPage(page); }, [page]); const handlePageChange = (newPage: number) => { if (newPage < 1 || newPage > totalPages || newPage === currentPage || isLoading) { return; } setCurrentPage(newPage); onPageChange(newPage); }; const handleLinkClick = (newPage: number, event: React.MouseEvent) => { event.preventDefault(); // Prevent default navigation behavior // Check if the new page is valid if (newPage < 1 || newPage > totalPages || newPage === currentPage || isLoading) { return; } handlePageChange(newPage); }; const renderPageNumbers = () => { const pageNumbers = []; const maxPagesToShow = mobilePagination ? 3 : 4; let startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2)); const endPage = Math.min(totalPages, startPage + maxPagesToShow - 1); if (endPage - startPage < maxPagesToShow - 1) { startPage = Math.max(1, endPage - maxPagesToShow + 1); } if (startPage > 1) { pageNumbers.push( <> {!mobilePagination && ( handleLinkClick(1, e)}> 1 )} {startPage > 2 && !mobilePagination && ( )} ); } for (let i = startPage; i <= endPage; i++) { pageNumbers.push( handleLinkClick(i, e)} > {loadingPage === i ? : i} ); } return pageNumbers; }; return ( {/* ">>" before the Previous button in mobile mode */} {mobilePagination && ( handleLinkClick(1, e)}> )} {/* Previous button - disabled on the first page */} 1 && generatePageUrl ? generatePageUrl(currentPage - 1) : ""} onClick={e => handleLinkClick(currentPage - 1, e)} aria-disabled={currentPage === 1} className={clsx(currentPage === 1 && "cursor-not-allowed")} /> {renderPageNumbers()} {!mobilePagination && currentPage < totalPages && totalPages - currentPage > 2 && ( <> handleLinkClick(totalPages, e)} > {totalPages} )} {/* Next button - disabled on the last page */} handleLinkClick(currentPage + 1, e)} aria-disabled={currentPage === totalPages} className={clsx(currentPage === totalPages && "cursor-not-allowed")} /> {/* ">>" after the Next button in mobile mode */} {mobilePagination && ( handleLinkClick(totalPages, e)} > )} ); }