From 94e4ace2d2eaee4f5b569a5b914b492037b1e5aa Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Wed, 10 Jan 2024 18:24:02 +0100 Subject: [PATCH] chore: include linking config for all screens in the example --- example/src/Screens/AuthFlow.tsx | 14 +- example/src/Screens/BottomTabs.tsx | 19 ++- example/src/Screens/CustomLayout.tsx | 18 ++- example/src/Screens/DynamicTabs.tsx | 59 ++++++-- example/src/Screens/Layouts.tsx | 14 +- example/src/Screens/LinkComponent.tsx | 14 +- example/src/Screens/LinkingScreen.tsx | 19 ++- example/src/Screens/MasterDetail.tsx | 17 ++- example/src/Screens/MaterialTopTabs.tsx | 10 +- example/src/Screens/MixedHeaderMode.tsx | 19 ++- example/src/Screens/MixedStack.tsx | 10 +- example/src/Screens/ModalStack.tsx | 10 +- example/src/Screens/NativeStack.tsx | 9 +- .../NativeStackHeaderCustomization.tsx | 24 ++- .../src/Screens/NativeStackPreventRemove.tsx | 20 ++- example/src/Screens/SimpleStack.tsx | 9 +- .../src/Screens/StackHeaderCustomization.tsx | 22 ++- example/src/Screens/StackPreloadFlow.tsx | 10 +- example/src/Screens/StackPreventRemove.tsx | 9 +- example/src/Screens/StackTransparent.tsx | 9 +- example/src/Screens/Static.tsx | 8 +- example/src/Screens/TabPreloadFlow.tsx | 10 +- example/src/Screens/TabView.tsx | 16 +- example/src/constants.tsx | 18 +++ example/src/index.tsx | 106 ++++++------- example/src/screens.tsx | 141 +++++++++++++----- 26 files changed, 460 insertions(+), 174 deletions(-) create mode 100644 example/src/constants.tsx diff --git a/example/src/Screens/AuthFlow.tsx b/example/src/Screens/AuthFlow.tsx index a85b7be90d..c75b9030a4 100644 --- a/example/src/Screens/AuthFlow.tsx +++ b/example/src/Screens/AuthFlow.tsx @@ -1,5 +1,9 @@ import { Button } from '@react-navigation/elements'; -import { type ParamListBase, useTheme } from '@react-navigation/native'; +import { + type ParamListBase, + type PathConfigMap, + useTheme, +} from '@react-navigation/native'; import { createStackNavigator, type StackScreenProps, @@ -8,12 +12,18 @@ import * as React from 'react'; import { ActivityIndicator, StyleSheet, TextInput, View } from 'react-native'; import { Title } from 'react-native-paper'; -type AuthStackParams = { +export type AuthStackParams = { Home: undefined; SignIn: undefined; Chat: undefined; }; +export const authLinking: PathConfigMap = { + Home: '', + SignIn: 'signin', + Chat: 'chat', +}; + const AUTH_CONTEXT_ERROR = 'Authentication context not found. Have your wrapped your components with AuthContext.Consumer?'; diff --git a/example/src/Screens/BottomTabs.tsx b/example/src/Screens/BottomTabs.tsx index b5360d19d3..26b3c24728 100644 --- a/example/src/Screens/BottomTabs.tsx +++ b/example/src/Screens/BottomTabs.tsx @@ -9,6 +9,7 @@ import { HeaderBackButton, useHeaderHeight } from '@react-navigation/elements'; import { type NavigatorScreenParams, type ParamListBase, + type PathConfigMap, useIsFocused, } from '@react-navigation/native'; import type { StackScreenProps } from '@react-navigation/stack'; @@ -27,7 +28,11 @@ import { Appbar, IconButton } from 'react-native-paper'; import { Albums } from '../Shared/Albums'; import { Chat } from '../Shared/Chat'; import { Contacts } from '../Shared/Contacts'; -import { SimpleStack, type SimpleStackParams } from './SimpleStack'; +import { + SimpleStack, + simpleStackLinking, + type SimpleStackParams, +} from './SimpleStack'; const getTabBarIcon = (name: React.ComponentProps['name']) => @@ -35,13 +40,23 @@ const getTabBarIcon = ); -type BottomTabParams = { +export type BottomTabParams = { TabStack: NavigatorScreenParams; TabAlbums: undefined; TabContacts: undefined; TabChat: undefined; }; +export const bottomTabLinking: PathConfigMap = { + TabStack: { + path: 'stack', + screens: simpleStackLinking, + }, + TabAlbums: 'albums', + TabContacts: 'contacts', + TabChat: 'chat', +}; + const AlbumsScreen = () => { const headerHeight = useHeaderHeight(); const tabBarHeight = useBottomTabBarHeight(); diff --git a/example/src/Screens/CustomLayout.tsx b/example/src/Screens/CustomLayout.tsx index a182d12a63..99a955fcc9 100644 --- a/example/src/Screens/CustomLayout.tsx +++ b/example/src/Screens/CustomLayout.tsx @@ -6,6 +6,7 @@ import { import { CommonActions, type ParamListBase, + type PathConfigMap, useTheme, } from '@react-navigation/native'; import { @@ -27,22 +28,29 @@ import { useSafeAreaInsets, } from 'react-native-safe-area-context'; +import { COMMON_LINKING_CONFIG } from '../constants'; import { Albums } from '../Shared/Albums'; import { Article } from '../Shared/Article'; import { NewsFeed } from '../Shared/NewsFeed'; -export type SimpleStackParams = { +export type CustomLayoutParams = { Article: { author: string } | undefined; NewsFeed: { date: number }; Albums: undefined; }; +export const customLayoutLinking: PathConfigMap = { + Article: COMMON_LINKING_CONFIG.Article, + NewsFeed: COMMON_LINKING_CONFIG.NewsFeed, + Albums: 'albums', +}; + const scrollEnabled = Platform.select({ web: true, default: false }); const ArticleScreen = ({ navigation, route, -}: StackScreenProps) => { +}: StackScreenProps) => { return ( @@ -67,7 +75,7 @@ const ArticleScreen = ({ const NewsFeedScreen = ({ route, navigation, -}: StackScreenProps) => { +}: StackScreenProps) => { return ( @@ -85,7 +93,7 @@ const NewsFeedScreen = ({ const AlbumsScreen = ({ navigation, -}: StackScreenProps) => { +}: StackScreenProps) => { return ( @@ -106,7 +114,7 @@ const AlbumsScreen = ({ ); }; -const Stack = createStackNavigator(); +const Stack = createStackNavigator(); export function CustomLayout({ navigation, diff --git a/example/src/Screens/DynamicTabs.tsx b/example/src/Screens/DynamicTabs.tsx index d54fec21b8..68a230e26a 100644 --- a/example/src/Screens/DynamicTabs.tsx +++ b/example/src/Screens/DynamicTabs.tsx @@ -5,11 +5,22 @@ import * as React from 'react'; import { StyleSheet, View } from 'react-native'; import { Title } from 'react-native-paper'; -type BottomTabParams = { - [key: string]: undefined; +import type { PathConfigMap } from '../../../packages/core/src/types'; + +export type DynamicBottomTabParams = { + [key: `tab-${number}`]: undefined; +}; + +export const dynamicBottomTabLinking: PathConfigMap = { + 'tab-0': 'tab/0', + 'tab-1': 'tab/1', + 'tab-2': 'tab/2', + 'tab-3': 'tab/3', + 'tab-4': 'tab/4', + 'tab-5': 'tab/5', }; -const BottomTabs = createBottomTabNavigator(); +const BottomTabs = createBottomTabNavigator(); export function DynamicTabs() { const [tabs, setTabs] = React.useState([0, 1]); @@ -30,18 +41,36 @@ export function DynamicTabs() { {() => ( Tab {i} - - + {tabs.length < 5 && ( + + )} + {tabs.length > 1 && ( + + )} )} diff --git a/example/src/Screens/Layouts.tsx b/example/src/Screens/Layouts.tsx index bd10ce5021..825a230791 100644 --- a/example/src/Screens/Layouts.tsx +++ b/example/src/Screens/Layouts.tsx @@ -1,5 +1,5 @@ import { Button } from '@react-navigation/elements'; -import type { ParamListBase } from '@react-navigation/native'; +import type { ParamListBase, PathConfigMap } from '@react-navigation/native'; import { createStackNavigator, type StackNavigationOptions, @@ -8,8 +8,12 @@ import { import * as React from 'react'; import { ScrollView, StyleSheet, Text, View } from 'react-native'; -export type SimpleStackParams = { - SuspenseDemo: { author: string } | undefined; +export type LayoutsStackParams = { + SuspenseDemo: undefined; +}; + +export const layoutsStackLinking: PathConfigMap = { + SuspenseDemo: 'suspense', }; let cached: number | undefined; @@ -24,7 +28,7 @@ const createPromise = () => const SuspenseDemoScreen = ({ navigation, -}: StackScreenProps) => { +}: StackScreenProps) => { const [promise, setPromise] = React.useState(createPromise); const [error, setError] = React.useState(null); @@ -92,7 +96,7 @@ class ErrorBoundary extends React.Component< } } -const Stack = createStackNavigator(); +const Stack = createStackNavigator(); export function LayoutsStack({ navigation, diff --git a/example/src/Screens/LinkComponent.tsx b/example/src/Screens/LinkComponent.tsx index 6277988efc..6776cfee7a 100644 --- a/example/src/Screens/LinkComponent.tsx +++ b/example/src/Screens/LinkComponent.tsx @@ -3,6 +3,7 @@ import { CommonActions, Link, type ParamListBase, + type PathConfigMap, StackActions, } from '@react-navigation/native'; import { @@ -12,10 +13,21 @@ import { import * as React from 'react'; import { Platform, ScrollView, StyleSheet, View } from 'react-native'; -import type { LinkComponentDemoParamList } from '../screens'; +import { COMMON_LINKING_CONFIG } from '../constants'; import { Albums } from '../Shared/Albums'; import { Article } from '../Shared/Article'; +export type LinkComponentDemoParamList = { + Article: { author: string }; + Albums: undefined; +}; + +export const linkComponentDemoLinking: PathConfigMap = + { + Article: COMMON_LINKING_CONFIG.Article, + Albums: 'albums', + }; + const scrollEnabled = Platform.select({ web: true, default: false }); const ArticleScreen = ({ diff --git a/example/src/Screens/LinkingScreen.tsx b/example/src/Screens/LinkingScreen.tsx index 92f4b5cc03..2d0531187f 100644 --- a/example/src/Screens/LinkingScreen.tsx +++ b/example/src/Screens/LinkingScreen.tsx @@ -1,5 +1,8 @@ import { Button } from '@react-navigation/elements'; -import { UNSTABLE_useUnhandledLinking } from '@react-navigation/native'; +import { + type PathConfigMap, + UNSTABLE_useUnhandledLinking, +} from '@react-navigation/native'; import { createStackNavigator, type StackScreenProps, @@ -13,12 +16,18 @@ const info = ` \u2022 http://localhost:19006/linking/profile `.trim(); -type StackParamList = { +export type LinkingStackParams = { Home: undefined; Profile: undefined; SignIn: undefined; }; +export const linkingStackLinking: PathConfigMap = { + Home: '', + Profile: 'profile', + SignIn: 'sign-in', +}; + const SigningContext = React.createContext<{ signIn: () => void; signOut: () => void; @@ -26,7 +35,7 @@ const SigningContext = React.createContext<{ const ProfileScreen = ({ navigation, -}: StackScreenProps) => { +}: StackScreenProps) => { const { signOut } = useContext(SigningContext)!; return ( @@ -41,7 +50,7 @@ const ProfileScreen = ({ const HomeScreen = ({ navigation, -}: StackScreenProps) => { +}: StackScreenProps) => { const { signOut } = useContext(SigningContext)!; return ( @@ -73,7 +82,7 @@ const SignInScreen = () => { ); }; -const Stack = createStackNavigator(); +const Stack = createStackNavigator(); export function LinkingScreen() { const [isSignedIn, setSignedIn] = React.useState(false); diff --git a/example/src/Screens/MasterDetail.tsx b/example/src/Screens/MasterDetail.tsx index b30493f2ed..e91ab35782 100644 --- a/example/src/Screens/MasterDetail.tsx +++ b/example/src/Screens/MasterDetail.tsx @@ -6,6 +6,7 @@ import { } from '@react-navigation/drawer'; import { type ParamListBase, + type PathConfigMap, useNavigation, useTheme, } from '@react-navigation/native'; @@ -18,12 +19,18 @@ import { Albums } from '../Shared/Albums'; import { Article } from '../Shared/Article'; import { NewsFeed } from '../Shared/NewsFeed'; -type DrawerParams = { +export type MasterDetailParams = { Article: undefined; NewsFeed: undefined; Albums: undefined; }; +export const masterDetailLinking: PathConfigMap = { + Article: 'article', + NewsFeed: 'feed', + Albums: 'albums', +}; + const useIsLargeScreen = () => { const dimensions = useWindowDimensions(); @@ -50,7 +57,7 @@ const Header = ({ const ArticleScreen = ({ navigation, -}: DrawerScreenProps) => { +}: DrawerScreenProps) => { return ( <>
navigation.toggleDrawer()} /> @@ -61,7 +68,7 @@ const ArticleScreen = ({ const NewsFeedScreen = ({ navigation, -}: DrawerScreenProps) => { +}: DrawerScreenProps) => { return ( <>
navigation.toggleDrawer()} /> @@ -72,7 +79,7 @@ const NewsFeedScreen = ({ const AlbumsScreen = ({ navigation, -}: DrawerScreenProps) => { +}: DrawerScreenProps) => { return ( <>
navigation.toggleDrawer()} /> @@ -96,7 +103,7 @@ const CustomDrawerContent = (props: DrawerContentComponentProps) => { ); }; -const Drawer = createDrawerNavigator(); +const Drawer = createDrawerNavigator(); type Props = Partial> & StackScreenProps; diff --git a/example/src/Screens/MaterialTopTabs.tsx b/example/src/Screens/MaterialTopTabs.tsx index df038dce25..9e1755eabc 100644 --- a/example/src/Screens/MaterialTopTabs.tsx +++ b/example/src/Screens/MaterialTopTabs.tsx @@ -1,5 +1,5 @@ import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; -import type { ParamListBase } from '@react-navigation/native'; +import type { ParamListBase, PathConfigMap } from '@react-navigation/native'; import type { StackScreenProps } from '@react-navigation/stack'; import * as React from 'react'; @@ -7,12 +7,18 @@ import { Albums } from '../Shared/Albums'; import { Chat } from '../Shared/Chat'; import { Contacts } from '../Shared/Contacts'; -type MaterialTopTabParams = { +export type MaterialTopTabParams = { Albums: undefined; Contacts: undefined; Chat: undefined; }; +export const materialTopTabLinking: PathConfigMap = { + Albums: 'albums', + Contacts: 'contacts', + Chat: 'chat', +}; + const MaterialTopTabs = createMaterialTopTabNavigator(); const ChatScreen = () => ; diff --git a/example/src/Screens/MixedHeaderMode.tsx b/example/src/Screens/MixedHeaderMode.tsx index 351a725a00..2f9bab54e5 100644 --- a/example/src/Screens/MixedHeaderMode.tsx +++ b/example/src/Screens/MixedHeaderMode.tsx @@ -1,5 +1,5 @@ import { Button } from '@react-navigation/elements'; -import type { ParamListBase } from '@react-navigation/native'; +import type { ParamListBase, PathConfigMap } from '@react-navigation/native'; import { createStackNavigator, HeaderStyleInterpolators, @@ -9,22 +9,29 @@ import { import * as React from 'react'; import { Platform, ScrollView, StyleSheet, View } from 'react-native'; +import { COMMON_LINKING_CONFIG } from '../constants'; import { Albums } from '../Shared/Albums'; import { Article } from '../Shared/Article'; import { NewsFeed } from '../Shared/NewsFeed'; -export type SimpleStackParams = { +export type MixedHeaderStackParams = { Article: { author: string } | undefined; NewsFeed: { date: number }; Albums: undefined; }; +export const mixedHeaderStackLinking: PathConfigMap = { + Article: COMMON_LINKING_CONFIG.Article, + NewsFeed: COMMON_LINKING_CONFIG.NewsFeed, + Albums: 'albums', +}; + const scrollEnabled = Platform.select({ web: true, default: false }); const ArticleScreen = ({ navigation, route, -}: StackScreenProps) => { +}: StackScreenProps) => { return ( @@ -49,7 +56,7 @@ const ArticleScreen = ({ const NewsFeedScreen = ({ route, navigation, -}: StackScreenProps) => { +}: StackScreenProps) => { return ( @@ -67,7 +74,7 @@ const NewsFeedScreen = ({ const AlbumsScreen = ({ navigation, -}: StackScreenProps) => { +}: StackScreenProps) => { return ( @@ -86,7 +93,7 @@ const AlbumsScreen = ({ ); }; -const SimpleStack = createStackNavigator(); +const SimpleStack = createStackNavigator(); export function MixedHeaderMode({ navigation, diff --git a/example/src/Screens/MixedStack.tsx b/example/src/Screens/MixedStack.tsx index f362823b64..96494c3b61 100644 --- a/example/src/Screens/MixedStack.tsx +++ b/example/src/Screens/MixedStack.tsx @@ -1,5 +1,5 @@ import { Button } from '@react-navigation/elements'; -import type { ParamListBase } from '@react-navigation/native'; +import type { ParamListBase, PathConfigMap } from '@react-navigation/native'; import { createStackNavigator, type StackScreenProps, @@ -7,14 +7,20 @@ import { import * as React from 'react'; import { Platform, ScrollView, StyleSheet, View } from 'react-native'; +import { COMMON_LINKING_CONFIG } from '../constants'; import { Albums } from '../Shared/Albums'; import { Article } from '../Shared/Article'; -type MixedStackParams = { +export type MixedStackParams = { Article: { author: string }; Albums: undefined; }; +export const mixedStackLinking: PathConfigMap = { + Article: COMMON_LINKING_CONFIG.Article, + Albums: 'albums', +}; + const scrollEnabled = Platform.select({ web: true, default: false }); const ArticleScreen = ({ diff --git a/example/src/Screens/ModalStack.tsx b/example/src/Screens/ModalStack.tsx index df34c4e373..6dc1f54638 100644 --- a/example/src/Screens/ModalStack.tsx +++ b/example/src/Screens/ModalStack.tsx @@ -1,5 +1,5 @@ import { Button } from '@react-navigation/elements'; -import type { ParamListBase } from '@react-navigation/native'; +import type { ParamListBase, PathConfigMap } from '@react-navigation/native'; import { createStackNavigator, type StackScreenProps, @@ -7,14 +7,20 @@ import { import * as React from 'react'; import { Platform, ScrollView, StyleSheet, View } from 'react-native'; +import { COMMON_LINKING_CONFIG } from '../constants'; import { Albums } from '../Shared/Albums'; import { Article } from '../Shared/Article'; -type ModalStackParams = { +export type ModalStackParams = { Article: { author: string }; Albums: undefined; }; +export const modalStackLinking: PathConfigMap = { + Article: COMMON_LINKING_CONFIG.Article, + Albums: 'albums', +}; + const scrollEnabled = Platform.select({ web: true, default: false }); const ArticleScreen = ({ diff --git a/example/src/Screens/NativeStack.tsx b/example/src/Screens/NativeStack.tsx index 4200872fe7..4757be35cd 100644 --- a/example/src/Screens/NativeStack.tsx +++ b/example/src/Screens/NativeStack.tsx @@ -1,5 +1,5 @@ import { Button, useHeaderHeight } from '@react-navigation/elements'; -import type { ParamListBase } from '@react-navigation/native'; +import type { ParamListBase, PathConfigMap } from '@react-navigation/native'; import { createNativeStackNavigator, type NativeStackScreenProps, @@ -7,6 +7,7 @@ import { import * as React from 'react'; import { Platform, ScrollView, StyleSheet, View } from 'react-native'; +import { COMMON_LINKING_CONFIG } from '../constants'; import { Albums } from '../Shared/Albums'; import { Article } from '../Shared/Article'; import { NewsFeed } from '../Shared/NewsFeed'; @@ -17,6 +18,12 @@ export type NativeStackParams = { Albums: undefined; }; +export const nativeStackLinking: PathConfigMap = { + Article: COMMON_LINKING_CONFIG.Article, + NewsFeed: COMMON_LINKING_CONFIG.NewsFeed, + Albums: 'albums', +}; + const scrollEnabled = Platform.select({ web: true, default: false }); const ArticleScreen = ({ diff --git a/example/src/Screens/NativeStackHeaderCustomization.tsx b/example/src/Screens/NativeStackHeaderCustomization.tsx index c632779d40..1fe06232d1 100644 --- a/example/src/Screens/NativeStackHeaderCustomization.tsx +++ b/example/src/Screens/NativeStackHeaderCustomization.tsx @@ -1,5 +1,5 @@ import { Button } from '@react-navigation/elements'; -import type { ParamListBase } from '@react-navigation/native'; +import type { ParamListBase, PathConfigMap } from '@react-navigation/native'; import { createNativeStackNavigator, type NativeStackScreenProps, @@ -15,22 +15,30 @@ import { } from 'react-native'; import { Appbar } from 'react-native-paper'; +import { COMMON_LINKING_CONFIG } from '../constants'; import { Albums } from '../Shared/Albums'; import { Article } from '../Shared/Article'; import { NewsFeed } from '../Shared/NewsFeed'; -export type NativeStackParams = { +export type NativeHeaderCustomizationStackParams = { Article: { author: string } | undefined; NewsFeed: { date: number }; Albums: undefined; }; +export const nativeHeaderCustomizationStackLinking: PathConfigMap = + { + Article: COMMON_LINKING_CONFIG.Article, + NewsFeed: COMMON_LINKING_CONFIG.NewsFeed, + Albums: 'albums', + }; + const scrollEnabled = Platform.select({ web: true, default: false }); const ArticleScreen = ({ navigation, route, -}: NativeStackScreenProps) => { +}: NativeStackScreenProps) => { return ( @@ -55,7 +63,10 @@ const ArticleScreen = ({ const NewsFeedScreen = ({ route, navigation, -}: NativeStackScreenProps) => { +}: NativeStackScreenProps< + NativeHeaderCustomizationStackParams, + 'NewsFeed' +>) => { return ( @@ -73,7 +84,7 @@ const NewsFeedScreen = ({ const AlbumsScreen = ({ navigation, -}: NativeStackScreenProps) => { +}: NativeStackScreenProps) => { return ( @@ -94,7 +105,8 @@ const AlbumsScreen = ({ ); }; -const Stack = createNativeStackNavigator(); +const Stack = + createNativeStackNavigator(); export function NativeStackHeaderCustomization({ navigation, diff --git a/example/src/Screens/NativeStackPreventRemove.tsx b/example/src/Screens/NativeStackPreventRemove.tsx index 1998ba94d1..38714ce024 100644 --- a/example/src/Screens/NativeStackPreventRemove.tsx +++ b/example/src/Screens/NativeStackPreventRemove.tsx @@ -1,4 +1,7 @@ -import { UNSTABLE_usePreventRemove } from '@react-navigation/core'; +import { + type PathConfigMap, + UNSTABLE_usePreventRemove, +} from '@react-navigation/core'; import { Button } from '@react-navigation/elements'; import { CommonActions, @@ -19,19 +22,26 @@ import { View, } from 'react-native'; +import { COMMON_LINKING_CONFIG } from '../constants'; import { Article } from '../Shared/Article'; -type PreventRemoveParams = { +export type NativePreventRemoveParams = { Article: { author: string }; Input: undefined; }; +export const nativePreventRemoveLinking: PathConfigMap = + { + Article: COMMON_LINKING_CONFIG.Article, + Input: 'input', + }; + const scrollEnabled = Platform.select({ web: true, default: false }); const ArticleScreen = ({ navigation, route, -}: NativeStackScreenProps) => { +}: NativeStackScreenProps) => { return ( @@ -52,7 +62,7 @@ const ArticleScreen = ({ const InputScreen = ({ navigation, -}: NativeStackScreenProps) => { +}: NativeStackScreenProps) => { const [text, setText] = React.useState(''); const { colors } = useTheme(); @@ -116,7 +126,7 @@ const InputScreen = ({ ); }; -const Stack = createNativeStackNavigator(); +const Stack = createNativeStackNavigator(); type Props = NativeStackScreenProps; diff --git a/example/src/Screens/SimpleStack.tsx b/example/src/Screens/SimpleStack.tsx index 8f152daa0c..94144e783a 100644 --- a/example/src/Screens/SimpleStack.tsx +++ b/example/src/Screens/SimpleStack.tsx @@ -1,5 +1,5 @@ import { Button } from '@react-navigation/elements'; -import type { ParamListBase } from '@react-navigation/native'; +import type { ParamListBase, PathConfigMap } from '@react-navigation/native'; import { createStackNavigator, HeaderStyleInterpolators, @@ -9,6 +9,7 @@ import { import * as React from 'react'; import { Platform, ScrollView, StyleSheet, View } from 'react-native'; +import { COMMON_LINKING_CONFIG } from '../constants'; import { Albums } from '../Shared/Albums'; import { Article } from '../Shared/Article'; import { NewsFeed } from '../Shared/NewsFeed'; @@ -19,6 +20,12 @@ export type SimpleStackParams = { Albums: undefined; }; +export const simpleStackLinking: PathConfigMap = { + Article: COMMON_LINKING_CONFIG.Article, + NewsFeed: COMMON_LINKING_CONFIG.NewsFeed, + Albums: 'albums', +}; + const scrollEnabled = Platform.select({ web: true, default: false }); const ArticleScreen = ({ diff --git a/example/src/Screens/StackHeaderCustomization.tsx b/example/src/Screens/StackHeaderCustomization.tsx index 73bd254399..c682f95343 100644 --- a/example/src/Screens/StackHeaderCustomization.tsx +++ b/example/src/Screens/StackHeaderCustomization.tsx @@ -4,7 +4,10 @@ import { HeaderBackground, useHeaderHeight, } from '@react-navigation/elements'; -import { type ParamListBase } from '@react-navigation/native'; +import { + type ParamListBase, + type PathConfigMap, +} from '@react-navigation/native'; import { createStackNavigator, Header, @@ -22,21 +25,28 @@ import { } from 'react-native'; import { Appbar } from 'react-native-paper'; +import { COMMON_LINKING_CONFIG } from '../constants'; import { Albums } from '../Shared/Albums'; import { Article } from '../Shared/Article'; import { BlurView } from '../Shared/BlurView'; -type SimpleStackParams = { +export type HeaderCustomizationStackParams = { Article: { author: string }; Albums: undefined; }; +export const headerCustomizationStackLinking: PathConfigMap = + { + Article: COMMON_LINKING_CONFIG.Article, + Albums: 'albums', + }; + const scrollEnabled = Platform.select({ web: true, default: false }); const ArticleScreen = ({ navigation, route, -}: StackScreenProps) => { +}: StackScreenProps) => { return ( @@ -55,7 +65,9 @@ const ArticleScreen = ({ ); }; -const AlbumsScreen = ({ navigation }: StackScreenProps) => { +const AlbumsScreen = ({ + navigation, +}: StackScreenProps) => { const headerHeight = useHeaderHeight(); return ( @@ -76,7 +88,7 @@ const AlbumsScreen = ({ navigation }: StackScreenProps) => { ); }; -const Stack = createStackNavigator(); +const Stack = createStackNavigator(); type Props = StackScreenProps; diff --git a/example/src/Screens/StackPreloadFlow.tsx b/example/src/Screens/StackPreloadFlow.tsx index 76aef01457..44f73aa73b 100644 --- a/example/src/Screens/StackPreloadFlow.tsx +++ b/example/src/Screens/StackPreloadFlow.tsx @@ -7,12 +7,20 @@ import * as React from 'react'; import { useEffect, useState } from 'react'; import { StyleSheet, Text, View } from 'react-native'; -type PreloadStackParams = { +import type { PathConfigMap } from '../../../packages/core/src/types'; + +export type PreloadStackParams = { Home: undefined; Details: undefined; Profile: undefined; }; +export const preloadStackLinking: PathConfigMap = { + Home: '', + Details: 'details', + Profile: 'profile', +}; + const DetailsScreen = ({ navigation, }: StackScreenProps) => { diff --git a/example/src/Screens/StackPreventRemove.tsx b/example/src/Screens/StackPreventRemove.tsx index d9c8704269..1352cc728a 100644 --- a/example/src/Screens/StackPreventRemove.tsx +++ b/example/src/Screens/StackPreventRemove.tsx @@ -2,6 +2,7 @@ import { Button } from '@react-navigation/elements'; import { CommonActions, type ParamListBase, + type PathConfigMap, useTheme, } from '@react-navigation/native'; import { @@ -18,13 +19,19 @@ import { View, } from 'react-native'; +import { COMMON_LINKING_CONFIG } from '../constants'; import { Article } from '../Shared/Article'; -type PreventRemoveParams = { +export type PreventRemoveParams = { Article: { author: string }; Input: undefined; }; +export const preventRemoveLinking: PathConfigMap = { + Article: COMMON_LINKING_CONFIG.Article, + Input: 'input', +}; + const scrollEnabled = Platform.select({ web: true, default: false }); const ArticleScreen = ({ diff --git a/example/src/Screens/StackTransparent.tsx b/example/src/Screens/StackTransparent.tsx index 2554af561b..ea71857d2e 100644 --- a/example/src/Screens/StackTransparent.tsx +++ b/example/src/Screens/StackTransparent.tsx @@ -16,15 +16,22 @@ import { } from 'react-native'; import { Paragraph } from 'react-native-paper'; +import { COMMON_LINKING_CONFIG } from '../constants'; import { Article } from '../Shared/Article'; import { NewsFeed } from '../Shared/NewsFeed'; -type TransparentStackParams = { +export type TransparentStackParams = { Article: { author: string }; NewsFeed: undefined; Dialog: undefined; }; +export const transparentStackLinking = { + Article: COMMON_LINKING_CONFIG.Article, + NewsFeed: COMMON_LINKING_CONFIG.NewsFeed, + Dialog: 'dialog', +}; + const scrollEnabled = Platform.select({ web: true, default: false }); const ArticleScreen = ({ diff --git a/example/src/Screens/Static.tsx b/example/src/Screens/Static.tsx index 10b9e63978..9f26feb730 100644 --- a/example/src/Screens/Static.tsx +++ b/example/src/Screens/Static.tsx @@ -57,18 +57,21 @@ const HomeTabs = createBottomTabNavigator({ options: { tabBarIcon: getTabBarIcon('image-album'), }, + linking: 'albums', }, Contacts: { screen: Contacts, options: { tabBarIcon: getTabBarIcon('contacts'), }, + linking: 'contacts', }, Chat: { screen: Chat, options: { tabBarIcon: getTabBarIcon('message-reply'), }, + linking: 'chat', if: useIsChatShown, }, }, @@ -79,7 +82,10 @@ const RootStack = createStackNavigator({ headerShown: false, }, screens: { - Home: HomeTabs, + Home: { + screen: HomeTabs, + linking: '', + }, }, }); diff --git a/example/src/Screens/TabPreloadFlow.tsx b/example/src/Screens/TabPreloadFlow.tsx index 25b9a9ed61..a1c3cbe680 100644 --- a/example/src/Screens/TabPreloadFlow.tsx +++ b/example/src/Screens/TabPreloadFlow.tsx @@ -5,11 +5,19 @@ import * as React from 'react'; import { useEffect, useState } from 'react'; import { StyleSheet, Text, View } from 'react-native'; -type PreloadBottomTabsParams = { +import type { PathConfigMap } from '../../../packages/core/src/types'; + +export type PreloadBottomTabsParams = { Home: undefined; Details: undefined; }; +export const preloadBottomTabsLinking: PathConfigMap = + { + Home: '', + Details: 'details', + }; + const DetailsScreen = ({ navigation, }: BottomTabScreenProps) => { diff --git a/example/src/Screens/TabView.tsx b/example/src/Screens/TabView.tsx index 020d9387f6..5a3180e041 100644 --- a/example/src/Screens/TabView.tsx +++ b/example/src/Screens/TabView.tsx @@ -1,4 +1,4 @@ -import type { ParamListBase } from '@react-navigation/native'; +import type { ParamListBase, PathConfigMap } from '@react-navigation/native'; import { createStackNavigator, type StackScreenProps, @@ -33,6 +33,20 @@ export type TabViewStackParams = { ExampleList: undefined; }; +export const tabViewStackLinking: PathConfigMap = { + ExampleList: '', + ...EXAMPLE_SCREEN_NAMES.reduce( + (acc, name) => ({ + ...acc, + [name]: name + .replace(/([A-Z]+)/g, '-$1') + .replace(/^-/, '') + .toLowerCase(), + }), + {} + ), +}; + const TabViewStack = createStackNavigator(); const ExampleListScreen = ({ diff --git a/example/src/constants.tsx b/example/src/constants.tsx new file mode 100644 index 0000000000..16c2dc1f8c --- /dev/null +++ b/example/src/constants.tsx @@ -0,0 +1,18 @@ +export const COMMON_LINKING_CONFIG = { + Article: { + path: 'article/:author?', + parse: { + author: (author: string) => + author.charAt(0).toUpperCase() + author.slice(1).replace(/-/g, ' '), + }, + stringify: { + author: (author: string) => author.toLowerCase().replace(/\s/g, '-'), + }, + }, + NewsFeed: { + path: 'feed/:date', + parse: { + date: Number, + }, + }, +}; diff --git a/example/src/index.tsx b/example/src/index.tsx index 93b95b4e65..b743f15f87 100644 --- a/example/src/index.tsx +++ b/example/src/index.tsx @@ -12,7 +12,6 @@ import { DefaultTheme, type InitialState, NavigationContainer, - type PathConfigMap, useNavigationContainerRef, } from '@react-navigation/native'; import { @@ -43,6 +42,7 @@ import { } from 'react-native-paper'; import { SafeAreaView } from 'react-native-safe-area-context'; +import type { LinkingOptions } from '../../packages/native/src/types'; import { type RootDrawerParamList, type RootStackParamList, @@ -60,6 +60,52 @@ const DIRECTION_PERSISTENCE_KEY = 'DIRECTION'; const SCREEN_NAMES = Object.keys(SCREENS) as (keyof typeof SCREENS)[]; +const linking: LinkingOptions = { + // To test deep linking on, run the following in the Terminal: + // Android: adb shell am start -a android.intent.action.VIEW -d "exp://127.0.0.1:19000/--/simple-stack" + // iOS: xcrun simctl openurl booted exp://127.0.0.1:19000/--/simple-stack + // Android (bare): adb shell am start -a android.intent.action.VIEW -d "rne://127.0.0.1:19000/--/simple-stack" + // iOS (bare): xcrun simctl openurl booted rne://127.0.0.1:19000/--/simple-stack + // The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`) + prefixes: [createURL('/')], + config: { + initialRouteName: 'Home', + screens: { + Home: { + screens: { + Examples: '', + }, + }, + NotFound: '*', + ...SCREEN_NAMES.reduce( + (acc, name) => { + // Convert screen names such as SimpleStack to kebab case (simple-stack) + const path = name + .replace(/([A-Z]+)/g, '-$1') + .replace(/^-/, '') + .toLowerCase(); + + const config = { + path, + screens: SCREENS[name].linking, + }; + + // @ts-expect-error: acc is not readonly + acc[name] = config; + + return acc; + }, + {} as { + [Key in keyof typeof SCREENS]: { + path: string; + screens: (typeof SCREENS)[Key]['linking']; + }; + } + ), + }, + }, +}; + let previousDirection = I18nManager.getConstants().isRTL ? 'rtl' : 'ltr'; if (Platform.OS === 'web') { @@ -201,63 +247,7 @@ export function App() { } theme={theme} direction={isRTL ? 'rtl' : 'ltr'} - linking={{ - // To test deep linking on, run the following in the Terminal: - // Android: adb shell am start -a android.intent.action.VIEW -d "exp://127.0.0.1:19000/--/simple-stack" - // iOS: xcrun simctl openurl booted exp://127.0.0.1:19000/--/simple-stack - // Android (bare): adb shell am start -a android.intent.action.VIEW -d "rne://127.0.0.1:19000/--/simple-stack" - // iOS (bare): xcrun simctl openurl booted rne://127.0.0.1:19000/--/simple-stack - // The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`) - prefixes: [createURL('/')], - config: { - initialRouteName: 'Home', - screens: SCREEN_NAMES.reduce>( - (acc, name) => { - // Convert screen names such as SimpleStack to kebab case (simple-stack) - const path = name - .replace(/([A-Z]+)/g, '-$1') - .replace(/^-/, '') - .toLowerCase(); - - acc[name] = { - path, - screens: { - Article: { - path: 'article/:author?', - parse: { - author: (author: string) => - author.charAt(0).toUpperCase() + - author.slice(1).replace(/-/g, ' '), - }, - stringify: { - author: (author: string) => - author.toLowerCase().replace(/\s/g, '-'), - }, - }, - Albums: 'music', - Chat: 'chat', - Contacts: 'people', - NewsFeed: 'feed', - Dialog: 'dialog', - SignIn: 'sign-in', - Profile: 'profile', - Home: 'home', - }, - }; - - return acc; - }, - { - Home: { - screens: { - Examples: '', - }, - }, - NotFound: '*', - } - ), - }, - }} + linking={linking} fallback={Loading…} documentTitle={{ formatter: (options, route) => diff --git a/example/src/screens.tsx b/example/src/screens.tsx index 75b1a10b6f..cbb7ab0dba 100644 --- a/example/src/screens.tsx +++ b/example/src/screens.tsx @@ -1,147 +1,208 @@ -import type { NavigatorScreenParams } from '@react-navigation/native'; +import type { + NavigatorScreenParams, + PathConfigMap, +} from '@react-navigation/native'; -import { AuthFlow } from './Screens/AuthFlow'; -import { BottomTabs } from './Screens/BottomTabs'; -import { CustomLayout } from './Screens/CustomLayout'; +import { AuthFlow, authLinking } from './Screens/AuthFlow'; +import { bottomTabLinking, BottomTabs } from './Screens/BottomTabs'; +import { CustomLayout, customLayoutLinking } from './Screens/CustomLayout'; import { DrawerView } from './Screens/DrawerView'; -import { DynamicTabs } from './Screens/DynamicTabs'; -import { LayoutsStack } from './Screens/Layouts'; -import { LinkComponent } from './Screens/LinkComponent'; -import { LinkingScreen } from './Screens/LinkingScreen'; -import { MasterDetail } from './Screens/MasterDetail'; -import { MaterialTopTabsScreen } from './Screens/MaterialTopTabs'; -import { MixedHeaderMode } from './Screens/MixedHeaderMode'; -import { MixedStack } from './Screens/MixedStack'; -import { ModalStack } from './Screens/ModalStack'; -import { NativeStack } from './Screens/NativeStack'; -import { NativeStackHeaderCustomization } from './Screens/NativeStackHeaderCustomization'; -import { NativeStackPreventRemove } from './Screens/NativeStackPreventRemove'; -import { SimpleStack } from './Screens/SimpleStack'; -import { StackHeaderCustomization } from './Screens/StackHeaderCustomization'; -import { StackPreloadFlow } from './Screens/StackPreloadFlow'; -import { StackPreventRemove } from './Screens/StackPreventRemove'; -import { StackTransparent } from './Screens/StackTransparent'; +import { dynamicBottomTabLinking, DynamicTabs } from './Screens/DynamicTabs'; +import { LayoutsStack, layoutsStackLinking } from './Screens/Layouts'; +import { + LinkComponent, + linkComponentDemoLinking, +} from './Screens/LinkComponent'; +import { LinkingScreen, linkingStackLinking } from './Screens/LinkingScreen'; +import { MasterDetail, masterDetailLinking } from './Screens/MasterDetail'; +import { + materialTopTabLinking, + MaterialTopTabsScreen, +} from './Screens/MaterialTopTabs'; +import { + MixedHeaderMode, + mixedHeaderStackLinking, +} from './Screens/MixedHeaderMode'; +import { MixedStack, mixedStackLinking } from './Screens/MixedStack'; +import { ModalStack, modalStackLinking } from './Screens/ModalStack'; +import { NativeStack, nativeStackLinking } from './Screens/NativeStack'; +import { + nativeHeaderCustomizationStackLinking, + NativeStackHeaderCustomization, +} from './Screens/NativeStackHeaderCustomization'; +import { + nativePreventRemoveLinking, + NativeStackPreventRemove, +} from './Screens/NativeStackPreventRemove'; +import { SimpleStack, simpleStackLinking } from './Screens/SimpleStack'; +import { + headerCustomizationStackLinking, + StackHeaderCustomization, +} from './Screens/StackHeaderCustomization'; +import { + preloadStackLinking, + StackPreloadFlow, +} from './Screens/StackPreloadFlow'; +import { + preventRemoveLinking, + StackPreventRemove, +} from './Screens/StackPreventRemove'; +import { + StackTransparent, + transparentStackLinking, +} from './Screens/StackTransparent'; import { StaticScreen } from './Screens/Static'; -import { TabPreloadFlow } from './Screens/TabPreloadFlow'; -import { TabView } from './Screens/TabView'; - -export type RootDrawerParamList = { - Examples: undefined; -}; - -export type LinkComponentDemoParamList = { - Article: { author: string }; - Albums: undefined; -}; +import { + preloadBottomTabsLinking, + TabPreloadFlow, +} from './Screens/TabPreloadFlow'; +import { TabView, tabViewStackLinking } from './Screens/TabView'; export const SCREENS = { NativeStack: { title: 'Native Stack', component: NativeStack, + linking: nativeStackLinking, }, SimpleStack: { title: 'Simple Stack', component: SimpleStack, + linking: simpleStackLinking, }, ModalStack: { title: 'Modal Stack', component: ModalStack, + linking: modalStackLinking, }, MixedStack: { title: 'Regular + Modal Stack', component: MixedStack, + linking: mixedStackLinking, }, MixedHeaderMode: { title: 'Float + Screen Header Stack', component: MixedHeaderMode, + linking: mixedHeaderStackLinking, }, StackTransparent: { title: 'Transparent Stack', component: StackTransparent, + linking: transparentStackLinking, }, StackHeaderCustomization: { title: 'Header Customization in Stack', component: StackHeaderCustomization, + linking: headerCustomizationStackLinking, }, NativeStackHeaderCustomization: { title: 'Header Customization in Native Stack', component: NativeStackHeaderCustomization, + linking: nativeHeaderCustomizationStackLinking, }, BottomTabs: { title: 'Bottom Tabs', component: BottomTabs, + linking: bottomTabLinking, }, MaterialTopTabs: { title: 'Material Top Tabs', component: MaterialTopTabsScreen, + linking: materialTopTabLinking, }, DynamicTabs: { title: 'Dynamic Tabs', component: DynamicTabs, + linking: dynamicBottomTabLinking, }, MasterDetail: { title: 'Master Detail', component: MasterDetail, + linking: masterDetailLinking, }, AuthFlow: { title: 'Auth Flow', component: AuthFlow, + linking: authLinking, }, Layouts: { title: 'Custom Layout', component: LayoutsStack, + linking: layoutsStackLinking, }, StackPreventRemove: { title: 'Prevent removing screen in Stack', component: StackPreventRemove, + linking: preventRemoveLinking, }, NativeStackPreventRemove: { title: 'Prevent removing screen in Native Stack', component: NativeStackPreventRemove, + linking: nativePreventRemoveLinking, }, CustomLayout: { title: 'Custom Layout', component: CustomLayout, + linking: customLayoutLinking, }, LinkComponent: { title: '', component: LinkComponent, + linking: linkComponentDemoLinking, }, TabView: { title: 'TabView', component: TabView, + linking: tabViewStackLinking, }, DrawerView: { title: 'DrawerView', component: DrawerView, + linking: {}, }, Static: { title: 'Static config', component: StaticScreen, + linking: {}, }, Linking: { title: 'Linking with authentication flow', component: LinkingScreen, + linking: linkingStackLinking, }, PreloadFlowStack: { title: 'Preloading flow for Stack', component: StackPreloadFlow, + linking: preloadStackLinking, }, PreloadFlowTab: { title: 'Preloading flow for Bottom Tabs', component: TabPreloadFlow, - }, + linking: preloadBottomTabsLinking, + }, +} as const satisfies { + [key: string]: { + title: string; + component: React.ComponentType; + linking: object; + }; }; -type ParamListTypes = { - Home: undefined; - NotFound: undefined; - LinkComponent: NavigatorScreenParams | undefined; +type ExampleScreensParamList = { + [Key in keyof typeof SCREENS]: (typeof SCREENS)[Key]['linking'] extends PathConfigMap< + infer P + > + ? NavigatorScreenParams

| undefined + : undefined; }; -export type RootStackParamList = { - [P in Exclude]: undefined; -} & ParamListTypes; +export type RootDrawerParamList = { + Examples: undefined; +}; + +export type RootStackParamList = ExampleScreensParamList & { + Home: NavigatorScreenParams | undefined; + NotFound: undefined; +}; // Make the default RootParamList the same as the RootStackParamList declare global {