From 667a0525154a0a4f01bb8c995d5c307d6aacbb75 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Thu, 16 Jan 2025 12:20:26 +0100 Subject: [PATCH] fix tooltipis on bottom tab bar --- .../Navigation/BottomTabBar/index.tsx | 8 +- .../Navigation/TopLevelBottomTabBar.tsx | 75 +++++++++++++++++++ .../TopLevelBottomTabBar.tsx | 42 ----------- .../createRootStackNavigator/index.tsx | 2 +- .../Navigation/helpers/isNavigatorName.ts | 9 ++- src/styles/index.ts | 8 +- 6 files changed, 93 insertions(+), 51 deletions(-) create mode 100644 src/components/Navigation/TopLevelBottomTabBar.tsx delete mode 100644 src/libs/Navigation/AppNavigator/createRootStackNavigator/TopLevelBottomTabBar.tsx diff --git a/src/components/Navigation/BottomTabBar/index.tsx b/src/components/Navigation/BottomTabBar/index.tsx index 7441161d0b8c..fc05f9d88454 100644 --- a/src/components/Navigation/BottomTabBar/index.tsx +++ b/src/components/Navigation/BottomTabBar/index.tsx @@ -11,7 +11,6 @@ import type {SearchQueryString} from '@components/Search/types'; import Text from '@components/Text'; import EducationalTooltip from '@components/Tooltip/EducationalTooltip'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; -import useBottomTabIsFocused from '@hooks/useBottomTabIsFocused'; import useCurrentReportID from '@hooks/useCurrentReportID'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -42,6 +41,7 @@ type BottomTabName = ValueOf; type BottomTabBarProps = { selectedTab: BottomTabName; + shouldShowTooltip: boolean | undefined; }; /** @@ -72,7 +72,7 @@ function handleQueryWithPolicyID(query: SearchQueryString, activePolicyID?: stri return SearchQueryUtils.buildSearchQueryString(queryJSON); } -function BottomTabBar({selectedTab}: BottomTabBarProps) { +function BottomTabBar({selectedTab, shouldShowTooltip = false}: BottomTabBarProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -89,12 +89,12 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) { const [chatTabBrickRoad, setChatTabBrickRoad] = useState(() => getChatTabBrickRoad(activeWorkspaceID, currentReportID, reports, betas, policies, priorityMode, transactionViolations), ); - const isFocused = useBottomTabIsFocused(); + 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, - selectedTab !== BOTTOM_TABS.HOME && isFocused, + shouldShowTooltip, ); useEffect(() => { setChatTabBrickRoad(getChatTabBrickRoad(activeWorkspaceID, currentReportID, reports, betas, policies, priorityMode, transactionViolations)); diff --git a/src/components/Navigation/TopLevelBottomTabBar.tsx b/src/components/Navigation/TopLevelBottomTabBar.tsx new file mode 100644 index 000000000000..0506f199458e --- /dev/null +++ b/src/components/Navigation/TopLevelBottomTabBar.tsx @@ -0,0 +1,75 @@ +import {findFocusedRoute, useNavigationState} from '@react-navigation/native'; +import React, {useEffect, useRef, useState} from 'react'; +import {InteractionManager, View} from 'react-native'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useStyledSafeAreaInsets from '@hooks/useStyledSafeAreaInsets'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {isFullScreenName, isSplitNavigatorName} from '@libs/Navigation/helpers/isNavigatorName'; +import {FULLSCREEN_TO_TAB, SIDEBAR_TO_SPLIT} from '@libs/Navigation/linkingConfig/RELATIONS'; +import type {FullScreenName} from '@libs/Navigation/types'; +import NAVIGATORS from '@src/NAVIGATORS'; +import SCREENS from '@src/SCREENS'; +import BottomTabBar, {BOTTOM_TABS} from './BottomTabBar'; + +const SCREENS_WITH_BOTTOM_TAB_BAR = [...Object.keys(SIDEBAR_TO_SPLIT), SCREENS.SEARCH.ROOT, SCREENS.SETTINGS.WORKSPACES]; + +/** + * TopLevelBottomTabBar is displayed when the user can interact with the bottom tab bar. + * We hide it when: + * 1. The bottom tab bar is not visible. + * 2. There is transition between screens with and without the bottom tab bar. + * 3. The bottom tab bar is under the overlay. + * For cases 2 and 3, local bottom tab bar mounted on the screen will be displayed. + */ + +function TopLevelBottomTabBar() { + const styles = useThemeStyles(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {paddingBottom} = useStyledSafeAreaInsets(); + const [isAfterClosingTransition, setIsAfterClosingTransition] = useState(false); + const cancelAfterInteractions = useRef | undefined>(); + + const selectedTab = useNavigationState((state) => { + const topmostFullScreenRoute = state?.routes.findLast((route) => isFullScreenName(route.name)); + return FULLSCREEN_TO_TAB[(topmostFullScreenRoute?.name as FullScreenName) ?? NAVIGATORS.REPORTS_SPLIT_NAVIGATOR]; + }); + + // There always should be a focused screen. + const isScreenWithBottomTabFocused = useNavigationState((state) => { + const focusedRoute = findFocusedRoute(state); + + // We are checking if the focused route is a split navigator because there may be a brief moment where the navigator don't have state yet. + // That mens we don't have screen with bottom tab focused. This caused glitching. + return SCREENS_WITH_BOTTOM_TAB_BAR.includes(focusedRoute?.name ?? '') || isSplitNavigatorName(focusedRoute?.name); + }); + + // Visible directly means not through the overlay. + const isScreenWithBottomTabVisibleDirectly = useNavigationState((state) => isFullScreenName(state.routes.at(-1)?.name)); + + const shouldDisplayTopLevelBottomTabBar = (shouldUseNarrowLayout ? isScreenWithBottomTabFocused : isScreenWithBottomTabVisibleDirectly) && selectedTab !== BOTTOM_TABS.HOME; + + useEffect(() => { + cancelAfterInteractions.current?.cancel(); + + if (!shouldDisplayTopLevelBottomTabBar) { + // If the bottom tab is not visible, that means there is a screen covering it. + // In that case we need to set the flag to true because there will be a transition for which we need to wait. + setIsAfterClosingTransition(false); + } else { + // If the bottom tab should be visible, we want to wait for transition to finish. + cancelAfterInteractions.current = InteractionManager.runAfterInteractions(() => { + setIsAfterClosingTransition(true); + }); + } + }, [shouldDisplayTopLevelBottomTabBar]); + + return ( + + + + ); +} +export default TopLevelBottomTabBar; diff --git a/src/libs/Navigation/AppNavigator/createRootStackNavigator/TopLevelBottomTabBar.tsx b/src/libs/Navigation/AppNavigator/createRootStackNavigator/TopLevelBottomTabBar.tsx deleted file mode 100644 index 61ea46715158..000000000000 --- a/src/libs/Navigation/AppNavigator/createRootStackNavigator/TopLevelBottomTabBar.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import {findFocusedRoute, useNavigationState} from '@react-navigation/native'; -import React from 'react'; -import {View} from 'react-native'; -import BottomTabBar from '@components/Navigation/BottomTabBar'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; -import useStyledSafeAreaInsets from '@hooks/useStyledSafeAreaInsets'; -import useThemeStyles from '@hooks/useThemeStyles'; -import {isFullScreenName} from '@libs/Navigation/helpers/isNavigatorName'; -import {FULLSCREEN_TO_TAB, SIDEBAR_TO_SPLIT} from '@libs/Navigation/linkingConfig/RELATIONS'; -import type {FullScreenName} from '@libs/Navigation/types'; -import NAVIGATORS from '@src/NAVIGATORS'; -import SCREENS from '@src/SCREENS'; - -const SCREENS_WITH_BOTTOM_TAB_BAR = [...Object.keys(SIDEBAR_TO_SPLIT), SCREENS.SEARCH.ROOT, SCREENS.SETTINGS.WORKSPACES]; - -/** - * Currently we are using the hybrid approach for the bottom tab bar. - * On wide screen we are using per screen bottom tab bar. It gives us more flexibility. We can display the bottom tab bar on any screen without any navigation structure constraints. - * On narrow layout we display the top level bottom tab bar. It allows us to implement proper animations between screens. - */ -function TopLevelBottomTabBar() { - const styles = useThemeStyles(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); - const topmostFullScreenRoute = useNavigationState((state) => state?.routes.findLast((route) => isFullScreenName(route.name))); - const {paddingBottom} = useStyledSafeAreaInsets(); - - // Home as fallback selected tab. - const selectedTab = FULLSCREEN_TO_TAB[(topmostFullScreenRoute?.name as FullScreenName) ?? NAVIGATORS.REPORTS_SPLIT_NAVIGATOR]; - - // There always should be a focused screen. - const isScreenWithBottomTabFocused = useNavigationState((state) => SCREENS_WITH_BOTTOM_TAB_BAR.includes(findFocusedRoute(state)?.name ?? '')); - - const shouldDisplayTopLevelBottomTabBar = isScreenWithBottomTabFocused && shouldUseNarrowLayout; - - return ( - - - - ); -} - -export default TopLevelBottomTabBar; diff --git a/src/libs/Navigation/AppNavigator/createRootStackNavigator/index.tsx b/src/libs/Navigation/AppNavigator/createRootStackNavigator/index.tsx index d7d0b0a88744..cfac8c92e059 100644 --- a/src/libs/Navigation/AppNavigator/createRootStackNavigator/index.tsx +++ b/src/libs/Navigation/AppNavigator/createRootStackNavigator/index.tsx @@ -1,12 +1,12 @@ import {createNavigatorFactory} from '@react-navigation/native'; import type {ParamListBase} from '@react-navigation/native'; +import TopLevelBottomTabBar from '@components/Navigation/TopLevelBottomTabBar'; import useNavigationResetOnLayoutChange from '@libs/Navigation/AppNavigator/useNavigationResetOnLayoutChange'; import {isFullScreenName} from '@libs/Navigation/helpers/isNavigatorName'; import createPlatformStackNavigatorComponent from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent'; import defaultPlatformStackScreenOptions from '@libs/Navigation/PlatformStackNavigation/defaultPlatformStackScreenOptions'; import type {CustomStateHookProps, PlatformStackNavigationEventMap, PlatformStackNavigationOptions, PlatformStackNavigationState} from '@libs/Navigation/PlatformStackNavigation/types'; import RootStackRouter from './RootStackRouter'; -import TopLevelBottomTabBar from './TopLevelBottomTabBar'; // This is an optimization to keep mounted only last few screens in the stack. function useCustomRootStackNavigatorState({state}: CustomStateHookProps) { diff --git a/src/libs/Navigation/helpers/isNavigatorName.ts b/src/libs/Navigation/helpers/isNavigatorName.ts index 062cdb581735..ceea8e5525d4 100644 --- a/src/libs/Navigation/helpers/isNavigatorName.ts +++ b/src/libs/Navigation/helpers/isNavigatorName.ts @@ -1,5 +1,5 @@ import {SIDEBAR_TO_SPLIT, SPLIT_TO_SIDEBAR} from '@libs/Navigation/linkingConfig/RELATIONS'; -import type {FullScreenName, OnboardingFlowName, SplitNavigatorSidebarScreen} from '@libs/Navigation/types'; +import type {FullScreenName, OnboardingFlowName, SplitNavigatorName, SplitNavigatorSidebarScreen} from '@libs/Navigation/types'; import SCREENS from '@src/SCREENS'; const ONBOARDING_SCREENS = [ @@ -15,6 +15,7 @@ const ONBOARDING_SCREENS = [ const FULL_SCREENS_SET = new Set([...Object.values(SIDEBAR_TO_SPLIT), SCREENS.SEARCH.ROOT]); const SIDEBARS_SET = new Set(Object.values(SPLIT_TO_SIDEBAR)); const ONBOARDING_SCREENS_SET = new Set(ONBOARDING_SCREENS); +const SPLIT_NAVIGATORS_SET = new Set(Object.values(SIDEBAR_TO_SPLIT)); /** * Functions defined below are used to check whether a screen belongs to a specific group. @@ -32,6 +33,10 @@ function isOnboardingFlowName(screen: string | undefined) { return checkIfScreenHasMatchingNameToSetValues(screen, ONBOARDING_SCREENS_SET); } +function isSplitNavigatorName(screen: string | undefined) { + return checkIfScreenHasMatchingNameToSetValues(screen, SPLIT_NAVIGATORS_SET); +} + function isFullScreenName(screen: string | undefined) { return checkIfScreenHasMatchingNameToSetValues(screen, FULL_SCREENS_SET); } @@ -40,4 +45,4 @@ function isSidebarScreenName(screen: string | undefined) { return checkIfScreenHasMatchingNameToSetValues(screen, SIDEBARS_SET); } -export {isFullScreenName, isOnboardingFlowName, isSidebarScreenName}; +export {isFullScreenName, isOnboardingFlowName, isSidebarScreenName, isSplitNavigatorName}; diff --git a/src/styles/index.ts b/src/styles/index.ts index fb407ac66960..dc8868333e93 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -562,11 +562,15 @@ const styles = (theme: ThemeColors) => borderRadius: variables.componentBorderRadiusLarge, }, - topLevelBottomTabBar: (shouldDisplayTopLevelBottomTabBar: boolean, bottomSafeAreaOffset: number) => ({ + topLevelBottomTabBar: (shouldDisplayTopLevelBottomTabBar: boolean, shouldUseNarrowLayout: boolean, bottomSafeAreaOffset: number) => ({ position: 'absolute', - width: '100%', + width: shouldUseNarrowLayout ? '100%' : variables.sideBarWidth, + transform: [{translateX: shouldUseNarrowLayout ? 0 : -variables.sideBarWidth}], paddingBottom: bottomSafeAreaOffset, bottom: shouldDisplayTopLevelBottomTabBar ? 0 : -(bottomSafeAreaOffset + variables.bottomTabHeight), + borderRightWidth: shouldUseNarrowLayout ? 0 : 1, + borderColor: theme.border, + backgroundColor: 'red', }), bottomTabBarContainer: {