import { Suspense, useCallback, useEffect, useRef, useState } from 'react' import { Outlet, useLocation } from 'react-router-dom' import Sidebar from './Sidebar' import Topbar from './Topbar' import AiDock from './AiDock' import { ROUTE_BY_PATH } from './routes' import { useBadges, useHotkeys, useNavOpen, useRouteMeta, useSidebarCollapsed } from './useShell' import Icon from '../ui/icons' import ErrorBoundary from '../components/ErrorBoundary' import Spinner from '../components/Spinner' export default function AppLayout() { const location = useLocation() const contentRef = useRef(null) const [navOpen, setNavOpen] = useNavOpen() const [collapsed, toggleCollapsed] = useSidebarCollapsed() const [dockOpen, setDockOpen] = useState(false) const badges = useBadges() const routeKey = location.pathname.split('/')[1] || 'dashboard' const route = ROUTE_BY_PATH[routeKey] useRouteMeta(route) const onEscape = useCallback(() => { setNavOpen(false) setDockOpen(false) }, [setNavOpen]) const searchRef = useHotkeys({ onEscape }) // Navigating closes the drawer and the dock, and resets scroll — the three // things Router.render did at the end of every route change (js/app.js:44-51). useEffect(() => { setNavOpen(false) setDockOpen(false) if (contentRef.current) contentRef.current.scrollTop = 0 }, [location.pathname, setNavOpen]) return (
Skip to content
setNavOpen((o) => !o)} searchRef={searchRef} />
{/* Keyed by pathname: navigating away from a crashed screen resets it. */}
}>
setDockOpen(false)} />
setNavOpen(false)} aria-hidden="true" />
) }