- Replace top header with left sidebar navigation - Sidebar shows module-aware nav items filtered by /api/modules - Collapsible: expanded shows icon+label, collapsed shows icon only - Toggle with Ctrl+J or collapse button, state persisted in localStorage - Keyboard navigable: Arrow Up/Down, Enter to navigate, Escape to collapse - Bottom section: density toggle, user info with role badge, logout - Add useModules hook for fetching module state - Add sidebar density variables to theme.css Closes #113, closes #114
69 lines
1.7 KiB
TypeScript
69 lines
1.7 KiB
TypeScript
import { useCallback, useEffect, useState } from "react";
|
|
import { Outlet } from "react-router-dom";
|
|
import { useAuth } from "../hooks/useAuth";
|
|
import { useDensity } from "../hooks/useDensity";
|
|
import { useModules } from "../hooks/useModules";
|
|
import { Sidebar } from "./Sidebar";
|
|
|
|
export function AppShell() {
|
|
const { user, loading, logout } = useAuth();
|
|
const [density, toggleDensity] = useDensity();
|
|
const { modules } = useModules();
|
|
|
|
const [sidebarOpen, setSidebarOpen] = useState(() => {
|
|
return localStorage.getItem("silo-sidebar") !== "closed";
|
|
});
|
|
|
|
const toggleSidebar = useCallback(() => {
|
|
setSidebarOpen((prev) => {
|
|
const next = !prev;
|
|
localStorage.setItem("silo-sidebar", next ? "open" : "closed");
|
|
return next;
|
|
});
|
|
}, []);
|
|
|
|
// Ctrl+J to toggle sidebar
|
|
useEffect(() => {
|
|
const handler = (e: KeyboardEvent) => {
|
|
if (e.ctrlKey && e.key === "j") {
|
|
e.preventDefault();
|
|
toggleSidebar();
|
|
}
|
|
};
|
|
window.addEventListener("keydown", handler);
|
|
return () => window.removeEventListener("keydown", handler);
|
|
}, [toggleSidebar]);
|
|
|
|
if (loading) {
|
|
return (
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
height: "100vh",
|
|
}}
|
|
>
|
|
<div className="spinner" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div style={{ display: "flex", height: "100vh" }}>
|
|
<Sidebar
|
|
open={sidebarOpen}
|
|
onToggle={toggleSidebar}
|
|
modules={modules}
|
|
user={user}
|
|
density={density}
|
|
onToggleDensity={toggleDensity}
|
|
onLogout={logout}
|
|
/>
|
|
<main style={{ flex: 1, overflow: "auto", padding: "1rem" }}>
|
|
<Outlet />
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|