Make the sidebar collapsible and let the knowledge graph go full screen

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Iqbal
2026-10-29 10:14:34 +07:00
parent bcd7964e0b
commit 278c66897a
5 changed files with 104 additions and 11 deletions

View File

@@ -128,6 +128,12 @@ function OrgSwitcher() {
)
}
// Remembered per browser; storage can throw in private windows, so it falls back to expanded.
const COLLAPSED_KEY = 'sidebar-collapsed'
function readCollapsed() {
try { return localStorage.getItem(COLLAPSED_KEY) === '1' } catch { return false }
}
export function AppLayout() {
useLang()
const [help, setHelp] = useState(false)
@@ -138,16 +144,22 @@ export function AppLayout() {
// Nav groups stay closed unless they hold the current page; a click on the label overrides that until the next route.
const [toggled, setToggled] = useState<Partial<Record<Key, boolean>>>({})
useEffect(() => setToggled({}), [pathname])
const [collapsed, setCollapsed] = useState(readCollapsed)
const toggleCollapsed = () => setCollapsed(c => {
try { localStorage.setItem(COLLAPSED_KEY, c ? '0' : '1') } catch { /* keep it for this visit only */ }
return !c
})
const name = user?.fullName || user?.primaryEmailAddress?.emailAddress || '-'
return (
<div className="app">
<div className={collapsed ? 'app sidebar-collapsed' : 'app'}>
<aside className="sidebar">
<NavLink to="/production"><Brand /></NavLink>
<OrgSwitcher />
<nav aria-label={t('layout.mainNav')}>
{NAV_GROUPS.map(g => {
const open = toggled[g.label] ?? g.items.some(n => pathname.startsWith(n.to))
// The icon rail has no group labels, so every item shows.
const open = collapsed || (toggled[g.label] ?? g.items.some(n => pathname.startsWith(n.to)))
return [
<button key={g.label} type="button" className="nav-label" aria-expanded={open} aria-controls={`nav-${g.label}`}
onClick={() => setToggled(s => ({ ...s, [g.label]: !open }))}>
@@ -155,28 +167,36 @@ export function AppLayout() {
</button>,
<div key={`${g.label}-items`} id={`nav-${g.label}`} className={open ? 'nav-items' : 'nav-items closed'}>
{g.items.map(n => (
<NavLink key={n.to} to={n.to} className={({ isActive }) => (isActive ? 'active' : undefined)}>
<Icon name={n.icon} />{t(n.label)}
<NavLink key={n.to} to={n.to} className={({ isActive }) => (isActive ? 'active' : undefined)}
title={collapsed ? t(n.label) : undefined}>
<Icon name={n.icon} /><span className="nav-text">{t(n.label)}</span>
</NavLink>
))}
</div>,
]
})}
</nav>
<div className="sidebar-help">
{collapsed ? (
<button type="button" className="icon-btn sidebar-help-icon" title={t('help.open')} aria-label={t('help.open')}
onClick={() => setHelp(true)}><Icon name="info" /></button>
) : <div className="sidebar-help">
<Icon name="info" />
<b>{t('help.boxTitle')}</b>
<p>{t('help.boxBody')}</p>
<button type="button" onClick={() => setHelp(true)}>{t('help.open')} <Icon name="arrow-right" /></button>
</div>
</div>}
<div className="profile">
<span className="avatar">{name.charAt(0).toUpperCase()}</span>
<span className="avatar" title={collapsed ? name : undefined}>{name.charAt(0).toUpperCase()}</span>
<div><b>{name}</b><small>{user?.primaryEmailAddress?.emailAddress}</small></div>
<button className="icon-btn" aria-label={t('layout.signOut')} onClick={() => signOut({ redirectUrl: '/' })}><Icon name="logout" /></button>
</div>
</aside>
<div className="workspace-main">
<header className="topbar">
<button type="button" className="icon-btn sidebar-toggle" aria-pressed={collapsed}
aria-label={t(collapsed ? 'layout.expandSidebar' : 'layout.collapseSidebar')}
title={t(collapsed ? 'layout.expandSidebar' : 'layout.collapseSidebar')}
onClick={toggleCollapsed}><Icon name="sidebar" /></button>
<div className="breadcrumb">{t('layout.breadcrumb')} <span>/</span> <b>{current ? t(current.label) : ''}</b></div>
<div className="topbar-right">
<span className="demo-indicator"><i></i> {t('layout.engine')}</span>