From 86a01f18d58f3768f19c458c6a7873bd6b9a191a Mon Sep 17 00:00:00 2001 From: Hailey Date: Mon, 7 Jul 2025 15:56:59 -0700 Subject: [PATCH] Revert "Update react-navigation (#5967)" This reverts commit f93896346269b117556b13898eba9c162d6098b6. --- package.json | 10 +- src/Navigation.tsx | 104 +++++++------ src/components/Link.tsx | 34 ++--- src/lib/hooks/useNavigationDeduped.ts | 47 ++++-- src/lib/routes/types.ts | 2 +- src/view/com/util/Link.tsx | 67 ++++----- src/view/shell/Drawer.tsx | 22 +-- src/view/shell/bottom-bar/BottomBar.tsx | 52 +++---- .../createNativeStackNavigatorWithAuth.tsx | 48 ++---- src/view/shell/desktop/LeftNav.tsx | 16 +- yarn.lock | 138 ++++++++---------- 11 files changed, 256 insertions(+), 284 deletions(-) diff --git a/package.json b/package.json index d47eac126b..c95495242a 100644 --- a/package.json +++ b/package.json @@ -94,10 +94,10 @@ "@react-native-async-storage/async-storage": "2.1.2", "@react-native-menu/menu": "^1.2.3", "@react-native-picker/picker": "2.11.0", - "@react-navigation/bottom-tabs": "^7.3.13", - "@react-navigation/drawer": "^7.3.12", - "@react-navigation/native": "^7.1.9", - "@react-navigation/native-stack": "^7.3.13", + "@react-navigation/bottom-tabs": "^6.5.20", + "@react-navigation/drawer": "^6.6.15", + "@react-navigation/native": "^6.1.17", + "@react-navigation/native-stack": "^6.9.26", "@sentry/react-native": "~6.14.0", "@tanstack/query-async-storage-persister": "^5.25.0", "@tanstack/react-query": "^5.8.1", @@ -198,7 +198,7 @@ "react-native-reanimated": "~3.17.5", "react-native-root-siblings": "^5.0.1", "react-native-safe-area-context": "5.4.0", - "react-native-screens": "^4.11.1", + "react-native-screens": "~4.10.0", "react-native-svg": "15.12.0", "react-native-uitextview": "^1.4.0", "react-native-url-polyfill": "^1.3.0", diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 26a2b2a2ae..f0b337041e 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -103,7 +103,7 @@ import { import {Wizard} from '#/screens/StarterPack/Wizard' import TopicScreen from '#/screens/Topic' import {VideoFeed} from '#/screens/VideoFeed' -import {type Theme, useTheme} from '#/alf' +import {useTheme} from '#/alf' import { EmailDialogScreenID, useEmailDialogControl, @@ -140,7 +140,7 @@ const Tab = createBottomTabNavigator() /** * These "common screens" are reused across stacks. */ -function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) { +function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { const title = (page: MessageDescriptor) => bskyTitle(i18n._(page), unreadCountLabel) @@ -610,10 +610,6 @@ function TabsNavigator() { tabBar={tabBar}> HomeTabNavigator} /> SearchTabNavigator} /> - MessagesTabNavigator} - /> NotificationsTabNavigator} @@ -622,26 +618,29 @@ function TabsNavigator() { name="MyProfileTab" getComponent={() => MyProfileTabNavigator} /> + MessagesTabNavigator} + /> ) } -function screenOptions(t: Theme) { - return { - fullScreenGestureEnabled: true, - headerShown: false, - contentStyle: t.atoms.bg, - } as const -} - function HomeTabNavigator() { const t = useTheme() return ( - + HomeScreen} /> HomeScreen} /> - {commonScreens(HomeTab as typeof Flat)} + {commonScreens(HomeTab)} ) } @@ -650,10 +649,15 @@ function SearchTabNavigator() { const t = useTheme() return ( + screenOptions={{ + animationDuration: 285, + gestureEnabled: true, + fullScreenGestureEnabled: true, + headerShown: false, + contentStyle: t.atoms.bg, + }}> SearchScreen} /> - {commonScreens(SearchTab as typeof Flat)} + {commonScreens(SearchTab as typeof HomeTab)} ) } @@ -662,14 +666,19 @@ function NotificationsTabNavigator() { const t = useTheme() return ( + screenOptions={{ + animationDuration: 285, + gestureEnabled: true, + fullScreenGestureEnabled: true, + headerShown: false, + contentStyle: t.atoms.bg, + }}> NotificationsScreen} options={{requireAuth: true}} /> - {commonScreens(NotificationsTab as typeof Flat)} + {commonScreens(NotificationsTab as typeof HomeTab)} ) } @@ -678,16 +687,23 @@ function MyProfileTabNavigator() { const t = useTheme() return ( + screenOptions={{ + animationDuration: 285, + gestureEnabled: true, + fullScreenGestureEnabled: true, + headerShown: false, + contentStyle: t.atoms.bg, + }}> ProfileScreen} - initialParams={{name: 'me', hideBackButton: true}} + initialParams={{ + name: 'me', + hideBackButton: true, + }} /> - {commonScreens(MyProfileTab as unknown as typeof Flat)} + {commonScreens(MyProfileTab as typeof HomeTab)} ) } @@ -696,8 +712,13 @@ function MessagesTabNavigator() { const t = useTheme() return ( + screenOptions={{ + animationDuration: 285, + gestureEnabled: true, + fullScreenGestureEnabled: true, + headerShown: false, + contentStyle: t.atoms.bg, + }}> MessagesScreen} @@ -706,7 +727,7 @@ function MessagesTabNavigator() { animationTypeForReplace: route.params?.animation ?? 'push', })} /> - {commonScreens(MessagesTab as typeof Flat)} + {commonScreens(MessagesTab as typeof HomeTab)} ) } @@ -724,7 +745,13 @@ const FlatNavigator = () => { return ( + screenOptions={{ + animationDuration: 285, + gestureEnabled: true, + fullScreenGestureEnabled: true, + headerShown: false, + contentStyle: t.atoms.bg, + }}> HomeScreen} @@ -750,7 +777,7 @@ const FlatNavigator = () => { getComponent={() => HomeScreen} options={{title: title(msg`Home`)}} /> - {commonScreens(Flat, numUnread)} + {commonScreens(Flat as typeof HomeTab, numUnread)} ) } @@ -857,14 +884,7 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) { logModuleInitTime() onReady() logger.metric('router:navigate', {}, {statsig: false}) - }} - // WARNING: Implicit navigation to nested navigators is depreciated in React Navigation 7.x - // However, there's a fair amount of places we do that, especially in when popping to the top of stacks. - // See BottomBar.tsx for an example of how to handle nested navigators in the tabs correctly. - // I'm scared of missing a spot (esp. with push notifications etc) so let's enable this legacy behaviour for now. - // We will need to confirm we handle nested navigators correctly by the time we migrate to React Navigation 8.x - // -sfn - navigationInChildEnabled> + }}> {children} diff --git a/src/components/Link.tsx b/src/components/Link.tsx index 127b2edfb1..060bb26de1 100644 --- a/src/components/Link.tsx +++ b/src/components/Link.tsx @@ -1,10 +1,7 @@ -import React, {useMemo} from 'react' +import React from 'react' import {type GestureResponderEvent} from 'react-native' import {sanitizeUrl} from '@braintree/sanitize-url' -import { - type LinkProps as RNLinkProps, - StackActions, -} from '@react-navigation/native' +import {StackActions, useLinkProps} from '@react-navigation/native' import {BSKY_DOWNLOAD_URL} from '#/lib/constants' import {useNavigationDeduped} from '#/lib/hooks/useNavigationDeduped' @@ -32,11 +29,12 @@ import {useGlobalDialogsControlContext} from './dialogs/Context' */ export {useButtonContext as useLinkContext} from '#/components/Button' -type BaseLinkProps = { +type BaseLinkProps = Pick< + Parameters>[0], + 'to' +> & { testID?: string - to: RNLinkProps | string - /** * The React Navigation `StackAction` to perform when the link is pressed. */ @@ -95,22 +93,10 @@ export function useLink({ shouldProxy?: boolean }) { const navigation = useNavigationDeduped() - const href = useMemo(() => { - return typeof to === 'string' - ? convertBskyAppUrlIfNeeded(sanitizeUrl(to)) - : to.screen - ? router.matchName(to.screen)?.build(to.params) - : to.href - ? convertBskyAppUrlIfNeeded(sanitizeUrl(to.href)) - : undefined - }, [to]) - - if (!href) { - throw new Error( - 'Could not resolve screen. Link `to` prop must be a string or an object with `screen` and `params` properties', - ) - } - + const {href} = useLinkProps({ + to: + typeof to === 'string' ? convertBskyAppUrlIfNeeded(sanitizeUrl(to)) : to, + }) const isExternal = isExternalUrl(href) const {closeModal} = useModalControls() const {linkWarningDialogControl} = useGlobalDialogsControlContext() diff --git a/src/lib/hooks/useNavigationDeduped.ts b/src/lib/hooks/useNavigationDeduped.ts index 136e5fb961..e8e4d4e939 100644 --- a/src/lib/hooks/useNavigationDeduped.ts +++ b/src/lib/hooks/useNavigationDeduped.ts @@ -1,8 +1,10 @@ -import {useMemo} from 'react' +import React from 'react' import {useNavigation} from '@react-navigation/core' +import {type NavigationState} from '@react-navigation/native' +import {type NavigationAction} from '@react-navigation/routers' import {useDedupe} from '#/lib/hooks/useDedupe' -import {type NavigationProp} from '#/lib/routes/types' +import {type AllNavigatorParams, type NavigationProp} from '#/lib/routes/types' export type DebouncedNavigationProp = Pick< NavigationProp, @@ -21,19 +23,46 @@ export function useNavigationDeduped() { const navigation = useNavigation() const dedupe = useDedupe() - return useMemo( - () => ({ - push: (...args: Parameters) => { + return React.useMemo( + (): DebouncedNavigationProp => ({ + // Types from @react-navigation/routers/lib/typescript/src/StackRouter.ts + push: ( + ...args: undefined extends AllNavigatorParams[RouteName] + ? + | [screen: RouteName] + | [screen: RouteName, params: AllNavigatorParams[RouteName]] + : [screen: RouteName, params: AllNavigatorParams[RouteName]] + ) => { dedupe(() => navigation.push(...args)) }, - navigate: (...args: Parameters) => { + // Types from @react-navigation/core/src/types.tsx + navigate: ( + ...args: RouteName extends unknown + ? undefined extends AllNavigatorParams[RouteName] + ? + | [screen: RouteName] + | [screen: RouteName, params: AllNavigatorParams[RouteName]] + : [screen: RouteName, params: AllNavigatorParams[RouteName]] + : never + ) => { dedupe(() => navigation.navigate(...args)) }, - replace: (...args: Parameters) => { + // Types from @react-navigation/routers/lib/typescript/src/StackRouter.ts + replace: ( + ...args: undefined extends AllNavigatorParams[RouteName] + ? + | [screen: RouteName] + | [screen: RouteName, params: AllNavigatorParams[RouteName]] + : [screen: RouteName, params: AllNavigatorParams[RouteName]] + ) => { dedupe(() => navigation.replace(...args)) }, - dispatch: (...args: Parameters) => { - dedupe(() => navigation.dispatch(...args)) + dispatch: ( + action: + | NavigationAction + | ((state: NavigationState) => NavigationAction), + ) => { + dedupe(() => navigation.dispatch(action)) }, popToTop: () => { dedupe(() => navigation.popToTop()) diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index b1db5caa64..e522f7ad53 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -105,7 +105,7 @@ export type NotificationsTabNavigatorParams = CommonNavigatorParams & { } export type MyProfileTabNavigatorParams = CommonNavigatorParams & { - MyProfile: {name: 'me'; hideBackButton: true} + MyProfile: undefined } export type MessagesTabNavigatorParams = CommonNavigatorParams & { diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx index a9c12ba0e3..e2cc2d3f13 100644 --- a/src/view/com/util/Link.tsx +++ b/src/view/com/util/Link.tsx @@ -1,4 +1,4 @@ -import {memo, useCallback, useMemo} from 'react' +import React, {type ComponentProps, memo, useMemo} from 'react' import { type GestureResponderEvent, Platform, @@ -11,7 +11,7 @@ import { type ViewStyle, } from 'react-native' import {sanitizeUrl} from '@braintree/sanitize-url' -import {StackActions} from '@react-navigation/native' +import {StackActions, useLinkProps} from '@react-navigation/native' import { type DebouncedNavigationProp, @@ -39,7 +39,7 @@ type Event = | React.MouseEvent | GestureResponderEvent -interface Props extends React.ComponentProps { +interface Props extends ComponentProps { testID?: string style?: StyleProp href?: string @@ -48,7 +48,7 @@ interface Props extends React.ComponentProps { hoverStyle?: StyleProp noFeedback?: boolean asAnchor?: boolean - dataSet?: any + dataSet?: Object | undefined anchorNoUnderline?: boolean navigationAction?: 'push' | 'replace' | 'navigate' onPointerEnter?: () => void @@ -70,7 +70,6 @@ export const Link = memo(function Link({ onBeforePress, accessibilityActions, onAccessibilityAction, - dataSet: dataSetProp, ...props }: Props) { const t = useTheme() @@ -79,7 +78,7 @@ export const Link = memo(function Link({ const anchorHref = asAnchor ? sanitizeUrl(href) : undefined const openLink = useOpenLink() - const onPress = useCallback( + const onPress = React.useCallback( (e?: Event) => { onBeforePress?.() if (typeof href === 'string') { @@ -101,14 +100,6 @@ export const Link = memo(function Link({ {name: 'activate', label: title}, ] - const dataSet = useMemo(() => { - const ds = {...dataSetProp} - if (anchorNoUnderline) { - ds.noUnderline = 1 - } - return ds - }, [dataSetProp, anchorNoUnderline]) - if (noFeedback) { return ( @@ -139,6 +130,17 @@ export const Link = memo(function Link({ ) } + if (anchorNoUnderline) { + // @ts-ignore web only -prf + props.dataSet = props.dataSet || {} + // @ts-ignore web only -prf + props.dataSet.noUnderline = 1 + } + + if (title && !props.accessibilityLabel) { + props.accessibilityLabel = title + } + const Com = props.hoverStyle ? PressableWithHover : Pressable return ( {children ? children : {title || 'link'}} @@ -166,14 +165,14 @@ export const TextLink = memo(function TextLink({ text, numberOfLines, lineHeight, - dataSet: dataSetProp, + dataSet, title, - onPress: onPressProp, + onPress, onBeforePress, disableMismatchWarning, navigationAction, anchorNoUnderline, - ...props + ...orgProps }: { testID?: string type?: TypographyVariant @@ -189,6 +188,7 @@ export const TextLink = memo(function TextLink({ anchorNoUnderline?: boolean onBeforePress?: () => void } & TextProps) { + const {...props} = useLinkProps({to: sanitizeUrl(href)}) const navigation = useNavigationDeduped() const {closeModal} = useModalControls() const {linkWarningDialogControl} = useGlobalDialogsControlContext() @@ -198,15 +198,12 @@ export const TextLink = memo(function TextLink({ console.error('Unable to detect mismatching label') } - const dataSet = useMemo(() => { - const ds = {...dataSetProp} - if (anchorNoUnderline) { - ds.noUnderline = 1 - } - return ds - }, [dataSetProp, anchorNoUnderline]) + if (anchorNoUnderline) { + dataSet = dataSet ?? {} + dataSet.noUnderline = 1 + } - const onPress = useCallback( + props.onPress = React.useCallback( (e?: Event) => { const requiresWarning = !disableMismatchWarning && @@ -228,10 +225,10 @@ export const TextLink = memo(function TextLink({ return } onBeforePress?.() - if (onPressProp) { + if (onPress) { e?.preventDefault?.() - // @ts-expect-error function signature differs by platform -prf - return onPressProp() + // @ts-ignore function signature differs by platform -prf + return onPress() } return onPressInner( closeModal, @@ -244,7 +241,7 @@ export const TextLink = memo(function TextLink({ }, [ onBeforePress, - onPressProp, + onPress, closeModal, navigation, href, @@ -277,10 +274,8 @@ export const TextLink = memo(function TextLink({ title={title} // @ts-ignore web only -prf hrefAttrs={hrefAttrs} // hack to get open in new tab to work on safari. without this, safari will open in a new window - onPress={onPress} - accessibilityRole="link" - href={convertBskyAppUrlIfNeeded(sanitizeUrl(href))} - {...props}> + {...props} + {...orgProps}> {text} ) diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx index 79d8a21aef..c4624e8e19 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: 'Home' | 'Search' | 'Messages' | 'Notifications' | 'MyProfile') => { + (tab: string) => { 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-expect-error struggles with string unions, apparently + // @ts-ignore must be Home, Search, Notifications, or MyProfile navigation.navigate(tab) } } else { @@ -176,23 +176,9 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => { if (tabState === TabState.InsideAtRoot) { emitSoftReset() } else if (tabState === TabState.Inside) { - // 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`}], - }) - } + navigation.dispatch(StackActions.popToTop()) } 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 01aa4afc42..667816635a 100644 --- a/src/view/shell/bottom-bar/BottomBar.tsx +++ b/src/view/shell/bottom-bar/BottomBar.tsx @@ -1,4 +1,4 @@ -import {useCallback} from 'react' +import React, {type ComponentProps} from 'react' import {type GestureResponderEvent, View} from 'react-native' import Animated from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' @@ -53,7 +53,13 @@ import { import {useDemoMode} from '#/storage/hooks/demo-mode' import {styles} from './BottomBarStyles' -type TabOptions = 'Home' | 'Search' | 'Messages' | 'Notifications' | 'MyProfile' +type TabOptions = + | 'Home' + | 'Search' + | 'Notifications' + | 'MyProfile' + | 'Feeds' + | 'Messages' export function BottomBar({navigation}: BottomTabBarProps) { const {hasSession, currentAccount} = useSession() @@ -77,62 +83,48 @@ export function BottomBar({navigation}: BottomTabBarProps) { const hideBorder = useHideBottomBarBorder() const iconWidth = 28 - const showSignIn = useCallback(() => { + const showSignIn = React.useCallback(() => { closeAllActiveElements() requestSwitchToAccount({requestedAccount: 'none'}) }, [requestSwitchToAccount, closeAllActiveElements]) - const showCreateAccount = useCallback(() => { + const showCreateAccount = React.useCallback(() => { closeAllActiveElements() requestSwitchToAccount({requestedAccount: 'new'}) // setShowLoggedOut(true) }, [requestSwitchToAccount, closeAllActiveElements]) - const onPressTab = useCallback( + const onPressTab = React.useCallback( (tab: TabOptions) => { const state = navigation.getState() const tabState = getTabState(state, tab) if (tabState === TabState.InsideAtRoot) { emitSoftReset() } else if (tabState === TabState.Inside) { - // 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`}], - }) - } - }) + dedupe(() => navigation.dispatch(StackActions.popToTop())) } else { dedupe(() => navigation.navigate(`${tab}Tab`)) } }, [navigation, dedupe], ) - const onPressHome = useCallback(() => onPressTab('Home'), [onPressTab]) - const onPressSearch = useCallback(() => onPressTab('Search'), [onPressTab]) - const onPressNotifications = useCallback( + const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab]) + const onPressSearch = React.useCallback( + () => onPressTab('Search'), + [onPressTab], + ) + const onPressNotifications = React.useCallback( () => onPressTab('Notifications'), [onPressTab], ) - const onPressProfile = useCallback(() => { + const onPressProfile = React.useCallback(() => { onPressTab('MyProfile') }, [onPressTab]) - const onPressMessages = useCallback(() => { + const onPressMessages = React.useCallback(() => { onPressTab('Messages') }, [onPressTab]) - const onLongPressProfile = useCallback(() => { + const onLongPressProfile = React.useCallback(() => { playHaptic() accountSwitchControl.open() }, [accountSwitchControl, playHaptic]) @@ -371,7 +363,7 @@ export function BottomBar({navigation}: BottomTabBarProps) { interface BtnProps extends Pick< - React.ComponentProps, + ComponentProps, | 'accessible' | 'accessibilityRole' | 'accessibilityHint' diff --git a/src/view/shell/createNativeStackNavigatorWithAuth.tsx b/src/view/shell/createNativeStackNavigatorWithAuth.tsx index 1c32971d42..868bba5b04 100644 --- a/src/view/shell/createNativeStackNavigatorWithAuth.tsx +++ b/src/view/shell/createNativeStackNavigatorWithAuth.tsx @@ -1,29 +1,25 @@ import * as React from 'react' import {View} from 'react-native' -// Based on @react-navigation/native-stack/src/navigators/createNativeStackNavigator.ts +// Based on @react-navigation/native-stack/src/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' @@ -52,14 +48,12 @@ function NativeStackNavigator({ id, initialRouteName, children, - layout, screenListeners, screenOptions, - screenLayout, ...rest }: NativeStackNavigatorProps) { // --- this is copy and pasted from the original native stack navigator --- - const {state, describe, descriptors, navigation, NavigationContent} = + const {state, descriptors, navigation, NavigationContent} = useNavigationBuilder< StackNavigationState, StackRouterOptions, @@ -70,12 +64,9 @@ function NativeStackNavigator({ id, initialRouteName, children, - layout, screenListeners, screenOptions, - screenLayout, }) - React.useEffect( () => // @ts-expect-error: there may not be a tab navigator in parent @@ -157,8 +148,7 @@ function NativeStackNavigator({ {...rest} state={state} navigation={navigation} - descriptors={descriptors} - describe={describe} + descriptors={newDescriptors} /> {isWeb && ( @@ -171,25 +161,9 @@ function NativeStackNavigator({ ) } -export function createNativeStackNavigatorWithAuth< - const ParamList extends ParamListBase, - const NavigatorID extends string | undefined = undefined, - const TypeBag extends NavigatorTypeBagBase = { - ParamList: ParamList - NavigatorID: NavigatorID - State: StackNavigationState - ScreenOptions: NativeStackNavigationOptionsWithAuth - EventMap: NativeStackNavigationEventMap - NavigationList: { - [RouteName in keyof ParamList]: NativeStackNavigationProp< - ParamList, - RouteName, - NavigatorID - > - } - Navigator: typeof NativeStackNavigator - }, - const Config extends StaticConfig = StaticConfig, ->(config?: Config): TypedNavigator { - return createNavigatorFactory(NativeStackNavigator)(config) -} +export const createNativeStackNavigatorWithAuth = createNavigatorFactory< + StackNavigationState, + NativeStackNavigationOptionsWithAuth, + NativeStackNavigationEventMap, + typeof NativeStackNavigator +>(NativeStackNavigator) diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index 52df66d709..f6c852ca12 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -1,10 +1,10 @@ -import {useCallback, useMemo, useState} from 'react' +import React 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 { - useLinkTo, + useLinkProps, 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] = useMemo(() => router.matchPath(href), [href]) + const [pathName] = React.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 linkTo = useLinkTo() - const onPressWrapped = useCallback( + const {onPress} = useLinkProps({to: href}) + const onPressWrapped = React.useCallback( (e: React.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 { - linkTo(href) + onPress() } }, - [linkTo, href, isCurrent], + [onPress, isCurrent], ) return ( @@ -468,7 +468,7 @@ function ComposeBtn() { const {openComposer} = useOpenComposer() const {_} = useLingui() const {leftNavMinimal} = useLayoutBreakpoints() - const [isFetchingHandle, setIsFetchingHandle] = useState(false) + const [isFetchingHandle, setIsFetchingHandle] = React.useState(false) const fetchHandle = useFetchHandle() const getProfileHandle = async () => { diff --git a/yarn.lock b/yarn.lock index 79cc5e0570..2b68d83358 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6295,69 +6295,65 @@ invariant "^2.2.4" nullthrows "^1.1.1" -"@react-navigation/bottom-tabs@^7.3.13": - version "7.3.14" - resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-7.3.14.tgz#9ee02baea86ab24abe267726665bc69c6df0bf4c" - integrity sha512-s2qinJggS2HYZdCOey9A+fN+bNpWeEKwiL/FjAVOTcv+uofxPWN6CtEZUZGPEjfRjis/srURBmCmpNZSI6sQ9Q== +"@react-navigation/bottom-tabs@^6.5.20": + version "6.5.20" + resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-6.5.20.tgz#5335e75b02c527ef0569bd97d4f9185d65616e49" + integrity sha512-ow6Z06iS4VqBO8d7FP+HsGjJLWt2xTWIvuWjpoCvsM/uQXzCRDIjBv9HaKcXbF0yTW7IMir0oDAbU5PFzEDdgA== dependencies: - "@react-navigation/elements" "^2.4.3" + "@react-navigation/elements" "^1.3.30" color "^4.2.3" + warn-once "^0.1.0" -"@react-navigation/core@^7.10.0": - version "7.10.0" - resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-7.10.0.tgz#8205ea6b84ce34b2fc2c196701b4cd9b434211b9" - integrity sha512-qZBA5gGm+9liT4+EHk+kl9apwvqh7HqhLF1XeX6SQRmC/n2QI0u1B8OevKc+EPUDEM9Od15IuwT/GRbSs7/Umw== +"@react-navigation/core@^6.4.16": + version "6.4.16" + resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-6.4.16.tgz#f9369a134805174536b9aa0f0f483b930511caf9" + integrity sha512-UDTJBsHxnzgFETR3ZxhctP+RWr4SkyeZpbhpkQoIGOuwSCkt1SE0qjU48/u6r6w6XlX8OqVudn1Ab0QFXTHxuQ== dependencies: - "@react-navigation/routers" "^7.4.0" + "@react-navigation/routers" "^6.1.9" escape-string-regexp "^4.0.0" - nanoid "^3.3.11" + nanoid "^3.1.23" query-string "^7.1.3" - react-is "^19.1.0" - use-latest-callback "^0.2.3" - use-sync-external-store "^1.5.0" + react-is "^16.13.0" + use-latest-callback "^0.1.9" -"@react-navigation/drawer@^7.3.12": - version "7.4.1" - resolved "https://registry.yarnpkg.com/@react-navigation/drawer/-/drawer-7.4.1.tgz#50517d8c57f09cdbfc20a485c47016066b918e76" - integrity sha512-kj5wL31smDLw/6l+0KPR5cjaOZg6oHJCl3RPQonFPuYolUPZBVnuS++uvlifWcD/mqdGmhl3rgLTircRH4vQ7Q== +"@react-navigation/drawer@^6.6.15": + version "6.6.15" + resolved "https://registry.yarnpkg.com/@react-navigation/drawer/-/drawer-6.6.15.tgz#fcedba68f735103dbc035911f5959ce926081d62" + integrity sha512-GLkFQNxjtmxB/qXSHmu1DfoB89jCzW64tmX68iPndth+9U+0IP27GcCCaMZxQfwj+nI8Kn2zlTlXAZDIIHE+DQ== dependencies: - "@react-navigation/elements" "^2.4.3" + "@react-navigation/elements" "^1.3.30" color "^4.2.3" - react-native-drawer-layout "^4.1.10" - use-latest-callback "^0.2.3" + warn-once "^0.1.0" -"@react-navigation/elements@^2.4.3": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-2.4.3.tgz#cc1dde4c98739d35a0c9c23872316063962cfaee" - integrity sha512-psoNmnZ0DQIt9nxxPITVLtYW04PGCAfnmd/Pcd3yhiBs93aj+HYKH+SDZDpUnXMf3BN7Wvo4+jPI+/Xjqb+m9w== - dependencies: - color "^4.2.3" +"@react-navigation/elements@^1.3.30": + version "1.3.30" + resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-1.3.30.tgz#a81371f599af1070b12014f05d6c09b1a611fd9a" + integrity sha512-plhc8UvCZs0UkV+sI+3bisIyn78wz9O/BiWZXpounu72k/R/Sj5PuZYFJ1fi6psvriUveMCGh4LeZckAZu2qiQ== -"@react-navigation/native-stack@^7.3.13": - version "7.3.14" - resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-7.3.14.tgz#d1c90f2e50cd13bbced923991cf2faee8083f725" - integrity sha512-45Sf7ReqSCIySXS5nrKtLGmNlFXm5x+u32YQMwKDONCqVGOBCfo4ryKqeQq1EMJ7Py6IDyOwHMhA+jhNOxnfPw== +"@react-navigation/native-stack@^6.9.26": + version "6.9.26" + resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-6.9.26.tgz#90facf7783c9927f094bc9f01c613af75b6c241e" + integrity sha512-++dueQ+FDj2XkZ902DVrK79ub1vp19nSdAZWxKRgd6+Bc0Niiesua6rMCqymYOVaYh+dagwkA9r00bpt/U5WLw== dependencies: - "@react-navigation/elements" "^2.4.3" - warn-once "^0.1.1" + "@react-navigation/elements" "^1.3.30" + warn-once "^0.1.0" -"@react-navigation/native@^7.1.9": - version "7.1.10" - resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-7.1.10.tgz#768f674f7c09b6a57215762052aa62a7dc107402" - integrity sha512-Ug4IML0DkAxZTMF/E7lyyLXSclkGAYElY2cxZWITwfBjtlVeda0NjsdnTWY5EGjnd7bwvhTIUC+CO6qSlrDn5A== +"@react-navigation/native@^6.1.17": + version "6.1.17" + resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-6.1.17.tgz#439f15a99809d26ea4682d2a3766081cf2ca31cf" + integrity sha512-mer3OvfwWOHoUSMJyLa4vnBH3zpFmCwuzrBPlw7feXklurr/ZDiLjLxUScOot6jLRMz/67GyilEYMmP99LL0RQ== dependencies: - "@react-navigation/core" "^7.10.0" + "@react-navigation/core" "^6.4.16" escape-string-regexp "^4.0.0" fast-deep-equal "^3.1.3" - nanoid "^3.3.11" - use-latest-callback "^0.2.3" + nanoid "^3.1.23" -"@react-navigation/routers@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@react-navigation/routers/-/routers-7.4.0.tgz#5bace799713ac163310c18711b98dfbe418c6b36" - integrity sha512-th5THnuWKJlmr7GGHiicy979di11ycDWub9iIXbEDvQwmwmsRzppmVbfs2nD8bC/MgyMgqWu/gxfys+HqN+kcw== +"@react-navigation/routers@^6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@react-navigation/routers/-/routers-6.1.9.tgz#73f5481a15a38e36592a0afa13c3c064b9f90bed" + integrity sha512-lTM8gSFHSfkJvQkxacGM6VJtBt61ip2XO54aNfswD+KMw6eeZ4oehl7m0me3CR9hnDE4+60iAZR8sAhvCiI3NA== dependencies: - nanoid "^3.3.11" + nanoid "^3.1.23" "@remirror/core-constants@3.0.0": version "3.0.0" @@ -14984,16 +14980,11 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nanoid@^3.3.1, nanoid@^3.3.6: +nanoid@^3.1.23, nanoid@^3.3.1, nanoid@^3.3.6: version "3.3.6" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== -nanoid@^3.3.11: - version "3.3.11" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" - integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== - nanoid@^3.3.7: version "3.3.7" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" @@ -16754,12 +16745,12 @@ react-image-crop@^11.0.7: resolved "https://registry.yarnpkg.com/react-image-crop/-/react-image-crop-11.0.7.tgz#25f3d37ccbb65a05d19d23b4740a5912835c741e" integrity sha512-ZciKWHDYzmm366JDL18CbrVyjnjH0ojufGDmScfS4ZUqLHg4nm6ATY+K62C75W4ZRNt4Ii+tX0bSjNk9LQ2xzQ== -react-is@19, react-is@^19.0.0, react-is@^19.1.0: +react-is@19, react-is@^19.0.0: version "19.1.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.1.0.tgz#805bce321546b7e14c084989c77022351bbdd11b" integrity sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg== -react-is@^16.13.1, react-is@^16.7.0: +react-is@^16.13.0, react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -16791,12 +16782,12 @@ react-native-dotenv@^3.4.11: dependencies: dotenv "^16.4.5" -react-native-drawer-layout@^4.1.10, react-native-drawer-layout@^4.1.8: - version "4.1.10" - resolved "https://registry.yarnpkg.com/react-native-drawer-layout/-/react-native-drawer-layout-4.1.10.tgz#9007cb747767ca8e1c9c3337671ad35ed95ad4d9" - integrity sha512-wejQo0F+EffCkOkRh+DP6ENWMB+aWEHkXV8Pd564PmtoySZLUsV/ksYrh/mrufh7T7EuvGT8+fNHz7mMRYftWg== +react-native-drawer-layout@^4.1.8: + version "4.1.12" + resolved "https://registry.yarnpkg.com/react-native-drawer-layout/-/react-native-drawer-layout-4.1.12.tgz#0cdfe2d2ca9288cb099f1e1808c8addf2fad25de" + integrity sha512-oKolvp5seiUieG+RHGjpFe8rH8Ds24iW0QBl31TlCVOX7tdn42IQIBl5tuD1i7h3q+VqqnbcT+NB2dcJ5suZkw== dependencies: - use-latest-callback "^0.2.3" + use-latest-callback "^0.2.4" react-native-edge-to-edge@1.6.0, react-native-edge-to-edge@^1.6.0: version "1.6.0" @@ -16833,7 +16824,7 @@ react-native-ios-context-menu@^1.15.3: dependencies: "@dominicstop/ts-event-emitter" "^1.1.0" -react-native-is-edge-to-edge@1.1.7, react-native-is-edge-to-edge@^1.1.7: +react-native-is-edge-to-edge@1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.7.tgz#28947688f9fafd584e73a4f935ea9603bd9b1939" integrity sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w== @@ -16902,13 +16893,12 @@ react-native-safe-area-context@5.4.0: resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-5.4.0.tgz#04b51940408c114f75628a12a93569d30c525454" integrity sha512-JaEThVyJcLhA+vU0NU8bZ0a1ih6GiF4faZ+ArZLqpYbL6j7R3caRqj+mE3lEtKCuHgwjLg3bCxLL1GPUJZVqUA== -react-native-screens@^4.11.1: - version "4.11.1" - resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-4.11.1.tgz#7d0f3d313d8ddc1e55437c5e038f15f8805dc991" - integrity sha512-F0zOzRVa3ptZfLpD0J8ROdo+y1fEPw+VBFq1MTY/iyDu08al7qFUO5hLMd+EYMda5VXGaTFCa8q7bOppUszhJw== +react-native-screens@~4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-4.10.0.tgz#40634aead590c6b7034ded6a9f92465d1d611906" + integrity sha512-Tw21NGuXm3PbiUGtZd0AnXirUixaAbPXDjNR0baBH7/WJDaDTTELLcQ7QRXuqAWbmr/EVCrKj1348ei1KFIr8A== dependencies: react-freeze "^1.0.0" - react-native-is-edge-to-edge "^1.1.7" warn-once "^0.1.0" react-native-svg@15.12.0: @@ -19401,10 +19391,15 @@ use-isomorphic-layout-effect@^1.1.1: resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== -use-latest-callback@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.2.3.tgz#2d644d3063040b9bc2d4c55bb525a13ae3de9e16" - integrity sha512-7vI3fBuyRcP91pazVboc4qu+6ZqM8izPWX9k7cRnT8hbD5svslcknsh3S9BUhaK11OmgTV4oWZZVSeQAiV53SQ== +use-latest-callback@^0.1.9: + version "0.1.9" + resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.1.9.tgz#10191dc54257e65a8e52322127643a8940271e2a" + integrity sha512-CL/29uS74AwreI/f2oz2hLTW7ZqVeV5+gxFeGudzQrgkCytrHw33G4KbnQOrRlAEzzAFXi7dDLMC9zhWcVpzmw== + +use-latest-callback@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.2.4.tgz#35c0f028f85a3f4cf025b06011110e87cc18f57e" + integrity sha512-LS2s2n1usUUnDq4oVh1ca6JFX9uSqUncTfAm44WMg0v6TxL7POUTk1B044NH8TeLkFbNajIsgDHcgNpNzZucdg== use-latest@^1.2.1: version "1.2.1" @@ -19434,11 +19429,6 @@ use-sync-external-store@^1.2.2: resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9" integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw== -use-sync-external-store@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0" - integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A== - util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -19540,7 +19530,7 @@ walker@^1.0.7, walker@^1.0.8: dependencies: makeerror "1.0.12" -warn-once@0.1.1, warn-once@^0.1.0, warn-once@^0.1.1: +warn-once@0.1.1, warn-once@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/warn-once/-/warn-once-0.1.1.tgz#952088f4fb56896e73fd4e6a3767272a3fccce43" integrity sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==