diff --git a/src/components/Button/index.js b/src/components/Button/index.js index 82850288ce42..5fe7dd1fe812 100644 --- a/src/components/Button/index.js +++ b/src/components/Button/index.js @@ -1,5 +1,6 @@ +import {useIsFocused} from '@react-navigation/native'; import PropTypes from 'prop-types'; -import React, {Component} from 'react'; +import React, {useCallback} from 'react'; import {ActivityIndicator, View} from 'react-native'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -7,10 +8,8 @@ import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import refPropTypes from '@components/refPropTypes'; import Text from '@components/Text'; import withNavigationFallback from '@components/withNavigationFallback'; -import withNavigationFocus from '@components/withNavigationFocus'; -import compose from '@libs/compose'; +import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import HapticFeedback from '@libs/HapticFeedback'; -import KeyboardShortcut from '@libs/KeyboardShortcut'; import styles from '@styles/styles'; import * as StyleUtils from '@styles/StyleUtils'; import themeColors from '@styles/themes/default'; @@ -112,9 +111,6 @@ const propTypes = { /** Should enable the haptic feedback? */ shouldEnableHapticFeedback: PropTypes.bool, - /** Whether Button is on active screen */ - isFocused: PropTypes.bool.isRequired, - /** Id to use for this button */ id: PropTypes.string, @@ -161,94 +157,116 @@ const defaultProps = { forwardedRef: undefined, }; -class Button extends Component { - constructor(props) { - super(props); - - this.renderContent = this.renderContent.bind(this); - } - - componentDidMount() { - if (!this.props.pressOnEnter) { - return; - } - - const shortcutConfig = CONST.KEYBOARD_SHORTCUTS.ENTER; - - // Setup and attach keypress handler for pressing the button with Enter key - this.unsubscribe = KeyboardShortcut.subscribe( - shortcutConfig.shortcutKey, - (e) => { - if (!validateSubmitShortcut(this.props.isFocused, this.props.isDisabled, this.props.isLoading, e)) { - return; - } - this.props.onPress(); - }, - shortcutConfig.descriptionKey, - shortcutConfig.modifiers, - true, - this.props.allowBubble, - this.props.enterKeyEventListenerPriority, - false, - ); - } - - componentWillUnmount() { - // Cleanup event listeners - if (!this.unsubscribe) { - return; - } - this.unsubscribe(); - } - - renderContent() { - if (this.props.children) { - return this.props.children; +function Button({ + allowBubble, + text, + shouldShowRightIcon, + + icon, + iconRight, + iconFill, + iconStyles, + iconRightStyles, + + small, + large, + medium, + + isLoading, + isDisabled, + + onPress, + onLongPress, + onPressIn, + onPressOut, + onMouseDown, + + pressOnEnter, + enterKeyEventListenerPriority, + + style, + innerStyles, + textStyles, + + shouldUseDefaultHover, + success, + danger, + children, + + shouldRemoveRightBorderRadius, + shouldRemoveLeftBorderRadius, + shouldEnableHapticFeedback, + + id, + accessibilityLabel, + forwardedRef, +}) { + const isFocused = useIsFocused(); + + const keyboardShortcutCallback = useCallback( + (event) => { + if (!validateSubmitShortcut(isFocused, isDisabled, isLoading, event)) { + return; + } + onPress(); + }, + [isDisabled, isFocused, isLoading, onPress], + ); + + useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ENTER, keyboardShortcutCallback, { + isActive: pressOnEnter, + shouldBubble: allowBubble, + priority: enterKeyEventListenerPriority, + shouldPreventDefault: false, + }); + + const renderContent = () => { + if (children) { + return children; } const textComponent = ( - {this.props.text} + {text} ); - if (this.props.icon || this.props.shouldShowRightIcon) { + if (icon || shouldShowRightIcon) { return ( - {this.props.icon && ( - + {icon && ( + )} {textComponent} - {this.props.shouldShowRightIcon && ( - + {shouldShowRightIcon && ( + )} @@ -257,87 +275,85 @@ class Button extends Component { } return textComponent; - } - - render() { - return ( - { - if (e && e.type === 'click') { - e.currentTarget.blur(); - } - - if (this.props.shouldEnableHapticFeedback) { - HapticFeedback.press(); - } - return this.props.onPress(e); - }} - onLongPress={(e) => { - if (this.props.shouldEnableHapticFeedback) { - HapticFeedback.longPress(); - } - this.props.onLongPress(e); - }} - onPressIn={this.props.onPressIn} - onPressOut={this.props.onPressOut} - onMouseDown={this.props.onMouseDown} - disabled={this.props.isLoading || this.props.isDisabled} - wrapperStyle={[ - this.props.isDisabled ? {...styles.cursorDisabled, ...styles.noSelect} : {}, - styles.buttonContainer, - this.props.shouldRemoveRightBorderRadius ? styles.noRightBorderRadius : undefined, - this.props.shouldRemoveLeftBorderRadius ? styles.noLeftBorderRadius : undefined, - ...StyleUtils.parseStyleAsArray(this.props.style), - ]} - style={[ - 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.isDisabled && (this.props.success || this.props.danger) ? styles.buttonOpacityDisabled : undefined, - this.props.isDisabled && !this.props.danger && !this.props.success ? styles.buttonDisabled : undefined, - this.props.shouldRemoveRightBorderRadius ? styles.noRightBorderRadius : undefined, - this.props.shouldRemoveLeftBorderRadius ? styles.noLeftBorderRadius : undefined, - this.props.icon || this.props.shouldShowRightIcon ? styles.alignItemsStretch : undefined, - ...this.props.innerStyles, - ]} - hoverStyle={[ - this.props.shouldUseDefaultHover && !this.props.isDisabled ? styles.buttonDefaultHovered : undefined, - this.props.success && !this.props.isDisabled ? styles.buttonSuccessHovered : undefined, - this.props.danger && !this.props.isDisabled ? styles.buttonDangerHovered : undefined, - ]} - id={this.props.id} - accessibilityLabel={this.props.accessibilityLabel} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} - hoverDimmingValue={1} - > - {this.renderContent()} - {this.props.isLoading && ( - - )} - - ); - } + }; + + return ( + { + if (event && event.type === 'click') { + event.currentTarget.blur(); + } + + if (shouldEnableHapticFeedback) { + HapticFeedback.press(); + } + return onPress(event); + }} + onLongPress={(event) => { + if (shouldEnableHapticFeedback) { + HapticFeedback.longPress(); + } + onLongPress(event); + }} + onPressIn={onPressIn} + onPressOut={onPressOut} + onMouseDown={onMouseDown} + disabled={isLoading || isDisabled} + wrapperStyle={[ + isDisabled ? {...styles.cursorDisabled, ...styles.noSelect} : {}, + styles.buttonContainer, + shouldRemoveRightBorderRadius ? styles.noRightBorderRadius : undefined, + shouldRemoveLeftBorderRadius ? styles.noLeftBorderRadius : undefined, + ...StyleUtils.parseStyleAsArray(style), + ]} + style={[ + styles.button, + small ? styles.buttonSmall : undefined, + medium ? styles.buttonMedium : undefined, + large ? styles.buttonLarge : undefined, + success ? styles.buttonSuccess : undefined, + danger ? styles.buttonDanger : undefined, + isDisabled && (success || danger) ? styles.buttonOpacityDisabled : undefined, + isDisabled && !danger && !success ? styles.buttonDisabled : undefined, + shouldRemoveRightBorderRadius ? styles.noRightBorderRadius : undefined, + shouldRemoveLeftBorderRadius ? styles.noLeftBorderRadius : undefined, + icon || shouldShowRightIcon ? styles.alignItemsStretch : undefined, + ...innerStyles, + ]} + hoverStyle={[ + shouldUseDefaultHover && !isDisabled ? styles.buttonDefaultHovered : undefined, + success && !isDisabled ? styles.buttonSuccessHovered : undefined, + danger && !isDisabled ? styles.buttonDangerHovered : undefined, + ]} + id={id} + accessibilityLabel={accessibilityLabel} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} + hoverDimmingValue={1} + > + {renderContent()} + {isLoading && ( + + )} + + ); } Button.propTypes = propTypes; Button.defaultProps = defaultProps; +Button.displayName = 'Button'; + +const ButtonWithRef = React.forwardRef((props, ref) => ( +