SPB Git forge

spb/cancerindex

Public
37commits 1branches 0releases
2.9 MBsize
maindefault branch
10 days agolast push
TypeScript 97.2% SQL 1.5% CSS 0.6% JavaScript 0.5%
3.0 KB · 87 lines tsx
Raw Blame History
1import Link from 'next/link';2import { fmtInt } from '@/lib/format';3import { pageInfo, pageWindow } from '@/lib/pagination';45/**6 * Server-side pager (URL state, §295). Renders "Showing a–b of N", previous/next links with7 * rel=prev/next, and a compact page window. Pure links: works without JavaScript and is crawlable.8 */9export function Pager({10  total,11  pageSize,12  page,13  hrefFor,14  label = 'Pagination',15  noun,16  className = '',17}: {18  total: number;19  pageSize: number;20  page: number;21  /** URL for a given page (1-based). Page 1 should drop the param so canonical URLs stay clean. */22  hrefFor: (page: number) => string;23  /** aria-label for the <nav>; use a distinct label when a page has several pagers. */24  label?: string;25  /** Row noun for the summary, e.g. "evidence items". */26  noun?: string;27  className?: string;28}) {29  const info = pageInfo(page, pageSize, total);30  if (total <= 0) return null;31  const linkCls = 'ci-pager-link';32  const disabledCls = 'ci-pager-link is-disabled';33  return (34    <nav aria-label={label} className={`ci-pager mt-3 flex flex-wrap items-center justify-between gap-2 text-[13px] text-ink-2 ${className}`}>35      <p className="m-0">36        Showing <span className="ci-num">{fmtInt(info.from)}</span>–<span className="ci-num">{fmtInt(info.to)}</span> of <span className="ci-num">{fmtInt(total)}</span>37        {noun ? ` ${noun}` : ''}38      </p>39      {info.pageCount > 1 ? (40        <ul className="m-0 flex list-none items-center gap-1 p-0">41          <li>42            {info.hasPrev ? (43              <Link className={linkCls} href={hrefFor(info.page - 1)} rel="prev" aria-label={`Previous page (${info.page - 1})`}>44                ← Previous45              </Link>46            ) : (47              <span className={disabledCls} aria-disabled="true">48                ← Previous49              </span>50            )}51          </li>52          {pageWindow(info.page, info.pageCount).map((p, i) =>53            p == null ? (54              <li key={`gap-${i}`} aria-hidden className="px-1 text-ink-4">55                …56              </li>57            ) : (58              <li key={p}>59                {p === info.page ? (60                  <span className="ci-pager-link is-current ci-num" aria-current="page" aria-label={`Page ${p}, current page`}>61                    {p}62                  </span>63                ) : (64                  <Link className={`${linkCls} ci-num`} href={hrefFor(p)} aria-label={`Page ${p}`}>65                    {p}66                  </Link>67                )}68              </li>69            ),70          )}71          <li>72            {info.hasNext ? (73              <Link className={linkCls} href={hrefFor(info.page + 1)} rel="next" aria-label={`Next page (${info.page + 1})`}>74                Next →75              </Link>76            ) : (77              <span className={disabledCls} aria-disabled="true">78                Next →79              </span>80            )}81          </li>82        </ul>83      ) : null}84    </nav>85  );86}87