{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "breadcrumbs",
  "title": "Breadcrumbs",
  "description": "A breadcrumb trail from a list of entries, collapsing the middle when it overflows.",
  "registryDependencies": [
    "https://guillermo-rebolledo.github.io/materialcn/r/breadcrumb.json",
    "https://guillermo-rebolledo.github.io/materialcn/r/dropdown-menu.json",
    "https://guillermo-rebolledo.github.io/materialcn/r/materialcn-theme.json",
    "https://guillermo-rebolledo.github.io/materialcn/r/utils.json"
  ],
  "files": [
    {
      "path": "src/components/ui/breadcrumbs.tsx",
      "content": "import { Fragment, useCallback, useLayoutEffect, useRef, useState } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  Breadcrumb,\n  BreadcrumbEllipsis,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbList,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n} from \"./breadcrumb\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"./dropdown-menu\"\nimport type { BreadcrumbEntry, BreadcrumbsProps } from \"./breadcrumbs.types\"\n\n/**\n * The lower bound on how many steps are shown: the root and the current page.\n *\n * Collapsing past this would fold the current page into the menu — leaving the\n * user with a trail whose only visible entries are the root and an ellipsis,\n * which says where they came from and not where they are.\n */\nconst MINIMUM_VISIBLE = 2\n\n/**\n * How many steps fit before the trail overflows, never fewer than the two ends.\n *\n * There is no CSS for \"collapse the middle when it does not fit\", so this\n * measures. It shrinks one step at a time and re-measures, which converges in a\n * few layout passes and — because it runs in a layout effect — before paint, so\n * the overflowing state is never shown.\n *\n * Widths are never cached. A cache would be faster and would go stale on a font\n * swap, a language change, or a type-scale step at a breakpoint, each of which\n * is exactly when the trail needs re-measuring.\n */\nfunction useFittedCount(total: number) {\n  const listRef = useRef<HTMLOListElement>(null)\n  const [count, setCount] = useState(total)\n\n  // A width change means everything might fit again, so measurement restarts\n  // from the top rather than only ever collapsing further.\n  const reset = useCallback(() => setCount(total), [total])\n\n  // A changed trail restarts measurement during render rather than in an\n  // effect: an effect would paint one frame of the old count against the new\n  // items, which is a visible flicker precisely when the trail changes.\n  const [measuredFor, setMeasuredFor] = useState(total)\n  if (measuredFor !== total) {\n    setMeasuredFor(total)\n    setCount(total)\n  }\n\n  useLayoutEffect(() => {\n    const list = listRef.current\n    if (!list) return\n    const observer = new ResizeObserver(reset)\n    observer.observe(list)\n    return () => observer.disconnect()\n  }, [reset])\n\n  useLayoutEffect(() => {\n    const list = listRef.current\n    if (!list) return\n    // The +1 absorbs sub-pixel rounding, which otherwise collapses a trail\n    // that fits by a fraction of a pixel.\n    if (list.scrollWidth > list.clientWidth + 1 && count > MINIMUM_VISIBLE) {\n      setCount((current) => current - 1)\n    }\n  }, [count])\n\n  return { count, listRef }\n}\n\n/**\n * Where the user is, and a way back to any ancestor.\n *\n * Driven by an array rather than composed from the parts, because the\n * collapsing has to know the whole trail: it decides which steps to hide, and\n * a component assembled child by child cannot be asked that. The parts remain\n * exported for a trail that needs something this shape cannot express.\n */\nfunction Breadcrumbs({\n  className,\n  icon,\n  items,\n  separator,\n  ...props\n}: BreadcrumbsProps) {\n  const { count, listRef } = useFittedCount(items.length)\n\n  const collapsed = count < items.length && items.length > MINIMUM_VISIBLE\n  // `count` counts the root, so the tail is one shorter. Floored at\n  // MINIMUM_VISIBLE above, the tail is never empty — which is what keeps the\n  // current page on screen no matter how narrow the container gets.\n  const hidden = collapsed ? items.slice(1, items.length - (count - 1)) : []\n  const tail = collapsed\n    ? items.slice(items.length - (count - 1))\n    : items.slice(1)\n\n  const step = (entry: BreadcrumbEntry, isCurrent: boolean) => (\n    <BreadcrumbItem key={`${entry.label}-${entry.href ?? \"current\"}`}>\n      {isCurrent ? (\n        <BreadcrumbPage>\n          {icon}\n          {entry.label}\n        </BreadcrumbPage>\n      ) : (\n        <BreadcrumbLink\n          href={entry.disabled ? undefined : entry.href}\n          aria-disabled={entry.disabled || undefined}\n        >\n          {icon}\n          {entry.label}\n        </BreadcrumbLink>\n      )}\n    </BreadcrumbItem>\n  )\n\n  const divider = (key: string) => (\n    <BreadcrumbSeparator key={key}>{separator}</BreadcrumbSeparator>\n  )\n\n  return (\n    <Breadcrumb className={cn(\"min-w-0\", className)} {...props}>\n      <BreadcrumbList ref={listRef} className=\"overflow-hidden\">\n        {items.length > 0 && step(items[0], items.length === 1)}\n\n        {collapsed && hidden.length > 0 && (\n          <>\n            {divider(\"sep-collapsed\")}\n            <BreadcrumbItem>\n              <DropdownMenu>\n                {/*\n                  The hidden steps stay reachable — a collapse that dropped them\n                  would remove navigation rather than fold it. The trigger is a\n                  real button so it is tabbable and named.\n                */}\n                <DropdownMenuTrigger\n                  data-slot=\"breadcrumb-ellipsis-trigger\"\n                  className=\"rounded-m3-xs hover:text-m3-on-surface focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-m3-secondary\"\n                  aria-label={`Show ${hidden.length} hidden steps`}\n                >\n                  <BreadcrumbEllipsis />\n                </DropdownMenuTrigger>\n                <DropdownMenuContent align=\"start\">\n                  {hidden.map((entry) => (\n                    <DropdownMenuItem\n                      key={entry.label}\n                      disabled={entry.disabled}\n                      render={<a href={entry.href} />}\n                    >\n                      {entry.label}\n                    </DropdownMenuItem>\n                  ))}\n                </DropdownMenuContent>\n              </DropdownMenu>\n            </BreadcrumbItem>\n          </>\n        )}\n\n        {/*\n          A Fragment, not a wrapper element: an `ol` may only contain `li`, and\n          a `div` here would take the list apart for a screen reader — which is\n          the structure the landmark exists to convey.\n        */}\n        {tail.map((entry, index) => (\n          <Fragment key={`${entry.label}-${index}`}>\n            {divider(`sep-${entry.label}-${index}`)}\n            {step(entry, index === tail.length - 1)}\n          </Fragment>\n        ))}\n      </BreadcrumbList>\n    </Breadcrumb>\n  )\n}\n\nexport { Breadcrumbs }\nexport type { BreadcrumbEntry, BreadcrumbsProps }\n",
      "type": "registry:ui"
    },
    {
      "path": "src/components/ui/breadcrumbs.types.ts",
      "content": "import type * as React from \"react\"\n\n/** One step in the trail. The last entry is the current page. */\nexport type BreadcrumbEntry = {\n  /** What the step is called. */\n  label: string\n  /** Where it goes. Absent on the current page, which is not a destination. */\n  href?: string\n  /** Prevents navigation while keeping the step in the trail. */\n  disabled?: boolean\n}\n\nexport type BreadcrumbsProps = Omit<React.ComponentProps<\"nav\">, \"children\"> & {\n  items: BreadcrumbEntry[]\n  /**\n   * Drawn between every step. Defaults to a chevron.\n   */\n  separator?: React.ReactNode\n  /**\n   * Drawn before every step's label.\n   *\n   * Deliberately one icon for the whole trail rather than one per entry: a\n   * trail with icons on some steps and not others reads as though the icons\n   * mean something, and they do not — the trail is a path, and every segment of\n   * it is the same kind of thing.\n   */\n  icon?: React.ReactNode\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "navigation"
  ],
  "type": "registry:ui"
}