{ "$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([]);\n const staggerTimers = React.useRef[]>([]);\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 \n );\n}\n\nfunction Sep() {\n return (\n \n /\n \n );\n}\n\nexport { type BreadcrumbDropdownProps, type BreadcrumbDropdownItem };\n", "type": "registry:ui", "target": "components/ruixen/breadcrumb-dropdown.tsx" } ] }