first commit
This commit is contained in:
205
docs/ui/react/react-ui/src/components/pagination/pagination.md
Normal file
205
docs/ui/react/react-ui/src/components/pagination/pagination.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# Pagination
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs --> - pagination.variants — `paginationVariants`, `pagina…
|
||||
- [pagination.variants](./pagination.variants.md) — `paginationVariants`, `paginationContentVariants`, `paginationItemVariants`, `paginationLinkVariants`, `paginationPrevN…
|
||||
- pagination.variants — `paginationVariants`, `pagina…
|
||||
|
||||
- [pagination.variants](./pagination.variants.md) — `paginationVariants`, `paginationContentVariants`, `paginationItemVariants`, `paginationLinkVariants`, `paginationPrevN…
|
||||
|
||||
- pagination.variants — `paginationVariants`, `pagina…
|
||||
|
||||
- [pagination.variants](./pagination.variants.md) — `paginationVariants`, `paginationContentVariants`, `paginationItemVariants`, `paginationLinkVariants`, `paginationPrevN…
|
||||
|
||||
- pagination.variants — `paginationVariants`, `pagina…
|
||||
|
||||
- [pagination.variants](./pagination.variants.md) — `paginationVariants`, `paginationContentVariants`, `paginationItemVariants`, `paginationLinkVariants`, `paginationPrevN…
|
||||
- [pagination.variants](./pagination.variants.md) — `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-label` on the wrapper (`react-ui.pagination.label`).
|
||||
- `<PaginationLink isActive>` carries `aria-current="page"` so screen-readers announce the active page.
|
||||
- `<PaginationPrevious>` / `<PaginationNext>` carry translated `aria-label`s (`react-ui.pagination.previous` / `react-ui.pagination.next`) and RTL-flipped chevron icons.
|
||||
- `<PaginationEllipsis>` provides a collapsed-page indicator with `aria-hidden="true"` plus a translated `aria-label` for assistive tech.
|
||||
- `<PaginationStatus>` renders "Page X of Y" via `MessageParser` ICU 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
|
||||
|
||||
```tsx
|
||||
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
|
||||
|
||||
```tsx
|
||||
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-label`s 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 carry `rtl:rotate-180` so the arrows flip in RTL contexts.
|
||||
- The `<PaginationStatus>` uses `aria-live="polite"` and `aria-atomic="true"` so screen-readers announce updates as the user navigates without interrupting the focus flow.
|
||||
- `MessageParser` is constructed once per render via `useMemo` so the parser cache is preserved across re-renders.
|
||||
- Per the Wave 3d reconciliation finding (#4), the `t()` call returns the ICU template, then `messageParser.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 }).locale` cast since `useTranslation()` throws `REACT_I18N.NO_PROVIDER` in dev mode if called outside a provider, making the optional-chain branch unreachable.
|
||||
Reference in New Issue
Block a user