{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "breadcrumb-dropdown",
  "type": "registry:ui",
  "title": "Breadcrumb Dropdown",
  "description": "Inline-expanding collapsible breadcrumb with staggered path reveal.",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/ruixenui/breadcrumb-dropdown.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\n/* ═══════════════════════════════════════════════════════════\n   Breadcrumb Dropdown — Inline-expanding collapsible path.\n\n   Kills the floating dropdown entirely. Instead of hovering\n   a trigger to summon a disembodied menu, click the ··· and\n   the hidden path segments unfold INLINE — items stagger in\n   from the left, each 50ms apart, expanding the breadcrumb\n   to reveal the full path. Click again to snap it closed.\n\n   Three distinct paradigms across the breadcrumb family:\n     • separator: hover affects OTHER items      (cascade)\n     • icon:      a HIGHLIGHT slides to target   (pill)\n     • dropdown:  HIDDEN CONTENT unfolds in place (expand)\n\n   The trigger icon morphs between three dots (collapsed)\n   and a dash (expanded) with a scale + rotate crossfade.\n\n   Curve: cubic-bezier(0.0, 0.0, 0.2, 1) — Material\n   standard deceleration. No spring overshoot — the stagger\n   choreography is the motion interest, not the curve.\n   ═══════════════════════════════════════════════════════════ */\n\ninterface BreadcrumbDropdownItem {\n  label: string;\n  href?: string;\n}\n\ninterface BreadcrumbDropdownProps {\n  items: BreadcrumbDropdownItem[];\n  maxVisible?: number;\n  className?: string;\n}\n\nexport function BreadcrumbDropdown({\n  items,\n  maxVisible = 3,\n  className,\n}: BreadcrumbDropdownProps) {\n  const [expanded, setExpanded] = React.useState(false);\n  const [itemVisible, setItemVisible] = React.useState<boolean[]>([]);\n  const staggerTimers = React.useRef<ReturnType<typeof setTimeout>[]>([]);\n\n  const shouldCollapse = items.length > maxVisible;\n  const hiddenItems = shouldCollapse\n    ? items.slice(1, items.length - (maxVisible - 2))\n    : [];\n  const visibleEnd = shouldCollapse\n    ? items.slice(items.length - (maxVisible - 2))\n    : items.slice(1);\n  const hiddenCount = hiddenItems.length;\n\n  const toggle = React.useCallback(() => {\n    staggerTimers.current.forEach(clearTimeout);\n    staggerTimers.current = [];\n\n    if (!expanded) {\n      setExpanded(true);\n      setItemVisible(new Array(hiddenCount).fill(false));\n      for (let i = 0; i < hiddenCount; i++) {\n        const timer = setTimeout(() => {\n          setItemVisible((prev) => {\n            const next = [...prev];\n            next[i] = true;\n            return next;\n          });\n        }, i * 50);\n        staggerTimers.current.push(timer);\n      }\n    } else {\n      setItemVisible([]);\n      setExpanded(false);\n    }\n  }, [expanded, hiddenCount]);\n\n  React.useEffect(() => {\n    return () => {\n      staggerTimers.current.forEach(clearTimeout);\n    };\n  }, []);\n\n  if (items.length === 0) return null;\n\n  return (\n    <nav aria-label=\"Breadcrumb\" className={cn(\"\", className)}>\n      <ol className=\"flex flex-wrap items-center gap-0.5 text-sm\">\n        {/* First item — always visible */}\n        <li className=\"inline-flex items-center\">\n          {items[0].href ? (\n            <a\n              href={items[0].href}\n              className=\"rounded-md px-1.5 py-0.5 text-muted-foreground transition-colors duration-200 hover:text-foreground\"\n            >\n              {items[0].label}\n            </a>\n          ) : (\n            <span className=\"px-1.5 py-0.5 text-muted-foreground\">\n              {items[0].label}\n            </span>\n          )}\n          {items.length > 1 && <Sep />}\n        </li>\n\n        {/* Expand / collapse trigger */}\n        {shouldCollapse && (\n          <li className=\"inline-flex items-center\">\n            <button\n              onClick={toggle}\n              className=\"inline-flex size-6 items-center justify-center rounded-md text-muted-foreground transition-colors duration-200 hover:bg-foreground/[0.06] hover:text-foreground\"\n              aria-expanded={expanded}\n              aria-label={expanded ? \"Collapse path\" : \"Expand path\"}\n            >\n              <span className=\"relative flex size-4 items-center justify-center\">\n                {/* Three dots — visible when collapsed */}\n                <svg\n                  width=\"16\"\n                  height=\"16\"\n                  viewBox=\"0 0 16 16\"\n                  fill=\"currentColor\"\n                  className=\"absolute inset-0\"\n                  style={{\n                    opacity: expanded ? 0 : 1,\n                    transform: expanded\n                      ? \"scale(0.8) rotate(-90deg)\"\n                      : \"scale(1) rotate(0deg)\",\n                    transition: \"all 0.2s cubic-bezier(0.0, 0.0, 0.2, 1)\",\n                  }}\n                >\n                  <circle cx=\"3\" cy=\"8\" r=\"1.2\" />\n                  <circle cx=\"8\" cy=\"8\" r=\"1.2\" />\n                  <circle cx=\"13\" cy=\"8\" r=\"1.2\" />\n                </svg>\n                {/* Dash — visible when expanded */}\n                <svg\n                  width=\"16\"\n                  height=\"16\"\n                  viewBox=\"0 0 16 16\"\n                  fill=\"none\"\n                  className=\"absolute inset-0\"\n                  style={{\n                    opacity: expanded ? 1 : 0,\n                    transform: expanded\n                      ? \"scale(1) rotate(0deg)\"\n                      : \"scale(0.8) rotate(90deg)\",\n                    transition: \"all 0.2s cubic-bezier(0.0, 0.0, 0.2, 1)\",\n                  }}\n                >\n                  <path\n                    d=\"M4 8H12\"\n                    stroke=\"currentColor\"\n                    strokeWidth=\"1.5\"\n                    strokeLinecap=\"round\"\n                  />\n                </svg>\n              </span>\n            </button>\n            <Sep />\n          </li>\n        )}\n\n        {/* Hidden items — unfold inline with stagger */}\n        {expanded &&\n          hiddenItems.map((item, i) => {\n            const visible = itemVisible[i] ?? false;\n            return (\n              <li\n                key={`hidden-${i}`}\n                className=\"inline-flex items-center\"\n                style={{\n                  opacity: visible ? 1 : 0,\n                  transform: visible ? \"translateX(0)\" : \"translateX(-8px)\",\n                  transition:\n                    \"opacity 0.25s cubic-bezier(0.0, 0.0, 0.2, 1), transform 0.25s cubic-bezier(0.0, 0.0, 0.2, 1)\",\n                }}\n              >\n                {item.href ? (\n                  <a\n                    href={item.href}\n                    className=\"rounded-md px-1.5 py-0.5 text-muted-foreground transition-colors duration-200 hover:text-foreground\"\n                  >\n                    {item.label}\n                  </a>\n                ) : (\n                  <span className=\"px-1.5 py-0.5 text-muted-foreground\">\n                    {item.label}\n                  </span>\n                )}\n                <Sep />\n              </li>\n            );\n          })}\n\n        {/* Visible end items */}\n        {visibleEnd.map((item, index) => {\n          const isLast = index === visibleEnd.length - 1;\n          return (\n            <li key={`end-${index}`} className=\"inline-flex items-center\">\n              {item.href && !isLast ? (\n                <a\n                  href={item.href}\n                  className=\"rounded-md px-1.5 py-0.5 text-muted-foreground transition-colors duration-200 hover:text-foreground\"\n                >\n                  {item.label}\n                </a>\n              ) : (\n                <span\n                  className={cn(\n                    \"px-1.5 py-0.5\",\n                    isLast\n                      ? \"font-medium text-foreground\"\n                      : \"text-muted-foreground\",\n                  )}\n                  {...(isLast ? { \"aria-current\": \"page\" as const } : {})}\n                >\n                  {item.label}\n                </span>\n              )}\n              {!isLast && <Sep />}\n            </li>\n          );\n        })}\n      </ol>\n    </nav>\n  );\n}\n\nfunction Sep() {\n  return (\n    <span\n      className=\"mx-1 select-none text-xs text-muted-foreground/30\"\n      aria-hidden=\"true\"\n    >\n      /\n    </span>\n  );\n}\n\nexport { type BreadcrumbDropdownProps, type BreadcrumbDropdownItem };\n",
      "type": "registry:ui",
      "target": "components/ruixen/breadcrumb-dropdown.tsx"
    }
  ]
}