From 4471e8bd1d80065ce3e5830bdeeb300104a4a511 Mon Sep 17 00:00:00 2001 From: Priyesh Shah Date: Fri, 28 Apr 2023 12:15:25 +1000 Subject: [PATCH 01/12] added disabling of Button during onPress call in PressableWithFeedback --- src/components/Button/index.js | 70 +++++++++---------- .../GenericPressable/BaseGenericPressable.js | 26 ++++--- .../Pressable/PressableWithFeedback.js | 32 ++++++++- src/libs/actions/Policy.js | 3 +- src/pages/workspace/WorkspacesListPage.js | 3 +- 5 files changed, 83 insertions(+), 51 deletions(-) diff --git a/src/components/Button/index.js b/src/components/Button/index.js index 9a1128b3f2d7..d38baa181eeb 100644 --- a/src/components/Button/index.js +++ b/src/components/Button/index.js @@ -1,9 +1,8 @@ import React, {Component} from 'react'; -import {Pressable, ActivityIndicator, View} from 'react-native'; +import {ActivityIndicator, View} from 'react-native'; import PropTypes from 'prop-types'; import styles from '../../styles/styles'; import themeColors from '../../styles/themes/default'; -import OpacityView from '../OpacityView'; import Text from '../Text'; import KeyboardShortcut from '../../libs/KeyboardShortcut'; import Icon from '../Icon'; @@ -15,6 +14,7 @@ import compose from '../../libs/compose'; import * as Expensicons from '../Icon/Expensicons'; import withNavigationFocus from '../withNavigationFocus'; import validateSubmitShortcut from './validateSubmitShortcut'; +import PressableWithFeedback from '../Pressable/PressableWithFeedback'; const propTypes = { /** The text for the button label */ @@ -109,6 +109,9 @@ const propTypes = { /** Id to use for this button */ nativeID: PropTypes.string, + + /** accessibility label for the component */ + accessibilityLabel: PropTypes.string, }; const defaultProps = { @@ -140,6 +143,7 @@ const defaultProps = { shouldRemoveLeftBorderRadius: false, shouldEnableHapticFeedback: false, nativeID: '', + accessibilityLabel: '', }; class Button extends Component { @@ -233,7 +237,7 @@ class Button extends Component { render() { return ( - { if (e && e.type === 'click') { e.currentTarget.blur(); @@ -254,47 +258,41 @@ class Button extends Component { onPressOut={this.props.onPressOut} onMouseDown={this.props.onMouseDown} disabled={this.props.isLoading || this.props.isDisabled} - style={[ + wrapperStyle={[ this.props.isDisabled ? {...styles.cursorDisabled, ...styles.noSelect} : {}, + (this.props.isDisabled && (this.props.success || this.props.danger)) ? styles.buttonOpacityDisabled : undefined, + (this.props.isDisabled && !this.props.danger && !this.props.success) ? styles.buttonDisabled : undefined, styles.buttonContainer, this.props.shouldRemoveRightBorderRadius ? styles.noRightBorderRadius : undefined, this.props.shouldRemoveLeftBorderRadius ? styles.noLeftBorderRadius : undefined, ...StyleUtils.parseStyleAsArray(this.props.style), ]} + style={({isHovered, isDisabled}) => [ + styles.button, + this.props.small ? styles.buttonSmall : undefined, + this.props.medium ? styles.buttonMedium : undefined, + this.props.large ? styles.buttonLarge : undefined, + this.props.success ? styles.buttonSuccess : undefined, + this.props.danger ? styles.buttonDanger : undefined, + (this.props.success && isHovered && !isDisabled) ? styles.buttonSuccessHovered : undefined, + (this.props.danger && isHovered && !isDisabled) ? styles.buttonDangerHovered : undefined, + this.props.shouldRemoveRightBorderRadius ? styles.noRightBorderRadius : undefined, + this.props.shouldRemoveLeftBorderRadius ? styles.noLeftBorderRadius : undefined, + ...this.props.innerStyles, + ]} nativeID={this.props.nativeID} + accessibilityLabel={this.props.accessibilityLabel} > - {({pressed, hovered}) => { - const activeAndHovered = !this.props.isDisabled && hovered; - return ( - - {this.renderContent()} - {this.props.isLoading && ( - - )} - - ); - }} - + + {this.renderContent()} + {this.props.isLoading && ( + + )} + + ); } } diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.js b/src/components/Pressable/GenericPressable/BaseGenericPressable.js index 6caba1c39cd0..7aeadd0c8a0b 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.js +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.js @@ -70,31 +70,37 @@ const GenericPressable = forwardRef((props, ref) => { }, [isScreenReaderActive, enableInScreenReaderStates, props.disabled]); const onLongPressHandler = useCallback(() => { + if (isDisabled) { + return; + } if (!onLongPress) { return; } if (shouldUseHapticsOnLongPress) { HapticFeedback.longPress(); } - if (ref.current) { + if (ref && ref.current) { ref.current.blur(); } onLongPress(); Accessibility.moveAccessibilityFocus(nextFocusRef); - }, [shouldUseHapticsOnLongPress, onLongPress, nextFocusRef, ref]); + }, [shouldUseHapticsOnLongPress, onLongPress, nextFocusRef, ref, isDisabled]); const onPressHandler = useCallback(() => { + if (isDisabled) { + return; + } if (shouldUseHapticsOnPress) { HapticFeedback.press(); } - if (ref.current) { + if (ref && ref.current) { ref.current.blur(); } onPress(); Accessibility.moveAccessibilityFocus(nextFocusRef); - }, [shouldUseHapticsOnPress, onPress, nextFocusRef, ref]); + }, [shouldUseHapticsOnPress, onPress, nextFocusRef, ref, isDisabled]); const onKeyPressHandler = useCallback((event) => { if (event.key !== 'Enter') { @@ -116,14 +122,14 @@ const GenericPressable = forwardRef((props, ref) => { hitSlop={shouldUseAutoHitSlop && hitSlop} onLayout={onLayout} ref={ref} - onPress={!isDisabled && onPressHandler} - onLongPress={!isDisabled && onLongPressHandler} - onKeyPress={!isDisabled && onKeyPressHandler} - onPressIn={!isDisabled && onPressIn} - onPressOut={!isDisabled && onPressOut} + onPress={!isDisabled ? onPressHandler : undefined} + onLongPress={!isDisabled ? onLongPressHandler : undefined} + onKeyPress={!isDisabled ? onKeyPressHandler : undefined} + onPressIn={!isDisabled ? onPressIn : undefined} + onPressOut={!isDisabled ? onPressOut : undefined} style={state => [ getCursorStyle(isDisabled, [props.accessibilityRole, props.role].includes('text')), - props.style, + StyleUtils.parseStyleFromFunction(props.style, state), isScreenReaderActive && StyleUtils.parseStyleFromFunction(props.screenReaderActiveStyle, state), state.focused && StyleUtils.parseStyleFromFunction(props.focusStyle, state), state.hovered && StyleUtils.parseStyleFromFunction(props.hoverStyle, state), diff --git a/src/components/Pressable/PressableWithFeedback.js b/src/components/Pressable/PressableWithFeedback.js index 66401fc1d974..af39c8f1c00e 100644 --- a/src/components/Pressable/PressableWithFeedback.js +++ b/src/components/Pressable/PressableWithFeedback.js @@ -1,6 +1,7 @@ -import React, {forwardRef} from 'react'; +import React, {forwardRef, useEffect, useState} from 'react'; import _ from 'underscore'; import propTypes from 'prop-types'; +import {InteractionManager} from 'react-native'; import GenericPressable from './GenericPressable'; import GenericPressablePropTypes from './GenericPressable/PropTypes'; import OpacityView from '../OpacityView'; @@ -24,9 +25,34 @@ const PressableWithFeedbackDefaultProps = { const PressableWithFeedback = forwardRef((props, ref) => { const propsWithoutStyling = _.omit(props, omittedProps); + const [disabled, setDisabled] = useState(props.disabled); + + useEffect(() => { + setDisabled(props.disabled); + }, [props.disabled]); + return ( - // eslint-disable-next-line react/jsx-props-no-spreading - + { + if (disabled) { return; } + setDisabled(true); + const onPress = props.onPress(e); + InteractionManager.runAfterInteractions(() => { + if (!(onPress instanceof Promise)) { + setDisabled(props.disabled); + return; + } + onPress.then(() => { + setDisabled(props.disabled); + }); + }); + }} + > {state => ( { if (transitionFromOldDot) { Navigation.dismissModal(); // Dismiss /transition route for OldDot to NewDot transitions diff --git a/src/pages/workspace/WorkspacesListPage.js b/src/pages/workspace/WorkspacesListPage.js index f1dbbbb7f1d3..c5b7a56a23e1 100755 --- a/src/pages/workspace/WorkspacesListPage.js +++ b/src/pages/workspace/WorkspacesListPage.js @@ -200,9 +200,10 @@ class WorkspacesListPage extends Component { )}