/** * @license * Copyright 2025 Qwen Team * SPDX-License-Identifier: Apache-2.0 */ import type React from 'react'; import { useEffect, useRef, useState } from 'react'; import './ClaudeCompletionMenu.css'; import type { CompletionItem } from './CompletionMenu.js'; interface ClaudeCompletionMenuProps { items: CompletionItem[]; onSelect: (item: CompletionItem) => void; onClose: () => void; title?: string; selectedIndex?: number; } /** * Claude Code-like anchored dropdown rendered above the input field. * Keyboard: Up/Down to move, Enter to select, Esc to close. */ export const ClaudeCompletionMenu: React.FC = ({ items, onSelect, onClose, title, selectedIndex = 0, }) => { const containerRef = useRef(null); const [selected, setSelected] = useState(selectedIndex); useEffect(() => setSelected(selectedIndex), [selectedIndex]); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( containerRef.current && !containerRef.current.contains(event.target as Node) ) { onClose(); } }; const handleKeyDown = (event: KeyboardEvent) => { switch (event.key) { case 'ArrowDown': event.preventDefault(); setSelected((prev) => Math.min(prev + 1, items.length - 1)); break; case 'ArrowUp': event.preventDefault(); setSelected((prev) => Math.max(prev - 1, 0)); break; case 'Enter': event.preventDefault(); if (items[selected]) { onSelect(items[selected]); } break; case 'Escape': event.preventDefault(); onClose(); break; default: break; } }; document.addEventListener('mousedown', handleClickOutside); document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('mousedown', handleClickOutside); document.removeEventListener('keydown', handleKeyDown); }; }, [items, selected, onSelect, onClose]); useEffect(() => { const selectedEl = containerRef.current?.querySelector( `[data-index="${selected}"]`, ); if (selectedEl) { selectedEl.scrollIntoView({ block: 'nearest' }); } }, [selected]); if (!items.length) { return null; } return (
{title &&
{title}
} {items.map((item, index) => { const selectedCls = index === selected ? 'jo' : ''; return (
onSelect(item)} onMouseEnter={() => setSelected(index)} role="menuitem" >
{item.icon && {item.icon}} {item.label} {item.description && ( {item.description} )}
); })}
); };