From cbe2f8fbdb5af4bfa468388e6093c05a340db81d Mon Sep 17 00:00:00 2001 From: OpenClaw Agent Date: Sun, 15 Mar 2026 19:28:43 +0000 Subject: [PATCH] feat: collapsible desktop sidebar --- src/components/sidebar.js | 28 +++++++++++++++++++++ src/style/layout.css | 53 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/components/sidebar.js b/src/components/sidebar.js index 9175362..8f65066 100644 --- a/src/components/sidebar.js +++ b/src/components/sidebar.js @@ -108,6 +108,22 @@ function _checkMultiInstances(el) { }).catch(() => {}) } +const LS_SIDEBAR_COLLAPSED = 'clawpanel_sidebar_collapsed' + +function _isDesktopSidebarCollapsed() { + try { return localStorage.getItem(LS_SIDEBAR_COLLAPSED) === '1' } catch { return false } +} + +function _setDesktopSidebarCollapsed(collapsed) { + try { localStorage.setItem(LS_SIDEBAR_COLLAPSED, collapsed ? '1' : '0') } catch {} + const sidebar = document.getElementById('sidebar') + if (sidebar) { + sidebar.classList.toggle('sidebar-collapsed', !!collapsed) + } + const btn = document.getElementById('btn-sidebar-collapse') + if (btn) btn.textContent = collapsed ? '»' : '«' +} + export function renderSidebar(el) { const current = getCurrentRoute() @@ -115,12 +131,14 @@ export function renderSidebar(el) { const isLocal = inst.type === 'local' const showSwitcher = !isLocal || _hasMultipleInstances + const collapsed = _isDesktopSidebarCollapsed() let html = ` ${showSwitcher ? `
@@ -172,6 +190,9 @@ export function renderSidebar(el) { el.innerHTML = html + // 应用折叠态(桌面端) + _setDesktopSidebarCollapsed(collapsed) + // 首次渲染时异步检测多实例 if (!_delegated) _checkMultiInstances(el) @@ -191,6 +212,13 @@ export function renderSidebar(el) { _closeMobileSidebar() return } + // 侧边栏折叠 + const collapseBtn = e.target.closest('#btn-sidebar-collapse') + if (collapseBtn) { + _setDesktopSidebarCollapsed(!_isDesktopSidebarCollapsed()) + // 不需要整体重渲染 + return + } // 主题切换 const themeBtn = e.target.closest('#btn-theme-toggle') if (themeBtn) { diff --git a/src/style/layout.css b/src/style/layout.css index 4b9c800..77a84c3 100644 --- a/src/style/layout.css +++ b/src/style/layout.css @@ -11,6 +11,38 @@ overflow: hidden; } +/* 桌面端折叠态 */ +#sidebar.sidebar-collapsed { + width: var(--sidebar-collapsed); +} +#sidebar.sidebar-collapsed .sidebar-title, +#sidebar.sidebar-collapsed .nav-section-title, +#sidebar.sidebar-collapsed .nav-item span, +#sidebar.sidebar-collapsed .sidebar-meta, +#sidebar.sidebar-collapsed .instance-switcher { + display: none; +} +#sidebar.sidebar-collapsed .sidebar-header { + justify-content: center; +} +#sidebar.sidebar-collapsed .nav-item { + justify-content: center; + padding-left: 0; + padding-right: 0; +} +#sidebar.sidebar-collapsed .nav-item svg { + opacity: 0.9; +} +#sidebar.sidebar-collapsed .sidebar-footer { + padding: var(--space-sm) 6px; +} +#sidebar.sidebar-collapsed #btn-theme-toggle { + justify-content: center; +} +#sidebar.sidebar-collapsed #btn-theme-toggle span { + display: none; +} + .sidebar-header { padding: var(--space-lg) var(--space-lg); display: flex; @@ -475,9 +507,25 @@ font-weight: 600; color: var(--text-primary); } +.sidebar-collapse-btn { + margin-left: auto; + font-size: 14px; + color: var(--text-secondary); + background: none; + border: 1px solid var(--border-secondary); + cursor: pointer; + padding: 2px 8px; + border-radius: 8px; + line-height: 1.2; +} +.sidebar-collapse-btn:hover { + color: var(--text-primary); + background: var(--bg-glass-hover); +} + .sidebar-close-btn { display: none; - margin-left: auto; + margin-left: 6px; font-size: 22px; color: var(--text-tertiary); background: none; @@ -511,6 +559,9 @@ .mobile-topbar { display: flex; } + .sidebar-collapse-btn { + display: none; + } .sidebar-close-btn { display: block; }