import { useState, useRef, useCallback, useEffect } from 'react'; import { Participant } from '@/types/calendar'; import { Input } from '@/components/ui/input'; import { X, Plus, Search, AlertCircle, Info } from 'lucide-react'; import { cn, getAvatarColor, getCalendarNameFromUrl } from '@/lib/utils'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@/components/ui/tooltip'; interface ParticipantSelectorProps { participants: Participant[]; selectedParticipants: Participant[]; onSelectionChange: (participants: Participant[]) => void; } export const ParticipantSelector = ({ participants, selectedParticipants, onSelectionChange, }: ParticipantSelectorProps) => { const [searchQuery, setSearchQuery] = useState(''); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [highlightedIndex, setHighlightedIndex] = useState(0); const inputRef = useRef(null); const containerRef = useRef(null); useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if (containerRef.current && !containerRef.current.contains(e.target as Node)) { setIsDropdownOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); const filteredParticipants = participants.filter( (p) => !selectedParticipants.find((sp) => sp.id === p.id) && (p.name.toLowerCase().includes(searchQuery.toLowerCase()) || p.email.toLowerCase().includes(searchQuery.toLowerCase())) ); const addParticipant = useCallback((participant: Participant) => { onSelectionChange([...selectedParticipants, participant]); setSearchQuery(''); // Keep dropdown open for multi-select; clamp highlight to new list length setHighlightedIndex((prev) => { const newLength = filteredParticipants.length - 1; return prev >= newLength ? Math.max(0, newLength - 1) : prev; }); // Keep focus on input so user can continue selecting requestAnimationFrame(() => inputRef.current?.focus()); }, [onSelectionChange, selectedParticipants, filteredParticipants.length]); const handleKeyDown = (e: React.KeyboardEvent) => { if (!isDropdownOpen || filteredParticipants.length === 0) return; if (e.key === 'ArrowDown') { e.preventDefault(); setHighlightedIndex((prev) => prev < filteredParticipants.length - 1 ? prev + 1 : 0 ); } else if (e.key === 'ArrowUp') { e.preventDefault(); setHighlightedIndex((prev) => prev > 0 ? prev - 1 : filteredParticipants.length - 1 ); } else if (e.key === 'Enter') { e.preventDefault(); addParticipant(filteredParticipants[highlightedIndex]); } else if (e.key === 'Escape') { setIsDropdownOpen(false); } }; const removeParticipant = (participantId: string) => { onSelectionChange(selectedParticipants.filter((p) => p.id !== participantId)); setIsDropdownOpen(false); inputRef.current?.blur(); }; const getInitials = (name: string) => { return name .split(' ') .map((n) => n[0]) .join('') .toUpperCase() .slice(0, 2); }; return (
{ setSearchQuery(e.target.value); setHighlightedIndex(0); setIsDropdownOpen(true); }} onFocus={() => setIsDropdownOpen(true)} onKeyDown={handleKeyDown} className="pl-10 h-12 bg-background border-border" /> {isDropdownOpen && filteredParticipants.length > 0 && (
{filteredParticipants.map((participant, index) => ( ))}
)}
{selectedParticipants.length > 0 && (
{selectedParticipants.map((participant, index) => (
{getInitials(participant.name)}
{participant.name.split(' ')[0]} {!participant.icsLink && ( )}
))}
)}
); };