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.
71 lines
2.0 KiB
TypeScript
71 lines
2.0 KiB
TypeScript
/**
|
||
* public/search.ts — scrollback search box for the active terminal (M1).
|
||
*
|
||
* Adds a 🔍 button to the toolbar that toggles a small search overlay.
|
||
* Enter = next, Shift+Enter = previous, Esc = close.
|
||
*/
|
||
|
||
export interface SearchHooks {
|
||
find: (query: string, dir: 'next' | 'prev') => void
|
||
clear: () => void
|
||
}
|
||
|
||
import { ICON_SEARCH } from './icons.js'
|
||
|
||
function makeBtn(label: string, title: string): HTMLButtonElement {
|
||
const b = document.createElement('button')
|
||
b.textContent = label
|
||
b.title = title
|
||
b.setAttribute('aria-label', title)
|
||
return b
|
||
}
|
||
|
||
export function mountSearch(toolbar: HTMLElement, hooks: SearchHooks): void {
|
||
const box = document.createElement('div')
|
||
box.id = 'searchbox'
|
||
box.style.display = 'none'
|
||
|
||
const input = document.createElement('input')
|
||
input.type = 'text'
|
||
input.className = 'search-input'
|
||
input.placeholder = 'Search scrollback…'
|
||
|
||
const prev = makeBtn('↑', 'Previous match (Shift+Enter)')
|
||
const next = makeBtn('↓', 'Next match (Enter)')
|
||
const close = makeBtn('×', 'Close (Esc)')
|
||
box.append(input, prev, next, close)
|
||
document.body.appendChild(box)
|
||
|
||
const open = (): void => {
|
||
box.style.display = 'flex'
|
||
input.focus()
|
||
input.select()
|
||
}
|
||
const hide = (): void => {
|
||
box.style.display = 'none'
|
||
hooks.clear()
|
||
}
|
||
|
||
input.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault()
|
||
hooks.find(input.value, e.shiftKey ? 'prev' : 'next')
|
||
} else if (e.key === 'Escape') {
|
||
e.preventDefault()
|
||
hide()
|
||
}
|
||
e.stopPropagation()
|
||
})
|
||
prev.addEventListener('click', () => hooks.find(input.value, 'prev'))
|
||
next.addEventListener('click', () => hooks.find(input.value, 'next'))
|
||
close.addEventListener('click', hide)
|
||
|
||
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)
|
||
}
|