Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web/Desktop Press Enter key to go next #3977

Merged
merged 18 commits into from
Jul 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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