Update react-navigation (#5967)

This commit is contained in:
dan
2025-06-09 20:29:53 +01:00
committed by GitHub
parent 42c4da1ec7
commit f938963462
11 changed files with 293 additions and 256 deletions
+42 -62
View File
@@ -103,7 +103,7 @@ import {
import {Wizard} from '#/screens/StarterPack/Wizard'
import TopicScreen from '#/screens/Topic'
import {VideoFeed} from '#/screens/VideoFeed'
import {useTheme} from '#/alf'
import {type Theme, useTheme} from '#/alf'
import {
EmailDialogScreenID,
useEmailDialogControl,
@@ -127,7 +127,7 @@ const Tab = createBottomTabNavigator<BottomTabNavigatorParams>()
/**
* These "common screens" are reused across stacks.
*/
function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
const title = (page: MessageDescriptor) =>
bskyTitle(i18n._(page), unreadCountLabel)
@@ -499,6 +499,10 @@ function TabsNavigator() {
tabBar={tabBar}>
<Tab.Screen name="HomeTab" getComponent={() => HomeTabNavigator} />
<Tab.Screen name="SearchTab" getComponent={() => SearchTabNavigator} />
<Tab.Screen
name="MessagesTab"
getComponent={() => MessagesTabNavigator}
/>
<Tab.Screen
name="NotificationsTab"
getComponent={() => NotificationsTabNavigator}
@@ -507,29 +511,26 @@ function TabsNavigator() {
name="MyProfileTab"
getComponent={() => MyProfileTabNavigator}
/>
<Tab.Screen
name="MessagesTab"
getComponent={() => MessagesTabNavigator}
/>
</Tab.Navigator>
)
}
function screenOptions(t: Theme) {
return {
fullScreenGestureEnabled: true,
headerShown: false,
contentStyle: t.atoms.bg,
} as const
}
function HomeTabNavigator() {
const t = useTheme()
return (
<HomeTab.Navigator
screenOptions={{
animationDuration: 285,
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
contentStyle: t.atoms.bg,
}}>
<HomeTab.Navigator screenOptions={screenOptions(t)} initialRouteName="Home">
<HomeTab.Screen name="Home" getComponent={() => HomeScreen} />
<HomeTab.Screen name="Start" getComponent={() => HomeScreen} />
{commonScreens(HomeTab)}
{commonScreens(HomeTab as typeof Flat)}
</HomeTab.Navigator>
)
}
@@ -538,15 +539,10 @@ function SearchTabNavigator() {
const t = useTheme()
return (
<SearchTab.Navigator
screenOptions={{
animationDuration: 285,
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
contentStyle: t.atoms.bg,
}}>
screenOptions={screenOptions(t)}
initialRouteName="Search">
<SearchTab.Screen name="Search" getComponent={() => SearchScreen} />
{commonScreens(SearchTab as typeof HomeTab)}
{commonScreens(SearchTab as typeof Flat)}
</SearchTab.Navigator>
)
}
@@ -555,19 +551,14 @@ function NotificationsTabNavigator() {
const t = useTheme()
return (
<NotificationsTab.Navigator
screenOptions={{
animationDuration: 285,
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
contentStyle: t.atoms.bg,
}}>
screenOptions={screenOptions(t)}
initialRouteName="Notifications">
<NotificationsTab.Screen
name="Notifications"
getComponent={() => NotificationsScreen}
options={{requireAuth: true}}
/>
{commonScreens(NotificationsTab as typeof HomeTab)}
{commonScreens(NotificationsTab as typeof Flat)}
</NotificationsTab.Navigator>
)
}
@@ -576,23 +567,16 @@ function MyProfileTabNavigator() {
const t = useTheme()
return (
<MyProfileTab.Navigator
screenOptions={{
animationDuration: 285,
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
contentStyle: t.atoms.bg,
}}>
screenOptions={screenOptions(t)}
initialRouteName="MyProfile">
<MyProfileTab.Screen
// @ts-ignore // TODO: fix this broken type in ProfileScreen
name="MyProfile"
// MyProfile is not in AllNavigationParams - asserting as Profile at least
// gives us typechecking for initialParams -sfn
name={'MyProfile' as 'Profile'}
getComponent={() => ProfileScreen}
initialParams={{
name: 'me',
hideBackButton: true,
}}
initialParams={{name: 'me', hideBackButton: true}}
/>
{commonScreens(MyProfileTab as typeof HomeTab)}
{commonScreens(MyProfileTab as unknown as typeof Flat)}
</MyProfileTab.Navigator>
)
}
@@ -601,13 +585,8 @@ function MessagesTabNavigator() {
const t = useTheme()
return (
<MessagesTab.Navigator
screenOptions={{
animationDuration: 285,
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
contentStyle: t.atoms.bg,
}}>
screenOptions={screenOptions(t)}
initialRouteName="Messages">
<MessagesTab.Screen
name="Messages"
getComponent={() => MessagesScreen}
@@ -616,7 +595,7 @@ function MessagesTabNavigator() {
animationTypeForReplace: route.params?.animation ?? 'push',
})}
/>
{commonScreens(MessagesTab as typeof HomeTab)}
{commonScreens(MessagesTab as typeof Flat)}
</MessagesTab.Navigator>
)
}
@@ -634,13 +613,7 @@ const FlatNavigator = () => {
return (
<Flat.Navigator
screenListeners={screenListeners}
screenOptions={{
animationDuration: 285,
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
contentStyle: t.atoms.bg,
}}>
screenOptions={screenOptions(t)}>
<Flat.Screen
name="Home"
getComponent={() => HomeScreen}
@@ -666,7 +639,7 @@ const FlatNavigator = () => {
getComponent={() => HomeScreen}
options={{title: title(msg`Home`)}}
/>
{commonScreens(Flat as typeof HomeTab, numUnread)}
{commonScreens(Flat, numUnread)}
</Flat.Navigator>
)
}
@@ -773,7 +746,14 @@ 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}
</NavigationContainer>
</>
+26 -10
View File
@@ -1,7 +1,11 @@
import React from 'react'
import React, {useMemo} from 'react'
import {type GestureResponderEvent} from 'react-native'
import {sanitizeUrl} from '@braintree/sanitize-url'
import {StackActions, useLinkProps} from '@react-navigation/native'
import {
type LinkProps as RNLinkProps,
StackActions,
useLinkBuilder,
} from '@react-navigation/native'
import {BSKY_DOWNLOAD_URL} from '#/lib/constants'
import {useNavigationDeduped} from '#/lib/hooks/useNavigationDeduped'
@@ -28,12 +32,11 @@ import {router} from '#/routes'
*/
export {useButtonContext as useLinkContext} from '#/components/Button'
type BaseLinkProps = Pick<
Parameters<typeof useLinkProps<AllNavigatorParams>>[0],
'to'
> & {
type BaseLinkProps = {
testID?: string
to: RNLinkProps<AllNavigatorParams> | string
/**
* The React Navigation `StackAction` to perform when the link is pressed.
*/
@@ -92,10 +95,23 @@ export function useLink({
shouldProxy?: boolean
}) {
const navigation = useNavigationDeduped()
const {href} = useLinkProps<AllNavigatorParams>({
to:
typeof to === 'string' ? convertBskyAppUrlIfNeeded(sanitizeUrl(to)) : to,
})
const {buildHref} = useLinkBuilder()
const href = useMemo(() => {
return typeof to === 'string'
? convertBskyAppUrlIfNeeded(sanitizeUrl(to))
: to.screen
? buildHref(to.screen, to.params)
: to.href
? convertBskyAppUrlIfNeeded(sanitizeUrl(to.href))
: undefined
}, [to, buildHref])
if (!href) {
throw new Error(
'Link `to` prop must be a string or an object with `screen` and `params` properties',
)
}
const isExternal = isExternalUrl(href)
const {openModal, closeModal} = useModalControls()
const openLink = useOpenLink()
+9 -38
View File
@@ -1,10 +1,8 @@
import React from 'react'
import {useMemo} from 'react'
import {useNavigation} from '@react-navigation/core'
import {NavigationState} from '@react-navigation/native'
import type {NavigationAction} from '@react-navigation/routers'
import {useDedupe} from '#/lib/hooks/useDedupe'
import {AllNavigatorParams, NavigationProp} from '#/lib/routes/types'
import {type NavigationProp} from '#/lib/routes/types'
export type DebouncedNavigationProp = Pick<
NavigationProp,
@@ -22,46 +20,19 @@ export function useNavigationDeduped() {
const navigation = useNavigation<NavigationProp>()
const dedupe = useDedupe()
return React.useMemo(
(): DebouncedNavigationProp => ({
// Types from @react-navigation/routers/lib/typescript/src/StackRouter.ts
push: <RouteName extends keyof AllNavigatorParams>(
...args: undefined extends AllNavigatorParams[RouteName]
?
| [screen: RouteName]
| [screen: RouteName, params: AllNavigatorParams[RouteName]]
: [screen: RouteName, params: AllNavigatorParams[RouteName]]
) => {
return useMemo<DebouncedNavigationProp>(
() => ({
push: (...args: Parameters<typeof navigation.push>) => {
dedupe(() => navigation.push(...args))
},
// Types from @react-navigation/core/src/types.tsx
navigate: <RouteName extends keyof AllNavigatorParams>(
...args: RouteName extends unknown
? undefined extends AllNavigatorParams[RouteName]
?
| [screen: RouteName]
| [screen: RouteName, params: AllNavigatorParams[RouteName]]
: [screen: RouteName, params: AllNavigatorParams[RouteName]]
: never
) => {
navigate: (...args: Parameters<typeof navigation.navigate>) => {
dedupe(() => navigation.navigate(...args))
},
// Types from @react-navigation/routers/lib/typescript/src/StackRouter.ts
replace: <RouteName extends keyof AllNavigatorParams>(
...args: undefined extends AllNavigatorParams[RouteName]
?
| [screen: RouteName]
| [screen: RouteName, params: AllNavigatorParams[RouteName]]
: [screen: RouteName, params: AllNavigatorParams[RouteName]]
) => {
replace: (...args: Parameters<typeof navigation.replace>) => {
dedupe(() => navigation.replace(...args))
},
dispatch: (
action:
| NavigationAction
| ((state: NavigationState) => NavigationAction),
) => {
dedupe(() => navigation.dispatch(action))
dispatch: (...args: Parameters<typeof navigation.dispatch>) => {
dedupe(() => navigation.dispatch(...args))
},
popToTop: () => {
dedupe(() => navigation.popToTop())
+1 -1
View File
@@ -92,7 +92,7 @@ export type NotificationsTabNavigatorParams = CommonNavigatorParams & {
}
export type MyProfileTabNavigatorParams = CommonNavigatorParams & {
MyProfile: undefined
MyProfile: {name: 'me'; hideBackButton: true}
}
export type MessagesTabNavigatorParams = CommonNavigatorParams & {
+44 -39
View File
@@ -1,20 +1,20 @@
import React, {ComponentProps, memo, useMemo} from 'react'
import {memo, useCallback, useMemo} from 'react'
import {
GestureResponderEvent,
type GestureResponderEvent,
Platform,
Pressable,
StyleProp,
TextProps,
TextStyle,
TouchableOpacity,
type StyleProp,
type TextProps,
type TextStyle,
type TouchableOpacity,
View,
ViewStyle,
type ViewStyle,
} from 'react-native'
import {sanitizeUrl} from '@braintree/sanitize-url'
import {StackActions, useLinkProps} from '@react-navigation/native'
import {StackActions} from '@react-navigation/native'
import {
DebouncedNavigationProp,
type DebouncedNavigationProp,
useNavigationDeduped,
} from '#/lib/hooks/useNavigationDeduped'
import {useOpenLink} from '#/lib/hooks/useOpenLink'
@@ -24,7 +24,7 @@ import {
isExternalUrl,
linkRequiresWarning,
} from '#/lib/strings/url-helpers'
import {TypographyVariant} from '#/lib/ThemeContext'
import {type TypographyVariant} from '#/lib/ThemeContext'
import {isAndroid, isWeb} from '#/platform/detection'
import {emitSoftReset} from '#/state/events'
import {useModalControls} from '#/state/modals'
@@ -38,7 +38,7 @@ type Event =
| React.MouseEvent<HTMLAnchorElement, MouseEvent>
| GestureResponderEvent
interface Props extends ComponentProps<typeof TouchableOpacity> {
interface Props extends React.ComponentProps<typeof TouchableOpacity> {
testID?: string
style?: StyleProp<ViewStyle>
href?: string
@@ -47,7 +47,7 @@ interface Props extends ComponentProps<typeof TouchableOpacity> {
hoverStyle?: StyleProp<ViewStyle>
noFeedback?: boolean
asAnchor?: boolean
dataSet?: Object | undefined
dataSet?: any
anchorNoUnderline?: boolean
navigationAction?: 'push' | 'replace' | 'navigate'
onPointerEnter?: () => void
@@ -69,6 +69,7 @@ export const Link = memo(function Link({
onBeforePress,
accessibilityActions,
onAccessibilityAction,
dataSet: dataSetProp,
...props
}: Props) {
const t = useTheme()
@@ -77,7 +78,7 @@ export const Link = memo(function Link({
const anchorHref = asAnchor ? sanitizeUrl(href) : undefined
const openLink = useOpenLink()
const onPress = React.useCallback(
const onPress = useCallback(
(e?: Event) => {
onBeforePress?.()
if (typeof href === 'string') {
@@ -99,6 +100,14 @@ 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 (
<WebAuxClickWrapper>
@@ -129,17 +138,6 @@ 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 (
<Com
@@ -148,8 +146,11 @@ export const Link = memo(function Link({
onPress={onPress}
accessible={accessible}
accessibilityRole="link"
accessibilityLabel={props.accessibilityLabel ?? title}
accessibilityHint={props.accessibilityHint}
// @ts-ignore web only -prf
href={anchorHref}
dataSet={dataSet}
{...props}>
{children ? children : <Text>{title || 'link'}</Text>}
</Com>
@@ -164,14 +165,14 @@ export const TextLink = memo(function TextLink({
text,
numberOfLines,
lineHeight,
dataSet,
dataSet: dataSetProp,
title,
onPress,
onPress: onPressProp,
onBeforePress,
disableMismatchWarning,
navigationAction,
anchorNoUnderline,
...orgProps
...props
}: {
testID?: string
type?: TypographyVariant
@@ -187,7 +188,6 @@ export const TextLink = memo(function TextLink({
anchorNoUnderline?: boolean
onBeforePress?: () => void
} & TextProps) {
const {...props} = useLinkProps({to: sanitizeUrl(href)})
const navigation = useNavigationDeduped()
const {openModal, closeModal} = useModalControls()
const openLink = useOpenLink()
@@ -196,12 +196,15 @@ export const TextLink = memo(function TextLink({
console.error('Unable to detect mismatching label')
}
if (anchorNoUnderline) {
dataSet = dataSet ?? {}
dataSet.noUnderline = 1
}
const dataSet = useMemo(() => {
const ds = {...dataSetProp}
if (anchorNoUnderline) {
ds.noUnderline = 1
}
return ds
}, [dataSetProp, anchorNoUnderline])
props.onPress = React.useCallback(
const onPress = useCallback(
(e?: Event) => {
const requiresWarning =
!disableMismatchWarning &&
@@ -224,10 +227,10 @@ export const TextLink = memo(function TextLink({
return
}
onBeforePress?.()
if (onPress) {
if (onPressProp) {
e?.preventDefault?.()
// @ts-ignore function signature differs by platform -prf
return onPress()
// @ts-expect-error function signature differs by platform -prf
return onPressProp()
}
return onPressInner(
closeModal,
@@ -240,7 +243,7 @@ export const TextLink = memo(function TextLink({
},
[
onBeforePress,
onPress,
onPressProp,
closeModal,
openModal,
navigation,
@@ -273,8 +276,10 @@ 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
{...props}
{...orgProps}>
onPress={onPress}
accessibilityRole="link"
href={convertBskyAppUrlIfNeeded(sanitizeUrl(href))}
{...props}>
{text}
</Text>
)
+18 -4
View File
@@ -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`)
}
}
+30 -22
View File
@@ -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'
@@ -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)
}
+8 -8
View File
@@ -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 () => {