{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"pagination","title":"Pagination","description":"Navigation between the pages of a long list or table, built from Button links.","dependencies":["lucide-react@1.18.0"],"registryDependencies":["@siteplane/base","@siteplane/button"],"files":[{"path":"packages/ui/src/components/pagination/pagination.tsx","content":"\"use client\";\n\nimport {\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  MoreHorizontalIcon,\n} from \"lucide-react\";\nimport * as React from \"react\";\n\nimport {\n  type SiteplaneControlSize,\n  useResolvedControlSize,\n  useSiteplaneUI,\n} from \"../../system/provider/siteplane-ui-provider\";\nimport { cn } from \"../../system/utils/cn\";\nimport { slotClass } from \"../../system/utils/slot-class\";\nimport {\n  type Button,\n  type ButtonBaseProps,\n  type ButtonStandardProps,\n} from \"../button\";\nimport { KitButton, type KitButtonProps } from \"../button/button-internal\";\n\nconst paginationIconSizeByControlSize = {\n  sm: \"icon-sm\",\n  md: \"icon-md\",\n  lg: \"icon-lg\",\n  xl: \"icon-xl\",\n} as const satisfies Record<\n  SiteplaneControlSize,\n  React.ComponentProps<typeof Button>[\"size\"]\n>;\n\n/**\n * The words of a Pagination. `labels` on Pagination replaces them, merged with\n * the English defaults: pass the translation of each one for an application in\n * another language.\n */\nexport type PaginationLabels = {\n  /** The name of the navigation landmark: \"pagination\" by default. */\n  navigation: string;\n  /** The text of PaginationPrevious: \"Previous\" by default. */\n  previous: string;\n  /** The accessible name of PaginationPrevious: \"Go to previous page\" by default. */\n  previousPage: string;\n  /** The text of PaginationNext: \"Next\" by default. */\n  next: string;\n  /** The accessible name of PaginationNext: \"Go to next page\" by default. */\n  nextPage: string;\n  /** The hidden text of PaginationEllipsis: \"More pages\" by default. */\n  morePages: string;\n};\n\nconst defaultPaginationLabels: PaginationLabels = {\n  navigation: \"pagination\",\n  previous: \"Previous\",\n  previousPage: \"Go to previous page\",\n  next: \"Next\",\n  nextPage: \"Go to next page\",\n  morePages: \"More pages\",\n};\n\nconst PaginationContext = React.createContext<{\n  labels: PaginationLabels;\n  variant: NonNullable<PaginationProps[\"variant\"]>;\n}>({ labels: defaultPaginationLabels, variant: \"default\" });\n\nexport interface PaginationProps extends React.ComponentProps<\"nav\"> {\n  animate?: boolean | undefined;\n  /** Replaces the English words of the parts, such as Previous and Next. */\n  labels?: Partial<PaginationLabels> | undefined;\n  variant?: \"default\" | \"ghost\" | undefined;\n}\n\nexport function Pagination({\n  animate = true,\n  children,\n  className,\n  labels: labelsProp,\n  variant = \"default\",\n  ...props\n}: PaginationProps): React.ReactElement {\n  const labels = React.useMemo(\n    () => ({ ...defaultPaginationLabels, ...labelsProp }),\n    [labelsProp],\n  );\n  const context = React.useMemo(() => ({ labels, variant }), [labels, variant]);\n  return (\n    <PaginationContext.Provider value={context}>\n      <nav\n        aria-label={labels.navigation}\n        className={cn(\n          slotClass(\"pagination\", props),\n          \"mx-auto flex w-full justify-center\",\n          className,\n        )}\n        data-animate={animate ? undefined : \"false\"}\n        data-slot=\"pagination\"\n        data-variant={variant}\n        {...props}\n      >\n        {children}\n      </nav>\n    </PaginationContext.Provider>\n  );\n}\n\nexport function PaginationContent({\n  className,\n  ref,\n  ...props\n}: React.ComponentProps<\"ul\">): React.ReactElement {\n  const listRef = React.useRef<HTMLUListElement>(null);\n  React.useImperativeHandle(ref, () => listRef.current as HTMLUListElement);\n  // The active fill sits under the current page's link. As Base UI and Radix\n  // place an indicator, it is measured in one effect with observers created\n  // once: when the list mounts, when it resizes, and when the current page or\n  // the links change. Each pass reads both boxes before it writes.\n  React.useLayoutEffect(() => {\n    const list = listRef.current;\n    if (!list) return;\n    const updateIndicator = () => {\n      const activeLink = list.querySelector<HTMLElement>(\n        '[data-slot=\"pagination-link\"][data-active=\"true\"]',\n      );\n      if (!activeLink) {\n        delete list.dataset.activeIndicator;\n        return;\n      }\n      const listRect = list.getBoundingClientRect();\n      const activeRect = activeLink.getBoundingClientRect();\n      list.style.setProperty(\n        \"--pagination-active-left\",\n        `${activeRect.left - listRect.left}px`,\n      );\n      list.style.setProperty(\n        \"--pagination-active-top\",\n        `${activeRect.top - listRect.top}px`,\n      );\n      list.style.setProperty(\n        \"--pagination-active-width\",\n        `${activeRect.width}px`,\n      );\n      list.style.setProperty(\n        \"--pagination-active-height\",\n        `${activeRect.height}px`,\n      );\n      list.dataset.activeIndicator = \"\";\n    };\n    updateIndicator();\n    const resizeObserver = new ResizeObserver(updateIndicator);\n    resizeObserver.observe(list);\n    // A page change moves `data-active` to another link; new pages replace the\n    // links or their text.\n    const changeObserver = new MutationObserver(updateIndicator);\n    changeObserver.observe(list, {\n      attributeFilter: [\"data-active\"],\n      attributes: true,\n      characterData: true,\n      childList: true,\n      subtree: true,\n    });\n    return () => {\n      resizeObserver.disconnect();\n      changeObserver.disconnect();\n    };\n  }, []);\n  return (\n    <ul\n      className={cn(\n        slotClass(\"pagination-content\", props),\n        \"relative z-0 flex flex-row items-center gap-1 before:pointer-events-none before:absolute before:top-0 before:left-0 before:-z-1 before:h-(--pagination-active-height) before:w-(--pagination-active-width) before:translate-x-(--pagination-active-left) before:translate-y-(--pagination-active-top) before:opacity-0 data-[active-indicator]:before:opacity-100\",\n        className,\n      )}\n      data-slot=\"pagination-content\"\n      ref={listRef}\n      {...props}\n    />\n  );\n}\n\nexport function PaginationItem(\n  props: React.ComponentProps<\"li\">,\n): React.ReactElement {\n  return (\n    <li\n      data-slot=\"pagination-item\"\n      {...props}\n      className={cn(slotClass(\"pagination-item\", props), props.className)}\n    />\n  );\n}\n\ntype PaginationActiveProps = {\n  /** Marks the current page: `aria-current=\"page\"` and the active fill. */\n  active?: boolean;\n  /** @deprecated Use `active`. */\n  isActive?: boolean;\n};\n\nexport type PaginationLinkProps = ButtonStandardProps & PaginationActiveProps;\n\ntype PaginationDirectionalProps = ButtonBaseProps & PaginationActiveProps;\n\nexport function PaginationLink(\n  linkProps: PaginationLinkProps,\n): React.ReactElement {\n  // Previous and Next pass the internal responsive-navigation profile through\n  // KitPaginationLink, the same function with the wider props type.\n  const {\n    active: activeProp,\n    className,\n    disabled,\n    isActive,\n    size,\n    render,\n    ...props\n  } = linkProps as KitPaginationLinkProps;\n  const { controlSize } = useSiteplaneUI();\n  const resolvedSize = size ?? paginationIconSizeByControlSize[controlSize];\n  const active = activeProp ?? isActive;\n  return (\n    <KitButton\n      aria-current={active ? \"page\" : undefined}\n      className={className}\n      data-active={active}\n      data-slot=\"pagination-link\"\n      disabled={disabled}\n      render={render}\n      size={resolvedSize}\n      variant=\"ghost\"\n      {...props}\n    />\n  );\n}\n\ntype KitPaginationLinkProps = KitButtonProps & PaginationActiveProps;\n\nconst KitPaginationLink = PaginationLink as (\n  props: KitPaginationLinkProps,\n) => React.ReactElement;\n\n// Previous points to the inline start and Next to the inline end, so their\n// chevrons mirror in a right-to-left page.\nexport function PaginationPrevious({\n  className,\n  size,\n  ...props\n}: PaginationDirectionalProps): React.ReactElement {\n  const resolvedSize = useResolvedControlSize(size);\n  const { labels, variant } = React.useContext(PaginationContext);\n  return (\n    <KitPaginationLink\n      aria-label={labels.previousPage}\n      className={className}\n      profile=\"responsive-navigation\"\n      profileMode={variant === \"ghost\" ? \"compact\" : \"responsive\"}\n      size={resolvedSize}\n      {...props}\n    >\n      <ChevronLeftIcon className=\"in-data-[profile-mode=compact]:m-0 rtl:-scale-x-100\" />\n      <span className=\"max-sm:hidden in-data-[profile-mode=compact]:hidden\">\n        {labels.previous}\n      </span>\n    </KitPaginationLink>\n  );\n}\n\nexport function PaginationNext({\n  className,\n  size,\n  ...props\n}: PaginationDirectionalProps): React.ReactElement {\n  const resolvedSize = useResolvedControlSize(size);\n  const { labels, variant } = React.useContext(PaginationContext);\n  return (\n    <KitPaginationLink\n      aria-label={labels.nextPage}\n      className={className}\n      profile=\"responsive-navigation\"\n      profileMode={variant === \"ghost\" ? \"compact\" : \"responsive\"}\n      size={resolvedSize}\n      {...props}\n    >\n      <span className=\"max-sm:hidden in-data-[profile-mode=compact]:hidden\">\n        {labels.next}\n      </span>\n      <ChevronRightIcon className=\"in-data-[profile-mode=compact]:m-0 rtl:-scale-x-100\" />\n    </KitPaginationLink>\n  );\n}\n\nexport function PaginationEllipsis({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">): React.ReactElement {\n  const { labels } = React.useContext(PaginationContext);\n  return (\n    <span\n      aria-hidden\n      className={cn(\n        slotClass(\"pagination-ellipsis\", props),\n        \"flex min-w-7 justify-center\",\n        className,\n      )}\n      data-slot=\"pagination-ellipsis\"\n      {...props}\n    >\n      <MoreHorizontalIcon className=\"size-5 sm:size-4\" />\n      <span className=\"sr-only\">{labels.morePages}</span>\n    </span>\n  );\n}\n","type":"registry:ui","target":"~/src/siteplane/components/pagination/pagination.tsx"},{"path":"packages/ui/src/components/pagination/index.ts","content":"export * from \"./pagination\";\n","type":"registry:ui","target":"~/src/siteplane/components/pagination/index.ts"}],"type":"registry:ui","docs":"https://ui.siteplane.io/docs-markdown/components/pagination.md","categories":["Navigation"],"meta":{"avoidWhen":["Avoid for infinite scrolling or a Load more action; use Button.","Avoid for a handful of sections; use Tabs."],"description":"Navigation between the pages of a long list or table, built from Button links.","knownLimitations":["Page count, slicing, URL state and data fetching are not built in; the application owns them."],"providerNotes":[],"releaseStatus":"stable","searchTerms":["pagination"],"useWhen":["Use to move between the pages of a long list or table when the application owns the page state."]}}