diff --git a/src/components/ContextMenuItem.js b/src/components/ContextMenuItem.js index 339e36afd6eb..639742efa00d 100644 --- a/src/components/ContextMenuItem.js +++ b/src/components/ContextMenuItem.js @@ -7,6 +7,7 @@ import Icon from './Icon'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; import getButtonState from '../libs/getButtonState'; +import withDelayToggleButtonState, {withDelayToggleButtonStatePropTypes} from './withDelayToggleButtonState'; const propTypes = { /** Icon Component */ @@ -32,6 +33,8 @@ const propTypes = { /** A description text to show under the title */ description: PropTypes.string, + + ...withDelayToggleButtonStatePropTypes, }; const defaultProps = { @@ -45,25 +48,15 @@ const defaultProps = { class ContextMenuItem extends Component { constructor(props) { super(props); - this.state = { - success: false, - }; - this.triggerPressAndUpdateSuccess = this.triggerPressAndUpdateSuccess.bind(this); - } - componentWillUnmount() { - if (!this.successResetTimer) { - return; - } - - clearTimeout(this.successResetTimer); + this.triggerPressAndUpdateSuccess = this.triggerPressAndUpdateSuccess.bind(this); } /** - * Called on button press and mark the run + * Method to call parent onPress and toggleDelayButtonState */ triggerPressAndUpdateSuccess() { - if (this.state.success) { + if (this.props.isDelayButtonStateComplete) { return; } this.props.onPress(); @@ -71,18 +64,13 @@ class ContextMenuItem extends Component { // We only set the success state when we have icon or text to represent the success state // We may want to replace this check by checking the Result from OnPress Callback in future. if (this.props.successIcon || this.props.successText) { - this.setState({ - success: true, - }); - if (this.props.autoReset) { - this.successResetTimer = setTimeout(() => this.setState({success: false}), 1800); - } + this.props.toggleDelayButtonState(this.props.autoReset); } } render() { - const icon = this.state.success ? this.props.successIcon || this.props.icon : this.props.icon; - const text = this.state.success ? this.props.successText || this.props.text : this.props.text; + const icon = this.props.isDelayButtonStateComplete ? this.props.successIcon || this.props.icon : this.props.icon; + const text = this.props.isDelayButtonStateComplete ? this.props.successText || this.props.text : this.props.text; return ( this.props.isMini ? ( @@ -94,14 +82,14 @@ class ContextMenuItem extends Component { style={ ({hovered, pressed}) => [ styles.reportActionContextMenuMiniButton, - StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed, this.state.success)), + StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed, this.props.isDelayButtonStateComplete)), ] } > {({hovered, pressed}) => ( )} @@ -112,7 +100,7 @@ class ContextMenuItem extends Component { icon={icon} onPress={this.triggerPressAndUpdateSuccess} wrapperStyle={styles.pr9} - success={this.state.success} + success={this.props.isDelayButtonStateComplete} description={this.props.description} /> ) @@ -123,4 +111,4 @@ class ContextMenuItem extends Component { ContextMenuItem.propTypes = propTypes; ContextMenuItem.defaultProps = defaultProps; -export default ContextMenuItem; +export default withDelayToggleButtonState(ContextMenuItem); diff --git a/src/components/HeaderWithCloseButton.js b/src/components/HeaderWithCloseButton.js index ef8588709c34..8b83e61ac279 100755 --- a/src/components/HeaderWithCloseButton.js +++ b/src/components/HeaderWithCloseButton.js @@ -1,7 +1,7 @@ -import React from 'react'; +import React, {Component} from 'react'; import PropTypes from 'prop-types'; import { - View, TouchableOpacity, Keyboard, + View, Keyboard, Pressable, } from 'react-native'; import styles from '../styles/styles'; import Header from './Header'; @@ -13,6 +13,10 @@ import withLocalize, {withLocalizePropTypes} from './withLocalize'; import Tooltip from './Tooltip'; import ThreeDotsMenu, {ThreeDotsMenuItemPropTypes} from './ThreeDotsMenu'; import VirtualKeyboard from '../libs/VirtualKeyboard'; +import getButtonState from '../libs/getButtonState'; +import * as StyleUtils from '../styles/StyleUtils'; +import withDelayToggleButtonState, {withDelayToggleButtonStatePropTypes} from './withDelayToggleButtonState'; +import compose from '../libs/compose'; const propTypes = { /** Title of the Header */ @@ -75,6 +79,8 @@ const propTypes = { }), ...withLocalizePropTypes, + + ...withDelayToggleButtonStatePropTypes, }; const defaultProps = { @@ -100,94 +106,123 @@ const defaultProps = { }, }; -const HeaderWithCloseButton = props => ( - - - {props.shouldShowBackButton && ( - - { - if (VirtualKeyboard.isOpen()) { - Keyboard.dismiss(); - } - props.onBackButtonPress(); - }} - style={[styles.touchableButtonImage]} - > - - - - )} -
- - { - props.shouldShowDownloadButton && ( - - - - - - - ) - } - - {props.shouldShowGetAssistanceButton - && ( - - Navigation.navigate(ROUTES.getGetAssistanceRoute(props.guidesCallTaskID))} - style={[styles.touchableButtonImage, styles.mr0]} - accessibilityRole="button" - accessibilityLabel={props.translate('getAssistancePage.questionMarkButtonTooltip')} - > - - - - )} - - {props.shouldShowThreeDotsButton && ( - + + {this.props.shouldShowBackButton && ( + + { + if (VirtualKeyboard.isOpen()) { + Keyboard.dismiss(); + } + this.props.onBackButtonPress(); + }} + style={[styles.touchableButtonImage]} + > + + + + )} +
- )} - - {props.shouldShowCloseButton - && ( - - - - - - )} + + { + this.props.shouldShowDownloadButton && ( + + + + + + + ) + } + + {this.props.shouldShowGetAssistanceButton + && ( + + Navigation.navigate(ROUTES.getGetAssistanceRoute(this.props.guidesCallTaskID))} + style={[styles.touchableButtonImage, styles.mr0]} + accessibilityRole="button" + accessibilityLabel={this.props.translate('getAssistancePage.questionMarkButtonTooltip')} + > + + + + )} + + {this.props.shouldShowThreeDotsButton && ( + + )} + + {this.props.shouldShowCloseButton + && ( + + + + + + )} + + - - -); + ); + } +} HeaderWithCloseButton.propTypes = propTypes; HeaderWithCloseButton.defaultProps = defaultProps; HeaderWithCloseButton.displayName = 'HeaderWithCloseButton'; -export default withLocalize(HeaderWithCloseButton); +export default compose( + withLocalize, + withDelayToggleButtonState, +)(HeaderWithCloseButton); diff --git a/src/components/withDelayToggleButtonState.js b/src/components/withDelayToggleButtonState.js new file mode 100644 index 000000000000..c312d18fba74 --- /dev/null +++ b/src/components/withDelayToggleButtonState.js @@ -0,0 +1,82 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import getComponentDisplayName from '../libs/getComponentDisplayName'; + +const withDelayToggleButtonStatePropTypes = { + /** A value whether the button state is complete */ + isDelayButtonStateComplete: PropTypes.bool.isRequired, + + /** A function to call to change the complete state */ + toggleDelayButtonState: PropTypes.func.isRequired, +}; + +export default function (WrappedComponent) { + class WithDelayToggleButtonState extends Component { + constructor(props) { + super(props); + + this.state = { + isDelayButtonStateComplete: false, + }; + this.toggleDelayButtonState = this.toggleDelayButtonState.bind(this); + } + + componentWillUnmount() { + if (!this.resetButtonStateCompleteTimer) { + return; + } + + clearTimeout(this.resetButtonStateCompleteTimer); + } + + /** + * @param {Boolean} resetAfterDelay Impose delay before toggling state + */ + toggleDelayButtonState(resetAfterDelay) { + this.setState({ + isDelayButtonStateComplete: true, + }); + + if (!resetAfterDelay) { + return; + } + + this.resetButtonStateCompleteTimer = setTimeout(() => { + this.setState({ + isDelayButtonStateComplete: false, + }); + }, 1800); + } + + render() { + return ( + + ); + } + } + + WithDelayToggleButtonState.displayName = `WithDelayToggleButtonState(${getComponentDisplayName(WrappedComponent)})`; + WithDelayToggleButtonState.propTypes = { + forwardedRef: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.shape({current: PropTypes.instanceOf(React.Component)}), + ]), + }; + WithDelayToggleButtonState.defaultProps = { + forwardedRef: undefined, + }; + + return React.forwardRef((props, ref) => ( + // eslint-disable-next-line react/jsx-props-no-spreading + + )); +} + +export { + withDelayToggleButtonStatePropTypes, +};