1import { ChevronDownIcon } from "lucide-react";2import { PhoneField } from "phonefield";3import { inputClassName } from "@/components/ui/input";4import {5 InputGroup,6 InputGroupAddon,7 InputGroupInput,8} from "@/components/ui/input-group";9import { cn } from "@/lib/utils";1011const defaultCountryClassNames = {12 trigger:13 "group/phone-country-trigger flex h-full w-fit shrink-0 cursor-default items-center gap-2 border-input border-r bg-transparent px-3 text-left text-sm outline-none select-none transition-colors duration-150 hover:bg-accent/50 focus-visible:bg-accent/50 data-popup-open:bg-accent/50",14 icon: "shrink-0 text-muted-foreground transition-transform duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] group-data-popup-open/phone-country-trigger:rotate-180 motion-reduce:transition-none",15 positioner: "isolate z-50",16 popup:17 "max-h-(--available-height) w-72 max-w-(--available-width) origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-2xl ring-1 ring-foreground/5 transition-[transform,opacity] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] data-ending-style:[transform:scale(0.97)] data-ending-style:opacity-0 data-starting-style:[transform:scale(0.97)] data-starting-style:opacity-0 motion-reduce:transform-none motion-reduce:transition-none",18 searchInputContainer: "p-1",19 searchInput: inputClassName,20 list: "max-h-72 scroll-py-1 overflow-y-auto overscroll-contain p-1",21 item: "flex cursor-default items-center rounded-lg px-3 py-2 text-sm outline-none select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-selected:bg-accent data-selected:text-accent-foreground",22 empty: "p-6 text-center text-sm text-muted-foreground",23} satisfies PhoneField.CountryClassNames;2425const defaultCountryIcon = <ChevronDownIcon aria-hidden className="size-4" />;2627function renderCountryItem(country: PhoneField.Country) {28 return (29 <span className="flex min-w-0 flex-1 items-center gap-2.5">30 <span aria-hidden className="shrink-0">31 {country.flag}32 </span>33 <span className="min-w-0 flex-1 truncate">{country.name}</span>34 <span className="shrink-0 text-muted-foreground">{country.dialCode}</span>35 </span>36 );37}3839function renderCountryValue(country: PhoneField.Country) {40 return (41 <span className="flex min-w-0 items-center gap-2">42 <span aria-hidden>{country.flag}</span>43 <span>{country.dialCode}</span>44 <span className="sr-only">{country.name}</span>45 </span>46 );47}4849type ProductionPhoneFieldProps = Omit<PhoneField.RootProps, "children"> & {50 countryClassNames?: PhoneField.CountryClassNames;51 countryProps?: Omit<PhoneField.CountryProps, "classNames">;52 inputClassName?: string;53 inputProps?: Omit<PhoneField.InputProps, "className">;54};5556export function ProductionPhoneField({57 className,58 countryClassNames = defaultCountryClassNames,59 countryProps,60 defaultCountry = "US",61 inputClassName: inputClassNameOverride,62 inputProps,63 ...props64}: ProductionPhoneFieldProps) {65 const {66 icon = defaultCountryIcon,67 renderCountryItem: countryItemRenderer = renderCountryItem,68 renderCountryValue: countryValueRenderer = renderCountryValue,69 slotProps,70 ...resolvedCountryProps71 } = countryProps ?? {};72 const { trigger: countryTriggerProps, ...resolvedCountrySlotProps } =73 slotProps ?? {};74 const {75 "aria-label": inputAriaLabel = "Phone number",76 ...resolvedInputProps77 } = inputProps ?? {};7879 return (80 <PhoneField.Root81 {...props}82 className={cn("w-full", className)}83 defaultCountry={defaultCountry}84 >85 <InputGroup86 className={cn(87 "h-10 overflow-hidden bg-background shadow-sm transition-[border-color,box-shadow] duration-150",88 "focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/50",89 "has-data-popup-open:border-ring has-data-popup-open:ring-2 has-data-popup-open:ring-ring/50",90 "has-aria-invalid:border-destructive has-aria-invalid:ring-2 has-aria-invalid:ring-destructive/20",91 )}92 >93 <PhoneField.Input94 render={<InputGroupInput />}95 {...resolvedInputProps}96 aria-label={inputAriaLabel}97 className={cn("h-full px-3", inputClassNameOverride)}98 />99 <InputGroupAddon100 align="inline-start"101 className="h-full cursor-default p-0"102 >103 <PhoneField.Country104 {...resolvedCountryProps}105 classNames={countryClassNames}106 icon={icon}107 renderCountryItem={countryItemRenderer}108 renderCountryValue={countryValueRenderer}109 slotProps={{110 ...resolvedCountrySlotProps,111 trigger: {112 "aria-label": "Country",113 ...countryTriggerProps,114 },115 }}116 />117 </InputGroupAddon>118 </InputGroup>119 </PhoneField.Root>120 );121}