polish(v0.6): replace toolbar emoji with themed line icons + hover tooltips
The glossy emoji (🔍⚙▦🕘⌨🔗📱) clashed with the Amber dark theme. Swap them for clean lucide line icons (MIT, stroke=currentColor) so they take the theme colour — muted by default, Amber on hover — and add a styled CSS hover tooltip (data-tip, right-anchored so it never clips the edge) showing what each does. - public/icons.ts: 7 line icons (search/settings/dashboard/history/keyboard/ share/device), script-generated from lucide - 7 toolbar modules: innerHTML = ICON_* + data-tip + aria-label - style.css .toolbtn: 18px svg, --text-dim → --accent on hover, [data-tip] tooltip Frontend-only. web typecheck + build clean; bundle has 0 toolbar emoji; verified in-browser: line icons in theme colour, amber + tooltip on hover.
This commit is contained in:
@@ -146,6 +146,15 @@
|
||||
- **commit**: <hash 或 N/A>
|
||||
======================================================= -->
|
||||
|
||||
### 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 · 真浏览器验证通过。
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* `claude --resume <id>`.
|
||||
*/
|
||||
|
||||
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()
|
||||
|
||||
@@ -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 (
|
||||
'<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" ' +
|
||||
'stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' +
|
||||
inner +
|
||||
'</svg>'
|
||||
)
|
||||
}
|
||||
|
||||
export const ICON_SEARCH = lineIcon('<path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" />')
|
||||
export const ICON_SETTINGS = lineIcon('<path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /> <circle cx="12" cy="12" r="3" />')
|
||||
export const ICON_DASHBOARD = lineIcon('<rect width="7" height="7" x="3" y="3" rx="1" /> <rect width="7" height="7" x="14" y="3" rx="1" /> <rect width="7" height="7" x="14" y="14" rx="1" /> <rect width="7" height="7" x="3" y="14" rx="1" />')
|
||||
export const ICON_HISTORY = lineIcon('<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" /> <path d="M3 3v5h5" /> <path d="M12 7v5l4 2" />')
|
||||
export const ICON_KEYBOARD = lineIcon('<path d="M10 8h.01" /> <path d="M12 12h.01" /> <path d="M14 8h.01" /> <path d="M16 12h.01" /> <path d="M18 8h.01" /> <path d="M6 8h.01" /> <path d="M7 16h10" /> <path d="M8 12h.01" /> <rect width="20" height="16" x="2" y="4" rx="2" />')
|
||||
export const ICON_SHARE = lineIcon('<circle cx="18" cy="5" r="3" /> <circle cx="6" cy="12" r="3" /> <circle cx="18" cy="19" r="3" /> <line x1="8.59" x2="15.42" y1="13.51" y2="17.49" /> <line x1="15.41" x2="8.59" y1="6.51" y2="10.49" />')
|
||||
export const ICON_DEVICE = lineIcon('<rect width="14" height="20" x="5" y="2" rx="2" ry="2" /> <path d="M12 18h.01" />')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user