{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "openlmis-pagination",
  "title": "Pagination",
  "description": "Pager for a server-paged list: rows per page, the range on screen out of the total, and first, previous, next and last page buttons, with a matching loading skeleton. Tightens to fit narrow containers.",
  "dependencies": [
    "lucide-react@^1",
    "@base-ui/react@^1"
  ],
  "registryDependencies": [
    "button",
    "select",
    "skeleton"
  ],
  "files": [
    {
      "path": "registry/components/openlmis/pagination/pagination.tsx",
      "content": "\"use client\"\n\nimport {\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  ChevronsLeftIcon,\n  ChevronsRightIcon,\n} from \"lucide-react\"\nimport { type ReactNode, useId } from \"react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\n\nexport type PaginationLabels = {\n  rowsPerPage: string\n  /** The rows on screen out of the total, e.g. \"1-10 / 1,211\". */\n  range: (from: number, to: number, total: number) => string\n  firstPage: string\n  previousPage: string\n  nextPage: string\n  lastPage: string\n}\n\n// One fixed locale, so a server render and the browser print the same digits.\nconst count = new Intl.NumberFormat(\"en-US\")\n\nconst defaultLabels: PaginationLabels = {\n  rowsPerPage: \"Rows Per Page\",\n  range: (from, to, total) =>\n    `${count.format(from)}-${count.format(to)} / ${count.format(total)}`,\n  firstPage: \"First Page\",\n  previousPage: \"Previous Page\",\n  nextPage: \"Next Page\",\n  lastPage: \"Last Page\",\n}\n\nconst DEFAULT_PAGE_SIZE_OPTIONS = [10, 20, 50, 100]\n\ntype PaginationProps = {\n  /** Zero-based. */\n  pageIndex: number\n  pageSize: number\n  /** Total rows across every page. */\n  rowCount: number\n  onPageChange: (pageIndex: number) => void\n  onPageSizeChange: (pageSize: number) => void\n  pageSizeOptions?: number[]\n  labels?: Partial<PaginationLabels>\n}\n\nexport function Pagination({\n  pageIndex,\n  pageSize,\n  rowCount,\n  onPageChange,\n  onPageSizeChange,\n  pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,\n  labels: labelOverrides,\n}: PaginationProps) {\n  const labels = { ...defaultLabels, ...labelOverrides }\n  const pageSizeId = useId()\n  const pageCount = Math.max(Math.ceil(rowCount / pageSize), 1)\n  const lastPageIndex = pageCount - 1\n  // A total that shrank under the current page still reads as the last page, not \"51-47 / 47\".\n  const current = Math.min(pageIndex, lastPageIndex)\n  const from = rowCount === 0 ? 0 : current * pageSize + 1\n  const to = Math.min((current + 1) * pageSize, rowCount)\n  const canPrevious = current > 0\n  const canNext = current < lastPageIndex\n  const items = pageSizeOptions.map((size) => ({\n    value: size,\n    label: String(size),\n  }))\n\n  const controls = [\n    {\n      label: labels.firstPage,\n      icon: ChevronsLeftIcon,\n      enabled: canPrevious,\n      page: 0,\n    },\n    {\n      label: labels.previousPage,\n      icon: ChevronLeftIcon,\n      enabled: canPrevious,\n      page: current - 1,\n    },\n    {\n      label: labels.nextPage,\n      icon: ChevronRightIcon,\n      enabled: canNext,\n      page: current + 1,\n    },\n    {\n      label: labels.lastPage,\n      icon: ChevronsRightIcon,\n      enabled: canNext,\n      page: lastPageIndex,\n    },\n  ]\n\n  return (\n    <PaginationLayout\n      controls={\n        <div className=\"flex items-center gap-1\">\n          {controls.map(({ label, icon: Icon, enabled, page }) => (\n            <Button\n              aria-label={label}\n              disabled={!enabled}\n              key={label}\n              onClick={() => onPageChange(page)}\n              size=\"icon-sm\"\n              variant=\"outline\"\n            >\n              <Icon className=\"rtl:rotate-180\" />\n            </Button>\n          ))}\n        </div>\n      }\n      pageSize={\n        <div className=\"flex items-center gap-2\">\n          {/* Screen readers still get the label when there is no room to show it. */}\n          <label\n            className=\"sr-only text-muted-foreground @md/pagination:not-sr-only\"\n            htmlFor={pageSizeId}\n          >\n            {labels.rowsPerPage}\n          </label>\n          <Select\n            items={items}\n            onValueChange={(value) => {\n              if (value !== null) onPageSizeChange(value)\n            }}\n            value={pageSize}\n          >\n            <SelectTrigger id={pageSizeId} size=\"sm\">\n              <SelectValue />\n            </SelectTrigger>\n            <SelectContent alignItemWithTrigger={false}>\n              {items.map((item) => (\n                <SelectItem key={item.value} value={item.value}>\n                  {item.label}\n                </SelectItem>\n              ))}\n            </SelectContent>\n          </Select>\n        </div>\n      }\n      range={\n        // Hidden but read aloud when narrow; `auto` keeps \"1-10 / 1,211\" in order inside right-to-left text.\n        <p\n          aria-live=\"polite\"\n          className=\"sr-only whitespace-nowrap text-muted-foreground tabular-nums @md/pagination:not-sr-only\"\n          dir=\"auto\"\n        >\n          {labels.range(from, to, rowCount)}\n        </p>\n      }\n    />\n  )\n}\n\n/** Placeholder with the pagination's own layout, so its row keeps its height while loading. */\nexport function PaginationSkeleton() {\n  return (\n    <PaginationLayout\n      controls={<Skeleton className=\"h-7 w-32\" />}\n      pageSize={<Skeleton className=\"h-7 w-16 @md/pagination:w-36\" />}\n      range={\n        <div className=\"hidden @md/pagination:block\">\n          <Skeleton className=\"h-5 w-20\" />\n        </div>\n      }\n    />\n  )\n}\n\ntype PaginationLayoutProps = {\n  pageSize: ReactNode\n  range: ReactNode\n  controls: ReactNode\n}\n\n// Sized by its own width, so it adapts wherever it is placed, not only to the window.\nfunction PaginationLayout({\n  pageSize,\n  range,\n  controls,\n}: PaginationLayoutProps) {\n  return (\n    <div className=\"@container/pagination w-full\">\n      <div className=\"flex items-center justify-between gap-3 text-sm\">\n        {pageSize}\n        <div className=\"flex items-center gap-3\">\n          {range}\n          {controls}\n        </div>\n      </div>\n    </div>\n  )\n}",
      "type": "registry:component",
      "target": "components/openlmis/pagination.tsx"
    }
  ],
  "cssVars": {
    "light": {
      "destructive": "oklch(0.577 0.245 27.325)",
      "destructive-foreground": "oklch(0.985 0 0)",
      "success": "oklch(0.596 0.145 163.225)",
      "success-foreground": "oklch(0.985 0 0)",
      "warning": "oklch(0.666 0.179 58.318)",
      "warning-foreground": "oklch(0.985 0 0)",
      "info": "oklch(0.55 0.13 237)",
      "info-foreground": "oklch(0.985 0 0)"
    },
    "dark": {
      "destructive": "oklch(0.704 0.191 22.216)",
      "destructive-foreground": "oklch(0.21 0.04 25)",
      "success": "oklch(0.696 0.17 162.48)",
      "success-foreground": "oklch(0.21 0.04 163)",
      "warning": "oklch(0.769 0.188 70.08)",
      "warning-foreground": "oklch(0.22 0.04 60)",
      "info": "oklch(0.7 0.14 237)",
      "info-foreground": "oklch(0.2 0.04 240)"
    }
  },
  "meta": {
    "project": "openlmis",
    "height": "288px"
  },
  "categories": [
    "navigation",
    "data-table"
  ],
  "type": "registry:component"
}
