// Dependencies: react ^18, lucide-react
import React, { useState } from 'react';
import { ChevronLeft, ChevronRight, ChevronDown } from 'lucide-react';
function cn(...classes: (string | false | null | undefined)[]) {
return classes.filter(Boolean).join(' ');
}
const PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
type Props = {
totalItems: number;
defaultPage?: number;
defaultPageSize?: number;
};
export function Pagination({
totalItems,
defaultPage = 1,
defaultPageSize = 10,
}: Props) {
const [page, setPage] = useState(defaultPage);
const [pageSize, setPageSize] = useState(defaultPageSize);
const [sizeOpen, setSizeOpen] = useState(false);
const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
const p = Math.min(Math.max(page, 1), totalPages);
const start = totalItems === 0 ? 0 : (p - 1) * pageSize + 1;
const end = Math.min(p * pageSize, totalItems);
const go = (n: number) => setPage(Math.max(1, Math.min(n, totalPages)));
const changeSize = (s: number) => { setPageSize(s); setPage(1); setSizeOpen(false); };
const getPageRange = (): (number | '...')[] => {
if (totalPages <= 7) return Array.from({ length: totalPages }, (_, i) => i + 1);
const left = Math.max(2, p - 1);
const right = Math.min(totalPages - 1, p + 1);
const out: (number | '...')[] = [1];
if (left > 2) out.push('...');
for (let i = left; i <= right; i++) out.push(i);
if (right < totalPages - 1) out.push('...');
out.push(totalPages);
return out;
};
return (
<div className="flex items-center justify-between gap-4 px-4 py-3 bg-[#0a0a0a] border-t border-white/[0.06] select-none flex-wrap">
{/* Rows per page */}
<div className="relative flex items-center gap-2">
<span className="text-[12px] text-zinc-500 whitespace-nowrap">Rows per page</span>
<button
onClick={() => setSizeOpen(o => !o)}
className="flex items-center gap-1.5 px-2 py-1 rounded-md bg-[#111] border border-[#1a1a1a] text-[12px] text-zinc-300 hover:border-[#2a2a2a] transition-colors"
>
{pageSize}
<ChevronDown className="w-3 h-3 text-zinc-600" />
</button>
{sizeOpen && (
<>
<div className="fixed inset-0 z-40" onClick={() => setSizeOpen(false)} />
<div className="absolute bottom-full mb-1.5 left-0 bg-[#1a1a1a] border border-[#2a2a2a] rounded-lg shadow-2xl z-50 py-1 min-w-[80px]">
{PAGE_SIZE_OPTIONS.map(s => (
<button
key={s}
onClick={() => changeSize(s)}
className={cn(
'w-full text-left px-3 py-1.5 text-[12px] hover:bg-white/[0.06] transition-colors',
s === pageSize ? 'text-indigo-400' : 'text-zinc-300'
)}
>
{s}
</button>
))}
</div>
</>
)}
</div>
{/* Range readout */}
<span
className="text-[12px] text-zinc-500 whitespace-nowrap"
style={{ fontVariantNumeric: 'tabular-nums' }}
>
{start}–{end} of {totalItems}
</span>
{/* Page controls */}
<div className="flex items-center gap-0.5">
<button
onClick={() => go(p - 1)}
disabled={p === 1}
aria-label="Previous page"
className="p-1.5 rounded-md hover:bg-white/[0.06] disabled:opacity-30 disabled:cursor-not-allowed transition-colors text-zinc-400"
>
<ChevronLeft className="w-3.5 h-3.5" />
</button>
{getPageRange().map((item, idx) =>
item === '...' ? (
<span key={'e' + idx} className="w-7 text-center text-[12px] text-zinc-600 select-none">
…
</span>
) : (
<button
key={item}
onClick={() => go(item as number)}
className={cn(
'w-7 h-7 rounded-md text-[12px] font-medium transition-colors',
item === p ? 'bg-indigo-600 text-white' : 'text-zinc-400 hover:bg-white/[0.06] hover:text-zinc-200'
)}
>
{item}
</button>
)
)}
<button
onClick={() => go(p + 1)}
disabled={p === totalPages}
aria-label="Next page"
className="p-1.5 rounded-md hover:bg-white/[0.06] disabled:opacity-30 disabled:cursor-not-allowed transition-colors text-zinc-400"
>
<ChevronRight className="w-3.5 h-3.5" />
</button>
</div>
</div>
);
}