feat: workspace tabs as side-by-side segmented control in sidebar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-19 20:40:12 +02:00
parent 315733a423
commit 86051c6f01
+23 -21
View File
@@ -147,7 +147,6 @@ const SIDEBAR_BG = '#0f1923'
const DIVIDER_COLOR = 'rgba(255,255,255,0.08)' const DIVIDER_COLOR = 'rgba(255,255,255,0.08)'
const TEXT_MUTED = '#94a3b8' const TEXT_MUTED = '#94a3b8'
const TEXT_WHITE = '#ffffff' const TEXT_WHITE = '#ffffff'
const ACTIVE_BG = 'rgba(255,255,255,0.1)'
const NAV_ACTIVE_BG = 'rgba(255,255,255,0.12)' const NAV_ACTIVE_BG = 'rgba(255,255,255,0.12)'
const NAV_HOVER_BG = 'rgba(255,255,255,0.06)' const NAV_HOVER_BG = 'rgba(255,255,255,0.06)'
@@ -173,7 +172,7 @@ function Sidebar({
orgName, orgName,
}: SidebarProps) { }: SidebarProps) {
const config = WORKSPACE_CONFIG[activeWorkspace] const config = WORKSPACE_CONFIG[activeWorkspace]
const width = collapsed ? 60 : 240 const width = collapsed ? 60 : 264
const visibleWorkspaces = WORKSPACE_ORDER.filter((ws) => allowedWorkspaces.includes(ws)) const visibleWorkspaces = WORKSPACE_ORDER.filter((ws) => allowedWorkspaces.includes(ws))
return ( return (
@@ -234,16 +233,16 @@ function Sidebar({
)} )}
</Box> </Box>
{/* Workspace tabs */} {/* Workspace tabs — side-by-side segmented control */}
<Box <Box
sx={{ sx={{
borderBottom: `1px solid ${DIVIDER_COLOR}`, borderBottom: `1px solid ${DIVIDER_COLOR}`,
py: 0.5, display: 'grid',
px: 0.5, gridTemplateColumns: collapsed ? '1fr' : `repeat(${visibleWorkspaces.length}, 1fr)`,
flexShrink: 0, flexShrink: 0,
}} }}
> >
{visibleWorkspaces.map((ws) => { {visibleWorkspaces.map((ws, idx) => {
const wsConfig = WORKSPACE_CONFIG[ws] const wsConfig = WORKSPACE_CONFIG[ws]
const Icon = wsConfig.icon const Icon = wsConfig.icon
const isActive = ws === activeWorkspace const isActive = ws === activeWorkspace
@@ -253,29 +252,32 @@ function Sidebar({
onClick={() => { onWorkspaceClick(ws); onClose?.() }} onClick={() => { onWorkspaceClick(ws); onClose?.() }}
sx={{ sx={{
display: 'flex', display: 'flex',
flexDirection: collapsed ? 'row' : 'column',
alignItems: 'center', alignItems: 'center',
gap: 1.25, justifyContent: 'center',
px: collapsed ? 0 : 1.5, gap: collapsed ? 0 : 0.5,
py: 0.75, py: collapsed ? 1 : 1.25,
borderRadius: 1,
cursor: 'pointer', cursor: 'pointer',
justifyContent: collapsed ? 'center' : 'flex-start', backgroundColor: isActive ? wsConfig.chipColor : 'transparent',
backgroundColor: isActive ? ACTIVE_BG : 'transparent', borderRight: !collapsed && idx < visibleWorkspaces.length - 1
'&:hover': { ? `1px solid ${DIVIDER_COLOR}`
backgroundColor: isActive ? ACTIVE_BG : NAV_HOVER_BG, : 'none',
},
transition: 'background-color 0.15s ease', transition: 'background-color 0.15s ease',
'&:hover': {
backgroundColor: isActive ? wsConfig.chipColor : NAV_HOVER_BG,
},
}} }}
> >
<Icon size={16} color={isActive ? TEXT_WHITE : TEXT_MUTED} /> <Icon size={15} color={isActive ? TEXT_WHITE : TEXT_MUTED} />
{!collapsed && ( {!collapsed && (
<Typography <Typography
variant="body2" variant="caption"
sx={{ sx={{
color: isActive ? TEXT_WHITE : TEXT_MUTED, color: isActive ? TEXT_WHITE : TEXT_MUTED,
fontWeight: isActive ? 600 : 400, fontWeight: isActive ? 700 : 400,
fontSize: '0.8125rem', fontSize: '0.6875rem',
whiteSpace: 'nowrap', lineHeight: 1,
letterSpacing: 0.2,
}} }}
> >
{wsConfig.label} {wsConfig.label}
@@ -557,7 +559,7 @@ export function AppShell() {
onClose={() => setMobileOpen(false)} onClose={() => setMobileOpen(false)}
variant="temporary" variant="temporary"
ModalProps={{ keepMounted: true }} ModalProps={{ keepMounted: true }}
slotProps={{ paper: { sx: { width: 240, bgcolor: 'transparent', boxShadow: 'none' } } }} slotProps={{ paper: { sx: { width: 264, bgcolor: 'transparent', boxShadow: 'none' } } }}
> >
{sidebarContent} {sidebarContent}
</Drawer> </Drawer>