react-native-international-phone-number
Version:
International mobile phone input component with mask for React Native
641 lines (531 loc) โข 26.2 kB
Markdown
<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
- ๐ **Phone Input Mask** โ Auto-formatting per selected country
- โ
**Validation** - Check phone number;
- ๐ฑ **Cross-Platform** โ Works seamlessly on **iOS, Android and Web**;
- ๐งฉ **Flexible Integration** โ Supports both **React Native CLI & Expo**;
- ๐จโ๐ป **Component Versatility** - Works with **functional & class components**;
- ๐จ **Modern UI** - Custom component with sleek design;
- ๐ถ **internationalization** - Supports **32 languages**;
- ๐งช **Test Ready** โ Smooth testing integration;
- โฟ **Accessibility** โ Accessibility standards to screen readers.
<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 to WEB](#additional-config-to-web)
- [React Native CLI](#using-react-native-cli)
- [Expo](#using-expo)
- [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)
- [Lib Props](#component-props-phoneinputprops)
- [Lib Functions](#functions)
- [Supported languages](#supported-languages)
- [Testing](#testing)
- [Accessibility](#accessibility)
- [Contributing](#contributing)
- [License](#license)
<br>
## Old Versions
- [Version 0.9.x](https://github.com/AstrOOnauta/react-native-international-phone-number/tree/v0.9.x)
- [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 to WEB
- ### 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-country-select/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-country-select/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?.translations?.eng?.common} (${this.state.selectedCountry?.cca2})`}
</Text>
<Text>
Phone Number: {`${this.state.selectedCountry?.idd?.root} ${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?.translations?.eng?.common} (${selectedCountry?.cca2})`}
</Text>
<Text>
Phone Number: {`${selectedCountry?.idd?.root} ${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?.translations?.eng?.common} (${selectedCountry?.cca2})`}
</Text>
<Text>
Phone Number: {`${selectedCountry?.idd?.root} ${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?.translations?.eng?.common} \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?.idd?.root} ${form.phoneNumber}`;
const isValid = isValidPhoneNumber(
form.phoneNumber,
selectedCountry as ICountry,
);
Alert.alert(
'Advanced Result',
`Country: ${selectedCountry?.translations?.eng?.common} \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
### PhoneInput Styles ([phoneInputStyles](lib/interfaces/phoneInputStyles.ts))
| Property | Type | Description |
| --------------- | --------- | --------------------------- |
| `container` | ViewStyle | Main input container |
| `flagContainer` | ViewStyle | Flag and dropdown container |
| `flag` | TextStyle | Flag emoji styling |
| `caret` | TextStyle | Dropdown arrow |
| `divider` | ViewStyle | Separator line |
| `callingCode` | TextStyle | Country calling code |
| `input` | TextStyle | Phone number input |
### Modal Styles ([modalStyles](https://github.com/AstrOOnauta/react-native-country-select/blob/main/lib/interface/countrySelectStyles.ts))
| Property | Type | Description |
| -------------------------- | --------- | ------------------------- |
| `backdrop` | ViewStyle | Modal background overlay |
| `container` | ViewStyle | Modal main container |
| `content` | ViewStyle | Modal content area |
| `dragHandleContainer` | ViewStyle | Drag Handle area |
| `dragHandleIndicator` | ViewStyle | Drag Handle Indicator |
| `searchContainer` | ViewStyle | Search input wrapper |
| `searchInput` | TextStyle | Search input field |
| `list` | ViewStyle | Countries list container |
| `countryItem` | ViewStyle | Individual country row |
| `flag` | TextStyle | Country flag in list |
| `countryInfo` | ViewStyle | Country details container |
| `callingCode` | TextStyle | Calling code in list |
| `countryName` | TextStyle | Country name in list |
| `sectionTitle` | TextStyle | Section headers |
| `closeButton` | ViewStyle | Close button container |
| `closeButtonText` | TextStyle | Close button text |
| `countryNotFoundContainer` | ViewStyle | No results container |
| `countryNotFoundMessage` | TextStyle | No results message |
<br>
## Component Props ([PhoneInputProps](lib/interfaces/phoneInputProps.ts))
| Prop | Type | Description |
| -------------------------------------- | ------------------------------- | ------------------------------------------------------------------------- |
| `theme` | `ITheme` | Theme configuration for the component |
| `language` | `ILanguage` | Language for country names and UI |
| `defaultValue` | `string` | Default phone number value (format: `+(country code)(area code)(number)`) |
| `value` | `string` | Controlled phone number value |
| `onChangePhoneNumber` | `(phoneNumber: string) => void` | Callback when phone number changes |
| `defaultCountry` | `ICountryCca2` | Default selected country (ISO 3166-1 alpha-2) |
| `selectedCountry` | `ICountry` | Currently selected country object |
| `onChangeSelectedCountry` | `(country: ICountry) => void` | Callback when country selection changes |
| `placeholder` | `string` | Placeholder text for phone input |
| `phoneInputPlaceholderTextColor` | `string` | Color of placeholder text |
| `phoneInputSelectionColor` | `string` | Color of text selection |
| `phoneInputStyles` | `IPhoneInputStyles` | Custom styles for phone input component |
| `modalStyles` | `ICountrySelectStyles` | Custom styles for country selection modal |
| `disabled` | `boolean` | Disable the entire phone input |
| `modalDisabled` | `boolean` | Disable only the country selection modal |
| `customMask` | `string` | Custom phone number mask (format like this: `(###) ###-####`) |
| `visibleCountries` | `ICountryCca2[]` | Array of country codes to show in modal |
| `hiddenCountries` | `ICountryCca2[]` | Array of country codes to hide from modal |
| `popularCountries` | `ICountryCca2[]` | Array of country codes to show in popular section |
| `customCaret` | `() => ReactNode` | Custom dropdown arrow component |
| `rtl` | `boolean` | Enable right-to-left layout |
| `isFullScreen` | `boolean` | Show modal in full screen mode |
| `modalType` | `'bottomSheet' \| 'popup'` | Type of modal presentation |
| `modalDragHandleIndicatorComponent` | `() => ReactNode` | Custom drag handle indicator component |
| `modalSearchInputPlaceholderTextColor` | `string` | Color of modal search placeholder text |
| `modalSearchInputPlaceholder` | `string` | Placeholder text for modal search input |
| `modalSearchInputSelectionColor` | `string` | Color of modal search text selection |
| `modalPopularCountriesTitle` | `string` | Title for popular countries section |
| `modalAllCountriesTitle` | `string` | Title for all countries section |
| `modalSectionTitleComponent` | `() => ReactNode` | Custom section title component |
| `modalCountryItemComponent` | `() => ReactNode` | Custom country item component |
| `modalCloseButtonComponent` | `() => ReactNode` | Custom close button component |
| `modalSectionTitleDisabled` | `boolean` | Disable section titles in modal |
| `modalNotFoundCountryMessage` | `string` | Message when no countries found |
| `disabledModalBackdropPress` | `boolean` | Disable modal close on backdrop press |
| `removedModalBackdrop` | `boolean` | Remove modal backdrop entirely |
| `onModalBackdropPress` | `() => void` | Callback when modal backdrop is pressed |
| `onModalRequestClose` | `() => void` | Callback when modal close is requested |
| `showModalSearchInput` | `boolean` | Show search input in modal |
| `showModalCloseButton` | `boolean` | Show close button in modal |
| `showModalScrollIndicator` | `boolean` | Show scroll indicator in modal |
| `ref` | `Ref<IPhoneInputRef>` | Ref to access component methods |
<br>
## Functions
| Function | Parameters | Return Type | Description |
| --------------------------- | ------------------------------------------ | ------------------------- | --------------------------------------------------------------------- |
| `getAllCountries` | `()` | `ICountry[]` | Returns an array of all available countries |
| `getCountriesByCallingCode` | `(callingCode: string)` | `ICountry[] \| undefined` | Returns countries that match the given calling code |
| `getCountryByCca2` | `(cca2: string)` | `ICountry \| undefined` | Returns a country by its ISO 3166-1 alpha-2 code |
| `getCountriesByName` | `(name: string, language: ILanguage)` | `ICountry[] \| undefined` | Returns countries that match the given name in the specified language |
| `getCountryByPhoneNumber` | `(phoneNumber: string)` | `ICountry \| undefined` | Returns the country that matches the given phone number |
| `isValidPhoneNumber` | `(phoneNumber: string, country: ICountry)` | `boolean` | Validates if a phone number is valid for the given country |
</br>
## Supported languages
The `language` prop supports the following values:
| Code | Language |
| ---------- | ------------------- |
| `ara` | Arabic |
| `bel` | Belarusian |
| `bre` | Breton |
| `bul` | Bulgarian |
| `ces` | Czech |
| `deu` | German |
| `ell` | Greek |
| `eng` | English |
| `est` | Estonian |
| `fin` | Finnish |
| `fra` | French |
| `heb` | Hebrew |
| `hrv` | Croatian |
| `hun` | Hungarian |
| `ita` | Italian |
| `jpn` | Japanese |
| `kor` | Korean |
| `nld` | Dutch |
| `per` | Persian |
| `pol` | Polish |
| `por` | Portuguese |
| `ron` | Romanian |
| `rus` | Russian |
| `slk` | Slovak |
| `spa` | Spanish |
| `srp` | Serbian |
| `swe` | Swedish |
| `tur` | Turkish |
| `ukr` | Ukrainian |
| `urd` | Urdu |
| `zho` | Chinese |
| `zho-Hans` | Simplified Chinese |
| `zho-Hant` | Traditional Chinese |
<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
const phoneInput = getByTestId('countryPickerPhoneInput');
const flagContainerButton = getByTestId('countryPickerFlagContainerButton');
const countrySelect = getByTestId('countrySelectSearchInput');
const countrySelectBackdrop = getByTestId('countrySelectBackdrop');
const countrySelectList = getByTestId('countrySelectList');
const countrySelectSearchInput = getByTestId('countrySelectSearchInput');
const countrySelectItem = getByTestId('countrySelectItem');
const countrySelectCloseButton = getByTestId('countrySelectCloseButton');
```
<br>
## Accessibility
Ensure your app is inclusive and usable by everyone by leveraging built-in React Native accessibility features. The accessibility props are covered by this package.
<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>