{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pagination",
  "title": "Pagination",
  "author": "Tori Bryan",
  "description": "Moves through a long list one page at a time.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "https://fibo.toribryan.com/r/button.json",
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/pagination.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronLeftIcon, ChevronRightIcon } from \"lucide-react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { cn } from \"@/lib/utils\"\n\ntype PaginationNoun = string | { one: string; other: string }\n\ntype PaginationProps = Omit<React.ComponentProps<\"nav\">, \"onChange\"> & {\n  /** The current page, counting from 1. Pass it to control the page. */\n  page?: number\n  /** The page to start on when the page isn't controlled. */\n  defaultPage?: number\n  /** How many pages there are. An empty list still has one page. */\n  pageCount: number\n  /** Called with the new page when Previous or Next is pressed. */\n  onPageChange?: (page: number) => void\n  /** Rows per page. With totalCount, shows the range, such as “1 to 25 of 10,000”. */\n  pageSize?: number\n  /** How many rows there are across every page. */\n  totalCount?: number\n  /** What the rows are, after the range: “members”, or { one, other } for one row. */\n  noun?: PaginationNoun\n}\n\nconst numberFormat = new Intl.NumberFormat(\"en-US\")\n\nfunction formatRange(\n  page: number,\n  pageSize: number,\n  totalCount: number,\n  noun?: PaginationNoun\n) {\n  const first = totalCount === 0 ? 0 : (page - 1) * pageSize + 1\n  const last = Math.min(page * pageSize, totalCount)\n  const label =\n    noun === undefined\n      ? \"\"\n      : typeof noun === \"string\"\n        ? ` ${noun}`\n        : ` ${totalCount === 1 ? noun.one : noun.other}`\n  return `${numberFormat.format(first)} to ${numberFormat.format(last)} of ${numberFormat.format(totalCount)}${label}`\n}\n\nfunction Pagination({\n  className,\n  page: pageProp,\n  defaultPage = 1,\n  pageCount,\n  onPageChange,\n  pageSize,\n  totalCount,\n  noun,\n  \"aria-label\": ariaLabel = \"Pagination\",\n  ...props\n}: PaginationProps) {\n  const [uncontrolledPage, setUncontrolledPage] = React.useState(defaultPage)\n  const lastPage = Math.max(1, pageCount)\n  const page = Math.min(Math.max(1, pageProp ?? uncontrolledPage), lastPage)\n  // Pages taken away keep the stored page with them, so it can't jump back\n  // to a page the reader left when the count grows again.\n  if (pageProp === undefined && uncontrolledPage !== page) {\n    setUncontrolledPage(page)\n  }\n\n  const goTo = (next: number) => {\n    if (next < 1 || next > lastPage || next === page) return\n    if (pageProp === undefined) setUncontrolledPage(next)\n    onPageChange?.(next)\n  }\n\n  return (\n    <nav\n      data-slot=\"pagination\"\n      aria-label={ariaLabel}\n      className={cn(\n        \"flex items-center justify-end gap-4 text-sm text-muted-foreground\",\n        className\n      )}\n      {...props}\n    >\n      {pageSize !== undefined && totalCount !== undefined ? (\n        <span data-slot=\"pagination-range\">\n          {formatRange(page, pageSize, totalCount, noun)}\n        </span>\n      ) : null}\n      <div className=\"flex items-center gap-1\">\n        {/*\n         * Focusable when disabled, so pressing Next onto the last page\n         * doesn't drop keyboard focus to the body.\n         */}\n        <Button\n          data-slot=\"pagination-previous\"\n          variant=\"outline\"\n          size=\"icon-sm\"\n          aria-label=\"Previous page\"\n          disabled={page <= 1}\n          focusableWhenDisabled\n          className=\"data-disabled:pointer-events-none data-disabled:opacity-50\"\n          onClick={() => goTo(page - 1)}\n        >\n          <ChevronLeftIcon />\n        </Button>\n        <span\n          data-slot=\"pagination-page\"\n          className=\"min-w-12 text-center text-foreground tabular-nums\"\n        >\n          <span aria-hidden=\"true\">\n            {numberFormat.format(page)} / {numberFormat.format(lastPage)}\n          </span>\n          <span className=\"sr-only\" aria-live=\"polite\">\n            Page {numberFormat.format(page)} of {numberFormat.format(lastPage)}\n          </span>\n        </span>\n        <Button\n          data-slot=\"pagination-next\"\n          variant=\"outline\"\n          size=\"icon-sm\"\n          aria-label=\"Next page\"\n          disabled={page >= lastPage}\n          focusableWhenDisabled\n          className=\"data-disabled:pointer-events-none data-disabled:opacity-50\"\n          onClick={() => goTo(page + 1)}\n        >\n          <ChevronRightIcon />\n        </Button>\n      </div>\n    </nav>\n  )\n}\n\nexport { formatRange, Pagination }\nexport type { PaginationNoun, PaginationProps }\n",
      "type": "registry:ui"
    }
  ],
  "meta": {
    "tier": "base-components",
    "group": "Navigation",
    "docs": "https://fibo.toribryan.com/?path=/docs/base-components-navigation-pagination--docs"
  },
  "docs": "Docs and live examples: https://fibo.toribryan.com/?path=/docs/base-components-navigation-pagination--docs",
  "categories": [
    "base-components",
    "navigation"
  ],
  "type": "registry:ui"
}