From 6af787614eec36933d859c64edbd587b1b0f1edb Mon Sep 17 00:00:00 2001 From: Tyler Date: Fri, 19 Jun 2026 19:37:01 -0600 Subject: [PATCH] feat(frontend): add Pagination primitive --- src/components/ui/pagination.tsx | 133 +++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 src/components/ui/pagination.tsx diff --git a/src/components/ui/pagination.tsx b/src/components/ui/pagination.tsx new file mode 100644 index 0000000..af852ed --- /dev/null +++ b/src/components/ui/pagination.tsx @@ -0,0 +1,133 @@ +import { ChevronLeft, ChevronRight } from "lucide-react"; +import { cn } from "@/lib/utils"; + +type PaginationProps = { + /** Current 1-indexed page number. */ + page: number; + /** Items per page. */ + pageSize: number; + /** Total item count across all pages. */ + total: number; + /** Called with a clamped, 1-indexed page number. */ + onPageChange: (page: number) => void; + className?: string; +}; + +function buildPageList(current: number, last: number): (number | "…")[] { + if (last <= 7) return Array.from({ length: last }, (_, i) => i + 1); + const out: (number | "…")[] = [1]; + const start = Math.max(2, current - 1); + const end = Math.min(last - 1, current + 1); + if (start > 2) out.push("…"); + for (let i = start; i <= end; i++) out.push(i); + if (end < last - 1) out.push("…"); + out.push(last); + return out; +} + +/** + * Tabular-num pagination. + * Voice: hairline-bordered Prev/Next + numbered pages, the active page + * echoing the nav-active style (filled muted tile with a left accent + * tick). Eyebrow shows "Page X of Y" + "Showing A–B of N". + */ +export function Pagination({ + page, + pageSize, + total, + onPageChange, + className, +}: PaginationProps) { + const last = Math.max(1, Math.ceil(total / pageSize)); + if (last <= 1) return null; + + const safePage = Math.min(Math.max(1, page), last); + const start = (safePage - 1) * pageSize + 1; + const end = Math.min(total, start + pageSize - 1); + const pages = buildPageList(safePage, last); + + return ( +
+
+ + Page {safePage} of {last} + + + Showing {start.toLocaleString()}–{end.toLocaleString()} of{" "} + {total.toLocaleString()} + +
+ +
+ ); +}