Skip to content

Commit

Permalink
Merge pull request #3977 from rushatgabhane/enter-go-next
Browse files Browse the repository at this point in the history
Web/Desktop Press Enter key to go next
  • Loading branch information
luacmartins authored Jul 15, 2021
2 parents b6d2c0d + 1eb0b0c commit f497ba8
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 90 deletions.
1 change: 1 addition & 0 deletions src/components/AttachmentModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ class AttachmentModal extends PureComponent {
textStyles={[styles.buttonConfirmText]}
text={this.props.translate('common.send')}
onPress={this.submitAndClose}
pressOnEnter
/>
)}
</Modal>
Expand Down
125 changes: 78 additions & 47 deletions src/components/Button.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import _ from 'underscore';
import React from 'react';
import React, {Component} from 'react';
import {Pressable, ActivityIndicator} from 'react-native';
import PropTypes from 'prop-types';
import {
Pressable, ActivityIndicator,
} from 'react-native';
import styles from '../styles/styles';
import themeColors from '../styles/themes/default';
import OpacityView from './OpacityView';
import Text from './Text';
import KeyboardShortcut from '../libs/KeyboardShortcut';

const propTypes = {
/** The text for the button label */
Expand All @@ -28,6 +27,9 @@ const propTypes = {
/** A function that is called when the button is clicked on */
onPress: PropTypes.func,

/** Call the onPress function when Enter key is pressed */
pressOnEnter: PropTypes.bool,

/** Additional styles to add after local styles */
style: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.object),
Expand Down Expand Up @@ -60,6 +62,7 @@ const defaultProps = {
small: false,
large: false,
onPress: () => {},
pressOnEnter: false,
style: [],
textStyles: [],
success: false,
Expand All @@ -69,67 +72,95 @@ const defaultProps = {
shouldRemoveLeftBorderRadius: false,
};

const Button = (props) => {
const additionalStyles = _.isArray(props.style) ? props.style : [props.style];
class Button extends Component {
constructor(props) {
super(props);
this.additionalStyles = _.isArray(this.props.style) ? this.props.style : [this.props.style];

this.renderContent = this.renderContent.bind(this);
}

componentDidMount() {
if (!this.props.pressOnEnter) {
return;
}

// Setup and attach keypress handler for pressing the button with Enter key
this.unsubscribe = KeyboardShortcut.subscribe('Enter', () => {
if (!this.props.isDisabled && !this.props.isLoading) {
this.props.onPress();
}
}, [], true);
}

function renderContent() {
const {ContentComponent} = props;
componentWillUnmount() {
// Cleanup event listeners
if (!this.unsubscribe) {
return;
}
this.unsubscribe();
}

renderContent() {
const {ContentComponent} = this.props;
if (ContentComponent) {
return <ContentComponent />;
}

return props.isLoading
return this.props.isLoading
? (
<ActivityIndicator color={themeColors.textReversed} />
) : (
<Text
selectable={false}
style={[
styles.buttonText,
props.small && styles.buttonSmallText,
props.large && styles.buttonLargeText,
props.success && styles.buttonSuccessText,
props.danger && styles.buttonDangerText,
...props.textStyles,
this.props.small && styles.buttonSmallText,
this.props.large && styles.buttonLargeText,
this.props.success && styles.buttonSuccessText,
this.props.danger && styles.buttonDangerText,
...this.props.textStyles,
]}
>
{props.text}
{this.props.text}
</Text>
);
}

return (
<Pressable
onPress={props.onPress}
disabled={props.isLoading || props.isDisabled}
style={[
props.isDisabled ? styles.cursorDisabled : {},
...additionalStyles,
]}
>
{({pressed, hovered}) => (
<OpacityView
shouldDim={pressed}
style={[
styles.button,
props.small ? styles.buttonSmall : undefined,
props.large ? styles.buttonLarge : undefined,
props.success ? styles.buttonSuccess : undefined,
props.danger ? styles.buttonDanger : undefined,
(props.isDisabled && props.danger) ? styles.buttonDangerDisabled : undefined,
(props.isDisabled && !props.danger) ? styles.buttonDisable : undefined,
(props.success && hovered) ? styles.buttonSuccessHovered : undefined,
(props.danger && hovered) ? styles.buttonDangerHovered : undefined,
props.shouldRemoveRightBorderRadius ? styles.noRightBorderRadius : undefined,
props.shouldRemoveLeftBorderRadius ? styles.noLeftBorderRadius : undefined,
]}
>
{renderContent()}
</OpacityView>
)}
</Pressable>
);
};
render() {
return (
<Pressable
onPress={this.props.onPress}
disabled={this.props.isLoading || this.props.isDisabled}
style={[
this.props.isDisabled ? styles.cursorDisabled : {},
...this.additionalStyles,
]}
>
{({pressed, hovered}) => (
<OpacityView
shouldDim={pressed}
style={[
styles.button,
this.props.small ? styles.buttonSmall : undefined,
this.props.large ? styles.buttonLarge : undefined,
this.props.success ? styles.buttonSuccess : undefined,
this.props.danger ? styles.buttonDanger : undefined,
(this.props.isDisabled && this.props.danger) ? styles.buttonDangerDisabled : undefined,
(this.props.isDisabled && !this.props.danger) ? styles.buttonDisable : undefined,
(this.props.success && hovered) ? styles.buttonSuccessHovered : undefined,
(this.props.danger && hovered) ? styles.buttonDangerHovered : undefined,
this.props.shouldRemoveRightBorderRadius ? styles.noRightBorderRadius : undefined,
this.props.shouldRemoveLeftBorderRadius ? styles.noLeftBorderRadius : undefined,
]}
>
{this.renderContent()}
</OpacityView>
)}
</Pressable>
);
}
}

Button.propTypes = propTypes;
Button.defaultProps = defaultProps;
Expand Down
1 change: 1 addition & 0 deletions src/components/ConfirmModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ const ConfirmModal = props => (
danger={props.danger}
style={[styles.mt4]}
onPress={props.onConfirm}
pressOnEnter
text={props.confirmText || props.translate('common.yes')}
/>
<Button
Expand Down
1 change: 1 addition & 0 deletions src/components/IOUConfirmationList.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ class IOUConfirmationList extends Component {
isLoading={this.props.iou.loading}
text={buttonText}
onPress={() => this.props.onConfirm(this.getSplits())}
pressOnEnter
/>
</FixedFooter>
</>
Expand Down
30 changes: 5 additions & 25 deletions src/components/Modal/BaseModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import {View} from 'react-native';
import PropTypes from 'prop-types';
import ReactNativeModal from 'react-native-modal';
import {SafeAreaInsetsContext} from 'react-native-safe-area-context';

import KeyboardShortcut from '../../libs/KeyboardShortcut';
import styles, {getModalPaddingStyles, getSafeAreaPadding} from '../../styles/styles';
import themeColors from '../../styles/themes/default';
import {propTypes as modalPropTypes, defaultProps as modalDefaultProps} from './ModalPropTypes';
Expand All @@ -27,22 +25,19 @@ class BaseModal extends PureComponent {
constructor(props) {
super(props);

this.hideModalAndRemoveEventListeners = this.hideModalAndRemoveEventListeners.bind(this);
this.subscribeToKeyEvents = this.subscribeToKeyEvents.bind(this);
this.unsubscribeFromKeyEvents = this.unsubscribeFromKeyEvents.bind(this);
this.hideModal = this.hideModal.bind(this);
}

componentWillUnmount() {
// we don't want to call the onModalHide on unmount
this.hideModalAndRemoveEventListeners(this.props.isVisible);
this.hideModal(this.props.isVisible);
}

/**
* Hides modal and unsubscribes from key event listeners
* Hides modal
* @param {Boolean} [callHideCallback=true] Should we call the onModalHide callback
*/
hideModalAndRemoveEventListeners(callHideCallback = true) {
this.unsubscribeFromKeyEvents();
hideModal(callHideCallback = true) {
if (this.props.shouldSetModalVisibility) {
setModalVisibility(false);
}
Expand All @@ -51,20 +46,6 @@ class BaseModal extends PureComponent {
}
}

/**
* Listens to specific keyboard keys when the modal has been opened
*/
subscribeToKeyEvents() {
KeyboardShortcut.subscribe('Enter', this.props.onSubmit, [], true);
}

/**
* Stops listening to keyboard keys when modal has been closed
*/
unsubscribeFromKeyEvents() {
KeyboardShortcut.unsubscribe('Enter');
}

render() {
const {
modalStyle,
Expand Down Expand Up @@ -97,14 +78,13 @@ class BaseModal extends PureComponent {
// eslint-disable-next-line react/jsx-props-no-multi-spaces
onBackButtonPress={this.props.onClose}
onModalShow={() => {
this.subscribeToKeyEvents();
if (this.props.shouldSetModalVisibility) {
setModalVisibility(true);
}
this.props.onModalShow();
}}
propagateSwipe={this.props.propagateSwipe}
onModalHide={this.hideModalAndRemoveEventListeners}
onModalHide={this.hideModal}
onSwipeComplete={this.props.onClose}
swipeDirection={swipeDirection}
isVisible={this.props.isVisible}
Expand Down
20 changes: 2 additions & 18 deletions src/pages/iou/steps/IOUAmountPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import withLocalize, {withLocalizePropTypes} from '../../../components/withLocal
import compose from '../../../libs/compose';
import Button from '../../../components/Button';
import Text from '../../../components/Text';
import KeyboardShortcut from '../../../libs/KeyboardShortcut';

const propTypes = {
// Whether or not this IOU has multiple participants
Expand Down Expand Up @@ -77,31 +76,15 @@ class IOUAmountPage extends React.Component {

componentDidMount() {
// Component is not initialized yet due to navigation transitions
// Wait until interactions are complete before trying to focus or attach listener
// Wait until interactions are complete before trying to focus
InteractionManager.runAfterInteractions(() => {
// Setup and attach keypress handler for navigating to the next screen
this.unsubscribe = KeyboardShortcut.subscribe('Enter', () => {
if (this.state.amount !== '') {
this.props.onStepComplete(this.state.amount);
}
}, [], true);

// Focus text input
if (this.textInput) {
this.textInput.focus();
}
});
}

componentWillUnmount() {
// Cleanup all keydown event listeners that we've set up
if (!this.unsubscribe) {
return;
}

this.unsubscribe();
}

/**
* Check if amount is a decimal upto 3 digits
*
Expand Down Expand Up @@ -197,6 +180,7 @@ class IOUAmountPage extends React.Component {
success
style={[styles.w100, styles.mt5]}
onPress={() => this.props.onStepComplete(this.state.amount)}
pressOnEnter
isDisabled={!this.state.amount.length || parseFloat(this.state.amount) < 0.01}
text={this.props.translate('common.next')}
/>
Expand Down
1 change: 1 addition & 0 deletions src/pages/settings/Payments/AddPayPalMePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class AddPayPalMePage extends React.Component {
<Button
success
onPress={this.setPayPalMeUsername}
pressOnEnter
style={[styles.mt3]}
text={this.props.translate('addPayPalMePage.addPayPalAccount')}
/>
Expand Down
2 changes: 2 additions & 0 deletions src/pages/workspace/NewWorkspacePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class NewWorkspacePage extends React.Component {
label={this.props.translate('workspace.new.chooseAName')}
value={this.state.name}
onChangeText={name => this.setState({name})}
onSubmitEditting={this.submit}
/>
<Text style={[styles.mt6]}>{this.props.translate('workspace.new.helpText')}</Text>
</View>
Expand All @@ -73,6 +74,7 @@ class NewWorkspacePage extends React.Component {
style={[styles.w100]}
text={this.props.translate('workspace.new.getStarted')}
onPress={this.submit}
pressOnEnter
/>
</View>
</ScreenWrapper>
Expand Down
1 change: 1 addition & 0 deletions src/pages/workspace/WorkspaceInvitePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ class WorkspaceInvitePage extends React.Component {
isDisabled={!this.state.emailOrPhone}
text={this.props.translate('common.invite')}
onPress={this.inviteUser}
pressOnEnter
/>
</FixedFooter>
</KeyboardAvoidingView>
Expand Down

0 comments on commit f497ba8

Please sign in to comment.