{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scoped-nav",
  "title": "Scoped Nav",
  "description": "The two-level sliding sidebar navigation: workspace-level nav and per-project nav with a Back-to-all-projects row, sliding between levels as a project enters or leaves scope.",
  "dependencies": [
    "@untitledui/icons@^0.0.22"
  ],
  "registryDependencies": [
    "@nx-ui/scope",
    "@nx-ui/sidebar",
    "@nx-ui/sidebar-nav",
    "@nx-ui/utils"
  ],
  "files": [
    {
      "path": "components/blocks/scoped-nav.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronLeft } from \"@untitledui/icons\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  SidebarGroup,\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n} from \"@/components/ui/sidebar\"\nimport { SidebarNav, type SidebarNavGroup } from \"@/components/blocks/sidebar-nav\"\nimport type { ProjectRef } from \"@/components/blocks/scope\"\n\n/**\n * ScopedNav — the two-level SLIDING sidebar navigation of the nx shell.\n *\n * With no project in scope the TOP LEVEL renders (workspace nav: the projects\n * picker, the cross-project dashboard…). Selecting a project slides the whole\n * nav to the PROJECT LEVEL — a different set of groups, headed by a\n * \"Back to all projects\" row. The incoming level slides in from the right\n * when entering project scope and from the left when leaving, with a fade\n * (~200ms; instant under `prefers-reduced-motion`). Switching between two\n * projects stays at project level — the content re-targets without sliding.\n *\n * Controlled and data-driven: the level is derived from `project`\n * (null = top level), the back row and the breadcrumbs' \"All projects\" action\n * both flow through the SAME consumer callback (`onBackToAllProjects` /\n * `onProjectClear`), so the two escapes cannot drift apart. Drop it inside an\n * AppSidebar's content — it renders standard SidebarNav groups within.\n *\n * This is the org-wide shell pattern shared across the Nexus app family —\n * see `docs/scoped-shell.md`.\n */\n\nexport type ScopedNavLevels = {\n  /** Workspace-level groups shown when no project is in scope. */\n  topLevel: SidebarNavGroup[]\n  /** Project-level groups, built per scoped project. */\n  projectLevel: (project: ProjectRef) => SidebarNavGroup[]\n}\n\nconst SLIDE_MS = 200\n\n/**\n * Slide phases. Settled states lay out ONLY the active panel (so the nav\n * column's height matches its level); the `-prep` states lay both panels out\n * with transitions off so the following frame can animate cleanly — a panel\n * coming back from `display:none` cannot transition in the same commit.\n *\n *   top ⇄ [entering-prep → entering] project ⇄ [leaving-prep → leaving] top\n */\ntype Phase =\n  | \"top\"\n  | \"entering-prep\"\n  | \"entering\"\n  | \"project\"\n  | \"leaving-prep\"\n  | \"leaving\"\n\nconst prefersReducedMotion = () =>\n  typeof window !== \"undefined\" &&\n  window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n\nfunction ScopedNav({\n  levels,\n  project,\n  onBackToAllProjects,\n  backLabel = \"Back to all projects\",\n  className,\n}: {\n  levels: ScopedNavLevels\n  /** The scoped project — null renders the top level. */\n  project: ProjectRef | null\n  /** The back row's action — wire to the same handler as `onProjectClear`. */\n  onBackToAllProjects?: () => void\n  backLabel?: string\n  className?: string\n}) {\n  const atProjectLevel = project != null\n\n  // Keep the last project rendered while sliding back out to the top level,\n  // so the outgoing panel doesn't blank mid-transition.\n  const [renderedProject, setRenderedProject] = React.useState(project)\n  React.useEffect(() => {\n    if (project) setRenderedProject(project)\n  }, [project])\n\n  const [phase, setPhase] = React.useState<Phase>(\n    atProjectLevel ? \"project\" : \"top\"\n  )\n\n  // Kick off a slide when the LEVEL changes (project switches at the same\n  // level re-target content without sliding). Reduced motion jumps straight\n  // to the settled state.\n  React.useEffect(() => {\n    setPhase((current) => {\n      const currentSide =\n        current === \"project\" || current === \"entering\" || current === \"entering-prep\"\n      if (atProjectLevel === currentSide) return current\n      if (prefersReducedMotion()) return atProjectLevel ? \"project\" : \"top\"\n      return atProjectLevel ? \"entering-prep\" : \"leaving-prep\"\n    })\n  }, [atProjectLevel])\n\n  // Advance prep → animate (next frame) → settled (after the slide).\n  React.useEffect(() => {\n    if (phase === \"entering-prep\" || phase === \"leaving-prep\") {\n      const next: Phase = phase === \"entering-prep\" ? \"entering\" : \"leaving\"\n      let raf2 = 0\n      const raf1 = window.requestAnimationFrame(() => {\n        raf2 = window.requestAnimationFrame(() => setPhase(next))\n      })\n      return () => {\n        window.cancelAnimationFrame(raf1)\n        window.cancelAnimationFrame(raf2)\n      }\n    }\n    if (phase === \"entering\" || phase === \"leaving\") {\n      const settled: Phase = phase === \"entering\" ? \"project\" : \"top\"\n      const timer = window.setTimeout(() => setPhase(settled), SLIDE_MS + 40)\n      return () => window.clearTimeout(timer)\n    }\n  }, [phase])\n\n  const layoutTop = phase !== \"project\"\n  const layoutProject = phase !== \"top\"\n  // The track shifts left while both panels are laid out and the project side\n  // should be on screen: during the enter animation, and in the leave prep\n  // frame (so the outgoing project panel starts exactly where it was).\n  const trackShifted = phase === \"entering\" || phase === \"leaving-prep\"\n  const animating = phase === \"entering\" || phase === \"leaving\"\n  // Panels hold full opacity through their prep frame so the fade runs in\n  // sync with the slide, not a frame ahead of it.\n  const topVisible =\n    phase === \"top\" || phase === \"leaving\" || phase === \"entering-prep\"\n  const projectVisible =\n    phase === \"project\" || phase === \"entering\" || phase === \"leaving-prep\"\n\n  const panelClass = (laidOut: boolean, visible: boolean) =>\n    cn(\n      \"w-1/2 shrink-0 transition-opacity duration-200 ease-out motion-reduce:transition-none\",\n      visible ? \"opacity-100\" : \"opacity-0\",\n      !laidOut && \"hidden\"\n    )\n\n  return (\n    <div\n      data-slot=\"scoped-nav\"\n      data-level={atProjectLevel ? \"project\" : \"top\"}\n      className={cn(\"relative w-full overflow-x-clip\", className)}\n    >\n      <div\n        className={cn(\n          \"flex w-[200%]\",\n          animating\n            ? \"transition-transform duration-200 ease-out motion-reduce:transition-none\"\n            : \"transition-none\",\n          trackShifted ? \"-translate-x-1/2\" : \"translate-x-0\"\n        )}\n      >\n        <div\n          data-slot=\"scoped-nav-top-level\"\n          className={panelClass(layoutTop, topVisible)}\n          inert={atProjectLevel || undefined}\n        >\n          <SidebarNav groups={levels.topLevel} />\n        </div>\n\n        <div\n          data-slot=\"scoped-nav-project-level\"\n          className={panelClass(layoutProject, projectVisible)}\n          inert={!atProjectLevel || undefined}\n        >\n          {renderedProject ? (\n            <>\n              <SidebarGroup className=\"pb-0\">\n                <SidebarMenu>\n                  <SidebarMenuItem>\n                    <SidebarMenuButton\n                      onClick={onBackToAllProjects}\n                      className=\"h-9 gap-2 rounded-lg font-semibold text-muted-foreground\"\n                    >\n                      <ChevronLeft className=\"size-4! shrink-0 text-fg-quaternary\" />\n                      <span>{backLabel}</span>\n                    </SidebarMenuButton>\n                  </SidebarMenuItem>\n                </SidebarMenu>\n              </SidebarGroup>\n              <SidebarNav groups={levels.projectLevel(renderedProject)} />\n            </>\n          ) : null}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport { ScopedNav }\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:block"
}