mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 08:48:11 +00:00
132 lines
3.7 KiB
TypeScript
132 lines
3.7 KiB
TypeScript
import { AppShell, Box } from "@mantine/core";
|
|
import { useDisclosure } from "@mantine/hooks";
|
|
import { type ReactNode, useEffect, useState } from "react";
|
|
|
|
import FreightDashboardHeader from "./FreightDashboardHeader";
|
|
import FreightSidebar from "./FreightSidebar";
|
|
import { getPageMeta } from "./route-meta";
|
|
import type { SidebarSection } from "./types";
|
|
|
|
type Theme = "light" | "dark";
|
|
const THEME_STORAGE_KEY = "edr-theme";
|
|
const HEADER_HEIGHT = 64;
|
|
const NAVBAR_WIDTH = 280;
|
|
|
|
function getInitialTheme(): Theme {
|
|
if (typeof window === "undefined") return "light";
|
|
const stored = window.localStorage.getItem(THEME_STORAGE_KEY);
|
|
if (stored === "dark" || stored === "light") return stored;
|
|
return window.matchMedia?.("(prefers-color-scheme: dark)").matches
|
|
? "dark"
|
|
: "light";
|
|
}
|
|
|
|
export interface FreightDashboardLayoutProps {
|
|
sidebarSections: SidebarSection[];
|
|
/** Render the shell with no navbar at all (used by GL clearance-only users). */
|
|
hideSidebar?: boolean;
|
|
activeHref?: string;
|
|
onNavigate?: (href: string) => void;
|
|
headerRight?: ReactNode;
|
|
enableThemeToggle?: boolean;
|
|
userName?: string;
|
|
userEmail?: string;
|
|
userInitials?: string;
|
|
onLogout?: () => void;
|
|
children: ReactNode;
|
|
}
|
|
|
|
const FreightDashboardLayout = ({
|
|
sidebarSections,
|
|
hideSidebar = false,
|
|
activeHref = "",
|
|
onNavigate,
|
|
headerRight,
|
|
enableThemeToggle = false,
|
|
userName,
|
|
userEmail,
|
|
userInitials,
|
|
onLogout,
|
|
children,
|
|
}: FreightDashboardLayoutProps) => {
|
|
const pageMeta = getPageMeta(activeHref);
|
|
const [mobileOpened, { toggle: toggleMobile, close: closeMobile }] =
|
|
useDisclosure(false);
|
|
|
|
const [theme, setTheme] = useState<Theme>(() =>
|
|
enableThemeToggle ? getInitialTheme() : "light",
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (!enableThemeToggle) return;
|
|
const root = document.documentElement;
|
|
root.classList.toggle("dark", theme === "dark");
|
|
window.localStorage.setItem(THEME_STORAGE_KEY, theme);
|
|
}, [theme, enableThemeToggle]);
|
|
|
|
const toggleTheme = () =>
|
|
setTheme((current) => (current === "dark" ? "light" : "dark"));
|
|
|
|
const navigate = (href: string) => {
|
|
closeMobile();
|
|
onNavigate?.(href);
|
|
};
|
|
|
|
return (
|
|
<AppShell
|
|
layout="alt"
|
|
padding={0}
|
|
className="bg-edr-bg"
|
|
header={{ height: HEADER_HEIGHT }}
|
|
// When the sidebar is hidden the navbar slot is dropped entirely so Main
|
|
// spans the full viewport width (GL clearance-only users).
|
|
navbar={
|
|
hideSidebar
|
|
? undefined
|
|
: {
|
|
width: NAVBAR_WIDTH,
|
|
breakpoint: "sm",
|
|
collapsed: { mobile: !mobileOpened },
|
|
}
|
|
}
|
|
>
|
|
<FreightDashboardHeader
|
|
pageMeta={pageMeta}
|
|
headerRight={headerRight}
|
|
enableThemeToggle={enableThemeToggle}
|
|
userName={userName}
|
|
userEmail={userEmail}
|
|
userInitials={userInitials}
|
|
onLogout={onLogout}
|
|
theme={theme}
|
|
onToggleTheme={toggleTheme}
|
|
mobileOpened={mobileOpened}
|
|
onToggleMobile={toggleMobile}
|
|
hideSidebarBurger={hideSidebar}
|
|
/>
|
|
|
|
{!hideSidebar && (
|
|
<FreightSidebar
|
|
sections={sidebarSections}
|
|
activeHref={activeHref}
|
|
onNavigate={navigate}
|
|
onClose={closeMobile}
|
|
/>
|
|
)}
|
|
|
|
<AppShell.Main>
|
|
{/* Internal scroll keeps the fixed-viewport model the dashboard pages
|
|
assume (sidebar + header stay put, content scrolls beneath). */}
|
|
<Box
|
|
className="overflow-y-auto bg-edr-bg"
|
|
style={{ height: `calc(100dvh - ${HEADER_HEIGHT}px)` }}
|
|
>
|
|
{children}
|
|
</Box>
|
|
</AppShell.Main>
|
|
</AppShell>
|
|
);
|
|
};
|
|
|
|
export default FreightDashboardLayout;
|