From 74422170370cefab92bc5b38d51a87376cb6bcf5 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Sun, 20 Feb 2022 10:08:10 +0530 Subject: [PATCH 001/204] feat: Add keyboard nav prop --- src/components/PopoverMenu/popoverMenuPropTypes.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/PopoverMenu/popoverMenuPropTypes.js b/src/components/PopoverMenu/popoverMenuPropTypes.js index 402b63418d1b..3ed64a18d7a9 100644 --- a/src/components/PopoverMenu/popoverMenuPropTypes.js +++ b/src/components/PopoverMenu/popoverMenuPropTypes.js @@ -49,6 +49,9 @@ const propTypes = { /** Whether disable the animations */ disableAnimation: PropTypes.bool, + + /** Enable keyboard navigation for menu items */ + allowKeyboardNavigation: PropTypes.bool, }; const defaultProps = { @@ -56,6 +59,7 @@ const defaultProps = { animationOut: 'fadeOut', headerText: undefined, disableAnimation: true, + allowKeyboardNavigation: false, }; export {propTypes, defaultProps}; From 7b4f2c4062ee17f91e7e1030f0cf9b6251a36888 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Sun, 20 Feb 2022 10:08:26 +0530 Subject: [PATCH 002/204] feat: MenuItem focused handling --- src/components/PopoverMenu/BasePopoverMenu.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/PopoverMenu/BasePopoverMenu.js b/src/components/PopoverMenu/BasePopoverMenu.js index 32551ba203d0..e0f7a77bf4cc 100644 --- a/src/components/PopoverMenu/BasePopoverMenu.js +++ b/src/components/PopoverMenu/BasePopoverMenu.js @@ -48,7 +48,7 @@ class BasePopoverMenu extends PureComponent { )} - {_.map(this.props.menuItems, item => ( + {_.map(this.props.menuItems, (item, index) => ( this.props.onItemSelected(item)} + focused={index === this.state.activeMenuIndex} /> ))} From 04c7aa60c918027821766d6a9a7d1abce6e166e9 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Sun, 20 Feb 2022 10:12:49 +0530 Subject: [PATCH 003/204] feat: Added keyboard nav handling to BasePopover --- src/components/PopoverMenu/BasePopoverMenu.js | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/components/PopoverMenu/BasePopoverMenu.js b/src/components/PopoverMenu/BasePopoverMenu.js index e0f7a77bf4cc..a60732e28eb8 100644 --- a/src/components/PopoverMenu/BasePopoverMenu.js +++ b/src/components/PopoverMenu/BasePopoverMenu.js @@ -26,6 +26,75 @@ const defaultProps = { }; class BasePopoverMenu extends PureComponent { + constructor(props) { + super(props); + this.state = { + activeMenuIndex: -1, + }; + } + + componentDidMount() { + if (!this.props.allowKeyboardNavigation) { + return; + } + this.setupEventHandlers(); + } + + componentWillUnmount() { + if (!this.props.allowKeyboardNavigation) { + return; + } + this.cleanupEventHandlers(); + } + + setupEventHandlers() { + if (!document) { + return; + } + + this.keyDownHandler = (keyBoardEvent) => { + if (keyBoardEvent.key.startsWith('Arrow')) { + this.highlightActiveMenu(keyBoardEvent.key); + return; + } + + if (keyBoardEvent.key === 'Enter' && this.state.activeMenuIndex !== -1) { + this.props.onItemSelected(this.props.menuItems[this.state.activeMenuIndex]); + } + }; + } + + highlightActiveMenu(arrowKey) { + let activeMenuIndex = this.state.activeMenuIndex; + if (arrowKey !== 'ArrowDown' && arrowKey !== 'ArrowUp') { + return; + } + + if (arrowKey === 'ArrowDown') { + if (activeMenuIndex === -1 || activeMenuIndex === this.props.menuItems.length - 1) { + activeMenuIndex = 0; + } else { + activeMenuIndex += 1; + } + } + + if (arrowKey === 'ArrowUp') { + if (activeMenuIndex === -1 || activeMenuIndex === 0) { + activeMenuIndex = this.props.menuItems.length - 1; + } else { + activeMenuIndex -= 1; + } + } + this.setState(() => ({activeMenuIndex})); + } + + cleanupEventHandlers() { + if (!document) { + return; + } + document.removeEventListener('keydown', this.keyDownHandler, true); + } + render() { return ( Date: Sun, 20 Feb 2022 10:13:05 +0530 Subject: [PATCH 004/204] feat: Force disabled keyboard nav to native popover --- src/components/PopoverMenu/index.native.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/PopoverMenu/index.native.js b/src/components/PopoverMenu/index.native.js index 7cb8722e0d29..b4b8377d2868 100644 --- a/src/components/PopoverMenu/index.native.js +++ b/src/components/PopoverMenu/index.native.js @@ -28,6 +28,7 @@ class PopoverMenu extends Component { this.selectItem(item)} /> From 22c2ec43e4ddd8412bb75ddcfe9ae597a2fe21c0 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Sun, 20 Feb 2022 10:17:42 +0530 Subject: [PATCH 005/204] feat: Added border color focus and nav to compose actions --- src/components/PopoverMenu/BasePopoverMenu.js | 1 + src/pages/home/report/ReportActionCompose.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/PopoverMenu/BasePopoverMenu.js b/src/components/PopoverMenu/BasePopoverMenu.js index a60732e28eb8..e7ef644c1975 100644 --- a/src/components/PopoverMenu/BasePopoverMenu.js +++ b/src/components/PopoverMenu/BasePopoverMenu.js @@ -127,6 +127,7 @@ class BasePopoverMenu extends PureComponent { description={item.description} onPress={() => this.props.onItemSelected(item)} focused={index === this.state.activeMenuIndex} + wrapperStyle={index === this.state.activeMenuIndex ? styles.borderColorFocus : {}} /> ))} diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index c25817ed821a..ae2a182b32f9 100755 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -457,6 +457,7 @@ class ReportActionCompose extends React.Component { onClose={() => this.setMenuVisibility(false)} onItemSelected={() => this.setMenuVisibility(false)} anchorPosition={styles.createMenuPositionReportActionCompose} + allowKeyboardNavigation animationIn="fadeInUp" animationOut="fadeOutDown" menuItems={[ From 8e56baba11b1fce4f1318de9634b635433992517 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Sun, 20 Feb 2022 10:59:06 +0530 Subject: [PATCH 006/204] feat: Added separate styles for menu Item --- src/components/PopoverMenu/BasePopoverMenu.js | 4 ++-- src/styles/styles.js | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/PopoverMenu/BasePopoverMenu.js b/src/components/PopoverMenu/BasePopoverMenu.js index e7ef644c1975..1af979820289 100644 --- a/src/components/PopoverMenu/BasePopoverMenu.js +++ b/src/components/PopoverMenu/BasePopoverMenu.js @@ -51,7 +51,6 @@ class BasePopoverMenu extends PureComponent { if (!document) { return; } - this.keyDownHandler = (keyBoardEvent) => { if (keyBoardEvent.key.startsWith('Arrow')) { this.highlightActiveMenu(keyBoardEvent.key); @@ -62,6 +61,7 @@ class BasePopoverMenu extends PureComponent { this.props.onItemSelected(this.props.menuItems[this.state.activeMenuIndex]); } }; + document.addEventListener('keydown', this.keyDownHandler, true); } highlightActiveMenu(arrowKey) { @@ -127,7 +127,7 @@ class BasePopoverMenu extends PureComponent { description={item.description} onPress={() => this.props.onItemSelected(item)} focused={index === this.state.activeMenuIndex} - wrapperStyle={index === this.state.activeMenuIndex ? styles.borderColorFocus : {}} + wrapperStyle={index === this.state.activeMenuIndex ? styles.focusedPopoverMenuItem : {}} /> ))} diff --git a/src/styles/styles.js b/src/styles/styles.js index efa5ab09f0f6..62c48a791b82 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -1065,6 +1065,12 @@ const styles = { width: '100%', }, + focusedPopoverMenuItem: { + borderWidth: 1, + borderRadius: 0, + borderColor: themeColors.borderFocus, + }, + popoverMenuIcon: { width: variables.componentSizeNormal, height: variables.componentSizeNormal, From 375193f6eb9c6ce00cd03a932e5e4b727f3d85a6 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Sun, 20 Feb 2022 17:44:19 +0530 Subject: [PATCH 007/204] feat: Reset activeMenuIndex on Popver close --- src/components/PopoverMenu/BasePopoverMenu.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/PopoverMenu/BasePopoverMenu.js b/src/components/PopoverMenu/BasePopoverMenu.js index 1af979820289..078b3672163a 100644 --- a/src/components/PopoverMenu/BasePopoverMenu.js +++ b/src/components/PopoverMenu/BasePopoverMenu.js @@ -31,6 +31,7 @@ class BasePopoverMenu extends PureComponent { this.state = { activeMenuIndex: -1, }; + this.onModalHide = this.onModalHide.bind(this); } componentDidMount() { @@ -47,6 +48,11 @@ class BasePopoverMenu extends PureComponent { this.cleanupEventHandlers(); } + onModalHide() { + this.setState({activeMenuIndex: -1}); + this.props.onMenuHide(); + } + setupEventHandlers() { if (!document) { return; @@ -101,7 +107,7 @@ class BasePopoverMenu extends PureComponent { anchorPosition={this.props.anchorPosition} onClose={this.props.onClose} isVisible={this.props.isVisible} - onModalHide={this.props.onMenuHide} + onModalHide={this.onModalHide} animationIn={this.props.animationIn} animationOut={this.props.animationOut} disableAnimation={this.props.disableAnimation} From 0832d6bbe89e8c6d7f6ac1d54a860a8837f37ee3 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Sun, 20 Feb 2022 22:18:35 +0530 Subject: [PATCH 008/204] feat: Removed side borders --- src/styles/styles.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/styles/styles.js b/src/styles/styles.js index 62c48a791b82..ab7a1f46e505 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -1068,6 +1068,8 @@ const styles = { focusedPopoverMenuItem: { borderWidth: 1, borderRadius: 0, + borderRightWidth: 0, + borderLeftWidth: 0, borderColor: themeColors.borderFocus, }, From fe073c34429b1a84c1e39a8b9b85193d0e265d5b Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 21 Mar 2022 21:11:18 +0530 Subject: [PATCH 009/204] fix: removed keyboard flag from native popover --- src/components/PopoverMenu/index.native.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/PopoverMenu/index.native.js b/src/components/PopoverMenu/index.native.js index b4b8377d2868..7cb8722e0d29 100644 --- a/src/components/PopoverMenu/index.native.js +++ b/src/components/PopoverMenu/index.native.js @@ -28,7 +28,6 @@ class PopoverMenu extends Component { this.selectItem(item)} /> From 12f03aac2820e676b41068c8d9cabf911a37f05a Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 21 Mar 2022 21:11:37 +0530 Subject: [PATCH 010/204] feat: added transparent border for non focused items --- src/components/PopoverMenu/BasePopoverMenu.js | 2 +- src/styles/styles.js | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/PopoverMenu/BasePopoverMenu.js b/src/components/PopoverMenu/BasePopoverMenu.js index 078b3672163a..79d7f75680c8 100644 --- a/src/components/PopoverMenu/BasePopoverMenu.js +++ b/src/components/PopoverMenu/BasePopoverMenu.js @@ -133,7 +133,7 @@ class BasePopoverMenu extends PureComponent { description={item.description} onPress={() => this.props.onItemSelected(item)} focused={index === this.state.activeMenuIndex} - wrapperStyle={index === this.state.activeMenuIndex ? styles.focusedPopoverMenuItem : {}} + wrapperStyle={[styles.unfocusedPopoverMenuItem, index === this.state.activeMenuIndex ? styles.focusedPopoverMenuItem : {}]} /> ))} diff --git a/src/styles/styles.js b/src/styles/styles.js index 42168875cee6..0a787bdbfbc4 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -1054,11 +1054,15 @@ const styles = { }, focusedPopoverMenuItem: { + borderColor: themeColors.borderFocus, + }, + + unfocusedPopoverMenuItem: { borderWidth: 1, borderRadius: 0, borderRightWidth: 0, borderLeftWidth: 0, - borderColor: themeColors.borderFocus, + borderColor: 'transparent', }, popoverMenuIcon: { From ba9773079626299f4616fc92a560f93dcbc16247 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 21 Mar 2022 21:21:27 +0530 Subject: [PATCH 011/204] feat: add onFocus to MenuItem Pressable --- src/components/MenuItem.js | 4 ++++ src/components/menuItemPropTypes.js | 3 +++ 2 files changed, 7 insertions(+) diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index 6c5c20eb008e..0a5f702ecce6 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -38,6 +38,7 @@ const defaultProps = { subtitle: undefined, iconType: 'icon', onPress: () => {}, + onFocus: () => {}, interactive: true, }; @@ -50,6 +51,9 @@ const MenuItem = props => ( props.onPress(e); }} + onFocus={(e) => { + props.onFocus(e); + }} style={({hovered, pressed}) => ([ styles.popoverMenuItem, StyleUtils.getButtonBackgroundColorStyle(getButtonState(props.focused || hovered, pressed, props.success, props.disabled, props.interactive)), diff --git a/src/components/menuItemPropTypes.js b/src/components/menuItemPropTypes.js index 0ad5a43498af..58635b9d799d 100644 --- a/src/components/menuItemPropTypes.js +++ b/src/components/menuItemPropTypes.js @@ -13,6 +13,9 @@ const propTypes = { /** Function to fire when component is pressed */ onPress: PropTypes.func, + /** Function to fire when component is focused */ + onFocus: PropTypes.func, + /** Icon to display on the left side of component */ icon: PropTypes.oneOfType([PropTypes.elementType, PropTypes.string]), From 7fa16c4fd9faf213148dfff5fc78b1c872c84a1b Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 21 Mar 2022 21:21:37 +0530 Subject: [PATCH 012/204] feat: set activeMenuIndex on tab --- src/components/PopoverMenu/BasePopoverMenu.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/PopoverMenu/BasePopoverMenu.js b/src/components/PopoverMenu/BasePopoverMenu.js index 79d7f75680c8..5e8124f3e704 100644 --- a/src/components/PopoverMenu/BasePopoverMenu.js +++ b/src/components/PopoverMenu/BasePopoverMenu.js @@ -133,6 +133,7 @@ class BasePopoverMenu extends PureComponent { description={item.description} onPress={() => this.props.onItemSelected(item)} focused={index === this.state.activeMenuIndex} + onFocus={() => this.setState({activeMenuIndex: index})} wrapperStyle={[styles.unfocusedPopoverMenuItem, index === this.state.activeMenuIndex ? styles.focusedPopoverMenuItem : {}]} /> ))} From 8e9b60dc817488aa47f3156e0ed3c61fad0162d4 Mon Sep 17 00:00:00 2001 From: Mahendra Liya Date: Tue, 14 Jun 2022 12:05:02 +0530 Subject: [PATCH 013/204] fixed closing of keyboard on changing text field on profile page --- src/ONYXKEYS.js | 1 + src/libs/actions/PersonalDetails.js | 4 + src/pages/settings/Profile/ProfilePage.js | 132 ++++++++++++++-------- 3 files changed, 88 insertions(+), 49 deletions(-) diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index 9e198d07aad7..6c032f30f881 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -194,5 +194,6 @@ export default { // List of Form ids FORMS: { ADD_DEBIT_CARD_FORM: 'addDebitCardForm', + PROFILE_FORM: 'profileForm', }, }; diff --git a/src/libs/actions/PersonalDetails.js b/src/libs/actions/PersonalDetails.js index cc7add84320e..d3504316b061 100644 --- a/src/libs/actions/PersonalDetails.js +++ b/src/libs/actions/PersonalDetails.js @@ -292,6 +292,10 @@ function setPersonalDetails(details, shouldGrowl) { } else { console.debug('Error while setting personal details', response); } + + Onyx.merge(ONYXKEYS.FORMS.PROFILE_FORM, { + isSubmitting: false, + }); }); } diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index fbeb7b69b16c..5d4da0f43740 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -2,7 +2,7 @@ import lodashGet from 'lodash/get'; import React, {Component} from 'react'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; -import {View, ScrollView} from 'react-native'; +import {View} from 'react-native'; import Str from 'expensify-common/lib/str'; import moment from 'moment-timezone'; import _ from 'underscore'; @@ -18,17 +18,15 @@ import Text from '../../../components/Text'; import LoginField from './LoginField'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import compose from '../../../libs/compose'; -import Button from '../../../components/Button'; import KeyboardAvoidingView from '../../../components/KeyboardAvoidingView'; -import FixedFooter from '../../../components/FixedFooter'; import TextInput from '../../../components/TextInput'; import Picker from '../../../components/Picker'; -import FullNameInputRow from '../../../components/FullNameInputRow'; import CheckboxWithLabel from '../../../components/CheckboxWithLabel'; import AvatarWithImagePicker from '../../../components/AvatarWithImagePicker'; import currentUserPersonalDetailsPropsTypes from './currentUserPersonalDetailsPropsTypes'; import * as ValidationUtils from '../../../libs/ValidationUtils'; import * as ReportUtils from '../../../libs/ReportUtils'; +import Form from '../../../components/Form'; const propTypes = { /* Onyx Props */ @@ -86,9 +84,9 @@ class ProfilePage extends Component { }; this.getLogins = this.getLogins.bind(this); + this.validate = this.validate.bind(this); this.setAutomaticTimezone = this.setAutomaticTimezone.bind(this); this.updatePersonalDetails = this.updatePersonalDetails.bind(this); - this.validateInputs = this.validateInputs.bind(this); this.updateAvatar = this.updateAvatar.bind(this); } @@ -162,10 +160,6 @@ class ProfilePage extends Component { * Submit form to update personal details */ updatePersonalDetails() { - if (!this.validateInputs()) { - return; - } - // Check if the user has modified their avatar if ((this.props.myPersonalDetails.avatar !== this.state.avatar.uri) && this.state.isAvatarChanged) { // If the user removed their profile photo, replace it accordingly with the default avatar @@ -190,17 +184,40 @@ class ProfilePage extends Component { }, true); } - validateInputs() { - const [hasFirstNameError, hasLastNameError, hasPronounError] = ValidationUtils.doesFailCharacterLimit( - 50, - [this.state.firstName.trim(), this.state.lastName.trim(), this.state.pronouns.trim()], - ); - this.setState({ - hasFirstNameError, - hasLastNameError, - hasPronounError, - }); - return !hasFirstNameError && !hasLastNameError && !hasPronounError; + validate(values) { + const errors = {}; + + if (values.firstName) { + const [hasFirstNameError] = ValidationUtils.doesFailCharacterLimit( + 50, + [values.firstName.trim()], + ); + + this.setState({hasFirstNameError}); + errors.firstName = PersonalDetails.getMaxCharacterError(this.state.hasFirstNameError); + } + + if (values.lastName) { + const [hasLastNameError] = ValidationUtils.doesFailCharacterLimit( + 50, + [values.lastName.trim()], + ); + + this.setState({hasLastNameError}); + errors.lastName = PersonalDetails.getMaxCharacterError(this.state.hasLastNameError); + } + + if (values.pronouns) { + const [hasPronounError] = ValidationUtils.doesFailCharacterLimit( + 50, + [values.pronouns.trim()], + ); + + this.setState({hasPronounError}); + errors.pronouns = PersonalDetails.getMaxCharacterError(this.state.hasPronounError); + } + + return errors; } render() { @@ -209,14 +226,6 @@ class ProfilePage extends Component { value: `${CONST.PRONOUNS.PREFIX}${key}`, })); - // Disables button if none of the form values have changed - const isButtonDisabled = (this.props.myPersonalDetails.firstName === this.state.firstName.trim()) - && (this.props.myPersonalDetails.lastName === this.state.lastName.trim()) - && (this.props.myPersonalDetails.timezone.selected === this.state.selectedTimezone) - && (this.props.myPersonalDetails.timezone.automatic === this.state.isAutomaticTimezone) - && (this.props.myPersonalDetails.pronouns === this.state.pronouns.trim()) - && (!this.state.isAvatarChanged || this.props.myPersonalDetails.avatarUploading); - const pronounsPickerValue = this.state.hasSelfSelectedPronouns ? CONST.PRONOUNS.SELF_SELECT : this.state.pronouns; return ( @@ -228,8 +237,15 @@ class ProfilePage extends Component { onBackButtonPress={() => Navigation.navigate(ROUTES.SETTINGS)} onCloseButtonPress={() => Navigation.dismissModal(true)} /> - +
{this.props.translate('profilePage.tellUsAboutYourself')} - this.setState({firstName})} - onChangeLastName={lastName => this.setState({lastName})} - style={[styles.mt4, styles.mb4]} - /> + + + + this.setState({firstName})} + placeholder={this.props.translate('profilePage.john')} + /> + + + this.setState({lastName})} + placeholder={this.props.translate('profilePage.doe')} + /> + + { const hasSelfSelectedPronouns = pronouns === CONST.PRONOUNS.SELF_SELECT; @@ -266,53 +301,52 @@ class ProfilePage extends Component { label: this.props.translate('profilePage.selectYourPronouns'), }} value={pronounsPickerValue} + shouldSaveDraft /> {this.state.hasSelfSelectedPronouns && ( this.setState({pronouns})} placeholder={this.props.translate('profilePage.selfSelectYourPronoun')} + hasError={this.state.hasPronounError} errorText={PersonalDetails.getMaxCharacterError(this.state.hasPronounError)} /> )} this.setState({selectedTimezone})} items={timezones} isDisabled={this.state.isAutomaticTimezone} value={this.state.selectedTimezone} + shouldSaveDraft /> - - -