diff --git a/src/ROUTES.js b/src/ROUTES.js index a95a68964998..b4ca019709b0 100644 --- a/src/ROUTES.js +++ b/src/ROUTES.js @@ -70,8 +70,6 @@ export default { getReportShareCodeRoute: (reportID) => `r/${reportID}/details/shareCode`, REPORT_ATTACHMENTS: 'r/:reportID/attachment', getReportAttachmentRoute: (reportID, source) => `r/${reportID}/attachment?source=${encodeURI(source)}`, - SELECT_YEAR: 'select-year', - getYearSelectionRoute: (minYear, maxYear, currYear, backTo) => `select-year?min=${minYear}&max=${maxYear}&year=${currYear}&backTo=${backTo}`, /** This is a utility route used to go to the user's concierge chat, or the sign-in page if the user's not authenticated */ CONCIERGE: 'concierge', diff --git a/src/components/CalendarPicker/calendarPickerPropTypes.js b/src/components/CalendarPicker/calendarPickerPropTypes.js deleted file mode 100644 index 8a2e62d302a7..000000000000 --- a/src/components/CalendarPicker/calendarPickerPropTypes.js +++ /dev/null @@ -1,42 +0,0 @@ -import PropTypes from 'prop-types'; -import moment from 'moment'; -import CONST from '../../CONST'; - -const propTypes = { - /** An initial value of date string */ - value: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]), - - /** A minimum date (oldest) allowed to select */ - minDate: PropTypes.objectOf(Date), - - /** A maximum date (earliest) allowed to select */ - maxDate: PropTypes.objectOf(Date), - - /** Default year to be set in the calendar picker. Used with navigation to set the correct year after going back to the view with calendar */ - selectedYear: PropTypes.string, - - /** Default month to be set in the calendar picker. Used to keep last selected month after going back to the view with calendar */ - selectedMonth: PropTypes.number, - - /** A function that is called when the date changed inside the calendar component */ - onChanged: PropTypes.func, - - /** A function called when the date is selected */ - onSelected: PropTypes.func, - - /** A function called when the year picker is opened */ - onYearPickerOpen: PropTypes.func, -}; - -const defaultProps = { - value: new Date(), - minDate: moment().year(CONST.CALENDAR_PICKER.MIN_YEAR).toDate(), - maxDate: moment().year(CONST.CALENDAR_PICKER.MAX_YEAR).toDate(), - selectedYear: null, - selectedMonth: null, - onChanged: () => {}, - onSelected: () => {}, - onYearPickerOpen: () => {}, -}; - -export {propTypes, defaultProps}; diff --git a/src/components/Modal/modalPropTypes.js b/src/components/Modal/modalPropTypes.js index 820340f9d93e..58de5a6c57ca 100644 --- a/src/components/Modal/modalPropTypes.js +++ b/src/components/Modal/modalPropTypes.js @@ -58,6 +58,14 @@ const propTypes = { /** Whether the modal should avoid the keyboard */ avoidKeyboard: PropTypes.bool, + /** + * Whether the modal should hide its content while animating. On iOS, set to true + * if `useNativeDriver` is also true, to avoid flashes in the UI. + * + * See: https://github.com/react-native-modal/react-native-modal/pull/116 + * */ + hideModalContentWhileAnimating: PropTypes.bool, + ...windowDimensionsPropTypes, }; @@ -75,6 +83,7 @@ const defaultProps = { innerContainerStyle: {}, statusBarTranslucent: true, avoidKeyboard: false, + hideModalContentWhileAnimating: false, }; export {propTypes, defaultProps}; diff --git a/src/components/CalendarPicker/ArrowIcon.js b/src/components/NewDatePicker/CalendarPicker/ArrowIcon.js similarity index 78% rename from src/components/CalendarPicker/ArrowIcon.js rename to src/components/NewDatePicker/CalendarPicker/ArrowIcon.js index 75312c894524..6d5757323480 100644 --- a/src/components/CalendarPicker/ArrowIcon.js +++ b/src/components/NewDatePicker/CalendarPicker/ArrowIcon.js @@ -1,11 +1,11 @@ import React from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; -import styles from '../../styles/styles'; -import * as Expensicons from '../Icon/Expensicons'; -import * as StyleUtils from '../../styles/StyleUtils'; -import Icon from '../Icon'; -import CONST from '../../CONST'; +import styles from '../../../styles/styles'; +import * as Expensicons from '../../Icon/Expensicons'; +import * as StyleUtils from '../../../styles/StyleUtils'; +import Icon from '../../Icon'; +import CONST from '../../../CONST'; const propTypes = { /** Specifies if the arrow icon should be disabled or not. */ diff --git a/src/components/NewDatePicker/CalendarPicker/YearPickerModal.js b/src/components/NewDatePicker/CalendarPicker/YearPickerModal.js new file mode 100644 index 000000000000..24e22fb1f746 --- /dev/null +++ b/src/components/NewDatePicker/CalendarPicker/YearPickerModal.js @@ -0,0 +1,85 @@ +import React, {useEffect, useMemo, useState} from 'react'; +import PropTypes from 'prop-types'; +import _ from 'underscore'; +import HeaderWithBackButton from '../../HeaderWithBackButton'; +import CONST from '../../../CONST'; +import SelectionListRadio from '../../SelectionListRadio'; +import Modal from '../../Modal'; +import {radioListItemPropTypes} from '../../SelectionListRadio/selectionListRadioPropTypes'; +import useLocalize from '../../../hooks/useLocalize'; + +const propTypes = { + /** Whether the modal is visible */ + isVisible: PropTypes.bool.isRequired, + + /** The list of years to render */ + years: PropTypes.arrayOf(PropTypes.shape(radioListItemPropTypes)).isRequired, + + /** Currently selected year */ + currentYear: PropTypes.number, + + /** Function to call when the user selects a year */ + onYearChange: PropTypes.func, + + /** Function to call when the user closes the year picker */ + onClose: PropTypes.func, +}; + +const defaultProps = { + currentYear: new Date().getFullYear(), + onYearChange: () => {}, + onClose: () => {}, +}; + +function YearPickerModal(props) { + const {translate} = useLocalize(); + const [searchText, setSearchText] = useState(''); + const {sections, headerMessage} = useMemo(() => { + const yearsList = searchText === '' ? props.years : _.filter(props.years, (year) => year.text.includes(searchText)); + return { + headerMessage: !yearsList.length ? translate('common.noResultsFound') : '', + sections: [{data: yearsList, indexOffset: 0}], + }; + }, [props.years, searchText, translate]); + + useEffect(() => { + if (props.isVisible) { + return; + } + setSearchText(''); + }, [props.isVisible]); + + return ( + + + setSearchText(text.replace(CONST.REGEX.NON_NUMERIC, '').trim())} + keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} + headerMessage={headerMessage} + sections={sections} + onSelectRow={(option) => props.onYearChange(option.value)} + initiallyFocusedOptionKey={props.currentYear.toString()} + /> + + ); +} + +YearPickerModal.propTypes = propTypes; +YearPickerModal.defaultProps = defaultProps; +YearPickerModal.displayName = 'YearPickerModal'; + +export default YearPickerModal; diff --git a/src/components/CalendarPicker/generateMonthMatrix.js b/src/components/NewDatePicker/CalendarPicker/generateMonthMatrix.js similarity index 98% rename from src/components/CalendarPicker/generateMonthMatrix.js rename to src/components/NewDatePicker/CalendarPicker/generateMonthMatrix.js index d58e3c0521d2..62a1a50b3297 100644 --- a/src/components/CalendarPicker/generateMonthMatrix.js +++ b/src/components/NewDatePicker/CalendarPicker/generateMonthMatrix.js @@ -1,5 +1,5 @@ import moment from 'moment'; -import CONST from '../../CONST'; +import CONST from '../../../CONST'; /** * Generates a matrix representation of a month's calendar given the year and month. diff --git a/src/components/CalendarPicker/index.js b/src/components/NewDatePicker/CalendarPicker/index.js similarity index 54% rename from src/components/CalendarPicker/index.js rename to src/components/NewDatePicker/CalendarPicker/index.js index f8dc9dc5b330..fe0c36d32e41 100644 --- a/src/components/CalendarPicker/index.js +++ b/src/components/NewDatePicker/CalendarPicker/index.js @@ -2,97 +2,94 @@ import _ from 'underscore'; import React from 'react'; import {View} from 'react-native'; import moment from 'moment'; +import PropTypes from 'prop-types'; import Str from 'expensify-common/lib/str'; -import Text from '../Text'; +import Text from '../../Text'; +import YearPickerModal from './YearPickerModal'; import ArrowIcon from './ArrowIcon'; -import styles from '../../styles/styles'; -import {propTypes as calendarPickerPropType, defaultProps as defaultCalendarPickerPropType} from './calendarPickerPropTypes'; +import styles from '../../../styles/styles'; import generateMonthMatrix from './generateMonthMatrix'; -import withLocalize from '../withLocalize'; -import Navigation from '../../libs/Navigation/Navigation'; -import ROUTES from '../../ROUTES'; -import CONST from '../../CONST'; -import getButtonState from '../../libs/getButtonState'; -import * as StyleUtils from '../../styles/StyleUtils'; -import PressableWithFeedback from '../Pressable/PressableWithFeedback'; -import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; +import withLocalize, {withLocalizePropTypes} from '../../withLocalize'; +import CONST from '../../../CONST'; +import getButtonState from '../../../libs/getButtonState'; +import * as StyleUtils from '../../../styles/StyleUtils'; +import PressableWithFeedback from '../../Pressable/PressableWithFeedback'; +import PressableWithoutFeedback from '../../Pressable/PressableWithoutFeedback'; + +const propTypes = { + /** An initial value of date string */ + value: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]), + + /** A minimum date (oldest) allowed to select */ + minDate: PropTypes.instanceOf(Date), + + /** A maximum date (earliest) allowed to select */ + maxDate: PropTypes.instanceOf(Date), + + /** A function called when the date is selected */ + onSelected: PropTypes.func, + + ...withLocalizePropTypes, +}; + +const defaultProps = { + value: new Date(), + minDate: moment().year(CONST.CALENDAR_PICKER.MIN_YEAR).toDate(), + maxDate: moment().year(CONST.CALENDAR_PICKER.MAX_YEAR).toDate(), + onSelected: () => {}, +}; class CalendarPicker extends React.PureComponent { constructor(props) { super(props); - let currentSelection = moment(props.value, CONST.DATE.MOMENT_FORMAT_STRING); - let currentDateView = currentSelection.toDate(); - - if (props.selectedYear) { - currentDateView = moment(currentDateView).set('year', props.selectedYear).toDate(); - } - if (props.selectedMonth != null) { - currentDateView = moment(currentDateView).set('month', props.selectedMonth).toDate(); + if (props.minDate >= props.maxDate) { + throw new Error('Minimum date cannot be greater than the maximum date.'); } + + let currentDateView = moment(props.value, CONST.DATE.MOMENT_FORMAT_STRING).toDate(); if (props.maxDate < currentDateView) { currentDateView = props.maxDate; - currentSelection = moment(props.maxDate); } else if (props.minDate > currentDateView) { currentDateView = props.minDate; - currentSelection = moment(props.minDate); } + const minYear = moment(this.props.minDate).year(); + const maxYear = moment(this.props.maxDate).year(); + this.state = { currentDateView, - selectedYear: currentSelection.get('year').toString(), - selectedMonth: this.getNumberStringWithLeadingZero(currentSelection.get('month') + 1), - selectedDay: this.getNumberStringWithLeadingZero(currentSelection.get('date')), + isYearPickerVisible: false, + years: _.map( + Array.from({length: maxYear - minYear + 1}, (v, i) => i + minYear), + (value) => ({ + text: value.toString(), + value, + keyForList: value.toString(), + isSelected: value === currentDateView.getFullYear(), + }), + ), }; this.moveToPrevMonth = this.moveToPrevMonth.bind(this); this.moveToNextMonth = this.moveToNextMonth.bind(this); - this.onYearPickerPressed = this.onYearPickerPressed.bind(this); this.onDayPressed = this.onDayPressed.bind(this); + this.onYearSelected = this.onYearSelected.bind(this); } - componentDidMount() { - if (this.props.minDate <= this.props.maxDate) { - return; - } - throw new Error('Minimum date cannot be greater than the maximum date.'); - } - - componentDidUpdate(prevProps, prevState) { - // if the selectedYear prop or state has changed, update the currentDateView state with the new year value - if (this.props.selectedYear === prevProps.selectedYear && prevState.selectedYear === this.state.selectedYear) { - return; - } - - // If we changed the prop for selectedYear, update the state to match it, otherwise use the state value - const newSelectedYear = this.props.selectedYear !== prevProps.selectedYear ? this.props.selectedYear : this.state.selectedYear; - - this.setState( - (prev) => { - const newMomentDate = moment(prev.currentDateView).set('year', newSelectedYear); - - return { - selectedYear: newSelectedYear, - currentDateView: this.clampDate(newMomentDate.toDate()), - }; - }, - () => { - this.props.onSelected(this.getSelectedDateString()); - }, - ); - } - - /** - * Handles the user pressing the year picker button. - * Opens the year selection screen with the minimum and maximum year range - * based on the props, the current year based on the state, and the active route. - */ - onYearPickerPressed() { - const minYear = moment(this.props.minDate).year(); - const maxYear = moment(this.props.maxDate).year(); - const currentYear = parseInt(this.state.selectedYear, 10); - Navigation.navigate(ROUTES.getYearSelectionRoute(minYear, maxYear, currentYear, Navigation.getActiveRoute())); - this.props.onYearPickerOpen(this.state.currentDateView); + onYearSelected(year) { + this.setState((prev) => { + const newCurrentDateView = moment(prev.currentDateView).set('year', year).toDate(); + + return { + currentDateView: newCurrentDateView, + isYearPickerVisible: false, + years: _.map(prev.years, (item) => ({ + ...item, + isSelected: item.value === newCurrentDateView.getFullYear(), + })), + }; + }); } /** @@ -101,101 +98,25 @@ class CalendarPicker extends React.PureComponent { */ onDayPressed(day) { this.setState( - (prev) => { - const momentDate = moment(prev.currentDateView).date(day); - - return { - selectedDay: this.getNumberStringWithLeadingZero(day), - currentDateView: this.clampDate(momentDate.toDate()), - }; - }, - () => { - this.props.onSelected(this.getSelectedDateString()); - }, + (prev) => ({ + currentDateView: moment(prev.currentDateView).set('date', day).toDate(), + }), + () => this.props.onSelected(moment(this.state.currentDateView).format('YYYY-MM-DD')), ); } - /** - * Gets the date string build from state values of selected year, month and day. - * @returns {string} - Date string in the 'YYYY-MM-DD' format. - */ - getSelectedDateString() { - // can't use moment.format() method here because it won't allow incorrect dates - return `${this.state.selectedYear}-${this.state.selectedMonth}-${this.state.selectedDay}`; - } - - /** - * Returns the string converted from the given number. If the number is lower than 10, - * it will add zero at the beginning of the string. - * @param {Number} number - The number to be converted. - * @returns {string} - Converted string prefixed by zero if necessary. - */ - getNumberStringWithLeadingZero(number) { - return `${number < 10 ? `0${number}` : number}`; - } - - /** - * Gives the new version of the state object, - * changing both selected month and year based on the given moment date. - * @param {moment.Moment} momentDate - Moment date object. - * @returns {{currentDateView: Date, selectedMonth: string, selectedYear: string}} - The new version of the state. - */ - getMonthState(momentDate) { - const clampedDate = this.clampDate(momentDate.toDate()); - const month = clampedDate.getMonth() + 1; - - return { - selectedMonth: this.getNumberStringWithLeadingZero(month), - selectedYear: clampedDate.getFullYear().toString(), // year might have changed too - currentDateView: clampedDate, - }; - } - /** * Handles the user pressing the previous month arrow of the calendar picker. */ moveToPrevMonth() { - this.setState( - (prev) => { - const momentDate = moment(prev.currentDateView).subtract(1, 'M'); - - return this.getMonthState(momentDate); - }, - () => { - this.props.onSelected(this.getSelectedDateString()); - }, - ); + this.setState((prev) => ({currentDateView: moment(prev.currentDateView).subtract(1, 'months').toDate()})); } /** * Handles the user pressing the next month arrow of the calendar picker. */ moveToNextMonth() { - this.setState( - (prev) => { - const momentDate = moment(prev.currentDateView).add(1, 'M'); - - return this.getMonthState(momentDate); - }, - () => { - this.props.onSelected(this.getSelectedDateString()); - }, - ); - } - - /** - * Checks whether the given date is in the min/max date range and returns the limit value if not. - * @param {Date} date - The date object to check. - * @returns {Date} - The date that is within the min/max date range. - */ - clampDate(date) { - if (this.props.maxDate < date) { - return this.props.maxDate; - } - if (this.props.minDate > date) { - return this.props.minDate; - } - return date; + this.setState((prev) => ({currentDateView: moment(prev.currentDateView).add(1, 'months').toDate()})); } render() { @@ -204,17 +125,18 @@ class CalendarPicker extends React.PureComponent { const currentMonthView = this.state.currentDateView.getMonth(); const currentYearView = this.state.currentDateView.getFullYear(); const calendarDaysMatrix = generateMonthMatrix(currentYearView, currentMonthView); - const hasAvailableDatesNextMonth = moment(this.props.maxDate).endOf('month').startOf('day') > moment(this.state.currentDateView).add(1, 'M'); - const hasAvailableDatesPrevMonth = moment(this.props.minDate).startOf('day') < moment(this.state.currentDateView).subtract(1, 'M').endOf('month'); + const hasAvailableDatesNextMonth = moment(this.props.maxDate).endOf('month').startOf('day') > moment(this.state.currentDateView).add(1, 'months'); + const hasAvailableDatesPrevMonth = moment(this.props.minDate).startOf('day') < moment(this.state.currentDateView).subtract(1, 'months').endOf('month'); return ( this.setState({isYearPickerVisible: true})} style={[styles.alignItemsCenter, styles.flexRow, styles.flex1, styles.justifyContentStart]} wrapperStyle={[styles.alignItemsCenter]} hoverDimmingValue={1} + testID="currentYearButton" accessibilityLabel={this.props.translate('common.currentYear')} > ))} + this.setState({isYearPickerVisible: false})} + /> ); } } -CalendarPicker.propTypes = calendarPickerPropType; -CalendarPicker.defaultProps = defaultCalendarPickerPropType; +CalendarPicker.propTypes = propTypes; +CalendarPicker.defaultProps = defaultProps; export default withLocalize(CalendarPicker); diff --git a/src/components/NewDatePicker/datePickerPropTypes.js b/src/components/NewDatePicker/datePickerPropTypes.js deleted file mode 100644 index b08371030fc0..000000000000 --- a/src/components/NewDatePicker/datePickerPropTypes.js +++ /dev/null @@ -1,38 +0,0 @@ -import PropTypes from 'prop-types'; -import moment from 'moment'; -import {propTypes as baseTextInputPropTypes, defaultProps as defaultBaseTextInputPropTypes} from '../TextInput/baseTextInputPropTypes'; -import CONST from '../../CONST'; - -const propTypes = { - ...baseTextInputPropTypes, - - /** - * The datepicker supports any value that `moment` can parse. - * `onInputChange` would always be called with a Date (or null) - */ - value: PropTypes.string, - - /** - * The datepicker supports any defaultValue that `moment` can parse. - * `onInputChange` would always be called with a Date (or null) - */ - defaultValue: PropTypes.string, - - /** A minimum date of calendar to select */ - minDate: PropTypes.objectOf(Date), - - /** A maximum date of calendar to select */ - maxDate: PropTypes.objectOf(Date), - - /** Default year to be set in the calendar picker */ - selectedYear: PropTypes.string, -}; - -const defaultProps = { - ...defaultBaseTextInputPropTypes, - minDate: moment().year(CONST.CALENDAR_PICKER.MIN_YEAR).toDate(), - maxDate: moment().year(CONST.CALENDAR_PICKER.MAX_YEAR).toDate(), - value: undefined, -}; - -export {propTypes, defaultProps}; diff --git a/src/components/NewDatePicker/index.js b/src/components/NewDatePicker/index.js index 5ab35aefb563..b5dad7992bfb 100644 --- a/src/components/NewDatePicker/index.js +++ b/src/components/NewDatePicker/index.js @@ -1,21 +1,43 @@ import React from 'react'; import {View} from 'react-native'; import moment from 'moment'; +import PropTypes from 'prop-types'; import TextInput from '../TextInput'; -import CalendarPicker from '../CalendarPicker'; import CONST from '../../CONST'; import styles from '../../styles/styles'; import * as Expensicons from '../Icon/Expensicons'; -import {propTypes as datePickerPropTypes, defaultProps as defaultDatePickerProps} from './datePickerPropTypes'; +import {propTypes as baseTextInputPropTypes, defaultProps as defaultBaseTextInputPropTypes} from '../TextInput/baseTextInputPropTypes'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; +import CalendarPicker from './CalendarPicker'; const propTypes = { + /** + * The datepicker supports any value that `moment` can parse. + * `onInputChange` would always be called with a Date (or null) + */ + value: PropTypes.string, + + /** + * The datepicker supports any defaultValue that `moment` can parse. + * `onInputChange` would always be called with a Date (or null) + */ + defaultValue: PropTypes.string, + + /** A minimum date of calendar to select */ + minDate: PropTypes.objectOf(Date), + + /** A maximum date of calendar to select */ + maxDate: PropTypes.objectOf(Date), + ...withLocalizePropTypes, - ...datePickerPropTypes, + ...baseTextInputPropTypes, }; const datePickerDefaultProps = { - ...defaultDatePickerProps, + ...defaultBaseTextInputPropTypes, + minDate: moment().year(CONST.CALENDAR_PICKER.MIN_YEAR).toDate(), + maxDate: moment().year(CONST.CALENDAR_PICKER.MAX_YEAR).toDate(), + value: undefined, }; class NewDatePicker extends React.Component { @@ -23,12 +45,10 @@ class NewDatePicker extends React.Component { super(props); this.state = { - selectedMonth: null, selectedDate: props.value || props.defaultValue || undefined, }; this.setDate = this.setDate.bind(this); - this.setCurrentSelectedMonth = this.setCurrentSelectedMonth.bind(this); // We're using uncontrolled input otherwise it won't be possible to // raise change events with a date value - each change will produce a date @@ -36,15 +56,6 @@ class NewDatePicker extends React.Component { this.defaultValue = props.defaultValue ? moment(props.defaultValue).format(CONST.DATE.MOMENT_FORMAT_STRING) : ''; } - /** - * Updates selected month when year picker is opened. - * This is used to keep the last visible month in the calendar when going back from year picker screen. - * @param {Date} currentDateView - */ - setCurrentSelectedMonth(currentDateView) { - this.setState({selectedMonth: currentDateView.getMonth()}); - } - /** * Trigger the `onInputChange` handler when the user input has a complete date or is cleared * @param {string} selectedDate @@ -81,9 +92,6 @@ class NewDatePicker extends React.Component { maxDate={this.props.maxDate} value={this.state.selectedDate} onSelected={this.setDate} - selectedMonth={this.state.selectedMonth} - selectedYear={this.props.selectedYear} - onYearPickerOpen={this.setCurrentSelectedMonth} /> diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index 518ff33b0ad4..b64d66782521 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -619,13 +619,6 @@ const SettingsModalStackNavigator = createModalStackNavigator([ }, name: 'GetAssistance', }, - { - getComponent: () => { - const YearPickerPage = require('../../../pages/YearPickerPage').default; - return YearPickerPage; - }, - name: 'YearPicker_Root', - }, { getComponent: () => { const SettingsTwoFactorAuthIsEnabled = require('../../../pages/settings/Security/TwoFactorAuth/IsEnabledPage').default; @@ -703,16 +696,6 @@ const WalletStatementStackNavigator = createModalStackNavigator([ }, ]); -const YearPickerStackNavigator = createModalStackNavigator([ - { - getComponent: () => { - const YearPickerPage = require('../../../pages/YearPickerPage').default; - return YearPickerPage; - }, - name: 'YearPicker_Root', - }, -]); - const FlagCommentStackNavigator = createModalStackNavigator([ { getComponent: () => { @@ -752,7 +735,6 @@ export { AddPersonalBankAccountModalStackNavigator, ReimbursementAccountModalStackNavigator, WalletStatementStackNavigator, - YearPickerStackNavigator, FlagCommentStackNavigator, EditRequestStackNavigator, }; diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js index a04e9bfc10ec..7d6d4cb2709c 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js @@ -85,10 +85,6 @@ function RigthModalNavigator() { name="Wallet_Statement" component={ModalStackNavigators.WalletStatementStackNavigator} /> - i + minYear), - (value) => ({ - text: value.toString(), - value, - keyForList: value.toString(), - isSelected: value === currentYear, - }), - ); - - this.updateYearOfBirth = this.updateSelectedYear.bind(this); - this.filterYearList = this.filterYearList.bind(this); - - this.state = { - inputText: '', - yearOptions: this.yearList, - }; - } - - /** - * Function called on selection of the year, to take user back to the previous screen - * - * @param {String} selectedYear - */ - updateSelectedYear(selectedYear) { - // We have to navigate using concatenation here as it is not possible to pass a function as a route param - const routes = lodashGet(this.props.navigation.getState(), 'routes', []); - const dateOfBirthRoute = _.find(routes, (route) => route.name === 'Settings_PersonalDetails_DateOfBirth'); - - if (dateOfBirthRoute) { - Navigation.setParams({year: selectedYear.toString()}, lodashGet(dateOfBirthRoute, 'key', '')); - Navigation.goBack(); - } else { - Navigation.goBack(`${ROUTES.SETTINGS_PERSONAL_DETAILS_DATE_OF_BIRTH}?year=${selectedYear}`); - } - } - - /** - * Function filtering the list of the items when using search input - * - * @param {String} text - */ - filterYearList(text) { - const searchText = text.replace(CONST.REGEX.NON_NUMERIC, ''); - this.setState((prevState) => { - if (searchText === prevState.inputText) { - return {}; - } - return { - inputText: searchText, - yearOptions: _.filter(this.yearList, (year) => year.text.includes(searchText)), - }; - }); - } - - render() { - const headerMessage = this.state.inputText.trim() && !this.state.yearOptions.length ? this.props.translate('common.noResultsFound') : ''; - return ( - - Navigation.goBack(`${this.props.route.params.backTo}?year=${this.currentYear}` || ROUTES.HOME)} - /> - this.updateSelectedYear(option.value)} - initiallyFocusedOptionKey={this.currentYear.toString()} - /> - - ); - } -} - -YearPickerPage.propTypes = propTypes; -YearPickerPage.defaultProps = defaultProps; - -export default withLocalize(YearPickerPage); diff --git a/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.js b/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.js index a0a6d2394314..422e31ee9fdf 100644 --- a/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.js +++ b/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.js @@ -1,21 +1,20 @@ -import React, {useCallback} from 'react'; +import moment from 'moment'; import PropTypes from 'prop-types'; +import React, {useCallback} from 'react'; import {withOnyx} from 'react-native-onyx'; -import moment from 'moment'; -import lodashGet from 'lodash/get'; -import ScreenWrapper from '../../../../components/ScreenWrapper'; +import CONST from '../../../../CONST'; +import ONYXKEYS from '../../../../ONYXKEYS'; +import ROUTES from '../../../../ROUTES'; +import Form from '../../../../components/Form'; import HeaderWithBackButton from '../../../../components/HeaderWithBackButton'; +import NewDatePicker from '../../../../components/NewDatePicker'; +import ScreenWrapper from '../../../../components/ScreenWrapper'; import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; -import Form from '../../../../components/Form'; -import ONYXKEYS from '../../../../ONYXKEYS'; +import Navigation from '../../../../libs/Navigation/Navigation'; import * as ValidationUtils from '../../../../libs/ValidationUtils'; -import styles from '../../../../styles/styles'; import * as PersonalDetails from '../../../../libs/actions/PersonalDetails'; import compose from '../../../../libs/compose'; -import NewDatePicker from '../../../../components/NewDatePicker'; -import CONST from '../../../../CONST'; -import Navigation from '../../../../libs/Navigation/Navigation'; -import ROUTES from '../../../../ROUTES'; +import styles from '../../../../styles/styles'; const propTypes = { /* Onyx Props */ @@ -34,16 +33,7 @@ const defaultProps = { }, }; -function DateOfBirthPage({translate, route, privatePersonalDetails}) { - /** - * The year should be set on the route when navigating back from the year picker - * This lets us pass the selected year without having to overwrite the value in Onyx - */ - const dobYear = String(moment(privatePersonalDetails.dob).year()); - const selectedYear = lodashGet(route.params, 'year', dobYear); - const minDate = moment().subtract(CONST.DATE_BIRTH.MAX_AGE, 'Y').toDate(); - const maxDate = moment().subtract(CONST.DATE_BIRTH.MIN_AGE, 'Y').toDate(); - +function DateOfBirthPage({translate, privatePersonalDetails}) { /** * @param {Object} values * @param {String} values.dob - date of birth @@ -83,9 +73,8 @@ function DateOfBirthPage({translate, route, privatePersonalDetails}) { inputID="dob" label={translate('common.date')} defaultValue={privatePersonalDetails.dob || ''} - minDate={minDate} - maxDate={maxDate} - selectedYear={selectedYear} + minDate={moment().subtract(CONST.DATE_BIRTH.MAX_AGE, 'years').toDate()} + maxDate={moment().subtract(CONST.DATE_BIRTH.MIN_AGE, 'years').toDate()} /> diff --git a/src/styles/styles.js b/src/styles/styles.js index fc226669696e..7e639f1c246d 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -827,6 +827,7 @@ const styles = { alignItems: 'center', paddingHorizontal: 15, paddingRight: 5, + ...userSelect.userSelectNone, }, calendarDayRoot: { @@ -834,6 +835,7 @@ const styles = { height: 45, justifyContent: 'center', alignItems: 'center', + ...userSelect.userSelectNone, }, calendarDayContainer: { @@ -1175,6 +1177,11 @@ const styles = { height: '100%', }, + sidebarAnimatedWrapperContainer: { + height: '100%', + position: 'absolute', + }, + sidebarFooter: { alignItems: 'center', display: 'flex', diff --git a/tests/unit/CalendarPickerTest.js b/tests/unit/CalendarPickerTest.js index 8313f8d709d5..71e31695a801 100644 --- a/tests/unit/CalendarPickerTest.js +++ b/tests/unit/CalendarPickerTest.js @@ -1,6 +1,6 @@ import {render, fireEvent, within} from '@testing-library/react-native'; import moment from 'moment'; -import CalendarPicker from '../../src/components/CalendarPicker'; +import CalendarPicker from '../../src/components/NewDatePicker/CalendarPicker'; import CONST from '../../src/CONST'; moment.locale(CONST.LOCALES.EN); @@ -11,21 +11,24 @@ jest.mock('@react-navigation/native', () => ({ createNavigationContainerRef: jest.fn(), })); -// eslint-disable-next-line arrow-body-style -function MockedCalendarPicker(props) { - return ( - ''} - preferredLocale={CONST.LOCALES.EN} - /> - ); -} +jest.mock('../../src/components/withLocalize', () => (Component) => (props) => ( + ''} + preferredLocale="en" + /> +)); + +jest.mock('../../src/hooks/useLocalize', () => + jest.fn(() => ({ + translate: jest.fn(), + })), +); describe('CalendarPicker', () => { test('renders calendar component', () => { - render(); + render(); }); test('displays the current month and year', () => { @@ -33,7 +36,7 @@ describe('CalendarPicker', () => { const maxDate = moment(currentDate).add(1, 'Y').toDate(); const minDate = moment(currentDate).subtract(1, 'Y').toDate(); const {getByText} = render( - , @@ -47,7 +50,7 @@ describe('CalendarPicker', () => { const minDate = new Date('2022-01-01'); const maxDate = new Date('2030-01-01'); const {getByTestId, getByText} = render( - , @@ -60,7 +63,7 @@ describe('CalendarPicker', () => { }); test('clicking previous month arrow updates the displayed month', () => { - const {getByTestId, getByText} = render(); + const {getByTestId, getByText} = render(); fireEvent.press(getByTestId('prev-month-arrow')); @@ -74,7 +77,7 @@ describe('CalendarPicker', () => { const maxDate = new Date('2030-01-01'); const value = '2023-01-01'; const {getByText} = render( - { const minDate = new Date('2022-01-01'); const maxDate = new Date('2030-01-01'); const {getByText, getByTestId} = render( - { const minDate = new Date('2003-02-01'); const value = new Date('2003-02-17'); const {getByTestId} = render( - , @@ -125,7 +128,7 @@ describe('CalendarPicker', () => { const maxDate = new Date('2003-02-24'); const value = '2003-02-17'; const {getByTestId} = render( - , @@ -138,7 +141,7 @@ describe('CalendarPicker', () => { const onSelectedMock = jest.fn(); const maxDate = new Date('2011-03-01'); const {getByText} = render( - , @@ -151,7 +154,7 @@ describe('CalendarPicker', () => { test('should open the calendar on a year from max date if it is earlier than current year', () => { const maxDate = new Date('2011-03-01'); - const {getByTestId} = render(); + const {getByTestId} = render(); expect(within(getByTestId('currentYearText')).getByText('2011')).toBeTruthy(); }); @@ -160,7 +163,7 @@ describe('CalendarPicker', () => { const minDate = new Date('2035-02-16'); const maxDate = new Date('2040-02-16'); const {getByTestId} = render( - , @@ -173,7 +176,7 @@ describe('CalendarPicker', () => { const value = '2003-02-17'; const minDate = new Date('2003-02-16'); const {getByLabelText} = render( - , @@ -186,7 +189,7 @@ describe('CalendarPicker', () => { const value = '2003-02-17'; const maxDate = new Date('2003-02-24'); const {getByLabelText} = render( - , @@ -199,7 +202,7 @@ describe('CalendarPicker', () => { const value = '2003-02-17'; const minDate = new Date('2003-02-16'); const {getByLabelText} = render( - , @@ -212,7 +215,7 @@ describe('CalendarPicker', () => { const value = '2003-02-17'; const maxDate = new Date('2003-02-24'); const {getByLabelText} = render( - , diff --git a/tests/unit/generateMonthMatrixTest.js b/tests/unit/generateMonthMatrixTest.js index b36ccc29f547..9f565a6e3a78 100644 --- a/tests/unit/generateMonthMatrixTest.js +++ b/tests/unit/generateMonthMatrixTest.js @@ -1,4 +1,4 @@ -import generateMonthMatrix from '../../src/components/CalendarPicker/generateMonthMatrix'; +import generateMonthMatrix from '../../src/components/NewDatePicker/CalendarPicker/generateMonthMatrix'; describe('generateMonthMatrix', () => { it('returns the correct matrix for January 2022', () => {