Spaces:
Running
Running
import React from 'react'; | |
import { Button, Container } from '@ifrc-go/ui'; | |
import { ChevronLeftLineIcon, ChevronRightLineIcon } from '@ifrc-go/icons'; | |
import styles from './Paginator.module.css'; | |
interface PaginatorProps { | |
currentPage: number; | |
totalPages: number; | |
totalItems: number; | |
itemsPerPage: number; | |
onPageChange: (page: number) => void; | |
className?: string; | |
} | |
export default function Paginator({ | |
currentPage, | |
totalPages, | |
totalItems, | |
itemsPerPage, | |
onPageChange, | |
className = '' | |
}: PaginatorProps) { | |
const startIndex = (currentPage - 1) * itemsPerPage; | |
const endIndex = Math.min(startIndex + itemsPerPage, totalItems); | |
// Don't render if only one page | |
if (totalPages <= 1) { | |
return null; | |
} | |
// Generate page numbers to display | |
const getPageNumbers = () => { | |
const pages = []; | |
const maxVisiblePages = 5; | |
if (totalPages <= maxVisiblePages) { | |
// Show all pages if total is small | |
for (let i = 1; i <= totalPages; i++) { | |
pages.push(i); | |
} | |
} else { | |
// Show smart range of pages | |
let start = Math.max(1, currentPage - 2); | |
let end = Math.min(totalPages, start + maxVisiblePages - 1); | |
// Adjust start if we're near the end | |
if (end === totalPages) { | |
start = Math.max(1, end - maxVisiblePages + 1); | |
} | |
for (let i = start; i <= end; i++) { | |
pages.push(i); | |
} | |
} | |
return pages; | |
}; | |
const pageNumbers = getPageNumbers(); | |
return ( | |
<div className={`${styles.paginatorContainer} ${className}`}> | |
{/* Pagination controls */} | |
<div className={styles.paginationControls}> | |
{/* Previous button */} | |
<Container withInternalPadding className="bg-white/20 backdrop-blur-sm rounded-md p-2"> | |
<Button | |
name="prev-page" | |
variant="tertiary" | |
size={1} | |
onClick={() => onPageChange(Math.max(1, currentPage - 1))} | |
disabled={currentPage === 1} | |
title="Previous page" | |
> | |
<ChevronLeftLineIcon className="w-4 h-4" /> | |
<span className="hidden sm:inline">Previous</span> | |
</Button> | |
</Container> | |
{/* Page numbers */} | |
<div className="flex items-center gap-1"> | |
{/* First page (if not visible) */} | |
{pageNumbers[0] > 1 && ( | |
<> | |
<Container withInternalPadding className="bg-white/20 backdrop-blur-sm rounded-md p-2"> | |
<Button | |
name="page-1" | |
variant="tertiary" | |
size={1} | |
onClick={() => onPageChange(1)} | |
> | |
1 | |
</Button> | |
</Container> | |
{pageNumbers[0] > 2 && ( | |
<Container withInternalPadding className="bg-white/20 backdrop-blur-sm rounded-md p-2"> | |
<span className="px-2 text-gray-500">...</span> | |
</Container> | |
)} | |
</> | |
)} | |
{/* Visible page numbers */} | |
{pageNumbers.map(pageNum => ( | |
<Container key={pageNum} withInternalPadding className="bg-white/20 backdrop-blur-sm rounded-md p-2"> | |
<Button | |
name={`page-${pageNum}`} | |
variant={currentPage === pageNum ? "primary" : "tertiary"} | |
size={1} | |
onClick={() => onPageChange(pageNum)} | |
> | |
{pageNum} | |
</Button> | |
</Container> | |
))} | |
{/* Last page (if not visible) */} | |
{pageNumbers[pageNumbers.length - 1] < totalPages && ( | |
<> | |
{pageNumbers[pageNumbers.length - 1] < totalPages - 1 && ( | |
<Container withInternalPadding className="bg-white/20 backdrop-blur-sm rounded-md p-2"> | |
<span className="px-2 text-gray-500">...</span> | |
</Container> | |
)} | |
<Container withInternalPadding className="bg-white/20 backdrop-blur-sm rounded-md p-2"> | |
<Button | |
name={`page-${totalPages}`} | |
variant="tertiary" | |
size={1} | |
onClick={() => onPageChange(totalPages)} | |
> | |
{totalPages} | |
</Button> | |
</Container> | |
</> | |
)} | |
</div> | |
{/* Next button */} | |
<Container withInternalPadding className="bg-white/20 backdrop-blur-sm rounded-md p-2"> | |
<Button | |
name="next-page" | |
variant="tertiary" | |
size={1} | |
onClick={() => onPageChange(Math.min(totalPages, currentPage + 1))} | |
disabled={currentPage === totalPages} | |
title="Next page" | |
> | |
<span className="hidden sm:inline">Next</span> | |
<ChevronRightLineIcon className="w-4 h-4" /> | |
</Button> | |
</Container> | |
</div> | |
</div> | |
); | |
} | |