{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pagination",
  "title": "Pagination",
  "description": "Previous, next and numbered pages for a long list, with gaps past the neighbours of the current page.",
  "registryDependencies": [
    "https://sproutboat.com/ikada/r/theme.json",
    "https://sproutboat.com/ikada/r/cn.json",
    "https://sproutboat.com/ikada/r/icons.json"
  ],
  "files": [
    {
      "path": "registry/ikada/ui/pagination.tsx",
      "content": "\"use client\";\n\nimport type { ComponentProps } from \"react\";\nimport { cn, coarseTarget, focusRing, pressMotion } from \"@/components/ikada/lib/cn\";\nimport { ChevronLeftIcon, ChevronRightIcon } from \"@/components/ikada/lib/icons\";\n\nexport interface PaginationProps extends Omit<ComponentProps<\"nav\">, \"onChange\"> {\n  /** Current page, starting at 1. */\n  page: number;\n  /** Total number of pages. */\n  pageCount: number;\n  /** Called with the page to go to. */\n  onPageChange: (page: number) => void;\n  /** Previous and next plus \"Page 3 of 12\", no number buttons. Use it on narrow screens. */\n  compact?: boolean;\n  /** Pages shown either side of the current one. Default 1. */\n  siblings?: number;\n}\n\n/** A run of hidden pages: before the current window or after it. */\nexport type PageGap = \"gap-start\" | \"gap-end\";\n\n/**\n * The page numbers to show. Past a short list it is always 2 * siblings + 5 slots (first, last, two\n * gaps or pages, and the window), so the control keeps its width while you page, and a gap never\n * hides a single page: it would take the same room as the number it replaces.\n */\nexport function pageRange(page: number, pageCount: number, siblings = 1): (number | PageGap)[] {\n  const s = Math.max(0, Math.floor(siblings));\n  const slots = 2 * s + 5;\n  const all = (from: number, to: number): number[] =>\n    Array.from({ length: Math.max(0, to - from + 1) }, (_, i) => from + i);\n  if (pageCount <= slots) return all(1, pageCount);\n  const current = Math.min(Math.max(1, page), pageCount);\n  if (current - s <= 3) return [...all(1, 2 * s + 3), \"gap-end\", pageCount];\n  if (current + s >= pageCount - 2) return [1, \"gap-start\", ...all(pageCount - (2 * s + 2), pageCount)];\n  return [1, \"gap-start\", ...all(current - s, current + s), \"gap-end\", pageCount];\n}\n\nconst cell = cn(\n  \"inline-grid h-8 min-w-8 place-items-center rounded-md px-2 text-sb-sm font-semibold tabular-nums text-fg-muted\",\n  \"hover:bg-hover hover:text-fg\",\n  pressMotion,\n  \"disabled:pointer-events-none disabled:opacity-50\",\n  coarseTarget,\n  focusRing,\n);\n\n/** Previous, next and numbered pages for a long list, with gaps past the neighbours of the current page. */\nexport function Pagination({\n  page,\n  pageCount,\n  onPageChange,\n  compact = false,\n  siblings = 1,\n  className,\n  ref,\n  ...props\n}: PaginationProps) {\n  const go = (next: number) => onPageChange(Math.min(Math.max(1, next), Math.max(1, pageCount)));\n  return (\n    <nav\n      data-slot=\"pagination\"\n      ref={ref}\n      aria-label=\"Pagination\"\n      className={cn(\"flex flex-wrap items-center gap-1\", className)}\n      {...props}\n    >\n      <button\n        type=\"button\"\n        aria-label=\"Previous page\"\n        disabled={page <= 1}\n        onClick={() => go(page - 1)}\n        className={cell}\n      >\n        <ChevronLeftIcon className=\"rtl:-scale-x-100\" />\n      </button>\n      {compact ? (\n        <span className=\"min-w-0 px-2 text-sb-sm text-fg-muted tabular-nums\" aria-live=\"polite\">\n          Page {page} of {pageCount}\n        </span>\n      ) : (\n        <ul className=\"flex flex-wrap items-center gap-1\">\n          {pageRange(page, pageCount, siblings).map((entry) => (\n            <li key={entry}>\n              {entry === \"gap-start\" || entry === \"gap-end\" ? (\n                <span aria-hidden=\"true\" className=\"inline-grid h-8 min-w-6 place-items-center text-fg-muted\">\n                  …\n                </span>\n              ) : (\n                <button\n                  type=\"button\"\n                  aria-label={`Page ${entry}`}\n                  aria-current={entry === page ? \"page\" : undefined}\n                  onClick={() => go(entry)}\n                  className={cn(\n                    cell,\n                    \"aria-[current=page]:bg-hover aria-[current=page]:text-fg aria-[current=page]:shadow-pill\",\n                  )}\n                >\n                  {entry}\n                </button>\n              )}\n            </li>\n          ))}\n        </ul>\n      )}\n      <button\n        type=\"button\"\n        aria-label=\"Next page\"\n        disabled={page >= pageCount}\n        onClick={() => go(page + 1)}\n        className={cell}\n      >\n        <ChevronRightIcon className=\"rtl:-scale-x-100\" />\n      </button>\n    </nav>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@components/ikada/pagination.tsx"
    }
  ],
  "categories": [
    "navigation"
  ],
  "type": "registry:ui"
}