import React, { JSX, PureComponent } from "react"; import { View, Text, TouchableOpacity, Image, TextInput, Modal, FlatList, StyleProp, TextStyle, ViewStyle } from "react-native"; import { PhoneNumberUtil } from "google-libphonenumber"; import * as flags from "./flags/flagsIndex"; const phoneUtil = PhoneNumberUtil.getInstance(); // Types export interface Country { code: string; name: string; callingCode: string; } export interface PhoneInputTheme { // Background colors containerBackground?: string; inputBackground?: string; modalBackground?: string; modalOverlay?: string; // Text colors labelTextColor?: string; inputTextColor?: string; placeholderTextColor?: string; codeTextColor?: string; dropdownTextColor?: string; // Border colors inputBorderColor?: string; modalBorderColor?: string; // Selection and focus colors selectionColor?: string; // Flag styling flagBorderRadius?: number; flagSize?: number; flagShape?: 'round' | 'square'; // Dropdown arrow dropdownArrowColor?: string; dropdownArrowOpacity?: number; } export interface PhoneInputProps { defaultCode?: string; value?: string; defaultValue?: string; disabled?: boolean; withShadow?: boolean; withDarkTheme?: boolean; autoFocus?: boolean; placeholder?: string; disableArrowIcon?: boolean; layout?: "first" | "second" | "third"; showSearch?: boolean; searchPlaceholder?: string; codeLabel?: string; phoneNumberLabel?: string; onChangeCountry?: (country: Country) => void; onChangeText?: (text: string) => void; onChangeFormattedText?: (text: string) => void; containerStyle?: StyleProp; textContainerStyle?: StyleProp; textInputStyle?: StyleProp; codeTextStyle?: StyleProp; textInputProps?: any; theme?: PhoneInputTheme; // Custom render props renderFlag?: (country: Country) => React.ReactNode; renderInput?: (props: { value: string; onChangeText: (text: string) => void; placeholder?: string; disabled?: boolean; style?: StyleProp; }) => React.ReactNode; renderCountryItem?: (country: Country, onSelect: (country: Country) => void) => React.ReactNode; renderDropdownIcon?: () => React.ReactNode; renderCountryModal?: (props: { visible: boolean; onClose: () => void; onSelectCountry: (country: Country) => void; countries: Country[]; searchText: string; onSearchChange: (text: string) => void; }) => React.ReactNode; } export interface PhoneInputState { code: string; number: string; modalVisible: boolean; countryCode: string; disabled: boolean; searchText: string; } // Import countries data const countriesData = require('./countries.json'); // Use the comprehensive countries data const countries: Country[] = countriesData; const dropDown = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAi0lEQVRYR+3WuQ6AIBRE0eHL1T83FBqU5S1szdiY2NyTKcCAzU/Y3AcBXIALcIF0gRPAsehgugDEXnYQrUC88RIgfpuJ+MRrgFmILN4CjEYU4xJgFKIa1wB6Ec24FuBFiHELwIpQxa0ALUId9wAkhCnuBdQQ5ngP4I9wxXsBDyJ9m+8y/g9wAS7ABW4giBshQZji3AAAAABJRU5ErkJggg=="; // Default theme const defaultTheme: PhoneInputTheme = { // Background colors containerBackground: 'transparent', inputBackground: '#FFFFFF', modalBackground: '#FFFFFF', modalOverlay: 'rgba(0,0,0,0.5)', // Text colors labelTextColor: '#666666', inputTextColor: '#000000', placeholderTextColor: '#999999', codeTextColor: '#666666', dropdownTextColor: '#666666', // Border colors inputBorderColor: '#E5E5E5', modalBorderColor: '#EEEEEE', // Selection and focus colors selectionColor: '#007AFF', // Flag styling flagBorderRadius: 999, flagSize: 30, flagShape: 'round', // Dropdown arrow dropdownArrowColor: '#666666', dropdownArrowOpacity: 0.6, }; // Helper function to get flag image const getFlagImage = (countryCode: string) => { const code = countryCode.toLowerCase(); return (flags as any)[code] || flags.us; }; export default class PhoneInput extends PureComponent { constructor(props: PhoneInputProps) { super(props); const defaultCountry = countries.find(c => c.code === props.defaultCode) || countries[0]; this.state = { code: defaultCountry.callingCode, number: props.value ? props.value : props.defaultValue ? props.defaultValue : "", modalVisible: false, countryCode: props.defaultCode ? props.defaultCode : "US", disabled: props.disabled || false, searchText: "", }; } getCallingCode = (countryCode: string): string => { const country = countries.find(c => c.code === countryCode); return country ? country.callingCode : "1"; }; getCountryCode = (): string => { return this.state.countryCode; }; getCurrentCallingCode = (): string => { return this.state.code; }; isValidNumber = (number: string): boolean => { try { const { countryCode } = this.state; const parsedNumber = phoneUtil.parse(number, countryCode); return phoneUtil.isValidNumber(parsedNumber); } catch (err) { return false; } }; onSelectCountry = (country: Country): void => { const { onChangeCountry } = this.props; this.setState( { countryCode: country.code, code: country.callingCode, modalVisible: false, searchText: "", }, () => { const { onChangeFormattedText } = this.props; if (onChangeFormattedText) { if (country.callingCode) { onChangeFormattedText( `+${country.callingCode}${this.state.number}` ); } else { onChangeFormattedText(this.state.number); } } } ); if (onChangeCountry) { onChangeCountry(country); } }; onChangeText = (text: string): void => { this.setState({ number: text }); const { onChangeText, onChangeFormattedText } = this.props; if (onChangeText) { onChangeText(text); } if (onChangeFormattedText) { const { code } = this.state; if (code) { onChangeFormattedText(text.length > 0 ? `+${code}${text}` : text); } else { onChangeFormattedText(text); } } }; getNumberAfterPossiblyEliminatingZero = (): { number: string; formattedNumber: string } => { let { number, code } = this.state; if (number.length > 0 && number.startsWith("0")) { number = number.substr(1); return { number, formattedNumber: code ? `+${code}${number}` : number }; } else { return { number, formattedNumber: code ? `+${code}${number}` : number }; } }; renderDropdownImage = (): JSX.Element => { const { theme = {} } = this.props; const mergedTheme = { ...defaultTheme, ...theme }; return ( ); }; renderCountryItem = ({ item }: { item: Country }): JSX.Element => { const { theme = {} } = this.props; const mergedTheme = { ...defaultTheme, ...theme }; return ( this.onSelectCountry(item)} > {item.name} +{item.callingCode} ); }; render(): JSX.Element { const { withShadow, withDarkTheme, codeTextStyle, textInputProps, autoFocus, placeholder, disableArrowIcon, containerStyle, textContainerStyle, textInputStyle, layout = "first", showSearch = true, searchPlaceholder = "Search countries...", codeLabel = "Code", phoneNumberLabel = "Phone Number", renderFlag, renderInput, renderCountryItem, renderDropdownIcon, renderCountryModal, theme = {}, } = this.props; const { modalVisible, code, countryCode, number, disabled, searchText } = this.state; const selectedCountry = countries.find(c => c.code === countryCode); // Filter countries based on search text const filteredCountries = searchText ? countries.filter(country => country.name.toLowerCase().includes(searchText.toLowerCase()) || country.callingCode.includes(searchText) || country.code.toLowerCase().includes(searchText.toLowerCase()) ) : countries; // Merge theme with defaults const mergedTheme = { ...defaultTheme, ...theme }; return ( {/* Labels */} {codeLabel} {phoneNumberLabel} this.setState({ modalVisible: true })} > {renderFlag && selectedCountry ? ( renderFlag(selectedCountry) ) : ( {layout === "third" && selectedCountry && ( +{selectedCountry.callingCode} )} )} {code && layout === "second" && ( {`+${code}`} )} {!disableArrowIcon && (renderDropdownIcon ? renderDropdownIcon() : this.renderDropdownImage())} {code && layout === "first" && ( {`+${code}`} )} {renderInput ? ( renderInput({ value: number, onChangeText: this.onChangeText, placeholder: placeholder || "Phone Number", disabled: disabled, style: textInputStyle, }) ) : ( )} {renderCountryModal ? ( renderCountryModal({ visible: modalVisible, onClose: () => this.setState({ modalVisible: false, searchText: "" }), onSelectCountry: this.onSelectCountry, countries: filteredCountries, searchText: searchText, onSearchChange: (text: string) => this.setState({ searchText: text }), }) ) : ( this.setState({ modalVisible: false, searchText: "" })} > Select Country {showSearch && ( this.setState({ searchText: text })} /> )} { if (renderCountryItem) { const customItem = renderCountryItem(item, this.onSelectCountry); return customItem ? <>{customItem} : this.renderCountryItem({ item }); } return this.renderCountryItem({ item }); }} keyExtractor={(item) => item.code} style={{ maxHeight: 400 }} /> this.setState({ modalVisible: false, searchText: "" })} > Cancel )} ); } } export const isValidNumber = (number: string, countryCode: string): boolean => { try { const parsedNumber = phoneUtil.parse(number, countryCode); return phoneUtil.isValidNumber(parsedNumber); } catch (err) { return false; } };