react-native-phone-country-picker-input
Version:
A React Native component to input phone numbers with a country picker modal.
166 lines (165 loc) • 8.45 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useMemo, useState } from 'react';
import { FlatList, Keyboard, KeyboardAvoidingView, Modal, Platform, Pressable, StyleSheet, Text, TextInput, View, } from 'react-native';
import { Countries } from '../data/CountryList';
import { COUNTRY_COMMON_NAMES } from '../data/CountryCommonNames';
/**
* Default country selection modal with search functionality.
*
* @param {CountryPickerModalProps} props - Modal props
* @param {boolean} props.modalVisible - Controls modal visibility
* @param {(visible: boolean) => void} props.setModalVisible - Modal visibility setter
* @param {CountryPickerModalStyles} props.customStyles - Custom style overrides
* @param {FlatListProps<Country>} props.otherFlatListProps - Additional FlatList props
* @param {string|React.ReactNode} props.searchIcon - Search icon (emoji or component)
* @param {string} props.countrySearchPlaceholder - Search input placeholder
* @param {TextInputProps} props.otherCountrySearchTextInputProps - Additional search input props
* @param {(country: Country) => void} props.setSelectedCountry - Country selection handler
*
* @featured
* - Smart country search (name, code, dial code, common aliases)
* - Customizable appearance
* - Accessibility support
*
* @returns {React.JSX.Element} Country selection modal
*/
export const CountryPickerModal = ({
// Modal Control
modalVisible, // Tracks if modal is currently visible
setModalVisible, // Controls modal visibility state
customStyles, // Allows complete style customization
// List Configuration
otherFlatListProps, // Extended FlatList customization
// Search Configuration
searchIcon = '🔍', // Defaults to magnifier emoji (supports custom components)
countrySearchPlaceholder = 'Search countries', // Search input placeholder text
otherCountrySearchTextInputProps, // Additional search input props
// Selection Handling
setSelectedCountry, // Callback when country is selected
}) => {
// Current search query state
const [searchText, setSearchText] = useState('');
// Gets alternative names for countries (e.g., "USA" for "United States")
function getCommonNames(country) {
return COUNTRY_COMMON_NAMES[country.iso2] || [];
}
// Memoized filtered country list based on search
const filteredCountries = useMemo(() => {
if (!searchText.trim())
return Countries;
const searchLower = searchText.toLowerCase();
return Countries.filter(country => {
const nameLower = country.name.toLowerCase();
const iso2Lower = country.iso2.toLowerCase();
const commonNames = getCommonNames(country);
// Multi-criteria search:
return (
// Exact matches
nameLower === searchLower ||
iso2Lower === searchLower ||
// Partial matches
nameLower.includes(searchLower) ||
iso2Lower.includes(searchLower) ||
// Common name matches
commonNames.some(n => n.toLowerCase().includes(searchLower)) ||
// Dial code matches (e.g., "+1")
country.dialCode.includes(searchText));
});
}, [searchText]);
/**
* Handles country selection, updating the selected country state, closing the
* modal, and clearing the search filter.
*
* @param {Country} country - Country object from the country list.
*/
const handleCountrySelect = (country) => {
setSelectedCountry(country);
setModalVisible(false);
setSearchText(''); // Clear the search filter
Keyboard.dismiss();
};
return (_jsx(Modal, { transparent: true, visible: modalVisible, accessible: true, accessibilityLabel: "Country picker modal", animationType: "slide", onRequestClose: () => setModalVisible(false), children: _jsx(Pressable, { style: { flex: 1 }, onPress: () => setModalVisible(false), children: _jsx(KeyboardAvoidingView, { behavior: Platform.OS === 'ios' ? 'padding' : undefined, style: { flex: 1 }, children: _jsx(View, { style: (customStyles === null || customStyles === void 0 ? void 0 : customStyles.modalOverlay) || styles.modalOverlay, children: _jsxs(Pressable, { onPress: e => {
e.stopPropagation();
Keyboard.dismiss();
}, style: (customStyles === null || customStyles === void 0 ? void 0 : customStyles.modalContent) || styles.modalContent, children: [_jsx(Text, { style: (customStyles === null || customStyles === void 0 ? void 0 : customStyles.modalTitle) || styles.modalTitle, children: "Select a country" }), _jsxs(View, { style: (customStyles === null || customStyles === void 0 ? void 0 : customStyles.modalSearchContainer) || styles.searchContainer, children: [typeof searchIcon === 'string' ? (_jsx(Text, { style: (customStyles === null || customStyles === void 0 ? void 0 : customStyles.modalSearchIcon) || styles.searchIcon, children: searchIcon })) : (searchIcon), _jsx(TextInput, Object.assign({ value: searchText, onChangeText: setSearchText, placeholder: countrySearchPlaceholder }, otherCountrySearchTextInputProps, { style: (customStyles === null || customStyles === void 0 ? void 0 : customStyles.modalSearchInput) || styles.modalSearchInput }))] }), _jsx(FlatList, Object.assign({ nestedScrollEnabled: true, keyboardShouldPersistTaps: "handled" // Better keyboard handling
, data: filteredCountries, keyExtractor: item => item.iso2 }, otherFlatListProps, { renderItem: ({ item }) => (_jsxs(Pressable, { onPress: () => handleCountrySelect(item), style: ({ pressed }) => [
(customStyles === null || customStyles === void 0 ? void 0 : customStyles.countryListContainer) ||
styles.countryListContainer,
pressed && { opacity: 0.7 }, // Visual feedback on press
], children: [_jsx(Text, { style: (customStyles === null || customStyles === void 0 ? void 0 : customStyles.countryListFlag) || styles.countryListFlag, children: item.flag }), _jsx(Text, { style: (customStyles === null || customStyles === void 0 ? void 0 : customStyles.countryName) || styles.countryName, children: item.name }), _jsxs(Text, { style: (customStyles === null || customStyles === void 0 ? void 0 : customStyles.countryListDialCode) ||
styles.countryListDialCode, children: ["(", item.dialCode, ")"] })] })) }))] }) }) }) }) }));
};
/**
* Default styles for the component
*/
const styles = StyleSheet.create({
modalOverlay: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.4)',
justifyContent: 'flex-end',
},
modalContent: {
height: '50%',
backgroundColor: '#fff',
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
padding: 20,
},
modalTitle: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: '5%',
textAlign: 'center',
},
searchContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: 10,
marginBottom: '5%',
borderWidth: 1,
borderRadius: 50,
borderColor: 'rgb(156, 156, 156)',
paddingVertical: 2,
paddingHorizontal: 10,
backgroundColor: 'rgb(221, 221, 221)',
shadowColor: 'rgba(18, 18, 19, 1)',
shadowOffset: { width: 1.5, height: 1.5 },
shadowOpacity: 0.25,
shadowRadius: 6,
},
searchIcon: {
fontSize: 18,
fontWeight: '400',
paddingLeft: 5,
},
modalSearchInput: {
width: '90%',
fontSize: 18,
fontWeight: '400',
borderRadius: 50,
},
countryListContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
paddingVertical: 10,
paddingHorizontal: 10,
backgroundColor: 'rgb(221, 221, 221)',
borderRadius: 6,
marginBottom: '2%',
},
countryListFlag: {
fontSize: 20,
fontWeight: '400',
},
countryName: {
fontSize: 20,
fontWeight: '400',
marginLeft: '5%',
},
countryListDialCode: {
fontSize: 20,
fontWeight: '400',
marginLeft: '2%',
},
});