diff --git a/src/components/CountryPicker.js b/src/components/CountryPicker.js
deleted file mode 100644
index 61bfd26a0e67..000000000000
--- a/src/components/CountryPicker.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import _ from 'underscore';
-import React, {forwardRef} from 'react';
-import PropTypes from 'prop-types';
-import Picker from './Picker';
-import withLocalize, {withLocalizePropTypes} from './withLocalize';
-
-const propTypes = {
- /** The label for the field */
- label: PropTypes.string,
-
- /** A callback method that is called when the value changes and it receives the selected value as an argument. */
- onInputChange: PropTypes.func.isRequired,
-
- /** The value that needs to be selected */
- value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
-
- /** The ID used to uniquely identify the input in a form */
- inputID: PropTypes.string,
-
- /** Saves a draft of the input value when used in a form */
- shouldSaveDraft: PropTypes.bool,
-
- /** Callback that is called when the text input is blurred */
- onBlur: PropTypes.func,
-
- /** Error text to display */
- errorText: PropTypes.string,
-
- ...withLocalizePropTypes,
-};
-
-const defaultProps = {
- label: '',
- value: undefined,
- errorText: '',
- shouldSaveDraft: false,
- inputID: undefined,
- onBlur: () => {},
-};
-
-const CountryPicker = forwardRef((props, ref) => {
- const COUNTRIES = _.map(props.translate('allCountries'), (countryName, countryISO) => ({
- value: countryISO,
- label: countryName,
- }));
-
- return (
-
- );
-});
-
-CountryPicker.propTypes = propTypes;
-CountryPicker.defaultProps = defaultProps;
-CountryPicker.displayName = 'CountryPicker';
-
-export default withLocalize(CountryPicker);
diff --git a/src/components/CountryPicker/CountrySelectorModal.js b/src/components/CountryPicker/CountrySelectorModal.js
new file mode 100644
index 000000000000..d16d97741d7c
--- /dev/null
+++ b/src/components/CountryPicker/CountrySelectorModal.js
@@ -0,0 +1,96 @@
+import _ from 'underscore';
+import React, {useMemo} from 'react';
+import PropTypes from 'prop-types';
+import CONST from '../../CONST';
+import useLocalize from '../../hooks/useLocalize';
+import HeaderWithBackButton from '../HeaderWithBackButton';
+import SelectionListRadio from '../SelectionListRadio';
+import Modal from '../Modal';
+
+const propTypes = {
+ /** Whether the modal is visible */
+ isVisible: PropTypes.bool.isRequired,
+
+ /** Country value selected */
+ currentCountry: PropTypes.string,
+
+ /** Function to call when the user selects a Country */
+ onCountrySelected: PropTypes.func,
+
+ /** Function to call when the user closes the Country modal */
+ onClose: PropTypes.func,
+
+ /** The search value from the selection list */
+ searchValue: PropTypes.string.isRequired,
+
+ /** Function to call when the user types in the search input */
+ setSearchValue: PropTypes.func.isRequired,
+};
+
+const defaultProps = {
+ currentCountry: '',
+ onClose: () => {},
+ onCountrySelected: () => {},
+};
+
+function filterOptions(searchValue, data) {
+ const trimmedSearchValue = searchValue.trim();
+ if (trimmedSearchValue.length === 0) {
+ return [];
+ }
+
+ return _.filter(data, (country) => country.text.toLowerCase().includes(searchValue.toLowerCase()));
+}
+
+function CountrySelectorModal({currentCountry, isVisible, onClose, onCountrySelected, setSearchValue, searchValue}) {
+ const {translate} = useLocalize();
+
+ const countries = useMemo(
+ () =>
+ _.map(translate('allCountries'), (countryName, countryISO) => ({
+ value: countryISO,
+ keyForList: countryISO,
+ text: countryName,
+ isSelected: currentCountry === countryISO,
+ })),
+ [translate, currentCountry],
+ );
+
+ const filteredData = filterOptions(searchValue, countries);
+ const headerMessage = searchValue.trim() && !filteredData.length ? translate('common.noResultsFound') : '';
+
+ return (
+
+
+
+
+ );
+}
+
+CountrySelectorModal.propTypes = propTypes;
+CountrySelectorModal.defaultProps = defaultProps;
+CountrySelectorModal.displayName = 'CountrySelectorModal';
+
+export default CountrySelectorModal;
diff --git a/src/components/CountryPicker/index.js b/src/components/CountryPicker/index.js
new file mode 100644
index 000000000000..20f9a981accb
--- /dev/null
+++ b/src/components/CountryPicker/index.js
@@ -0,0 +1,93 @@
+import React, {useEffect, useState} from 'react';
+import {View} from 'react-native';
+import PropTypes from 'prop-types';
+import lodashGet from 'lodash/get';
+import styles from '../../styles/styles';
+import MenuItemWithTopDescription from '../MenuItemWithTopDescription';
+import useLocalize from '../../hooks/useLocalize';
+import CountrySelectorModal from './CountrySelectorModal';
+import FormHelpMessage from '../FormHelpMessage';
+
+const propTypes = {
+ /** Form Error description */
+ errorText: PropTypes.string,
+
+ /** Country to display */
+ value: PropTypes.string,
+
+ /** Callback to call when the input changes */
+ onInputChange: PropTypes.func,
+
+ /** A ref to forward to MenuItemWithTopDescription */
+ forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(React.Component)})]),
+};
+
+const defaultProps = {
+ value: undefined,
+ forwardedRef: undefined,
+ errorText: '',
+ onInputChange: () => {},
+};
+
+function CountryPicker({value, errorText, onInputChange, forwardedRef}) {
+ const {translate} = useLocalize();
+ const allCountries = translate('allCountries');
+ const [isPickerVisible, setIsPickerVisible] = useState(false);
+ const [searchValue, setSearchValue] = useState(lodashGet(allCountries, value, ''));
+
+ useEffect(() => {
+ setSearchValue(lodashGet(allCountries, value, ''));
+ }, [value, allCountries]);
+
+ const showPickerModal = () => {
+ setIsPickerVisible(true);
+ };
+
+ const hidePickerModal = () => {
+ setIsPickerVisible(false);
+ };
+
+ const updateCountryInput = (country) => {
+ onInputChange(country.value);
+ hidePickerModal();
+ };
+
+ const title = allCountries[value] || '';
+ const descStyle = title.length === 0 ? styles.textNormal : null;
+
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+CountryPicker.propTypes = propTypes;
+CountryPicker.defaultProps = defaultProps;
+CountryPicker.displayName = 'CountryPicker';
+
+export default React.forwardRef((props, ref) => (
+
+));
diff --git a/src/components/Form.js b/src/components/Form.js
index c9c1b79d7679..ffcd3cbe8cd5 100644
--- a/src/components/Form.js
+++ b/src/components/Form.js
@@ -334,7 +334,7 @@ function Form(props) {
}
if (child.props.onValueChange) {
- child.props.onValueChange(value);
+ child.props.onValueChange(value, inputKey);
}
},
});
diff --git a/src/components/MenuItemWithTopDescription.js b/src/components/MenuItemWithTopDescription.js
index ce6fd452341a..ee51d2f41ccd 100644
--- a/src/components/MenuItemWithTopDescription.js
+++ b/src/components/MenuItemWithTopDescription.js
@@ -9,6 +9,7 @@ function MenuItemWithTopDescription(props) {
@@ -18,4 +19,10 @@ function MenuItemWithTopDescription(props) {
MenuItemWithTopDescription.propTypes = propTypes;
MenuItemWithTopDescription.displayName = 'MenuItemWithTopDescription';
-export default MenuItemWithTopDescription;
+export default React.forwardRef((props, ref) => (
+
+));
diff --git a/src/components/StatePicker.js b/src/components/StatePicker.js
deleted file mode 100644
index 1d18652a625f..000000000000
--- a/src/components/StatePicker.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import _ from 'underscore';
-import React, {forwardRef} from 'react';
-import PropTypes from 'prop-types';
-import Picker from './Picker';
-import withLocalize, {withLocalizePropTypes} from './withLocalize';
-
-const propTypes = {
- /** The label for the field */
- label: PropTypes.string,
-
- /** A callback method that is called when the value changes and it receives the selected value as an argument. */
- onInputChange: PropTypes.func.isRequired,
-
- /** The value that needs to be selected */
- value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
-
- /** The ID used to uniquely identify the input in a Form */
- inputID: PropTypes.string,
-
- /** Saves a draft of the input value when used in a form */
- shouldSaveDraft: PropTypes.bool,
-
- /** Callback that is called when the text input is blurred */
- onBlur: PropTypes.func,
-
- /** Error text to display */
- errorText: PropTypes.string,
-
- ...withLocalizePropTypes,
-};
-
-const defaultProps = {
- label: '',
- value: undefined,
- errorText: '',
- shouldSaveDraft: false,
- inputID: undefined,
- onBlur: () => {},
-};
-
-const StatePicker = forwardRef((props, ref) => {
- const STATES = _.chain(props.translate('allStates'))
- .sortBy((state) => state.stateName.toLowerCase())
- .map((state) => ({value: state.stateISO, label: state.stateName}))
- .value();
-
- return (
-
- );
-});
-
-StatePicker.propTypes = propTypes;
-StatePicker.defaultProps = defaultProps;
-StatePicker.displayName = 'StatePicker';
-
-export default withLocalize(StatePicker);
diff --git a/src/components/StatePicker/StateSelectorModal.js b/src/components/StatePicker/StateSelectorModal.js
new file mode 100644
index 000000000000..4497eab72de8
--- /dev/null
+++ b/src/components/StatePicker/StateSelectorModal.js
@@ -0,0 +1,97 @@
+import _ from 'underscore';
+import React, {useMemo} from 'react';
+import PropTypes from 'prop-types';
+import CONST from '../../CONST';
+import Modal from '../Modal';
+import HeaderWithBackButton from '../HeaderWithBackButton';
+import SelectionListRadio from '../SelectionListRadio';
+import useLocalize from '../../hooks/useLocalize';
+
+const propTypes = {
+ /** Whether the modal is visible */
+ isVisible: PropTypes.bool.isRequired,
+
+ /** State value selected */
+ currentState: PropTypes.string,
+
+ /** Function to call when the user selects a State */
+ onStateSelected: PropTypes.func,
+
+ /** Function to call when the user closes the State modal */
+ onClose: PropTypes.func,
+
+ /** The search value from the selection list */
+ searchValue: PropTypes.string.isRequired,
+
+ /** Function to call when the user types in the search input */
+ setSearchValue: PropTypes.func.isRequired,
+};
+
+const defaultProps = {
+ currentState: '',
+ onClose: () => {},
+ onStateSelected: () => {},
+};
+
+function filterOptions(searchValue, data) {
+ const trimmedSearchValue = searchValue.trim();
+ if (trimmedSearchValue.length === 0) {
+ return [];
+ }
+
+ return _.filter(data, (country) => country.text.toLowerCase().includes(searchValue.toLowerCase()));
+}
+
+function StateSelectorModal({currentState, isVisible, onClose, onStateSelected, searchValue, setSearchValue}) {
+ const {translate} = useLocalize();
+
+ const countryStates = useMemo(
+ () =>
+ _.map(translate('allStates'), (state) => ({
+ value: state.stateISO,
+ keyForList: state.stateISO,
+ text: state.stateName,
+ isSelected: currentState === state.stateISO,
+ })),
+ [translate, currentState],
+ );
+
+ const filteredData = filterOptions(searchValue, countryStates);
+ const headerMessage = searchValue.trim() && !filteredData.length ? translate('common.noResultsFound') : '';
+
+ return (
+
+
+
+
+ );
+}
+
+StateSelectorModal.propTypes = propTypes;
+StateSelectorModal.defaultProps = defaultProps;
+StateSelectorModal.displayName = 'StateSelectorModal';
+
+export default StateSelectorModal;
diff --git a/src/components/StatePicker/index.js b/src/components/StatePicker/index.js
new file mode 100644
index 000000000000..7c8fbdae36bb
--- /dev/null
+++ b/src/components/StatePicker/index.js
@@ -0,0 +1,93 @@
+import React, {useEffect, useState} from 'react';
+import {View} from 'react-native';
+import lodashGet from 'lodash/get';
+import PropTypes from 'prop-types';
+import styles from '../../styles/styles';
+import MenuItemWithTopDescription from '../MenuItemWithTopDescription';
+import useLocalize from '../../hooks/useLocalize';
+import FormHelpMessage from '../FormHelpMessage';
+import StateSelectorModal from './StateSelectorModal';
+
+const propTypes = {
+ /** Error text to display */
+ errorText: PropTypes.string,
+
+ /** State to display */
+ value: PropTypes.string,
+
+ /** Callback to call when the input changes */
+ onInputChange: PropTypes.func,
+
+ /** A ref to forward to MenuItemWithTopDescription */
+ forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(React.Component)})]),
+};
+
+const defaultProps = {
+ value: undefined,
+ forwardedRef: undefined,
+ errorText: '',
+ onInputChange: () => {},
+};
+
+function StatePicker({value, errorText, onInputChange, forwardedRef}) {
+ const {translate} = useLocalize();
+ const allStates = translate('allStates');
+ const [isPickerVisible, setIsPickerVisible] = useState(false);
+ const [searchValue, setSearchValue] = useState(lodashGet(allStates, `${value}.stateName`, ''));
+
+ useEffect(() => {
+ setSearchValue(lodashGet(allStates, `${value}.stateName`, ''));
+ }, [value, allStates]);
+
+ const showPickerModal = () => {
+ setIsPickerVisible(true);
+ };
+
+ const hidePickerModal = () => {
+ setIsPickerVisible(false);
+ };
+
+ const updateStateInput = (state) => {
+ onInputChange(state.value);
+ hidePickerModal();
+ };
+
+ const title = allStates[value] ? allStates[value].stateName : '';
+ const descStyle = title.length === 0 ? styles.textNormal : null;
+
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+StatePicker.propTypes = propTypes;
+StatePicker.defaultProps = defaultProps;
+StatePicker.displayName = 'StatePicker';
+
+export default React.forwardRef((props, ref) => (
+
+));
diff --git a/src/languages/en.js b/src/languages/en.js
index d7d34fa94580..91996e9e7d29 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -1535,4 +1535,10 @@ export default {
levelTwoResult: 'Message hidden from channel, plus anonymous warning and message is reported for review.',
levelThreeResult: 'Message removed from channel plus anonymous warning and message is reported for review.',
},
+ countrySelectorModal: {
+ placeholderText: 'Search to see options',
+ },
+ stateSelectorModal: {
+ placeholderText: 'Search to see options',
+ },
};
diff --git a/src/languages/es.js b/src/languages/es.js
index c6d6f2f2b67a..712709f68d08 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -2006,4 +2006,10 @@ export default {
levelTwoResult: 'Mensaje ocultado del canal, más advertencia anónima y mensaje reportado para revisión.',
levelThreeResult: 'Mensaje eliminado del canal, más advertencia anónima y mensaje reportado para revisión.',
},
+ countrySelectorModal: {
+ placeholderText: 'Buscar para ver opciones',
+ },
+ stateSelectorModal: {
+ placeholderText: 'Buscar para ver opciones',
+ },
};
diff --git a/src/pages/ReimbursementAccount/AddressForm.js b/src/pages/ReimbursementAccount/AddressForm.js
index 03b31cc92196..d8fbc0290136 100644
--- a/src/pages/ReimbursementAccount/AddressForm.js
+++ b/src/pages/ReimbursementAccount/AddressForm.js
@@ -121,12 +121,13 @@ function AddressForm(props) {
errorText={props.errors.city ? props.translate('bankAccount.error.addressCity') : ''}
containerStyles={[styles.mt4]}
/>
-
+
+
props.onFieldChange({state: value})}
errorText={props.errors.state ? props.translate('bankAccount.error.addressState') : ''}
/>
@@ -137,7 +138,6 @@ function AddressForm(props) {
label={props.translate('common.zip')}
accessibilityLabel={props.translate('common.zip')}
accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT}
- containerStyles={[styles.mt4]}
keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD}
value={props.values.zipCode}
defaultValue={props.defaultValues.zipCode}
diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js
index 24eda3cc9b66..a57c80b0a7e2 100644
--- a/src/pages/ReimbursementAccount/CompanyStep.js
+++ b/src/pages/ReimbursementAccount/CompanyStep.js
@@ -7,6 +7,7 @@ import {withOnyx} from 'react-native-onyx';
import PropTypes from 'prop-types';
import {parsePhoneNumber} from 'awesome-phonenumber';
import HeaderWithBackButton from '../../components/HeaderWithBackButton';
+import StatePicker from '../../components/StatePicker';
import CONST from '../../CONST';
import * as BankAccounts from '../../libs/actions/BankAccounts';
import Text from '../../components/Text';
@@ -15,7 +16,6 @@ import TextInput from '../../components/TextInput';
import styles from '../../styles/styles';
import CheckboxWithLabel from '../../components/CheckboxWithLabel';
import TextLink from '../../components/TextLink';
-import StatePicker from '../../components/StatePicker';
import withLocalize from '../../components/withLocalize';
import * as ValidationUtils from '../../libs/ValidationUtils';
import compose from '../../libs/compose';
@@ -171,7 +171,7 @@ class CompanyStep extends React.Component {
onSubmit={this.submit}
scrollContextEnabled
submitButtonText={this.props.translate('common.saveAndContinue')}
- style={[styles.ph5, styles.flexGrow1]}
+ style={[styles.mh5, styles.flexGrow1]}
>
{this.props.translate('companyStep.subtitle')}
-
+
{
const errors = {};
-
const requiredFields = ['addressLine1', 'city', 'country', 'state'];
// Check "State" dropdown is a valid state if selected Country is USA
@@ -120,31 +115,39 @@ function AddressPage(props) {
return errors;
}, []);
- if (lodashGet(props.privatePersonalDetails, 'isLoading', true)) {
+ const handleAddressChange = (value, key) => {
+ if (key !== 'country') {
+ return;
+ }
+ setCurrentCountry(value);
+ };
+
+ if (lodashGet(privatePersonalDetails, 'isLoading', true)) {
return ;
}
return (
Navigation.goBack(ROUTES.SETTINGS_PERSONAL_DETAILS)}
/>
);
@@ -220,11 +222,8 @@ function AddressPage(props) {
AddressPage.propTypes = propTypes;
AddressPage.defaultProps = defaultProps;
-export default compose(
- withLocalize,
- withOnyx({
- privatePersonalDetails: {
- key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS,
- },
- }),
-)(AddressPage);
+export default withOnyx({
+ privatePersonalDetails: {
+ key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS,
+ },
+})(AddressPage);
diff --git a/src/styles/styles.js b/src/styles/styles.js
index 19a6da129792..2618074a8763 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -3426,6 +3426,11 @@ const styles = {
maxWidth: 375,
},
+ formSpaceVertical: {
+ height: 20,
+ width: 1,
+ },
+
taskCheckbox: {
height: 16,
width: 16,