import { useState, useMemo } from "react"; import { Input } from "~/components/ui/input"; import { Button } from "~/components/ui/button"; import { Check, ChevronsUpDown, X } from "lucide-react"; import { Popover, PopoverContent, PopoverTrigger, } from "~/components/ui/popover"; import { cn } from "~/lib/utils"; interface Participant { id: string; name: string; } interface ParticipantSelectorProps { participants: Participant[]; value: string; onValueChange: (value: string) => void; placeholder?: string; disabled?: boolean; } /** * Searchable participant selector optimized for large lists (305+ items) * Only renders filtered results to maintain performance */ export function ParticipantSelector({ participants, value, onValueChange, placeholder = "Select participant...", disabled = false, }: ParticipantSelectorProps) { const [open, setOpen] = useState(false); const [search, setSearch] = useState(""); // Find selected participant const selectedParticipant = useMemo( () => participants.find((p) => p.id === value), [participants, value] ); // Filter participants based on search (case-insensitive) const filteredParticipants = useMemo(() => { if (!search) return participants; const searchLower = search.toLowerCase(); return participants.filter((p) => p.name.toLowerCase().includes(searchLower) ); }, [participants, search]); // Limit displayed results to prevent DOM overload const displayedParticipants = useMemo( () => filteredParticipants.slice(0, 50), [filteredParticipants] ); const handleSelect = (participantId: string) => { onValueChange(participantId); setOpen(false); setSearch(""); }; const handleClear = (e: React.MouseEvent) => { e.stopPropagation(); onValueChange(""); setSearch(""); }; return (
{/* Search input */}
setSearch(e.target.value)} className="h-9" autoFocus />
{/* Results */}
{displayedParticipants.length === 0 ? (
No participants found.
) : ( <> {displayedParticipants.map((participant) => ( ))} {filteredParticipants.length > 50 && (
Showing 50 of {filteredParticipants.length} results. Keep typing to refine...
)} )}
); }