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:
Yaojia Wang
2026-06-30 14:36:26 +02:00
parent 59de784c8a
commit 67b0a43b39
10 changed files with 93 additions and 16 deletions

View File

@@ -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)

View File

@@ -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()

View File

@@ -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" />')

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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') {

View File

@@ -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)

View File

@@ -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'

View File

@@ -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 {