diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index 7e5820f425c5..0bf649b54a6e 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -1,7 +1,7 @@ import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import type {ImageContentFit} from 'expo-image'; import type {ForwardedRef, ReactNode} from 'react'; -import React, {forwardRef, useEffect, useMemo, useRef, useState} from 'react'; +import React, {forwardRef, useContext, useEffect, useMemo, useRef, useState} from 'react'; import type {GestureResponderEvent, StyleProp, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; import type {AnimatedStyle} from 'react-native-reanimated'; @@ -29,6 +29,7 @@ import Hoverable from './Hoverable'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import * as defaultWorkspaceAvatars from './Icon/WorkspaceDefaultAvatars'; +import {MenuItemGroupContext} from './MenuItemGroup'; import MultipleAvatars from './MultipleAvatars'; import PressableWithSecondaryInteraction from './PressableWithSecondaryInteraction'; import RenderHTML from './RenderHTML'; @@ -303,6 +304,7 @@ function MenuItem( const {isSmallScreenWidth} = useWindowDimensions(); const [html, setHtml] = useState(''); const titleRef = useRef(''); + const {isExecuting, singleExecution, waitForNavigate} = useContext(MenuItemGroupContext) ?? {}; const isDeleted = style && Array.isArray(style) ? style.includes(styles.offlineFeedback.deleted) : false; const descriptionVerticalMargin = shouldShowDescriptionOnTop ? styles.mb1 : styles.mt1; @@ -378,7 +380,11 @@ function MenuItem( } if (onPress && event) { - onPress(event); + if (!singleExecution || !waitForNavigate) { + onPress(event); + return; + } + singleExecution(waitForNavigate(() => onPress(event)))(); } }; @@ -403,7 +409,7 @@ function MenuItem( ] as StyleProp } disabledStyle={shouldUseDefaultCursorWhenDisabled && [styles.cursorDefault]} - disabled={disabled} + disabled={disabled || isExecuting} ref={ref} role={CONST.ROLE.MENUITEM} accessibilityLabel={title ? title.toString() : ''} diff --git a/src/components/MenuItemGroup.tsx b/src/components/MenuItemGroup.tsx new file mode 100644 index 000000000000..8dc8586028d8 --- /dev/null +++ b/src/components/MenuItemGroup.tsx @@ -0,0 +1,35 @@ +import React, {createContext, useMemo} from 'react'; +import useSingleExecution from '@hooks/useSingleExecution'; +import type {Action} from '@hooks/useSingleExecution'; +import useWaitForNavigation from '@hooks/useWaitForNavigation'; + +type MenuItemGroupContextProps = { + isExecuting: boolean; + singleExecution: (action?: Action | undefined) => (...params: T) => void; + waitForNavigate: ReturnType; +}; + +const MenuItemGroupContext = createContext(undefined); + +type MenuItemGroupProps = { + /* Actual content wrapped by this component */ + children: React.ReactNode; + + /** Whether or not to use the single execution hook */ + shouldUseSingleExecution?: boolean; +}; + +function MenuItemGroup({children, shouldUseSingleExecution = true}: MenuItemGroupProps) { + const {isExecuting, singleExecution} = useSingleExecution(); + const waitForNavigate = useWaitForNavigation(); + + const value = useMemo( + () => (shouldUseSingleExecution ? {isExecuting, singleExecution, waitForNavigate} : undefined), + [shouldUseSingleExecution, isExecuting, singleExecution, waitForNavigate], + ); + + return {children}; +} + +export {MenuItemGroupContext}; +export default MenuItemGroup; diff --git a/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.js b/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.js index 41e86aa40d98..22b3af5be07c 100644 --- a/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.js +++ b/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.js @@ -5,6 +5,7 @@ import {ScrollView, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import MenuItemGroup from '@components/MenuItemGroup'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import {withNetwork} from '@components/OnyxProvider'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -77,25 +78,27 @@ function PersonalDetailsInitialPage(props) { {props.translate('privatePersonalDetails.privateDataMessage')} - Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_LEGAL_NAME)} - /> - Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_DATE_OF_BIRTH)} - titleStyle={[styles.flex1]} - /> - Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS)} - /> + + Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_LEGAL_NAME)} + /> + Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_DATE_OF_BIRTH)} + titleStyle={[styles.flex1]} + /> + Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS)} + /> + )}