import React from 'react'; import { KnowledgeGroup } from '../types'; import { Check, ChevronDown, Search } from 'lucide-react'; interface GroupSelectorProps { groups: KnowledgeGroup[]; selectedGroups: string[]; onSelectionChange: (groupIds: string[]) => void; showSelectAll?: boolean; placeholder?: string; minimal?: boolean; direction?: 'up' | 'bottom'; // Added direction prop } export const GroupSelector: React.FC = ({ groups, selectedGroups, onSelectionChange, showSelectAll = true, placeholder = '选择分组范围', minimal = false, direction = 'bottom' }) => { const [isOpen, setIsOpen] = React.useState(false); const [dropdownStyle, setDropdownStyle] = React.useState({}); const [searchTerm, setSearchTerm] = React.useState(''); const containerRef = React.useRef(null); const searchInputRef = React.useRef(null); React.useEffect(() => { if (isOpen && containerRef.current) { const button = containerRef.current.querySelector('button') as HTMLElement; if (button) { const rect = button.getBoundingClientRect(); // Calculate style based on direction const style: React.CSSProperties = { left: rect.left, width: Math.max(rect.width, 240), // Min width for readability }; if (direction === 'up') { style.bottom = window.innerHeight - rect.top + 4; style.maxHeight = '320px'; // Increased height for search + list } else { style.top = rect.bottom + 4; style.maxHeight = '320px'; } setDropdownStyle(style); // Auto-focus search input when opening setTimeout(() => searchInputRef.current?.focus(), 50); } } else { setSearchTerm(''); // Reset search on close } }, [isOpen, direction]); const filteredGroups = groups.filter(g => g.name.toLowerCase().includes(searchTerm.toLowerCase()) ); const isAllSelected = selectedGroups.length === 0; // Optimized display logic let selectedGroupNames = ''; if (selectedGroups.length === 0) { selectedGroupNames = '全部分组'; } else if (selectedGroups.length <= 2) { selectedGroupNames = selectedGroups.map(id => groups.find(g => g.id === id)?.name).filter(Boolean).join(', '); } else { selectedGroupNames = `已选 ${selectedGroups.length} 个分组`; } const handleToggleGroup = (groupId: string) => { if (selectedGroups.includes(groupId)) { onSelectionChange(selectedGroups.filter(id => id !== groupId)); } else { onSelectionChange([...selectedGroups, groupId]); } }; const handleSelectAll = () => { onSelectionChange([]); }; return (
{isOpen && ( <>
setIsOpen(false)} />
{/* Search Box */}
setSearchTerm(e.target.value)} className="w-full pl-8 pr-3 py-1.5 text-sm border border-gray-200 rounded-md focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 bg-gray-50" onClick={(e) => e.stopPropagation()} />
{!searchTerm && showSelectAll && (
{isAllSelected && }
全部分组
)}
{filteredGroups.map((group) => { const isSelected = selectedGroups.includes(group.id); return (
handleToggleGroup(group.id)} className={`flex items - center px - 3 py - 2 cursor - pointer hover: bg - gray - 50 transition - colors ${isSelected ? 'bg-blue-50' : '' } `} >
{isSelected && }
{group.name}
{group.fileCount} 文件
); })} {filteredGroups.length === 0 && (
{searchTerm ? '未找到相关分组' : '暂无分组'}
)}
)}
); };