158 lines
4.7 KiB
TypeScript
158 lines
4.7 KiB
TypeScript
import { type ReactNode, useEffect, useState } from "react";
|
|
import { Sidebar } from "../navigation/Sidebar";
|
|
import { useTelemetryStream } from "../../features/telemetry/hooks/useTelemetryStream";
|
|
import { useCurrentUser } from "../../features/auth/hooks/useCurrentUser";
|
|
import type { TelemetrySnapshot } from "../../types/telemetry";
|
|
import type { AppPage } from "../../app/App";
|
|
|
|
type Theme = "dark" | "light";
|
|
|
|
type AppShellRenderProps = {
|
|
theme: Theme;
|
|
snapshots: TelemetrySnapshot[];
|
|
};
|
|
|
|
type AppShellProps = {
|
|
activePage: AppPage;
|
|
onNavigate: (page: AppPage) => void;
|
|
children: (props: AppShellRenderProps) => ReactNode;
|
|
};
|
|
|
|
const THEME_STORAGE_KEY = "app-theme";
|
|
|
|
export function AppShell({ activePage, onNavigate, children }: AppShellProps) {
|
|
const telemetry = useTelemetryStream();
|
|
const currentUser = useCurrentUser();
|
|
|
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
|
const [autoCompact, setAutoCompact] = useState(false);
|
|
|
|
const [theme, setTheme] = useState<Theme>(() => {
|
|
const stored = localStorage.getItem(THEME_STORAGE_KEY);
|
|
return stored === "light" || stored === "dark" ? stored : "dark";
|
|
});
|
|
|
|
const isDark = theme === "dark";
|
|
const isDashboard = activePage === "dashboard";
|
|
|
|
useEffect(() => {
|
|
localStorage.setItem(THEME_STORAGE_KEY, theme);
|
|
}, [theme]);
|
|
|
|
const toggleTheme = () => {
|
|
setTheme((current) => (current === "dark" ? "light" : "dark"));
|
|
};
|
|
|
|
useEffect(() => {
|
|
const update = () => {
|
|
const compact = window.innerWidth <= 1366 || window.innerHeight <= 760;
|
|
setAutoCompact(compact);
|
|
|
|
if (compact) {
|
|
setSidebarCollapsed(true);
|
|
}
|
|
};
|
|
|
|
update();
|
|
window.addEventListener("resize", update);
|
|
|
|
return () => window.removeEventListener("resize", update);
|
|
}, []);
|
|
|
|
return (
|
|
<div
|
|
className={
|
|
isDark
|
|
? "relative h-full overflow-hidden bg-[#07101B] text-slate-100"
|
|
: "relative h-full overflow-hidden bg-white text-slate-950"
|
|
}
|
|
>
|
|
<style>{`
|
|
.app-scrollbar {
|
|
scrollbar-width: thin;
|
|
scrollbar-color: ${isDark ? "#334155 #07101B" : "#94a3b8 #f8fafc"};
|
|
}
|
|
|
|
.app-scrollbar::-webkit-scrollbar {
|
|
width: 10px;
|
|
height: 10px;
|
|
}
|
|
|
|
.app-scrollbar::-webkit-scrollbar-track {
|
|
background: ${isDark ? "#07101B" : "#f8fafc"};
|
|
border-left: 1px solid ${isDark ? "rgba(255,255,255,0.06)" : "#e2e8f0"};
|
|
}
|
|
|
|
.app-scrollbar::-webkit-scrollbar-thumb {
|
|
background: ${isDark ? "#334155" : "#94a3b8"};
|
|
border-radius: 999px;
|
|
border: 2px solid ${isDark ? "#07101B" : "#f8fafc"};
|
|
}
|
|
|
|
.app-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
background: ${isDark ? "#475569" : "#64748b"};
|
|
}
|
|
|
|
.app-scrollbar::-webkit-scrollbar-corner {
|
|
background: transparent;
|
|
}
|
|
`}</style>
|
|
|
|
<div className="flex h-full overflow-hidden">
|
|
<aside className="relative h-full shrink-0 overflow-hidden">
|
|
<Sidebar
|
|
theme={theme}
|
|
activePage={activePage}
|
|
collapsed={sidebarCollapsed}
|
|
userInitials={currentUser.initials}
|
|
userName={currentUser.name}
|
|
userRole={currentUser.role}
|
|
onNavigate={onNavigate}
|
|
onToggleCollapsed={() => setSidebarCollapsed((current) => !current)}
|
|
onToggleTheme={toggleTheme}
|
|
/>
|
|
|
|
<div
|
|
className={
|
|
isDark
|
|
? "pointer-events-none absolute right-0 top-0 h-full w-px bg-white/10"
|
|
: "pointer-events-none absolute right-0 top-0 h-full w-px bg-slate-200"
|
|
}
|
|
/>
|
|
</aside>
|
|
|
|
<div className="flex min-w-0 flex-1 flex-col overflow-hidden">
|
|
<main
|
|
className={
|
|
isDark
|
|
? `app-scrollbar min-h-0 flex-1 ${isDashboard
|
|
? "overflow-y-auto bg-[#07101B] p-0"
|
|
: `${autoCompact ? "p-3" : "p-4"} overflow-y-auto bg-[#0b1220]`
|
|
}`
|
|
: `app-scrollbar min-h-0 flex-1 ${isDashboard
|
|
? "overflow-y-auto bg-white p-0"
|
|
: `${autoCompact ? "p-3" : "p-4"} overflow-y-auto bg-slate-100`
|
|
}`
|
|
}
|
|
>
|
|
<div
|
|
className={
|
|
isDashboard
|
|
? isDark
|
|
? "h-full w-full bg-[#07101B]"
|
|
: "h-full w-full bg-white"
|
|
: "h-full w-full"
|
|
}
|
|
>
|
|
{children({
|
|
theme,
|
|
snapshots: telemetry.snapshots,
|
|
})}
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|