Skip to content
Merged
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5192,6 +5192,7 @@ const CONST = {
REPORT_ACTION: 'REPORT_ACTION',
EMAIL: 'EMAIL',
REPORT: 'REPORT',
TEXT: 'TEXT',
},

PROMOTED_ACTIONS: {
Expand Down
26 changes: 23 additions & 3 deletions src/components/MenuItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {ImageContentFit} from 'expo-image';
import type {ReactElement, ReactNode, Ref} from 'react';
import React, {forwardRef, useContext, useMemo} from 'react';
import React, {forwardRef, useContext, useMemo, useRef} from 'react';
import type {GestureResponderEvent, StyleProp, TextStyle, ViewStyle} from 'react-native';
import {ActivityIndicator, View} from 'react-native';
import type {ValueOf} from 'type-fest';
Expand All @@ -12,8 +12,10 @@ import ControlSelection from '@libs/ControlSelection';
import convertToLTR from '@libs/convertToLTR';
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
import getButtonState from '@libs/getButtonState';
import mergeRefs from '@libs/mergeRefs';
import Parser from '@libs/Parser';
import type {AvatarSource} from '@libs/UserUtils';
import {showContextMenu} from '@pages/home/report/ContextMenu/ReportActionContextMenu';
import variables from '@styles/variables';
import {callFunctionIfActionIsAllowed} from '@userActions/Session';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -358,6 +360,9 @@ type MenuItemBaseProps = {

/** Whether to teleport the portal to the modal layer */
shouldTeleportPortalToModalLayer?: boolean;

/** The value to copy on secondary interaction */
copyValue?: string;
};

type MenuItemProps = (IconProps | AvatarProps | NoIcon) & MenuItemBaseProps;
Expand Down Expand Up @@ -473,6 +478,7 @@ function MenuItem(
shouldBreakWord = false,
pressableTestID,
shouldTeleportPortalToModalLayer,
copyValue,
}: MenuItemProps,
ref: PressableRef,
) {
Expand All @@ -482,6 +488,7 @@ function MenuItem(
const combinedStyle = [styles.popoverMenuItem, style];
const {shouldUseNarrowLayout} = useResponsiveLayout();
const {isExecuting, singleExecution, waitForNavigate} = useContext(MenuItemGroupContext) ?? {};
const popoverAnchor = useRef<View>(null);

const isDeleted = style && Array.isArray(style) ? style.includes(styles.offlineFeedback.deleted) : false;
const descriptionVerticalMargin = shouldShowDescriptionOnTop ? styles.mb1 : styles.mt1;
Expand Down Expand Up @@ -594,6 +601,19 @@ function MenuItem(
}
};

const secondaryInteraction = (event: GestureResponderEvent | MouseEvent) => {
if (!copyValue) {
return;
}
showContextMenu({
type: CONST.CONTEXT_MENU_TYPES.TEXT,
event,
selection: copyValue,
contextMenuAnchor: popoverAnchor.current,
});
onSecondaryInteraction?.(event);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker, just a simple thought, maybe enabling the copy function would mean no other secondary interaction would happen. :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I think there might be cases where we still need it but I'm not sure.

};

return (
<View onBlur={onBlur}>
{!!label && !isLabelHoverable && (
Expand All @@ -618,7 +638,7 @@ function MenuItem(
onPress={shouldCheckActionAllowedOnPress ? callFunctionIfActionIsAllowed(onPressAction, isAnonymousAction) : onPressAction}
onPressIn={() => shouldBlockSelection && shouldUseNarrowLayout && canUseTouchScreen() && ControlSelection.block()}
onPressOut={ControlSelection.unblock}
onSecondaryInteraction={onSecondaryInteraction}
onSecondaryInteraction={copyValue ? secondaryInteraction : onSecondaryInteraction}
wrapperStyle={outerWrapperStyle}
activeOpacity={!interactive ? 1 : variables.pressDimValue}
opacityAnimationDuration={0}
Expand All @@ -637,7 +657,7 @@ function MenuItem(
}
disabledStyle={shouldUseDefaultCursorWhenDisabled && [styles.cursorDefault]}
disabled={disabled || isExecuting}
ref={ref}
ref={mergeRefs(ref, popoverAnchor)}
role={CONST.ROLE.MENUITEM}
accessibilityLabel={title ? title.toString() : ''}
accessible
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Travel/CarTripDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function CarTripDetails({reservation, personalDetails}: CarTripDetailsProps) {
<MenuItemWithTopDescription
description={translate('travel.carDetails.confirmation')}
title={reservation.reservationID}
interactive={false}
copyValue={reservation.reservationID}
/>
)}
{!!displayName && (
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Travel/FlightTripDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function FlightTripDetails({reservation, prevReservation, personalDetails}: Flig
<MenuItemWithTopDescription
description={`${translate('travel.flight')} ${CONST.DOT_SEPARATOR} ${flightDuration}`}
title={`${reservation.company?.longName} ${CONST.DOT_SEPARATOR} ${reservation.route?.airlineCode}`}
interactive={false}
copyValue={`${reservation.company?.longName} ${CONST.DOT_SEPARATOR} ${reservation.route?.airlineCode}`}
/>
<MenuItemWithTopDescription
description={translate('common.date')}
Expand Down Expand Up @@ -106,7 +106,7 @@ function FlightTripDetails({reservation, prevReservation, personalDetails}: Flig
<MenuItemWithTopDescription
description={translate('travel.flightDetails.recordLocator')}
title={reservation.confirmations?.at(0)?.value}
interactive={false}
copyValue={reservation.confirmations?.at(0)?.value}
/>
</View>
)}
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Travel/HotelTripDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ function HotelTripDetails({reservation, personalDetails}: HotelTripDetailsProps)
<MenuItemWithTopDescription
description={translate('common.address')}
title={StringUtils.removeDoubleQuotes(reservation.start.address)}
interactive={false}
numberOfLinesTitle={2}
pressableTestID={CONST.RESERVATION_ADDRESS_TEST_ID}
copyValue={reservation.start.address}
/>
<MenuItemWithTopDescription
description={translate('travel.hotelDetails.checkIn')}
Expand Down Expand Up @@ -76,7 +76,7 @@ function HotelTripDetails({reservation, personalDetails}: HotelTripDetailsProps)
<MenuItemWithTopDescription
description={translate('travel.hotelDetails.confirmation')}
title={reservation.confirmations?.at(0)?.value}
interactive={false}
copyValue={reservation.confirmations?.at(0)?.value}
/>
)}
{!!displayName && (
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Travel/TrainTripDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function TrainTripDetails({reservation, personalDetails}: TrainTripDetailsProps)
<MenuItemWithTopDescription
description={`${translate('travel.train')} ${trainDuration ? `${CONST.DOT_SEPARATOR} ${trainDuration}` : ''}`}
title={reservation.route?.name}
interactive={false}
copyValue={reservation.route?.name}
/>
<MenuItemWithTopDescription
description={translate('common.date')}
Expand Down Expand Up @@ -86,7 +86,7 @@ function TrainTripDetails({reservation, personalDetails}: TrainTripDetailsProps)
<MenuItemWithTopDescription
description={translate('travel.trainDetails.confirmation')}
title={reservation.confirmations?.at(0)?.value}
interactive={false}
copyValue={reservation.confirmations?.at(0)?.value}
/>
)}

Expand Down
13 changes: 13 additions & 0 deletions src/pages/home/report/ContextMenu/ContextMenuActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,19 @@ const ContextMenuActions: ContextMenuAction[] = [
},
getDescription: (selection) => selection,
},
{
isAnonymousAction: true,
textTranslateKey: 'reportActionContextMenu.copyToClipboard',
icon: Expensicons.Copy,
successTextTranslateKey: 'reportActionContextMenu.copied',
successIcon: Expensicons.Checkmark,
shouldShow: ({type}) => type === CONST.CONTEXT_MENU_TYPES.TEXT,
onPress: (closePopover, {selection}) => {
Clipboard.setString(selection);
hideContextMenu(true, ReportActionComposeFocusManager.focus);
},
getDescription: () => undefined,
},
{
isAnonymousAction: true,
textTranslateKey: 'reportActionContextMenu.copyEmailToClipboard',
Expand Down
1 change: 1 addition & 0 deletions src/styles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4281,6 +4281,7 @@ const styles = (theme: ThemeColors) =>
},

contextMenuItemPopoverMaxWidth: {
minWidth: 320,
maxWidth: 375,
},

Expand Down