import { PureComponent, lazy, Suspense } from 'react'; import Box from '@mui/material/Box'; import List from '@mui/material/List'; import ListItemButton from '@mui/material/ListItemButton'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import ButtonBase from '@mui/material/ButtonBase'; import Typography from '@mui/material/Typography'; import { Outlet, useLocation, useNavigate } from 'react-router-dom'; import UserMenu from './UserMenu'; import HelpBadge from './HelpBadge'; import { NAV_ITEMS, SUB_NAV, resolveActiveNav, resolveActiveSub } from '../nav'; import { artikelQueryPath, buildSearchUrl } from './article/artikelQuery'; import { defaultCategoryPath } from './category/categoryRoute'; import { navigationAutomat } from '../automats'; import { NAV_ITEM_ACTIVE_ACCENT, NAV_SUB_ACTIVE, NAV_SUB_ACTIVE_HOVER, NAV_SURFACE, } from '../colors'; import { ChatFab, ChatDrawer } from './chat'; import { PinnedAppletFabs, PinnedAppletDialog } from './applet'; const BreakpointOverlay = 0 && import.meta.env.DEV ? lazy(() => import('./BreakpointOverlay')) : null; const SIDEBAR_WIDTH = 144; const SUB_NAV_WIDTH = 88; class AppSidebar extends PureComponent { constructor(props) { super(props); this.state = { lastDeliveryPath: navigationAutomat.state.lastDeliveryPath, lastArticleSku: navigationAutomat.state.lastArticleSku, lastArticleSearchPath: navigationAutomat.state.lastArticleSearchPath, lastSearchTerm: navigationAutomat.state.lastSearchTerm, }; } componentDidMount() { this.unsub = navigationAutomat.subscribe(this, (state) => ({ lastDeliveryPath: state.lastDeliveryPath, lastArticleSku: state.lastArticleSku, lastArticleSearchPath: state.lastArticleSearchPath, lastSearchTerm: state.lastSearchTerm, })); } componentWillUnmount() { this.unsub?.(); } render() { const { activeNav, navigate } = this.props; const { lastDeliveryPath, lastArticleSku, lastArticleSearchPath, lastSearchTerm, } = this.state; return ( ({ width: { xs: '100%', lg: SIDEBAR_WIDTH }, flexShrink: 0, display: 'flex', flexDirection: { xs: 'row', lg: 'column' }, alignItems: { xs: 'center', lg: 'stretch' }, backgroundColor: theme.nav.backgroundColor || NAV_SURFACE, background: theme.nav.background, position: 'sticky', top: 0, zIndex: theme.zIndex.appBar || 1100, alignSelf: 'flex-start', height: { xs: 52, sm: 56, lg: 'var(--df-vh, 100vh)' }, px: { xs: 1, sm: 1.5, lg: 0 }, boxSizing: 'border-box', boxShadow: { xs: '0 2px 8px rgba(0,0,0,0.18)', lg: 'none' }, })} > {NAV_ITEMS.map(({ path, label, help, icon: Icon }) => { const active = path === activeNav; return ( e.preventDefault()} onClick={() => { if (path === '/artikel') { if (lastArticleSku) { navigate(`/artikel/${lastArticleSku}`); } else if (lastArticleSearchPath) { navigate(lastArticleSearchPath); } else if (lastSearchTerm) { navigate(buildSearchUrl(lastSearchTerm)); } else { navigate(artikelQueryPath()); } } else if (path === '/lieferung') { if (lastDeliveryPath) { navigate(lastDeliveryPath); } else { navigate(path); } } else { navigate(path); } }} sx={(theme) => ({ borderRadius: { xs: 1, lg: 0 }, width: { xs: 'auto', lg: '100%' }, height: { xs: '100%', lg: 'auto' }, px: { xs: active ? 1.25 : 1, sm: active ? 1.5 : 1.25, md: 1.5, lg: 1.25 }, py: { xs: 0.75, sm: 1, lg: 1.25 }, borderLeft: { xs: 'none', lg: '3px solid transparent' }, borderBottom: { xs: '3px solid transparent', lg: 'none' }, color: theme.nav.item, whiteSpace: 'nowrap', flexShrink: 0, '& .MuiListItemIcon-root': { color: 'inherit', minWidth: { xs: 'auto', lg: 32 }, mr: active ? { xs: 0.75, lg: 0 } : { xs: 0, md: 0.75, lg: 0 }, justifyContent: { xs: 'center', lg: 'flex-start' }, }, '&:hover': { bgcolor: theme.nav.itemHover, color: theme.nav.itemHoverText, }, '&.Mui-selected': { bgcolor: theme.nav.itemActive, color: theme.nav.itemActiveText, fontWeight: 600, borderLeftColor: { xs: 'transparent', lg: theme.nav.itemActiveAccent }, borderBottomColor: { xs: theme.nav.itemActiveAccent, lg: 'transparent' }, '&:hover': { bgcolor: theme.nav.itemActive, color: theme.nav.itemActiveText, }, }, })} > ); })} ); } } class AppSubNav extends PureComponent { constructor(props) { super(props); this.state = { lastArticleSearchPath: navigationAutomat.state.lastArticleSearchPath, lastSearchTerm: navigationAutomat.state.lastSearchTerm, lastCategoryPath: navigationAutomat.state.lastCategoryPath, lastCategorySlug: navigationAutomat.state.lastCategorySlug, }; } componentDidMount() { this.unsub = navigationAutomat.subscribe(this, (state) => ({ lastArticleSearchPath: state.lastArticleSearchPath, lastSearchTerm: state.lastSearchTerm, lastCategoryPath: state.lastCategoryPath, lastCategorySlug: state.lastCategorySlug, })); } componentWillUnmount() { this.unsub?.(); } render() { const { activeNav, activeSub, navigate } = this.props; const { lastArticleSearchPath, lastSearchTerm, lastCategoryPath, lastCategorySlug, } = this.state; const subNav = activeNav ? SUB_NAV[activeNav] : null; if (!subNav?.length) { return ( ); } return ( ({ width: { xs: '100%', sm: 72, md: SUB_NAV_WIDTH }, flexShrink: 0, display: 'flex', flexDirection: { xs: 'row', sm: 'column' }, alignItems: 'center', gap: { xs: 1, sm: 1.5 }, position: 'sticky', top: { xs: 52, sm: 56, lg: 0 }, zIndex: { xs: 1090, sm: 'auto' }, alignSelf: 'flex-start', height: { xs: 'auto', sm: 'calc(var(--df-vh, 100vh) - 56px)', lg: 'var(--df-vh, 100vh)', }, py: { xs: 0.75, sm: 2, lg: 4 }, px: { xs: 1.5, sm: 0 }, ml: { xs: 0, sm: 2, lg: 3 }, mr: { xs: 0, sm: 1.5, lg: 2 }, overflowX: { xs: 'auto', sm: 'visible' }, scrollbarWidth: 'none', '&::-webkit-scrollbar': { display: 'none' }, bgcolor: { xs: theme.nav?.mainBackground || 'background.default', sm: 'transparent' }, borderBottom: { xs: '1px solid', sm: 'none' }, borderColor: { xs: 'divider', sm: 'transparent' }, boxSizing: 'border-box', })} > {subNav.map(({ segment, label, title, help, icon: Icon }) => { const active = segment === activeSub; return ( e.preventDefault()} onClick={() => { if (!segment && activeNav === '/artikel') { const target = lastArticleSearchPath || (lastSearchTerm ? buildSearchUrl(lastSearchTerm) : null) || artikelQueryPath(); navigate(target); return; } if (segment === 'kategorien') { const target = lastCategoryPath || (lastCategorySlug ? `/kategorien/${lastCategorySlug}` : null) || defaultCategoryPath(); navigate(target); return; } navigate(segment ? `${activeNav}/${segment}` : activeNav); }} sx={(theme) => ({ width: { xs: 'auto', sm: 72, md: SUB_NAV_WIDTH }, py: { xs: 0.5, sm: 1 }, px: { xs: active ? 1.25 : 0.75, sm: 0.5 }, borderRadius: 2, display: 'flex', flexDirection: { xs: 'row', sm: 'column' }, alignItems: 'center', justifyContent: 'center', gap: { xs: active ? 0.75 : 0, sm: 0.5 }, flexShrink: 0, whiteSpace: 'nowrap', color: active ? theme.nav?.itemActiveAccent || NAV_ITEM_ACTIVE_ACCENT : theme.palette.text.secondary, bgcolor: active ? theme.nav?.subActive || NAV_SUB_ACTIVE : 'transparent', '&:hover': { bgcolor: active ? theme.nav?.subActiveHover || NAV_SUB_ACTIVE_HOVER : theme.palette.action.hover, color: active ? theme.nav?.itemActiveAccent || NAV_ITEM_ACTIVE_ACCENT : theme.palette.primary.main, }, })} > {label ? ( {label} ) : null} ); })} ); } } export default function AppShell() { const navigate = useNavigate(); const { pathname } = useLocation(); const activeNav = resolveActiveNav(pathname); const activeSub = activeNav ? resolveActiveSub(pathname, activeNav) : null; return ( ({ flexGrow: 1, display: 'flex', flexDirection: 'column', py: { xs: 2, md: 3, lg: 4 }, pr: { xs: 1.5, sm: 2, lg: 3 }, pl: { xs: 1.5, sm: 2, lg: 0 }, bgcolor: theme.nav.mainBackground, minWidth: 0, })} > {BreakpointOverlay && ( )} ); }