turash/bugulma/frontend/components/ui/Pagination.tsx
2025-12-15 10:06:41 +01:00

159 lines
4.1 KiB
TypeScript

import React from 'react';
import { clsx } from 'clsx';
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
import Button from './Button';
export interface PaginationProps {
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
pageSize?: number;
totalItems?: number;
showPageNumbers?: boolean;
showFirstLast?: boolean;
className?: string;
}
/**
* Pagination component
*/
export const Pagination = ({
currentPage,
totalPages,
onPageChange,
pageSize,
totalItems,
showPageNumbers = true,
showFirstLast = true,
className,
}: PaginationProps) => {
const getPageNumbers = () => {
const delta = 2;
const range = [];
const rangeWithDots = [];
for (
let i = Math.max(2, currentPage - delta);
i <= Math.min(totalPages - 1, currentPage + delta);
i++
) {
range.push(i);
}
if (currentPage - delta > 2) {
rangeWithDots.push(1, '...');
} else {
rangeWithDots.push(1);
}
rangeWithDots.push(...range);
if (currentPage + delta < totalPages - 1) {
rangeWithDots.push('...', totalPages);
} else {
rangeWithDots.push(totalPages);
}
return rangeWithDots;
};
const pageNumbers = getPageNumbers();
if (totalPages <= 1) return null;
return (
<div className={clsx('flex items-center justify-between gap-2', className)}>
{/* Info */}
{totalItems !== undefined && pageSize && (
<div className="text-sm text-muted-foreground hidden sm:block">
Showing {(currentPage - 1) * pageSize + 1} to{' '}
{Math.min(currentPage * pageSize, totalItems)} of {totalItems} results
</div>
)}
{/* Pagination Controls */}
<div className="flex items-center gap-1">
{showFirstLast && (
<Button
variant="outline"
size="sm"
onClick={() => onPageChange(1)}
disabled={currentPage === 1}
aria-label="First page"
>
<ChevronsLeft className="h-4 w-4" />
</Button>
)}
<Button
variant="outline"
size="sm"
onClick={() => onPageChange(currentPage - 1)}
disabled={currentPage === 1}
aria-label="Previous page"
>
<ChevronLeft className="h-4 w-4" />
</Button>
{showPageNumbers && (
<div className="flex items-center gap-1">
{pageNumbers.map((page, index) => {
if (page === '...') {
return (
<span
key={`ellipsis-${index}`}
className="px-3 py-2 text-sm text-muted-foreground"
>
...
</span>
);
}
const pageNum = page as number;
const isActive = pageNum === currentPage;
return (
<Button
key={pageNum}
variant={isActive ? 'primary' : 'outline'}
size="sm"
onClick={() => onPageChange(pageNum)}
className={clsx('min-w-[2.5rem]', {
'pointer-events-none': isActive,
})}
aria-label={`Page ${pageNum}`}
aria-current={isActive ? 'page' : undefined}
>
{pageNum}
</Button>
);
})}
</div>
)}
<Button
variant="outline"
size="sm"
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage === totalPages}
aria-label="Next page"
>
<ChevronRight className="h-4 w-4" />
</Button>
{showFirstLast && (
<Button
variant="outline"
size="sm"
onClick={() => onPageChange(totalPages)}
disabled={currentPage === totalPages}
aria-label="Last page"
>
<ChevronsRight className="h-4 w-4" />
</Button>
)}
</div>
</div>
);
};