'use client' import React from 'react' interface PaginationProps { page: number total: number // total pages count: number // total items pageSize: number onChange: (page: number) => void /** Scroll to top on page change. Default true. */ scrollTop?: boolean } /** * Shared pagination control used across all list pages. * * Visual design: * ‹ 1 2 … 5 [6] 7 … 12 › Showing 151–180 of 340 * * Active page uses brand orange. Ellipsis collapses distant pages. * Prev/Next disabled at boundaries (dimmed, not hidden). */ export default function Pagination({ page, total, count, pageSize, onChange, scrollTop = true, }: PaginationProps) { if (total <= 1) return null const from = (page - 1) * pageSize + 1 const to = Math.min(page * pageSize, count) function go(p: number) { if (p < 1 || p > total || p === page) return onChange(p) if (scrollTop) window.scrollTo({ top: 0, behavior: 'smooth' }) } /** Page number list with ellipsis compression. */ function pages(): (number | '…')[] { if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1) const out: (number | '…')[] = [] const push = (n: number | '…') => { if (out[out.length - 1] !== n) out.push(n) } push(1) if (page > 3) push('…') for (let i = Math.max(2, page - 1); i <= Math.min(total - 1, page + 1); i++) push(i) if (page < total - 2) push('…') push(total) return out } return (
{/* ← Prev */} go(page - 1)} disabled={page === 1} label="Previous page"> Prev {/* Page numbers */}
{pages().map((n, i) => n === '…' ? ( ) : ( ) )}
{/* Next → */} go(page + 1)} disabled={page === total} label="Next page"> Next {/* Count */} {from}–{to} of {count.toLocaleString()}
) } // ── Sub-components ──────────────────────────────────────────────────────────── function PageBtn({ num, active, onClick }: { num: number; active: boolean; onClick: (n: number) => void }) { const [hover, setHover] = React.useState(false) const bg = active ? 'var(--brand, #f5a524)' : hover ? 'var(--bg-sunk)' : 'transparent' const color = active ? '#fff' : hover ? 'var(--ink)' : 'var(--ink-2)' const border = active ? '1.5px solid var(--brand, #f5a524)' : '1.5px solid var(--line)' return ( ) } function NavBtn({ onClick, disabled, label, children, }: { onClick: () => void; disabled: boolean; label: string; children: React.ReactNode }) { const [hover, setHover] = React.useState(false) return ( ) } // ── Styles ──────────────────────────────────────────────────────────────────── const wrapStyle: React.CSSProperties = { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6, padding: '28px 0 8px', flexWrap: 'wrap', } const numsStyle: React.CSSProperties = { display: 'flex', alignItems: 'center', gap: 4, flexWrap: 'wrap', justifyContent: 'center', } const ellipsisStyle: React.CSSProperties = { width: 28, textAlign: 'center', fontSize: 13, color: 'var(--ink-4)', userSelect: 'none', flexShrink: 0, } const countStyle: React.CSSProperties = { fontSize: 11, color: 'var(--ink-3)', fontVariantNumeric: 'tabular-nums', marginLeft: 6, whiteSpace: 'nowrap', }