import { useMemo, useState } from 'preact/hooks'; /** Generic searchable table. `columns`: [{key, label, render?}]. `rows`: array * of plain objects. Search matches any column's stringified value. Renders * markdown-lite backtick/glyph cells as-is (the API sends raw source cells). */ export default function SearchableTable({ columns, rows, placeholder, caption }) { const [q, setQ] = useState(''); const filtered = useMemo(() => { const needle = q.trim().toLowerCase(); if (!needle) return rows; return rows.filter((r) => columns.some((c) => String(r[c.key] ?? '').toLowerCase().includes(needle)), ); }, [q, rows, columns]); return (
setQ(e.currentTarget.value)} /> {filtered.length} / {rows.length} rows
{caption &&

{caption}

}
{columns.map((c) => ( ))} {filtered.length === 0 && ( )} {filtered.map((r, i) => ( {columns.map((c) => ( ))} ))}
{c.label}
No rows match “{q}”.
{c.render ? c.render(r) : r[c.key]}
); }