search navigation

This commit is contained in:
2026-03-29 20:05:49 -07:00
parent 94de24bc90
commit 2846dbc3af
2 changed files with 34 additions and 3 deletions

View File

@@ -573,11 +573,13 @@ function ContextMenu({
}) { }) {
const [openCat, setOpenCat] = useState(null); const [openCat, setOpenCat] = useState(null);
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [selectedIndex, setSelectedIndex] = useState(0);
const menuRef = useRef(null); const menuRef = useRef(null);
const [menuPos, setMenuPos] = useState({ x, y }); const [menuPos, setMenuPos] = useState({ x, y });
const subMenuRef = useRef(null); const subMenuRef = useRef(null);
const [subPos, setSubPos] = useState({ x: 0, y: 0 }); const [subPos, setSubPos] = useState({ x: 0, y: 0 });
const catRowRefs = useRef({}); const catRowRefs = useRef({});
const selectedItemRef = useRef(null);
// Group by category, optionally filtering to compatible nodes // Group by category, optionally filtering to compatible nodes
const categories = useMemo(() => { const categories = useMemo(() => {
@@ -658,6 +660,31 @@ function ContextMenu({
return results; return results;
}, [search, categories]); }, [search, categories]);
// Reset selection to top whenever results change
useEffect(() => {
setSelectedIndex(0);
}, [searchResults]);
// Scroll selected item into view
useEffect(() => {
selectedItemRef.current?.scrollIntoView({ block: 'nearest' });
}, [selectedIndex]);
const handleSearchKeyDown = useCallback((e) => {
if (!searchResults || searchResults.length === 0) return;
if (e.key === 'ArrowDown') {
e.preventDefault();
setSelectedIndex((i) => Math.min(i + 1, searchResults.length - 1));
} else if (e.key === 'ArrowUp') {
e.preventDefault();
setSelectedIndex((i) => Math.max(i - 1, 0));
} else if (e.key === 'Enter') {
e.preventDefault();
const item = searchResults[selectedIndex];
if (item) { onAdd(item.className, item.def); onClose(); }
}
}, [searchResults, selectedIndex, onAdd, onClose]);
// Clamp main menu position to viewport on mount // Clamp main menu position to viewport on mount
useEffect(() => { useEffect(() => {
const el = menuRef.current; const el = menuRef.current;
@@ -740,6 +767,7 @@ function ContextMenu({
placeholder="Search…" placeholder="Search…"
value={search} value={search}
onChange={(e) => { setSearch(e.target.value); setOpenCat(null); }} onChange={(e) => { setSearch(e.target.value); setOpenCat(null); }}
onKeyDown={handleSearchKeyDown}
autoFocus autoFocus
/> />
</div> </div>
@@ -758,11 +786,13 @@ function ContextMenu({
{searchResults.length === 0 ? ( {searchResults.length === 0 ? (
<div className="context-item" style={{ color: 'var(--text-muted)' }}>No matches</div> <div className="context-item" style={{ color: 'var(--text-muted)' }}>No matches</div>
) : ( ) : (
searchResults.map(({ className, def }) => ( searchResults.map(({ className, def }, idx) => (
<div <div
key={className} key={className}
className="context-item" ref={idx === selectedIndex ? selectedItemRef : null}
className={`context-item${idx === selectedIndex ? ' context-item--selected' : ''}`}
onClick={() => { onAdd(className, def); onClose(); }} onClick={() => { onAdd(className, def); onClose(); }}
onMouseEnter={() => setSelectedIndex(idx)}
> >
{def.display_name || className} {def.display_name || className}
</div> </div>

View File

@@ -1493,7 +1493,8 @@ html, body, #root {
color: var(--text-primary); color: var(--text-primary);
white-space: nowrap; white-space: nowrap;
} }
.context-item:hover { .context-item:hover,
.context-item--selected {
background: var(--accent-bg); background: var(--accent-bg);
} }