{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pagination-motion",
  "title": "Motion pagination",
  "description": "Styled pagination: the Motion set. 5 decorative pagination variations (SlidePagination, PopPagination, FadePagination, BouncePagination, MorphPagination) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2",
    "lucide-react@^1.23.0"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/pagination-motion.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronLeft, ChevronRight, MoreHorizontal } from \"lucide-react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Motion pagination family: 5 paginators where the active-page indicator moves\n   between pages WITH ANIMATION. The technique differs: a slide with layoutId,\n   pop (scale), fade, bounce (a springy hop) and morph (changing shape).\n   The layoutId derives from React.useId() - so that when several paginations are\n   rendered on the same page the indicators do not fly into each other. All motion\n   is gated on useReducedMotion(): in reduced there is no transform and the\n   indicator lands instantly. Color comes only from semantic tokens; alpha via\n   color-mix. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst iconSize: Record<StyledSize, string> = {\n  sm: \"size-8\",\n  md: \"size-9\",\n  lg: \"size-10\",\n  xl: \"size-12\",\n}\n\nconst focusRing =\n  \"outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50\"\n\nconst btnBase =\n  \"relative inline-flex shrink-0 select-none items-center justify-center font-medium whitespace-nowrap transition-colors [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\nconst arrowBtn = \"rounded-lg border border-border text-foreground hover:bg-accent\"\n\ninterface PaginationProps {\n  className?: string\n  size?: StyledSize\n  total?: number\n  defaultPage?: number\n}\n\n/* Dahili sayfa durumu. */\nfunction usePageState(total: number, defaultPage: number) {\n  const n = Math.max(total, 1)\n  const clamp = React.useCallback((p: number) => Math.min(Math.max(p, 1), n), [n])\n  const [current, setCurrent] = React.useState(() => clamp(defaultPage))\n  const goto = (p: number) => setCurrent(clamp(p))\n  return { current: clamp(current), total: n, goto }\n}\n\nfunction pageRange(current: number, total: number): (number | \"ellipsis\")[] {\n  if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1)\n  const out: (number | \"ellipsis\")[] = [1]\n  const left = Math.max(2, current - 1)\n  const right = Math.min(total - 1, current + 1)\n  if (left > 2) out.push(\"ellipsis\")\n  for (let p = left; p <= right; p++) out.push(p)\n  if (right < total - 1) out.push(\"ellipsis\")\n  out.push(total)\n  return out\n}\n\n/* Shared shell: the indicator renders with each variant's own technique. */\nfunction MotionShell({\n  props,\n  renderIndicator,\n  radius = \"rounded-full\",\n}: {\n  props: PaginationProps\n  renderIndicator: (uid: string, reduce: boolean) => React.ReactNode\n  radius?: string\n}) {\n  const { className, size = \"md\", total = 8, defaultPage = 1 } = props\n  const reduce = useReducedMotion() ?? false\n  const { current, total: n, goto } = usePageState(total, defaultPage)\n  const items = pageRange(current, n)\n  const uid = React.useId()\n  return (\n    <nav\n      data-slot=\"styled-pagination\"\n      aria-label=\"Pagination\"\n      className={cn(\"inline-flex items-center gap-1\", className)}\n    >\n      <button\n        type=\"button\"\n        aria-label=\"Go to previous page\"\n        disabled={current <= 1}\n        onClick={() => goto(current - 1)}\n        className={cn(btnBase, focusRing, iconSize[size], arrowBtn)}\n      >\n        <ChevronLeft />\n      </button>\n      {items.map((it, i) =>\n        it === \"ellipsis\" ? (\n          <span\n            key={`e${i}`}\n            aria-hidden=\"true\"\n            className={cn(iconSize[size], \"inline-flex items-center justify-center text-muted-foreground\")}\n          >\n            <MoreHorizontal />\n          </span>\n        ) : (\n          <button\n            key={it}\n            type=\"button\"\n            aria-label={`Go to page ${it}`}\n            aria-current={it === current ? \"page\" : undefined}\n            onClick={() => goto(it)}\n            className={cn(\n              btnBase,\n              focusRing,\n              iconSize[size],\n              radius,\n              it === current\n                ? \"text-primary-foreground\"\n                : \"text-foreground hover:bg-[color-mix(in_oklab,var(--color-foreground)_8%,transparent)]\"\n            )}\n          >\n            {it === current ? renderIndicator(uid, reduce) : null}\n            <span className=\"relative\">{it}</span>\n          </button>\n        )\n      )}\n      <button\n        type=\"button\"\n        aria-label=\"Go to next page\"\n        disabled={current >= n}\n        onClick={() => goto(current + 1)}\n        className={cn(btnBase, focusRing, iconSize[size], arrowBtn)}\n      >\n        <ChevronRight />\n      </button>\n    </nav>\n  )\n}\n\n/* Slide: gosterge sayfalar arasinda yayla kayar (layoutId). */\nexport function SlidePagination(props: PaginationProps) {\n  return (\n    <MotionShell\n      props={props}\n      renderIndicator={(uid, reduce) => (\n        <motion.span\n          layoutId={reduce ? undefined : `${uid}-slide`}\n          aria-hidden=\"true\"\n          transition={reduce ? { duration: 0 } : { type: \"spring\" as const, stiffness: 520, damping: 36 }}\n          className=\"absolute inset-0 rounded-full bg-primary\"\n        />\n      )}\n    />\n  )\n}\n\n/* Pop: the indicator also does a scale \"pop\" while it slides. */\nexport function PopPagination(props: PaginationProps) {\n  return (\n    <MotionShell\n      props={props}\n      renderIndicator={(uid, reduce) => (\n        <motion.span\n          layoutId={reduce ? undefined : `${uid}-pop`}\n          aria-hidden=\"true\"\n          initial={reduce ? false : { scale: 0.4 }}\n          animate={reduce ? {} : { scale: 1 }}\n          transition={reduce ? { duration: 0 } : { type: \"spring\" as const, stiffness: 700, damping: 20 }}\n          className=\"absolute inset-0 rounded-full bg-primary\"\n        />\n      )}\n    />\n  )\n}\n\n/* Fade: gosterge eski sayfada soner, yenisinde belirir (transform yok). */\nexport function FadePagination(props: PaginationProps) {\n  return (\n    <MotionShell\n      props={props}\n      radius=\"rounded-lg\"\n      renderIndicator={(uid, reduce) => (\n        <AnimatePresence initial={false}>\n          <motion.span\n            key={uid}\n            aria-hidden=\"true\"\n            initial={{ opacity: reduce ? 1 : 0 }}\n            animate={{ opacity: 1 }}\n            exit={{ opacity: 0 }}\n            transition={reduce ? { duration: 0 } : { duration: 0.22, ease: \"easeOut\" }}\n            className=\"absolute inset-0 rounded-lg bg-primary\"\n          />\n        </AnimatePresence>\n      )}\n    />\n  )\n}\n\n/* Bounce: a low-damping spring, so the indicator stops with a slight hop at its\n   target. */\nexport function BouncePagination(props: PaginationProps) {\n  return (\n    <MotionShell\n      props={props}\n      renderIndicator={(uid, reduce) => (\n        <motion.span\n          layoutId={reduce ? undefined : `${uid}-bounce`}\n          aria-hidden=\"true\"\n          transition={reduce ? { duration: 0 } : { type: \"spring\" as const, stiffness: 420, damping: 12, mass: 0.8 }}\n          className=\"absolute inset-0 rounded-full bg-primary\"\n        />\n      )}\n    />\n  )\n}\n\n/* Morph: gosterge kayarken kare ile daire arasinda sekil degistirir. */\nexport function MorphPagination(props: PaginationProps) {\n  return (\n    <MotionShell\n      props={props}\n      radius=\"rounded-lg\"\n      renderIndicator={(uid, reduce) => (\n        <motion.span\n          layoutId={reduce ? undefined : `${uid}-morph`}\n          aria-hidden=\"true\"\n          initial={reduce ? false : { borderRadius: \"50%\" }}\n          animate={reduce ? {} : { borderRadius: \"0.5rem\" }}\n          transition={reduce ? { duration: 0 } : { type: \"spring\" as const, stiffness: 380, damping: 30 }}\n          className=\"absolute inset-0 bg-primary\"\n        />\n      )}\n    />\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/pagination-motion.tsx"
    }
  ],
  "categories": [
    "styled",
    "pagination"
  ],
  "type": "registry:component"
}