diff --git a/.changeset/small-tables-work.md b/.changeset/small-tables-work.md new file mode 100644 index 000000000..0a9282e0c --- /dev/null +++ b/.changeset/small-tables-work.md @@ -0,0 +1,9 @@ +--- +"@equinor/mad-core": minor +--- + +BREAKING: replace `createCoreStackNavigator` with +`createStackCoreNavigator`/`createNativeStackCoreNavigator`. Under the hood they use +`createStackNavigator`/`createNativeStackNavigator` respectively. If you don't want any breaking +changes in your app, migrate `createCoreStackNavigator` to `createNativeStackCoreNavigator`. The +stack-type you use is displayed in the about-screen. diff --git a/.changeset/thin-rice-sort.md b/.changeset/thin-rice-sort.md new file mode 100644 index 000000000..8330c9941 --- /dev/null +++ b/.changeset/thin-rice-sort.md @@ -0,0 +1,5 @@ +--- +"@equinor/mad-navigation": minor +--- + +add stack navigator diff --git a/apps/chronicles/navigation/index.tsx b/apps/chronicles/navigation/index.tsx index c8af49514..ab40b0b9d 100644 --- a/apps/chronicles/navigation/index.tsx +++ b/apps/chronicles/navigation/index.tsx @@ -16,9 +16,9 @@ import { Color, Icon, IconName, useBreakpoint, useToken } from "@equinor/mad-com import { createBottomTabNavigator, createNativeStackNavigator, - createCoreStackNavigator, NavigationContainer, getDefaultScreenOptionsForLoginScreen, + createNativeStackCoreNavigator, } from "@equinor/mad-core"; import { config } from "../mad.config"; import { GoToSettingsButton } from "../components/GoToSettingsButton"; @@ -36,6 +36,7 @@ import { DFWComponentScreen } from "../screens/dfw/DFWComponentsScreen"; import { DFWComponentName } from "../types/dfwcomponents"; import { SampleLoginScreen } from "./LoginScreen"; import { ToastScreen } from "../screens/ToastScreen"; + export default function Navigation({ colorScheme }: { colorScheme: ColorSchemeName }) { const token = useToken(); return ( @@ -58,7 +59,7 @@ export default function Navigation({ colorScheme }: { colorScheme: ColorSchemeNa ); } -const CoreStack = createCoreStackNavigator(config); +const CoreStack = createNativeStackCoreNavigator(config); function RootNavigator() { return ( diff --git a/apps/chronicles/package.json b/apps/chronicles/package.json index 8f4ca5dbb..0daa82100 100644 --- a/apps/chronicles/package.json +++ b/apps/chronicles/package.json @@ -15,6 +15,7 @@ "@react-navigation/bottom-tabs": "^6.6.0", "@react-navigation/native": "^6.1.17", "@react-navigation/native-stack": "^6.10.0", + "@react-navigation/stack": "^6", "@shopify/react-native-skia": "1.5.0", "expo": "~52.0.11", "expo-asset": "~11.0.1", diff --git a/apps/docs/docs/mad-core/migration-guide/3-use-createCoreStackNavigator.md b/apps/docs/docs/mad-core/migration-guide/3-use-createCoreStackNavigator.md index a9c177abe..9f1f3e670 100644 --- a/apps/docs/docs/mad-core/migration-guide/3-use-createCoreStackNavigator.md +++ b/apps/docs/docs/mad-core/migration-guide/3-use-createCoreStackNavigator.md @@ -1,24 +1,35 @@ --- -sidebar_label: Use createCoreStackNavigator -description: Learn how to use createCoreStackNavigator! +sidebar_label: Use createStackCoreNavigator/createNativeStackCoreNavigator +description: Learn how to use `createStackCoreNavigator`/`createNativeStackCoreNavigator`! --- -# Use `createCoreStackNavigator` +# Use `createStackCoreNavigator`/`createNativeStackCoreNavigator` Next step is replacing your topmost `createStackNavigator`/`createNativeStackNavigator` with -`createCoreStackNavigator` from `@equinor/mad-core`. It takes one argument: The config you created -in step 2. You use it the same way you would a normal `Stack`. +`createStackCoreNavigator`/`createNativeStackCoreNavigator` from `@equinor/mad-core`. It takes one +argument: The config you created in step 2. You use it the same way you would a normal `Stack`. ```tsx -import { createCoreStackNavigator } from "@equinor/mad-core"; +import { createNativeStackCoreNavigator } from "@equinor/mad-core"; import { config } from "path/to/mad.config.ts"; import { RootStackParamList } from "path/to/paramList.ts"; -const RootStack = createCoreStackNavigator(config); +const RootStack = createNativeStackCoreNavigator(config); +``` + +or + +```tsx +import { createStackCoreNavigator } from "@equinor/mad-core"; +import { config } from "path/to/mad.config.ts"; +import { RootStackParamList } from "path/to/paramList.ts"; + +const RootStack = createStackCoreNavigator(config); ``` If you have leftover screens from `mad-expo-core` in the stack, they should be removed. -`createCoreStackNavigator` will add similar screens for you behind the scenes. +`createStackCoreNavigator`/`createNativeStackCoreNavigator` will add similar screens for you behind +the scenes. `SettingsScreen` also has to be added manually. This is because you most likely have app-specific settings you want to hook up to the settings screen. @@ -30,7 +41,7 @@ suggest creating a wrapper component that passes in the props you need to `Setti Example stack: ```tsx -const CoreStack = createCoreStackNavigator(config); +const CoreStack = createNativeStackCoreNavigator(config); function RootNavigator() { return ( diff --git a/apps/docs/docs/mad-core/migration-guide/4-replace-navigation-imports.md b/apps/docs/docs/mad-core/migration-guide/4-replace-navigation-imports.md index c3d6f3a0c..a3d7080e3 100644 --- a/apps/docs/docs/mad-core/migration-guide/4-replace-navigation-imports.md +++ b/apps/docs/docs/mad-core/migration-guide/4-replace-navigation-imports.md @@ -22,7 +22,7 @@ navigation tracking for you automatically, which is a nice feature to have! import { createBottomTabNavigator, createNativeStackNavigator, - createCoreStackNavigator, + createNativeStackCoreNavigator, NavigationContainer, } from "@equinor/mad-core"; ``` diff --git a/apps/docs/docs/mad-navigation/2-installation.md b/apps/docs/docs/mad-navigation/2-installation.md index 7a3e742a2..bcb0398cc 100644 --- a/apps/docs/docs/mad-navigation/2-installation.md +++ b/apps/docs/docs/mad-navigation/2-installation.md @@ -16,3 +16,4 @@ running a newer version of v6 and some features are missing, create an issue_ | @react-navigation/bottom-tabs | 6.5.7 | | @react-navigation/native | 6.1.6 | | @react-navigation/native-stack | 6.9.12 | +| @react-navigation/stack | 6.4.1 | diff --git a/apps/docs/docs/mad-navigation/3-usage.md b/apps/docs/docs/mad-navigation/3-usage.md index 823f5463c..3ee8a974d 100644 --- a/apps/docs/docs/mad-navigation/3-usage.md +++ b/apps/docs/docs/mad-navigation/3-usage.md @@ -6,8 +6,8 @@ description: Learn how to use this package! # Usage If you want to add custom sub-headers to your navigation system, you first have to create custom -navigator-creator functions. You can do so with `createBottomTabNavigatorFactory` and -`createNativeStackNavigatorFactory`. +navigator-creator functions. You can do so with `createBottomTabNavigatorFactory`, +`createNativeStackNavigatorFactory` and `createStackNavigatorFactory`. Step 1: Create your custom sub-header: @@ -22,11 +22,13 @@ Step 2: Create your new navigator-creator functions: import { createBottomTabNavigatorFactory, createNativeStackNavigatorFactory, + createStackNavigatorFactory, } from "@equinor/mad-navigation"; import { CustomSubHeader } from "path/to/subHeader"; export const createBottomTabNavigator = createBottomTabNavigatorFactory(CustomSubHeader); export const createNativeStackNavigator = createNativeStackNavigatorFactory(CustomSubHeader); +export const createStackNavigator = createStackNavigatorFactory(CustomSubHeader); ``` Follow [React Navigation’s documentation](https://reactnavigation.org/docs/getting-started/). When diff --git a/packages/core/src/components/CoreNavigatorTypeProvider.tsx b/packages/core/src/components/CoreNavigatorTypeProvider.tsx new file mode 100644 index 000000000..b2cb2f501 --- /dev/null +++ b/packages/core/src/components/CoreNavigatorTypeProvider.tsx @@ -0,0 +1,17 @@ +import React, { createContext, PropsWithChildren, useContext } from "react"; + +export type CoreNavigatorType = "stack" | "native-stack" + +const CoreNavigatorTypeContext = createContext(null) + +type CoreNavigatorTypeProviderProps = PropsWithChildren<{type: CoreNavigatorType}> + +export const CoreNavigatorTypeProvider = ({type, children}: CoreNavigatorTypeProviderProps) => { + return {children} +} + +export const useCoreNavigatorType = () => { + const coreNavigatorType = useContext(CoreNavigatorTypeContext) + if (!coreNavigatorType) throw new Error("Could not find core navigator type. You have most likely not added a core navigator to your application") + return coreNavigatorType; +} \ No newline at end of file diff --git a/packages/core/src/components/createCoreStackNavigator.tsx b/packages/core/src/components/createCoreStackNavigator.tsx index aaf4b4556..951f3f0d7 100644 --- a/packages/core/src/components/createCoreStackNavigator.tsx +++ b/packages/core/src/components/createCoreStackNavigator.tsx @@ -1,15 +1,27 @@ -import { CoreStackParamListBase, MadConfig } from "../types"; -import { createNativeStackNavigator } from "./navigation"; -import { createMadCoreNavigator } from "../utils/createMadCoreNavigator"; -import { setConfig } from "../store/mad-config"; import { ParamListBase } from "@react-navigation/native"; +import { setConfig } from "../store/mad-config"; +import { CoreStackParamListBase, MadConfig } from "../types"; +import { createMadCoreStackNavigator, createMadCoreNativeStackNavigator } from "../utils/createMadCoreNavigator"; import { initiateAuthenticationClient } from "../utils/initiateAuthenticationClient"; +import { createNativeStackNavigator, createStackNavigator } from "./navigation"; -export const createCoreStackNavigator = (config: MadConfig) => { +export const createStackCoreNavigator = (config: MadConfig) => { + setConfig(config as MadConfig); + initiateAuthenticationClient(); + const Stack = createStackNavigator(); + const Navigator = createMadCoreStackNavigator(Stack); + return { ...Stack, Navigator }; +} +export const createNativeStackCoreNavigator = (config: MadConfig) => { setConfig(config as MadConfig); initiateAuthenticationClient(); const Stack = createNativeStackNavigator(); - const Navigator = createMadCoreNavigator(Stack); + const Navigator = createMadCoreNativeStackNavigator(Stack); + return { ...Stack, Navigator }; +} - return { ...Stack, Navigator }; -}; +/** + * + * @deprecated This functions is replaced by `createStackCoreNavigator` and `createNativeStackCoreNavigator`. USE `createNativeStackCoreNavigator` IF YOU DON'T WANT BREAKING CHANGES IN YOUR APP + */ +export const createCoreStackNavigator = (config: MadConfig) => createNativeStackCoreNavigator(config) \ No newline at end of file diff --git a/packages/core/src/components/navigation/navigation-creators.ts b/packages/core/src/components/navigation/navigation-creators.ts index 47f775a69..d70419cd5 100644 --- a/packages/core/src/components/navigation/navigation-creators.ts +++ b/packages/core/src/components/navigation/navigation-creators.ts @@ -1,8 +1,10 @@ import { createBottomTabNavigatorFactory, createNativeStackNavigatorFactory, + createStackNavigatorFactory, } from "@equinor/mad-navigation"; import { MadCoreSubHeader } from "./MadCoreSubHeader"; export const createBottomTabNavigator = createBottomTabNavigatorFactory(MadCoreSubHeader); export const createNativeStackNavigator = createNativeStackNavigatorFactory(MadCoreSubHeader); +export const createStackNavigator = createStackNavigatorFactory(MadCoreSubHeader); \ No newline at end of file diff --git a/packages/core/src/components/screens/AboutScreen.tsx b/packages/core/src/components/screens/AboutScreen.tsx index f7622f332..70247f47f 100644 --- a/packages/core/src/components/screens/AboutScreen.tsx +++ b/packages/core/src/components/screens/AboutScreen.tsx @@ -8,6 +8,7 @@ import { useExperimentalFeatures, } from "../../store/mad-config"; import { getMadCommonBaseUrl } from "../../utils/madCommonUtils"; +import { useCoreNavigatorType } from "../CoreNavigatorTypeProvider"; export const AboutScreen = () => { const styles = useStyles(themeStyles); @@ -15,6 +16,7 @@ export const AboutScreen = () => { const environmentName = environment.charAt(0).toUpperCase() + environment.slice(1); const appVersion = useAppVersion(); const about = useAbout(); + const coreNavigatorType = useCoreNavigatorType(); const experimentalFeatures = useExperimentalFeatures(); const authenticationMethod = experimentalFeatures?.useExpoAuthSession ? "Expo" : "MSAL"; const endpoints = [getMadCommonBaseUrl(environment)].concat(about?.endpoints ?? []); @@ -33,12 +35,14 @@ export const AboutScreen = () => { BuildNr App version Authentication + Core navigator type {environmentName} {about?.buildNumber} {appVersion} {authenticationMethod} + {coreNavigatorType} diff --git a/packages/core/src/store/mad-config/mad-config.ts b/packages/core/src/store/mad-config/mad-config.ts index cb42111f8..87eec907c 100644 --- a/packages/core/src/store/mad-config/mad-config.ts +++ b/packages/core/src/store/mad-config/mad-config.ts @@ -26,7 +26,7 @@ const useMadConfigStore = create()( ); const MAD_CONFIG_NOT_FOUND_ERROR = - "Mad config has not been provided! Make sure you use 'createCoreStackNavigator' to provide your config"; + "Mad config has not been provided! Make sure you use 'createNativeStackCoreNavigator'/'createStackCoreNavigator' to provide your config"; export const useMadConfig = (): EnvironmentContextualConfig => { const config = useMadConfigStore().config; diff --git a/packages/core/src/utils/MadCoreProviders.tsx b/packages/core/src/utils/MadCoreProviders.tsx new file mode 100644 index 000000000..5f58e888b --- /dev/null +++ b/packages/core/src/utils/MadCoreProviders.tsx @@ -0,0 +1,34 @@ +import React, { PropsWithChildren } from "react"; +import { ParamListBase } from "@react-navigation/native"; +import { MadConfig, WithoutEnvironmentOptionValues } from "../types"; +import { AppInsightsInitializer } from "@equinor/mad-insights"; +import { ToastEmitter } from "@equinor/mad-toast"; +import { ServiceMessageProvider } from "../components/service-message/ServiceMessageProvider"; +import { EnvironmentProvider } from "../components/EnvironmentProvider"; +import { + CoreNavigatorType, + CoreNavigatorTypeProvider, +} from "../components/CoreNavigatorTypeProvider"; + +export type MadCoreProvidersProps = PropsWithChildren<{ + config: WithoutEnvironmentOptionValues>; + type: CoreNavigatorType; +}>; +export const MadCoreProviders = ({ + config, + type, + children, +}: MadCoreProvidersProps) => { + return ( + + + + + {children} + + + + + + ); +}; diff --git a/packages/core/src/utils/createMadCoreNavigator.tsx b/packages/core/src/utils/createMadCoreNavigator.tsx index a4bf5c0d3..d08195266 100644 --- a/packages/core/src/utils/createMadCoreNavigator.tsx +++ b/packages/core/src/utils/createMadCoreNavigator.tsx @@ -1,79 +1,108 @@ import React from "react"; -import { EnvironmentProvider } from "../components/EnvironmentProvider"; -import { LoginScreen } from "../components/screens/LoginScreen"; -import { ParamListBase } from "@react-navigation/native"; -import { CoreStackParamListBase } from "../types"; -import { WhatsNewScreen } from "../components/screens/release-notes/WhatsNewScreen"; -import { AppInsightsInitializer } from "@equinor/mad-insights"; -import { createNativeStackNavigator } from "../components"; -import { ReleaseNotesScreen } from "../components/screens/release-notes/ReleaseNotesScreen"; +import { createNativeStackNavigator, createStackNavigator } from "../components"; +import { CoreRoutes } from "../components/navigation/coreRoutes"; import { AboutScreen } from "../components/screens/AboutScreen"; import { CreateIncidentScreen } from "../components/screens/create-incident/CreateIncidentScreen"; import { SelectLanguageScreen } from "../components/screens/language/SelectLanguageScreen"; -import { ServiceMessageProvider } from "../components/service-message/ServiceMessageProvider"; +import { LoginScreen } from "../components/screens/LoginScreen"; +import { ReleaseNotesScreen } from "../components/screens/release-notes/ReleaseNotesScreen"; +import { WhatsNewScreen } from "../components/screens/release-notes/WhatsNewScreen"; import { useMadConfig } from "../store"; -import { CoreRoutes } from "../components/navigation/coreRoutes"; +import { CoreStackParamListBase } from "../types"; import { getDefaultScreenOptionsForLoginScreen } from "./getDefaultScreenOptionsForLoginScreen"; -import { ToastEmitter } from "../components/ToastEmitter"; +import { MadCoreProviders } from "./MadCoreProviders"; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -- We need to specify how a general function looks like -type GeneralFunction = (...args: any) => any; -type PropsOf = Parameters[0]; -type StackType = ReturnType; -// @ts-expect-error this works, I don't know why it complains -type NavigatorProps = PropsOf; +export const createMadCoreNativeStackNavigator = ( + Stack: ReturnType> +) => { + //@ts-expect-error this works + type Props = Parameters[0] + function MadCoreNavigator(props: Omit) { + const config = useMadConfig(); + if (!config) return null; + return ( + + {/*@ts-expect-error this works */} + + {config.login.addScreenManually !== true && ( + + )} + + + {config.about && ( + + )} + {config.serviceNow && ( + + )} + {config.language.supportedLanguages.length > 1 && ( + <> + + + + )} + {props.children} + + + ); + } + return MadCoreNavigator; +}; -export const createMadCoreNavigator = ( - Stack: ReturnType>, +export const createMadCoreStackNavigator = ( + Stack: ReturnType> ) => { - function MadCoreNavigator(props: Omit) { + //@ts-expect-error this works + type Props = Parameters[0] + function MadCoreNavigator(props: Omit) { const config = useMadConfig(); if (!config) return null; return ( - - - - - {config.login.addScreenManually !== true && ( - - )} + + {/*@ts-expect-error this works */} + + {config.login.addScreenManually !== true && ( + + )} + + + {config.about && ( + + )} + {config.serviceNow && ( + + )} + {config.language.supportedLanguages.length > 1 && ( + <> + null }} /> - - {config.about && ( - - )} - {config.serviceNow && ( - - )} - {config.language.supportedLanguages.length > 1 && ( - <> - - - - )} - {props.children} - - - - - + + )} + {props.children} + + ); } return MadCoreNavigator; diff --git a/packages/core/src/utils/getDefaultScreenOptionsForLoginScreen.tsx b/packages/core/src/utils/getDefaultScreenOptionsForLoginScreen.tsx index 64435c16f..5c03d8e88 100644 --- a/packages/core/src/utils/getDefaultScreenOptionsForLoginScreen.tsx +++ b/packages/core/src/utils/getDefaultScreenOptionsForLoginScreen.tsx @@ -1,6 +1,6 @@ -import { MadNativeStackNavigationOptions } from "@equinor/mad-navigation"; +import { MadNavigationOptions } from "@equinor/mad-navigation/dist/_internal/types"; -export const getDefaultScreenOptionsForLoginScreen = (): MadNativeStackNavigationOptions => ({ +export const getDefaultScreenOptionsForLoginScreen = (): MadNavigationOptions & {headerShown?: boolean} => ({ headerShown: false, customSubHeaderShown: true, customSubHeaderFloat: true, diff --git a/packages/navigation/package.json b/packages/navigation/package.json index 955233da0..35a856d2b 100644 --- a/packages/navigation/package.json +++ b/packages/navigation/package.json @@ -35,6 +35,7 @@ "@react-navigation/elements": "1.3.30", "@react-navigation/native": "^6.1.17", "@react-navigation/native-stack": "^6.10.0", + "@react-navigation/stack": "^6.4.1", "react": "18.3.1", "react-dom": "18.3.1", "react-native": "0.76.3" @@ -47,5 +48,8 @@ "test": "jest" }, "type": "module", - "types": "./dist/index.d.ts" + "types": "./dist/index.d.ts", + "dependencies": { + "warn-once": "^0.1.1" + } } diff --git a/packages/navigation/src/index.ts b/packages/navigation/src/index.ts index 609efbbc4..608ba9eb7 100644 --- a/packages/navigation/src/index.ts +++ b/packages/navigation/src/index.ts @@ -1,4 +1,5 @@ export * from "./bottom-tab"; export * from "./native-stack"; +export * from "./stack" export * from "./NavigationContainer"; -export { HeaderHeightProvider, useHeaderHeight } from "./header-height-context"; +export { HeaderHeightProvider, useHeaderHeight } from "./header-height-context"; \ No newline at end of file diff --git a/packages/navigation/src/stack/createStackNavigator.tsx b/packages/navigation/src/stack/createStackNavigator.tsx new file mode 100644 index 000000000..c957b257c --- /dev/null +++ b/packages/navigation/src/stack/createStackNavigator.tsx @@ -0,0 +1,120 @@ +/* eslint @typescript-eslint/no-unsafe-assignment: 0 -- this file is mostly copied from react-navigation. They have different rules than us */ +/* eslint @typescript-eslint/no-unsafe-return: 0 -- this file is mostly copied from react-navigation. They have different rules than us */ +/* eslint @typescript-eslint/no-unsafe-call: 0 -- this file is mostly copied from react-navigation. They have different rules than us */ + + +import { createNavigatorFactory, EventArg, ParamListBase, StackActionHelpers, StackActions, StackNavigationState, StackRouter, StackRouterOptions, useNavigationBuilder } from "@react-navigation/native"; +import { MadStackNavigationOptions, StackHeaderMode, StackNavigatorProps } from "./types"; +import { StackNavigationEventMap, StackNavigationOptions, StackView } from "@react-navigation/stack"; +import React from "react"; +import warnOnce from "warn-once"; +import { createMadDescriptors } from "../_internal/createMadDescriptors"; + +function StackNavigator({ + id, + initialRouteName, + children, + screenListeners, + screenOptions, + customSubHeader, + ...rest + }: StackNavigatorProps) { + // @ts-expect-error: mode is deprecated + const mode = rest.mode as 'card' | 'modal' | undefined; + + warnOnce( + mode != null, + `Stack Navigator: 'mode="${mode}"' is deprecated. Use 'presentation: "${mode}"' in 'screenOptions' instead.\n\nSee https://reactnavigation.org/docs/stack-navigator#presentation for more details.` + ); + + // @ts-expect-error: headerMode='none' is deprecated + const headerMode = rest.headerMode as StackHeaderMode | 'none' | undefined; + + warnOnce( + headerMode === 'none', + `Stack Navigator: 'headerMode="none"' is deprecated. Use 'headerShown: false' in 'screenOptions' instead.\n\nSee https://reactnavigation.org/docs/stack-navigator/#headershown for more details.` + ); + + warnOnce( + headerMode != null && headerMode !== 'none', + `Stack Navigator: 'headerMode' is moved to 'options'. Moved it to 'screenOptions' to keep current behavior.\n\nSee https://reactnavigation.org/docs/stack-navigator/#headermode for more details.` + ); + + // @ts-expect-error headerMode='none' is deprecated + const keyboardHandlingEnabled = rest.keyboardHandlingEnabled; + + warnOnce( + keyboardHandlingEnabled !== undefined, + `Stack Navigator: 'keyboardHandlingEnabled' is moved to 'options'. Moved it to 'screenOptions' to keep current behavior.\n\nSee https://reactnavigation.org/docs/stack-navigator/#keyboardhandlingenabled for more details.` + ); + + const defaultScreenOptions: StackNavigationOptions = { + presentation: mode, + headerShown: headerMode ? headerMode !== 'none' : true, + headerMode: headerMode && headerMode !== 'none' ? headerMode : undefined, + keyboardHandlingEnabled, + }; + + const { state, descriptors, navigation, NavigationContent } = + useNavigationBuilder< + StackNavigationState, + StackRouterOptions, + StackActionHelpers, + MadStackNavigationOptions, + StackNavigationEventMap + >(StackRouter, { + id, + initialRouteName, + children, + screenListeners, + screenOptions, + defaultScreenOptions, + }); + + React.useEffect( + () => + // @ts-expect-error: there may not be a tab navigator in parent + navigation.addListener?.('tabPress', (e) => { + const isFocused = navigation.isFocused(); + + // Run the operation in the next frame so we're sure all listeners have been run + // This is necessary to know if preventDefault() has been called + requestAnimationFrame(() => { + if ( + state.index > 0 && + isFocused && + !(e as unknown as EventArg<'tabPress', true>).defaultPrevented + ) { + // When user taps on already focused tab and we're inside the tab, + // reset the stack to replicate native behaviour + navigation.dispatch({ + ...StackActions.popToTop(), + target: state.key, + }); + } + }); + }), + [navigation, state.index, state.key] + ); + + const modifiedDescriptors = createMadDescriptors(descriptors, screenOptions, customSubHeader); + + return ( + + + + ); + } + + export const createStackNavigatorFactory = (customSubHeader?: () => React.ReactNode) => + createNavigatorFactory< + StackNavigationState, + MadStackNavigationOptions, + StackNavigationEventMap, + typeof StackNavigator + >(props => ); \ No newline at end of file diff --git a/packages/navigation/src/stack/index.ts b/packages/navigation/src/stack/index.ts new file mode 100644 index 000000000..cb755849a --- /dev/null +++ b/packages/navigation/src/stack/index.ts @@ -0,0 +1,2 @@ +export * from "./createStackNavigator" +export * from "./types" \ No newline at end of file diff --git a/packages/navigation/src/stack/types.ts b/packages/navigation/src/stack/types.ts new file mode 100644 index 000000000..b26d25ab4 --- /dev/null +++ b/packages/navigation/src/stack/types.ts @@ -0,0 +1,31 @@ +import { + DefaultNavigatorOptions, + ParamListBase, + StackNavigationState, + StackRouterOptions, +} from "@react-navigation/native"; +import { StackNavigationEventMap, StackNavigationOptions } from "@react-navigation/stack"; +import { MadCustomFactoryProps, MadNavigationOptions } from "../_internal/types"; + +export type StackNavigatorProps = DefaultNavigatorOptions< + ParamListBase, + StackNavigationState, + MadNavigationOptions, + StackNavigationEventMap +> & + StackRouterOptions & + StackNavigationConfig & MadCustomFactoryProps; + +export type StackNavigationConfig = { + /** + * Whether inactive screens should be detached from the view hierarchy to save memory. + * This will have no effect if you disable `react-native-screens`. + * + * Defaults to `true`. + */ + detachInactiveScreens?: boolean; +}; + +export type StackHeaderMode = 'float' | 'screen' + +export type MadStackNavigationOptions = StackNavigationOptions & MadNavigationOptions \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 8d59416b6..e1d92ae17 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4079,6 +4079,7 @@ __metadata: "@react-navigation/bottom-tabs": "npm:^6.6.0" "@react-navigation/native": "npm:^6.1.17" "@react-navigation/native-stack": "npm:^6.10.0" + "@react-navigation/stack": "npm:^6" "@shopify/react-native-skia": "npm:1.5.0" "@types/react": "npm:~18.3.12" detox: "npm:^20.23.0" @@ -4244,11 +4245,13 @@ __metadata: jest: "npm:^29.7.0" ts-jest: "npm:^29.2.5" tsup: "npm:^8.3.5" + warn-once: "npm:^0.1.1" peerDependencies: "@react-navigation/bottom-tabs": ^6.6.0 "@react-navigation/elements": 1.3.30 "@react-navigation/native": ^6.1.17 "@react-navigation/native-stack": ^6.10.0 + "@react-navigation/stack": ^6.4.1 react: 18.3.1 react-dom: 18.3.1 react-native: 0.76.3 @@ -6165,6 +6168,24 @@ __metadata: languageName: node linkType: hard +"@react-navigation/stack@npm:^6": + version: 6.4.1 + resolution: "@react-navigation/stack@npm:6.4.1" + dependencies: + "@react-navigation/elements": "npm:^1.3.31" + color: "npm:^4.2.3" + warn-once: "npm:^0.1.0" + peerDependencies: + "@react-navigation/native": ^6.0.0 + react: "*" + react-native: "*" + react-native-gesture-handler: ">= 1.0.0" + react-native-safe-area-context: ">= 3.0.0" + react-native-screens: ">= 3.0.0" + checksum: 10c0/6bc28e5e4e99161a538c1cb9752d70727704ca7b4137e71a44992dfe2ff6e0a3028353184424af1457ec59a4ea6ef9757fc99203280fd7339f36e61b80c9050a + languageName: node + linkType: hard + "@rollup/rollup-android-arm-eabi@npm:4.27.3": version: 4.27.3 resolution: "@rollup/rollup-android-arm-eabi@npm:4.27.3" @@ -23390,7 +23411,7 @@ __metadata: languageName: node linkType: hard -"warn-once@npm:0.1.1, warn-once@npm:^0.1.0": +"warn-once@npm:0.1.1, warn-once@npm:^0.1.0, warn-once@npm:^0.1.1": version: 0.1.1 resolution: "warn-once@npm:0.1.1" checksum: 10c0/f531e7b2382124f51e6d8f97b8c865246db8ab6ff4e53257a2d274e0f02b97d7201eb35db481843dc155815e154ad7afb53b01c4d4db15fb5aa073562496aff7