"use client"; import { useVirtualizer } from "@tanstack/react-virtual"; import { CheckIcon, ChevronsUpDown, Globe } from "lucide-react"; import * as React from "react"; import * as RPNInput from "react-phone-number-input"; import flags from "react-phone-number-input/flags"; import EN from "react-phone-number-input/locale/en"; import { Button } from "~/components/ui/button"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, } from "~/components/ui/command"; import { Input } from "~/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger, } from "~/components/ui/popover"; import { ScrollArea } from "~/components/ui/scroll-area"; import { cn } from "~/lib/utils"; type PhoneInputProps = Omit< React.ComponentProps<"input">, "onChange" | "value" | "ref" > & Omit, "onChange"> & { onChange?: (value: RPNInput.Value) => void; }; const PhoneInput: React.ForwardRefExoticComponent = React.memo( React.forwardRef< React.ComponentRef, PhoneInputProps >(({ className, onChange, ...props }, ref) => { return ( onChange?.(value || ("" as RPNInput.Value))} ref={ref} /** * Handles the onChange event. * * react-phone-number-input might trigger the onChange event as undefined * when a valid phone number is not entered. To prevent this, * the value is coerced to an empty string. * * @param {E164Number | undefined} value - The entered value */ smartCaret={false} {...props} /> ); }), ); PhoneInput.displayName = "PhoneInput"; const InputComponent = React.forwardRef< HTMLInputElement, React.ComponentProps<"input"> >(({ className, ...props }, ref) => ( )); InputComponent.displayName = "InputComponent"; type CountryEntry = { label: string; value: RPNInput.Country | undefined }; type CountrySelectProps = { disabled?: boolean; value: RPNInput.Country; options: CountryEntry[]; onChange: (country: RPNInput.Country) => void; }; const CountrySelect = React.memo( ({ disabled, value: selectedCountry, options: countryList, onChange, }: CountrySelectProps) => { const [filteredCountries, setFilteredCountries] = React.useState< CountryEntry[] >(countryList || []); const [open, setOpen] = React.useState(false); const [parentNode, setParentNode] = React.useState( null, ); const refCallback = React.useCallback((node: HTMLDivElement) => { if (node) { setParentNode(node); } }, []); const virtualizer = useVirtualizer({ count: filteredCountries.length, estimateSize: () => 35, getScrollElement: () => parentNode, overscan: 5, }); const handleSearch = (search: string) => { if (!search.trim()) { setFilteredCountries(countryList); } else { setFilteredCountries( countryList.filter((country) => country.label.toLowerCase().includes(search.toLowerCase()), ), ); } }; return ( No countries found
{virtualizer .getVirtualItems() .map(({ index, size, start }) => { const country = filteredCountries[index]; if (!country?.value) return null; return ( onSelect={() => onChange(country.value!)} style={{ height: `${size}px`, left: 0, position: "absolute", top: 0, transform: `translateY(${start}px)`, width: "100%", }} value={country.value} > {country.label} {`+${RPNInput.getCountryCallingCode( country.value, )}`} ); })}
); }, ); CountrySelect.displayName = "CountrySelect"; const FlagComponent = ({ country, countryName }: RPNInput.FlagProps) => { const Flag = flags[country]; return ( {Flag ? : } ); }; export { PhoneInput };