{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "breadcrumb-collapse",
  "title": "Collapse breadcrumb",
  "description": "Styled breadcrumb: the Collapse set. 5 decorative breadcrumb variations (EllipsisBreadcrumb, DropdownBreadcrumb, ScrollBreadcrumb, TruncateBreadcrumb, CompactBreadcrumb) on the ai2 token system, driven by CSS token transitions and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "lucide-react@^1.23.0"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/breadcrumb-collapse.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronRight, MoreHorizontal } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Collapse breadcrumb family: 5 strategies for a long trail overflowing. The separator is always a chevron; what changes is how the long trail is shortened (ellipsis, dropdown, horizontal scroll, label truncation, first plus last only). Colour comes ONLY from tokens (alpha via color-mix), the size is a text scale plus gap. The last item is the current page (aria-current), not a link. Deterministic, no motion. */\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\n/* The overflow behavior is only visible on a long trail, which is why this family's\n   default trail is deliberately deep. */\nconst defaultItems: Crumb[] = [\n  { label: \"Home\", href: \"/\" },\n  { label: \"Docs\", href: \"/docs\" },\n  { label: \"Components\", href: \"/components\" },\n  { label: \"Navigation\", href: \"/docs/styled/breadcrumb\" },\n  { label: \"Breadcrumb\", href: \"/docs/components/breadcrumb\" },\n  { label: \"Collapse\" },\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 = \"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\n/* A single trail: the last item (or the item with no href) is the current page. */\nfunction CrumbNode({ item, last }: { item: Crumb; last: boolean }) {\n  if (last || !item.href) {\n    return (\n      <span className={current} aria-current=\"page\">\n        {item.label}\n      </span>\n    )\n  }\n  return (\n    <a href={item.href} className={link}>\n      {item.label}\n    </a>\n  )\n}\n\n/* Ellipsis: ilk oge + statik uc nokta + son iki oge. Gizli olanlar bir metin\n   ozeti olarak ekran okuyuculara verilir. */\nexport function EllipsisBreadcrumb({ className, size = \"md\", items = defaultItems }: Props) {\n  const collapsed = items.length > 4\n  const head = collapsed ? items.slice(0, 1) : items\n  const tail = collapsed ? items.slice(-2) : []\n  const hidden = collapsed ? items.length - 3 : 0\n\n  return (\n    <nav data-slot=\"styled-breadcrumb\" aria-label=\"Breadcrumb\">\n      <ol className={cn(\"flex flex-wrap items-center\", trail[size], className)}>\n        {head.map((item, i) => (\n          <li key={`h-${i}`} className=\"inline-flex items-center gap-1.5\">\n            <CrumbNode item={item} last={!collapsed && i === items.length - 1} />\n            {(collapsed || i < items.length - 1) && <Sep />}\n          </li>\n        ))}\n        {collapsed && (\n          <li className=\"inline-flex items-center gap-1.5\">\n            <span\n              className=\"flex items-center text-muted-foreground [&>svg]:size-4 [&>svg]:shrink-0 [&>i]:text-base [&>i]:leading-none\"\n              role=\"presentation\"\n            >\n              <MoreHorizontal aria-hidden=\"true\" />\n              <span className=\"sr-only\">{hidden} more levels</span>\n            </span>\n            <Sep />\n          </li>\n        )}\n        {tail.map((item, i) => (\n          <li key={`t-${i}`} className=\"inline-flex items-center gap-1.5\">\n            <CrumbNode item={item} last={i === tail.length - 1} />\n            {i < tail.length - 1 && <Sep />}\n          </li>\n        ))}\n      </ol>\n    </nav>\n  )\n}\n\nconst menuBtn =\n  \"inline-flex size-6 items-center justify-center rounded-md text-muted-foreground outline-none transition-colors hover:bg-[color-mix(in_oklab,var(--color-foreground)_8%,transparent)] hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 [&>svg]:size-4 [&>svg]:shrink-0 [&>i]:text-base [&>i]:leading-none\"\n\n/* Dropdown: the hidden crumbs are listed in a working popover. It closes on Escape and on an outside click; focus moves to the first item when it opens. */\nexport function DropdownBreadcrumb({ className, size = \"md\", items = defaultItems }: Props) {\n  const [open, setOpen] = React.useState(false)\n  const wrapRef = React.useRef<HTMLLIElement>(null)\n  const listRef = React.useRef<HTMLDivElement>(null)\n\n  const collapsed = items.length > 4\n  const head = collapsed ? items.slice(0, 1) : items\n  const middle = collapsed ? items.slice(1, -2) : []\n  const tail = collapsed ? items.slice(-2) : []\n\n  React.useEffect(() => {\n    if (!open) return\n    const onKey = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") setOpen(false)\n    }\n    const onDown = (e: MouseEvent) => {\n      if (!wrapRef.current?.contains(e.target as Node)) setOpen(false)\n    }\n    window.addEventListener(\"keydown\", onKey)\n    window.addEventListener(\"pointerdown\", onDown)\n    const id = window.requestAnimationFrame(() =>\n      listRef.current?.querySelector(\"a\")?.focus()\n    )\n    return () => {\n      window.removeEventListener(\"keydown\", onKey)\n      window.removeEventListener(\"pointerdown\", onDown)\n      window.cancelAnimationFrame(id)\n    }\n  }, [open])\n\n  return (\n    <nav data-slot=\"styled-breadcrumb\" aria-label=\"Breadcrumb\">\n      <ol className={cn(\"flex flex-wrap items-center\", trail[size], className)}>\n        {head.map((item, i) => (\n          <li key={`h-${i}`} className=\"inline-flex items-center gap-1.5\">\n            <CrumbNode item={item} last={!collapsed && i === items.length - 1} />\n            {(collapsed || i < items.length - 1) && <Sep />}\n          </li>\n        ))}\n        {collapsed && (\n          <li ref={wrapRef} className=\"relative inline-flex items-center gap-1.5\">\n            <button\n              type=\"button\"\n              className={menuBtn}\n              aria-label=\"Show hidden levels\"\n              aria-expanded={open}\n              aria-haspopup=\"true\"\n              onClick={() => setOpen((o) => !o)}\n            >\n              <MoreHorizontal />\n            </button>\n            {open && (\n              <div\n                ref={listRef}\n                className=\"absolute left-0 top-full z-50 mt-1.5 min-w-40 rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-lg\"\n              >\n                {middle.map((item, i) => (\n                  <a\n                    key={i}\n                    href={item.href ?? \"#\"}\n                    className=\"block rounded-sm px-2 py-1.5 text-sm text-muted-foreground outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n                    onClick={() => setOpen(false)}\n                  >\n                    {item.label}\n                  </a>\n                ))}\n              </div>\n            )}\n            <Sep />\n          </li>\n        )}\n        {tail.map((item, i) => (\n          <li key={`t-${i}`} className=\"inline-flex items-center gap-1.5\">\n            <CrumbNode item={item} last={i === tail.length - 1} />\n            {i < tail.length - 1 && <Sep />}\n          </li>\n        ))}\n      </ol>\n    </nav>\n  )\n}\n\n/* Scroll: the trail never shortens, it scrolls horizontally in a narrow space; the\n   mask at both ends signals that it is scrollable. */\nexport function ScrollBreadcrumb({ className, size = \"md\", items = defaultItems }: Props) {\n  return (\n    <nav data-slot=\"styled-breadcrumb\" aria-label=\"Breadcrumb\" className=\"relative max-w-full\">\n      <div className=\"overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden [mask-image:linear-gradient(to_right,transparent,black_1.5rem,black_calc(100%-1.5rem),transparent)]\">\n        <ol className={cn(\"flex w-max flex-nowrap items-center px-6\", trail[size], className)}>\n          {items.map((item, i) => {\n            const last = i === items.length - 1\n            return (\n              <li key={i} className=\"inline-flex shrink-0 items-center gap-1.5 whitespace-nowrap\">\n                <CrumbNode item={item} last={last} />\n                {!last && <Sep />}\n              </li>\n            )\n          })}\n        </ol>\n      </div>\n    </nav>\n  )\n}\n\nconst clampWidth: Record<StyledSize, string> = {\n  sm: \"max-w-14\",\n  md: \"max-w-16\",\n  lg: \"max-w-20\",\n  xl: \"max-w-24\",\n}\n\n/* Truncate: every item stays in place and long labels are clipped to one line; the last item keeps its full width. The full text stays reachable through title. */\nexport function TruncateBreadcrumb({ className, size = \"md\", items = defaultItems }: Props) {\n  return (\n    <nav data-slot=\"styled-breadcrumb\" aria-label=\"Breadcrumb\" className=\"max-w-full\">\n      <ol className={cn(\"flex flex-nowrap items-center overflow-hidden\", trail[size], className)}>\n        {items.map((item, i) => {\n          const last = i === items.length - 1\n          const label =\n            typeof item.label === \"string\" ? item.label : undefined\n          return (\n            <li key={i} className=\"inline-flex min-w-0 items-center gap-1.5\">\n              {last || !item.href ? (\n                <span className={cn(current, \"block truncate\")} aria-current=\"page\" title={label}>\n                  {item.label}\n                </span>\n              ) : (\n                <a\n                  href={item.href}\n                  className={cn(link, \"block truncate\", clampWidth[size])}\n                  title={label}\n                >\n                  {item.label}\n                </a>\n              )}\n              {!last && <Sep />}\n            </li>\n          )\n        })}\n      </ol>\n    </nav>\n  )\n}\n\n/* Compact: the harshest truncation. Only the first and last item remain, the ones between are counted and the count is shown as a badge. */\nexport function CompactBreadcrumb({ className, size = \"md\", items = defaultItems }: Props) {\n  const collapsed = items.length > 2\n  const hidden = collapsed ? items.length - 2 : 0\n  const first = items[0]\n  const last = items[items.length - 1]\n\n  return (\n    <nav data-slot=\"styled-breadcrumb\" aria-label=\"Breadcrumb\">\n      <ol className={cn(\"flex flex-wrap items-center\", trail[size], className)}>\n        <li className=\"inline-flex items-center gap-1.5\">\n          <CrumbNode item={first} last={items.length === 1} />\n          {items.length > 1 && <Sep />}\n        </li>\n        {collapsed && (\n          <li className=\"inline-flex items-center gap-1.5\">\n            <span className=\"inline-flex h-5 items-center rounded-full bg-[color-mix(in_oklab,var(--color-primary)_14%,transparent)] px-2 text-xs font-medium text-primary\">\n              +{hidden}\n              <span className=\"sr-only\"> hidden levels</span>\n            </span>\n            <Sep />\n          </li>\n        )}\n        {items.length > 1 && (\n          <li className=\"inline-flex items-center gap-1.5\">\n            <CrumbNode item={last} last />\n          </li>\n        )}\n      </ol>\n    </nav>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/breadcrumb-collapse.tsx"
    }
  ],
  "categories": [
    "styled",
    "breadcrumb"
  ],
  "type": "registry:component"
}