'use client'; import {AppShell} from '@astryxdesign/core/AppShell'; import {Layout, LayoutContent, LayoutPanel} from '@astryxdesign/core/Layout'; import {AppSideNav} from '@/components/shell/AppSideNav'; import {AppTopNav} from '@/components/shell/AppTopNav'; import {MobileScopeNav} from '@/components/shell/MobileScopeNav'; import {CopilotPanel} from '@/features/copilot/CopilotPanel'; import {CopilotSlideOver} from '@/features/copilot/CopilotSlideOver'; import {useCopilot} from '@/features/copilot/CopilotProvider'; import {useBreakpoint, isPanelInline, copilotWidth} from '@/lib/breakpoints'; /** * The three-column shell, and the reason the Copilot survives navigation. * * Next's App Router preserves a layout's element identity across sibling route * changes, so /dashboard → /staff swaps only {children}. Everything else here — * nav, scroll position, the Copilot — is untouched. * * Structure, and why AppShell alone is not enough: AppShell renders * Layout{start, content} internally and exposes no end/panel slot. The Copilot * column therefore comes from a NESTED Layout inside AppShell's children, * using its `end` slot. * * AppShell(topNav, sideNav) * └─ Layout(content = page, end = LayoutPanel > CopilotPanel) * * variant="elevated" is what produces the described visual for free: with both * navs present the shell root and rails paint --color-background-body (#000), * and the content area sits on an elevated --color-background-surface (#0F0F10) * backdrop with --radius-page on the start corner. */ export default function WorkspaceLayout({ children, }: { children: React.ReactNode; }) { const bp = useBreakpoint(); const {isOpen} = useCopilot(); const inline = isPanelInline(bp); const showInlinePanel = inline && isOpen; return ( } sideNav={} // Supplied explicitly rather than auto-generated: the default drawer // mirrors sideNav only, which would leave the store and range controls // unreachable below 768px (they live in TopNav's collapsing start slot). mobileNav={{breakpoint: 'md', content: }} > {children}} end={ showInlinePanel ? ( ) : undefined } /> {/* Mounted only below the inline breakpoint. Because every piece of Copilot state lives in CopilotProvider (above the route tree), moving between these two presentations is lossless. */} {!inline ? : null} ); }