TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import Link from 'next/link';2import { ChevronLeft, ChevronRight } from 'lucide-react';3import { cn } from '@/lib/format';45/** URL-driven pagination: keeps every other search param. Touch targets ≥ 44 px on mobile. */6export function Pagination({ page, pageSize, total, basePath, params, className }: { page: number; pageSize: number; total: number; basePath: string; params: Record<string, string | undefined>; className?: string }) {7 const pages = Math.max(1, Math.ceil(total / pageSize));8 if (pages <= 1) return null;9 const href = (p: number) => {10 const sp = new URLSearchParams();11 for (const [k, v] of Object.entries(params)) if (v) sp.set(k, v);12 if (p > 1) sp.set('page', String(p));13 else sp.delete('page');14 const qs = sp.toString();15 return qs ? `${basePath}?${qs}` : basePath;16 };17 const from = (page - 1) * pageSize + 1;18 const to = Math.min(total, page * pageSize);19 const btn = 'inline-flex h-10 min-w-[44px] items-center justify-center gap-1 rounded-md border border-border bg-elevated px-3 text-xs font-medium hover:bg-sunken aria-disabled:pointer-events-none aria-disabled:opacity-40 md:h-8 md:min-w-0';20 const around: number[] = [];21 for (let p = Math.max(1, page - 2); p <= Math.min(pages, page + 2); p++) around.push(p);22 return (23 <nav className={cn('mt-4 flex flex-wrap items-center justify-between gap-3 text-xs text-muted', className)} aria-label="Pagination">24 <span className="num">25 {from.toLocaleString('en-US')}–{to.toLocaleString('en-US')} of {total.toLocaleString('en-US')}26 </span>27 <div className="flex items-center gap-1">28 <Link href={href(Math.max(1, page - 1))} aria-disabled={page <= 1} className={btn} aria-label="Previous page">29 <ChevronLeft className="h-3.5 w-3.5" />30 <span className="hidden sm:inline">Prev</span>31 </Link>32 {around[0]! > 1 ? (33 <>34 <Link href={href(1)} className={cn(btn, 'num')}>35 136 </Link>37 {around[0]! > 2 ? <span className="px-1 text-subtle">…</span> : null}38 </>39 ) : null}40 {around.map((p) => (41 <Link key={p} href={href(p)} aria-current={p === page ? 'page' : undefined} className={cn(btn, 'num', p === page && 'border-fg bg-accent text-accent-fg hover:bg-accent')}>42 {p}43 </Link>44 ))}45 {around[around.length - 1]! < pages ? (46 <>47 {around[around.length - 1]! < pages - 1 ? <span className="px-1 text-subtle">…</span> : null}48 <Link href={href(pages)} className={cn(btn, 'num')}>49 {pages.toLocaleString('en-US')}50 </Link>51 </>52 ) : null}53 <Link href={href(Math.min(pages, page + 1))} aria-disabled={page >= pages} className={btn} aria-label="Next page">54 <span className="hidden sm:inline">Next</span>55 <ChevronRight className="h-3.5 w-3.5" />56 </Link>57 </div>58 </nav>59 );60}61