import React, { useRef, useEffect } from 'react'; import { NodeTemplate, NodeTemplates } from '@esengine/behavior-tree'; import { Search, X, LucideIcon } from 'lucide-react'; interface QuickCreateMenuProps { visible: boolean; position: { x: number; y: number }; searchText: string; selectedIndex: number; mode: 'create' | 'replace'; iconMap: Record; onSearchChange: (text: string) => void; onIndexChange: (index: number) => void; onNodeSelect: (template: NodeTemplate) => void; onClose: () => void; } export const QuickCreateMenu: React.FC = ({ visible, position, searchText, selectedIndex, iconMap, onSearchChange, onIndexChange, onNodeSelect, onClose }) => { const selectedNodeRef = useRef(null); const allTemplates = NodeTemplates.getAllTemplates(); const searchTextLower = searchText.toLowerCase(); const filteredTemplates = searchTextLower ? allTemplates.filter((t: NodeTemplate) => { const className = t.className || ''; return t.displayName.toLowerCase().includes(searchTextLower) || t.description.toLowerCase().includes(searchTextLower) || t.category.toLowerCase().includes(searchTextLower) || className.toLowerCase().includes(searchTextLower); }) : allTemplates; useEffect(() => { if (selectedNodeRef.current) { selectedNodeRef.current.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } }, [selectedIndex]); if (!visible) return null; return ( <>
e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()} > {/* 搜索框 */}
{ onSearchChange(e.target.value); onIndexChange(0); }} onKeyDown={(e) => { if (e.key === 'Escape') { onClose(); } else if (e.key === 'ArrowDown') { e.preventDefault(); onIndexChange(Math.min(selectedIndex + 1, filteredTemplates.length - 1)); } else if (e.key === 'ArrowUp') { e.preventDefault(); onIndexChange(Math.max(selectedIndex - 1, 0)); } else if (e.key === 'Enter' && filteredTemplates.length > 0) { e.preventDefault(); const selectedTemplate = filteredTemplates[selectedIndex]; if (selectedTemplate) { onNodeSelect(selectedTemplate); } } }} style={{ flex: 1, background: 'transparent', border: 'none', outline: 'none', color: '#ccc', fontSize: '14px', padding: '4px' }} />
{/* 节点列表 */}
{filteredTemplates.length === 0 ? (
未找到匹配的节点
) : ( filteredTemplates.map((template: NodeTemplate, index: number) => { const IconComponent = template.icon ? iconMap[template.icon] : null; const className = template.className || ''; const isSelected = index === selectedIndex; return (
onNodeSelect(template)} onMouseEnter={() => onIndexChange(index)} style={{ padding: '8px 12px', marginBottom: '4px', backgroundColor: isSelected ? '#0e639c' : '#1e1e1e', borderLeft: `3px solid ${template.color || '#666'}`, borderRadius: '3px', cursor: 'pointer', transition: 'all 0.15s', transform: isSelected ? 'translateX(2px)' : 'translateX(0)' }} >
{IconComponent && ( )}
{template.displayName}
{className && (
{className}
)}
{template.description}
{template.category}
); }) )}
); };