update ui update and fix layout issue
This commit is contained in:
@@ -1,160 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import {AppShell} from '@astryxdesign/core/AppShell';
|
||||
import {
|
||||
Layout,
|
||||
LayoutContent,
|
||||
LayoutHeader,
|
||||
LayoutPanel,
|
||||
VStack,
|
||||
} from '@astryxdesign/core/Layout';
|
||||
import {AppSideNav} from '@/components/shell/AppSideNav';
|
||||
import {AppTopNav} from '@/components/shell/AppTopNav';
|
||||
import {MobileMenu} from '@/components/shell/MobileMenu';
|
||||
import {SidebarProvider, useSidebar} from '@/components/shell/SidebarProvider';
|
||||
import {CopilotPanel} from '@/features/copilot/CopilotPanel';
|
||||
import {CopilotSlideOver} from '@/features/copilot/CopilotSlideOver';
|
||||
import {useCopilot} from '@/features/copilot/CopilotProvider';
|
||||
import {
|
||||
useBreakpoint,
|
||||
isPanelInline,
|
||||
isSideNavInline,
|
||||
copilotWidth,
|
||||
contentMaxWidth,
|
||||
} from '@/lib/breakpoints';
|
||||
import {ProtectedLayout} from '@/shared/layouts/ProtectedLayout';
|
||||
|
||||
/**
|
||||
* The three-column shell, and the reason the Copilot survives navigation.
|
||||
* The route-group boundary for every authenticated screen.
|
||||
*
|
||||
* 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{header, start, content} internally and exposes no end/panel slot. The
|
||||
* Copilot column therefore comes from a NESTED Layout inside its children.
|
||||
*
|
||||
* AppShell(sideNav)
|
||||
* └─ Layout(header = TopNav, content = page, end = LayoutPanel > Copilot)
|
||||
*
|
||||
* The top nav is deliberately NOT passed to AppShell's `topNav` slot. That slot
|
||||
* renders Layout{header} at the shell root, which spans the full viewport width
|
||||
* and pushes the sidebar — and therefore the branding — 48px down the screen.
|
||||
* Measured in the browser, the logo sat at y=64 with the top-left 260x48 region
|
||||
* empty. Moving the bar into the content column's own header starts the sidebar
|
||||
* at y=0, so the brand anchors in the window's top-left corner and the bar
|
||||
* begins where the content does. This is the arrangement Linear, Cursor and
|
||||
* Stripe use, and no amount of padding inside the sidebar could produce it.
|
||||
*
|
||||
* The one thing given up: with no `topNav`, AppShell drops the --radius-page
|
||||
* corner it draws where the top bar meets the sidebar. The content area still
|
||||
* paints elevated --color-background-surface (#0F0F10) against the #000 rails,
|
||||
* and with a full-height sidebar that corner had nothing left to round.
|
||||
* Deliberately thin: the composition lives in ProtectedLayout, so this file
|
||||
* never needs editing again and the shell can be reused (tests, a future
|
||||
* embedded view) without a route existing for it.
|
||||
*/
|
||||
export default function WorkspaceLayout({
|
||||
export default function WorkspaceRouteLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<WorkspaceShell>{children}</WorkspaceShell>
|
||||
</SidebarProvider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split out from the layout only so it can READ the sidebar context that the
|
||||
* layout mounts — a provider's own component sits above its value.
|
||||
*/
|
||||
function WorkspaceShell({children}: {children: React.ReactNode}) {
|
||||
const bp = useBreakpoint();
|
||||
const {isOpen} = useCopilot();
|
||||
const inline = isPanelInline(bp);
|
||||
const showInlinePanel = inline && isOpen;
|
||||
|
||||
// Both flags come from SidebarProvider now. AppShell keeps a copy of the
|
||||
// open state for its own context (MobileNav reads it for aria wiring), but
|
||||
// ours is the one that decides — uncontrolled, there would be no way to
|
||||
// dismiss the drawer when a nav item is selected.
|
||||
const {isDrawerOpen, setDrawerOpen} = useSidebar();
|
||||
|
||||
// Below the breakpoint the rail is gone — it lives in the drawer — so there
|
||||
// is no top-left corner to anchor the brand to and nothing for the bar to
|
||||
// start after. Hand the top nav back to AppShell there. Keeping it in the
|
||||
// content column instead pushed the SideNav into AppShell's
|
||||
// `autoMobileTopBar` fallback, which crops the lockup into a 48px bar.
|
||||
const sideNavInline = isSideNavInline(bp);
|
||||
|
||||
return (
|
||||
<AppShell
|
||||
variant="elevated"
|
||||
height="fill"
|
||||
// Layout/LayoutContent own the padding so the Copilot panel can sit
|
||||
// flush against the workspace edge.
|
||||
contentPadding={0}
|
||||
topNav={sideNavInline ? undefined : <AppTopNav />}
|
||||
sideNav={<AppSideNav />}
|
||||
// 'sm' (640px) matches the mobile/tablet line in lib/breakpoints —
|
||||
// AppShell's own threshold and ours have to agree or the hamburger and
|
||||
// the rail are both visible, or neither is.
|
||||
mobileNav={{
|
||||
breakpoint: 'sm',
|
||||
isOpen: isDrawerOpen,
|
||||
onOpenChange: setDrawerOpen,
|
||||
// hasToggle: false — AppShell would otherwise inject a SECOND
|
||||
// hamburger into TopNav's mobile bar, next to the one in `heading`
|
||||
// that works at every width. See AppTopNav.
|
||||
hasToggle: false,
|
||||
content: <MobileMenu />,
|
||||
}}
|
||||
>
|
||||
<Layout
|
||||
height="fill"
|
||||
header={
|
||||
sideNavInline ? (
|
||||
<LayoutHeader padding={0}>
|
||||
<AppTopNav />
|
||||
</LayoutHeader>
|
||||
) : undefined
|
||||
}
|
||||
content={
|
||||
<LayoutContent padding={5}>
|
||||
{/*
|
||||
Above 1920 the workspace is capped and centred. Uncapped, a
|
||||
30-day line chart stretches across ~1900px, which flattens every
|
||||
trend it exists to show, and body copy runs well past the ~90ch
|
||||
where reading breaks down. Below the cap this is a no-op —
|
||||
maxWidth only binds once there is more width than content wants.
|
||||
*/}
|
||||
<VStack
|
||||
width="100%"
|
||||
maxWidth={contentMaxWidth(bp)}
|
||||
className={bp === 'ultrawide' ? 'mx-auto' : undefined}
|
||||
>
|
||||
{children}
|
||||
</VStack>
|
||||
</LayoutContent>
|
||||
}
|
||||
end={
|
||||
showInlinePanel ? (
|
||||
<LayoutPanel
|
||||
hasDivider
|
||||
width={copilotWidth(bp)}
|
||||
// CopilotPanel supplies its own header and tab chrome.
|
||||
padding={0}
|
||||
role="complementary"
|
||||
label="Loyaly AI"
|
||||
>
|
||||
<CopilotPanel />
|
||||
</LayoutPanel>
|
||||
) : 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 ? <CopilotSlideOver /> : null}
|
||||
</AppShell>
|
||||
);
|
||||
return <ProtectedLayout>{children}</ProtectedLayout>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user