From 4ea145b74c76a3da4ea82792c704203cc5f422e7 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Sun, 23 Jun 2024 19:54:00 +0200 Subject: [PATCH 1/2] chore: tweak example bottom tab bar style --- example/src/Screens/BottomTabs.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/example/src/Screens/BottomTabs.tsx b/example/src/Screens/BottomTabs.tsx index 991e04e4aa..ab9b0571c8 100644 --- a/example/src/Screens/BottomTabs.tsx +++ b/example/src/Screens/BottomTabs.tsx @@ -184,10 +184,10 @@ export function BottomTabs() { tabBarIcon: getTabBarIcon('image-album'), tabBarInactiveTintColor: 'rgba(255, 255, 255, 0.5)', tabBarActiveTintColor: '#fff', - tabBarStyle: { - position: isLargeScreen ? undefined : 'absolute', - borderColor: 'rgba(0, 0, 0, .2)', - }, + tabBarStyle: [ + { borderWidth: 0 }, + isLargeScreen ? null : { position: 'absolute' }, + ], tabBarBackground: () => ( <> {isLargeScreen && ( From b68a81907947621c203d74e35f47588725664fd8 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Fri, 3 Feb 2023 19:59:34 +0100 Subject: [PATCH 2/2] refactor: drop unmountOnBlur in favor of popToTopOnBlur --- example/src/Screens/BottomTabs.tsx | 3 +- packages/bottom-tabs/src/types.tsx | 4 +- .../bottom-tabs/src/views/BottomTabView.tsx | 48 ++++++++++++++----- packages/drawer/src/types.tsx | 4 +- packages/drawer/src/views/DrawerView.tsx | 31 ++++++++++-- 5 files changed, 69 insertions(+), 21 deletions(-) diff --git a/example/src/Screens/BottomTabs.tsx b/example/src/Screens/BottomTabs.tsx index ab9b0571c8..20da48ec62 100644 --- a/example/src/Screens/BottomTabs.tsx +++ b/example/src/Screens/BottomTabs.tsx @@ -145,6 +145,7 @@ export function BottomTabs() { name="TabStack" component={SimpleStack} options={{ + popToTopOnBlur: true, title: 'Article', headerShown: false, tabBarIcon: getTabBarIcon('file-document'), @@ -154,7 +155,7 @@ export function BottomTabs() { name="TabChat" component={Chat} options={{ - tabBarLabel: 'Chat', + title: 'Chat', tabBarIcon: getTabBarIcon('message-reply'), tabBarBadge: 2, }} diff --git a/packages/bottom-tabs/src/types.tsx b/packages/bottom-tabs/src/types.tsx index 0d963f1f3c..8a5e4df5be 100644 --- a/packages/bottom-tabs/src/types.tsx +++ b/packages/bottom-tabs/src/types.tsx @@ -259,10 +259,10 @@ export type BottomTabNavigationOptions = HeaderOptions & { headerShown?: boolean; /** - * Whether this screen should be unmounted when navigating away from it. + * Whether any nested stack should be popped to top when navigating away from the tab. * Defaults to `false`. */ - unmountOnBlur?: boolean; + popToTopOnBlur?: boolean; /** * Whether inactive screens should be suspended from re-rendering. Defaults to `false`. diff --git a/packages/bottom-tabs/src/views/BottomTabView.tsx b/packages/bottom-tabs/src/views/BottomTabView.tsx index 3e1b710d9c..8bf542c23a 100644 --- a/packages/bottom-tabs/src/views/BottomTabView.tsx +++ b/packages/bottom-tabs/src/views/BottomTabView.tsx @@ -4,9 +4,11 @@ import { SafeAreaProviderCompat, Screen, } from '@react-navigation/elements'; -import type { - ParamListBase, - TabNavigationState, +import { + type NavigationAction, + type ParamListBase, + StackActions, + type TabNavigationState, } from '@react-navigation/native'; import * as React from 'react'; import { Animated, Platform, StyleSheet } from 'react-native'; @@ -95,7 +97,23 @@ export function BottomTabView(props: Props) { React.useEffect(() => { const previousRouteKey = previousRouteKeyRef.current; - previousRouteKeyRef.current = focusedRouteKey; + let popToTopAction: NavigationAction | undefined; + + if ( + previousRouteKey !== focusedRouteKey && + descriptors[previousRouteKey]?.options.popToTopOnBlur + ) { + const prevRoute = state.routes.find( + (route) => route.key === previousRouteKey + ); + + if (prevRoute?.state?.type === 'stack' && prevRoute.state.key) { + popToTopAction = { + ...StackActions.popToTop(), + target: prevRoute.state.key, + }; + } + } const animateToIndex = () => { Animated.parallel( @@ -131,11 +149,24 @@ export function BottomTabView(props: Props) { }); }) .filter(Boolean) as Animated.CompositeAnimation[] - ).start(); + ).start(({ finished }) => { + if (finished && popToTopAction) { + navigation.dispatch(popToTopAction); + } + }); }; animateToIndex(); - }, [descriptors, focusedRouteKey, state.index, state.routes, tabAnims]); + + previousRouteKeyRef.current = focusedRouteKey; + }, [ + descriptors, + focusedRouteKey, + navigation, + state.index, + state.routes, + tabAnims, + ]); const dimensions = SafeAreaProviderCompat.initialMetrics.frame; const [tabBarHeight, setTabBarHeight] = React.useState(() => @@ -205,17 +236,12 @@ export function BottomTabView(props: Props) { const descriptor = descriptors[route.key]; const { lazy = true, - unmountOnBlur, animation = 'none', sceneStyleInterpolator = NAMED_TRANSITIONS_PRESETS[animation] .sceneStyleInterpolator, } = descriptor.options; const isFocused = state.index === index; - if (unmountOnBlur && !isFocused) { - return null; - } - if ( lazy && !loaded.includes(route.key) && diff --git a/packages/drawer/src/types.tsx b/packages/drawer/src/types.tsx index 5210c28810..d1ae0d58bf 100644 --- a/packages/drawer/src/types.tsx +++ b/packages/drawer/src/types.tsx @@ -200,10 +200,10 @@ export type DrawerNavigationOptions = HeaderOptions & { keyboardDismissMode?: 'on-drag' | 'none'; /** - * Whether this screen should be unmounted when navigating away from it. + * Whether any nested stack should be popped to top when navigating away from the tab. * Defaults to `false`. */ - unmountOnBlur?: boolean; + popToTopOnBlur?: boolean; /** * Whether inactive screens should be suspended from re-rendering. Defaults to `false`. diff --git a/packages/drawer/src/views/DrawerView.tsx b/packages/drawer/src/views/DrawerView.tsx index e1df98395f..ce1a489326 100644 --- a/packages/drawer/src/views/DrawerView.tsx +++ b/packages/drawer/src/views/DrawerView.tsx @@ -10,6 +10,7 @@ import { type DrawerNavigationState, type DrawerStatus, type ParamListBase, + StackActions, useLocale, useTheme, } from '@react-navigation/native'; @@ -82,6 +83,30 @@ function DrawerViewBase({ setLoaded([...loaded, focusedRouteKey]); } + const previousRouteKeyRef = React.useRef(focusedRouteKey); + + React.useEffect(() => { + const previousRouteKey = previousRouteKeyRef.current; + + if ( + previousRouteKey !== focusedRouteKey && + descriptors[previousRouteKey]?.options.popToTopOnBlur + ) { + const prevRoute = state.routes.find( + (route) => route.key === previousRouteKey + ); + + if (prevRoute?.state?.type === 'stack' && prevRoute.state.key) { + navigation.dispatch({ + ...StackActions.popToTop(), + target: prevRoute.state.key, + }); + } + } + + previousRouteKeyRef.current = focusedRouteKey; + }, [descriptors, focusedRouteKey, navigation, state.routes]); + const dimensions = useSafeAreaFrame(); const { colors } = useTheme(); @@ -194,13 +219,9 @@ function DrawerViewBase({ > {state.routes.map((route, index) => { const descriptor = descriptors[route.key]; - const { lazy = true, unmountOnBlur } = descriptor.options; + const { lazy = true } = descriptor.options; const isFocused = state.index === index; - if (unmountOnBlur && !isFocused) { - return null; - } - if ( lazy && !loaded.includes(route.key) &&