This repository has been archived on 2024-10-29. You can view files and clone it, but cannot push or open issues or pull requests.
Files
scoresaber-reloadedv3/src/components/input/pagination.tsx

154 lines
4.5 KiB
TypeScript
Raw Normal View History

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 {
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
Pagination as ShadCnPagination,
} from "../ui/pagination";
type PaginationItemWrapperProps = {
2024-09-12 11:38:19 +01:00
/**
* Whether a page is currently loading.
*/
isLoadingPage: boolean;
2024-09-12 11:38:19 +01:00
/**
* The children to render.
*/
children: React.ReactNode;
};
function PaginationItemWrapper({ isLoadingPage, children }: PaginationItemWrapperProps) {
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}
>
{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;
/**
* 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;
};
export default function Pagination({ mobilePagination, page, totalPages, loadingPage, onPageChange }: Props) {
2024-09-11 21:26:24 +01:00
totalPages = Math.round(totalPages);
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) => {
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(
<>
<PaginationItemWrapper key="start" isLoadingPage={isLoading}>
2024-09-11 21:26:24 +01:00
<PaginationLink onClick={() => handlePageChange(1)}>1</PaginationLink>
</PaginationItemWrapper>
2024-09-12 15:08:25 +01:00
{/* Only show ellipsis if more than 2 pages from the start */}
{startPage > 2 && (
<PaginationItemWrapper key="ellipsis-start" isLoadingPage={isLoading}>
<PaginationEllipsis />
</PaginationItemWrapper>
)}
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(
<PaginationItemWrapper key={i} isLoadingPage={isLoading}>
2024-09-11 21:26:24 +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>
</PaginationItemWrapper>
2024-09-11 21:26:24 +01:00
);
}
return pageNumbers;
};
return (
<ShadCnPagination className="select-none">
<PaginationContent>
{/* Previous button for mobile and desktop */}
<PaginationItemWrapper isLoadingPage={isLoading}>
2024-09-11 21:26:24 +01:00
<PaginationPrevious onClick={() => handlePageChange(currentPage - 1)} />
</PaginationItemWrapper>
2024-09-11 21:26:24 +01:00
{renderPageNumbers()}
{/* For desktop, show ellipsis and link to the last page */}
2024-09-12 15:08:25 +01:00
{!mobilePagination && currentPage < totalPages && totalPages - currentPage > 2 && (
2024-09-11 21:26:24 +01:00
<>
<PaginationItemWrapper key="ellipsis-end" isLoadingPage={isLoading}>
2024-09-11 23:18:02 +01:00
<PaginationEllipsis className="cursor-default" />
</PaginationItemWrapper>
<PaginationItemWrapper key="end" isLoadingPage={isLoading}>
2024-09-11 21:26:24 +01:00
<PaginationLink onClick={() => handlePageChange(totalPages)}>{totalPages}</PaginationLink>
</PaginationItemWrapper>
2024-09-11 21:26:24 +01:00
</>
)}
{/* Next button for mobile and desktop */}
<PaginationItemWrapper isLoadingPage={isLoading}>
2024-09-11 21:26:24 +01:00
<PaginationNext onClick={() => handlePageChange(currentPage + 1)} />
</PaginationItemWrapper>
2024-09-11 21:26:24 +01:00
</PaginationContent>
</ShadCnPagination>
);
}