diff --git a/src/components/FloatingActionButton.tsx b/src/components/FloatingActionButton.tsx index 8ba640956bf3..04bc4847a00f 100644 --- a/src/components/FloatingActionButton.tsx +++ b/src/components/FloatingActionButton.tsx @@ -7,6 +7,7 @@ import {useOnyx} from 'react-native-onyx'; import Animated, {createAnimatedPropAdapter, Easing, interpolateColor, processColor, useAnimatedProps, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import Svg, {Path} from 'react-native-svg'; import useBottomTabIsFocused from '@hooks/useBottomTabIsFocused'; +import useIsCurrentRouteHome from '@hooks/useIsCurrentRouteHome'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -69,9 +70,11 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo const isNarrowScreenOnWeb = shouldUseNarrowLayout && platform === CONST.PLATFORM.WEB; const isFocused = useBottomTabIsFocused(); const [isSidebarLoaded] = useOnyx(ONYXKEYS.IS_SIDEBAR_LOADED, {initialValue: false}); + const isActiveRouteHome = useIsCurrentRouteHome(); const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext( CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.GLOBAL_CREATE_TOOLTIP, - isFocused && isSidebarLoaded, + // On Home screen, We need to wait for the sidebar to load before showing the tooltip because there is the Concierge tooltip which is higher priority + isFocused && (!isActiveRouteHome || isSidebarLoaded), ); const sharedValue = useSharedValue(isActive ? 1 : 0); const buttonRef = ref; @@ -108,6 +111,7 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo ); const toggleFabAction = (event: GestureResponderEvent | KeyboardEvent | undefined) => { + hideProductTrainingTooltip(); // Drop focus to avoid blue focus ring. fabPressable.current?.blur(); onPress(event); @@ -120,11 +124,10 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo horizontal: isNarrowScreenOnWeb ? CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.CENTER : CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, }} - shouldUseOverlay shiftHorizontal={isNarrowScreenOnWeb ? 0 : variables.fabTooltipShiftHorizontal} renderTooltipContent={renderProductTrainingTooltip} wrapperStyle={styles.productTrainingTooltipWrapper} - onHideTooltip={hideProductTrainingTooltip} + shouldHideOnNavigate={false} > { diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index ead50d44634c..cf30aa8ff96a 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -16,6 +16,7 @@ import SubscriptAvatar from '@components/SubscriptAvatar'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; import EducationalTooltip from '@components/Tooltip/EducationalTooltip'; +import useIsCurrentRouteHome from '@hooks/useIsCurrentRouteHome'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -50,18 +51,22 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); const session = useSession(); - const isActiveWorkspaceChat = ReportUtils.isPolicyExpenseChat(report) && activePolicyID === report?.policyID && session?.accountID === report?.ownerAccountID; + const shouldShowWokspaceChatTooltip = ReportUtils.isPolicyExpenseChat(report) && activePolicyID === report?.policyID && session?.accountID === report?.ownerAccountID; const isOnboardingGuideAssigned = introSelected?.choice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM && !session?.email?.includes('+'); const shouldShowGetStartedTooltip = isOnboardingGuideAssigned ? ReportUtils.isAdminRoom(report) : ReportUtils.isConciergeChatReport(report); + const isActiveRouteHome = useIsCurrentRouteHome(); const {tooltipToRender, shouldShowTooltip} = useMemo(() => { const tooltip = shouldShowGetStartedTooltip ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCEIRGE_LHN_GBR : CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.LHN_WORKSPACE_CHAT_TOOLTIP; + const shouldShowTooltips = shouldShowWokspaceChatTooltip || shouldShowGetStartedTooltip; + const shouldTooltipBeVisible = shouldUseNarrowLayout ? isScreenFocused && isActiveRouteHome : isActiveRouteHome; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - return {tooltipToRender: tooltip, shouldShowTooltip: shouldUseNarrowLayout ? isScreenFocused : true}; - }, [shouldShowGetStartedTooltip, isScreenFocused, shouldUseNarrowLayout]); + return {tooltipToRender: tooltip, shouldShowTooltip: shouldShowTooltips && shouldTooltipBeVisible}; + }, [shouldShowGetStartedTooltip, shouldShowWokspaceChatTooltip, isScreenFocused, shouldUseNarrowLayout, isActiveRouteHome]); const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(tooltipToRender, shouldShowTooltip); + const {translate} = useLocalize(); const [isContextMenuActive, setIsContextMenuActive] = useState(false); @@ -159,16 +164,14 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti > @@ -183,6 +186,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti event?.preventDefault(); // Enable Composer to focus on clicking the same chat after opening the context menu. ReportActionComposeFocusManager.focus(); + hideProductTrainingTooltip(); onSelectRow(optionItem, popoverAnchor); }} onMouseDown={(event) => { diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index 59e7b78feeda..8ae468262cca 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -340,9 +340,6 @@ type MenuItemBaseProps = { /** Should selected item be marked with checkmark */ shouldShowSelectedItemCheck?: boolean; - /** Handles what to do when hiding the tooltip */ - onHideTooltip?: () => void; - /** Should use auto width for the icon container. */ shouldIconUseAutoWidthStyle?: boolean; @@ -351,6 +348,9 @@ type MenuItemBaseProps = { /** Pressable component Test ID. Used to locate the component in tests. */ pressableTestID?: string; + + /** Whether to teleport the portal to the modal layer */ + shouldTeleportPortalToModalLayer?: boolean; }; type MenuItemProps = (IconProps | AvatarProps | NoIcon) & MenuItemBaseProps; @@ -461,10 +461,10 @@ function MenuItem( renderTooltipContent, additionalIconStyles, shouldShowSelectedItemCheck = false, - onHideTooltip, shouldIconUseAutoWidthStyle = false, shouldBreakWord = false, pressableTestID, + shouldTeleportPortalToModalLayer, }: MenuItemProps, ref: PressableRef, ) { @@ -600,8 +600,7 @@ function MenuItem( wrapperStyle={tooltipWrapperStyle} shiftHorizontal={tooltipShiftHorizontal} shiftVertical={tooltipShiftVertical} - shouldAutoDismiss - onHideTooltip={onHideTooltip} + shouldTeleportPortalToModalLayer={shouldTeleportPortalToModalLayer} > diff --git a/src/components/ProductTrainingContext/TOOLTIPS.ts b/src/components/ProductTrainingContext/TOOLTIPS.ts index dc2a761a4903..1e0fa28e87e4 100644 --- a/src/components/ProductTrainingContext/TOOLTIPS.ts +++ b/src/components/ProductTrainingContext/TOOLTIPS.ts @@ -72,8 +72,9 @@ const TOOLTIPS: Record = { }, [WORKSAPCE_CHAT_CREATE]: { content: [ - {text: 'productTrainingTooltip.workspaceChatCreate.part1', isBold: true}, - {text: 'productTrainingTooltip.workspaceChatCreate.part2', isBold: false}, + {text: 'productTrainingTooltip.workspaceChatCreate.part1', isBold: false}, + {text: 'productTrainingTooltip.workspaceChatCreate.part2', isBold: true}, + {text: 'productTrainingTooltip.workspaceChatCreate.part3', isBold: false}, ], onHideTooltip: () => dismissProductTraining(WORKSAPCE_CHAT_CREATE), name: WORKSAPCE_CHAT_CREATE, diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index 5d964f5f5671..5e4069a0c43f 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -37,6 +37,10 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const [modal] = useOnyx(ONYXKEYS.MODAL); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const isModalVisible = modal?.isVisible || modal?.willAlertModalBecomeVisible; + const [activeTooltips, setActiveTooltips] = useState>(new Set()); const unregisterTooltip = useCallback( @@ -92,11 +96,16 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { return false; } + // We need to make an exception for the QAB tooltip because it is shown in a modal, otherwise it would be hidden if a modal is visible + if (tooltipName !== CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.QUICK_ACTION_BUTTON && isModalVisible) { + return false; + } + return tooltipConfig.shouldShow({ shouldUseNarrowLayout, }); }, - [dismissedProductTraining, hasBeenAddedToNudgeMigration, isOnboardingCompleted, isOnboardingCompletedMetadata, shouldUseNarrowLayout], + [dismissedProductTraining, hasBeenAddedToNudgeMigration, isOnboardingCompleted, isOnboardingCompletedMetadata, shouldUseNarrowLayout, isModalVisible], ); const registerTooltip = useCallback( @@ -156,11 +165,10 @@ const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shou useEffect(() => { if (shouldShow) { registerTooltip(tooltipName); - return () => { - unregisterTooltip(tooltipName); - }; } - return () => {}; + return () => { + unregisterTooltip(tooltipName); + }; }, [tooltipName, registerTooltip, unregisterTooltip, shouldShow]); const renderProductTrainingTooltip = useCallback(() => { @@ -209,10 +217,13 @@ const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shou }, [shouldRenderTooltip, tooltipName, shouldShow]); const hideProductTrainingTooltip = useCallback(() => { + if (!shouldShowProductTrainingTooltip) { + return; + } const tooltip = TOOLTIPS[tooltipName]; tooltip.onHideTooltip(); unregisterTooltip(tooltipName); - }, [tooltipName, unregisterTooltip]); + }, [tooltipName, shouldShowProductTrainingTooltip, unregisterTooltip]); return { renderProductTrainingTooltip, diff --git a/src/components/Search/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader.tsx index 21a5832052c0..fe7b49314ae0 100644 --- a/src/components/Search/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader.tsx @@ -1,5 +1,5 @@ -import {useIsFocused} from '@react-navigation/native'; -import React, {useMemo, useState} from 'react'; +import {useFocusEffect} from '@react-navigation/native'; +import React, {useCallback, useMemo, useState} from 'react'; import {InteractionManager, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; @@ -58,14 +58,25 @@ function SearchPageHeader({queryJSON}: SearchPageHeaderProps) { const [isDeleteExpensesConfirmModalVisible, setIsDeleteExpensesConfirmModalVisible] = useState(false); const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false); const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); - const isFocused = useIsFocused(); + + const [isScreenFocused, setIsScreenFocused] = useState(false); + const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext( CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SEARCH_FILTER_BUTTON_TOOLTIP, - isFocused, + isScreenFocused, ); const {status, hash} = queryJSON; + useFocusEffect( + useCallback(() => { + setIsScreenFocused(true); + return () => { + setIsScreenFocused(false); + }; + }, []), + ); + const selectedTransactionsKeys = Object.keys(selectedTransactions ?? {}); const handleDeleteExpenses = () => { @@ -334,6 +345,7 @@ function SearchPageHeader({queryJSON}: SearchPageHeaderProps) { } const onFiltersButtonPress = () => { + hideProductTrainingTooltip(); const filterFormValues = SearchQueryUtils.buildFilterFormValuesFromQuery(queryJSON, policyCategories, policyTagsLists, currencyList, personalDetails, cardList, reports, taxRates); SearchActions.updateAdvancedFilters(filterFormValues); @@ -362,11 +374,9 @@ function SearchPageHeader({queryJSON}: SearchPageHeaderProps) { vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, }} - shouldUseOverlay shiftHorizontal={variables.searchFiltersTooltipShiftHorizontal} wrapperStyle={styles.productTrainingTooltipWrapper} renderTooltipContent={renderProductTrainingTooltip} - onHideTooltip={hideProductTrainingTooltip} >