diff --git a/src/CONST.ts b/src/CONST.ts index 38af2f2d4f8a..672219eb1a7c 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -6992,6 +6992,9 @@ const CONST = { SCAN_TEST_TOOLTIP: 'scanTestTooltip', SCAN_TEST_TOOLTIP_MANAGER: 'scanTestTooltipManager', SCAN_TEST_CONFIRMATION: 'scanTestConfirmation', + OUTSANDING_FILTER: 'outstandingFilter', + SETTINGS_TAB: 'settingsTab', + WORKSPACES_SETTINGS: 'workspacesSettings', GBR_RBR_CHAT: 'chatGBRRBR', ACCOUNT_SWITCHER: 'accountSwitcher', EXPENSE_REPORTS_FILTER: 'expenseReportsFilter', diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index 61aac7d171ba..b5948b56fe79 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -350,6 +350,9 @@ type MenuItemBaseProps = { /** Callback to fire when the education tooltip is pressed */ onEducationTooltipPress?: () => void; + /** Whether the tooltip should hide on scroll */ + shouldHideOnScroll?: boolean; + shouldShowLoadingSpinnerIcon?: boolean; /** Should selected item be marked with checkmark */ @@ -474,6 +477,7 @@ function MenuItem( onBlur, avatarID, shouldRenderTooltip = false, + shouldHideOnScroll = false, tooltipAnchorAlignment, tooltipWrapperStyle = {}, tooltipShiftHorizontal = 0, @@ -639,6 +643,7 @@ function MenuItem( shiftVertical={tooltipShiftVertical} shouldTeleportPortalToModalLayer={shouldTeleportPortalToModalLayer} onTooltipPress={onEducationTooltipPress} + shouldHideOnScroll={shouldHideOnScroll} > diff --git a/src/components/Navigation/NavigationTabBar/index.tsx b/src/components/Navigation/NavigationTabBar/index.tsx index 84b1e644a5ff..91c4f8ea8c06 100644 --- a/src/components/Navigation/NavigationTabBar/index.tsx +++ b/src/components/Navigation/NavigationTabBar/index.tsx @@ -62,10 +62,11 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar const [chatTabBrickRoad, setChatTabBrickRoad] = useState(undefined); const platform = getPlatform(); const isWebOrDesktop = platform === CONST.PLATFORM.WEB || platform === CONST.PLATFORM.DESKTOP; - const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext( - CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.BOTTOM_NAV_INBOX_TOOLTIP, - isTooltipAllowed && selectedTab !== NAVIGATION_TABS.HOME, - ); + const { + renderProductTrainingTooltip: renderInboxTooltip, + shouldShowProductTrainingTooltip: shouldShowInboxTooltip, + hideProductTrainingTooltip: hideInboxTooltip, + } = useProductTrainingContext(CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.BOTTOM_NAV_INBOX_TOOLTIP, isTooltipAllowed && selectedTab !== NAVIGATION_TABS.HOME); const StyleUtils = useStyleUtils(); // On a wide layout DebugTabView should be rendered only within the navigation tab bar displayed directly on screens. @@ -82,9 +83,9 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar return; } - hideProductTrainingTooltip(); + hideInboxTooltip(); Navigation.navigate(ROUTES.HOME); - }, [hideProductTrainingTooltip, selectedTab]); + }, [hideInboxTooltip, selectedTab]); const navigateToSearch = useCallback(() => { if (selectedTab === NAVIGATION_TABS.SEARCH) { @@ -220,13 +221,13 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar /> @@ -314,13 +317,13 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar )} diff --git a/src/components/ProductTrainingContext/TOOLTIPS.ts b/src/components/ProductTrainingContext/TOOLTIPS.ts index 513888c957e9..29ac4ce6f4a3 100644 --- a/src/components/ProductTrainingContext/TOOLTIPS.ts +++ b/src/components/ProductTrainingContext/TOOLTIPS.ts @@ -12,6 +12,9 @@ const { SCAN_TEST_TOOLTIP, SCAN_TEST_TOOLTIP_MANAGER, SCAN_TEST_CONFIRMATION, + OUTSANDING_FILTER, + SETTINGS_TAB, + WORKSPACES_SETTINGS, GBR_RBR_CHAT, ACCOUNT_SWITCHER, EXPENSE_REPORTS_FILTER, @@ -160,6 +163,36 @@ const TOOLTIPS: Record = { priority: 1100, shouldShow: () => true, }, + [OUTSANDING_FILTER]: { + content: [ + {text: 'productTrainingTooltip.outstandingFilter.part1', isBold: false}, + {text: 'productTrainingTooltip.outstandingFilter.part2', isBold: true}, + ], + onHideTooltip: () => dismissProductTraining(OUTSANDING_FILTER), + name: OUTSANDING_FILTER, + priority: 1925, + shouldShow: ({isUserPolicyAdmin}) => isUserPolicyAdmin, + }, + [SETTINGS_TAB]: { + content: [ + {text: 'productTrainingTooltip.settingsTab.part1', isBold: false}, + {text: 'productTrainingTooltip.settingsTab.part2', isBold: true}, + ], + onHideTooltip: () => dismissProductTraining(SETTINGS_TAB), + name: SETTINGS_TAB, + priority: 1750, + shouldShow: ({isUserPolicyAdmin}) => isUserPolicyAdmin, + }, + [WORKSPACES_SETTINGS]: { + content: [ + {text: 'productTrainingTooltip.workspacesSettings.part1', isBold: false}, + {text: 'productTrainingTooltip.workspacesSettings.part2', isBold: true}, + ], + onHideTooltip: () => dismissProductTraining(WORKSPACES_SETTINGS), + name: WORKSPACES_SETTINGS, + priority: 1550, + shouldShow: ({isUserPolicyAdmin}) => isUserPolicyAdmin, + }, }; export default TOOLTIPS; diff --git a/src/components/Search/SearchPageHeader/SearchStatusBar.tsx b/src/components/Search/SearchPageHeader/SearchStatusBar.tsx index c5334dac2b35..60440901c7da 100644 --- a/src/components/Search/SearchPageHeader/SearchStatusBar.tsx +++ b/src/components/Search/SearchPageHeader/SearchStatusBar.tsx @@ -1,4 +1,5 @@ -import React, {useMemo, useRef} from 'react'; +import {useFocusEffect} from '@react-navigation/native'; +import React, {useCallback, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; // eslint-disable-next-line no-restricted-imports import type {ScrollView as RNScrollView, ViewStyle} from 'react-native'; @@ -7,13 +8,16 @@ import Button from '@components/Button'; import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; import * as Expensicons from '@components/Icon/Expensicons'; +import {useProductTrainingContext} from '@components/ProductTrainingContext'; import ScrollView from '@components/ScrollView'; import {useSearchContext} from '@components/Search/SearchContext'; import type {ChatSearchStatus, ExpenseSearchStatus, InvoiceSearchStatus, SearchGroupBy, SearchQueryJSON, TripSearchStatus} from '@components/Search/types'; import SearchStatusSkeleton from '@components/Skeletons/SearchStatusSkeleton'; +import EducationalTooltip from '@components/Tooltip/EducationalTooltip'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useScrollEventEmitter from '@hooks/useScrollEventEmitter'; import useSingleExecution from '@hooks/useSingleExecution'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; @@ -229,6 +233,27 @@ function SearchStatusBar({queryJSON, onStatusChange, headerButtonsOptions}: Sear const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, {canBeMissing: false}); const {isOffline} = useNetwork(); + const [isScreenFocused, setIsScreenFocused] = useState(false); + + useFocusEffect( + useCallback(() => { + setIsScreenFocused(true); + return () => { + setIsScreenFocused(false); + }; + }, []), + ); + const isOutstandingStatusActive = Array.isArray(queryJSON.status) + ? queryJSON.status.includes(CONST.SEARCH.STATUS.EXPENSE.OUTSTANDING) + : queryJSON.status === CONST.SEARCH.STATUS.EXPENSE.OUTSTANDING; + const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext( + CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.OUTSANDING_FILTER, + isScreenFocused && !isOutstandingStatusActive, + ); + // Controls the visibility of the educational tooltip based on user scrolling. + // Hides the tooltip when the user is scrolling and displays it once scrolling stops. + const triggerScrollEvent = useScrollEventEmitter(); + const selectedTransactionsKeys = useMemo(() => Object.keys(selectedTransactions ?? {}), [selectedTransactions]); const shouldShowSelectedDropdown = headerButtonsOptions.length > 0 && (!shouldUseNarrowLayout || (!!selectionMode && selectionMode.isEnabled)); @@ -279,9 +304,16 @@ function SearchStatusBar({queryJSON, onStatusChange, headerButtonsOptions}: Sear ref={scrollRef} horizontal showsHorizontalScrollIndicator={false} + onScroll={() => { + triggerScrollEvent(); + }} > {options.map((item, index) => { + const isOutstanding = item.status === CONST.SEARCH.STATUS.EXPENSE.OUTSTANDING; const onPress = singleExecution(() => { + if (isOutstanding) { + hideProductTrainingTooltip(); + } onStatusChange?.(); const query = buildSearchQueryString({...queryJSON, status: item.status}); Navigation.setParams({q: query}); @@ -291,27 +323,41 @@ function SearchStatusBar({queryJSON, onStatusChange, headerButtonsOptions}: Sear const isLastItem = index === options.length - 1; return ( -