From 4788db6812cecee9073184e488277ac54c963cb4 Mon Sep 17 00:00:00 2001 From: Arafat Zahan Date: Tue, 30 Jul 2024 02:04:27 +0600 Subject: [PATCH 1/3] Prepare new release. --- README.md | 39 ++++++++++++++++++++--------- example/app.json | 4 +-- example/src/application.tsx | 22 ++++++++++++----- src/CountryPicker.tsx | 48 +++++++++++++++++++++++++----------- src/PhoneNumberInput.tsx | 49 ++++++++++++++++++++++++------------- src/data/countries.ts | 5 ---- src/types.ts | 7 ++++-- 7 files changed, 115 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 59a02ec..093242b 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,10 @@ Import the `PhoneNumberInput` component from the library. You can then use it in import React, { useState } from 'react'; import { PhoneNumberInput, getCountryByCode } from 'react-native-paper-phone-number-input'; +// `showFirstOnList`, `includeCountries` and `excludeCountries` should be defined outsude +// the component to prevent uncessary recomputations and re-renders. +const includeCountries = ['AZ', 'BD', 'CA', 'GB', 'IN', 'NZ', 'US', 'TR']; + export default function App() { const [countryCode, setCountryCode] = useState('BD'); // Default country code const [phoneNumber, setPhoneNumber] = useState(); @@ -133,7 +137,7 @@ export default function App() { setCode={setCountryCode} phoneNumber={phoneNumber} setPhoneNumber={setPhoneNumber} - onlyCountries={['AZ', 'BD', 'CA', 'GB', 'IN', 'NZ', 'US', 'TR']} + includeCountries={includeCountries} /> ); } @@ -147,17 +151,28 @@ A more complete example can be found in the `example` directory. #### Props -| Prop | Type | Description | Notes | -| --------------------- | ------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | -| `code` | `string` | The country code. | Optional. By default, the country code is set to `##` which shows a world icon. | -| `setCode` | `(code: string) => void` | A function that sets the country code. | Required. | -| `phoneNumber` | `string` | The phone number. | Optional. By default, no phone number is set. | -| `setPhoneNumber` | `(phoneNumber: string) => void` | A function that sets the phone number. | Required. | -| `showFirstOnList` | `string[]` | A list of country codes that should be shown on top of the list. | Optional. By default, countries are shown alphabetically. | -| `modalStyle` | `StyleProp` | The style of the modal that shows the country code picker. | Optional. | -| `modalContainerStyle` | `StyleProp` | The style of the container of the modal that shows the country code picker. | Optional. | -| `onlyCountries` | `string[]` | A list of country codes that specifies which countries can be selected. | Optional. | -| `...rest` | `...TextInputProps` | Any other props that you want to pass to the `TextInput` component of React Native Paper. | Optional. | +| Prop | Type | Description | Notes | +| --------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | +| `code` | `string` | The country code. | Optional. By default, the country code is set to `##` which shows a world icon. | +| `setCode` | `(code: string) => void` | A function that sets the country code. | Required. | +| `phoneNumber` | `string` | The phone number. | Optional. By default, no phone number is set. | +| `setPhoneNumber` | `(phoneNumber: string) => void` | A function that sets the phone number. | Required. | +| `showFirstOnList` | `string[]` | A list of country codes that should be shown on top of the list. | Optional. By default, countries are shown alphabetically. | +| `includeCountries` | `string[]` | A list of country codes that specifies which countries can be selected. | Optional. By default, shows all countries. | +| `excludeCountries` | `string[]` | A list of country codes that specifies which countries cannot be selected. | Optional. By default, does not exclude any countries. | +| 'limitMaxLength' | `boolean` | Limit the maximum length of the phone number for the country as defined in [E.164](https://en.wikipedia.org/wiki/E.164). | Optional. By default, the maximum length of the phone number is not limited. | +| `modalStyle` | `StyleProp` | The style of the modal that shows the country code picker. | Optional. | +| `modalContainerStyle` | `StyleProp` | The style of the container of the modal that shows the country code picker. | Optional. | +| `...rest` | `...TextInputProps` | Any other props that you want to pass to the `TextInput` component of React Native Paper. | Optional. | + +> [!TIP] +> The props that accepts a list of country codes such as `showFirstOnList`, `includeCountries` and `excludeCountries` should be defined outside the component or memoized to prevent unnecessary recomputations and re-renders! + +> [!CAUTION] +> If you set contradictory prop values in `includeCountries` and `excludeCountries` props, the `excludeCountries` prop will take precedence over the `includeCountries` prop. ie. If you set the same country code in both `includeCountries` and `excludeCountries`, the country will be excluded. + +> [!WARNING] +> If you are using the `limitMaxLength` prop, make sure to set the `phoneNumber` state to an empty string when the country code changes. This is because the maximum length of the phone number can change when the country code changes. #### Ref Methods diff --git a/example/app.json b/example/app.json index 98e5fb0..524c1e4 100644 --- a/example/app.json +++ b/example/app.json @@ -11,9 +11,7 @@ "resizeMode": "contain", "backgroundColor": "#ffffff" }, - "assetBundlePatterns": [ - "**/*" - ], + "assetBundlePatterns": ["**/*"], "ios": { "supportsTablet": true, "userInterfaceStyle": "automatic" diff --git a/example/src/application.tsx b/example/src/application.tsx index 186654d..02391a5 100644 --- a/example/src/application.tsx +++ b/example/src/application.tsx @@ -12,10 +12,10 @@ import { SafeAreaView } from 'react-native-safe-area-context'; const isWeb = Platform.OS === 'web'; -// list of countries that should be shown first in the country picker. -// Put this variable outside the component to avoid re-creating it on each render -// Or you can use useMemo() hook to create it. +// Put these variables outside the component to avoid re-creating it on each render +// Or you can use useMemo() hook to create them. const countriesToShowFirst = ['BD', 'US', 'CA', 'GB', 'AU', 'IN', 'NZ']; +const countriesToInclude = ['AZ', 'BD', 'CA', 'GB', 'IN', 'NZ', 'US', 'TR', 'AU']; const Application: React.FC = () => { const [countryCode, setCountryCode] = useState('BD'); // Default country code @@ -46,7 +46,7 @@ const Application: React.FC = () => { setPhoneNumber={setPhoneNumber} showFirstOnList={countriesToShowFirst} modalStyle={isWeb ? styles.web : undefined} - onlyCountries={['AZ', 'BD', 'CA', 'GB', 'IN', 'NZ', 'US', 'TR', 'AU']} + limitMaxLength /> @@ -67,11 +67,22 @@ const Application: React.FC = () => { + + Only Show Specific Countries + + Disabled State { Not Editable ( showFirstOnList, modalStyle, modalContainerStyle, + includeCountries, + excludeCountries, // Prpos from TextInput that needs special handling disabled, editable = true, @@ -70,22 +72,40 @@ export const CountryPicker = forwardRef( })); const countriesList = useMemo(() => { - if (!showFirstOnList?.length) { - return countries; + // By default, show all countries. + let filteredCountries = countries; + + // First filter the countries based on the includeCountries. + if (Array.isArray(includeCountries) && includeCountries.length > 0) { + filteredCountries = includeCountries.map((code) => ({ + ...getCountryByCode(code), + code, + })); } - const countriesToShowOnTop = showFirstOnList.map((code) => ({ - ...getCountryByCode(code), - code, - })); - - return [ - ...countriesToShowOnTop, - ...countries.filter( - (country) => !countriesToShowOnTop.some((c) => c.code === country.code) - ), - ]; - }, [showFirstOnList]); + // If showFirstOnList is provided, show those countries on top of the list. + if (Array.isArray(showFirstOnList) && showFirstOnList.length > 0) { + // If the country is not in the includeCountries, do not show it. + // This is to prevent showing countries that are not in the includeCountries list. + const countriesToShowOnTop = filteredCountries.filter((country) => + showFirstOnList.includes(country.code) + ); + + filteredCountries = countriesToShowOnTop.concat( + // Filter out the countries that are already shown on top. + filteredCountries.filter((country) => !showFirstOnList.includes(country.code)) + ); + } + + // If excludeCountries is provided, filter out those countries. + if (Array.isArray(excludeCountries) && excludeCountries.length > 0) { + filteredCountries = filteredCountries.filter( + (country) => !excludeCountries.includes(country.code) + ); + } + + return filteredCountries; + }, [showFirstOnList, includeCountries, excludeCountries]); const searchResult = useMemo(() => { if (!debouncedSearchQuery) { diff --git a/src/PhoneNumberInput.tsx b/src/PhoneNumberInput.tsx index 2d2f89a..e0fc956 100644 --- a/src/PhoneNumberInput.tsx +++ b/src/PhoneNumberInput.tsx @@ -28,7 +28,9 @@ export const PhoneNumberInput = forwardRef { + // By default, show all countries. let filteredCountries = countries; - if (onlyCountries.length > 0) { - filteredCountries = countries.filter((country) => onlyCountries.includes(country.code)); + + // First filter the countries based on the includeCountries. + if (Array.isArray(includeCountries) && includeCountries.length > 0) { + filteredCountries = includeCountries.map((code) => ({ + ...getCountryByCode(code), + code, + })); } - if (!showFirstOnList?.length) { - return filteredCountries; + // If showFirstOnList is provided, show those countries on top of the list. + if (Array.isArray(showFirstOnList) && showFirstOnList.length > 0) { + // If the country is not in the includeCountries, do not show it. + // This is to prevent showing countries that are not in the includeCountries list. + const countriesToShowOnTop = filteredCountries.filter((country) => + showFirstOnList.includes(country.code) + ); + + filteredCountries = countriesToShowOnTop.concat( + // Filter out the countries that are already shown on top. + filteredCountries.filter((country) => !showFirstOnList.includes(country.code)) + ); } - const countriesToShowOnTop = showFirstOnList.map((code) => ({ - ...getCountryByCode(code), - code, - })); + // If excludeCountries is provided, filter out those countries. + if (Array.isArray(excludeCountries) && excludeCountries.length > 0) { + filteredCountries = filteredCountries.filter( + (country) => !excludeCountries.includes(country.code) + ); + } - return [ - ...countriesToShowOnTop, - ...filteredCountries.filter( - (country) => !countriesToShowOnTop.some((c) => c.code === country.code) - ), - ]; - }, [showFirstOnList, onlyCountries]); + return filteredCountries; + }, [showFirstOnList, includeCountries, excludeCountries]); const searchResult = useMemo(() => { if (!debouncedSearchQuery) { @@ -156,7 +171,7 @@ export const PhoneNumberInput = forwardRef Math.max(longest, country.dialCode.length), - 0 -); diff --git a/src/types.ts b/src/types.ts index 423a9b3..6b445c5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -19,7 +19,9 @@ export interface PhoneNumberInputProps extends Omit>; showFirstOnList?: string[]; - onlyCountries? : string[]; + includeCountries?: string[]; + excludeCountries?: string[]; + limitMaxLength?: boolean; modalStyle?: StyleProp; modalContainerStyle?: StyleProp; } @@ -32,7 +34,8 @@ export interface CountryPickerRef { export interface CountryPickerProps extends Omit { country?: string; setCountry: React.Dispatch>; - onlyCountries? : string[]; + includeCountries?: string[]; + excludeCountries?: string[]; showFirstOnList?: string[]; modalStyle?: StyleProp; modalContainerStyle?: StyleProp; From d47c1835afdbd7abaa499c6f49f1a60b9f9ee562 Mon Sep 17 00:00:00 2001 From: Arafat Zahan Date: Tue, 30 Jul 2024 02:11:34 +0600 Subject: [PATCH 2/3] Update readme file. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 093242b..9f0a14f 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ - Looks and feels consistent with React Native Paper. - Allows specifying default country. - Allows specifying a list of countries to show on top of the list. -- Allows user to specify which countries to show. +- Allows user to specify which countries to show or hide in the list. - Exposes imperative methods to open and close the country code picker. - Supports light and dark themes. - Works well on Android, iOS and Web. @@ -168,12 +168,12 @@ A more complete example can be found in the `example` directory. > [!TIP] > The props that accepts a list of country codes such as `showFirstOnList`, `includeCountries` and `excludeCountries` should be defined outside the component or memoized to prevent unnecessary recomputations and re-renders! -> [!CAUTION] -> If you set contradictory prop values in `includeCountries` and `excludeCountries` props, the `excludeCountries` prop will take precedence over the `includeCountries` prop. ie. If you set the same country code in both `includeCountries` and `excludeCountries`, the country will be excluded. - > [!WARNING] > If you are using the `limitMaxLength` prop, make sure to set the `phoneNumber` state to an empty string when the country code changes. This is because the maximum length of the phone number can change when the country code changes. +> [!CAUTION] +> If you set contradictory prop values in `includeCountries` and `excludeCountries` props, the `excludeCountries` prop will take precedence over the `includeCountries` prop. ie. If you set the same country code in both `includeCountries` and `excludeCountries`, the country will be excluded. + #### Ref Methods | Method Name | Description | From 8e0c89f1b6317b86b83d834daf184b335d1a6ce1 Mon Sep 17 00:00:00 2001 From: Arafat Zahan Date: Tue, 30 Jul 2024 02:19:59 +0600 Subject: [PATCH 3/3] Improve limitMaxLength prop correctness. --- README.md | 7 ++----- src/PhoneNumberInput.tsx | 1 + 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9f0a14f..1864502 100644 --- a/README.md +++ b/README.md @@ -160,16 +160,13 @@ A more complete example can be found in the `example` directory. | `showFirstOnList` | `string[]` | A list of country codes that should be shown on top of the list. | Optional. By default, countries are shown alphabetically. | | `includeCountries` | `string[]` | A list of country codes that specifies which countries can be selected. | Optional. By default, shows all countries. | | `excludeCountries` | `string[]` | A list of country codes that specifies which countries cannot be selected. | Optional. By default, does not exclude any countries. | -| 'limitMaxLength' | `boolean` | Limit the maximum length of the phone number for the country as defined in [E.164](https://en.wikipedia.org/wiki/E.164). | Optional. By default, the maximum length of the phone number is not limited. | +| `limitMaxLength` | `boolean` | Limit the maximum length of the phone number for the country as defined in [E.164](https://en.wikipedia.org/wiki/E.164). | Optional. By default, the maximum length of the phone number is not limited. | | `modalStyle` | `StyleProp` | The style of the modal that shows the country code picker. | Optional. | | `modalContainerStyle` | `StyleProp` | The style of the container of the modal that shows the country code picker. | Optional. | | `...rest` | `...TextInputProps` | Any other props that you want to pass to the `TextInput` component of React Native Paper. | Optional. | > [!TIP] -> The props that accepts a list of country codes such as `showFirstOnList`, `includeCountries` and `excludeCountries` should be defined outside the component or memoized to prevent unnecessary recomputations and re-renders! - -> [!WARNING] -> If you are using the `limitMaxLength` prop, make sure to set the `phoneNumber` state to an empty string when the country code changes. This is because the maximum length of the phone number can change when the country code changes. +> The props that accepts a list o f country codes such as `showFirstOnList`, `includeCountries` and `excludeCountries` should be defined outside the component or memoized to prevent unnecessary recomputations and re-renders! > [!CAUTION] > If you set contradictory prop values in `includeCountries` and `excludeCountries` props, the `excludeCountries` prop will take precedence over the `includeCountries` prop. ie. If you set the same country code in both `includeCountries` and `excludeCountries`, the country will be excluded. diff --git a/src/PhoneNumberInput.tsx b/src/PhoneNumberInput.tsx index e0fc956..b0b33ad 100644 --- a/src/PhoneNumberInput.tsx +++ b/src/PhoneNumberInput.tsx @@ -229,6 +229,7 @@ export const PhoneNumberInput = forwardRef { setCode(item.code); setVisible(false); + limitMaxLength && item.length < phoneNumber.length && setPhoneNumber(''); }} theme={theme} >