@artmajeur/react-native-paper-phone-number-input
Version:
A performant phone number input component for react-native-paper with country picker
230 lines (226 loc) • 7.25 kB
JavaScript
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
import { FlatList, Platform, PlatformColor, StyleSheet, View } from 'react-native';
import { DataTable, IconButton, Modal, Portal, Text, TextInput, TouchableRipple } from 'react-native-paper';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { isIOS } from './constants';
import { countries } from './data/countries';
import translatedCountries from './data/translatedCountries';
import { useCountriesList, useCountrySearch } from './hooks';
import { useDebouncedValue } from './use-debounced-value';
import useThemeWithFlagsFont from './useThemeWithFlagsFont';
export const CountryPicker = /*#__PURE__*/forwardRef(({
country,
setCountry,
showFirstOnList,
modalStyle,
modalContainerStyle,
includeCountries,
excludeCountries,
// Prpos from TextInput that needs special handling
disabled,
editable = true,
theme,
lang = 'fr',
placeholder = '',
searchLabel = '',
// rest of the props
...rest
}, ref) => {
const insets = useSafeAreaInsets();
const themeWithFlagsFont = useThemeWithFlagsFont(theme);
// States for the modal
const [visible, setVisible] = useState(false);
const countryFlag = useMemo(() => {
if (country) {
const matchedCountry = countries.find(c => c.code.toLocaleLowerCase() === country.toLocaleLowerCase());
return matchedCountry?.flag;
}
return undefined;
}, [country]);
// States for the searchbar
const [searchQuery, setSearchQuery] = useState('');
const debouncedSearchQuery = useDebouncedValue(searchQuery, 300);
const searchbarRef = useRef(null);
const openModal = useCallback(() => {
setVisible(true);
}, []);
const closeModal = useCallback(() => {
setVisible(false);
}, []);
// Focus the search bar when the modal becomes visible
useEffect(() => {
if (visible) {
// We need a small delay to ensure the modal is fully animated and the search bar is rendered
setTimeout(() => {
searchbarRef.current?.focus();
}, 100);
}
}, [visible]);
useImperativeHandle(ref, () => ({
openCountryPicker: openModal,
closeCountryPicker: closeModal
}), [openModal, closeModal]);
const countriesList = useCountriesList({
showFirstOnList,
includeCountries,
excludeCountries
});
const searchResult = useCountrySearch({
searchQuery: debouncedSearchQuery,
countriesList,
lang
});
const handleCountrySelect = useCallback(selectedCountry => {
setCountry(selectedCountry.code);
closeModal();
}, [setCountry, closeModal]);
const renderCountryItem = useCallback(({
item
}) => /*#__PURE__*/React.createElement(DataTable.Row, {
onPress: () => handleCountrySelect(item),
theme: theme
}, /*#__PURE__*/React.createElement(DataTable.Cell, {
theme: themeWithFlagsFont
}, `${item.flag} ${translatedCountries.getName(item.code, lang) || item.name}`)), [handleCountrySelect, theme, themeWithFlagsFont, lang]);
const keyExtractor = useCallback(item => item.code, []);
const value = useMemo(() => {
if (country && countryFlag) {
return `${countryFlag} ${translatedCountries.getName(country, lang)}`;
}
return placeholder;
}, [country, countryFlag, lang, placeholder]);
// Dynamic styles based on theme
const dynamicStyles = useMemo(() => ({
searchbar: {
flex: 1
},
searchbarContent: {
backgroundColor: 'transparent',
fontSize: 16
},
outlined: {
borderRadius: 30,
borderColor: theme?.dark ? '#343740' : '#CBD5E1'
}
}), [theme]);
return /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement(TextInput, _extends({
right: /*#__PURE__*/React.createElement(TextInput.Icon, {
icon: "chevron-down"
})
}, rest, {
disabled: disabled,
editable: editable,
value: value,
theme: themeWithFlagsFont
})), /*#__PURE__*/React.createElement(TouchableRipple, {
disabled: disabled || !editable,
style: styles.ripple,
onPress: openModal,
theme: theme
}, /*#__PURE__*/React.createElement(Text, null, " ")), /*#__PURE__*/React.createElement(Portal, {
theme: theme
}, /*#__PURE__*/React.createElement(Modal, {
style: [styles.modal, modalStyle],
contentContainerStyle: [styles.countries, {
backgroundColor: themeWithFlagsFont.colors.background,
paddingTop: insets.top + 16,
paddingBottom: insets.bottom + 16
},, modalContainerStyle],
visible: visible,
onDismiss: closeModal,
theme: theme
}, /*#__PURE__*/React.createElement(View, {
style: styles.searchbox
}, /*#__PURE__*/React.createElement(IconButton, {
icon: "arrow-left",
onPress: closeModal,
theme: theme
}), /*#__PURE__*/React.createElement(TextInput, {
style: [styles.searchbar, dynamicStyles.searchbar],
placeholder: searchLabel,
onChangeText: setSearchQuery,
value: searchQuery,
ref: searchbarRef,
mode: "outlined",
dense: true,
theme: theme,
onKeyPress: ({
nativeEvent
}) => {
if (nativeEvent.key === 'Escape') {
closeModal();
}
},
selectionColor: Platform.select({
ios: PlatformColor('systemBlue'),
android: PlatformColor('@android:color/holo_blue_light')
}),
cursorColor: Platform.select({
android: PlatformColor('@android:color/holo_blue_light')
}),
left: /*#__PURE__*/React.createElement(TextInput.Icon, {
icon: "magnify",
size: 20,
style: styles.searchIcon
}),
underlineStyle: styles.searchbarUnderline,
contentStyle: [styles.searchbarContent, dynamicStyles.searchbarContent],
outlineStyle: [styles.outlined, dynamicStyles.outlined]
})), /*#__PURE__*/React.createElement(DataTable, {
style: styles.flex1
}, /*#__PURE__*/React.createElement(FlatList, {
keyboardShouldPersistTaps: "handled",
data: searchResult,
keyExtractor: keyExtractor,
renderItem: renderCountryItem,
removeClippedSubviews: true,
maxToRenderPerBatch: 20,
updateCellsBatchingPeriod: 50,
initialNumToRender: 15,
windowSize: 10
})))));
});
const styles = StyleSheet.create({
ripple: {
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0
},
flex1: {
flex: isIOS ? undefined : 1
},
modal: {
marginTop: undefined,
marginBottom: undefined,
justifyContent: undefined
},
countries: {
paddingHorizontal: 16,
flex: isIOS ? undefined : 1,
marginBottom: isIOS ? 150 : undefined,
justifyContent: undefined
},
searchbox: {
flexDirection: 'row'
},
searchbar: {
flex: 1
},
searchbarContent: {
backgroundColor: 'transparent'
},
searchbarUnderline: {
display: 'none'
},
outlined: {
borderRadius: 30
},
searchIcon: {
alignSelf: 'center',
marginTop: 15
}
});
//# sourceMappingURL=CountryPicker.js.map