import React, { useState, useMemo } from 'react'; import { Search, Check, X } from 'lucide-react'; import { cn } from '../lib/utils'; interface ColumnFilterPopoverProps { uniqueValues: string[]; selectedValues: string[]; onToggle: (val: string) => void; onSelectAll: (vals: string[]) => void; onClear: () => void; onClose: () => void; title?: string; className?: string; } export function ColumnFilterPopover({ uniqueValues, selectedValues, onToggle, onSelectAll, onClear, onClose, title, className }: ColumnFilterPopoverProps) { const [search, setSearch] = useState(''); const [isDragging, setIsDragging] = useState(false); const [dragStart, setDragStart] = useState(null); const [hoveredIndex, setHoveredIndex] = useState(null); const listRef = React.useRef(null); const filteredValues = useMemo(() => { return uniqueValues.filter(v => String(v || '').toLowerCase().includes(search.toLowerCase()) ); }, [uniqueValues, search]); const isAllSelected = selectedValues.length === uniqueValues.length && uniqueValues.length > 0; const isDraggingRef = React.useRef(false); const dragStartRef = React.useRef(null); const hoveredIndexRef = React.useRef(null); const selectedValuesRef = React.useRef(selectedValues); const onSelectAllRef = React.useRef(onSelectAll); const filteredValuesRef = React.useRef(filteredValues); selectedValuesRef.current = selectedValues; onSelectAllRef.current = onSelectAll; filteredValuesRef.current = filteredValues; const handleMouseDown = (index: number) => { isDraggingRef.current = true; dragStartRef.current = index; hoveredIndexRef.current = index; setIsDragging(true); setDragStart(index); setHoveredIndex(index); }; const handleMouseEnter = (index: number) => { if (isDraggingRef.current && dragStartRef.current !== null) { hoveredIndexRef.current = index; setHoveredIndex(index); } }; React.useEffect(() => { const handleGlobalMouseUp = () => { if (isDraggingRef.current) { const start = dragStartRef.current; const end = hoveredIndexRef.current; if (start !== null && end !== null) { const s = Math.min(start, end); const e = Math.max(start, end); const itemsToSelect = filteredValuesRef.current.slice(s, e + 1).map(v => v); const newSelected = new Set([...selectedValuesRef.current]); itemsToSelect.forEach(v => newSelected.add(v)); onSelectAllRef.current(Array.from(newSelected)); } isDraggingRef.current = false; dragStartRef.current = null; hoveredIndexRef.current = null; setIsDragging(false); setDragStart(null); setHoveredIndex(null); } }; document.addEventListener('mouseup', handleGlobalMouseUp); return () => document.removeEventListener('mouseup', handleGlobalMouseUp); }, []); return (
e.stopPropagation()} > {title &&
{title}
}
setSearch(e.target.value)} className="w-full bg-slate-900 border border-slate-700 rounded p-1.5 pl-8 pr-7 text-xs text-white focus:outline-none focus:border-blue-500" autoFocus /> {search && ( )}
{filteredValues.map((val, idx) => { const isDragSelected = isDragging && dragStart !== null && hoveredIndex !== null && ((idx >= dragStart && idx <= hoveredIndex) || (idx <= dragStart && idx >= hoveredIndex)); return (
{ e.preventDefault(); handleMouseDown(idx); }} onMouseEnter={() => handleMouseEnter(idx)} onClick={(e) => { if (!isDragging) onToggle(val); }} onKeyDown={(e) => { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); onToggle(val); } }} className={cn( "flex items-center gap-2 p-1.5 rounded cursor-pointer group transition-colors", isDragSelected ? "bg-blue-600/40" : "hover:bg-slate-700/50" )} >
{selectedValues.includes(val) && }
{val || '(Empty)'}
); })} {filteredValues.length === 0 && (
No values found
)}
); }