88 lines
2.5 KiB
TypeScript
88 lines
2.5 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState, type RefObject } from 'react';
|
|
import {
|
|
parsePhoneNumberFromString,
|
|
type CountryCode,
|
|
} from 'libphonenumber-js';
|
|
|
|
import { Input } from '@/components/ui/input';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
import { buildPhoneValue, getNationalValue } from './countries';
|
|
import { CountryCombobox } from './CountryCombobox';
|
|
|
|
interface PhoneInputProps {
|
|
value?: string;
|
|
onChange: (value: string) => void;
|
|
onBlur?: () => void;
|
|
disabled?: boolean;
|
|
'aria-invalid'?: boolean;
|
|
placeholder?: string;
|
|
defaultCountry?: CountryCode;
|
|
portalContainer?: RefObject<HTMLDivElement | null>;
|
|
}
|
|
|
|
export function PhoneInput({
|
|
value = '',
|
|
onChange,
|
|
onBlur,
|
|
disabled = false,
|
|
'aria-invalid': ariaInvalid,
|
|
placeholder = 'Enter phone number',
|
|
defaultCountry = 'IN',
|
|
portalContainer,
|
|
}: PhoneInputProps) {
|
|
const parsedCountry = parsePhoneNumberFromString(value)?.country;
|
|
const [country, setCountry] = useState<CountryCode>(
|
|
parsedCountry ?? defaultCountry,
|
|
);
|
|
|
|
useEffect(() => {
|
|
const parsed = parsePhoneNumberFromString(value);
|
|
if (parsed?.country) {
|
|
setCountry(parsed.country);
|
|
}
|
|
}, [value]);
|
|
|
|
const nationalValue = getNationalValue(value, country);
|
|
|
|
function handleCountryChange(nextCountry: CountryCode) {
|
|
setCountry(nextCountry);
|
|
onChange(buildPhoneValue(nextCountry, nationalValue));
|
|
}
|
|
|
|
function handleNationalChange(nextNationalValue: string) {
|
|
onChange(buildPhoneValue(country, nextNationalValue));
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent shadow-xs transition-[color,box-shadow] focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50 dark:bg-input/30',
|
|
ariaInvalid &&
|
|
'border-destructive focus-within:border-destructive focus-within:ring-destructive/20',
|
|
disabled && 'cursor-not-allowed opacity-50',
|
|
)}
|
|
>
|
|
<CountryCombobox
|
|
value={country}
|
|
onValueChange={handleCountryChange}
|
|
disabled={disabled}
|
|
portalContainer={portalContainer}
|
|
/>
|
|
|
|
<Input
|
|
type="tel"
|
|
value={nationalValue}
|
|
onChange={(event) => handleNationalChange(event.target.value)}
|
|
onBlur={onBlur}
|
|
disabled={disabled}
|
|
aria-invalid={ariaInvalid}
|
|
placeholder={placeholder}
|
|
className="h-9 flex-1 rounded-none border-0 bg-transparent px-3 py-1 shadow-none focus-visible:ring-0"
|
|
/>
|
|
</div>
|
|
);
|
|
}
|