feat(web): MS15 Phase 1 — Design System & App Shell (#451)
All checks were successful
ci/woodpecker/push/web Pipeline was successful
All checks were successful
ci/woodpecker/push/web Pipeline was successful
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #451.
This commit is contained in:
@@ -4,10 +4,79 @@ import { useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useAuth } from "@/lib/auth/auth-context";
|
||||
import { IS_MOCK_AUTH_MODE } from "@/lib/config";
|
||||
import { Navigation } from "@/components/layout/Navigation";
|
||||
import { AppHeader } from "@/components/layout/AppHeader";
|
||||
import { AppSidebar } from "@/components/layout/AppSidebar";
|
||||
import { SidebarProvider, useSidebar } from "@/components/layout/SidebarContext";
|
||||
import { ChatOverlay } from "@/components/chat";
|
||||
import { MosaicSpinner } from "@/components/ui/MosaicSpinner";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const SIDEBAR_EXPANDED_WIDTH = "240px";
|
||||
const SIDEBAR_COLLAPSED_WIDTH = "60px";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Inner shell — must be a child of SidebarProvider to use useSidebar
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface AppShellProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
function AppShell({ children }: AppShellProps): React.JSX.Element {
|
||||
const { collapsed, isMobile } = useSidebar();
|
||||
|
||||
// On tablet (md–lg), hide sidebar from the grid when the sidebar is collapsed.
|
||||
// On mobile, the sidebar is fixed-position so the grid is always single-column.
|
||||
const sidebarHidden = !isMobile && collapsed;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="app-shell"
|
||||
data-sidebar-hidden={sidebarHidden ? "true" : undefined}
|
||||
style={
|
||||
{
|
||||
"--sidebar-w": collapsed ? SIDEBAR_COLLAPSED_WIDTH : SIDEBAR_EXPANDED_WIDTH,
|
||||
transition: "grid-template-columns 0.2s var(--ease, ease)",
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{/* Full-width header — grid-column: 1 / -1 via .app-header CSS class */}
|
||||
<AppHeader />
|
||||
|
||||
{/* Sidebar — left column, row 2, via .app-sidebar CSS class */}
|
||||
<AppSidebar />
|
||||
|
||||
{/* Main content — right column, row 2, via .app-main CSS class */}
|
||||
<main className="app-main" id="main-content">
|
||||
{IS_MOCK_AUTH_MODE && (
|
||||
<div
|
||||
className="border-b px-4 py-2 text-xs font-medium flex-shrink-0"
|
||||
style={{
|
||||
borderColor: "var(--ms-amber-500)",
|
||||
background: "rgba(245, 158, 11, 0.08)",
|
||||
color: "var(--ms-amber-400)",
|
||||
}}
|
||||
data-testid="mock-auth-banner"
|
||||
>
|
||||
Mock Auth Mode (Local Only): Real authentication is bypassed for frontend development.
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1 overflow-y-auto p-5">{children}</div>
|
||||
</main>
|
||||
|
||||
{!IS_MOCK_AUTH_MODE && <ChatOverlay />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Authenticated layout — handles auth guard + provides sidebar context
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export default function AuthenticatedLayout({
|
||||
children,
|
||||
}: {
|
||||
@@ -23,11 +92,7 @@ export default function AuthenticatedLayout({
|
||||
}, [isAuthenticated, isLoading, router]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900"></div>
|
||||
</div>
|
||||
);
|
||||
return <MosaicSpinner size={48} fullPage />;
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
@@ -35,20 +100,8 @@ export default function AuthenticatedLayout({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<Navigation />
|
||||
<div className="pt-16">
|
||||
{IS_MOCK_AUTH_MODE && (
|
||||
<div
|
||||
className="border-b border-amber-300 bg-amber-50 px-4 py-2 text-sm text-amber-900"
|
||||
data-testid="mock-auth-banner"
|
||||
>
|
||||
Mock Auth Mode (Local Only): Real authentication is bypassed for frontend development.
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
{!IS_MOCK_AUTH_MODE && <ChatOverlay />}
|
||||
</div>
|
||||
<SidebarProvider>
|
||||
<AppShell>{children}</AppShell>
|
||||
</SidebarProvider>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user