{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pagination",
  "title": "Pagination",
  "author": "AniBT <https://anibt.net>",
  "description": "AniBT pager: quiet 32px cells in the round face, the current page washed pink with a ring, 44px touch targets, shadcn's parts with Chinese defaults, and a Pager that draws the whole row from page and pageCount.",
  "dependencies": [
    "lucide-react",
    "radix-ui"
  ],
  "registryDependencies": [
    "@anibt/theme"
  ],
  "files": [
    {
      "path": "registry/anibt/ui/pagination.tsx",
      "content": "import * as React from \"react\"\nimport { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from \"lucide-react\"\nimport { Slot } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\n/**\n * Pagination — the AniBT 「贴纸手账」 pager.\n *\n * Quiet 32px cells in the round face (`anibt-pager-item`); the pointer brings\n * the pink wash, and the current page is washed with a pink ring. On touch\n * widths each cell is a 44px target, from `sm` A's 32.\n *\n * The parts are shadcn's (`Pagination`, `PaginationContent`, `PaginationItem`,\n * `PaginationLink`, `PaginationPrevious`, `PaginationNext`,\n * `PaginationEllipsis`), with Chinese defaults for the words. Which pages\n * exist and where a link points stay with the page that owns the data —\n * or hand both to `Pager`, which draws the whole row from `page` and\n * `pageCount`.\n *\n * A link with an `href` is an `<a>`; without one it is a `<button>` (a pager\n * driven by state or a cursor still has to be reachable by Tab). `asChild`\n * hands the cell to a router link.\n */\nfunction Pagination({ className, ...props }: React.ComponentProps<\"nav\">) {\n  return (\n    <nav\n      aria-label=\"分页\"\n      data-slot=\"pagination\"\n      className={cn(\"mx-auto flex w-full justify-center\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction PaginationContent({ className, ...props }: React.ComponentProps<\"ul\">) {\n  return (\n    <ul data-slot=\"pagination-content\" className={cn(\"flex flex-row items-center gap-1\", className)} {...props} />\n  )\n}\n\nfunction PaginationItem({ ...props }: React.ComponentProps<\"li\">) {\n  return <li data-slot=\"pagination-item\" {...props} />\n}\n\nconst CELL =\n  \"anibt-pager-item h-11 min-w-11 outline-hidden select-none sm:h-8 sm:min-w-8 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:outline-solid disabled:pointer-events-none disabled:text-muted-foreground/60 aria-disabled:pointer-events-none aria-disabled:text-muted-foreground/60 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\"\n\ntype PaginationLinkProps = {\n  /** The current page: the pink ring and `aria-current=\"page\"`. */\n  isActive?: boolean\n  /** shadcn's button size names; `icon` is the square cell, the rest pad. */\n  size?: \"default\" | \"sm\" | \"lg\" | \"icon\" | \"icon-sm\" | \"icon-lg\" | \"xs\" | \"icon-xs\"\n  /** Hand the cell to the single child (a router `Link`). */\n  asChild?: boolean\n  disabled?: boolean\n} & Omit<React.ComponentProps<\"a\">, \"ref\"> &\n  Omit<React.ComponentProps<\"button\">, keyof React.ComponentProps<\"a\"> | \"ref\">\n\nfunction PaginationLink({\n  className,\n  isActive,\n  size = \"icon\",\n  asChild = false,\n  disabled,\n  href,\n  ...props\n}: PaginationLinkProps) {\n  const classes = cn(CELL, size.startsWith(\"icon\") ? \"px-2\" : \"gap-1 px-2.5\", isActive && \"anibt-pager-item-on\", className)\n  const shared = {\n    \"data-slot\": \"pagination-link\",\n    \"data-active\": isActive ? \"true\" : undefined,\n    \"aria-current\": isActive ? (\"page\" as const) : undefined,\n    className: classes,\n  }\n\n  if (asChild) {\n    return <Slot.Root {...shared} aria-disabled={disabled || undefined} {...props} />\n  }\n\n  if (href !== undefined) {\n    return (\n      <a\n        {...shared}\n        href={disabled ? undefined : href}\n        aria-disabled={disabled || undefined}\n        {...(props as React.ComponentProps<\"a\">)}\n      />\n    )\n  }\n\n  return (\n    <button type=\"button\" {...shared} disabled={disabled} {...(props as React.ComponentProps<\"button\">)} />\n  )\n}\n\ntype PaginationStepProps = React.ComponentProps<typeof PaginationLink> & {\n  /** The word beside the arrow (hidden below `sm`). */\n  text?: string\n  /** Arrow only, at every width. */\n  iconOnly?: boolean\n}\n\nfunction PaginationPrevious({ className, text = \"上一页\", iconOnly = false, ...props }: PaginationStepProps) {\n  return (\n    <PaginationLink\n      aria-label={text}\n      size={iconOnly ? \"icon\" : \"default\"}\n      className={cn(!iconOnly && \"sm:pl-2\", className)}\n      {...props}\n    >\n      <ChevronLeftIcon aria-hidden=\"true\" />\n      {iconOnly ? null : <span className=\"hidden sm:block\">{text}</span>}\n    </PaginationLink>\n  )\n}\n\nfunction PaginationNext({ className, text = \"下一页\", iconOnly = false, ...props }: PaginationStepProps) {\n  return (\n    <PaginationLink\n      aria-label={text}\n      size={iconOnly ? \"icon\" : \"default\"}\n      className={cn(!iconOnly && \"sm:pr-2\", className)}\n      {...props}\n    >\n      {iconOnly ? null : <span className=\"hidden sm:block\">{text}</span>}\n      <ChevronRightIcon aria-hidden=\"true\" />\n    </PaginationLink>\n  )\n}\n\nfunction PaginationEllipsis({ className, label = \"更多页\", ...props }: React.ComponentProps<\"span\"> & { label?: string }) {\n  return (\n    <span\n      data-slot=\"pagination-ellipsis\"\n      className={cn(\"flex h-11 w-8 items-center justify-center text-muted-foreground sm:h-8 sm:w-6\", className)}\n      {...props}\n    >\n      <MoreHorizontalIcon className=\"size-4\" aria-hidden=\"true\" />\n      <span className=\"sr-only\">{label}</span>\n    </span>\n  )\n}\n\n/**\n * The pages to draw for `page` of `pageCount`: the first and last\n * `boundaries` pages, `siblings` on each side of the current one, and\n * `\"ellipsis\"` for every gap of two or more. A gap of one page draws the page.\n */\nfunction getPageWindow(page: number, pageCount: number, siblings = 1, boundaries = 1): Array<number | \"ellipsis\"> {\n  const count = Math.max(1, Math.floor(pageCount))\n  const current = Math.min(Math.max(1, Math.floor(page)), count)\n  const keep = new Set<number>()\n  for (let i = 1; i <= Math.min(boundaries, count); i++) keep.add(i)\n  for (let i = Math.max(1, count - boundaries + 1); i <= count; i++) keep.add(i)\n  for (let i = Math.max(1, current - siblings); i <= Math.min(count, current + siblings); i++) keep.add(i)\n\n  const sorted = [...keep].sort((a, b) => a - b)\n  const out: Array<number | \"ellipsis\"> = []\n  for (const value of sorted) {\n    const last = out[out.length - 1]\n    if (typeof last === \"number\" && value - last === 2) out.push(last + 1)\n    else if (typeof last === \"number\" && value - last > 2) out.push(\"ellipsis\")\n    out.push(value)\n  }\n  return out\n}\n\ntype PagerProps = Omit<React.ComponentProps<\"nav\">, \"onChange\"> & {\n  /** The current page, from 1. */\n  page: number\n  pageCount: number\n  /** Links: the URL of a page. Leave it out to get buttons. */\n  getHref?: (page: number) => string\n  /** Called with the page a reader picked (with or without `getHref`). */\n  onPageChange?: (page: number) => void\n  /** Pages kept on each side of the current one. Default 1. */\n  siblings?: number\n  /** Pages kept at each end. Default 1. */\n  boundaries?: number\n  /** Show 上一页 / 下一页 beside the arrows from `sm`. Default arrows only. */\n  showText?: boolean\n  previousText?: string\n  nextText?: string\n  /** The accessible name of a page cell. Default `第 N 页`. */\n  pageLabel?: (page: number) => string\n}\n\n/**\n * A whole pager row from `page` and `pageCount`: ‹ 1 2 [3] 4 … 12 ›.\n * One page draws nothing — a short list is not paginated.\n */\nfunction Pager({\n  page,\n  pageCount,\n  getHref,\n  onPageChange,\n  siblings = 1,\n  boundaries = 1,\n  showText = false,\n  previousText = \"上一页\",\n  nextText = \"下一页\",\n  pageLabel = (value) => `第 ${value} 页`,\n  ...props\n}: PagerProps) {\n  if (pageCount <= 1) return null\n  const current = Math.min(Math.max(1, page), pageCount)\n\n  const target = (value: number) => ({\n    href: getHref?.(value),\n    onClick: onPageChange ? () => onPageChange(value) : undefined,\n  })\n\n  return (\n    <Pagination {...props}>\n      <PaginationContent>\n        <PaginationItem>\n          <PaginationPrevious\n            text={previousText}\n            iconOnly={!showText}\n            disabled={current <= 1}\n            {...target(current - 1)}\n          />\n        </PaginationItem>\n        {getPageWindow(current, pageCount, siblings, boundaries).map((value, index) =>\n          value === \"ellipsis\" ? (\n            <PaginationItem key={`gap-${index}`}>\n              <PaginationEllipsis />\n            </PaginationItem>\n          ) : (\n            <PaginationItem key={value}>\n              <PaginationLink isActive={value === current} aria-label={pageLabel(value)} {...target(value)}>\n                {value}\n              </PaginationLink>\n            </PaginationItem>\n          )\n        )}\n        <PaginationItem>\n          <PaginationNext\n            text={nextText}\n            iconOnly={!showText}\n            disabled={current >= pageCount}\n            {...target(current + 1)}\n          />\n        </PaginationItem>\n      </PaginationContent>\n    </Pagination>\n  )\n}\n\nexport {\n  Pagination,\n  PaginationContent,\n  PaginationEllipsis,\n  PaginationItem,\n  PaginationLink,\n  PaginationNext,\n  PaginationPrevious,\n  Pager,\n  getPageWindow,\n}\nexport type { PaginationLinkProps, PagerProps }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "navigation"
  ],
  "type": "registry:ui"
}