'use client'; import { FunctionComponent, PropsWithChildren, ReactNode, useState } from 'react'; import { useTranslations } from 'next-intl'; import { Box, Stack, useTheme } from '@mui/material'; import { AppIconButton, ErrorBoundary, RouteFadeIn } from '@/components'; import { LinkToPage } from '@/utils'; import { TopBar } from './components'; import SideBar from './components/SideBar'; import { DarkModeToggleButton } from './components/DarkModeButton'; import { TOP_BAR_DESKTOP_HEIGHT, TOP_BAR_MOBILE_HEIGHT } from './config'; import { useRouteTitle } from './routeTitle'; interface Props { sidebarItems: Array; /** Rendered below the sidebar's brand header (the nurse shell's `ProfileSummary` identity card). */ sidebarIdentity?: ReactNode; /** Rendered in the TopBar, before the header controls (the admin/partner identity chip). */ identity?: ReactNode; /** Extra chrome actions (e.g. the notification bell) rendered next to the dark-mode toggle. */ headerActions?: ReactNode; /** * A mobile-only (` void) => ReactNode; } /** * The shared engine behind the nurse/admin/partner shells: a fixed `TopBar` showing the current * page title (`useRouteTitle`, longest-prefix over `ROUTES.*`) plus a `SideBar` that renders as a * mobile overlay and a desktop in-flow column at once (see `SideBar` for why that kills the SSR * flash). The sidebar and the main column are flex siblings in a row — native RTL flexbox puts the * sidebar at the reading-start side with no manual offset math. * @layout TopBarAndSideBarLayout */ const TopBarAndSideBarLayout: FunctionComponent> = ({ children, sidebarItems, sidebarIdentity, identity, headerActions, mobileBottomBar, }) => { const tChrome = useTranslations('routeChrome'); const tCommon = useTranslations('common'); const theme = useTheme(); const title = useRouteTitle(); const [mobileOpen, setMobileOpen] = useState(false); const anchor = theme.direction === 'rtl' ? 'right' : 'left'; const headerControls = ( {identity} {headerActions} {/* DarkModeToggleButton subscribes to useColorScheme() on its own — this layout never re-renders on a theme flip */} ); return ( setMobileOpen(true)} sx={{ display: { xs: 'inline-flex', md: 'none' } }} /> } title={title ?? ''} align="start" endNode={headerControls} /> setMobileOpen(false)} /> {children} {mobileBottomBar && {mobileBottomBar(() => setMobileOpen(true))}} ); }; export default TopBarAndSideBarLayout;