{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pagination",
  "title": "Pagination",
  "dependencies": [
    "@phosphor-icons/react"
  ],
  "registryDependencies": [
    "https://ui.hotfix.jobs/r/recipes.json",
    "https://ui.hotfix.jobs/r/tokens.json",
    "https://ui.hotfix.jobs/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/components/ui/pagination.tsx",
      "content": "\"use client\";\n\nimport type * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { selectionFocus } from \"@/lib/recipes\";\n\nimport { CaretLeft, CaretRight } from \"@phosphor-icons/react/dist/ssr\";\nexport interface PaginationProps {\n  /** Current page (1-indexed). */\n  page: number;\n  totalPages: number;\n  /** When provided, each control renders as a real `<a href>` for SEO-friendly navigation. */\n  href?: (page: number) => string;\n  /** Client-side handler. Rendered as `<button>` when `href` is not given. */\n  onPageChange?: (page: number) => void;\n  /** Number of page-number siblings on each side of the current page. Default 1. */\n  siblingCount?: number;\n  loading?: boolean;\n  className?: string;\n}\n\n/** Loose row of page chips flanked by prev/next arrows. Renders nothing when totalPages <= 1. */\nexport function Pagination({\n  page,\n  totalPages,\n  href,\n  onPageChange,\n  siblingCount = 1,\n  loading = false,\n  className,\n}: PaginationProps): React.ReactElement | null {\n  if (totalPages <= 1) return null;\n\n  const atFirst = page === 1;\n  const atLast = page === totalPages;\n  const items = getPageItems(page, totalPages, siblingCount);\n\n  return (\n    <nav\n      data-slot=\"pagination\"\n      aria-label=\"Pagination\"\n      aria-busy={loading || undefined}\n      className={cn(\n        \"inline-flex items-center gap-0.5\",\n        loading && \"opacity-70\",\n        className,\n      )}\n    >\n      <PageCell\n        page={page - 1}\n        disabled={atFirst || loading}\n        href={href}\n        onPageChange={onPageChange}\n        ariaLabel=\"Previous page\"\n        icon={<CaretLeft className=\"size-3.5\" />}\n      />\n      {items.map((item, i) => {\n        if (item === \"ellipsis\") {\n          return (\n            <span\n              key={`ellipsis-${i}`}\n              aria-hidden\n              className=\"inline-flex size-8 shrink-0 items-center justify-center text-ink-tertiary tabular-nums\"\n              data-slot=\"pagination-ellipsis\"\n            >\n              …\n            </span>\n          );\n        }\n        return (\n          <PageCell\n            key={item}\n            page={item}\n            current={item === page}\n            disabled={loading}\n            href={href}\n            onPageChange={onPageChange}\n            ariaLabel={`Page ${item}`}\n            label={String(item)}\n          />\n        );\n      })}\n      <PageCell\n        page={page + 1}\n        disabled={atLast || loading}\n        href={href}\n        onPageChange={onPageChange}\n        ariaLabel=\"Next page\"\n        icon={<CaretRight className=\"size-3.5\" />}\n      />\n    </nav>\n  );\n}\n\ninterface PageCellProps {\n  page: number;\n  label?: string;\n  icon?: React.ReactNode;\n  ariaLabel: string;\n  current?: boolean;\n  disabled?: boolean;\n  href?: (page: number) => string;\n  onPageChange?: (page: number) => void;\n}\n\nfunction PageCell({\n  page,\n  label,\n  icon,\n  ariaLabel,\n  current,\n  disabled,\n  href,\n  onPageChange,\n}: PageCellProps): React.ReactElement {\n  const className = cn(\n    // Fixed 32x32 chip + tabular-nums so the row width stays stable across page counts.\n    \"inline-flex size-8 shrink-0 items-center justify-center rounded-[var(--radius-8)] text-small tabular-nums transition-colors duration-[var(--duration-state)] ease-[var(--ease-standard)]\",\n    current\n      ? \"bg-layer-hover font-medium text-ink\"\n      : \"text-ink-muted hover:bg-layer-hover hover:text-ink\",\n    \"active:bg-layer-hover\",\n    disabled && \"pointer-events-none opacity-50 text-ink-tertiary hover:bg-transparent active:bg-transparent\",\n    selectionFocus,\n  );\n  const inner = icon ?? label;\n\n  if (href && !disabled) {\n    return (\n      <a\n        href={href(page)}\n        aria-label={ariaLabel}\n        aria-current={current ? \"page\" : undefined}\n        className={className}\n      >\n        {inner}\n      </a>\n    );\n  }\n  return (\n    <button\n      type=\"button\"\n      onClick={() => !disabled && onPageChange?.(page)}\n      disabled={disabled}\n      aria-label={ariaLabel}\n      aria-current={current ? \"page\" : undefined}\n      className={className}\n    >\n      {inner}\n    </button>\n  );\n}\n\n// Produces exactly `siblingCount * 2 + 5` items when ellipses are needed, so the row\n// width stays stable as `page` changes. Near start/end, one side widens to absorb\n// the slot the missing ellipsis would have taken.\nfunction getPageItems(\n  page: number,\n  totalPages: number,\n  siblingCount: number,\n): Array<number | \"ellipsis\"> {\n  const totalRendered = siblingCount * 2 + 5;\n\n  // Short ranges: everything fits without ellipsis.\n  if (totalPages <= totalRendered) {\n    return Array.from({ length: totalPages }, (_, i) => i + 1);\n  }\n\n  const items: Array<number | \"ellipsis\"> = [];\n\n  // Near start: [1 .. 2·sib+3] … last  → same total slots.\n  if (page <= siblingCount + 3) {\n    for (let p = 1; p <= siblingCount * 2 + 3; p++) items.push(p);\n    items.push(\"ellipsis\");\n    items.push(totalPages);\n    return items;\n  }\n\n  // Near end: 1 … [last-(2·sib+2) .. last]\n  if (page >= totalPages - siblingCount - 2) {\n    items.push(1);\n    items.push(\"ellipsis\");\n    for (let p = totalPages - siblingCount * 2 - 2; p <= totalPages; p++) {\n      items.push(p);\n    }\n    return items;\n  }\n\n  // Middle: 1 … [current-sib .. current+sib] … last\n  items.push(1);\n  items.push(\"ellipsis\");\n  for (let p = page - siblingCount; p <= page + siblingCount; p++) items.push(p);\n  items.push(\"ellipsis\");\n  items.push(totalPages);\n  return items;\n}\n",
      "type": "registry:ui",
      "target": "components/ui/pagination.tsx"
    }
  ],
  "type": "registry:ui"
}