{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "breadcrumb-motion",
  "title": "Motion breadcrumb",
  "description": "Styled breadcrumb: the Motion set. 5 decorative breadcrumb variations (HoverBreadcrumb, SlideBreadcrumb, FadeBreadcrumb, UnderlineBreadcrumb, MagneticBreadcrumb) 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/breadcrumb-motion.tsx",
      "content": "\"use client\"\n\nimport type * as React from \"react\"\nimport { ChevronRight } from \"lucide-react\"\nimport { motion, useMotionValue, useReducedMotion, useSpring } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Motion breadcrumb family: 5 motion techniques. The separator is always a\n   chevron; what changes is how the trail behaves (lifting on hover, sliding in\n   from the left in sequence, fading in in sequence, an underline that grows on\n   hover, a magnetic trail that leans toward the cursor). All of them are disabled\n   through useReducedMotion: in reduced mode there is no transform, only an\n   instant appearance or a fade. Color comes ONLY from tokens (via alpha\n   color-mix), size covers the text scale + gap. The last item is the current page\n   (aria-current), not a link. Deterministic: the delays derive from the index,\n   with no randomness. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\ntype Crumb = { label: React.ReactNode; href?: string }\n\ntype Props = {\n  className?: string\n  size?: StyledSize\n  items?: Crumb[]\n}\n\nconst trail: Record<StyledSize, string> = {\n  sm: \"gap-1.5 text-xs\",\n  md: \"gap-2 text-sm\",\n  lg: \"gap-2.5 text-sm\",\n  xl: \"gap-3 text-base\",\n}\n\nconst defaultItems: Crumb[] = [\n  { label: \"Home\", href: \"#\" },\n  { label: \"Components\", href: \"#\" },\n  { label: \"Button\" },\n]\n\nconst link =\n  \"inline-flex items-center gap-1.5 rounded-sm text-muted-foreground transition-colors hover:text-primary focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-3.5 [&_svg]:shrink-0 [&_i]:text-sm [&_i]:leading-none\"\n\nconst current =\n  \"inline-flex items-center gap-1.5 font-medium text-foreground [&_svg]:size-3.5 [&_svg]:shrink-0 [&_i]:text-sm [&_i]:leading-none\"\n\nconst sep =\n  \"flex items-center text-muted-foreground/60 [&>svg]:size-3.5 [&>svg]:shrink-0 [&>i]:text-sm [&>i]:leading-none\"\n\nfunction Sep() {\n  return (\n    <span className={sep} aria-hidden=\"true\">\n      <ChevronRight />\n    </span>\n  )\n}\n\nconst softSpring = { type: \"spring\" as const, stiffness: 380, damping: 30, mass: 0.6 }\n\n/* Hover: every link rises and grows a notch on hover and focus. Only the colour changes under reduced. */\nexport function HoverBreadcrumb({ className, size = \"md\", items = defaultItems }: Props) {\n  const reduce = useReducedMotion()\n  return (\n    <nav data-slot=\"styled-breadcrumb\" aria-label=\"Breadcrumb\">\n      <ol className={cn(\"flex flex-wrap items-center\", trail[size], className)}>\n        {items.map((item, i) => {\n          const last = i === items.length - 1\n          return (\n            <li key={i} className=\"inline-flex items-center gap-1.5\">\n              {last || !item.href ? (\n                <span className={current} aria-current=\"page\">\n                  {item.label}\n                </span>\n              ) : (\n                <motion.a\n                  href={item.href}\n                  className={link}\n                  whileHover={reduce ? undefined : { y: -2, scale: 1.04 }}\n                  whileFocus={reduce ? undefined : { y: -2, scale: 1.04 }}\n                  whileTap={reduce ? undefined : { scale: 0.98 }}\n                  transition={softSpring}\n                >\n                  {item.label}\n                </motion.a>\n              )}\n              {!last && <Sep />}\n            </li>\n          )\n        })}\n      </ol>\n    </nav>\n  )\n}\n\n/* Slide: the crumbs slide in from the left in sequence; the delay derives from the\n   index. In reduced mode there is no slide, only a fade. */\nexport function SlideBreadcrumb({ className, size = \"md\", items = defaultItems }: Props) {\n  const reduce = useReducedMotion()\n  return (\n    <nav data-slot=\"styled-breadcrumb\" aria-label=\"Breadcrumb\">\n      <ol className={cn(\"flex flex-wrap items-center\", trail[size], className)}>\n        {items.map((item, i) => {\n          const last = i === items.length - 1\n          return (\n            <motion.li\n              key={i}\n              className=\"inline-flex items-center gap-1.5\"\n              initial={reduce ? { opacity: 0 } : { opacity: 0, x: -12 }}\n              animate={reduce ? { opacity: 1 } : { opacity: 1, x: 0 }}\n              transition={{ duration: 0.28, delay: i * 0.06, ease: \"easeOut\" }}\n            >\n              {last || !item.href ? (\n                <span className={current} aria-current=\"page\">\n                  {item.label}\n                </span>\n              ) : (\n                <a href={item.href} className={link}>\n                  {item.label}\n                </a>\n              )}\n              {!last && <Sep />}\n            </motion.li>\n          )\n        })}\n      </ol>\n    </nav>\n  )\n}\n\n/* Fade: the crumbs stay in place and only the opacity opens in sequence. Safe in\n   reduced mode too: a fade contains no transform anyway, it just speeds up. */\nexport function FadeBreadcrumb({ className, size = \"md\", items = defaultItems }: Props) {\n  const reduce = useReducedMotion()\n  return (\n    <nav data-slot=\"styled-breadcrumb\" aria-label=\"Breadcrumb\">\n      <ol className={cn(\"flex flex-wrap items-center\", trail[size], className)}>\n        {items.map((item, i) => {\n          const last = i === items.length - 1\n          return (\n            <motion.li\n              key={i}\n              className=\"inline-flex items-center gap-1.5\"\n              initial={{ opacity: 0 }}\n              animate={{ opacity: 1 }}\n              transition={{ duration: reduce ? 0.15 : 0.35, delay: reduce ? 0 : i * 0.08 }}\n            >\n              {last || !item.href ? (\n                <span className={current} aria-current=\"page\">\n                  {item.label}\n                </span>\n              ) : (\n                <a href={item.href} className={link}>\n                  {item.label}\n                </a>\n              )}\n              {!last && <Sep />}\n            </motion.li>\n          )\n        })}\n      </ol>\n    </nav>\n  )\n}\n\n/* Underline: an underline growing from left to right on hover and focus. Under reduced it does not grow, it appears instantly. */\nexport function UnderlineBreadcrumb({ className, size = \"md\", items = defaultItems }: Props) {\n  const reduce = useReducedMotion()\n  return (\n    <nav data-slot=\"styled-breadcrumb\" aria-label=\"Breadcrumb\">\n      <ol className={cn(\"flex flex-wrap items-center\", trail[size], className)}>\n        {items.map((item, i) => {\n          const last = i === items.length - 1\n          return (\n            <li key={i} className=\"inline-flex items-center gap-1.5\">\n              {last || !item.href ? (\n                <span className={current} aria-current=\"page\">\n                  {item.label}\n                </span>\n              ) : (\n                <motion.a\n                  href={item.href}\n                  className={cn(link, \"relative\")}\n                  initial=\"rest\"\n                  whileHover=\"active\"\n                  whileFocus=\"active\"\n                  animate=\"rest\"\n                >\n                  {item.label}\n                  <motion.span\n                    aria-hidden=\"true\"\n                    className=\"absolute -bottom-0.5 left-0 h-px w-full origin-left bg-primary\"\n                    variants={\n                      reduce\n                        ? { rest: { opacity: 0 }, active: { opacity: 1 } }\n                        : { rest: { scaleX: 0 }, active: { scaleX: 1 } }\n                    }\n                    transition={{ duration: 0.22, ease: \"easeOut\" }}\n                  />\n                </motion.a>\n              )}\n              {!last && <Sep />}\n            </li>\n          )\n        })}\n      </ol>\n    </nav>\n  )\n}\n\n/* Tek magnetik link: imlecin kutu merkezinden sapmasini sinirli olcude izler.\n   Pointer ayrilinca yaya ile merkeze doner. */\nfunction MagneticLink({ item }: { item: Crumb }) {\n  const reduce = useReducedMotion()\n  const x = useMotionValue(0)\n  const y = useMotionValue(0)\n  const sx = useSpring(x, softSpring)\n  const sy = useSpring(y, softSpring)\n\n  const onMove = (e: React.PointerEvent<HTMLAnchorElement>) => {\n    if (reduce) return\n    const r = e.currentTarget.getBoundingClientRect()\n    const dx = e.clientX - (r.left + r.width / 2)\n    const dy = e.clientY - (r.top + r.height / 2)\n    x.set(Math.max(-6, Math.min(6, dx * 0.3)))\n    y.set(Math.max(-4, Math.min(4, dy * 0.3)))\n  }\n\n  const reset = () => {\n    x.set(0)\n    y.set(0)\n  }\n\n  return (\n    <motion.a\n      href={item.href}\n      className={link}\n      style={reduce ? undefined : { x: sx, y: sy }}\n      onPointerMove={onMove}\n      onPointerLeave={reset}\n      onBlur={reset}\n    >\n      {item.label}\n    </motion.a>\n  )\n}\n\n/* Magnetic: the links lean slightly towards the cursor. Under reduced no transform is applied at all and the link stays completely still. */\nexport function MagneticBreadcrumb({ className, size = \"md\", items = defaultItems }: Props) {\n  return (\n    <nav data-slot=\"styled-breadcrumb\" aria-label=\"Breadcrumb\">\n      <ol className={cn(\"flex flex-wrap items-center\", trail[size], className)}>\n        {items.map((item, i) => {\n          const last = i === items.length - 1\n          return (\n            <li key={i} className=\"inline-flex items-center gap-1.5\">\n              {last || !item.href ? (\n                <span className={current} aria-current=\"page\">\n                  {item.label}\n                </span>\n              ) : (\n                <MagneticLink item={item} />\n              )}\n              {!last && <Sep />}\n            </li>\n          )\n        })}\n      </ol>\n    </nav>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/breadcrumb-motion.tsx"
    }
  ],
  "categories": [
    "styled",
    "breadcrumb"
  ],
  "type": "registry:component"
}