'use client'; import { useMemo, useState } from 'react'; import { Check, ChevronsUpDown } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, } from '@/components/ui/command'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { cn } from '@/lib/utils'; import { formatTimezoneLabel, listAllTimezones, timezonesForCountry } from '@/lib/i18n/timezones'; import type { CountryCode } from '@/lib/i18n/countries'; interface TimezoneComboboxProps { value: string | null | undefined; onChange: (iana: string | null) => void; /** When set, the dropdown surfaces matching zones first under a "Suggested" group. */ countryHint?: CountryCode; placeholder?: string; disabled?: boolean; className?: string; clearable?: boolean; id?: string; 'data-testid'?: string; /** Open the dropdown on first render. Used by inline-edit wrappers. */ defaultOpen?: boolean; /** Notified whenever the dropdown opens/closes. Inline-edit wrappers use * this to auto-exit edit mode when the user dismisses without picking. */ onOpenChange?: (open: boolean) => void; } export function TimezoneCombobox({ value, onChange, countryHint, placeholder = 'Select timezone…', disabled, className, clearable = true, id, 'data-testid': testId, defaultOpen = false, onOpenChange, }: TimezoneComboboxProps) { const [open, setOpen] = useState(defaultOpen); const handleOpenChange = (next: boolean) => { setOpen(next); onOpenChange?.(next); }; const allOptions = useMemo(() => { return listAllTimezones().map((tz) => ({ tz, label: formatTimezoneLabel(tz), })); }, []); const suggested = useMemo(() => { if (!countryHint) return []; const set = new Set(timezonesForCountry(countryHint)); return allOptions.filter((o) => set.has(o.tz)); }, [allOptions, countryHint]); const rest = useMemo(() => { if (!suggested.length) return allOptions; const suggestedSet = new Set(suggested.map((s) => s.tz)); return allOptions.filter((o) => !suggestedSet.has(o.tz)); }, [allOptions, suggested]); const selectedLabel = value ? formatTimezoneLabel(value) : placeholder; return ( // `modal` is critical for iOS Safari when this combobox is nested // inside a Sheet (Radix Dialog). Without it, the parent Dialog's // pointer-events handling can swallow the trigger's touch event, // so tapping the button does nothing on iPhone. modal=true makes // Radix isolate the Popover's pointer context from the parent. No timezone found. {clearable && value ? ( { onChange(null); setOpen(false); }} className="text-muted-foreground" > Clear selection ) : null} {suggested.length > 0 ? ( <> {suggested.map((opt) => ( { onChange(opt.tz); setOpen(false); }} > {opt.label} ))} ) : null} 0 ? 'All zones' : undefined}> {rest.map((opt) => ( { onChange(opt.tz); setOpen(false); }} > {opt.label} ))} ); }