Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | 'use client';
/**
* Pagination - Reusable pagination component
*/
import React from 'react';
import { Button, IconButton } from '@/components/ui';
export interface PaginationProps {
/** Current page number (1-indexed) */
currentPage: number;
/** Total number of pages */
totalPages: number;
/** Callback when page changes */
onPageChange: (page: number) => void;
/** Number of page buttons to show on each side of current */
siblingCount?: number;
}
export default function Pagination({
currentPage,
totalPages,
onPageChange,
siblingCount = 1
}: PaginationProps) {
// Generate page numbers to display
const getPageNumbers = (): (number | 'ellipsis')[] => {
const pages: (number | 'ellipsis')[] = [];
// Always show first page
pages.push(1);
// Calculate range around current page
const leftSibling = Math.max(2, currentPage - siblingCount);
const rightSibling = Math.min(totalPages - 1, currentPage + siblingCount);
// Add ellipsis after first page if needed
if (leftSibling > 2) {
pages.push('ellipsis');
}
// Add pages in range
for (let i = leftSibling; i <= rightSibling; i++) {
if (i !== 1 && i !== totalPages) {
pages.push(i);
}
}
// Add ellipsis before last page if needed
if (rightSibling < totalPages - 1) {
pages.push('ellipsis');
}
// Always show last page if there's more than one page
if (totalPages > 1) {
pages.push(totalPages);
}
return pages;
};
if (totalPages <= 1) {
return null;
}
const pages = getPageNumbers();
return (
<nav className="flex items-center justify-center gap-1" aria-label="Pagination">
{/* Previous Button */}
<IconButton
icon="chevron-left"
variant="ghost"
size="sm"
onClick={() => onPageChange(currentPage - 1)}
disabled={currentPage === 1}
aria-label="Previous page"
className="dark:text-gray-300 dark:hover:bg-gray-700"
/>
{/* Page Numbers */}
<div className="flex items-center gap-1">
{pages.map((page, index) => {
if (page === 'ellipsis') {
return (
<span
key={`ellipsis-${index}`}
className="px-3 py-2 text-gray-400 dark:text-gray-500"
aria-hidden="true"
>
...
</span>
);
}
const isActive = page === currentPage;
return (
<Button
key={page}
variant={isActive ? 'primary' : 'ghost'}
size="sm"
onClick={() => onPageChange(page)}
className={`min-w-[40px] ${!isActive ? 'dark:text-gray-300 dark:hover:bg-gray-700' : ''}`}
aria-label={`Page ${page}`}
aria-current={isActive ? 'page' : undefined}
>
{page}
</Button>
);
})}
</div>
{/* Next Button */}
<IconButton
icon="chevron-right"
variant="ghost"
size="sm"
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage === totalPages}
aria-label="Next page"
className="dark:text-gray-300 dark:hover:bg-gray-700"
/>
</nav>
);
}
|