diff --git a/web/src/components/layout/workspace-sidebar-nav.tsx b/web/src/components/layout/workspace-sidebar-nav.tsx index e81a4e9dd..e5c45c1de 100644 --- a/web/src/components/layout/workspace-sidebar-nav.tsx +++ b/web/src/components/layout/workspace-sidebar-nav.tsx @@ -266,6 +266,62 @@ function NavItem({ item, activeId, onSelect, onOpenSearch, onLogout, level = 0 } ); } +function NavGroup({ group, activeId, onNavigate, onOpenSearch, onLogout }: { + group: WorkspaceNavGroup; + activeId: string; + onNavigate: () => void; + onOpenSearch: () => void; + onLogout: () => void; +}) { + const [isOpen, setIsOpen] = useState(true); + const hasActive = group.items.some((item) => item.id === activeId || (item.id === "settings" && activeId.startsWith("settings:"))); + + // 激活项所在分组自动展开,保证当前位置可见。 + useEffect(() => { + if (hasActive) setIsOpen(true); + }, [hasActive]); + + const content = ( +
+ {group.items.map((item) => ( + + ))} +
+ ); + + // 无标题分组(核心导航入口)常驻展示,不做折叠。 + if (!group.heading) { + return
{content}
; + } + + return ( +
+ +
+
{content}
+
+
+ ); +} + export function WorkspaceSidebarNav({ onNavigate, onOpenSearch }: { onNavigate: () => void; onOpenSearch: () => void }) { const { pathname } = useLocation(); const [searchParams] = useSearchParams(); @@ -325,21 +381,14 @@ export function WorkspaceSidebarNav({ onNavigate, onOpenSearch }: { onNavigate: )} > {groups.map((group, index) => ( -
- {group.heading ? {group.heading} : null} -
- {group.items.map((item) => ( - void handleLogout()} - /> - ))} -
-
+ void handleLogout()} + /> ))} diff --git a/web/src/styles/globals.css b/web/src/styles/globals.css index a2d26fc38..cec08d8e9 100644 --- a/web/src/styles/globals.css +++ b/web/src/styles/globals.css @@ -941,15 +941,44 @@ pointer-events: none; } - /* 分组标题:小号大写标签,弱化但可读 */ + /* 分组标题:小号大写标签,弱化但可读;有标题的分组支持折叠,padding/间距由折叠按钮接管 */ .app-workspace-nav-group-label { display: block; - padding: 0 10px 6px; font-size: var(--fs-label); font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: color-mix(in srgb, var(--foreground) 36%, transparent); + transition: color var(--motion-instant) ease-out; + } + + .app-workspace-nav-group-toggle { + display: flex; + width: 100%; + min-height: 28px; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 0 8px; + border-radius: var(--r-sm); + text-align: left; + color: inherit; + transition: background-color var(--motion-instant) ease-out; + } + + .app-workspace-nav-group-toggle:hover, + .app-workspace-nav-group-toggle:focus-visible { + background: var(--surface-hover); + } + + .app-workspace-nav-group-toggle:hover .app-workspace-nav-group-label, + .app-workspace-nav-group-toggle:focus-visible .app-workspace-nav-group-label { + color: color-mix(in srgb, var(--foreground) 55%, transparent); + } + + .app-workspace-nav-group-toggle:hover svg, + .app-workspace-nav-group-toggle:focus-visible svg { + color: color-mix(in srgb, var(--foreground) 70%, transparent); } /* 工作区切换器下拉浮层 */