import { useState, useRef, useEffect } from 'react'; import { createPortal } from 'react-dom'; import { ChevronDown, Check, Edit3 } from 'lucide-react'; import { cn } from '../../utils/cn'; export interface SelectOption { value: string; label: string; group?: string; } interface GroupedSelectProps { value: string; onChange: (value: string) => void; options: SelectOption[]; placeholder?: string; className?: string; disabled?: boolean; allowCustomInput?: boolean; // 新增: 是否允许自定义输入 } export default function GroupedSelect({ value, onChange, options, placeholder = 'Select...', className = '', disabled = false, allowCustomInput = false // 新增: 默认不允许自定义输入 }: GroupedSelectProps) { const [isOpen, setIsOpen] = useState(false); const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0, width: 0 }); const [customInput, setCustomInput] = useState(''); // 新增: 自定义输入值 const containerRef = useRef(null); const buttonRef = useRef(null); const dropdownRef = useRef(null); // 新增: 下拉菜单引用 const customInputRef = useRef(null); // 新增: 自定义输入框引用 // 按组分组选项 const groupedOptions = options.reduce((acc, option) => { const group = option.group || 'Other'; if (!acc[group]) { acc[group] = []; } acc[group].push(option); return acc; }, {} as Record); // 获取当前选中项的标签 const selectedOption = options.find(opt => opt.value === value); const selectedLabel = selectedOption?.label || value || placeholder; // 更新下拉菜单位置 const updateDropdownPosition = () => { if (buttonRef.current) { const rect = buttonRef.current.getBoundingClientRect(); setDropdownPosition({ top: rect.bottom + window.scrollY + 4, left: rect.left + window.scrollX, width: Math.max(rect.width * 1.1, 220) // 增加宽度到 1.1 倍,最小 220px }); } }; // 点击外部关闭下拉菜单 useEffect(() => { const handleClickOutside = (event: MouseEvent) => { // 修复: 检查点击是否在容器或下拉菜单内部 const target = event.target as Node; const isClickInsideContainer = containerRef.current?.contains(target); const isClickInsideDropdown = dropdownRef.current?.contains(target); if (!isClickInsideContainer && !isClickInsideDropdown) { setIsOpen(false); } }; if (isOpen) { updateDropdownPosition(); document.addEventListener('mousedown', handleClickOutside); window.addEventListener('scroll', updateDropdownPosition, true); window.addEventListener('resize', updateDropdownPosition); } return () => { document.removeEventListener('mousedown', handleClickOutside); window.removeEventListener('scroll', updateDropdownPosition, true); window.removeEventListener('resize', updateDropdownPosition); }; }, [isOpen]); const handleSelect = (optionValue: string) => { console.log('[GroupedSelect] handleSelect called:', optionValue); onChange(optionValue); setIsOpen(false); }; const handleCustomInputSubmit = () => { if (customInput.trim()) { console.log('[GroupedSelect] Custom input submitted:', customInput.trim()); onChange(customInput.trim()); setCustomInput(''); setIsOpen(false); } }; const handleToggle = () => { if (!disabled) { setIsOpen(!isOpen); if (!isOpen) { updateDropdownPosition(); } } }; return (
{/* 触发按钮 */} {/* 下拉菜单 - 使用 Portal 渲染到 body */} {isOpen && createPortal(
{Object.entries(groupedOptions).map(([group, groupOptions]) => (
{/* 分组标题 */}
{group}
{/* 分组选项 */} {groupOptions.map((option) => ( ))}
))} {/* 自定义输入区域 */} {allowCustomInput && (
setCustomInput(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); handleCustomInputSubmit(); } }} placeholder="输入自定义模型 ID..." className={cn( 'flex-1 px-2 py-1 text-[10px] font-mono', 'bg-white dark:bg-gray-800', 'border border-gray-300 dark:border-gray-600', 'rounded focus:outline-none focus:ring-1 focus:ring-blue-500', 'text-gray-900 dark:text-gray-100', 'placeholder:text-gray-400 dark:placeholder:text-gray-500' )} />
)}
, document.body )}
); }