import React, { useState, useRef, useEffect, useMemo } from 'react'; import { ChevronDownIcon } from '@heroicons/react/24/outline'; import type { TimezoneOption } from '../../utils/timezoneUtils'; interface TimezoneDropdownProps { value: string; onChange: (timezone: string) => void; timezonesByRegion: Record; getRegionDisplayName: (region: string) => string; } const TimezoneDropdown: React.FC = ({ value, onChange, timezonesByRegion, getRegionDisplayName, }) => { const [isOpen, setIsOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const dropdownRef = useRef(null); const searchInputRef = useRef(null); // Flatten all timezones into a single searchable list const allTimezones = useMemo(() => { const flattened: (TimezoneOption & { regionName: string })[] = []; Object.keys(timezonesByRegion).forEach((region) => { timezonesByRegion[region].forEach((tz) => { flattened.push({ ...tz, regionName: getRegionDisplayName(region), }); }); }); return flattened; }, [timezonesByRegion, getRegionDisplayName]); // Get the current timezone display label const selectedTimezone = useMemo(() => { if (value === 'UTC') return 'UTC'; const found = allTimezones.find((tz) => tz.value === value); return found ? found.label : value; }, [value, allTimezones]); // Filter timezones based on search query const filteredTimezones = useMemo(() => { if (!searchQuery.trim()) return allTimezones; const query = searchQuery.toLowerCase(); return allTimezones.filter( (tz) => tz.label.toLowerCase().includes(query) || tz.value.toLowerCase().includes(query) || tz.regionName.toLowerCase().includes(query) ); }, [searchQuery, allTimezones]); // Group filtered timezones by region const groupedFilteredTimezones = useMemo(() => { const grouped: Record< string, (TimezoneOption & { regionName: string })[] > = {}; filteredTimezones.forEach((tz) => { if (!grouped[tz.regionName]) { grouped[tz.regionName] = []; } grouped[tz.regionName].push(tz); }); return grouped; }, [filteredTimezones]); // Handle click outside to close dropdown useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( dropdownRef.current && !dropdownRef.current.contains(event.target as Node) ) { setIsOpen(false); setSearchQuery(''); } }; if (isOpen) { document.addEventListener('mousedown', handleClickOutside); } return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [isOpen]); // Focus search input when dropdown opens useEffect(() => { if (isOpen && searchInputRef.current) { searchInputRef.current.focus(); } }, [isOpen]); const handleSelect = (timezone: string) => { onChange(timezone); setIsOpen(false); setSearchQuery(''); }; return (
{/* Dropdown trigger button */} {/* Dropdown menu */} {isOpen && (
{/* Search input */}
setSearchQuery(e.target.value)} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
{/* Timezone list */}
{/* UTC option */} {(!searchQuery || 'utc'.includes(searchQuery.toLowerCase())) && ( )} {/* Grouped timezones */} {Object.keys(groupedFilteredTimezones) .sort() .map((regionName) => (
{regionName}
{groupedFilteredTimezones[regionName].map( (tz) => ( ) )}
))} {/* No results message */} {filteredTimezones.length === 0 && searchQuery && 'utc'.includes(searchQuery.toLowerCase()) === false && (
No timezones found matching " {searchQuery}"
)}
)}
); }; export default TimezoneDropdown;