{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pagination-glass",
  "title": "Glass pagination",
  "description": "Styled pagination: the Glass set. 5 decorative pagination variations (FrostPagination, TintPagination, SmokePagination, CrystalPagination, DepthPagination) 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",
    "@ai2/glass"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/pagination-glass.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\"\nimport { glassDepth } from \"@/registry/ai2/ui/glass\"\n\n/* Glass pagination family: 5 frosted paginators. They all share the same skeleton;\n   the difference is ONLY on the surface of the container (tone, edge, depth). The\n   glass is NO LONGER hand-written: every variant derives from the glassDepth scale\n   inside @ai2/glass, so the ai2 signature (top inset highlight + ambient shadow) is\n   the same language across all variants.\n\n   Choosing the depth (a bar-shaped control): a paginator is wide but SHORT and sits\n   in the page flow; behind a short bar a heavy blur brings no gain, so the scale\n   starts low and only the genuinely floating variant reaches the top.\n\n   NO GLASS ON GLASS: the glass surface is the BAR itself; the active page is a\n   separate highlight on top of it (bg-primary, opaque) - a second glass layer would\n   have turned to mud. primary here is not a decorative tint but the semantic role\n   of the active/selected state.\n\n   Color comes only from semantic tokens; alpha via color-mix. The active page\n   slides with a framer layoutId, and the layoutId derives from React.useId() (there\n   are many instances on the same page). */\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 padSize: Record<StyledSize, string> = {\n  sm: \"p-1\",\n  md: \"p-1\",\n  lg: \"p-1.5\",\n  xl: \"p-1.5\",\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  defaultPage?: number\n}\n\n/* Dahili sayfa durumu: tiklama gercekten sayfayi degistirir. */\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\n/* 1 ... k-1 k k+1 ... N sekilli 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/* Shared frosted shell: shellClassName carries each variant's glass technique, activeClassName the sliding highlight of the active page. */\nfunction GlassShell({\n  props,\n  shellClassName,\n  activeClassName,\n  idleClassName,\n}: {\n  props: PaginationProps\n  shellClassName: string\n  activeClassName: string\n  idleClassName: string\n}) {\n  const { className, size = \"md\", total = 8, defaultPage = 1 } = props\n  const reduce = useReducedMotion()\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 rounded-full\", padSize[size], shellClassName, 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\", idleClassName)}\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\" : cn(\"text-foreground\", idleClassName)\n            )}\n          >\n            {it === current && (\n              <motion.span\n                layoutId={reduce ? undefined : `${uid}-glass-active`}\n                aria-hidden=\"true\"\n                transition={reduce ? { duration: 0 } : { type: \"spring\" as const, stiffness: 500, damping: 34 }}\n                className={cn(\"absolute inset-0 rounded-full\", activeClassName)}\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\", idleClassName)}\n      >\n        <ChevronRight />\n      </button>\n    </nav>\n  )\n}\n\n/* Frost: a light blur + a thin edge. DEPTH sm (4px): the plainest container; behind\n   a short paginator bar anything more is an invisible cost. */\nexport function FrostPagination(props: PaginationProps) {\n  return (\n    <GlassShell\n      props={props}\n      shellClassName={cn(\n        glassDepth.sm,\n        \"border border-[color-mix(in_oklab,var(--color-foreground)_10%,transparent)]\"\n      )}\n      activeClassName=\"bg-primary\"\n      idleClassName=\"text-foreground hover:bg-[color-mix(in_oklab,var(--color-foreground)_8%,transparent)]\"\n    />\n  )\n}\n\n/* Tint: an info-toned glass container. DEPTH md (8px, the canonical default): the character comes from the TONE, not the blur. Tint overrides both the bg-* and the supports-* step. */\nexport function TintPagination(props: PaginationProps) {\n  return (\n    <GlassShell\n      props={props}\n      shellClassName={cn(\n        glassDepth.md,\n        \"border border-[color-mix(in_oklab,var(--color-info)_22%,transparent)]\",\n        \"bg-[color-mix(in_oklab,var(--color-info)_10%,transparent)]\",\n        \"supports-[backdrop-filter]:bg-[color-mix(in_oklab,var(--color-info)_10%,transparent)]\"\n      )}\n      activeClassName=\"bg-primary\"\n      idleClassName=\"text-foreground hover:bg-[color-mix(in_oklab,var(--color-info)_16%,transparent)]\"\n    />\n  )\n}\n\n/* Smoke: a smoky scrim. DEPTH md (8px): DELIBERATELY the same step as Tint - glass at the same distance, the difference being tone only (foreground smoke versus info). */\nexport function SmokePagination(props: PaginationProps) {\n  return (\n    <GlassShell\n      props={props}\n      shellClassName={cn(\n        glassDepth.md,\n        \"border border-[color-mix(in_oklab,var(--color-foreground)_14%,transparent)]\",\n        \"bg-[color-mix(in_oklab,var(--color-foreground)_12%,transparent)]\",\n        \"supports-[backdrop-filter]:bg-[color-mix(in_oklab,var(--color-foreground)_12%,transparent)]\"\n      )}\n      activeClassName=\"bg-primary\"\n      idleClassName=\"text-foreground hover:bg-[color-mix(in_oklab,var(--color-foreground)_18%,transparent)]\"\n    />\n  )\n}\n\n/* Crystal: a clear pane. DEPTH lg (16px): diffusion is the SUBJECT here, not decoration - what is behind becomes texture, not text. The active page is now a solid bg-primary: the old color-mix(primary 88%) was semi-transparent and weakened contrast over the glass; the active item must be an OPAQUE emphasis above the bar. */\nexport function CrystalPagination(props: PaginationProps) {\n  return (\n    <GlassShell\n      props={props}\n      shellClassName={cn(\n        glassDepth.lg,\n        \"border border-[color-mix(in_oklab,var(--color-foreground)_8%,transparent)]\",\n        \"border-t-[color-mix(in_oklab,var(--color-background)_70%,transparent)]\",\n        \"supports-[backdrop-filter]:backdrop-saturate-150\"\n      )}\n      activeClassName=\"bg-primary\"\n      idleClassName=\"text-foreground hover:bg-[color-mix(in_oklab,var(--color-background)_60%,transparent)]\"\n    />\n  )\n}\n\n/* Depth: layered depth. DEPTH xl (24px): the ONLY variant that earns the top of the scale - it reads like a lifted overlay bar, and the further from the surface the more what is behind scatters. Note: the `shadow-lg` on the shell and the `shadow-md` on the active item were removed - the first would override the same CSS property as the glassDepth signature, the second was already creating a second depth layer above the bar. The body separation comes from a ring. */\nexport function DepthPagination(props: PaginationProps) {\n  return (\n    <GlassShell\n      props={props}\n      shellClassName={cn(\n        glassDepth.xl,\n        \"border border-[color-mix(in_oklab,var(--color-foreground)_12%,transparent)]\",\n        \"bg-gradient-to-b supports-[backdrop-filter]:bg-transparent\",\n        \"from-[color-mix(in_oklab,var(--color-background)_70%,transparent)]\",\n        \"to-[color-mix(in_oklab,var(--color-foreground)_10%,transparent)]\",\n        \"ring-1 ring-[color-mix(in_oklab,var(--color-foreground)_8%,transparent)]\"\n      )}\n      activeClassName=\"bg-primary\"\n      idleClassName=\"text-foreground hover:bg-[color-mix(in_oklab,var(--color-background)_70%,transparent)]\"\n    />\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/pagination-glass.tsx"
    }
  ],
  "categories": [
    "styled",
    "pagination"
  ],
  "type": "registry:component"
}