{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pagination-styled",
  "title": "Pagination essentials",
  "description": "Styled pagination: the essentials set. 5 decorative pagination variations (NumberedPagination, ArrowsPagination, DotsPagination, PillPagination, MinimalPagination) 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-styled.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronLeft, ChevronRight, MoreHorizontal } from \"lucide-react\"\nimport { motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Pagination family: 5 decorative paginators. Color comes ONLY from semantic tokens;\n   the active page is a primary token, via alpha color-mix (never var(--x)/0.4).\n   Controlled (page/ onPageChange) or uncontrolled (defaultPage). The buttons are real\n   <button>s with a focus-visible ring. Gated on framer-motion's useReducedMotion. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst controlSize: Record<StyledSize, string> = {\n  sm: \"h-8 min-w-8 text-sm\",\n  md: \"h-9 min-w-9 text-sm\",\n  lg: \"h-10 min-w-10 text-sm\",\n  xl: \"h-12 min-w-12 text-base\",\n}\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\ninterface PaginationProps {\n  className?: string\n  size?: StyledSize\n  total?: number\n  page?: number\n  defaultPage?: number\n  onPageChange?: (p: number) => void\n}\n\n/* page state: kontrollu (page verilirse) yoksa kontrolsuz (defaultPage). */\nfunction usePagination(props: PaginationProps) {\n  const { total = 8, page, defaultPage = 1, onPageChange } = props\n  const clamp = React.useCallback(\n    (p: number) => Math.min(Math.max(p, 1), Math.max(total, 1)),\n    [total]\n  )\n  const [internal, setInternal] = React.useState(() => clamp(defaultPage))\n  const current = clamp(page ?? internal)\n  const goto = (p: number) => {\n    const next = clamp(p)\n    if (next === current) return\n    if (page === undefined) setInternal(next)\n    onPageChange?.(next)\n  }\n  return { current, total: Math.max(total, 1), goto }\n}\n\n/* 1 ... k-1 k k+1 ... N sekilli, ortada current komsulariyla ellipsis'li dizi. */\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/* NumberedPagination: yuvarlak sayi butonlari, aktif primary dolgulu, framer\n   layoutId ile kayan vurgu. */\nexport function NumberedPagination({\n  className,\n  size = \"md\",\n  total = 8,\n  page,\n  defaultPage = 1,\n  onPageChange,\n}: PaginationProps) {\n  const reduce = useReducedMotion()\n  const { current, total: n, goto } = usePagination({ total, page, defaultPage, onPageChange })\n  const items = pageRange(current, n)\n  const highlightId = React.useId()\n  return (\n    <nav\n      data-slot=\"styled-pagination\"\n      aria-label=\"Pagination\"\n      className={cn(\"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], \"rounded-full text-foreground hover:bg-accent\")}\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              \"rounded-full\",\n              it === current ? \"text-primary-foreground\" : \"text-foreground hover:bg-accent\"\n            )}\n          >\n            {it === current && (\n              <motion.span\n                layoutId={reduce ? undefined : `${highlightId}-active`}\n                aria-hidden=\"true\"\n                transition={reduce ? { duration: 0 } : { type: \"spring\", stiffness: 500, damping: 34 }}\n                className=\"absolute inset-0 rounded-full bg-primary\"\n              />\n            )}\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], \"rounded-full text-foreground hover:bg-accent\")}\n      >\n        <ChevronRight />\n      </button>\n    </nav>\n  )\n}\n\n/* ArrowsPagination: kompakt prev / \"Page X of N\" / next. */\nexport function ArrowsPagination({\n  className,\n  size = \"md\",\n  total = 8,\n  page,\n  defaultPage = 1,\n  onPageChange,\n}: PaginationProps) {\n  const { current, total: n, goto } = usePagination({ total, page, defaultPage, onPageChange })\n  return (\n    <nav\n      data-slot=\"styled-pagination\"\n      aria-label=\"Pagination\"\n      className={cn(\"inline-flex items-center\", 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(\n          btnBase,\n          focusRing,\n          iconSize[size],\n          \"rounded-lg border border-border text-foreground hover:bg-accent\"\n        )}\n      >\n        <ChevronLeft />\n      </button>\n      <span className={cn(controlSize[size], \"inline-flex items-center px-2 tabular-nums text-muted-foreground\")}>\n        Page <span className=\"mx-1 font-medium text-foreground\">{current}</span> of{\" \"}\n        <span className=\"ml-1 font-medium text-foreground\">{n}</span>\n      </span>\n      <button\n        type=\"button\"\n        aria-label=\"Go to next page\"\n        disabled={current >= n}\n        onClick={() => goto(current + 1)}\n        className={cn(\n          btnBase,\n          focusRing,\n          iconSize[size],\n          \"rounded-lg border border-border text-foreground hover:bg-accent\"\n        )}\n      >\n        <ChevronRight />\n      </button>\n    </nav>\n  )\n}\n\n/* DotsPagination: dots; the active dot widens and turns primary (suits a carousel). */\nexport function DotsPagination({\n  className,\n  size = \"md\",\n  total = 8,\n  page,\n  defaultPage = 1,\n  onPageChange,\n}: PaginationProps) {\n  const reduce = useReducedMotion()\n  const { current, total: n, goto } = usePagination({ total, page, defaultPage, onPageChange })\n  const dot: Record<StyledSize, string> = {\n    sm: \"h-1.5\",\n    md: \"h-2\",\n    lg: \"h-2.5\",\n    xl: \"h-3\",\n  }\n  return (\n    <nav\n      data-slot=\"styled-pagination\"\n      aria-label=\"Pagination\"\n      className={cn(\"inline-flex items-center gap-2\", className)}\n    >\n      {Array.from({ length: n }, (_, i) => i + 1).map((it) => {\n        const active = it === current\n        return (\n          <button\n            key={it}\n            type=\"button\"\n            aria-label={`Go to page ${it}`}\n            aria-current={active ? \"page\" : undefined}\n            onClick={() => goto(it)}\n            className={cn(\n              \"relative inline-flex h-6 min-w-6 shrink-0 items-center justify-center rounded-full\",\n              focusRing\n            )}\n          >\n            <motion.span\n              aria-hidden=\"true\"\n              layout={!reduce}\n              transition={reduce ? { duration: 0 } : { type: \"spring\", stiffness: 500, damping: 34 }}\n              className={cn(\n                dot[size],\n                \"block rounded-full transition-colors\",\n                active\n                  ? \"w-6 bg-primary\"\n                  : \"w-2 bg-[color-mix(in_oklch,var(--color-foreground)_25%,transparent)] hover:bg-[color-mix(in_oklch,var(--color-foreground)_45%,transparent)]\"\n              )}\n            />\n          </button>\n        )\n      })}\n    </nav>\n  )\n}\n\n/* PillPagination: pill kabinin icinde sayilar; aktif olan ic pill. */\nexport function PillPagination({\n  className,\n  size = \"md\",\n  total = 8,\n  page,\n  defaultPage = 1,\n  onPageChange,\n}: PaginationProps) {\n  const reduce = useReducedMotion()\n  const { current, total: n, goto } = usePagination({ total, page, defaultPage, onPageChange })\n  const items = pageRange(current, n)\n  const groupId = React.useId()\n  const pad: Record<StyledSize, string> = {\n    sm: \"p-1\",\n    md: \"p-1\",\n    lg: \"p-1.5\",\n    xl: \"p-1.5\",\n  }\n  return (\n    <nav\n      data-slot=\"styled-pagination\"\n      aria-label=\"Pagination\"\n      className={cn(\"inline-flex items-center rounded-full border border-border bg-accent\", pad[size], 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], \"rounded-full text-foreground hover:bg-background\")}\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              \"rounded-full\",\n              it === current ? \"text-primary-foreground\" : \"text-foreground hover:bg-background\"\n            )}\n          >\n            {it === current && (\n              <motion.span\n                layoutId={reduce ? undefined : `${groupId}-pill`}\n                aria-hidden=\"true\"\n                transition={reduce ? { duration: 0 } : { type: \"spring\", stiffness: 500, damping: 34 }}\n                className=\"absolute inset-0 rounded-full bg-primary\"\n              />\n            )}\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], \"rounded-full text-foreground hover:bg-background\")}\n      >\n        <ChevronRight />\n      </button>\n    </nav>\n  )\n}\n\n/* MinimalPagination: no frame, only prev/next chevrons + the current number. */\nexport function MinimalPagination({\n  className,\n  size = \"md\",\n  total = 8,\n  page,\n  defaultPage = 1,\n  onPageChange,\n}: PaginationProps) {\n  const { current, total: n, goto } = usePagination({ total, page, defaultPage, onPageChange })\n  return (\n    <nav\n      data-slot=\"styled-pagination\"\n      aria-label=\"Pagination\"\n      className={cn(\"inline-flex items-center gap-3\", 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], \"rounded-md text-muted-foreground hover:text-foreground\")}\n      >\n        <ChevronLeft />\n      </button>\n      <span className={cn(controlSize[size], \"inline-flex items-center tabular-nums text-foreground\")}>\n        <span className=\"font-medium\">{current}</span>\n        <span className=\"mx-1 text-muted-foreground\">/</span>\n        <span className=\"text-muted-foreground\">{n}</span>\n      </span>\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], \"rounded-md text-muted-foreground hover:text-foreground\")}\n      >\n        <ChevronRight />\n      </button>\n    </nav>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/pagination-styled.tsx"
    }
  ],
  "categories": [
    "styled",
    "pagination"
  ],
  "type": "registry:component"
}