From 186d91f25eaea53f39759bf37f1369769ecd9987 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Tue, 11 Apr 2023 21:10:36 +0100 Subject: [PATCH 01/14] avoid nested Pressable and change the root MenuItem component, with some refactoring --- src/components/MenuItem.js | 15 ++++--- .../index.js | 4 +- ...ssableWithSecondaryInteractionPropTypes.js | 4 ++ src/components/menuItemPropTypes.js | 9 +++++ src/libs/showPopover.js | 19 +++++++++ src/pages/settings/AboutPage/AboutPage.js | 13 +++++- src/pages/settings/AppDownloadLinks.js | 40 +++++-------------- src/pages/settings/InitialSettingsPage.js | 13 ++++++ 8 files changed, 80 insertions(+), 37 deletions(-) create mode 100644 src/libs/showPopover.js diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index 8e1e089148db..ecf9c8bd2a51 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -1,8 +1,6 @@ import _ from 'underscore'; import React from 'react'; -import { - View, Pressable, -} from 'react-native'; +import {View} from 'react-native'; import Text from './Text'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; @@ -18,6 +16,7 @@ import colors from '../styles/colors'; import variables from '../styles/variables'; import MultipleAvatars from './MultipleAvatars'; import * as defaultWorkspaceAvatars from './Icon/WorkspaceDefaultAvatars'; +import PressableWithSecondaryInteraction from './PressableWithSecondaryInteraction'; const propTypes = { ...menuItemPropTypes, @@ -45,6 +44,9 @@ const defaultProps = { subtitle: undefined, iconType: CONST.ICON_TYPE_ICON, onPress: () => {}, + onPressIn: () => {}, + onPressOut: () => {}, + onSecondaryInteraction: () => {}, interactive: true, fallbackIcon: Expensicons.FallbackAvatar, brickRoadIndicator: '', @@ -70,7 +72,7 @@ const MenuItem = (props) => { ], props.style); return ( - { if (props.disabled) { return; @@ -82,6 +84,9 @@ const MenuItem = (props) => { props.onPress(e); }} + onPressIn={props.onPressIn} + onPressOut={props.onPressOut} + onSecondaryInteraction={props.onSecondaryInteraction} style={({hovered, pressed}) => ([ styles.popoverMenuItem, StyleUtils.getButtonBackgroundColorStyle(getButtonState(props.focused || hovered, pressed, props.success, props.disabled, props.interactive), true), @@ -210,7 +215,7 @@ const MenuItem = (props) => { )} - + ); }; diff --git a/src/components/PressableWithSecondaryInteraction/index.js b/src/components/PressableWithSecondaryInteraction/index.js index f3ea25a471fd..5ece13126413 100644 --- a/src/components/PressableWithSecondaryInteraction/index.js +++ b/src/components/PressableWithSecondaryInteraction/index.js @@ -4,6 +4,7 @@ import {Pressable} from 'react-native'; import * as pressableWithSecondaryInteractionPropTypes from './pressableWithSecondaryInteractionPropTypes'; import styles from '../../styles/styles'; import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; +import * as StyleUtils from '../../styles/StyleUtils'; /** * This is a special Pressable that calls onSecondaryInteraction when LongPressed, or right-clicked. @@ -50,11 +51,12 @@ class PressableWithSecondaryInteraction extends Component { render() { const defaultPressableProps = _.omit(this.props, ['onSecondaryInteraction', 'children', 'onLongPress']); + const pressableStyle = this.props.style && StyleUtils.combineStyles(this.props.style); // On Web, Text does not support LongPress events thus manage inline mode with styling instead of using Text. return ( { if (DeviceCapabilities.hasHoverSupport()) { diff --git a/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js b/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js index bffe11bc4cd8..ebbf8f5eab43 100644 --- a/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js +++ b/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js @@ -1,4 +1,5 @@ import PropTypes from 'prop-types'; +import stylePropTypes from '../../styles/stylePropTypes'; const propTypes = { /** The function that should be called when this pressable is pressed */ @@ -34,6 +35,9 @@ const propTypes = { /** Disable focus trap for the element on secondary interaction */ withoutFocusOnSecondaryInteraction: PropTypes.bool, + + /** Used to apply styles to the Pressable */ + style: stylePropTypes, }; const defaultProps = { diff --git a/src/components/menuItemPropTypes.js b/src/components/menuItemPropTypes.js index 9f3b40a55b43..67422b8871df 100644 --- a/src/components/menuItemPropTypes.js +++ b/src/components/menuItemPropTypes.js @@ -85,6 +85,15 @@ const propTypes = { /** Prop to identify if we should load avatars vertically instead of diagonally */ shouldStackHorizontally: PropTypes.bool, + + /** The function that should be called when this component is pressedIn */ + onPressIn: PropTypes.func, + + /** The function that should be called when this component is pressedOut */ + onPressOut: PropTypes.func, + + /** The function that should be called when this component is LongPressed or right-clicked. */ + onSecondaryInteraction: PropTypes.func, }; export default propTypes; diff --git a/src/libs/showPopover.js b/src/libs/showPopover.js new file mode 100644 index 000000000000..fde1a352f599 --- /dev/null +++ b/src/libs/showPopover.js @@ -0,0 +1,19 @@ +import * as ContextMenuActions from '../pages/home/report/ContextMenu/ContextMenuActions'; +import * as ReportActionContextMenu from '../pages/home/report/ContextMenu/ReportActionContextMenu'; + +/** + * Show the ReportActionContextMenu modal popover. + * + * @param {Object} [event] - A press event. + * @param {String} [selection] - Copied content. + */ +const showPopover = (event, selection, popoverAnchor) => { + ReportActionContextMenu.showContextMenu( + ContextMenuActions.CONTEXT_MENU_TYPES.LINK, + event, + selection, + popoverAnchor, + ); +}; + +export default showPopover; diff --git a/src/pages/settings/AboutPage/AboutPage.js b/src/pages/settings/AboutPage/AboutPage.js index 7c7840fdbbdb..035abab787a0 100644 --- a/src/pages/settings/AboutPage/AboutPage.js +++ b/src/pages/settings/AboutPage/AboutPage.js @@ -19,6 +19,9 @@ import * as Report from '../../../libs/actions/Report'; import * as Link from '../../../libs/actions/Link'; import getPlatformSpecificMenuItems from './getPlatformSpecificMenuItems'; import compose from '../../../libs/compose'; +import * as DeviceCapabilities from '../../../libs/DeviceCapabilities'; +import ControlSelection from '../../../libs/ControlSelection'; +import showPopover from '../../../libs/showPopover'; const propTypes = { ...withLocalizePropTypes, @@ -26,6 +29,8 @@ const propTypes = { }; const AboutPage = (props) => { + let popoverAnchor; + const platformSpecificMenuItems = getPlatformSpecificMenuItems(props.isSmallScreenWidth); const menuItems = [ @@ -44,6 +49,7 @@ const AboutPage = (props) => { action: () => { Link.openExternalLink(CONST.GITHUB_URL); }, + link: CONST.GITHUB_URL, }, { translationKey: 'initialSettingsPage.aboutPage.viewOpenJobs', @@ -52,13 +58,14 @@ const AboutPage = (props) => { action: () => { Link.openExternalLink(CONST.UPWORK_URL); }, + link: CONST.UPWORK_URL, }, { translationKey: 'initialSettingsPage.aboutPage.reportABug', icon: Expensicons.Bug, action: Report.navigateToConciergeChat, }, - ]; + ] return ( @@ -107,6 +114,10 @@ const AboutPage = (props) => { icon={item.icon} iconRight={item.iconRight} onPress={() => item.action()} + onPressIn={() => !_.isEmpty(item.link) && props.isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} + onPressOut={() => !_.isEmpty(item.link) && ControlSelection.unblock()} + onSecondaryInteraction={e => !_.isEmpty(item.link) && showPopover(e, item.link, popoverAnchor)} + ref={el => popoverAnchor = el} shouldShowRightIcon /> ))} diff --git a/src/pages/settings/AppDownloadLinks.js b/src/pages/settings/AppDownloadLinks.js index 9b47bdb644ef..6becbc70c421 100644 --- a/src/pages/settings/AppDownloadLinks.js +++ b/src/pages/settings/AppDownloadLinks.js @@ -11,12 +11,10 @@ import compose from '../../libs/compose'; import MenuItem from '../../components/MenuItem'; import styles from '../../styles/styles'; import * as Link from '../../libs/actions/Link'; -import PressableWithSecondaryInteraction from '../../components/PressableWithSecondaryInteraction'; import ControlSelection from '../../libs/ControlSelection'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions'; import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; -import * as ReportActionContextMenu from '../home/report/ContextMenu/ReportActionContextMenu'; -import * as ContextMenuActions from '../home/report/ContextMenu/ContextMenuActions'; +import showPopover from '../../libs/showPopover'; const propTypes = { ...withLocalizePropTypes, @@ -56,21 +54,6 @@ const AppDownloadLinksPage = (props) => { }, ]; - /** - * Show the ReportActionContextMenu modal popover. - * - * @param {Object} [event] - A press event. - * @param {String} [selection] - Copied content. - */ - const showPopover = (event, selection) => { - ReportActionContextMenu.showContextMenu( - ContextMenuActions.CONTEXT_MENU_TYPES.LINK, - event, - selection, - popoverAnchor, - ); - }; - return ( { /> {_.map(menuItems, item => ( - item.action()} onPressIn={() => props.isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} onPressOut={() => ControlSelection.unblock()} - onSecondaryInteraction={e => showPopover(e, item.link)} - ref={el => popoverAnchor = el} + onSecondaryInteraction={e => showPopover(e, item.link, popoverAnchor)} onKeyDown={(event) => { event.target.blur(); }} - > - item.action()} - shouldShowRightIcon - /> - + ref={el => popoverAnchor = el} + title={props.translate(item.translationKey)} + icon={item.icon} + iconRight={item.iconRight} + shouldShowRightIcon + /> ))} diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js index 0593c10e4fb6..bfee31b2efba 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -36,6 +36,10 @@ import * as Link from '../../libs/actions/Link'; import OfflineWithFeedback from '../../components/OfflineWithFeedback'; import * as UserUtils from '../../libs/UserUtils'; import policyMemberPropType from '../policyMemberPropType'; +import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions'; +import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; +import ControlSelection from '../../libs/ControlSelection'; +import showPopover from '../../libs/showPopover'; const propTypes = { /* Onyx Props */ @@ -96,6 +100,7 @@ const propTypes = { ...withLocalizePropTypes, ...withCurrentUserPersonalDetailsPropTypes, + ...windowDimensionsPropTypes, }; const defaultProps = { @@ -117,6 +122,8 @@ class InitialSettingsPage extends React.Component { constructor(props) { super(props); + this.popoverAnchor = React.createRef(); + this.getWalletBalance = this.getWalletBalance.bind(this); this.getDefaultMenuItems = this.getDefaultMenuItems.bind(this); this.getMenuItem = this.getMenuItem.bind(this); @@ -203,6 +210,7 @@ class InitialSettingsPage extends React.Component { action: () => { Link.openExternalLink(CONST.NEWHELP_URL); }, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, + link: CONST.NEWHELP_URL, }, { translationKey: 'initialSettingsPage.about', @@ -236,6 +244,10 @@ class InitialSettingsPage extends React.Component { brickRoadIndicator={item.brickRoadIndicator} floatRightAvatars={item.floatRightAvatars} shouldStackHorizontally={item.shouldStackHorizontally} + ref={this.popoverAnchor} + onPressIn={() => !_.isEmpty(item.link) && this.props.isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} + onPressOut={() => !_.isEmpty(item.link) && ControlSelection.unblock()} + onSecondaryInteraction={e => !_.isEmpty(item.link) && showPopover(e, item.link, this.popoverAnchor.current)} /> ); } @@ -334,6 +346,7 @@ InitialSettingsPage.propTypes = propTypes; InitialSettingsPage.defaultProps = defaultProps; export default compose( + withWindowDimensions, withLocalize, withCurrentUserPersonalDetails, withOnyx({ From 38933ddae677673ab113fab7941aedbdd96aeb4a Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Tue, 11 Apr 2023 22:01:08 +0100 Subject: [PATCH 02/14] use React.forwardRef for MenuItem, and fix prop types --- src/components/MenuItem.js | 5 +++-- .../pressableWithSecondaryInteractionPropTypes.js | 10 ++++++++-- src/libs/showPopover.js | 1 + src/pages/settings/AboutPage/AboutPage.js | 2 +- src/pages/settings/InitialSettingsPage.js | 2 +- src/styles/stylePropTypes.js | 1 + 6 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index ecf9c8bd2a51..fbbbd54aa2d4 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -54,7 +54,7 @@ const defaultProps = { shouldStackHorizontally: false, }; -const MenuItem = (props) => { +const MenuItem = React.forwardRef((props, ref) => { const titleTextStyle = StyleUtils.combineStyles([ styles.popoverMenuText, (props.icon ? styles.ml3 : undefined), @@ -93,6 +93,7 @@ const MenuItem = (props) => { ..._.isArray(props.wrapperStyle) ? props.wrapperStyle : [props.wrapperStyle], ])} disabled={props.disabled} + ref={ref} > {({hovered, pressed}) => ( <> @@ -217,7 +218,7 @@ const MenuItem = (props) => { )} ); -}; +}); MenuItem.propTypes = propTypes; MenuItem.defaultProps = defaultProps; diff --git a/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js b/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js index ebbf8f5eab43..d0f59efb8b84 100644 --- a/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js +++ b/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js @@ -15,10 +15,16 @@ const propTypes = { onSecondaryInteraction: PropTypes.func.isRequired, /** The children which should be contained in this wrapper component. */ - children: PropTypes.node.isRequired, + children: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.node, + ]).isRequired, /** The ref to the search input (may be null on small screen widths) */ - forwardedRef: PropTypes.func, + forwardedRef: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.object, + ]), /** Prevent the default ContextMenu on web/Desktop */ preventDefaultContentMenu: PropTypes.bool, diff --git a/src/libs/showPopover.js b/src/libs/showPopover.js index fde1a352f599..da5fd8f50c98 100644 --- a/src/libs/showPopover.js +++ b/src/libs/showPopover.js @@ -6,6 +6,7 @@ import * as ReportActionContextMenu from '../pages/home/report/ContextMenu/Repor * * @param {Object} [event] - A press event. * @param {String} [selection] - Copied content. + * @param {Object} [popoverAnchor] - The popover anchor. */ const showPopover = (event, selection, popoverAnchor) => { ReportActionContextMenu.showContextMenu( diff --git a/src/pages/settings/AboutPage/AboutPage.js b/src/pages/settings/AboutPage/AboutPage.js index 035abab787a0..42caac05dd1c 100644 --- a/src/pages/settings/AboutPage/AboutPage.js +++ b/src/pages/settings/AboutPage/AboutPage.js @@ -65,7 +65,7 @@ const AboutPage = (props) => { icon: Expensicons.Bug, action: Report.navigateToConciergeChat, }, - ] + ]; return ( diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js index bfee31b2efba..3af1edfbd68c 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -123,7 +123,7 @@ class InitialSettingsPage extends React.Component { super(props); this.popoverAnchor = React.createRef(); - + this.getWalletBalance = this.getWalletBalance.bind(this); this.getDefaultMenuItems = this.getDefaultMenuItems.bind(this); this.getMenuItem = this.getMenuItem.bind(this); diff --git a/src/styles/stylePropTypes.js b/src/styles/stylePropTypes.js index 4c7e825a8848..edc5d0383a75 100644 --- a/src/styles/stylePropTypes.js +++ b/src/styles/stylePropTypes.js @@ -3,4 +3,5 @@ import PropTypes from 'prop-types'; export default PropTypes.oneOfType([ PropTypes.object, PropTypes.arrayOf(PropTypes.object), + PropTypes.func, ]); From e03eafe2d6bf56e586d1dcab507aa4eff66dda29 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Wed, 12 Apr 2023 12:16:58 +0100 Subject: [PATCH 03/14] changes based on PR review feedback --- src/components/MenuItem.js | 23 +++++++++------- .../index.js | 3 +-- src/components/menuItemPropTypes.js | 15 ++++++----- src/libs/showPopover.js | 20 -------------- .../ContextMenu/ReportActionContextMenu.js | 27 +++++++++++++++++++ src/pages/settings/AboutPage/AboutPage.js | 9 +++---- src/pages/settings/AppDownloadLinks.js | 7 +++-- src/pages/settings/InitialSettingsPage.js | 12 +++------ 8 files changed, 60 insertions(+), 56 deletions(-) delete mode 100644 src/libs/showPopover.js diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index fbbbd54aa2d4..c146ae31e085 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -17,9 +17,13 @@ import variables from '../styles/variables'; import MultipleAvatars from './MultipleAvatars'; import * as defaultWorkspaceAvatars from './Icon/WorkspaceDefaultAvatars'; import PressableWithSecondaryInteraction from './PressableWithSecondaryInteraction'; +import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; +import * as DeviceCapabilities from '../libs/DeviceCapabilities'; +import ControlSelection from '../libs/ControlSelection'; const propTypes = { ...menuItemPropTypes, + ...windowDimensionsPropTypes, }; const defaultProps = { @@ -44,17 +48,16 @@ const defaultProps = { subtitle: undefined, iconType: CONST.ICON_TYPE_ICON, onPress: () => {}, - onPressIn: () => {}, - onPressOut: () => {}, onSecondaryInteraction: () => {}, interactive: true, fallbackIcon: Expensicons.FallbackAvatar, brickRoadIndicator: '', floatRightAvatars: [], shouldStackHorizontally: false, + shouldBlockSelection: false, }; -const MenuItem = React.forwardRef((props, ref) => { +const MenuItem = (props) => { const titleTextStyle = StyleUtils.combineStyles([ styles.popoverMenuText, (props.icon ? styles.ml3 : undefined), @@ -84,8 +87,8 @@ const MenuItem = React.forwardRef((props, ref) => { props.onPress(e); }} - onPressIn={props.onPressIn} - onPressOut={props.onPressOut} + onPressIn={() => props.shouldBlockSelection && props.isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} + onPressOut={() => ControlSelection.unblock()} onSecondaryInteraction={props.onSecondaryInteraction} style={({hovered, pressed}) => ([ styles.popoverMenuItem, @@ -93,7 +96,7 @@ const MenuItem = React.forwardRef((props, ref) => { ..._.isArray(props.wrapperStyle) ? props.wrapperStyle : [props.wrapperStyle], ])} disabled={props.disabled} - ref={ref} + ref={props.forwardedRef} > {({hovered, pressed}) => ( <> @@ -218,10 +221,12 @@ const MenuItem = React.forwardRef((props, ref) => { )} ); -}); +}; MenuItem.propTypes = propTypes; MenuItem.defaultProps = defaultProps; MenuItem.displayName = 'MenuItem'; - -export default MenuItem; +export default withWindowDimensions(React.forwardRef((props, ref) => ( + // eslint-disable-next-line react/jsx-props-no-spreading + +))); diff --git a/src/components/PressableWithSecondaryInteraction/index.js b/src/components/PressableWithSecondaryInteraction/index.js index 5ece13126413..d9a3d55ad841 100644 --- a/src/components/PressableWithSecondaryInteraction/index.js +++ b/src/components/PressableWithSecondaryInteraction/index.js @@ -51,12 +51,11 @@ class PressableWithSecondaryInteraction extends Component { render() { const defaultPressableProps = _.omit(this.props, ['onSecondaryInteraction', 'children', 'onLongPress']); - const pressableStyle = this.props.style && StyleUtils.combineStyles(this.props.style); // On Web, Text does not support LongPress events thus manage inline mode with styling instead of using Text. return ( { if (DeviceCapabilities.hasHoverSupport()) { diff --git a/src/components/menuItemPropTypes.js b/src/components/menuItemPropTypes.js index 67422b8871df..425ab02eda96 100644 --- a/src/components/menuItemPropTypes.js +++ b/src/components/menuItemPropTypes.js @@ -86,14 +86,17 @@ const propTypes = { /** Prop to identify if we should load avatars vertically instead of diagonally */ shouldStackHorizontally: PropTypes.bool, - /** The function that should be called when this component is pressedIn */ - onPressIn: PropTypes.func, - - /** The function that should be called when this component is pressedOut */ - onPressOut: PropTypes.func, - /** The function that should be called when this component is LongPressed or right-clicked. */ onSecondaryInteraction: PropTypes.func, + + /** Flag to indicate whether or not text selection should be disabled from long-pressing the menu item. */ + shouldBlockSelection: PropTypes.bool, + + /** The ref to the menu item */ + forwardedRef: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.object, + ]), }; export default propTypes; diff --git a/src/libs/showPopover.js b/src/libs/showPopover.js deleted file mode 100644 index da5fd8f50c98..000000000000 --- a/src/libs/showPopover.js +++ /dev/null @@ -1,20 +0,0 @@ -import * as ContextMenuActions from '../pages/home/report/ContextMenu/ContextMenuActions'; -import * as ReportActionContextMenu from '../pages/home/report/ContextMenu/ReportActionContextMenu'; - -/** - * Show the ReportActionContextMenu modal popover. - * - * @param {Object} [event] - A press event. - * @param {String} [selection] - Copied content. - * @param {Object} [popoverAnchor] - The popover anchor. - */ -const showPopover = (event, selection, popoverAnchor) => { - ReportActionContextMenu.showContextMenu( - ContextMenuActions.CONTEXT_MENU_TYPES.LINK, - event, - selection, - popoverAnchor, - ); -}; - -export default showPopover; diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js index df544f2e7202..1870bd0ee954 100644 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js @@ -1,5 +1,11 @@ +import _ from 'underscore'; import React from 'react'; +/** + * LINK type which is used in the showPopover method. + */ +const CONTEXT_MENU_TYPE_LINK = 'LINK'; + const contextMenuRef = React.createRef(); /** @@ -112,6 +118,26 @@ function isActiveReportAction(actionID) { return contextMenuRef.current.isActiveReportAction(actionID); } +/** + * Show the ReportActionContextMenu modal popover. + * + * @param {Object} [event] - A press event. + * @param {String} [selection] - Copied content. + * @param {Object} [popoverAnchor] - The popover anchor. + */ +function showPopover(event, selection, popoverAnchor) { + if (_.isEmpty(selection)) { + return; + } + + showContextMenu( + CONTEXT_MENU_TYPE_LINK, + event, + selection, + popoverAnchor, + ); +} + export { contextMenuRef, showContextMenu, @@ -119,4 +145,5 @@ export { isActiveReportAction, showDeleteModal, hideDeleteModal, + showPopover, }; diff --git a/src/pages/settings/AboutPage/AboutPage.js b/src/pages/settings/AboutPage/AboutPage.js index 42caac05dd1c..303ed949af6d 100644 --- a/src/pages/settings/AboutPage/AboutPage.js +++ b/src/pages/settings/AboutPage/AboutPage.js @@ -19,9 +19,7 @@ import * as Report from '../../../libs/actions/Report'; import * as Link from '../../../libs/actions/Link'; import getPlatformSpecificMenuItems from './getPlatformSpecificMenuItems'; import compose from '../../../libs/compose'; -import * as DeviceCapabilities from '../../../libs/DeviceCapabilities'; -import ControlSelection from '../../../libs/ControlSelection'; -import showPopover from '../../../libs/showPopover'; +import * as ReportActionContextMenu from '../../home/report/ContextMenu/ReportActionContextMenu'; const propTypes = { ...withLocalizePropTypes, @@ -114,9 +112,8 @@ const AboutPage = (props) => { icon={item.icon} iconRight={item.iconRight} onPress={() => item.action()} - onPressIn={() => !_.isEmpty(item.link) && props.isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} - onPressOut={() => !_.isEmpty(item.link) && ControlSelection.unblock()} - onSecondaryInteraction={e => !_.isEmpty(item.link) && showPopover(e, item.link, popoverAnchor)} + shouldBlockSelection={!_.isEmpty(item.link)} + onSecondaryInteraction={e => ReportActionContextMenu.showPopover(e, item.link, popoverAnchor)} ref={el => popoverAnchor = el} shouldShowRightIcon /> diff --git a/src/pages/settings/AppDownloadLinks.js b/src/pages/settings/AppDownloadLinks.js index 6becbc70c421..e8421d821d83 100644 --- a/src/pages/settings/AppDownloadLinks.js +++ b/src/pages/settings/AppDownloadLinks.js @@ -13,8 +13,7 @@ import styles from '../../styles/styles'; import * as Link from '../../libs/actions/Link'; import ControlSelection from '../../libs/ControlSelection'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions'; -import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; -import showPopover from '../../libs/showPopover'; +import * as ReportActionContextMenu from '../home/report/ContextMenu/ReportActionContextMenu'; const propTypes = { ...withLocalizePropTypes, @@ -67,9 +66,8 @@ const AppDownloadLinksPage = (props) => { item.action()} - onPressIn={() => props.isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} onPressOut={() => ControlSelection.unblock()} - onSecondaryInteraction={e => showPopover(e, item.link, popoverAnchor)} + onSecondaryInteraction={e => ReportActionContextMenu.showPopover(e, item.link, popoverAnchor)} onKeyDown={(event) => { event.target.blur(); }} @@ -77,6 +75,7 @@ const AppDownloadLinksPage = (props) => { title={props.translate(item.translationKey)} icon={item.icon} iconRight={item.iconRight} + shouldBlockSelection shouldShowRightIcon /> ))} diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js index 3af1edfbd68c..0b8114fa7228 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -36,10 +36,7 @@ import * as Link from '../../libs/actions/Link'; import OfflineWithFeedback from '../../components/OfflineWithFeedback'; import * as UserUtils from '../../libs/UserUtils'; import policyMemberPropType from '../policyMemberPropType'; -import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions'; -import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; -import ControlSelection from '../../libs/ControlSelection'; -import showPopover from '../../libs/showPopover'; +import * as ReportActionContextMenu from '../home/report/ContextMenu/ReportActionContextMenu'; const propTypes = { /* Onyx Props */ @@ -100,7 +97,6 @@ const propTypes = { ...withLocalizePropTypes, ...withCurrentUserPersonalDetailsPropTypes, - ...windowDimensionsPropTypes, }; const defaultProps = { @@ -245,9 +241,8 @@ class InitialSettingsPage extends React.Component { floatRightAvatars={item.floatRightAvatars} shouldStackHorizontally={item.shouldStackHorizontally} ref={this.popoverAnchor} - onPressIn={() => !_.isEmpty(item.link) && this.props.isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} - onPressOut={() => !_.isEmpty(item.link) && ControlSelection.unblock()} - onSecondaryInteraction={e => !_.isEmpty(item.link) && showPopover(e, item.link, this.popoverAnchor.current)} + shouldBlockSelection={!_.isEmpty(item.link)} + onSecondaryInteraction={e => ReportActionContextMenu.showPopover(e, item.link, this.popoverAnchor.current)} /> ); } @@ -346,7 +341,6 @@ InitialSettingsPage.propTypes = propTypes; InitialSettingsPage.defaultProps = defaultProps; export default compose( - withWindowDimensions, withLocalize, withCurrentUserPersonalDetails, withOnyx({ From c1ec51724eee9527069280a4e087dcea808b2e78 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Wed, 12 Apr 2023 12:22:20 +0100 Subject: [PATCH 04/14] clean up onPressOut in MenuItem component --- src/components/MenuItem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index c146ae31e085..775134a34ace 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -88,7 +88,7 @@ const MenuItem = (props) => { props.onPress(e); }} onPressIn={() => props.shouldBlockSelection && props.isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} - onPressOut={() => ControlSelection.unblock()} + onPressOut={ControlSelection.unblock} onSecondaryInteraction={props.onSecondaryInteraction} style={({hovered, pressed}) => ([ styles.popoverMenuItem, From 7b4f4557da1b79fc4d3362986893b063669561e1 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Wed, 12 Apr 2023 19:28:49 +0100 Subject: [PATCH 05/14] implement Copy to Clipboard behaviour for all links --- src/components/MenuItemList.js | 45 +++++++++++++----- src/libs/actions/Link.js | 47 ++++++++++--------- src/pages/GetAssistancePage.js | 1 + .../ContextMenu/ReportActionContextMenu.js | 27 ----------- src/pages/settings/AboutPage/AboutPage.js | 3 +- src/pages/settings/AppDownloadLinks.js | 5 +- src/pages/settings/InitialSettingsPage.js | 3 +- .../travel/WorkspaceTravelVBAView.js | 6 ++- 8 files changed, 71 insertions(+), 66 deletions(-) diff --git a/src/components/MenuItemList.js b/src/components/MenuItemList.js index e442d3766893..7a6380fe8f13 100644 --- a/src/components/MenuItemList.js +++ b/src/components/MenuItemList.js @@ -3,6 +3,8 @@ import _ from 'underscore'; import PropTypes from 'prop-types'; import MenuItem from './MenuItem'; import menuItemPropTypes from './menuItemPropTypes'; +import * as ReportActionContextMenu from '../pages/home/report/ContextMenu/ReportActionContextMenu'; +import {CONTEXT_MENU_TYPES} from '../pages/home/report/ContextMenu/ContextMenuActions'; const propTypes = { /** An array of props that are pass to individual MenuItem components */ @@ -12,17 +14,38 @@ const defaultProps = { menuItems: [], }; -const MenuItemList = props => ( - <> - {_.map(props.menuItems, menuItemProps => ( - - ))} - -); +const MenuItemList = (props) => { + let popoverAnchor; + + /** + * Handle the secondary interaction for a menu item. + * + * @param {any} link the menu item link object or function + * @param {Event} e the interaction event + */ + const secondaryInteraction = (link, e) => { + if (typeof link === 'function') { + link().then(url => ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, url, popoverAnchor)); + } else if (!_.isEmpty(link)) { + ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, link, popoverAnchor); + } + }; + + return ( + <> + {_.map(props.menuItems, menuItemProps => ( + secondaryInteraction(menuItemProps.link, e)} + ref={el => popoverAnchor = el} + shouldBlockSelection={typeof menuItemProps.link === 'function' || !_.isEmpty(menuItemProps.link)} + // eslint-disable-next-line react/jsx-props-no-spreading + {...menuItemProps} + /> + ))} + + ); +}; MenuItemList.displayName = 'MenuItemList'; MenuItemList.propTypes = propTypes; diff --git a/src/libs/actions/Link.js b/src/libs/actions/Link.js index c38f901bb391..2cccfc447f6b 100644 --- a/src/libs/actions/Link.js +++ b/src/libs/actions/Link.js @@ -34,33 +34,35 @@ function showGrowlIfOffline() { } /** - * @param {String} url + * @param {String} [url] the url path + * @param {String} [shortLivedAuthToken] + * + * @returns {Promise} */ -function openOldDotLink(url) { - /** - * @param {String} [shortLivedAuthToken] - * @returns {Promise} - */ - function buildOldDotURL(shortLivedAuthToken) { - const hasHashParams = url.indexOf('#') !== -1; - const hasURLParams = url.indexOf('?') !== -1; +function buildOldDotURL(url, shortLivedAuthToken) { + const hasHashParams = url.indexOf('#') !== -1; + const hasURLParams = url.indexOf('?') !== -1; - const authTokenParam = shortLivedAuthToken ? `authToken=${shortLivedAuthToken}` : ''; - const emailParam = `email=${encodeURIComponent(currentUserEmail)}`; + const authTokenParam = shortLivedAuthToken ? `authToken=${shortLivedAuthToken}` : ''; + const emailParam = `email=${encodeURIComponent(currentUserEmail)}`; - const params = _.compact([authTokenParam, emailParam]).join('&'); + const params = _.compact([authTokenParam, emailParam]).join('&'); - return Environment.getOldDotEnvironmentURL() - .then((environmentURL) => { - const oldDotDomain = Url.addTrailingForwardSlash(environmentURL); + return Environment.getOldDotEnvironmentURL() + .then((environmentURL) => { + const oldDotDomain = Url.addTrailingForwardSlash(environmentURL); - // If the URL contains # or ?, we can assume they don't need to have the `?` token to start listing url parameters. - return `${oldDotDomain}${url}${hasHashParams || hasURLParams ? '&' : '?'}${params}`; - }); - } + // If the URL contains # or ?, we can assume they don't need to have the `?` token to start listing url parameters. + return `${oldDotDomain}${url}${hasHashParams || hasURLParams ? '&' : '?'}${params}`; + }); +} +/** + * @param {String} url the url path + */ +function openOldDotLink(url) { if (isNetworkOffline) { - buildOldDotURL().then(oldDotURL => Linking.openURL(oldDotURL)); + buildOldDotURL(url).then(oldDotURL => Linking.openURL(oldDotURL)); return; } @@ -69,11 +71,11 @@ function openOldDotLink(url) { API.makeRequestWithSideEffects( 'OpenOldDotLink', {}, {}, ).then((response) => { - buildOldDotURL(response.shortLivedAuthToken).then((oldDotUrl) => { + buildOldDotURL(url, response.shortLivedAuthToken).then((oldDotUrl) => { Linking.openURL(oldDotUrl); }); }).catch(() => { - buildOldDotURL().then((oldDotUrl) => { + buildOldDotURL(url).then((oldDotUrl) => { Linking.openURL(oldDotUrl); }); }); @@ -92,6 +94,7 @@ function openExternalLink(url, shouldSkipCustomSafariLogic = false) { } export { + buildOldDotURL, openOldDotLink, openExternalLink, }; diff --git a/src/pages/GetAssistancePage.js b/src/pages/GetAssistancePage.js index e2115c6d7814..89544b25dcfe 100644 --- a/src/pages/GetAssistancePage.js +++ b/src/pages/GetAssistancePage.js @@ -57,6 +57,7 @@ const GetAssistancePage = (props) => { shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], + link: CONST.NEWHELP_URL, }]; // If the user is eligible for calls with their Guide, add the 'Schedule a setup call' item at the second position in the list diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js index 1870bd0ee954..df544f2e7202 100644 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js @@ -1,11 +1,5 @@ -import _ from 'underscore'; import React from 'react'; -/** - * LINK type which is used in the showPopover method. - */ -const CONTEXT_MENU_TYPE_LINK = 'LINK'; - const contextMenuRef = React.createRef(); /** @@ -118,26 +112,6 @@ function isActiveReportAction(actionID) { return contextMenuRef.current.isActiveReportAction(actionID); } -/** - * Show the ReportActionContextMenu modal popover. - * - * @param {Object} [event] - A press event. - * @param {String} [selection] - Copied content. - * @param {Object} [popoverAnchor] - The popover anchor. - */ -function showPopover(event, selection, popoverAnchor) { - if (_.isEmpty(selection)) { - return; - } - - showContextMenu( - CONTEXT_MENU_TYPE_LINK, - event, - selection, - popoverAnchor, - ); -} - export { contextMenuRef, showContextMenu, @@ -145,5 +119,4 @@ export { isActiveReportAction, showDeleteModal, hideDeleteModal, - showPopover, }; diff --git a/src/pages/settings/AboutPage/AboutPage.js b/src/pages/settings/AboutPage/AboutPage.js index 303ed949af6d..996342095ced 100644 --- a/src/pages/settings/AboutPage/AboutPage.js +++ b/src/pages/settings/AboutPage/AboutPage.js @@ -20,6 +20,7 @@ import * as Link from '../../../libs/actions/Link'; import getPlatformSpecificMenuItems from './getPlatformSpecificMenuItems'; import compose from '../../../libs/compose'; import * as ReportActionContextMenu from '../../home/report/ContextMenu/ReportActionContextMenu'; +import {CONTEXT_MENU_TYPES} from '../../home/report/ContextMenu/ContextMenuActions'; const propTypes = { ...withLocalizePropTypes, @@ -113,7 +114,7 @@ const AboutPage = (props) => { iconRight={item.iconRight} onPress={() => item.action()} shouldBlockSelection={!_.isEmpty(item.link)} - onSecondaryInteraction={e => ReportActionContextMenu.showPopover(e, item.link, popoverAnchor)} + onSecondaryInteraction={e => !_.isEmpty(item.link) && ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, popoverAnchor)} ref={el => popoverAnchor = el} shouldShowRightIcon /> diff --git a/src/pages/settings/AppDownloadLinks.js b/src/pages/settings/AppDownloadLinks.js index e8421d821d83..8acd2e95dafb 100644 --- a/src/pages/settings/AppDownloadLinks.js +++ b/src/pages/settings/AppDownloadLinks.js @@ -11,9 +11,9 @@ import compose from '../../libs/compose'; import MenuItem from '../../components/MenuItem'; import styles from '../../styles/styles'; import * as Link from '../../libs/actions/Link'; -import ControlSelection from '../../libs/ControlSelection'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions'; import * as ReportActionContextMenu from '../home/report/ContextMenu/ReportActionContextMenu'; +import {CONTEXT_MENU_TYPES} from '../home/report/ContextMenu/ContextMenuActions'; const propTypes = { ...withLocalizePropTypes, @@ -66,8 +66,7 @@ const AppDownloadLinksPage = (props) => { item.action()} - onPressOut={() => ControlSelection.unblock()} - onSecondaryInteraction={e => ReportActionContextMenu.showPopover(e, item.link, popoverAnchor)} + onSecondaryInteraction={e => ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, popoverAnchor)} onKeyDown={(event) => { event.target.blur(); }} diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js index 0b8114fa7228..82fa2259e546 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -37,6 +37,7 @@ import OfflineWithFeedback from '../../components/OfflineWithFeedback'; import * as UserUtils from '../../libs/UserUtils'; import policyMemberPropType from '../policyMemberPropType'; import * as ReportActionContextMenu from '../home/report/ContextMenu/ReportActionContextMenu'; +import {CONTEXT_MENU_TYPES} from '../home/report/ContextMenu/ContextMenuActions'; const propTypes = { /* Onyx Props */ @@ -242,7 +243,7 @@ class InitialSettingsPage extends React.Component { shouldStackHorizontally={item.shouldStackHorizontally} ref={this.popoverAnchor} shouldBlockSelection={!_.isEmpty(item.link)} - onSecondaryInteraction={e => ReportActionContextMenu.showPopover(e, item.link, this.popoverAnchor.current)} + onSecondaryInteraction={e => !_.isEmpty(item.link) && ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, this.popoverAnchor.current)} /> ); } diff --git a/src/pages/workspace/travel/WorkspaceTravelVBAView.js b/src/pages/workspace/travel/WorkspaceTravelVBAView.js index 9c79b55c90b4..b9d59805d1cf 100644 --- a/src/pages/workspace/travel/WorkspaceTravelVBAView.js +++ b/src/pages/workspace/travel/WorkspaceTravelVBAView.js @@ -13,6 +13,8 @@ const propTypes = { ...withLocalizePropTypes, }; +const CONCIERGE_TRAVEL_URL = 'https://community.expensify.com/discussion/7066/introducing-concierge-travel'; + const WorkspaceTravelVBAView = props => (
( shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL('domain_companycards'), }, { title: props.translate('workspace.travel.bookTravelWithConcierge'), @@ -37,11 +40,12 @@ const WorkspaceTravelVBAView = props => ( }, { title: props.translate('requestorStep.learnMore'), - onPress: () => Link.openExternalLink('https://community.expensify.com/discussion/7066/introducing-concierge-travel'), + onPress: () => Link.openExternalLink(CONCIERGE_TRAVEL_URL), icon: Expensicons.Info, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], + link: CONCIERGE_TRAVEL_URL, }, ]} > From 1905f519edfe141ca08b4290348d69a265e6eafc Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Wed, 12 Apr 2023 19:43:08 +0100 Subject: [PATCH 06/14] cleanup --- src/CONST.js | 2 ++ src/components/MenuItemList.js | 2 +- src/pages/workspace/travel/WorkspaceTravelVBAView.js | 7 +++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index f0ccd47b3a8f..21ceaa8ef866 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -2200,6 +2200,8 @@ const CONST = { PATHS_TO_TREAT_AS_EXTERNAL: [ 'NewExpensify.dmg', ], + + CONCIERGE_TRAVEL_URL: 'https://community.expensify.com/discussion/7066/introducing-concierge-travel', }; export default CONST; diff --git a/src/components/MenuItemList.js b/src/components/MenuItemList.js index 7a6380fe8f13..65b1357fff27 100644 --- a/src/components/MenuItemList.js +++ b/src/components/MenuItemList.js @@ -20,7 +20,7 @@ const MenuItemList = (props) => { /** * Handle the secondary interaction for a menu item. * - * @param {any} link the menu item link object or function + * @param {*} link the menu item link or function to get the link * @param {Event} e the interaction event */ const secondaryInteraction = (link, e) => { diff --git a/src/pages/workspace/travel/WorkspaceTravelVBAView.js b/src/pages/workspace/travel/WorkspaceTravelVBAView.js index b9d59805d1cf..79b256136028 100644 --- a/src/pages/workspace/travel/WorkspaceTravelVBAView.js +++ b/src/pages/workspace/travel/WorkspaceTravelVBAView.js @@ -8,13 +8,12 @@ import * as Illustrations from '../../../components/Icon/Illustrations'; import Section from '../../../components/Section'; import * as Link from '../../../libs/actions/Link'; import * as Report from '../../../libs/actions/Report'; +import CONST from '../../../CONST'; const propTypes = { ...withLocalizePropTypes, }; -const CONCIERGE_TRAVEL_URL = 'https://community.expensify.com/discussion/7066/introducing-concierge-travel'; - const WorkspaceTravelVBAView = props => (
( }, { title: props.translate('requestorStep.learnMore'), - onPress: () => Link.openExternalLink(CONCIERGE_TRAVEL_URL), + onPress: () => Link.openExternalLink(CONST.CONCIERGE_TRAVEL_URL), icon: Expensicons.Info, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], - link: CONCIERGE_TRAVEL_URL, + link: CONST.CONCIERGE_TRAVEL_URL, }, ]} > From caf992c1b600598b378b613f902ebc52f4fbf9e6 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 13 Apr 2023 10:28:15 +0100 Subject: [PATCH 07/14] cleanup boolean checks --- src/components/MenuItemList.js | 2 +- src/pages/settings/AboutPage/AboutPage.js | 4 ++-- src/pages/settings/InitialSettingsPage.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/MenuItemList.js b/src/components/MenuItemList.js index 65b1357fff27..80ecce3ebd91 100644 --- a/src/components/MenuItemList.js +++ b/src/components/MenuItemList.js @@ -38,7 +38,7 @@ const MenuItemList = (props) => { key={menuItemProps.title} onSecondaryInteraction={e => secondaryInteraction(menuItemProps.link, e)} ref={el => popoverAnchor = el} - shouldBlockSelection={typeof menuItemProps.link === 'function' || !_.isEmpty(menuItemProps.link)} + shouldBlockSelection={Boolean(menuItemProps.link)} // eslint-disable-next-line react/jsx-props-no-spreading {...menuItemProps} /> diff --git a/src/pages/settings/AboutPage/AboutPage.js b/src/pages/settings/AboutPage/AboutPage.js index 996342095ced..1cdcce623f8e 100644 --- a/src/pages/settings/AboutPage/AboutPage.js +++ b/src/pages/settings/AboutPage/AboutPage.js @@ -113,8 +113,8 @@ const AboutPage = (props) => { icon={item.icon} iconRight={item.iconRight} onPress={() => item.action()} - shouldBlockSelection={!_.isEmpty(item.link)} - onSecondaryInteraction={e => !_.isEmpty(item.link) && ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, popoverAnchor)} + shouldBlockSelection={Boolean(item.link)} + onSecondaryInteraction={e => Boolean(item.link) && ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, popoverAnchor)} ref={el => popoverAnchor = el} shouldShowRightIcon /> diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js index 82fa2259e546..09e1af81cc68 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -242,8 +242,8 @@ class InitialSettingsPage extends React.Component { floatRightAvatars={item.floatRightAvatars} shouldStackHorizontally={item.shouldStackHorizontally} ref={this.popoverAnchor} - shouldBlockSelection={!_.isEmpty(item.link)} - onSecondaryInteraction={e => !_.isEmpty(item.link) && ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, this.popoverAnchor.current)} + shouldBlockSelection={Boolean(item.link)} + onSecondaryInteraction={e => Boolean(item.link) && ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, this.popoverAnchor.current)} /> ); } From 8396a50808f3dd4fc9306241e712594d2e720403 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 13 Apr 2023 13:00:35 +0100 Subject: [PATCH 08/14] update more links with the Copy to Clipboard behaviour --- .../ReimbursementAccount/Enable2FAPrompt.js | 53 ++++++++------ .../bills/WorkspaceBillsFirstSection.js | 4 +- .../workspace/bills/WorkspaceBillsVBAView.js | 51 +++++++------ .../card/WorkspaceCardVBAWithECardView.js | 15 +++- .../invoices/WorkspaceInvoicesFirstSection.js | 73 ++++++++++--------- .../invoices/WorkspaceInvoicesVBAView.js | 51 +++++++------ .../reimburse/WorkspaceReimburseSection.js | 4 +- .../reimburse/WorkspaceReimburseView.js | 5 +- 8 files changed, 147 insertions(+), 109 deletions(-) diff --git a/src/pages/ReimbursementAccount/Enable2FAPrompt.js b/src/pages/ReimbursementAccount/Enable2FAPrompt.js index 51c62b8957c1..cb7bc1f004d3 100644 --- a/src/pages/ReimbursementAccount/Enable2FAPrompt.js +++ b/src/pages/ReimbursementAccount/Enable2FAPrompt.js @@ -13,31 +13,36 @@ import themeColors from '../../styles/themes/default'; const propTypes = { ...withLocalizePropTypes, }; -const Enable2FAPrompt = props => ( -
{ - Link.openOldDotLink(encodeURI(`settings?param={"section":"account","action":"enableTwoFactorAuth","exitTo":"${ROUTES.getBankAccountRoute()}","isFromNewDot":"true"}`)); +const Enable2FAPrompt = (props) => { + const secureYourAccountUrl = encodeURI(`settings?param={"section":"account","action":"enableTwoFactorAuth","exitTo":"${ROUTES.getBankAccountRoute()}","isFromNewDot":"true"}`); + + return ( +
{ + Link.openOldDotLink(secureYourAccountUrl); + }, + icon: Expensicons.Shield, + shouldShowRightIcon: true, + iconRight: Expensicons.NewWindow, + iconFill: themeColors.success, + wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(secureYourAccountUrl), }, - icon: Expensicons.Shield, - shouldShowRightIcon: true, - iconRight: Expensicons.NewWindow, - iconFill: themeColors.success, - wrapperStyle: [styles.cardMenuItem], - }, - ]} - > - - - {props.translate('validationStep.enable2FAText')} - - -
-); + ]} + > + + + {props.translate('validationStep.enable2FAText')} + + +
+ ); +}; Enable2FAPrompt.propTypes = propTypes; Enable2FAPrompt.displayName = 'Enable2FAPrompt'; diff --git a/src/pages/workspace/bills/WorkspaceBillsFirstSection.js b/src/pages/workspace/bills/WorkspaceBillsFirstSection.js index f97b6ba946ae..0d7f35155d15 100644 --- a/src/pages/workspace/bills/WorkspaceBillsFirstSection.js +++ b/src/pages/workspace/bills/WorkspaceBillsFirstSection.js @@ -42,6 +42,7 @@ const defaultProps = { const WorkspaceBillsFirstSection = (props) => { const emailDomain = Str.extractEmailDomain(props.session.email); + const manageYourBillsUrl = `reports?policyID=${props.policyID}&from=all&type=bill&showStates=Open,Processing,Approved,Reimbursed,Archived&isAdvancedFilterMode=true`; return (
{ { title: props.translate('workspace.bills.viewAllBills'), onPress: () => ( - Link.openOldDotLink(`reports?policyID=${props.policyID}&from=all&type=bill&showStates=Open,Processing,Approved,Reimbursed,Archived&isAdvancedFilterMode=true`) + Link.openOldDotLink(manageYourBillsUrl) ), icon: Expensicons.Bill, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOdDotURL(manageYourBillsUrl), }, ]} containerStyles={[styles.cardSection]} diff --git a/src/pages/workspace/bills/WorkspaceBillsVBAView.js b/src/pages/workspace/bills/WorkspaceBillsVBAView.js index 3731a9cc7b8f..dcb625777c7d 100644 --- a/src/pages/workspace/bills/WorkspaceBillsVBAView.js +++ b/src/pages/workspace/bills/WorkspaceBillsVBAView.js @@ -17,30 +17,35 @@ const propTypes = { ...withLocalizePropTypes, }; -const WorkspaceBillsVBAView = props => ( - <> - +const WorkspaceBillsVBAView = (props) => { + const reportsUrl = `reports?policyID=${props.policyID}&from=all&type=bill&showStates=Processing,Approved&isAdvancedFilterMode=true`; -
Link.openOldDotLink(`reports?policyID=${props.policyID}&from=all&type=bill&showStates=Processing,Approved&isAdvancedFilterMode=true`), - icon: Expensicons.Bill, - shouldShowRightIcon: true, - iconRight: Expensicons.NewWindow, - wrapperStyle: [styles.cardMenuItem], - }, - ]} - > - - {props.translate('workspace.bills.VBACopy')} - -
- -); + return ( + <> + + +
Link.openOldDotLink(reportsUrl), + icon: Expensicons.Bill, + shouldShowRightIcon: true, + iconRight: Expensicons.NewWindow, + wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(reportsUrl), + }, + ]} + > + + {props.translate('workspace.bills.VBACopy')} + +
+ + ); +}; WorkspaceBillsVBAView.propTypes = propTypes; WorkspaceBillsVBAView.displayName = 'WorkspaceBillsVBAView'; diff --git a/src/pages/workspace/card/WorkspaceCardVBAWithECardView.js b/src/pages/workspace/card/WorkspaceCardVBAWithECardView.js index a9a551f23131..4c1213800bac 100644 --- a/src/pages/workspace/card/WorkspaceCardVBAWithECardView.js +++ b/src/pages/workspace/card/WorkspaceCardVBAWithECardView.js @@ -13,31 +13,40 @@ const propTypes = { ...withLocalizePropTypes, }; +const MENU_LINKS = { + ISSUE_AND_MANAGE_CARDS: 'domain_companycards', + RECONCILE_CARDS: encodeURI('domain_companycards?param={"section":"cardReconciliation"}'), + SETTLEMENT_FREQUENCY: encodeURI('domain_companycards?param={"section":"configureSettings"}'), +}; + const WorkspaceCardVBAWithECardView = (props) => { const menuItems = [ { title: props.translate('workspace.common.issueAndManageCards'), - onPress: () => Link.openOldDotLink('domain_companycards'), + onPress: () => Link.openOldDotLink(MENU_LINKS.ISSUE_AND_MANAGE_CARDS), icon: Expensicons.ExpensifyCard, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(MENU_LINKS.ISSUE_AND_MANAGE_CARDS), }, { title: props.translate('workspace.common.reconcileCards'), - onPress: () => Link.openOldDotLink(encodeURI('domain_companycards?param={"section":"cardReconciliation"}')), + onPress: () => Link.openOldDotLink(MENU_LINKS.RECONCILE_CARDS), icon: Expensicons.ReceiptSearch, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(MENU_LINKS.RECONCILE_CARDS), }, { title: props.translate('workspace.common.settlementFrequency'), - onPress: () => Link.openOldDotLink(encodeURI('domain_companycards?param={"section":"configureSettings"}')), + onPress: () => Link.openOldDotLink(MENU_LINKS.SETTLEMENT_FREQUENCY), icon: Expensicons.Gear, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(MENU_LINKS.SETTLEMENT_FREQUENCY), }, ]; diff --git a/src/pages/workspace/invoices/WorkspaceInvoicesFirstSection.js b/src/pages/workspace/invoices/WorkspaceInvoicesFirstSection.js index 4d027542b155..9f3c84d28ff1 100644 --- a/src/pages/workspace/invoices/WorkspaceInvoicesFirstSection.js +++ b/src/pages/workspace/invoices/WorkspaceInvoicesFirstSection.js @@ -16,39 +16,46 @@ const propTypes = { ...withLocalizePropTypes, }; -const WorkspaceInvoicesFirstSection = props => ( -
Link.openOldDotLink(encodeURI('reports?param={"createInvoice":true}')), - icon: Expensicons.Send, - shouldShowRightIcon: true, - iconRight: Expensicons.NewWindow, - wrapperStyle: [styles.cardMenuItem], - }, - { - title: props.translate('workspace.invoices.viewAllInvoices'), - onPress: () => ( - Link.openOldDotLink(`reports?policyID=${props.policyID}&from=all&type=invoice&showStates=Open,Processing,Approved,Reimbursed,Archived&isAdvancedFilterMode=true`) - ), - icon: Expensicons.Invoice, - shouldShowRightIcon: true, - iconRight: Expensicons.NewWindow, - wrapperStyle: [styles.cardMenuItem], - }, - ]} - containerStyles={[styles.cardSection]} - > - - - {props.translate('workspace.invoices.invoiceFirstSectionCopy')} - - -
-); +const WorkspaceInvoicesFirstSection = (props) => { + const sendInvoiceUrl = encodeURI('reports?param={"createInvoice":true}'); + const viewAllInvoicesUrl = `reports?policyID=${props.policyID}&from=all&type=invoice&showStates=Open,Processing,Approved,Reimbursed,Archived&isAdvancedFilterMode=true`; + + return ( +
Link.openOldDotLink(sendInvoiceUrl), + icon: Expensicons.Send, + shouldShowRightIcon: true, + iconRight: Expensicons.NewWindow, + wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(sendInvoiceUrl), + }, + { + title: props.translate('workspace.invoices.viewAllInvoices'), + onPress: () => ( + Link.openOldDotLink(viewAllInvoicesUrl) + ), + icon: Expensicons.Invoice, + shouldShowRightIcon: true, + iconRight: Expensicons.NewWindow, + wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(viewAllInvoicesUrl), + }, + ]} + containerStyles={[styles.cardSection]} + > + + + {props.translate('workspace.invoices.invoiceFirstSectionCopy')} + + +
+ ); +}; WorkspaceInvoicesFirstSection.propTypes = propTypes; WorkspaceInvoicesFirstSection.displayName = 'WorkspaceInvoicesFirstSection'; diff --git a/src/pages/workspace/invoices/WorkspaceInvoicesVBAView.js b/src/pages/workspace/invoices/WorkspaceInvoicesVBAView.js index c88a61eef18b..c4f50bb9d49d 100644 --- a/src/pages/workspace/invoices/WorkspaceInvoicesVBAView.js +++ b/src/pages/workspace/invoices/WorkspaceInvoicesVBAView.js @@ -17,30 +17,35 @@ const propTypes = { ...withLocalizePropTypes, }; -const WorkspaceInvoicesVBAView = props => ( - <> - +const WorkspaceInvoicesVBAView = (props) => { + const viewUnpaidInvoicesUrl = `reports?policyID=${props.policyID}&from=all&type=invoice&showStates=Processing&isAdvancedFilterMode=true`; -
Link.openOldDotLink(`reports?policyID=${props.policyID}&from=all&type=invoice&showStates=Processing&isAdvancedFilterMode=true`), - icon: Expensicons.Hourglass, - shouldShowRightIcon: true, - iconRight: Expensicons.NewWindow, - wrapperStyle: [styles.cardMenuItem], - }, - ]} - > - - {props.translate('workspace.invoices.unlockVBACopy')} - -
- -); + return ( + <> + + +
Link.openOldDotLink(viewUnpaidInvoicesUrl), + icon: Expensicons.Hourglass, + shouldShowRightIcon: true, + iconRight: Expensicons.NewWindow, + wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(viewUnpaidInvoicesUrl), + }, + ]} + > + + {props.translate('workspace.invoices.unlockVBACopy')} + +
+ + ); +}; WorkspaceInvoicesVBAView.propTypes = propTypes; WorkspaceInvoicesVBAView.displayName = 'WorkspaceInvoicesVBAView'; diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseSection.js b/src/pages/workspace/reimburse/WorkspaceReimburseSection.js index 20f443bf5671..3928816fb686 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseSection.js +++ b/src/pages/workspace/reimburse/WorkspaceReimburseSection.js @@ -57,6 +57,7 @@ class WorkspaceReimburseSection extends React.Component { render() { const achState = lodashGet(this.props.reimbursementAccount, 'achData.state', ''); const hasVBA = achState === BankAccount.STATE.OPEN; + const reimburseReceiptsUrl = `reports?policyID=${this.props.policy.id}&from=all&type=expense&showStates=Archived&isAdvancedFilterMode=true`; if (this.props.network.isOffline) { return ( @@ -93,11 +94,12 @@ class WorkspaceReimburseSection extends React.Component { menuItems={[ { title: this.props.translate('workspace.reimburse.reimburseReceipts'), - onPress: () => Link.openOldDotLink(`reports?policyID=${this.props.policy.id}&from=all&type=expense&showStates=Archived&isAdvancedFilterMode=true`), + onPress: () => Link.openOldDotLink(reimburseReceiptsUrl), icon: Expensicons.Bank, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(reimburseReceiptsUrl), }, ]} > diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.js b/src/pages/workspace/reimburse/WorkspaceReimburseView.js index d103121b9e2a..ccb40d053e7f 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseView.js +++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.js @@ -214,6 +214,8 @@ class WorkspaceReimburseView extends React.Component { } render() { + const viewAllReceiptsUrl = `expenses?policyIDList=${this.props.policy.id}&billableReimbursable=reimbursable&submitterEmail=%2B%2B`; + return ( <>
Link.openOldDotLink(`expenses?policyIDList=${this.props.policy.id}&billableReimbursable=reimbursable&submitterEmail=%2B%2B`), + onPress: () => Link.openOldDotLink(viewAllReceiptsUrl), icon: Expensicons.Receipt, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(viewAllReceiptsUrl), }, ]} > From cae262a1cfe7e127308e5ede2f714ced91449ca8 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 13 Apr 2023 19:20:59 +0100 Subject: [PATCH 09/14] conditional onSecondaryInteraction --- src/components/MenuItemList.js | 2 +- src/components/PressableWithSecondaryInteraction/index.js | 7 +++++++ .../PressableWithSecondaryInteraction/index.native.js | 3 +++ src/pages/settings/AboutPage/AboutPage.js | 3 ++- src/pages/settings/InitialSettingsPage.js | 2 +- 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/MenuItemList.js b/src/components/MenuItemList.js index 80ecce3ebd91..00f464e042ba 100644 --- a/src/components/MenuItemList.js +++ b/src/components/MenuItemList.js @@ -36,7 +36,7 @@ const MenuItemList = (props) => { {_.map(props.menuItems, menuItemProps => ( secondaryInteraction(menuItemProps.link, e)} + onSecondaryInteraction={_.isEmpty(menuItemProps.link) ? e => secondaryInteraction(menuItemProps.link, e) : undefined} ref={el => popoverAnchor = el} shouldBlockSelection={Boolean(menuItemProps.link)} // eslint-disable-next-line react/jsx-props-no-spreading diff --git a/src/components/PressableWithSecondaryInteraction/index.js b/src/components/PressableWithSecondaryInteraction/index.js index d9a3d55ad841..a2e488cf43fe 100644 --- a/src/components/PressableWithSecondaryInteraction/index.js +++ b/src/components/PressableWithSecondaryInteraction/index.js @@ -31,6 +31,10 @@ class PressableWithSecondaryInteraction extends Component { * https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event */ executeSecondaryInteractionOnContextMenu(e) { + if (!this.props.onSecondaryInteraction) { + return; + } + e.stopPropagation(); if (this.props.preventDefaultContentMenu) { e.preventDefault(); @@ -58,6 +62,9 @@ class PressableWithSecondaryInteraction extends Component { style={StyleUtils.combineStyles(this.props.inline ? styles.dInline : this.props.style)} onPressIn={this.props.onPressIn} onLongPress={(e) => { + if (!this.props.onSecondaryInteraction) { + return; + } if (DeviceCapabilities.hasHoverSupport()) { return; } diff --git a/src/components/PressableWithSecondaryInteraction/index.native.js b/src/components/PressableWithSecondaryInteraction/index.native.js index c51671ba835c..744c92e4f81b 100644 --- a/src/components/PressableWithSecondaryInteraction/index.native.js +++ b/src/components/PressableWithSecondaryInteraction/index.native.js @@ -19,6 +19,9 @@ const PressableWithSecondaryInteraction = (props) => { ref={props.forwardedRef} onPress={props.onPress} onLongPress={(e) => { + if (!props.onSecondaryInteraction) { + return; + } e.preventDefault(); HapticFeedback.longPress(); props.onSecondaryInteraction(e); diff --git a/src/pages/settings/AboutPage/AboutPage.js b/src/pages/settings/AboutPage/AboutPage.js index 1cdcce623f8e..b4a816ea5740 100644 --- a/src/pages/settings/AboutPage/AboutPage.js +++ b/src/pages/settings/AboutPage/AboutPage.js @@ -114,7 +114,8 @@ const AboutPage = (props) => { iconRight={item.iconRight} onPress={() => item.action()} shouldBlockSelection={Boolean(item.link)} - onSecondaryInteraction={e => Boolean(item.link) && ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, popoverAnchor)} + onSecondaryInteraction={!_.isEmpty(item.link) + ? e => ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, popoverAnchor) : undefined} ref={el => popoverAnchor = el} shouldShowRightIcon /> diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js index ce2a9e138588..ef001593a7c0 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -243,7 +243,7 @@ class InitialSettingsPage extends React.Component { shouldStackHorizontally={item.shouldStackHorizontally} ref={this.popoverAnchor} shouldBlockSelection={Boolean(item.link)} - onSecondaryInteraction={e => Boolean(item.link) && ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, this.popoverAnchor.current)} + onSecondaryInteraction={!_.isEmpty(item.link) ? e => ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, this.popoverAnchor.current) : undefined} /> ); } From f701283512463d6f7bb8f7f9a2cf6cb38d76fea0 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 13 Apr 2023 19:36:59 +0100 Subject: [PATCH 10/14] conditional onLongPress --- src/components/PressableWithSecondaryInteraction/index.js | 7 ++----- .../PressableWithSecondaryInteraction/index.native.js | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/components/PressableWithSecondaryInteraction/index.js b/src/components/PressableWithSecondaryInteraction/index.js index a2e488cf43fe..edac38a8e0cd 100644 --- a/src/components/PressableWithSecondaryInteraction/index.js +++ b/src/components/PressableWithSecondaryInteraction/index.js @@ -61,10 +61,7 @@ class PressableWithSecondaryInteraction extends Component { { - if (!this.props.onSecondaryInteraction) { - return; - } + onLongPress={this.props.onSecondaryInteraction ? (e) => { if (DeviceCapabilities.hasHoverSupport()) { return; } @@ -72,7 +69,7 @@ class PressableWithSecondaryInteraction extends Component { this.pressableRef.blur(); } this.props.onSecondaryInteraction(e); - }} + } : undefined} onPressOut={this.props.onPressOut} onPress={this.props.onPress} ref={el => this.pressableRef = el} diff --git a/src/components/PressableWithSecondaryInteraction/index.native.js b/src/components/PressableWithSecondaryInteraction/index.native.js index 744c92e4f81b..7c30f0abd551 100644 --- a/src/components/PressableWithSecondaryInteraction/index.native.js +++ b/src/components/PressableWithSecondaryInteraction/index.native.js @@ -18,14 +18,11 @@ const PressableWithSecondaryInteraction = (props) => { { - if (!props.onSecondaryInteraction) { - return; - } + onLongPress={props.onSecondaryInteraction ? (e) => { e.preventDefault(); HapticFeedback.longPress(); props.onSecondaryInteraction(e); - }} + } : undefined} onPressIn={props.onPressIn} onPressOut={props.onPressOut} // eslint-disable-next-line react/jsx-props-no-spreading From c0d15eb5b66a7601a0020d4aff7be53038146e80 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 13 Apr 2023 19:53:06 +0100 Subject: [PATCH 11/14] cleanup conditional checks, make onSecondaryInteraction prop type optional --- src/components/MenuItem.js | 2 +- .../index.js | 19 ++++++++++--------- .../index.native.js | 7 +++++-- ...ssableWithSecondaryInteractionPropTypes.js | 2 +- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index d31553248900..34339012b6b1 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -48,7 +48,7 @@ const defaultProps = { subtitle: undefined, iconType: CONST.ICON_TYPE_ICON, onPress: () => {}, - onSecondaryInteraction: () => {}, + onSecondaryInteraction: undefined, interactive: true, fallbackIcon: Expensicons.FallbackAvatar, brickRoadIndicator: '', diff --git a/src/components/PressableWithSecondaryInteraction/index.js b/src/components/PressableWithSecondaryInteraction/index.js index edac38a8e0cd..d32170494c33 100644 --- a/src/components/PressableWithSecondaryInteraction/index.js +++ b/src/components/PressableWithSecondaryInteraction/index.js @@ -55,21 +55,22 @@ class PressableWithSecondaryInteraction extends Component { render() { const defaultPressableProps = _.omit(this.props, ['onSecondaryInteraction', 'children', 'onLongPress']); + const executeSecondaryInteraction = (e) => { + if (DeviceCapabilities.hasHoverSupport()) { + return; + } + if (this.props.withoutFocusOnSecondaryInteraction && this.pressableRef) { + this.pressableRef.blur(); + } + this.props.onSecondaryInteraction(e); + }; // On Web, Text does not support LongPress events thus manage inline mode with styling instead of using Text. return ( { - if (DeviceCapabilities.hasHoverSupport()) { - return; - } - if (this.props.withoutFocusOnSecondaryInteraction && this.pressableRef) { - this.pressableRef.blur(); - } - this.props.onSecondaryInteraction(e); - } : undefined} + onLongPress={this.props.onSecondaryInteraction ? executeSecondaryInteraction : undefined} onPressOut={this.props.onPressOut} onPress={this.props.onPress} ref={el => this.pressableRef = el} diff --git a/src/components/PressableWithSecondaryInteraction/index.native.js b/src/components/PressableWithSecondaryInteraction/index.native.js index 7c30f0abd551..744c92e4f81b 100644 --- a/src/components/PressableWithSecondaryInteraction/index.native.js +++ b/src/components/PressableWithSecondaryInteraction/index.native.js @@ -18,11 +18,14 @@ const PressableWithSecondaryInteraction = (props) => { { + onLongPress={(e) => { + if (!props.onSecondaryInteraction) { + return; + } e.preventDefault(); HapticFeedback.longPress(); props.onSecondaryInteraction(e); - } : undefined} + }} onPressIn={props.onPressIn} onPressOut={props.onPressOut} // eslint-disable-next-line react/jsx-props-no-spreading diff --git a/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js b/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js index d0f59efb8b84..0964edaa9506 100644 --- a/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js +++ b/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js @@ -12,7 +12,7 @@ const propTypes = { onPressOut: PropTypes.func, /** The function that should be called when this pressable is LongPressed or right-clicked. */ - onSecondaryInteraction: PropTypes.func.isRequired, + onSecondaryInteraction: PropTypes.func, /** The children which should be contained in this wrapper component. */ children: PropTypes.oneOfType([ From 7d54491847ddb26f96ca17c029bc255f5e7d28cf Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 13 Apr 2023 20:24:11 +0100 Subject: [PATCH 12/14] make executeSecondaryInteraction a class method, fix typo --- .../index.js | 22 ++++++++++--------- .../bills/WorkspaceBillsFirstSection.js | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/components/PressableWithSecondaryInteraction/index.js b/src/components/PressableWithSecondaryInteraction/index.js index d32170494c33..e37726d297be 100644 --- a/src/components/PressableWithSecondaryInteraction/index.js +++ b/src/components/PressableWithSecondaryInteraction/index.js @@ -12,6 +12,7 @@ import * as StyleUtils from '../../styles/StyleUtils'; class PressableWithSecondaryInteraction extends Component { constructor(props) { super(props); + this.executeSecondaryInteraction = this.executeSecondaryInteraction.bind(this); this.executeSecondaryInteractionOnContextMenu = this.executeSecondaryInteractionOnContextMenu.bind(this); } @@ -26,6 +27,16 @@ class PressableWithSecondaryInteraction extends Component { this.pressableRef.removeEventListener('contextmenu', this.executeSecondaryInteractionOnContextMenu); } + executeSecondaryInteraction(e) { + if (DeviceCapabilities.hasHoverSupport()) { + return; + } + if (this.props.withoutFocusOnSecondaryInteraction && this.pressableRef) { + this.pressableRef.blur(); + } + this.props.onSecondaryInteraction(e); + } + /** * @param {contextmenu} e - A right-click MouseEvent. * https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event @@ -55,22 +66,13 @@ class PressableWithSecondaryInteraction extends Component { render() { const defaultPressableProps = _.omit(this.props, ['onSecondaryInteraction', 'children', 'onLongPress']); - const executeSecondaryInteraction = (e) => { - if (DeviceCapabilities.hasHoverSupport()) { - return; - } - if (this.props.withoutFocusOnSecondaryInteraction && this.pressableRef) { - this.pressableRef.blur(); - } - this.props.onSecondaryInteraction(e); - }; // On Web, Text does not support LongPress events thus manage inline mode with styling instead of using Text. return ( this.pressableRef = el} diff --git a/src/pages/workspace/bills/WorkspaceBillsFirstSection.js b/src/pages/workspace/bills/WorkspaceBillsFirstSection.js index 0d7f35155d15..15397ea96776 100644 --- a/src/pages/workspace/bills/WorkspaceBillsFirstSection.js +++ b/src/pages/workspace/bills/WorkspaceBillsFirstSection.js @@ -57,7 +57,7 @@ const WorkspaceBillsFirstSection = (props) => { shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], - link: () => Link.buildOdDotURL(manageYourBillsUrl), + link: () => Link.buildOldDotURL(manageYourBillsUrl), }, ]} containerStyles={[styles.cardSection]} From 0fc1675c8e084bebf97c6145792b1e029247abe0 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 13 Apr 2023 20:39:26 +0100 Subject: [PATCH 13/14] add JSDoc for executeSecondaryInteraction --- src/components/PressableWithSecondaryInteraction/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/PressableWithSecondaryInteraction/index.js b/src/components/PressableWithSecondaryInteraction/index.js index e37726d297be..412755a4ccef 100644 --- a/src/components/PressableWithSecondaryInteraction/index.js +++ b/src/components/PressableWithSecondaryInteraction/index.js @@ -27,6 +27,9 @@ class PressableWithSecondaryInteraction extends Component { this.pressableRef.removeEventListener('contextmenu', this.executeSecondaryInteractionOnContextMenu); } + /** + * @param {Event} e - the secondary interaction event + */ executeSecondaryInteraction(e) { if (DeviceCapabilities.hasHoverSupport()) { return; From 00cd2d632ac65a337db6d46136eb43eb72bd2c4d Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 13 Apr 2023 21:03:16 +0100 Subject: [PATCH 14/14] fix onSecondaryInteraction prop check in MenuItemList --- src/components/MenuItemList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MenuItemList.js b/src/components/MenuItemList.js index 00f464e042ba..fe7d40b582f8 100644 --- a/src/components/MenuItemList.js +++ b/src/components/MenuItemList.js @@ -36,7 +36,7 @@ const MenuItemList = (props) => { {_.map(props.menuItems, menuItemProps => ( secondaryInteraction(menuItemProps.link, e) : undefined} + onSecondaryInteraction={!_.isUndefined(menuItemProps.link) ? e => secondaryInteraction(menuItemProps.link, e) : undefined} ref={el => popoverAnchor = el} shouldBlockSelection={Boolean(menuItemProps.link)} // eslint-disable-next-line react/jsx-props-no-spreading