diff options
Diffstat (limited to 'src/view/shell')
-rw-r--r-- | src/view/shell/Drawer.tsx | 22 | ||||
-rw-r--r-- | src/view/shell/bottom-bar/BottomBar.tsx | 52 | ||||
-rw-r--r-- | src/view/shell/createNativeStackNavigatorWithAuth.tsx | 48 | ||||
-rw-r--r-- | src/view/shell/desktop/LeftNav.tsx | 16 |
4 files changed, 93 insertions, 45 deletions
diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx index c4624e8e1..79d8a21ae 100644 --- a/src/view/shell/Drawer.tsx +++ b/src/view/shell/Drawer.tsx @@ -160,7 +160,7 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => { // = const onPressTab = React.useCallback( - (tab: string) => { + (tab: 'Home' | 'Search' | 'Messages' | 'Notifications' | 'MyProfile') => { const state = navigation.getState() setDrawerOpen(false) if (isWeb) { @@ -168,7 +168,7 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => { if (tab === 'MyProfile') { navigation.navigate('Profile', {name: currentAccount!.handle}) } else { - // @ts-ignore must be Home, Search, Notifications, or MyProfile + // @ts-expect-error struggles with string unions, apparently navigation.navigate(tab) } } else { @@ -176,9 +176,23 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => { if (tabState === TabState.InsideAtRoot) { emitSoftReset() } else if (tabState === TabState.Inside) { - navigation.dispatch(StackActions.popToTop()) + // find the correct navigator in which to pop-to-top + const target = state.routes.find(route => route.name === `${tab}Tab`) + ?.state?.key + if (target) { + // if we found it, trigger pop-to-top + navigation.dispatch({ + ...StackActions.popToTop(), + target, + }) + } else { + // fallback: reset navigation + navigation.reset({ + index: 0, + routes: [{name: `${tab}Tab`}], + }) + } } else { - // @ts-ignore must be Home, Search, Notifications, or MyProfile navigation.navigate(`${tab}Tab`) } } diff --git a/src/view/shell/bottom-bar/BottomBar.tsx b/src/view/shell/bottom-bar/BottomBar.tsx index 92be6c67e..5e9168ecd 100644 --- a/src/view/shell/bottom-bar/BottomBar.tsx +++ b/src/view/shell/bottom-bar/BottomBar.tsx @@ -1,4 +1,4 @@ -import React, {type ComponentProps} from 'react' +import {useCallback} from 'react' import {type GestureResponderEvent, View} from 'react-native' import Animated from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' @@ -52,13 +52,7 @@ import { import {useDemoMode} from '#/storage/hooks/demo-mode' import {styles} from './BottomBarStyles' -type TabOptions = - | 'Home' - | 'Search' - | 'Notifications' - | 'MyProfile' - | 'Feeds' - | 'Messages' +type TabOptions = 'Home' | 'Search' | 'Messages' | 'Notifications' | 'MyProfile' export function BottomBar({navigation}: BottomTabBarProps) { const {hasSession, currentAccount} = useSession() @@ -81,48 +75,62 @@ export function BottomBar({navigation}: BottomTabBarProps) { const gate = useGate() const iconWidth = 28 - const showSignIn = React.useCallback(() => { + const showSignIn = useCallback(() => { closeAllActiveElements() requestSwitchToAccount({requestedAccount: 'none'}) }, [requestSwitchToAccount, closeAllActiveElements]) - const showCreateAccount = React.useCallback(() => { + const showCreateAccount = useCallback(() => { closeAllActiveElements() requestSwitchToAccount({requestedAccount: 'new'}) // setShowLoggedOut(true) }, [requestSwitchToAccount, closeAllActiveElements]) - const onPressTab = React.useCallback( + const onPressTab = useCallback( (tab: TabOptions) => { const state = navigation.getState() const tabState = getTabState(state, tab) if (tabState === TabState.InsideAtRoot) { emitSoftReset() } else if (tabState === TabState.Inside) { - dedupe(() => navigation.dispatch(StackActions.popToTop())) + // find the correct navigator in which to pop-to-top + const target = state.routes.find(route => route.name === `${tab}Tab`) + ?.state?.key + dedupe(() => { + if (target) { + // if we found it, trigger pop-to-top + navigation.dispatch({ + ...StackActions.popToTop(), + target, + }) + } else { + // fallback: reset navigation + navigation.reset({ + index: 0, + routes: [{name: `${tab}Tab`}], + }) + } + }) } else { dedupe(() => navigation.navigate(`${tab}Tab`)) } }, [navigation, dedupe], ) - const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab]) - const onPressSearch = React.useCallback( - () => onPressTab('Search'), - [onPressTab], - ) - const onPressNotifications = React.useCallback( + const onPressHome = useCallback(() => onPressTab('Home'), [onPressTab]) + const onPressSearch = useCallback(() => onPressTab('Search'), [onPressTab]) + const onPressNotifications = useCallback( () => onPressTab('Notifications'), [onPressTab], ) - const onPressProfile = React.useCallback(() => { + const onPressProfile = useCallback(() => { onPressTab('MyProfile') }, [onPressTab]) - const onPressMessages = React.useCallback(() => { + const onPressMessages = useCallback(() => { onPressTab('Messages') }, [onPressTab]) - const onLongPressProfile = React.useCallback(() => { + const onLongPressProfile = useCallback(() => { playHaptic() accountSwitchControl.open() }, [accountSwitchControl, playHaptic]) @@ -361,7 +369,7 @@ export function BottomBar({navigation}: BottomTabBarProps) { interface BtnProps extends Pick< - ComponentProps<typeof PressableScale>, + React.ComponentProps<typeof PressableScale>, | 'accessible' | 'accessibilityRole' | 'accessibilityHint' diff --git a/src/view/shell/createNativeStackNavigatorWithAuth.tsx b/src/view/shell/createNativeStackNavigatorWithAuth.tsx index 868bba5b0..1c32971d4 100644 --- a/src/view/shell/createNativeStackNavigatorWithAuth.tsx +++ b/src/view/shell/createNativeStackNavigatorWithAuth.tsx @@ -1,25 +1,29 @@ import * as React from 'react' import {View} from 'react-native' -// Based on @react-navigation/native-stack/src/createNativeStackNavigator.ts +// Based on @react-navigation/native-stack/src/navigators/createNativeStackNavigator.ts // MIT License // Copyright (c) 2017 React Navigation Contributors import { createNavigatorFactory, type EventArg, + type NavigatorTypeBagBase, type ParamListBase, type StackActionHelpers, StackActions, type StackNavigationState, StackRouter, type StackRouterOptions, + type StaticConfig, + type TypedNavigator, useNavigationBuilder, } from '@react-navigation/native' +import {NativeStackView} from '@react-navigation/native-stack' import { type NativeStackNavigationEventMap, type NativeStackNavigationOptions, + type NativeStackNavigationProp, + type NativeStackNavigatorProps, } from '@react-navigation/native-stack' -import {NativeStackView} from '@react-navigation/native-stack' -import {type NativeStackNavigatorProps} from '@react-navigation/native-stack/src/types' import {PWI_ENABLED} from '#/lib/build-flags' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' @@ -48,12 +52,14 @@ function NativeStackNavigator({ id, initialRouteName, children, + layout, screenListeners, screenOptions, + screenLayout, ...rest }: NativeStackNavigatorProps) { // --- this is copy and pasted from the original native stack navigator --- - const {state, descriptors, navigation, NavigationContent} = + const {state, describe, descriptors, navigation, NavigationContent} = useNavigationBuilder< StackNavigationState<ParamListBase>, StackRouterOptions, @@ -64,9 +70,12 @@ function NativeStackNavigator({ id, initialRouteName, children, + layout, screenListeners, screenOptions, + screenLayout, }) + React.useEffect( () => // @ts-expect-error: there may not be a tab navigator in parent @@ -148,7 +157,8 @@ function NativeStackNavigator({ {...rest} state={state} navigation={navigation} - descriptors={newDescriptors} + descriptors={descriptors} + describe={describe} /> </View> {isWeb && ( @@ -161,9 +171,25 @@ function NativeStackNavigator({ ) } -export const createNativeStackNavigatorWithAuth = createNavigatorFactory< - StackNavigationState<ParamListBase>, - NativeStackNavigationOptionsWithAuth, - NativeStackNavigationEventMap, - typeof NativeStackNavigator ->(NativeStackNavigator) +export function createNativeStackNavigatorWithAuth< + const ParamList extends ParamListBase, + const NavigatorID extends string | undefined = undefined, + const TypeBag extends NavigatorTypeBagBase = { + ParamList: ParamList + NavigatorID: NavigatorID + State: StackNavigationState<ParamList> + ScreenOptions: NativeStackNavigationOptionsWithAuth + EventMap: NativeStackNavigationEventMap + NavigationList: { + [RouteName in keyof ParamList]: NativeStackNavigationProp< + ParamList, + RouteName, + NavigatorID + > + } + Navigator: typeof NativeStackNavigator + }, + const Config extends StaticConfig<TypeBag> = StaticConfig<TypeBag>, +>(config?: Config): TypedNavigator<TypeBag, Config> { + return createNavigatorFactory(NativeStackNavigator)(config) +} diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index f6c852ca1..52df66d70 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -1,10 +1,10 @@ -import React from 'react' +import {useCallback, useMemo, useState} from 'react' import {StyleSheet, View} from 'react-native' import {type AppBskyActorDefs} from '@atproto/api' import {msg, plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import { - useLinkProps, + useLinkTo, useNavigation, useNavigationState, } from '@react-navigation/native' @@ -326,7 +326,7 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) { const {_} = useLingui() const {currentAccount} = useSession() const {leftNavMinimal} = useLayoutBreakpoints() - const [pathName] = React.useMemo(() => router.matchPath(href), [href]) + const [pathName] = useMemo(() => router.matchPath(href), [href]) const currentRouteInfo = useNavigationState(state => { if (!state) { return {name: 'Home'} @@ -339,8 +339,8 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) { (currentRouteInfo.params as CommonNavigatorParams['Profile']).name === currentAccount?.handle : isTab(currentRouteInfo.name, pathName) - const {onPress} = useLinkProps({to: href}) - const onPressWrapped = React.useCallback( + const linkTo = useLinkTo() + const onPressWrapped = useCallback( (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => { if (e.ctrlKey || e.metaKey || e.altKey) { return @@ -349,10 +349,10 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) { if (isCurrent) { emitSoftReset() } else { - onPress() + linkTo(href) } }, - [onPress, isCurrent], + [linkTo, href, isCurrent], ) return ( @@ -468,7 +468,7 @@ function ComposeBtn() { const {openComposer} = useOpenComposer() const {_} = useLingui() const {leftNavMinimal} = useLayoutBreakpoints() - const [isFetchingHandle, setIsFetchingHandle] = React.useState(false) + const [isFetchingHandle, setIsFetchingHandle] = useState(false) const fetchHandle = useFetchHandle() const getProfileHandle = async () => { |