'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, } from '@/components/ui/command'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { cn } from '@/lib/utils'; import { ALL_COUNTRY_CODES, getCountryName, type CountryCode } from '@/lib/i18n/countries'; interface CountryComboboxProps { value: string | null | undefined; onChange: (iso: CountryCode | null) => void; /** Display locale; defaults to navigator.language so country names follow the user. */ locale?: string; /** When true, renders just the flag/code (compact 24×24 trigger). */ compact?: boolean; placeholder?: string; disabled?: boolean; className?: string; /** Allow clearing the selection. */ clearable?: boolean; id?: string; 'data-testid'?: string; } /** * Returns the regional-indicator emoji flag for an ISO alpha-2 code. * E.g. 'GB' → 🇬🇧. Avoids shipping a flag-image asset and respects the * platform's emoji rendering (iOS/macOS render real flags; Windows * shows the country code on a flag rectangle). */ function flagEmoji(code: string): string { if (code.length !== 2) return ''; const A = 0x1f1e6; const a = 'A'.charCodeAt(0); const cp1 = A + code.charCodeAt(0) - a; const cp2 = A + code.charCodeAt(1) - a; return String.fromCodePoint(cp1, cp2); } export function CountryCombobox({ value, onChange, locale, compact = false, placeholder = 'Select country…', disabled, className, clearable = true, id, 'data-testid': testId, }: CountryComboboxProps) { const [open, setOpen] = useState(false); const effectiveLocale = locale ?? (typeof navigator !== 'undefined' ? navigator.language : 'en'); // Pre-build the options list once per locale change so the cmdk filter // can search by both code + localized name without re-allocating. const options = useMemo(() => { return ALL_COUNTRY_CODES.map((code) => ({ code, name: getCountryName(code, effectiveLocale), flag: flagEmoji(code), })).sort((a, b) => a.name.localeCompare(b.name, effectiveLocale)); }, [effectiveLocale]); const selected = value ? options.find((o) => o.code === value) : undefined; return ( No country found. {clearable && value ? ( { onChange(null); setOpen(false); }} className="text-muted-foreground" > Clear selection ) : null} {options.map((opt) => ( { onChange(opt.code); setOpen(false); }} > {opt.flag} {opt.name} {opt.code} ))} ); }