11import * as React from 'react' ;
2- import PhoneInput from 'react-phone-number-input' ;
3- import 'react-phone-number-input/style.css' ;
4- import './phone-input.css' ;
2+ import { getCountries , getCountryCallingCode } from 'react-phone-number-input/input' ;
3+ import { parsePhoneNumber , AsYouType , isValidPhoneNumber } from 'libphonenumber-js' ;
54import { cn } from './utils' ;
65
6+ // Import country flags
7+ import 'country-flag-icons/css/flag-icons.min.css' ;
8+
79export interface PhoneInputProps extends Omit < React . InputHTMLAttributes < HTMLInputElement > , 'onChange' | 'value' > {
810 value ?: string ;
911 onChange ?: ( value ?: string ) => void ;
1012 defaultCountry ?: string ;
1113 international ?: boolean ;
1214 className ?: string ;
1315 inputClassName ?: string ;
16+ selectClassName ?: string ;
1417}
1518
1619export const PhoneNumberInput = ( {
@@ -20,20 +23,101 @@ export const PhoneNumberInput = ({
2023 international = true ,
2124 className,
2225 inputClassName,
26+ selectClassName,
2327 ...props
2428} : PhoneInputProps & { ref ?: React . Ref < HTMLInputElement > } ) => {
29+ const [ selectedCountry , setSelectedCountry ] = React . useState ( defaultCountry ) ;
30+ const [ inputValue , setInputValue ] = React . useState ( '' ) ;
31+ const inputRef = React . useRef < HTMLInputElement > ( null ) ;
32+
33+ // Get list of countries
34+ const countries = React . useMemo ( ( ) => getCountries ( ) , [ ] ) ;
35+
36+ // Format the full phone number (with country code)
37+ const formatFullNumber = React . useCallback ( ( country : string , nationalNumber : string ) => {
38+ if ( ! nationalNumber ) return '' ;
39+
40+ const formatter = new AsYouType ( country ) ;
41+ const formatted = formatter . input ( nationalNumber ) ;
42+
43+ if ( international ) {
44+ return `+${ getCountryCallingCode ( country ) } ${ formatted . startsWith ( '+' ) ? formatted . substring ( 1 ) : formatted } ` ;
45+ }
46+
47+ return formatted ;
48+ } , [ international ] ) ;
49+
50+ // Initialize input value from props
51+ React . useEffect ( ( ) => {
52+ if ( value ) {
53+ try {
54+ const phoneNumber = parsePhoneNumber ( value ) ;
55+ if ( phoneNumber ) {
56+ setSelectedCountry ( phoneNumber . country || defaultCountry ) ;
57+ setInputValue ( phoneNumber . nationalNumber || '' ) ;
58+ }
59+ } catch ( error ) {
60+ // If parsing fails, just use the value as is
61+ setInputValue ( value ) ;
62+ }
63+ } else {
64+ setInputValue ( '' ) ;
65+ }
66+ } , [ value , defaultCountry ] ) ;
67+
68+ // Handle country change
69+ const handleCountryChange = ( e : React . ChangeEvent < HTMLSelectElement > ) => {
70+ const newCountry = e . target . value ;
71+ setSelectedCountry ( newCountry ) ;
72+
73+ // Update the full number with the new country code
74+ const fullNumber = formatFullNumber ( newCountry , inputValue ) ;
75+ onChange ?.( fullNumber ) ;
76+ } ;
77+
78+ // Handle input change
79+ const handleInputChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
80+ const newInput = e . target . value ;
81+ setInputValue ( newInput ) ;
82+
83+ // Update the full number
84+ const fullNumber = formatFullNumber ( selectedCountry , newInput ) ;
85+ onChange ?.( fullNumber ) ;
86+ } ;
87+
2588 return (
26- < div className = { cn ( 'phone-input-container' , className ) } >
27- < PhoneInput
28- value = { value }
29- onChange = { onChange }
30- defaultCountry = { defaultCountry }
31- international = { international }
32- className = { cn ( 'phone-input' , inputClassName ) }
89+ < div className = { cn ( 'flex gap-2' , className ) } >
90+ < select
91+ value = { selectedCountry }
92+ onChange = { handleCountryChange }
93+ className = { cn (
94+ 'flex h-10 w-auto text-base sm:text-sm rounded-md border border-input bg-background px-3 py-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50' ,
95+ selectClassName
96+ ) }
97+ aria-label = "Country code"
98+ >
99+ { countries . map ( ( country ) => (
100+ < option key = { country } value = { country } >
101+ { country } +{ getCountryCallingCode ( country ) }
102+ </ option >
103+ ) ) }
104+ </ select >
105+
106+ < input
107+ ref = { inputRef }
108+ type = "tel"
109+ value = { inputValue }
110+ onChange = { handleInputChange }
111+ className = { cn (
112+ 'flex h-10 w-full text-base sm:text-sm rounded-md border border-input bg-background px-3 py-2 ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50' ,
113+ inputClassName
114+ ) }
115+ data-slot = "input"
33116 { ...props }
34117 />
35118 </ div >
36119 ) ;
37120} ;
38121
39122PhoneNumberInput . displayName = 'PhoneNumberInput' ;
123+
0 commit comments