fix(sidebar): 增强引擎切换器视觉发现性 (#228, #235)

用户反馈"翻遍界面"找不到引擎切换入口,原切换器样式与 sidebar 其他
元素区分度低,容易被当作静态装饰忽略。

改进:
- 切换器上方新增 "ENGINE" section 标签,明确告诉用户这是引擎选择
- 按钮背景改为 accent 色调混合,提示可交互
- 图标使用 accent 色,尺寸从 14px 增大到 16px
- chevron 透明度 0.4 → 0.75,尺寸 12px → 14px
- 打开下拉时 chevron 旋转 180°(aria-expanded 驱动)
- 按钮加 title tooltip 和 aria-haspopup / aria-expanded(可访问性)
- 字体从 xs 增到 sm,字体权重 500
- :active 时轻微下沉,给出点击反馈

翻译覆盖 11 种语言。折叠侧边栏时仍然隐藏切换器(保持原行为)。
This commit is contained in:
晴天
2026-04-20 03:07:10 +08:00
parent 024e4c9517
commit 97e2fb507b
3 changed files with 43 additions and 16 deletions

View File

@@ -112,10 +112,11 @@ function _renderEngineSwitcher() {
const active = getActiveEngine()
if (!active) return ''
return `<div class="engine-switcher" id="engine-switcher">
<button class="engine-current" id="btn-engine-toggle">
<div class="engine-switcher-label">${_escSidebar(t('engine.switcherSectionLabel'))}</div>
<button class="engine-current" id="btn-engine-toggle" title="${_escSidebar(t('engine.switcherTooltip'))}" aria-haspopup="listbox" aria-expanded="false">
<span class="engine-icon">${active.icon || ''}</span>
<span class="engine-label">${_escSidebar(active.name)}</span>
<svg class="engine-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="12" height="12"><path d="M6 9l6 6 6-6"/></svg>
<svg class="engine-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><path d="M6 9l6 6 6-6"/></svg>
</button>
<div class="engine-dropdown" id="engine-dropdown">
${engines.map(e => `<div class="engine-option${e.id === active.id ? ' active' : ''}" data-engine="${e.id}">
@@ -130,13 +131,21 @@ function _renderEngineSwitcher() {
function _closeEngineDropdown() {
const dd = document.getElementById('engine-dropdown')
if (dd) dd.classList.remove('open')
const btn = document.getElementById('btn-engine-toggle')
if (btn) btn.setAttribute('aria-expanded', 'false')
}
function _toggleEngineDropdown() {
const dd = document.getElementById('engine-dropdown')
if (!dd) return
if (dd.classList.contains('open')) { dd.classList.remove('open'); return }
const btn = document.getElementById('btn-engine-toggle')
if (dd.classList.contains('open')) {
dd.classList.remove('open')
if (btn) btn.setAttribute('aria-expanded', 'false')
return
}
dd.classList.add('open')
if (btn) btn.setAttribute('aria-expanded', 'true')
}
const LS_SIDEBAR_COLLAPSED = 'clawpanel_sidebar_collapsed'

View File

@@ -3,6 +3,8 @@ import { _ } from '../helper.js'
export default {
switchedTo: _('已切换到 {name} 模式', 'Switched to {name} mode', '已切換到 {name} 模式', '{name} モードに切り替えました', '{name} 모드로 전환됨'),
switchFailed: _('引擎切换失败,请稍后重试', 'Engine switch failed, please try again later', '引擎切換失敗,請稍後重試', 'エンジンの切り替えに失敗しました。後でもう一度お試しください', '엔진 전환에 실패했습니다. 잠시 후 다시 시도해 주세요'),
switcherSectionLabel: _('引擎', 'Engine', '引擎', 'エンジン', '엔진', 'Động cơ', 'Motor', 'Motor', 'Движок', 'Moteur', 'Engine'),
switcherTooltip: _('点击切换引擎OpenClaw / Hermes Agent', 'Click to switch engine (OpenClaw / Hermes Agent)', '點擊切換引擎OpenClaw / Hermes Agent', 'クリックしてエンジンを切り替え (OpenClaw / Hermes Agent)', '엔진 전환하려면 클릭 (OpenClaw / Hermes Agent)', 'Nhấp để chuyển đổi engine (OpenClaw / Hermes Agent)', 'Haga clic para cambiar de motor (OpenClaw / Hermes Agent)', 'Clique para alternar o motor (OpenClaw / Hermes Agent)', 'Нажмите, чтобы переключить движок (OpenClaw / Hermes Agent)', 'Cliquez pour changer de moteur (OpenClaw / Hermes Agent)', 'Klicken, um die Engine zu wechseln (OpenClaw / Hermes Agent)'),
hermesSetupDesc: _('安装并配置 Hermes Agent', 'Install and configure Hermes Agent', '安裝並配置 Hermes Agent'),
hermesPhaseClickHint: _('点击可返回此步骤', 'Click to go back to this step', '點擊可返回此步驟', 'このステップに戻るにはクリック', '이 단계로 돌아가려면 클릭'),
hermesSetupIntro: _(

View File

@@ -85,36 +85,49 @@
/* === Engine Switcher === */
.engine-switcher {
padding: var(--space-xs) var(--space-sm);
padding: var(--space-xs) var(--space-sm) var(--space-sm);
border-bottom: 1px solid var(--border-secondary);
position: relative;
}
.engine-switcher-label {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-tertiary);
padding: 2px 4px 4px;
}
.engine-current {
display: flex;
align-items: center;
gap: 6px;
gap: 8px;
width: 100%;
padding: 5px 10px;
border: 1px solid var(--border-secondary);
padding: 7px 10px;
border: 1px solid color-mix(in srgb, var(--accent) 25%, var(--border-secondary));
border-radius: var(--radius-sm);
background: var(--bg-tertiary);
color: var(--text-secondary);
font-size: var(--font-size-xs);
background: color-mix(in srgb, var(--accent) 8%, var(--bg-tertiary));
color: var(--text-primary);
font-size: var(--font-size-sm);
font-weight: 500;
cursor: pointer;
transition: border-color 0.15s, background 0.15s;
transition: border-color 0.15s, background 0.15s, transform 0.1s;
}
.engine-current:hover {
border-color: var(--accent);
background: var(--bg-secondary);
background: color-mix(in srgb, var(--accent) 14%, var(--bg-secondary));
}
.engine-current:active {
transform: translateY(1px);
}
.engine-icon {
flex-shrink: 0;
display: flex;
align-items: center;
color: var(--accent);
}
.engine-icon svg {
width: 14px;
height: 14px;
width: 16px;
height: 16px;
}
.engine-label {
flex: 1;
@@ -122,11 +135,14 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 500;
}
.engine-chevron {
flex-shrink: 0;
opacity: 0.4;
opacity: 0.75;
transition: transform 0.15s;
}
.engine-current[aria-expanded="true"] .engine-chevron {
transform: rotate(180deg);
}
.engine-dropdown {
display: none;