diff --git a/src/CONST.ts b/src/CONST.ts index bc0a0c3216f0..d18bdd02ba71 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3040,6 +3040,14 @@ const CONST = { DEFAULT: 5, CAROUSEL: 3, }, + + /** Context menu types */ + CONTEXT_MENU_TYPES: { + LINK: 'LINK', + REPORT_ACTION: 'REPORT_ACTION', + EMAIL: 'EMAIL', + REPORT: 'REPORT', + }, } as const; export default CONST; diff --git a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.js b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx similarity index 64% rename from src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.js rename to src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx index 4d32b4427d92..1bcdbb383f7a 100644 --- a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.js +++ b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx @@ -1,9 +1,6 @@ import Str from 'expensify-common/lib/str'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useEffect} from 'react'; -import {StyleSheet} from 'react-native'; -import _ from 'underscore'; +import React, {useEffect, useRef} from 'react'; +import {Text as RNText, StyleSheet} from 'react-native'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; @@ -11,29 +8,19 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import * as ContextMenuActions from '@pages/home/report/ContextMenu/ContextMenuActions'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; -import {propTypes as anchorForCommentsOnlyPropTypes} from './anchorForCommentsOnlyPropTypes'; - -const propTypes = { - /** Press in handler for the link */ - // eslint-disable-next-line react/require-default-props - onPressIn: PropTypes.func, - - /** Press out handler for the link */ - // eslint-disable-next-line react/require-default-props - onPressOut: PropTypes.func, - - ...anchorForCommentsOnlyPropTypes, -}; +import type {BaseAnchorForCommentsOnlyProps, LinkProps} from './types'; /* * This is a default anchor component for regular links. */ -function BaseAnchorForCommentsOnly({onPressIn, onPressOut, href = '', rel = '', target = '', children = null, style = {}, onPress, ...rest}) { +function BaseAnchorForCommentsOnly({onPressIn, onPressOut, href = '', rel = '', target = '', children = null, style, onPress, ...rest}: BaseAnchorForCommentsOnlyProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); + const linkRef = useRef(null); + const flattenStyle = StyleSheet.flatten(style); + useEffect( () => () => { ReportActionContextMenu.hideContextMenu(); @@ -43,10 +30,8 @@ function BaseAnchorForCommentsOnly({onPressIn, onPressOut, href = '', rel = '', const {isSmallScreenWidth} = useWindowDimensions(); - let linkRef; - - const linkProps = {}; - if (_.isFunction(onPress)) { + const linkProps: LinkProps = {}; + if (onPress) { linkProps.onPress = onPress; } else { linkProps.href = href; @@ -58,21 +43,16 @@ function BaseAnchorForCommentsOnly({onPressIn, onPressOut, href = '', rel = '', { - ReportActionContextMenu.showContextMenu( - isEmail ? ContextMenuActions.CONTEXT_MENU_TYPES.EMAIL : ContextMenuActions.CONTEXT_MENU_TYPES.LINK, - event, - href, - lodashGet(linkRef, 'current'), - ); + ReportActionContextMenu.showContextMenu(isEmail ? CONST.CONTEXT_MENU_TYPES.EMAIL : CONST.CONTEXT_MENU_TYPES.LINK, event, href, linkRef.current); }} onPress={(event) => { if (!linkProps.onPress) { return; } - event.preventDefault(); + event?.preventDefault(); linkProps.onPress(); }} onPressIn={onPressIn} @@ -82,14 +62,14 @@ function BaseAnchorForCommentsOnly({onPressIn, onPressOut, href = '', rel = '', > (linkRef = el)} + ref={linkRef} style={StyleSheet.flatten([style, defaultTextStyle])} role={CONST.ROLE.LINK} hrefAttrs={{ rel, target: isEmail || !linkProps.href ? '_self' : target, }} - href={linkProps.href || href} + href={href} suppressHighlighting // Add testID so it gets selected as an anchor tag by SelectionScraper testID="a" @@ -103,7 +83,6 @@ function BaseAnchorForCommentsOnly({onPressIn, onPressOut, href = '', rel = '', ); } -BaseAnchorForCommentsOnly.propTypes = propTypes; BaseAnchorForCommentsOnly.displayName = 'BaseAnchorForCommentsOnly'; export default BaseAnchorForCommentsOnly; diff --git a/src/components/AnchorForCommentsOnly/anchorForCommentsOnlyPropTypes.js b/src/components/AnchorForCommentsOnly/anchorForCommentsOnlyPropTypes.js deleted file mode 100644 index 6bf1d094497d..000000000000 --- a/src/components/AnchorForCommentsOnly/anchorForCommentsOnlyPropTypes.js +++ /dev/null @@ -1,35 +0,0 @@ -import PropTypes from 'prop-types'; -import stylePropTypes from '@styles/stylePropTypes'; - -const propTypes = { - /** The URL to open */ - href: PropTypes.string, - - /** What headers to send to the linked page (usually noopener and noreferrer) - This is unused in native, but is here for parity with web */ - rel: PropTypes.string, - - /** Used to determine where to open a link ("_blank" is passed for a new tab) - This is unused in native, but is here for parity with web */ - target: PropTypes.string, - - /** Any children to display */ - children: PropTypes.node, - - /** Any additional styles to apply */ - style: stylePropTypes, - - /** Press handler for the link, when not passed, default href is used to create a link like behaviour */ - onPress: PropTypes.func, -}; - -const defaultProps = { - href: '', - rel: '', - target: '', - children: null, - style: {}, - onPress: undefined, -}; - -export {propTypes, defaultProps}; diff --git a/src/components/AnchorForCommentsOnly/index.native.js b/src/components/AnchorForCommentsOnly/index.native.js deleted file mode 100644 index b9dc74b7ba05..000000000000 --- a/src/components/AnchorForCommentsOnly/index.native.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import {Linking} from 'react-native'; -import _ from 'underscore'; -import * as anchorForCommentsOnlyPropTypes from './anchorForCommentsOnlyPropTypes'; -import BaseAnchorForCommentsOnly from './BaseAnchorForCommentsOnly'; - -// eslint-disable-next-line react/jsx-props-no-spreading -function AnchorForCommentsOnly(props) { - const onPress = () => (_.isFunction(props.onPress) ? props.onPress() : Linking.openURL(props.href)); - - return ( - - ); -} - -AnchorForCommentsOnly.propTypes = anchorForCommentsOnlyPropTypes.propTypes; -AnchorForCommentsOnly.defaultProps = anchorForCommentsOnlyPropTypes.defaultProps; -AnchorForCommentsOnly.displayName = 'AnchorForCommentsOnly'; - -export default AnchorForCommentsOnly; diff --git a/src/components/AnchorForCommentsOnly/index.native.tsx b/src/components/AnchorForCommentsOnly/index.native.tsx new file mode 100644 index 000000000000..e022ad611618 --- /dev/null +++ b/src/components/AnchorForCommentsOnly/index.native.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import {Linking} from 'react-native'; +import BaseAnchorForCommentsOnly from './BaseAnchorForCommentsOnly'; +import type {AnchorForCommentsOnlyProps} from './types'; + +function AnchorForCommentsOnly({onPress, href = '', ...props}: AnchorForCommentsOnlyProps) { + const onLinkPress = () => { + if (onPress) { + onPress(); + } else { + Linking.openURL(href); + } + }; + + return ( + + ); +} + +AnchorForCommentsOnly.displayName = 'AnchorForCommentsOnly'; + +export default AnchorForCommentsOnly; diff --git a/src/components/AnchorForCommentsOnly/index.js b/src/components/AnchorForCommentsOnly/index.tsx similarity index 69% rename from src/components/AnchorForCommentsOnly/index.js rename to src/components/AnchorForCommentsOnly/index.tsx index 24a903dca5fa..493697dc610f 100644 --- a/src/components/AnchorForCommentsOnly/index.js +++ b/src/components/AnchorForCommentsOnly/index.tsx @@ -1,10 +1,10 @@ import React from 'react'; import ControlSelection from '@libs/ControlSelection'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import * as anchorForCommentsOnlyPropTypes from './anchorForCommentsOnlyPropTypes'; import BaseAnchorForCommentsOnly from './BaseAnchorForCommentsOnly'; +import type {AnchorForCommentsOnlyProps} from './types'; -function AnchorForCommentsOnly(props) { +function AnchorForCommentsOnly(props: AnchorForCommentsOnlyProps) { return ( ; + + /** Press handler for the link, when not passed, default href is used to create a link like behaviour */ + onPress?: () => void; +}; + +type BaseAnchorForCommentsOnlyProps = AnchorForCommentsOnlyProps & { + /** Press in handler for the link */ + onPressIn?: () => void; + + /** Press out handler for the link */ + onPressOut?: () => void; +}; + +type LinkProps = { + /** Press handler for the link, when not passed, default href is used to create a link like behaviour */ + onPress?: () => void; + + /** The URL to open */ + href?: string; +}; + +export type {AnchorForCommentsOnlyProps, BaseAnchorForCommentsOnlyProps, LinkProps}; diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.js index f0f59130017a..59a392ff4e67 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.js +++ b/src/components/LHNOptionsList/OptionRowLHN.js @@ -25,7 +25,6 @@ import {getGroupChatName} from '@libs/GroupChatUtils'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import * as ReportUtils from '@libs/ReportUtils'; -import * as ContextMenuActions from '@pages/home/report/ContextMenu/ContextMenuActions'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; @@ -129,7 +128,7 @@ function OptionRowLHN(props) { } setIsContextMenuActive(true); ReportActionContextMenu.showContextMenu( - ContextMenuActions.CONTEXT_MENU_TYPES.REPORT, + CONST.CONTEXT_MENU_TYPES.REPORT, event, '', popoverAnchor, diff --git a/src/components/MenuItemList.js b/src/components/MenuItemList.js index b9f2e6fc228b..c9eee8e888e1 100644 --- a/src/components/MenuItemList.js +++ b/src/components/MenuItemList.js @@ -2,8 +2,8 @@ import PropTypes from 'prop-types'; import React from 'react'; import _ from 'underscore'; import useSingleExecution from '@hooks/useSingleExecution'; -import {CONTEXT_MENU_TYPES} from '@pages/home/report/ContextMenu/ContextMenuActions'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; +import CONST from '@src/CONST'; import MenuItem from './MenuItem'; import menuItemPropTypes from './menuItemPropTypes'; @@ -31,9 +31,9 @@ function MenuItemList(props) { */ const secondaryInteraction = (link, e) => { if (typeof link === 'function') { - link().then((url) => ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, url, popoverAnchor)); + link().then((url) => ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, e, url, popoverAnchor)); } else if (!_.isEmpty(link)) { - ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, link, popoverAnchor); + ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, e, link, popoverAnchor); } }; diff --git a/src/components/ShowContextMenuContext.js b/src/components/ShowContextMenuContext.js index 6248478e5fea..28822451956d 100644 --- a/src/components/ShowContextMenuContext.js +++ b/src/components/ShowContextMenuContext.js @@ -1,8 +1,8 @@ import React from 'react'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as ReportUtils from '@libs/ReportUtils'; -import * as ContextMenuActions from '@pages/home/report/ContextMenu/ContextMenuActions'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; +import CONST from '@src/CONST'; const ShowContextMenuContext = React.createContext({ anchor: null, @@ -28,7 +28,7 @@ function showContextMenuForReport(event, anchor, reportID, action, checkIfContex return; } ReportActionContextMenu.showContextMenu( - ContextMenuActions.CONTEXT_MENU_TYPES.REPORT_ACTION, + CONST.CONTEXT_MENU_TYPES.REPORT_ACTION, event, '', anchor, diff --git a/src/pages/ReferralDetailsPage.js b/src/pages/ReferralDetailsPage.js index e05b6c5b0553..209b8f5fadc3 100644 --- a/src/pages/ReferralDetailsPage.js +++ b/src/pages/ReferralDetailsPage.js @@ -18,7 +18,6 @@ import * as Link from '@userActions/Link'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import SCREENS from '@src/SCREENS'; -import {CONTEXT_MENU_TYPES} from './home/report/ContextMenu/ContextMenuActions'; import * as ReportActionContextMenu from './home/report/ContextMenu/ReportActionContextMenu'; const propTypes = { @@ -96,7 +95,7 @@ function ReferralDetailsPage({route, account}) { disabled={isExecuting} shouldBlockSelection onPress={singleExecution(() => Link.openExternalLink(CONST.REFERRAL_PROGRAM.LEARN_MORE_LINK))} - onSecondaryInteraction={(e) => ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, CONST.REFERRAL_PROGRAM.LEARN_MORE_LINK, popoverAnchor.current)} + onSecondaryInteraction={(e) => ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, e, CONST.REFERRAL_PROGRAM.LEARN_MORE_LINK, popoverAnchor.current)} /> ); diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js index 0a9941072df7..fc06176edd3b 100755 --- a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js @@ -16,7 +16,7 @@ import compose from '@libs/compose'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ContextMenuActions, {CONTEXT_MENU_TYPES} from './ContextMenuActions'; +import ContextMenuActions from './ContextMenuActions'; import {defaultProps as GenericReportActionContextMenuDefaultProps, propTypes as genericReportActionContextMenuPropTypes} from './genericReportActionContextMenuPropTypes'; import {hideContextMenu} from './ReportActionContextMenu'; @@ -41,7 +41,7 @@ const propTypes = { }; const defaultProps = { - type: CONTEXT_MENU_TYPES.REPORT_ACTION, + type: CONST.CONTEXT_MENU_TYPES.REPORT_ACTION, anchor: null, contentRef: null, isChronosReport: false, diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 2e51ef714562..f1a46785a59a 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -49,19 +49,12 @@ function setClipboardMessage(content) { } } -const CONTEXT_MENU_TYPES = { - LINK: 'LINK', - REPORT_ACTION: 'REPORT_ACTION', - EMAIL: 'EMAIL', - REPORT: 'REPORT', -}; - // A list of all the context actions in this menu. export default [ { isAnonymousAction: false, shouldKeepOpen: true, - shouldShow: (type, reportAction) => type === CONTEXT_MENU_TYPES.REPORT_ACTION && _.has(reportAction, 'message') && !ReportActionsUtils.isMessageDeleted(reportAction), + shouldShow: (type, reportAction) => type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && _.has(reportAction, 'message') && !ReportActionsUtils.isMessageDeleted(reportAction), renderContent: (closePopover, {reportID, reportAction, close: closeManually, openContextMenu}) => { const isMini = !closePopover; @@ -138,7 +131,7 @@ export default [ successTextTranslateKey: '', successIcon: null, shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID) => { - if (type !== CONTEXT_MENU_TYPES.REPORT_ACTION) { + if (type !== CONST.CONTEXT_MENU_TYPES.REPORT_ACTION) { return false; } const isCommentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT; @@ -217,7 +210,7 @@ export default [ childReportNotificationPreference = isActionCreator ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; } const subscribed = childReportNotificationPreference !== 'hidden'; - if (type !== CONTEXT_MENU_TYPES.REPORT_ACTION) { + if (type !== CONST.CONTEXT_MENU_TYPES.REPORT_ACTION) { return false; } const isCommentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && !ReportUtils.isThreadFirstChat(reportAction, reportID); @@ -250,7 +243,7 @@ export default [ icon: Expensicons.Copy, successTextTranslateKey: 'reportActionContextMenu.copied', successIcon: Expensicons.Checkmark, - shouldShow: (type) => type === CONTEXT_MENU_TYPES.LINK, + shouldShow: (type) => type === CONST.CONTEXT_MENU_TYPES.LINK, onPress: (closePopover, {selection}) => { Clipboard.setString(selection); hideContextMenu(true, ReportActionComposeFocusManager.focus); @@ -263,7 +256,7 @@ export default [ icon: Expensicons.Copy, successTextTranslateKey: 'reportActionContextMenu.copied', successIcon: Expensicons.Checkmark, - shouldShow: (type) => type === CONTEXT_MENU_TYPES.EMAIL, + shouldShow: (type) => type === CONST.CONTEXT_MENU_TYPES.EMAIL, onPress: (closePopover, {selection}) => { Clipboard.setString(EmailUtils.trimMailTo(selection)); hideContextMenu(true, ReportActionComposeFocusManager.focus); @@ -277,7 +270,7 @@ export default [ successTextTranslateKey: 'reportActionContextMenu.copied', successIcon: Expensicons.Checkmark, shouldShow: (type, reportAction) => - type === CONTEXT_MENU_TYPES.REPORT_ACTION && !ReportActionsUtils.isReportActionAttachment(reportAction) && !ReportActionsUtils.isMessageDeleted(reportAction), + type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && !ReportActionsUtils.isReportActionAttachment(reportAction) && !ReportActionsUtils.isMessageDeleted(reportAction), // If return value is true, we switch the `text` and `icon` on // `ContextMenuItem` with `successText` and `successIcon` which will fall back to @@ -333,7 +326,7 @@ export default [ // Only hide the copylink menu item when context menu is opened over img element. const isAttachmentTarget = lodashGet(menuTarget, 'tagName') === 'IMG' && isAttachment; - return Permissions.canUseCommentLinking(betas) && type === CONTEXT_MENU_TYPES.REPORT_ACTION && !isAttachmentTarget && !ReportActionsUtils.isMessageDeleted(reportAction); + return Permissions.canUseCommentLinking(betas) && type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && !isAttachmentTarget && !ReportActionsUtils.isMessageDeleted(reportAction); }, onPress: (closePopover, {reportAction, reportID}) => { Environment.getEnvironmentURL().then((environmentURL) => { @@ -351,7 +344,7 @@ export default [ icon: Expensicons.Mail, successIcon: Expensicons.Checkmark, shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID, isPinnedChat, isUnreadChat) => - type === CONTEXT_MENU_TYPES.REPORT_ACTION || (type === CONTEXT_MENU_TYPES.REPORT && !isUnreadChat), + type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION || (type === CONST.CONTEXT_MENU_TYPES.REPORT && !isUnreadChat), onPress: (closePopover, {reportAction, reportID}) => { Report.markCommentAsUnread(reportID, reportAction.created); if (closePopover) { @@ -366,7 +359,7 @@ export default [ textTranslateKey: 'reportActionContextMenu.markAsRead', icon: Expensicons.Mail, successIcon: Expensicons.Checkmark, - shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID, isPinnedChat, isUnreadChat) => type === CONTEXT_MENU_TYPES.REPORT && isUnreadChat, + shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID, isPinnedChat, isUnreadChat) => type === CONST.CONTEXT_MENU_TYPES.REPORT && isUnreadChat, onPress: (closePopover, {reportID}) => { Report.readNewestAction(reportID); if (closePopover) { @@ -381,7 +374,7 @@ export default [ textTranslateKey: 'reportActionContextMenu.editAction', icon: Expensicons.Pencil, shouldShow: (type, reportAction, isArchivedRoom, betas, menuTarget, isChronosReport) => - type === CONTEXT_MENU_TYPES.REPORT_ACTION && ReportUtils.canEditReportAction(reportAction) && !isArchivedRoom && !isChronosReport, + type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && ReportUtils.canEditReportAction(reportAction) && !isArchivedRoom && !isChronosReport, onPress: (closePopover, {reportID, reportAction, draftMessage}) => { if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { hideContextMenu(false); @@ -416,7 +409,7 @@ export default [ icon: Expensicons.Trashcan, shouldShow: (type, reportAction, isArchivedRoom, betas, menuTarget, isChronosReport, reportID) => // Until deleting parent threads is supported in FE, we will prevent the user from deleting a thread parent - type === CONTEXT_MENU_TYPES.REPORT_ACTION && + type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && ReportUtils.canDeleteReportAction(reportAction, reportID) && !isArchivedRoom && !isChronosReport && @@ -437,7 +430,7 @@ export default [ isAnonymousAction: false, textTranslateKey: 'common.pin', icon: Expensicons.Pin, - shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID, isPinnedChat) => type === CONTEXT_MENU_TYPES.REPORT && !isPinnedChat, + shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID, isPinnedChat) => type === CONST.CONTEXT_MENU_TYPES.REPORT && !isPinnedChat, onPress: (closePopover, {reportID}) => { Report.togglePinnedState(reportID, false); if (closePopover) { @@ -450,7 +443,7 @@ export default [ isAnonymousAction: false, textTranslateKey: 'common.unPin', icon: Expensicons.Pin, - shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID, isPinnedChat) => type === CONTEXT_MENU_TYPES.REPORT && isPinnedChat, + shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID, isPinnedChat) => type === CONST.CONTEXT_MENU_TYPES.REPORT && isPinnedChat, onPress: (closePopover, {reportID}) => { Report.togglePinnedState(reportID, true); if (closePopover) { @@ -464,7 +457,7 @@ export default [ textTranslateKey: 'reportActionContextMenu.flagAsOffensive', icon: Expensicons.Flag, shouldShow: (type, reportAction, isArchivedRoom, betas, menuTarget, isChronosReport, reportID) => - type === CONTEXT_MENU_TYPES.REPORT_ACTION && + type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && ReportUtils.canFlagReportAction(reportAction, reportID) && !isArchivedRoom && !isChronosReport && @@ -481,5 +474,3 @@ export default [ getDescription: () => {}, }, ]; - -export {CONTEXT_MENU_TYPES}; diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js deleted file mode 100644 index 9467ff19b2f5..000000000000 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js +++ /dev/null @@ -1,138 +0,0 @@ -import React from 'react'; - -const contextMenuRef = React.createRef(); - -/** - * Hide the ReportActionContextMenu modal popover. - * Hides the popover menu with an optional delay - * @param {Boolean} shouldDelay - whether the menu should close after a delay - * @param {Function} [onHideCallback=() => {}] - Callback to be called after Context Menu is completely hidden - */ -function hideContextMenu(shouldDelay, onHideCallback = () => {}) { - if (!contextMenuRef.current) { - return; - } - if (!shouldDelay) { - contextMenuRef.current.hideContextMenu(onHideCallback); - - return; - } - - // Save the active instanceID for which hide action was called. - // If menu is being closed with a delay, check that whether the same instance exists or a new was created. - // If instance is not same, cancel the hide action - const instanceID = contextMenuRef.current.instanceID; - setTimeout(() => { - if (contextMenuRef.current.instanceID !== instanceID) { - return; - } - - contextMenuRef.current.hideContextMenu(onHideCallback); - }, 800); -} - -/** - * Show the ReportActionContextMenu modal popover. - * - * @param {string} type - the context menu type to display [EMAIL, LINK, REPORT_ACTION, REPORT] - * @param {Object} [event] - A press event. - * @param {String} [selection] - Copied content. - * @param {Element} contextMenuAnchor - popoverAnchor - * @param {String} reportID - Active Report Id - * @param {String} reportActionID - ReportActionID for ContextMenu - * @param {String} originalReportID - The currrent Report Id of the reportAction - * @param {String} draftMessage - ReportAction Draftmessage - * @param {Function} [onShow=() => {}] - Run a callback when Menu is shown - * @param {Function} [onHide=() => {}] - Run a callback when Menu is hidden - * @param {Boolean} isArchivedRoom - Whether the provided report is an archived room - * @param {Boolean} isChronosReport - Flag to check if the chat participant is Chronos - * @param {Boolean} isPinnedChat - Flag to check if the chat is pinned in the LHN. Used for the Pin/Unpin action - * @param {Boolean} isUnreadChat - Flag to check if the chat has unread messages in the LHN. Used for the Mark as Read/Unread action - */ -function showContextMenu( - type, - event, - selection, - contextMenuAnchor, - reportID = '0', - reportActionID = '0', - originalReportID = '0', - draftMessage = '', - onShow = () => {}, - onHide = () => {}, - isArchivedRoom = false, - isChronosReport = false, - isPinnedChat = false, - isUnreadChat = false, -) { - if (!contextMenuRef.current) { - return; - } - // If there is an already open context menu, close it first before opening - // a new one. - if (contextMenuRef.current.instanceID) { - hideContextMenu(); - contextMenuRef.current.runAndResetOnPopoverHide(); - } - - contextMenuRef.current.showContextMenu( - type, - event, - selection, - contextMenuAnchor, - reportID, - reportActionID, - originalReportID, - draftMessage, - onShow, - onHide, - isArchivedRoom, - isChronosReport, - isPinnedChat, - isUnreadChat, - ); -} - -function hideDeleteModal() { - if (!contextMenuRef.current) { - return; - } - contextMenuRef.current.hideDeleteModal(); -} - -/** - * Opens the Confirm delete action modal - * @param {String} reportID - * @param {Object} reportAction - * @param {Boolean} [shouldSetModalVisibility] - * @param {Function} [onConfirm] - * @param {Function} [onCancel] - */ -function showDeleteModal(reportID, reportAction, shouldSetModalVisibility, onConfirm, onCancel) { - if (!contextMenuRef.current) { - return; - } - contextMenuRef.current.showDeleteModal(reportID, reportAction, shouldSetModalVisibility, onConfirm, onCancel); -} - -/** - * Whether Context Menu is active for the Report Action. - * - * @param {Number|String} actionID - * @return {Boolean} - */ -function isActiveReportAction(actionID) { - if (!contextMenuRef.current) { - return; - } - return contextMenuRef.current.isActiveReportAction(actionID); -} - -function clearActiveReportAction() { - if (!contextMenuRef.current) { - return; - } - return contextMenuRef.current.clearActiveReportAction(); -} - -export {contextMenuRef, showContextMenu, hideContextMenu, isActiveReportAction, clearActiveReportAction, showDeleteModal, hideDeleteModal}; diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts new file mode 100644 index 000000000000..b269bc276b55 --- /dev/null +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts @@ -0,0 +1,175 @@ +import React from 'react'; +import {GestureResponderEvent, Text as RNText} from 'react-native'; +import {OnyxEntry} from 'react-native-onyx'; +import {ValueOf} from 'type-fest'; +import CONST from '@src/CONST'; +import type {ReportAction} from '@src/types/onyx'; + +type OnHideCallback = () => void; + +type OnConfirm = () => void; + +type OnCancel = () => void; + +type ContextMenuType = ValueOf; + +type ShowContextMenu = ( + type: ContextMenuType, + event: GestureResponderEvent | MouseEvent, + selection: string, + contextMenuAnchor: RNText | null, + reportID?: string, + reportActionID?: string, + originalReportID?: string, + draftMessage?: string, + onShow?: () => void, + onHide?: () => void, + isArchivedRoom?: boolean, + isChronosReport?: boolean, + isPinnedChat?: boolean, + isUnreadChat?: boolean, +) => void; + +type ReportActionContextMenu = { + showContextMenu: ShowContextMenu; + hideContextMenu: (callback: OnHideCallback) => void; + showDeleteModal: (reportID: string, reportAction: OnyxEntry, shouldSetModalVisibility?: boolean, onConfirm?: OnConfirm, onCancel?: OnCancel) => void; + hideDeleteModal: () => void; + isActiveReportAction: (accountID: string | number) => boolean; + instanceID: string; + runAndResetOnPopoverHide: () => void; + clearActiveReportAction: () => void; +}; + +const contextMenuRef = React.createRef(); + +/** + * Hide the ReportActionContextMenu modal popover. + * Hides the popover menu with an optional delay + * @param [shouldDelay] - whether the menu should close after a delay + * @param [onHideCallback] - Callback to be called after Context Menu is completely hidden + */ +function hideContextMenu(shouldDelay?: boolean, onHideCallback = () => {}) { + if (!contextMenuRef.current) { + return; + } + if (!shouldDelay) { + contextMenuRef.current.hideContextMenu(onHideCallback); + + return; + } + + // Save the active instanceID for which hide action was called. + // If menu is being closed with a delay, check that whether the same instance exists or a new was created. + // If instance is not same, cancel the hide action + const instanceID = contextMenuRef.current.instanceID; + setTimeout(() => { + if (contextMenuRef.current?.instanceID !== instanceID) { + return; + } + + contextMenuRef.current.hideContextMenu(onHideCallback); + }, 800); +} + +/** + * Show the ReportActionContextMenu modal popover. + * + * @param type - the context menu type to display [EMAIL, LINK, REPORT_ACTION, REPORT] + * @param [event] - A press event. + * @param [selection] - Copied content. + * @param contextMenuAnchor - popoverAnchor + * @param reportID - Active Report Id + * @param reportActionID - ReportActionID for ContextMenu + * @param originalReportID - The currrent Report Id of the reportAction + * @param draftMessage - ReportAction Draftmessage + * @param [onShow=() => {}] - Run a callback when Menu is shown + * @param [onHide=() => {}] - Run a callback when Menu is hidden + * @param isArchivedRoom - Whether the provided report is an archived room + * @param isChronosReport - Flag to check if the chat participant is Chronos + * @param isPinnedChat - Flag to check if the chat is pinned in the LHN. Used for the Pin/Unpin action + * @param isUnreadChat - Flag to check if the chat has unread messages in the LHN. Used for the Mark as Read/Unread action + */ +function showContextMenu( + type: ContextMenuType, + event: GestureResponderEvent | MouseEvent, + selection: string, + contextMenuAnchor: RNText | null, + reportID = '0', + reportActionID = '0', + originalReportID = '0', + draftMessage = '', + onShow = () => {}, + onHide = () => {}, + isArchivedRoom = false, + isChronosReport = false, + isPinnedChat = false, + isUnreadChat = false, +) { + if (!contextMenuRef.current) { + return; + } + // If there is an already open context menu, close it first before opening + // a new one. + if (contextMenuRef.current.instanceID) { + hideContextMenu(); + contextMenuRef.current.runAndResetOnPopoverHide(); + } + + contextMenuRef.current.showContextMenu( + type, + event, + selection, + contextMenuAnchor, + reportID, + reportActionID, + originalReportID, + draftMessage, + onShow, + onHide, + isArchivedRoom, + isChronosReport, + isPinnedChat, + isUnreadChat, + ); +} + +/** + * Hides the Confirm delete action modal + */ +function hideDeleteModal() { + if (!contextMenuRef.current) { + return; + } + contextMenuRef.current.hideDeleteModal(); +} + +/** + * Opens the Confirm delete action modal + */ +function showDeleteModal(reportID: string, reportAction: OnyxEntry, shouldSetModalVisibility?: boolean, onConfirm?: OnConfirm, onCancel?: OnCancel) { + if (!contextMenuRef.current) { + return; + } + contextMenuRef.current.showDeleteModal(reportID, reportAction, shouldSetModalVisibility, onConfirm, onCancel); +} + +/** + * Whether Context Menu is active for the Report Action. + */ +function isActiveReportAction(actionID: string | number): boolean { + if (!contextMenuRef.current) { + return false; + } + return contextMenuRef.current.isActiveReportAction(actionID); +} + +function clearActiveReportAction() { + if (!contextMenuRef.current) { + return; + } + + return contextMenuRef.current.clearActiveReportAction(); +} + +export {contextMenuRef, showContextMenu, hideContextMenu, isActiveReportAction, clearActiveReportAction, showDeleteModal, hideDeleteModal}; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index c81e47016dcc..78aea7d6418b 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -61,7 +61,6 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground'; -import * as ContextMenuActions from './ContextMenu/ContextMenuActions'; import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; import {hideContextMenu} from './ContextMenu/ReportActionContextMenu'; @@ -274,7 +273,7 @@ function ReportActionItem(props) { setIsContextMenuActive(true); const selection = SelectionScraper.getCurrentSelection(); ReportActionContextMenu.showContextMenu( - ContextMenuActions.CONTEXT_MENU_TYPES.REPORT_ACTION, + CONST.CONTEXT_MENU_TYPES.REPORT_ACTION, event, selection, popoverAnchorRef, diff --git a/src/pages/settings/AboutPage/AboutPage.js b/src/pages/settings/AboutPage/AboutPage.js index 16ae590cf86b..4cc0eb9a5181 100644 --- a/src/pages/settings/AboutPage/AboutPage.js +++ b/src/pages/settings/AboutPage/AboutPage.js @@ -16,7 +16,6 @@ import useWaitForNavigation from '@hooks/useWaitForNavigation'; import compose from '@libs/compose'; import * as Environment from '@libs/Environment/Environment'; import Navigation from '@libs/Navigation/Navigation'; -import {CONTEXT_MENU_TYPES} from '@pages/home/report/ContextMenu/ContextMenuActions'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import * as Link from '@userActions/Link'; import * as Report from '@userActions/Report'; @@ -88,7 +87,7 @@ function AboutPage(props) { iconRight: item.iconRight, onPress: item.action, shouldShowRightIcon: true, - onSecondaryInteraction: !_.isEmpty(item.link) ? (e) => ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, popoverAnchor) : undefined, + onSecondaryInteraction: !_.isEmpty(item.link) ? (e) => ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, e, item.link, popoverAnchor) : undefined, ref: popoverAnchor, shouldBlockSelection: Boolean(item.link), })); diff --git a/src/pages/settings/AppDownloadLinks.js b/src/pages/settings/AppDownloadLinks.js index 471a38b039b2..e6c03f6015d7 100644 --- a/src/pages/settings/AppDownloadLinks.js +++ b/src/pages/settings/AppDownloadLinks.js @@ -10,7 +10,6 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withW import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; -import {CONTEXT_MENU_TYPES} from '@pages/home/report/ContextMenu/ContextMenuActions'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import * as Link from '@userActions/Link'; import CONST from '@src/CONST'; @@ -66,7 +65,7 @@ function AppDownloadLinksPage(props) { item.action()} - onSecondaryInteraction={(e) => ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, popoverAnchor)} + onSecondaryInteraction={(e) => ReportActionContextMenu.showContextMenu(CONST.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 b10c0b2e4d2b..d2b91ed6b76b 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -32,7 +32,6 @@ import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as UserUtils from '@libs/UserUtils'; import walletTermsPropTypes from '@pages/EnablePayments/walletTermsPropTypes'; -import {CONTEXT_MENU_TYPES} from '@pages/home/report/ContextMenu/ContextMenuActions'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import policyMemberPropType from '@pages/policyMemberPropType'; import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimbursementAccountPropTypes'; @@ -330,7 +329,7 @@ function InitialSettingsPage(props) { ref={popoverAnchor} shouldBlockSelection={Boolean(item.link)} onSecondaryInteraction={ - !_.isEmpty(item.link) ? (e) => ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, popoverAnchor.current) : undefined + !_.isEmpty(item.link) ? (e) => ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, e, item.link, popoverAnchor.current) : undefined } /> );