diff --git a/docs/PROGRESS_LOG.md b/docs/PROGRESS_LOG.md index 996a9ef..cf6382d 100644 --- a/docs/PROGRESS_LOG.md +++ b/docs/PROGRESS_LOG.md @@ -146,6 +146,15 @@ - **commit**: ======================================================= --> +### 2026-06-30 · v0.6 工具栏图标换成线性图标 + hover tooltip + +- **状态**: `[x]` DONE。web typecheck 干净 · `build:web` OK · 真浏览器验证通过。 +- **需求**(用户): 工具栏那些 emoji 图标(🔍⚙▦🕘⌨🔗📱)不好看,换成符合主题色的图标;hover 时显示图标含义。 +- **改动**(纯前端): `public/icons.ts` 加 7 个 **lucide 线性图标**(MIT,`stroke=currentColor`,脚本从 unpkg 抓取生成):`ICON_SEARCH/SETTINGS/DASHBOARD/HISTORY/KEYBOARD/SHARE/DEVICE`。7 个工具栏模块(search/settings/dashboard/history/shortcuts/share/qr)把 `textContent=emoji` 换成 `innerHTML=ICON_*` + `dataset.tip`(tooltip 文案)+ `aria-label`。`style.css` `.toolbtn`:`svg` 18px、默认 `--text-dim`、hover 变 **Amber**;新增 `[data-tip]::after` **CSS hover tooltip**(右对齐避免右边缘裁切)。 +- **验证**: `npx tsc -p tsconfig.web.json` 干净;`build:web` OK;bundle 内 0 toolbar emoji;真浏览器:7 按钮均为线性图标(`--text-dim` 色)、hover 变琥珀 + tooltip(实测 "All sessions" 等显示)。 +- **遗留**: share/qr 的 aria-label 仍是旧描述(无害,屏幕阅读器友好);可选统一。 +- **commit**: (本次提交) + ### 2026-06-30 · v0.6 "New session" 改为网格卡片(去掉别扭的大按钮) - **状态**: `[x]` DONE。web typecheck 干净 · `build:web` OK · 真浏览器验证通过。 diff --git a/public/dashboard.ts b/public/dashboard.ts index 65af614..ba9fb36 100644 --- a/public/dashboard.ts +++ b/public/dashboard.ts @@ -6,6 +6,8 @@ * open it re-renders every second so statuses stay live. */ +import { ICON_DASHBOARD } from './icons.js' + export interface TabInfo { idx: number title: string @@ -86,8 +88,9 @@ export function mountDashboard(toolbar: HTMLElement, hooks: DashboardHooks): voi const btn = document.createElement('button') btn.className = 'toolbtn' - btn.textContent = '▦' - btn.title = 'All sessions' + btn.innerHTML = ICON_DASHBOARD + btn.dataset.tip = 'All sessions' + btn.setAttribute('aria-label', 'All sessions') btn.setAttribute('aria-label', 'All sessions') btn.addEventListener('click', () => (overlay.style.display === 'none' ? open() : hide())) toolbar.appendChild(btn) diff --git a/public/history.ts b/public/history.ts index 0955220..66c0b02 100644 --- a/public/history.ts +++ b/public/history.ts @@ -6,6 +6,8 @@ * `claude --resume `. */ +import { ICON_HISTORY } from './icons.js' + export interface HistoryHooks { resume: (cwd: string, id: string) => void } @@ -98,8 +100,9 @@ export function mountHistory(toolbar: HTMLElement, hooks: HistoryHooks): void { const toggle = document.createElement('button') toggle.className = 'toolbtn' - toggle.textContent = '🕘' - toggle.title = 'Resume a past Claude session' + toggle.innerHTML = ICON_HISTORY + toggle.dataset.tip = 'Session history' + toggle.setAttribute('aria-label', 'Session history') toggle.setAttribute('aria-label', 'Session history') toggle.addEventListener('click', () => { if (overlay.style.display === 'none') void open() diff --git a/public/icons.ts b/public/icons.ts index 8dcfd96..d5e63ca 100644 --- a/public/icons.ts +++ b/public/icons.ts @@ -25,3 +25,21 @@ export const CODEX_LOGO = logoSvg( export const VSCODE_LOGO = logoSvg( 'M23.15 2.587L18.21.21a1.494 1.494 0 0 0-1.705.29l-9.46 8.63-4.12-3.128a.999.999 0 0 0-1.276.057L.327 7.261A1 1 0 0 0 .326 8.74L3.899 12 .326 15.26a1 1 0 0 0 .001 1.479L1.65 17.94a.999.999 0 0 0 1.276.057l4.12-3.128 9.46 8.63a1.492 1.492 0 0 0 1.704.29l4.942-2.377A1.5 1.5 0 0 0 24 20.06V3.939a1.5 1.5 0 0 0-.85-1.352zm-5.146 14.861L10.826 12l7.178-5.448v10.896z', ) + +/* ── UI line icons (lucide, MIT) — stroke=currentColor; coloured by CSS ── */ +function lineIcon(inner: string): string { + return ( + '' + ) +} + +export const ICON_SEARCH = lineIcon(' ') +export const ICON_SETTINGS = lineIcon(' ') +export const ICON_DASHBOARD = lineIcon(' ') +export const ICON_HISTORY = lineIcon(' ') +export const ICON_KEYBOARD = lineIcon(' ') +export const ICON_SHARE = lineIcon(' ') +export const ICON_DEVICE = lineIcon(' ') diff --git a/public/qr.ts b/public/qr.ts index 7995cea..c38fc49 100644 --- a/public/qr.ts +++ b/public/qr.ts @@ -7,6 +7,7 @@ */ import QRCode from 'qrcode' +import { ICON_DEVICE } from './icons.js' export function mountQrConnect(toolbar: HTMLElement): void { const modal = document.createElement('div') @@ -55,8 +56,9 @@ export function mountQrConnect(toolbar: HTMLElement): void { const toggle = document.createElement('button') toggle.className = 'toolbtn' - toggle.textContent = '📱' - toggle.title = 'Connect another device (QR)' + toggle.innerHTML = ICON_DEVICE + toggle.dataset.tip = 'Connect a device' + toggle.setAttribute('aria-label', 'Connect a device') toggle.setAttribute('aria-label', 'Connect another device') toggle.addEventListener('click', open) toolbar.appendChild(toggle) diff --git a/public/search.ts b/public/search.ts index a3fb9aa..d2a406e 100644 --- a/public/search.ts +++ b/public/search.ts @@ -10,6 +10,8 @@ export interface SearchHooks { clear: () => void } +import { ICON_SEARCH } from './icons.js' + function makeBtn(label: string, title: string): HTMLButtonElement { const b = document.createElement('button') b.textContent = label @@ -58,8 +60,11 @@ export function mountSearch(toolbar: HTMLElement, hooks: SearchHooks): void { next.addEventListener('click', () => hooks.find(input.value, 'next')) close.addEventListener('click', hide) - const toggle = makeBtn('🔍', 'Search scrollback') + const toggle = makeBtn('', '') toggle.className = 'toolbtn' + toggle.innerHTML = ICON_SEARCH + toggle.dataset.tip = 'Search' + toggle.setAttribute('aria-label', 'Search') toggle.addEventListener('click', () => (box.style.display === 'none' ? open() : hide())) toolbar.appendChild(toggle) } diff --git a/public/settings.ts b/public/settings.ts index 0c6efac..6ccd29e 100644 --- a/public/settings.ts +++ b/public/settings.ts @@ -6,6 +6,7 @@ */ import type { ITheme } from '@xterm/xterm' +import { ICON_SETTINGS } from './icons.js' export interface Settings { theme: string @@ -118,8 +119,9 @@ export function mountSettings( const toggle = document.createElement('button') toggle.className = 'toolbtn' - toggle.textContent = '⚙' - toggle.title = 'Settings (theme, font)' + toggle.innerHTML = ICON_SETTINGS + toggle.dataset.tip = 'Settings' + toggle.setAttribute('aria-label', 'Settings') toggle.setAttribute('aria-label', 'Settings') toggle.addEventListener('click', () => { if (panel.style.display === 'none') { diff --git a/public/share.ts b/public/share.ts index f64f05a..816a8a9 100644 --- a/public/share.ts +++ b/public/share.ts @@ -8,6 +8,7 @@ */ import QRCode from 'qrcode' +import { ICON_SHARE } from './icons.js' export function mountShareSession(toolbar: HTMLElement, getActiveId: () => string | null): void { const modal = document.createElement('div') @@ -64,8 +65,9 @@ export function mountShareSession(toolbar: HTMLElement, getActiveId: () => strin const toggle = document.createElement('button') toggle.className = 'toolbtn' - toggle.textContent = '🔗' - toggle.title = 'Share this session (mirror it on another device)' + toggle.innerHTML = ICON_SHARE + toggle.dataset.tip = 'Share session' + toggle.setAttribute('aria-label', 'Share session') toggle.setAttribute('aria-label', 'Share this session') toggle.addEventListener('click', open) toolbar.appendChild(toggle) diff --git a/public/shortcuts.ts b/public/shortcuts.ts index f10e0eb..06f1b37 100644 --- a/public/shortcuts.ts +++ b/public/shortcuts.ts @@ -6,6 +6,8 @@ * are tagged "键栏". Source: official Claude Code interactive-mode docs. */ +import { ICON_KEYBOARD } from './icons.js' + interface Shortcut { keys: string desc: string @@ -132,8 +134,9 @@ export function mountShortcuts(toolbar: HTMLElement): void { const toggle = document.createElement('button') toggle.className = 'toolbtn' - toggle.textContent = '⌨' - toggle.title = 'Claude Code 快捷键速查' + toggle.innerHTML = ICON_KEYBOARD + toggle.dataset.tip = 'Keyboard shortcuts' + toggle.setAttribute('aria-label', 'Keyboard shortcuts') toggle.setAttribute('aria-label', 'Keyboard shortcuts') toggle.addEventListener('click', () => { overlay.style.display = overlay.style.display === 'none' ? 'flex' : 'none' diff --git a/public/style.css b/public/style.css index 7aaf228..661993d 100644 --- a/public/style.css +++ b/public/style.css @@ -237,20 +237,50 @@ body { border-left: 1px solid var(--border); } .toolbtn { + position: relative; /* for the hover tooltip */ + display: inline-flex; + align-items: center; + justify-content: center; background: none; border: none; color: var(--text-dim); cursor: pointer; - font-size: 15px; padding: 7px 9px; border-radius: 8px; line-height: 1; - transition: background 0.12s ease; + transition: background 0.12s ease, color 0.12s ease; +} +.toolbtn svg { + display: block; + width: 18px; + height: 18px; } .toolbtn:hover { - color: #fff; + color: var(--accent); background: var(--surface-2); } +/* Hover tooltip (right-anchored so it never clips the screen edge). */ +.toolbtn[data-tip]::after { + content: attr(data-tip); + position: absolute; + top: calc(100% + 6px); + right: 0; + white-space: nowrap; + background: var(--surface-3); + color: var(--text); + border: 1px solid var(--border-strong); + padding: 4px 8px; + border-radius: 6px; + font-size: 12px; + font-weight: 500; + opacity: 0; + pointer-events: none; + transition: opacity 0.12s ease; + z-index: 60; +} +.toolbtn[data-tip]:hover::after { + opacity: 1; +} /* ── Terminal area ───────────────────────────────────────────────── */ #term {