9.1 KiB
Pagination
(no submodules)
- pagination.variants — `paginationVariants`, `pagina…-
pagination.variants —
paginationVariants,paginationContentVariants,paginationItemVariants,paginationLinkVariants, `paginationPrevN… -
pagination.variants —
paginationVariants, `pagina… -
pagination.variants —
paginationVariants,paginationContentVariants,paginationItemVariants,paginationLinkVariants, `paginationPrevN… -
pagination.variants —
paginationVariants, `pagina… -
pagination.variants —
paginationVariants,paginationContentVariants,paginationItemVariants,paginationLinkVariants, `paginationPrevN… -
pagination.variants —
paginationVariants, `pagina… -
pagination.variants —
paginationVariants,paginationContentVariants,paginationItemVariants,paginationLinkVariants, `paginationPrevN… -
pagination.variants —
paginationVariants,paginationContentVariants,paginationItemVariants,paginationLinkVariants, `paginationPrevN…
Purpose
<Pagination> and its sub-components (<PaginationContent>, <PaginationItem>, <PaginationLink>, <PaginationPrevious>, <PaginationNext>, <PaginationEllipsis>, <PaginationStatus>) are the chassis page-navigation primitive. Pure HTML composition — no Radix wrapper — so the markup is deterministic and trivially server-rendered. The wrapper is a semantic <nav> whose accessible name flows through the bundled react-ui.pagination.label translation key. Inside, an unordered list (<PaginationContent>) carries page link rows (<PaginationLink>) interleaved with previous / next / ellipsis affordances. The current page sets isActive, which adds aria-current="page" so screen-readers announce it as the active page.
The "Page X of Y" status caption (<PaginationStatus>) renders through a real ICU MessageFormat via @suaveplan/i18n MessageParser so the locale's plural rule chooses the right form. Welsh covers six plural categories (zero | one | two | few | many | other), and the chassis test suite exercises each one against the bundled cy-GB template post-wireup.
Features
- Semantic
<nav>+<ul>+<li>markup — pure HTML; SSR-trivial; no JavaScript required for the trail to render. - Translated
aria-labelon the wrapper (react-ui.pagination.label). <PaginationLink isActive>carriesaria-current="page"so screen-readers announce the active page.<PaginationPrevious>/<PaginationNext>carry translatedaria-labels (react-ui.pagination.previous/react-ui.pagination.next) and RTL-flipped chevron icons.<PaginationEllipsis>provides a collapsed-page indicator witharia-hidden="true"plus a translatedaria-labelfor assistive tech.<PaginationStatus>renders "Page X of Y" viaMessageParserICU plural so locale-appropriate plurals fire — Welsh's six-form plural is exercised end-to-end.- Three size variants on
<PaginationLink>(default/sm/icon). - All sub-components forward refs to their underlying HTML elements.
Usage: Basic Example
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@suaveplan/react-ui";
export function PagedList({ page }: { page: number }) {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href={`?page=${page - 1}`} />
</PaginationItem>
{[1, 2, 3].map((n) => (
<PaginationItem key={n}>
<PaginationLink
href={`?page=${n}`}
isActive={n === page}
>
{n}
</PaginationLink>
</PaginationItem>
))}
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationNext href={`?page=${page + 1}`} />
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
Usage: Advanced Example
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
PaginationStatus,
} from "@suaveplan/react-ui";
interface Props {
readonly current: number;
readonly total: number;
readonly buildHref: (page: number) => string;
}
function paginationWindow(
current: number,
total: number
): ReadonlyArray<number | null> {
if (total <= 7) {
return Array.from({ length: total }, (_, i) => i + 1);
}
if (current <= 3) return [1, 2, 3, 4, null, total - 1, total];
if (current >= total - 2) {
return [1, 2, null, total - 3, total - 2, total - 1, total];
}
return [1, null, current - 1, current, current + 1, null, total];
}
export function FullPagination({ current, total, buildHref }: Props) {
const items = paginationWindow(current, total);
return (
<div className="flex flex-col items-center gap-2">
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href={buildHref(Math.max(1, current - 1))}
/>
</PaginationItem>
{items.map((entry, i) => (
<PaginationItem key={`${entry}-${i}`}>
{entry === null ? (
<PaginationEllipsis />
) : (
<PaginationLink
href={buildHref(entry)}
isActive={entry === current}
>
{entry}
</PaginationLink>
)}
</PaginationItem>
))}
<PaginationItem>
<PaginationNext
href={buildHref(Math.min(total, current + 1))}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
<PaginationStatus current={current} total={total} />
</div>
);
}
API Reference
<Pagination>
Renders a <nav role="navigation"> with a translated aria-label.
| Prop | Type | Default | Description |
|---|---|---|---|
ariaLabelKey |
TranslationKey |
react-ui.pagination.label |
Override the accessible name. |
<PaginationContent>, <PaginationItem>
Pure layout — <ul> and <li> containers.
<PaginationLink>
Renders an <a>. Sets aria-current="page" when isActive is true.
| Prop | Type | Default | Description |
|---|---|---|---|
size |
"default" | "sm" | "icon" |
"icon" |
Visual size. |
isActive |
boolean |
false |
Marks as the current page. |
<PaginationPrevious>, <PaginationNext>
Arrow affordances with translated aria-labels and chevron icons.
| Prop | Type | Default | Description |
|---|---|---|---|
ariaLabelKey |
TranslationKey |
react-ui.pagination.previous / .next |
Override the accessible name. |
<PaginationEllipsis>
Collapsed-page indicator. Pure visual filler with aria-hidden="true" and a translated aria-label (react-ui.pagination.morePages).
<PaginationStatus>
The "Page X of Y" caption.
| Prop | Type | Default | Description |
|---|---|---|---|
current |
number |
required | Active page number. |
total |
number |
required | Total page count. |
statusKey |
TranslationKey |
react-ui.pagination.pageXofY |
Override the ICU template key. |
The template format is "Page {current} of {total}" in en-GB, with locale-appropriate plural variants in the other bundled locales.
Implementation Notes
- The chassis NEVER wraps pagination in a Radix primitive — pure HTML keeps SSR trivial and matches shadcn upstream's recommendation.
<PaginationPrevious>/<PaginationNext>chevron icons carryrtl:rotate-180so the arrows flip in RTL contexts.- The
<PaginationStatus>usesaria-live="polite"andaria-atomic="true"so screen-readers announce updates as the user navigates without interrupting the focus flow. MessageParseris constructed once per render viauseMemoso the parser cache is preserved across re-renders.- Per the Wave 3d reconciliation finding (#4), the
t()call returns the ICU template, thenmessageParser.format(template, values, { locale })performs interpolation and plural resolution. - Per the Wave 3d reconciliation finding (#2), the active locale is read via the
(i18n as { readonly locale: string }).localecast sinceuseTranslation()throwsREACT_I18N.NO_PROVIDERin dev mode if called outside a provider, making the optional-chain branch unreachable.