UNPKG

react-native-international-phone-number

Version:
888 lines (745 loc) โ€ข 22.3 kB
<br> <div align = "center"> <img src="lib/assets/images/preview.png" alt="React Native International Phone Number Input Lib preview"> </div> <br> <h1 align="center">React Native International Phone Number Input</h1> <p align="center"> <a href="https://www.npmjs.com/package/react-native-international-phone-number"> <img src="https://img.shields.io/npm/v/react-native-international-phone-number.svg?style=flat-square"> </a> <a href="https://www.npmjs.com/package/react-native-international-phone-number"> <img src="https://img.shields.io/npm/dt/react-native-international-phone-number.svg?style=flat-square&color=success"> </a> <a href="https://github.com/AstrOOnauta/react-native-international-phone-number"> <img src="https://img.shields.io/github/stars/AstrOOnauta/react-native-international-phone-number?style=flat-square&color=success"/> </a> <a href="https://github.com/AstrOOnauta/react-native-international-phone-number/issues"> <img src="https://img.shields.io/github/issues/AstrOOnauta/react-native-international-phone-number?style=flat-square&color=blue"/> </a> <a href="https://github.com/AstrOOnauta/react-native-international-phone-number/pulls"> <img src="https://img.shields.io/github/issues-pr/AstrOOnauta/react-native-international-phone-number?style=flat-square&color=blue"/> </a> <a href="LICENSE.md"> <img src="https://img.shields.io/:license-isc-yellow.svg?style=flat-square"/> </a> </p> <br> <div align = "center"> <a href="https://www.buymeacoffee.com/astroonauta" target="_blank"> <img src="https://survivingmexico.files.wordpress.com/2018/07/button-gif.gif" alt="Buy Me A Coffee" style="height: auto !important;width: 60% !important;"> </a> </div> <br> ## Features - ๐Ÿ“ฑ Works with iOS, Android (Cross-platform), Expo and Web; - โœ… Check phone number validation; - ๐ŸŽจ Lib with UI customizable; - ๐ŸŒŽ Phone Input Mask according with the selected country; - ๐Ÿ‘จโ€๐Ÿ’ป Functional and class component support; - ๐Ÿˆถ 22 languages supported. <br> ## Try it out - [Demo](https://snack.expo.dev/@astroonauta/react-native-international-phone-number) <br> ## List of Contents - [Old Versions](#old-versions) - [Installation](#installation) - [Additional Config](#additional-config) - [React Native CLI](#using-react-native-cli) - [Expo](#using-expo) - [Web](#using-web) - [Basic Usage](#basic-usage) - [With Class Component](#class-component) - [With Function Component](#function-component) - [With Typescript](#typescript) - [Intermediate Usage](#intermediate-usage) - [Typescript + useRef](#typescript--useref) - [Advanced Usage](#advanced-usage) - [React-Hook-Form + Typescript + Default Phone Number Value](#react-hook-form--typescript--default-phone-number-value) - [Customizing Lib](#customizing-lib) - [Dark Mode](#dark-mode) - [Custom Lib Styles](#custom-lib-styles) - [Custom Caret](#custom-caret) - [Custom Placeholders/Messages](#custom-placeholdersmessages) - [Custom Modal Height](#custom-modal-height) - [Country Modal Disabled Mode](#country-modal-disabled-mode) - [Phone Input Disabled Mode](#phone-input-disabled-mode) - [Custom Disabled Mode Style](#custom-disabled-mode-style) - [Change Default Language](#change-default-language) - [Custom Phone Mask](#custom-phone-mask) - [Custom Default Flag/Country](#custom-default-flagcountry) - [Default Phone Number Value](#default-phone-number-value) - [Show Only Some Countries Inside Modal](#show-only-some-countries) - [Exclude some countries Inside Modal](#exclude-some-countries) - [Show Popular Countries at the Top of the Countries List Inside Modal](#show-popular-countries-at-the-top-of-the-countries-list-inside-modal) - [Custom Modal Section Titles](#custom-modal-section-titles) - [Hide the Modal Section Titles](#hide-the-modal-section-titles) - [Right to Left Input](#right-to-left-input) - [Dont allow Zero After Calling Code](#dont-allow-zero-after-calling-code) - [Lib Props](#component-props-phoneinputprops) - [Lib Functions](#functions) - [Supported languages](#๐ŸŽŒ-supported-languages-๐ŸŽŒ) - [Testing](#testing) - [Contributing](#contributing) - [License](#license) <br> ## Old Versions - [Version 0.8.x](https://github.com/AstrOOnauta/react-native-international-phone-number/tree/v0.8.x) - [Version 0.7.x](https://github.com/AstrOOnauta/react-native-international-phone-number/tree/v0.7.x) - [Version 0.6.x](https://github.com/AstrOOnauta/react-native-international-phone-number/tree/v0.6.x) - [Version 0.5.x](https://github.com/AstrOOnauta/react-native-international-phone-number/tree/v0.5.x) - [Version 0.4.x](https://github.com/AstrOOnauta/react-native-international-phone-number/tree/v0.4.x) <br> ## Installation ```bash $ npm i --save react-native-international-phone-number ``` OR ```bash $ yarn add react-native-international-phone-number ``` ## Additional config - ### Using React Native CLI: Create a `react-native.config.js` file at the root of your react-native project with: ```bash module.exports = { project: { ios: {}, android: {}, }, assets: [ './node_modules/react-native-international-phone-number/lib/assets/fonts', ], }; ``` Then link the font to your native projects with: ```bash npx react-native-asset ``` - ### Using Expo: 1. Install [expo-fonts](https://docs.expo.dev/versions/latest/sdk/font/): `npx expo install expo-font`; 2. Initialize the `expo-font`: ```bash import { useFonts } from 'expo-font'; ... useFonts({ 'TwemojiMozilla': require('./node_modules/react-native-international-phone-number/lib/assets/fonts/TwemojiMozilla.woff2'), }); ... ``` > Observation: you need to recompile your project after adding new fonts. <br> ## Basic Usage - ### Class Component: ```jsx import React from 'react'; import { View, Text } from 'react-native'; import PhoneInput, { isValidPhoneNumber } from 'react-native-international-phone-number'; export class App extends React.Component { constructor(props) { super(props) this.state = { selectedCountry: null, inputValue: '' } } function handleSelectedCountry(country) { this.setState({ selectedCountry: country }) } function handleInputValue(phoneNumber) { this.setState({ inputValue: phoneNumber }) } render(){ return ( <View style={{ width: '100%', flex: 1, padding: 24 }}> <PhoneInput value={this.state.inputValue} onChangePhoneNumber={this.handleInputValue} selectedCountry={this.state.selectedCountry} onChangeSelectedCountry={this.handleSelectedCountry} /> <View style={{ marginTop: 10 }}> <Text> Country:{' '} {`${this.state.selectedCountry?.name?.en} (${this.state.selectedCountry?.cca2})`} </Text> <Text> Phone Number: {`${this.state.selectedCountry?.callingCode} ${this.state.inputValue}`} </Text> <Text> isValid:{' '} {isValidPhoneNumber(this.state.inputValue, this.state.selectedCountry) ? 'true' : 'false'} </Text> </View> </View> ); } } ``` - ### Function Component: ```jsx import React, { useState } from 'react'; import { View, Text } from 'react-native'; import PhoneInput, { isValidPhoneNumber, } from 'react-native-international-phone-number'; export default function App() { const [selectedCountry, setSelectedCountry] = useState(null); const [inputValue, setInputValue] = useState(''); function handleInputValue(phoneNumber) { setInputValue(phoneNumber); } function handleSelectedCountry(country) { setSelectedCountry(country); } return ( <View style={{ width: '100%', flex: 1, padding: 24 }}> <PhoneInput value={inputValue} onChangePhoneNumber={handleInputValue} selectedCountry={selectedCountry} onChangeSelectedCountry={handleSelectedCountry} /> <View style={{ marginTop: 10 }}> <Text> Country:{' '} {`${selectedCountry?.name?.en} (${selectedCountry?.cca2})`} </Text> <Text> Phone Number:{' '} {`${selectedCountry?.callingCode} ${inputValue}`} </Text> <Text> isValid:{' '} {isValidPhoneNumber(inputValue, selectedCountry) ? 'true' : 'false'} </Text> </View> </View> ); } ``` - ### Typescript ```tsx import React, { useState } from 'react'; import { View, Text } from 'react-native'; import PhoneInput, { ICountry, isValidPhoneNumber, } from 'react-native-international-phone-number'; export default function App() { const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null); const [inputValue, setInputValue] = useState<string>(''); function handleInputValue(phoneNumber: string) { setInputValue(phoneNumber); } function handleSelectedCountry(country: ICountry) { setSelectedCountry(country); } return ( <View style={{ width: '100%', flex: 1, padding: 24 }}> <PhoneInput value={inputValue} onChangePhoneNumber={handleInputValue} selectedCountry={selectedCountry} onChangeSelectedCountry={handleSelectedCountry} /> <View style={{ marginTop: 10 }}> <Text> Country:{' '} {`${selectedCountry?.name?.en} (${selectedCountry?.cca2})`} </Text> <Text> Phone Number:{' '} {`${selectedCountry?.callingCode} ${inputValue}`} </Text> <Text> isValid:{' '} {isValidPhoneNumber(inputValue, selectedCountry) ? 'true' : 'false'} </Text> </View> </View> ); } ``` <br> ## Intermediate Usage - ### Typescript + useRef ```tsx import React, { useRef } from 'react'; import { View, Text } from 'react-native'; import PhoneInput, { ICountry, IPhoneInputRef, } from 'react-native-international-phone-number'; export default function App() { const phoneInputRef = useRef<IPhoneInputRef>(null); function onSubmitRef() { Alert.alert( 'Intermediate Result', `Country: ${inputRef.current?.selectedCountry?.name?.en} \nPhone Number: ${inputRef.current?.fullPhoneNumber} \nisValid: ${inputRef.current?.isValid}` ); } return ( <View style={{ width: '100%', flex: 1, padding: 24 }}> <PhoneInput ref={phoneInputRef} /> <TouchableOpacity style={{ width: '100%', paddingVertical: 12, backgroundColor: '#2196F3', borderRadius: 4, marginTop: 10, }} onPress={onSubmit} > <Text style={{ color: '#F3F3F3', textAlign: 'center', fontSize: 16, fontWeight: 'bold', }} > Submit </Text> </TouchableOpacity> </View> ); } ``` > Observation: Don't use the useRef hook combined with the useState hook to manage the phoneNumber and selectedCountry values. Instead, choose to use just one of them (useRef or useState). <br> ## Advanced Usage - ### React-Hook-Form + Typescript + Default Phone Number Value ```tsx import React, { useState, useEffect } from 'react'; import { View, Text, TouchableOpacity, Alert } from 'react-native'; import PhoneInput, { ICountry, isValidPhoneNumber, } from 'react-native-international-phone-number'; import { Controller, FieldValues } from 'react-hook-form'; interface FormProps extends FieldValues { phoneNumber: string; } export default function App() { const [selectedCountry, setSelectedCountry] = useState< undefined | ICountry >(undefined); function handleSelectedCountry(country: ICountry) { setSelectedCountry(country); } function onSubmit(form: FormProps) { const phoneNumber = `${selectedCountry?.callingCode} ${form.phoneNumber}`; const isValid = isValidPhoneNumber( form.phoneNumber, selectedCountry as ICountry ); Alert.alert( 'Advanced Result', `Country: ${selectedCountry?.name?.en} \nPhone Number: ${phoneNumber} \nisValid: ${isValid}` ); } return ( <View style={{ width: '100%', flex: 1, padding: 24 }}> <Controller name="phoneNumber" control={control} render={({ field: { onChange, value } }) => ( <PhoneInput defaultValue="+12505550199" value={value} onChangePhoneNumber={onChange} selectedCountry={selectedCountry} onChangeSelectedCountry={handleSelectedCountry} /> )} /> <TouchableOpacity style={{ width: '100%', paddingVertical: 12, backgroundColor: '#2196F3', borderRadius: 4, }} onPress={handleSubmit(onSubmit)} > <Text style={{ color: '#F3F3F3', textAlign: 'center', fontSize: 16, fontWeight: 'bold', }} > Submit </Text> </TouchableOpacity> </View> ); } ``` > Observations: > > 1. You need to use a default value with the following format: `+(country callling code)(area code)(number phone)` > 2. The lib has the mechanism to set the flag and mask of the supplied `defaultValue`. However, if the supplied `defaultValue` does not match any international standard, the `input mask of the defaultValue` will be set to "BR" (please make sure that the default value is in the format mentioned above). <br> ## Customizing lib <img src="lib/assets/images/custom-styles.png" alt="Custom lib styles"> - ### Dark Mode: ```jsx ... <PhoneInput ... theme="dark" /> ... ``` - ### Custom Lib Styles: ```jsx ... <PhoneInput ... phoneInputStyles={{ container: { backgroundColor: '#575757', borderWidth: 1, borderStyle: 'solid', borderColor: '#F3F3F3', }, flagContainer: { borderTopLeftRadius: 7, borderBottomLeftRadius: 7, backgroundColor: '#808080', justifyContent: 'center', }, flag: {}, caret: { color: '#F3F3F3', fontSize: 16, }, divider: { backgroundColor: '#F3F3F3', } callingCode: { fontSize: 16, fontWeight: 'bold', color: '#F3F3F3', }, input: { color: '#F3F3F3', }, }} modalStyles={{ modal: { backgroundColor: '#333333', borderWidth: 1, }, backdrop: {}, divider: { backgroundColor: 'transparent', }, countriesList: {}, searchInput: { borderRadius: 8, borderWidth: 1, borderColor: '#F3F3F3', color: '#F3F3F3', backgroundColor: '#333333', paddingHorizontal: 12, height: 46, }, countryButton: { borderWidth: 1, borderColor: '#F3F3F3', backgroundColor: '#666666', marginVertical: 4, paddingVertical: 0, }, noCountryText: {}, noCountryContainer: {}, flag: { color: '#FFFFFF', fontSize: 20, }, callingCode: { color: '#F3F3F3', }, countryName: { color: '#F3F3F3', }, sectionTitle: { marginVertical: 10, color: '#F3F3F3', } }} /> ... ``` - ### Custom Caret: ```jsx ... <PhoneInput ... customCaret={<Icon name="chevron-down" size={30} color="#000000" />} // react-native-vector-icons /> ... ``` - ### Custom Placeholders/Messages: ```jsx ... <PhoneInput ... placeholder="Custom Phone Input Placeholder" modalSearchInputPlaceholder="Custom Modal Search Input Placeholder" modalNotFoundCountryMessage="Custom Modal Not Found Country Message" /> ... ``` - ### Custom Modal Height: ```jsx ... <PhoneInput ... modalHeight="80%" /> ... ``` - ### Country Modal Disabled Mode: ```jsx ... <PhoneInput ... modalDisabled /> ... ``` - ### Phone Input Disabled Mode: ```jsx ... <PhoneInput ... disabled /> ... ``` - ### Custom Disabled Mode Style: ```jsx const [isDisabled, setIsDisabled] = useState<boolean>(true) ... <PhoneInput ... containerStyle={ isDisabled ? { backgroundColor: 'yellow' } : {} } disabled={isDisabled} /> ... ``` - ### Change Default Language: ```jsx ... <PhoneInput ... language="pt" /> ... ``` - ### Custom Phone Mask: ```jsx ... <PhoneInput ... customMask={['#### ####', '##### ####']} /> ... ``` - ### Custom Default Flag/Country: ```jsx ... <PhoneInput ... defaultCountry="CA" /> ... ``` - ### Default Phone Number Value: ```jsx ... <PhoneInput ... defaultValue="+12505550199" /> ... ``` > Observations: > > 1. You need to use a default value with the [e164](https://en.wikipedia.org/wiki/E.164) format: `+(country callling code)(area code)(number phone)` > 2. The lib has the mechanism to set the flag and mask of the supplied `defaultValue`. However, if the supplied `defaultValue` does not match any international standard, the `input mask of the defaultValue` will be set to "BR" (please make sure that the default value is in the format mentioned above). - ### Show Only Some Countries Inside Modal: ```jsx ... <PhoneInput ... showOnly={['BR', 'PT', 'CA', 'US']} /> ... ``` - ### Exclude Some Countries Inside Modal: ```jsx ... <PhoneInput ... excludedCountries={['BR', 'PT', 'CA', 'US']} /> ... ``` - ### Show Popular Countries at the Top of the Countries List Inside Modal: ```jsx ... <PhoneInput ... popularCountriess={['BR', 'PT', 'CA', 'US']} /> ... ``` - ### Custom Modal Section Titles: ```jsx ... <PhoneInput ... popularCountriess={['BR', 'PT', 'CA', 'US']} popularCountriesSectionTitle='Suggested' restOfCountriesSectionTitle='All' /> ... ``` - ### Hide the Modal Section Titles: ```jsx ... <PhoneInput ... popularCountriess={['BR', 'PT', 'CA', 'US']} modalSectionTitleDisabled /> ... ``` - ### Right to Left Input: ```jsx import { I18nManager } from "react-native"; ... <PhoneInput ... rtl={I18nManager.isRTL} /> ... ``` - ### Don't allow Zero After Calling Code: ```jsx ... <PhoneInput ... allowZeroAfterCallingCode={false} /> ... ``` </br> ## Component Props ([PhoneInputProps](lib/interfaces/phoneInputProps.ts)) - `language?:` [ILanguage](lib/interfaces/language.ts); - `customMask?:` string[]; - `defaultValue?:` string; - `value?:` string; - `onChangePhoneNumber?:` (phoneNumber: string) => void; - `defaultCountry?:` [ICountryCca2](lib/interfaces/countryCca2.ts); - `selectedCountry?:` [ICountry](lib/interfaces/country.ts); - `onChangeSelectedCountry?:` (country: [ICountry](lib/interfaces/country.ts)) => void; - `showOnly?:` [ICountryCca2[]](lib/interfaces/countryCca2.ts); - `excludedCountries?:` [ICountryCca2[]](lib/interfaces/countryCca2.ts); - `popularCountries?:` [ICountryCca2[]](lib/interfaces/countryCca2.ts); - `popularCountriesSectionTitle?:` string; - `restOfCountriesSectionTitle?:` string; - `modalSectionTitleDisabled?:` boolean; - `rtl?:` boolean; - `disabled?:` boolean; - `modalDisabled?:` boolean; - `modalHeight?:` number | string; - `theme?:` [ITheme](lib/interfaces/theme.ts); - `phoneInputStyles?:` [IPhoneInputStyles](lib/interfaces/phoneInputStyles.ts); - `modalStyles?:` [IModalStyles](lib/interfaces/modalStyles.ts); - `modalSearchInputPlaceholder?:` string; - `modalSearchInputPlaceholderTextColor?:` string; - `modalSearchInputSelectionColor?:` string; - `modalNotFoundCountryMessage?:` string; - `customCaret?:` [ReactNode](https://reactnative.dev/docs/react-node); - `ref?:` [Ref](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/663f439d11d78b65f1dfd38d120f3728ea2cc207/types/react/index.d.ts#L100)<[IPhoneInputRef](lib/interfaces/phoneInputRef.ts)>; - `allowZeroAfterCallingCode?:` boolean. <br> ## Functions - `getAllCountries:` () => [ICountry](lib/interfaces/country.ts)[]; - `getCountriesByCallingCode:` (callingCode: string) => [ICountry](lib/interfaces/country.ts)[] | undefined; - `getCountryByCca2:` (cca2: string) => [ICountry](lib/interfaces/country.ts) | undefined; - `getCountriesByName:` (name: string, language: [ILanguage](lib/interfaces/language.ts)) => [ICountry](lib/interfaces/country.ts)[] | undefined; - `getCountryByPhoneNumber:` (phoneNumber: string) => [ICountry](lib/interfaces/country.ts) | undefined; - `isValidPhoneNumber:` (phoneNumber: string, country: [ICountry](lib/interfaces/country.ts)) => boolean. </br> ## ๐ŸŽŒ Supported languages ๐ŸŽŒ ```js "name": { "bg": "Bulgarian", "by": "Belarusian", "cn": "Chinese", "cz": "Czech", "de": "German", "ee": "Estonian", "el": "Greek", "en": "English", "es": "Espanol", "fr": "French", "he": "Hebrew", "it": "Italian", "jp": "Japanese", "nl": "Dutch", "pl": "Polish", "pt": "Portuguese", "ro": "Romanian", "ru": "Russian", "ua": "Ukrainian", "zh": "Chinese (Simplified)", "ar": "Arabic", "tr": "Turkish" }, ``` <br> ## Testing When utilizing this package, you may need to target the PhoneInput component in your automated tests. To facilitate this, we provide a testID props for the PhoneInput component. The testID can be integrated with popular testing libraries such as @testing-library/react-native or Maestro. This enables you to efficiently locate and interact with PhoneInput elements within your tests, ensuring a robust and reliable testing experience. ```jsx // Assuming PhoneInput has testID="phone-number" const phoneInput = getByTestId('phone-number-flag'); ``` <br> ## Contributing - Fork or clone this repository ```bash $ git clone https://github.com/AstrOOnauta/react-native-international-phone-number.git ``` - Repair, Update and Enjoy ๐Ÿ› ๏ธ๐Ÿšงโš™๏ธ - Create a new PR to this repository <br> ## License [ISC](LICENSE.md) <br> <div align = "center"> <br> Thanks for stopping by! ๐Ÿ˜ <br> </div>