"use client"; import React, { useEffect, useState, useMemo, useRef, useCallback, } from "react"; import { Button } from "@/app/_components/ui/button"; import { Check, Search, type LucideIcon } from "lucide-react"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/app/_components/ui/dropdown-menu"; import { Badge } from "@/app/_components/ui/badge"; import { Input } from "@/app/_components/ui/input"; import { cn } from "@/app/_lib/utils"; type Option = { value: T; prefix?: LucideIcon; label: string; subLabel?: string; beta?: boolean; isCurrent?: boolean; }; type Variant = "outline" | "ghost"; interface DropdownSwitcherProps { options: Option[]; selectedValue: T; onChange: (value: T) => void; showTitle?: boolean; title?: string; prefix?: boolean; suffix?: LucideIcon; variant?: Variant; searchable?: boolean; searchPlaceholder?: string; currentLabel?: string; selectLabel?: string; disabled?: boolean; } const ICON_SIZE = 16; const DropdownSwitcher = ({ options, selectedValue, onChange, showTitle = false, title = "Select", prefix = true, suffix, variant = "outline", searchable = false, searchPlaceholder = "Search...", currentLabel = "Current timezone", selectLabel = "Select a timezone", disabled = false, }: DropdownSwitcherProps) => { const [searchQuery, setSearchQuery] = useState(""); const [isOpen, setIsOpen] = useState(false); const searchInputRef = useRef(null); const currentOption = useMemo( () => options.find((option) => option.value === selectedValue) || options[0], [selectedValue, options] ); const filteredOptions = useMemo(() => { if (!searchQuery.trim()) return options; const query = searchQuery.toLowerCase(); return options.filter( (option) => option.label.toLowerCase().includes(query) || option.value.toLowerCase().includes(query) || (option.subLabel && option.subLabel.toLowerCase().includes(query)) ); }, [options, searchQuery]); const { currentOptions, selectableOptions } = useMemo( () => ({ currentOptions: filteredOptions.filter( (opt) => opt.value === selectedValue ), selectableOptions: filteredOptions.filter( (opt) => opt.value !== selectedValue ), }), [filteredOptions, selectedValue] ); useEffect(() => { if (!isOpen) { setSearchQuery(""); } }, [isOpen]); useEffect(() => { if (isOpen && searchable) { const timeoutId = setTimeout(() => { if (searchInputRef.current) { searchInputRef.current.focus(); } }, 10); return () => clearTimeout(timeoutId); } }, [isOpen, searchable]); const handleSearchChange = useCallback( (e: React.ChangeEvent) => { setSearchQuery(e.target.value); }, [] ); const handleOpenChange = useCallback((open: boolean) => { setIsOpen(open); }, []); const handleOptionSelect = useCallback( (value: T) => { onChange(value); setIsOpen(false); }, [onChange] ); const handleSearchInteraction = useCallback( (e: React.MouseEvent | React.KeyboardEvent) => { e.stopPropagation(); }, [] ); return ( e.preventDefault()} > {searchable && (
{ if (e.key === "Escape") { e.stopPropagation(); setIsOpen(false); } }} disabled={disabled} />
)} {currentOptions.length > 0 && ( <>

{currentLabel}

{currentOptions.map((option) => (
{option.label} {option.subLabel && ( {option.subLabel} )}
))}
)}

{selectLabel}

{selectableOptions.length === 0 ? (

No timezones found

) : ( selectableOptions.map((option) => ( handleOptionSelect(option.value)} disabled={disabled} >
{option.label} {option.subLabel && ( {option.subLabel} )}
{option.beta && ( BETA )}
)) )}
); }; export default DropdownSwitcher;