spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1import Link from "next/link";2import { ChevronLeft, ChevronRight } from "lucide-react";3import { fmtInt } from "@/lib/format";4import { cn } from "./cn";56export interface PaginationProps {7 page: number;8 perPage: number;9 total: number;10 /** returns href for a page number */11 hrefFor: (page: number) => string;12 className?: string;13 label?: string;14}1516/** Compact pager: "1–50 of 1,234" + prev/next + a few numbered links on ≥ sm. */17export function Pagination({ page, perPage, total, hrefFor, className, label = "results" }: PaginationProps) {18 const pages = Math.max(1, Math.ceil(total / Math.max(1, perPage)));19 if (total <= 0) return null;20 const from = (page - 1) * perPage + 1;21 const to = Math.min(total, page * perPage);22 const nums = new Set<number>([1, pages, page, page - 1, page + 1, page - 2, page + 2].filter((n) => n >= 1 && n <= pages));23 const list = Array.from(nums).sort((a, b) => a - b);24 return (25 <nav aria-label="Pagination" className={cn("flex items-center justify-between gap-3 border-t border-hair py-3 text-[12.5px]", className)}>26 <div className="figure text-ink-3">27 {fmtInt(from)}–{fmtInt(to)} of {fmtInt(total)} {label}28 </div>29 <div className="flex items-center gap-1">30 <PageLink href={page > 1 ? hrefFor(page - 1) : null} label="Previous page">31 <ChevronLeft className="size-4" />32 </PageLink>33 <div className="hidden items-center gap-1 sm:flex">34 {list.map((n, i) => {35 const prev = list[i - 1];36 const gap = prev !== undefined && n - prev > 1;37 return (38 <span key={n} className="flex items-center gap-1">39 {gap && <span className="px-1 text-ink-4">…</span>}40 <Link41 href={hrefFor(n)}42 aria-current={n === page ? "page" : undefined}43 scroll={false}44 className={cn("figure inline-flex h-8 min-w-8 items-center justify-center rounded-sm border px-2", n === page ? "border-ink bg-ink text-paper" : "border-hair text-ink-2 hover:bg-surface-2")}45 >46 {n}47 </Link>48 </span>49 );50 })}51 </div>52 <span className="figure px-1 text-ink-3 sm:hidden">53 {page} / {pages}54 </span>55 <PageLink href={page < pages ? hrefFor(page + 1) : null} label="Next page">56 <ChevronRight className="size-4" />57 </PageLink>58 </div>59 </nav>60 );61}6263function PageLink({ href, label, children }: { href: string | null; label: string; children: React.ReactNode }) {64 const cls = "inline-flex size-10 items-center justify-center rounded-sm border border-hair text-ink-2 sm:size-9";65 if (!href) return <span className={cn(cls, "opacity-40")}>{children}</span>;66 return (67 <Link href={href} aria-label={label} scroll={false} className={cn(cls, "hover:bg-surface-2")}>68 {children}69 </Link>70 );71}72