Spaces:
Sleeping
Sleeping
| "use client"; | |
| import { cn } from "@/shared/utils/cn"; | |
| import Button from "./Button"; | |
| export default function Pagination({ | |
| currentPage, | |
| pageSize, | |
| totalItems, | |
| onPageChange, | |
| onPageSizeChange, | |
| className, | |
| }) { | |
| const totalPages = Math.ceil(totalItems / pageSize); | |
| const startItem = totalItems > 0 ? (currentPage - 1) * pageSize + 1 : 0; | |
| const endItem = Math.min(currentPage * pageSize, totalItems); | |
| const getPageNumbers = () => { | |
| const pages = []; | |
| const showMax = 5; | |
| let start = Math.max(1, currentPage - 2); | |
| let end = Math.min(totalPages, start + showMax - 1); | |
| if (end - start + 1 < showMax) { | |
| start = Math.max(1, end - showMax + 1); | |
| } | |
| for (let i = start; i <= end; i++) { | |
| pages.push(i); | |
| } | |
| return pages; | |
| }; | |
| const pageNumbers = getPageNumbers(); | |
| return ( | |
| <div | |
| className={cn( | |
| "flex flex-col sm:flex-row items-center justify-between gap-4 py-4 px-2", | |
| className | |
| )} | |
| > | |
| {/* Info text */} | |
| {totalItems > 0 && ( | |
| <div className="text-sm text-text-muted"> | |
| Showing <span className="font-medium text-text-main">{startItem}</span> to{" "} | |
| <span className="font-medium text-text-main">{endItem}</span> of{" "} | |
| <span className="font-medium text-text-main">{totalItems}</span> results | |
| </div> | |
| )} | |
| <div className="flex flex-wrap items-center justify-center gap-2 sm:gap-4"> | |
| {/* Page size selector */} | |
| {onPageSizeChange && ( | |
| <div className="flex items-center gap-2"> | |
| <span className="text-sm text-text-muted">Rows:</span> | |
| <select | |
| value={pageSize} | |
| onChange={(e) => onPageSizeChange(Number(e.target.value))} | |
| className={cn( | |
| "h-9 rounded-lg border border-black/10 dark:border-white/10 bg-surface", | |
| "text-sm text-text-main focus:outline-none focus:ring-2 focus:ring-primary/20", | |
| "cursor-pointer" | |
| )} | |
| style={{ colorScheme: 'auto' }} | |
| > | |
| {[10, 20, 50].map((size) => ( | |
| <option key={size} value={size}> | |
| {size} | |
| </option> | |
| ))} | |
| </select> | |
| </div> | |
| )} | |
| {totalPages > 1 && ( | |
| <div className="flex items-center gap-1"> | |
| <Button | |
| variant="outline" | |
| size="sm" | |
| onClick={() => onPageChange(currentPage - 1)} | |
| disabled={currentPage === 1} | |
| className="w-9 px-0" | |
| > | |
| <span className="material-symbols-outlined text-[18px]">chevron_left</span> | |
| </Button> | |
| {pageNumbers[0] > 1 && ( | |
| <> | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| onClick={() => onPageChange(1)} | |
| className="w-9 px-0 hidden sm:inline-flex" | |
| > | |
| 1 | |
| </Button> | |
| {pageNumbers[0] > 2 && ( | |
| <span className="text-text-muted px-1 hidden sm:inline">...</span> | |
| )} | |
| </> | |
| )} | |
| {pageNumbers.map((page) => ( | |
| <Button | |
| key={page} | |
| variant={currentPage === page ? "primary" : "ghost"} | |
| size="sm" | |
| onClick={() => onPageChange(page)} | |
| className={cn( | |
| "w-9 px-0", | |
| currentPage === page ? "inline-flex" : "hidden sm:inline-flex" | |
| )} | |
| > | |
| {page} | |
| </Button> | |
| ))} | |
| {pageNumbers[pageNumbers.length - 1] < totalPages && ( | |
| <> | |
| {pageNumbers[pageNumbers.length - 1] < totalPages - 1 && ( | |
| <span className="text-text-muted px-1 hidden sm:inline">...</span> | |
| )} | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| onClick={() => onPageChange(totalPages)} | |
| className="w-9 px-0 hidden sm:inline-flex" | |
| > | |
| {totalPages} | |
| </Button> | |
| </> | |
| )} | |
| <Button | |
| variant="outline" | |
| size="sm" | |
| onClick={() => onPageChange(currentPage + 1)} | |
| disabled={currentPage === totalPages} | |
| className="w-9 px-0" | |
| > | |
| <span className="material-symbols-outlined text-[18px]">chevron_right</span> | |
| </Button> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |