2024-09-12 11:35:37 +01:00
|
|
|
import { ArrowPathIcon } from "@heroicons/react/24/solid";
|
|
|
|
import clsx from "clsx";
|
2024-09-11 21:26:24 +01:00
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
import {
|
2024-09-13 13:45:04 +01:00
|
|
|
Pagination as ShadCnPagination,
|
2024-09-11 21:26:24 +01:00
|
|
|
PaginationContent,
|
|
|
|
PaginationEllipsis,
|
|
|
|
PaginationItem,
|
|
|
|
PaginationLink,
|
|
|
|
PaginationNext,
|
|
|
|
PaginationPrevious,
|
|
|
|
} from "../ui/pagination";
|
|
|
|
|
2024-09-12 11:35:37 +01:00
|
|
|
type PaginationItemWrapperProps = {
|
2024-09-12 11:38:19 +01:00
|
|
|
/**
|
|
|
|
* Whether a page is currently loading.
|
|
|
|
*/
|
2024-09-12 11:35:37 +01:00
|
|
|
isLoadingPage: boolean;
|
2024-09-12 11:38:19 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The children to render.
|
|
|
|
*/
|
2024-09-12 11:35:37 +01:00
|
|
|
children: React.ReactNode;
|
|
|
|
};
|
|
|
|
|
2024-09-30 22:16:55 +01:00
|
|
|
function PaginationItemWrapper({ isLoadingPage, children }: PaginationItemWrapperProps) {
|
2024-09-12 11:35:37 +01:00
|
|
|
return (
|
2024-09-12 11:38:19 +01:00
|
|
|
<PaginationItem
|
|
|
|
className={clsx(isLoadingPage ? "cursor-not-allowed" : "cursor-pointer")}
|
|
|
|
aria-disabled={isLoadingPage}
|
|
|
|
tabIndex={isLoadingPage ? -1 : undefined}
|
|
|
|
>
|
2024-09-12 11:35:37 +01:00
|
|
|
{children}
|
|
|
|
</PaginationItem>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-09-11 21:26:24 +01:00
|
|
|
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;
|
|
|
|
|
2024-09-12 11:35:37 +01:00
|
|
|
/**
|
|
|
|
* The page to show a loading icon on.
|
|
|
|
*/
|
|
|
|
loadingPage: number | undefined;
|
|
|
|
|
2024-09-11 21:26:24 +01:00
|
|
|
/**
|
|
|
|
* Callback function that is called when the user clicks on a page number.
|
|
|
|
*/
|
|
|
|
onPageChange: (page: number) => void;
|
|
|
|
};
|
|
|
|
|
2024-09-30 22:16:55 +01:00
|
|
|
export default function Pagination({ mobilePagination, page, totalPages, loadingPage, onPageChange }: Props) {
|
2024-09-11 21:26:24 +01:00
|
|
|
totalPages = Math.round(totalPages);
|
2024-09-12 11:35:37 +01:00
|
|
|
const isLoading = loadingPage !== undefined;
|
2024-09-11 21:26:24 +01:00
|
|
|
const [currentPage, setCurrentPage] = useState(page);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
setCurrentPage(page);
|
|
|
|
}, [page]);
|
|
|
|
|
|
|
|
const handlePageChange = (newPage: number) => {
|
2024-09-30 22:16:55 +01:00
|
|
|
if (newPage < 1 || newPage > totalPages || newPage == currentPage || isLoading) {
|
2024-09-11 21:26:24 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
setCurrentPage(newPage);
|
|
|
|
onPageChange(newPage);
|
|
|
|
};
|
|
|
|
|
|
|
|
const renderPageNumbers = () => {
|
|
|
|
const pageNumbers = [];
|
|
|
|
const maxPagesToShow = mobilePagination ? 3 : 4;
|
|
|
|
let startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2));
|
2024-09-11 21:27:32 +01:00
|
|
|
const endPage = Math.min(totalPages, startPage + maxPagesToShow - 1);
|
2024-09-11 21:26:24 +01:00
|
|
|
|
|
|
|
if (endPage - startPage < maxPagesToShow - 1) {
|
|
|
|
startPage = Math.max(1, endPage - maxPagesToShow + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Show "Jump to Start" with Ellipsis if currentPage is greater than 3 in desktop view
|
|
|
|
if (startPage > 1 && !mobilePagination) {
|
|
|
|
pageNumbers.push(
|
|
|
|
<>
|
2024-09-12 11:35:37 +01:00
|
|
|
<PaginationItemWrapper key="start" isLoadingPage={isLoading}>
|
2024-09-30 22:16:55 +01:00
|
|
|
<PaginationLink onClick={() => handlePageChange(1)}>1</PaginationLink>
|
2024-09-12 11:35:37 +01:00
|
|
|
</PaginationItemWrapper>
|
2024-09-12 15:08:25 +01:00
|
|
|
{/* Only show ellipsis if more than 2 pages from the start */}
|
|
|
|
{startPage > 2 && (
|
2024-09-30 22:16:55 +01:00
|
|
|
<PaginationItemWrapper key="ellipsis-start" isLoadingPage={isLoading}>
|
2024-09-12 15:08:25 +01:00
|
|
|
<PaginationEllipsis />
|
|
|
|
</PaginationItemWrapper>
|
|
|
|
)}
|
2024-09-30 22:16:55 +01:00
|
|
|
</>
|
2024-09-11 21:26:24 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate page numbers between startPage and endPage for desktop view
|
|
|
|
for (let i = startPage; i <= endPage; i++) {
|
|
|
|
pageNumbers.push(
|
2024-09-12 11:35:37 +01:00
|
|
|
<PaginationItemWrapper key={i} isLoadingPage={isLoading}>
|
2024-09-30 22:16:55 +01:00
|
|
|
<PaginationLink isActive={i === currentPage} onClick={() => handlePageChange(i)}>
|
|
|
|
{loadingPage === i ? <ArrowPathIcon className="w-4 h-4 animate-spin" /> : i}
|
2024-09-11 21:26:24 +01:00
|
|
|
</PaginationLink>
|
2024-09-30 22:16:55 +01:00
|
|
|
</PaginationItemWrapper>
|
2024-09-11 21:26:24 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return pageNumbers;
|
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
|
|
|
<ShadCnPagination className="select-none">
|
|
|
|
<PaginationContent>
|
|
|
|
{/* Previous button for mobile and desktop */}
|
2024-09-12 11:35:37 +01:00
|
|
|
<PaginationItemWrapper isLoadingPage={isLoading}>
|
2024-09-30 22:16:55 +01:00
|
|
|
<PaginationPrevious onClick={() => handlePageChange(currentPage - 1)} />
|
2024-09-12 11:35:37 +01:00
|
|
|
</PaginationItemWrapper>
|
2024-09-11 21:26:24 +01:00
|
|
|
|
|
|
|
{renderPageNumbers()}
|
|
|
|
|
|
|
|
{/* For desktop, show ellipsis and link to the last page */}
|
2024-09-30 22:16:55 +01:00
|
|
|
{!mobilePagination && currentPage < totalPages && totalPages - currentPage > 2 && (
|
|
|
|
<>
|
|
|
|
<PaginationItemWrapper key="ellipsis-end" isLoadingPage={isLoading}>
|
|
|
|
<PaginationEllipsis className="cursor-default" />
|
|
|
|
</PaginationItemWrapper>
|
|
|
|
<PaginationItemWrapper key="end" isLoadingPage={isLoading}>
|
|
|
|
<PaginationLink onClick={() => handlePageChange(totalPages)}>{totalPages}</PaginationLink>
|
|
|
|
</PaginationItemWrapper>
|
|
|
|
</>
|
|
|
|
)}
|
2024-09-11 21:26:24 +01:00
|
|
|
|
|
|
|
{/* Next button for mobile and desktop */}
|
2024-09-12 11:35:37 +01:00
|
|
|
<PaginationItemWrapper isLoadingPage={isLoading}>
|
2024-09-11 21:26:24 +01:00
|
|
|
<PaginationNext onClick={() => handlePageChange(currentPage + 1)} />
|
2024-09-12 11:35:37 +01:00
|
|
|
</PaginationItemWrapper>
|
2024-09-11 21:26:24 +01:00
|
|
|
</PaginationContent>
|
|
|
|
</ShadCnPagination>
|
|
|
|
);
|
|
|
|
}
|