# Pagination

Links between pages of a list, in Candor's theme: previous, next and numbered pages, with the current page marked `aria-current="page"` and a labelled navigation landmark. It draws the pages the app passes; it never works out how many there are. A page a person can't go to (before the first, past the last) stays in place, marked and explained.

- Registry name: `pagination` (`@candor/pagination`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/pagination

## Install

```bash
npx candor-ui add pagination
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/pagination`.
It brings in: button, locale, locked, render-prop, utils.
npm packages: lucide-react@^1.67.0, radix-ui@^1.8.1.

## Example

```tsx
"use client"

import * as React from "react"
import {
  Pagination,
  PaginationContent,
  PaginationEllipsis,
  PaginationItem,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
  PaginationRange,
  pageWindow,
} from "@/components/ui/pagination"

// The numbers come from the service: 312 items, 20 to a page. The component never works out a total itself.
const total = 312
const pageSize = 20
const pageCount = Math.ceil(total / pageSize)

export default function Example() {
  const [page, setPage] = React.useState(1)
  const go = (next: number) => (event: React.MouseEvent) => {
    event.preventDefault()
    setPage(next)
  }
  return (
    <div className="flex flex-col items-center gap-3">
      <PaginationRange from={(page - 1) * pageSize + 1} to={Math.min(page * pageSize, total)} total={total} />
      <Pagination>
        <PaginationContent>
          <PaginationItem>
            <PaginationPrevious href="#previous" locked={page === 1 ? "You’re on the first page." : undefined} onClick={go(page - 1)} />
          </PaginationItem>
          {pageWindow({ page, pageCount }).map((entry) => (
            <PaginationItem key={entry}>
              {typeof entry === "number" ? (
                <PaginationLink href={`#page-${entry}`} page={entry} isActive={entry === page} onClick={go(entry)}>
                  {entry}
                </PaginationLink>
              ) : (
                <PaginationEllipsis />
              )}
            </PaginationItem>
          ))}
          <PaginationItem>
            <PaginationNext href="#next" locked={page === pageCount ? "You’re on the last page." : undefined} onClick={go(page + 1)} />
          </PaginationItem>
        </PaginationContent>
      </Pagination>
    </div>
  )
}
```

## <Pagination>

No props.

Also takes the props of `React.ComponentProps<"nav">`.

## <PaginationContent>

No props.

Also takes the props of `React.ComponentProps<"ul">`.

## <PaginationEllipsis>

No props.

Also takes the props of `React.ComponentProps<"span">`.

## <PaginationItem>

No props.

Also takes the props of `React.ComponentProps<"li">`.

## <PaginationLink>

Props:

- `children?: React.ReactNode | undefined`: The content of the link: the page number, or text and icons.
- `isActive?: boolean | undefined`: Marks this link as the current page: `aria-current="page"` and the outlined look.
- `size?: React.ComponentProps<typeof Button>["size"] | undefined` (default `"icon"`): The size of the link, as a button's. Defaults to icon, a square for a page number.
- `page?: number | undefined`: The page this link goes to, used to name it ("Page 3") for screen readers when it has no `aria-label`.
- `asChild?: boolean | undefined`: Render the child element (a router link, say) with the link's styles instead of an <a>.
- `locked?: string | undefined`: Why the person can't go to this page right now ("You're on the first page."). When set, the link stays focusable and marked `aria-disabled`, following it does nothing, and the reason is shown on hover and focus and read to screen readers (see `Locked`).

Also takes the props of `Omit<React.ComponentProps<"a">, "children">`, `RenderProp`.

## <PaginationNext>

No props.

Also takes the props of `Omit<PaginationLinkProps, "size" | "children">`.

## <PaginationPrevious>

No props.

Also takes the props of `Omit<PaginationLinkProps, "size" | "children">`.

## <PaginationRange>

Says which items the page shows, from the numbers the app has: "Showing 21–40 of 312". When the total isn't known (a cursor-paged list) it says "Showing 21–40" and nothing about how many there are; it never estimates one.

Props:

- `from: number`: The position of the first item on this page, counting from 1.
- `to: number`: The position of the last item on this page.
- `total?: number | undefined`: How many items there are in all, when the service has said. Left out, nothing is claimed about the total.

Also takes the props of `Omit<React.ComponentProps<"p">, "children">`.

## Functions

### pageWindow

```ts
pageWindow({ page, pageCount, siblings = 1 }: { page: number; pageCount: number; siblings?: number | undefined })
```

The page numbers to draw for a list of `pageCount` pages with `page` current: the first and last page, the current page and `siblings` pages either side, and an ellipsis marker where pages are left out. Never more entries than it needs, and always exactly the pages that exist. Call it from a client component; it is a plain function with no state, so its output is for the app to render with PaginationLink.

## Strings (`pagination`)

The fixed words, English by default. Override any of them under `pagination` in `<LocaleProvider strings={{ pagination: { … } }}>`; the table is exported as `paginationStrings`.

- `label`: Pagination
- `previous`: Previous
- `next`: Next
- `previousPage`: Go to previous page
- `nextPage`: Go to next page
- `page`: Page {page}
- `morePages`: More pages
- `range`: Showing {from}–{to} of {total}
- `rangeOpen`: Showing {from}–{to}
- `none`: Nothing to show

## Motion

- **tint** (tint): The current page's colours change when the person moves to another page. The page that is current when the pagination first appears is drawn current at once.

Set with `--cd-motion-pagination-<moment>-*` in your brand file; the families are in the design reference.

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider` (fixed strings) and `TermsProvider` (nouns); entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
