{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scope-breadcrumbs",
  "title": "Scope Breadcrumbs",
  "description": "The top-bar context switcher: an organisation crumb with a workspace menu, a project crumb with a project switcher and an All-projects escape (shown only when scoped), then ordinary path crumbs.",
  "dependencies": [
    "@untitledui/icons@^0.0.22"
  ],
  "registryDependencies": [
    "@nx-ui/breadcrumb",
    "@nx-ui/dropdown-menu",
    "@nx-ui/org-switcher",
    "@nx-ui/scope",
    "@nx-ui/utils"
  ],
  "files": [
    {
      "path": "components/blocks/scope-breadcrumbs.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  Check,\n  ChevronDown,\n  ChevronSelectorVertical,\n  Folder,\n  Grid01,\n  Plus,\n} from \"@untitledui/icons\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  Breadcrumb,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbList,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n} from \"@/components/ui/breadcrumb\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\nimport { OrgAvatar } from \"@/components/blocks/org-switcher\"\nimport type {\n  OrgRef,\n  ProjectRef,\n  Scope,\n  ScopeChangeHandlers,\n} from \"@/components/blocks/scope\"\n\n/**\n * ScopeBreadcrumbs — the nx top-bar CONTEXT SWITCHER, not a plain trail.\n *\n * The first crumb is the scoped ORGANISATION (the auth boundary) with a\n * workspace-switcher menu; the second, present only when a project is in\n * scope, is the scoped CONSTRUCTION PROJECT with its own switcher menu and an\n * \"All projects\" escape; any `trail` items follow as ordinary crumbs, the\n * last one rendered as the current page.\n *\n *   Org ▾  ▸  Project ⇕  ▸  Movements  ▸  MOV-2041\n *\n * Purely controlled: the active scope arrives via `scope`, changes leave via\n * the `Scope` callbacks (`onOrgChange` / `onProjectChange` / `onProjectClear`).\n * No routing, persistence or auth lives here — see `docs/scoped-shell.md` for\n * how consumers wire those. Mount it in the AppShell `topBar` left slot.\n */\n\nexport type ScopeTrailItem = {\n  label: string\n  href?: string\n  onSelect?: () => void\n}\n\nconst crumbTriggerClass =\n  \"flex shrink-0 cursor-pointer items-center gap-1.5 rounded-md px-1.5 py-1 outline-none transition duration-100 ease-linear hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring/25\"\n\n/**\n * True for the clicks the browser itself turns into navigation the consumer\n * did not ask for: modifier clicks (new tab/window, download) and non-primary\n * buttons. Nothing calls preventDefault on those, so the anchor's native\n * behaviour stands — running `onSelect` as well would navigate the CURRENT tab\n * too, on top of the new one (double navigation).\n */\nfunction isModifiedClick(event: React.MouseEvent): boolean {\n  return (\n    event.metaKey ||\n    event.ctrlKey ||\n    event.shiftKey ||\n    event.altKey ||\n    event.button !== 0\n  )\n}\n\nfunction OrgCrumb({\n  org,\n  orgs,\n  onOrgChange,\n  onCreateOrg,\n}: {\n  org: OrgRef\n  orgs: OrgRef[]\n  onOrgChange?: (orgId: string) => void\n  onCreateOrg?: () => void\n}) {\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        className={crumbTriggerClass}\n        aria-label=\"Switch organisation\"\n      >\n        <OrgAvatar org={org} size=\"sm\" className=\"size-5 text-[0.5rem]\" />\n        <span className=\"max-w-40 truncate text-sm font-semibold text-foreground\">\n          {org.name}\n        </span>\n        <ChevronDown className=\"size-3.5 shrink-0 stroke-[2.5px] text-fg-quaternary\" />\n      </DropdownMenuTrigger>\n\n      <DropdownMenuContent align=\"start\" className=\"min-w-64\">\n        <DropdownMenuGroup>\n          <DropdownMenuLabel>Switch organisation</DropdownMenuLabel>\n          {orgs.map((entry) => {\n            const isCurrent = entry.id === org.id\n            return (\n              <DropdownMenuItem\n                key={entry.id}\n                onClick={() => onOrgChange?.(entry.id)}\n                className=\"gap-2.5\"\n              >\n                <OrgAvatar org={entry} size=\"sm\" />\n                <span className=\"flex min-w-0 flex-1 flex-col\">\n                  <span className=\"truncate text-sm font-semibold text-secondary-foreground\">\n                    {entry.name}\n                  </span>\n                  {entry.meta ? (\n                    <span className=\"truncate text-xs text-muted-foreground\">\n                      {entry.meta}\n                    </span>\n                  ) : null}\n                </span>\n                {isCurrent ? (\n                  <Check\n                    className=\"size-4 shrink-0 text-fg-brand-primary\"\n                    aria-label=\"Current\"\n                  />\n                ) : null}\n              </DropdownMenuItem>\n            )\n          })}\n        </DropdownMenuGroup>\n\n        {onCreateOrg ? (\n          <>\n            <DropdownMenuSeparator />\n            <DropdownMenuItem onClick={onCreateOrg}>\n              <Plus className=\"size-5 text-fg-quaternary\" />\n              <span className=\"text-sm font-semibold text-secondary-foreground\">\n                Create organisation\n              </span>\n            </DropdownMenuItem>\n          </>\n        ) : null}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n\nfunction ProjectCrumb({\n  project,\n  projects,\n  onProjectChange,\n  onProjectClear,\n  onCreateProject,\n  allProjectsLabel,\n}: {\n  project: ProjectRef\n  projects: ProjectRef[]\n  onProjectChange?: (projectId: string) => void\n  onProjectClear?: () => void\n  onCreateProject?: () => void\n  allProjectsLabel: string\n}) {\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        className={cn(crumbTriggerClass, \"min-w-0 shrink\")}\n        aria-label=\"Switch project\"\n      >\n        <Folder className=\"size-4 shrink-0 text-fg-quaternary\" />\n        <span className=\"max-w-56 min-w-0 truncate text-sm font-medium text-muted-foreground\">\n          {project.name}\n        </span>\n        <ChevronSelectorVertical className=\"size-3.5 shrink-0 text-fg-quaternary\" />\n      </DropdownMenuTrigger>\n\n      <DropdownMenuContent align=\"start\" className=\"min-w-72\">\n        <DropdownMenuGroup>\n          <DropdownMenuLabel>Switch project</DropdownMenuLabel>\n          <div className=\"flex max-h-72 flex-col overflow-y-auto\">\n            {projects.map((entry) => {\n              const isCurrent = entry.id === project.id\n              const meta = [entry.client, entry.reference]\n                .filter(Boolean)\n                .join(\" · \")\n              return (\n                <DropdownMenuItem\n                  key={entry.id}\n                  onClick={() => onProjectChange?.(entry.id)}\n                >\n                  <span className=\"flex min-w-0 flex-1 flex-col\">\n                    <span className=\"truncate text-sm font-semibold text-secondary-foreground\">\n                      {entry.name}\n                    </span>\n                    {meta ? (\n                      <span className=\"truncate text-xs text-muted-foreground\">\n                        {meta}\n                      </span>\n                    ) : null}\n                  </span>\n                  {isCurrent ? (\n                    <Check\n                      className=\"size-4 shrink-0 text-fg-brand-primary\"\n                      aria-label=\"Current\"\n                    />\n                  ) : null}\n                </DropdownMenuItem>\n              )\n            })}\n          </div>\n        </DropdownMenuGroup>\n\n        <DropdownMenuSeparator />\n        <DropdownMenuItem onClick={onProjectClear}>\n          <Grid01 className=\"size-4 text-fg-quaternary\" />\n          <span className=\"text-sm font-semibold text-secondary-foreground\">\n            {allProjectsLabel}\n          </span>\n        </DropdownMenuItem>\n        {onCreateProject ? (\n          <DropdownMenuItem onClick={onCreateProject}>\n            <Plus className=\"size-4 text-fg-quaternary\" />\n            <span className=\"text-sm font-semibold text-secondary-foreground\">\n              New project\n            </span>\n          </DropdownMenuItem>\n        ) : null}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n\nfunction ScopeBreadcrumbs({\n  scope,\n  orgs,\n  projects,\n  onOrgChange,\n  onProjectChange,\n  onProjectClear,\n  onCreateOrg,\n  onCreateProject,\n  allProjectsLabel = \"All projects\",\n  trail = [],\n  className,\n  ...props\n}: React.ComponentProps<typeof Breadcrumb> &\n  ScopeChangeHandlers & {\n    /** The active org ▸ project scope (project `null` at workspace level). */\n    scope: Scope\n    /** Organisations the viewer can switch to (including the active one). */\n    orgs: OrgRef[]\n    /** Projects of the ACTIVE org (including the scoped one, when set). */\n    projects: ProjectRef[]\n    /** \"Create organisation\" menu slot — omit to hide the action. */\n    onCreateOrg?: () => void\n    /** \"New project\" menu slot — omit to hide the action. */\n    onCreateProject?: () => void\n    /** Label of the leave-project-scope action in the project menu. */\n    allProjectsLabel?: string\n    /** Ordinary path crumbs after the scope (last renders as current page). */\n    trail?: ScopeTrailItem[]\n  }) {\n  return (\n    <Breadcrumb\n      aria-label=\"Breadcrumb\"\n      className={cn(\"min-w-0\", className)}\n      {...props}\n    >\n      <BreadcrumbList className=\"flex-nowrap gap-0.5 sm:gap-0.5\">\n        <BreadcrumbItem>\n          <OrgCrumb\n            org={scope.org}\n            orgs={orgs}\n            onOrgChange={onOrgChange}\n            onCreateOrg={onCreateOrg}\n          />\n        </BreadcrumbItem>\n\n        {scope.project ? (\n          <>\n            <BreadcrumbSeparator className=\"[&>svg]:size-4\" />\n            <BreadcrumbItem className=\"min-w-0\">\n              <ProjectCrumb\n                project={scope.project}\n                projects={projects}\n                onProjectChange={onProjectChange}\n                onProjectClear={onProjectClear}\n                onCreateProject={onCreateProject}\n                allProjectsLabel={allProjectsLabel}\n              />\n            </BreadcrumbItem>\n          </>\n        ) : null}\n\n        {trail.map((crumb, index) => {\n          const isLast = index === trail.length - 1\n          return (\n            <React.Fragment key={`${crumb.label}-${index}`}>\n              <BreadcrumbSeparator className=\"[&>svg]:size-4\" />\n              <BreadcrumbItem>\n                {isLast || (!crumb.href && !crumb.onSelect) ? (\n                  <BreadcrumbPage\n                    className={cn(\n                      \"shrink-0 px-1.5 py-1 text-sm\",\n                      isLast\n                        ? \"font-semibold\"\n                        : \"font-medium text-muted-foreground\"\n                    )}\n                  >\n                    {crumb.label}\n                  </BreadcrumbPage>\n                ) : (\n                  <BreadcrumbLink\n                    render={\n                      crumb.href ? <a href={crumb.href} /> : <button type=\"button\" />\n                    }\n                    // On an anchor the browser already handles modifier and\n                    // middle clicks; onSelect must sit them out or the current\n                    // tab navigates alongside the new one.\n                    onClick={(event) => {\n                      if (crumb.href && isModifiedClick(event)) return\n                      crumb.onSelect?.()\n                    }}\n                    className=\"shrink-0 cursor-pointer rounded-md px-1.5 py-1 text-sm font-medium transition hover:bg-muted\"\n                  >\n                    {crumb.label}\n                  </BreadcrumbLink>\n                )}\n              </BreadcrumbItem>\n            </React.Fragment>\n          )\n        })}\n      </BreadcrumbList>\n    </Breadcrumb>\n  )\n}\n\nexport { ScopeBreadcrumbs }\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:block"
}