From 43cab46064b0416efebe2dfde4ba12971a211773 Mon Sep 17 00:00:00 2001 From: lukewalczak Date: Wed, 23 Apr 2025 15:54:17 +0200 Subject: [PATCH 1/8] chore: create adapter for both navigation, adjust types --- package.json | 2 +- .../__tests__/adapter.v6.test.ts | 26 ++++++++++++++ .../__tests__/adapter.v7.test.ts | 26 ++++++++++++++ src/react-navigation/adapter.ts | 24 +++++++++++++ .../createMaterialBottomTabNavigator.tsx | 33 +++++++++++++---- .../views/MaterialBottomTabView.tsx | 4 +-- yarn.lock | 35 +++++++------------ 7 files changed, 118 insertions(+), 32 deletions(-) create mode 100644 src/react-navigation/__tests__/adapter.v6.test.ts create mode 100644 src/react-navigation/__tests__/adapter.v7.test.ts create mode 100644 src/react-navigation/adapter.ts diff --git a/package.json b/package.json index e55d84949c..e67199dba7 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,7 @@ "__fixtures__\\/[^/]+\\/(output|error)\\.js" ], "transformIgnorePatterns": [ - "node_modules/(?!(@react-native|react-native(-.*)?)/)" + "node_modules/(?!(@react-navigation|@react-native|react-native(-.*)?)/)" ] }, "greenkeeper": { diff --git a/src/react-navigation/__tests__/adapter.v6.test.ts b/src/react-navigation/__tests__/adapter.v6.test.ts new file mode 100644 index 0000000000..37d3525374 --- /dev/null +++ b/src/react-navigation/__tests__/adapter.v6.test.ts @@ -0,0 +1,26 @@ +// @ts-ignore +import { useLinkTools, useLinkBuilder } from '@react-navigation/native'; +import { renderHook } from '@testing-library/react-native'; + +import { useNavigationLink } from '../adapter'; + +jest.mock('@react-navigation/native', () => ({ + useLinkBuilder: jest.fn().mockReturnValue(jest.fn()), +})); + +describe('React Navigation adapter', () => { + describe('when v6 is used', () => { + it('should use useLinkBuilder() to create link', () => { + let builder; + + renderHook(() => { + builder = useNavigationLink(); + builder('routeKey'); + }); + + expect(useLinkTools).toBeUndefined(); + expect(useLinkBuilder).toHaveBeenCalled(); + expect(builder).toHaveBeenCalledWith('routeKey'); + }); + }); +}); diff --git a/src/react-navigation/__tests__/adapter.v7.test.ts b/src/react-navigation/__tests__/adapter.v7.test.ts new file mode 100644 index 0000000000..4bc11e678e --- /dev/null +++ b/src/react-navigation/__tests__/adapter.v7.test.ts @@ -0,0 +1,26 @@ +// @ts-ignore +import { useLinkTools, useLinkBuilder } from '@react-navigation/native'; +import { renderHook } from '@testing-library/react-native'; + +import { useNavigationLink } from '../adapter'; + +jest.mock('@react-navigation/native', () => ({ + useLinkTools: jest.fn().mockReturnValue({ buildHref: jest.fn() }), +})); + +describe('React Navigation adapter', () => { + describe('when v7 is used', () => { + it('should use useLinkTools() to create link', () => { + let builder; + + renderHook(() => { + builder = useNavigationLink(); + builder('routeKey'); + }); + + expect(useLinkTools).toHaveBeenCalled(); + expect(useLinkBuilder).toBeUndefined(); + expect(builder).toHaveBeenCalledWith('routeKey'); + }); + }); +}); diff --git a/src/react-navigation/adapter.ts b/src/react-navigation/adapter.ts new file mode 100644 index 0000000000..30131b8012 --- /dev/null +++ b/src/react-navigation/adapter.ts @@ -0,0 +1,24 @@ +import { + // @ts-ignore: this hook is not available in React Navigation v7 + useLinkBuilder, + // @ts-ignore: this hook is not available in React Navigation v6 + useLinkTools, +} from '@react-navigation/native'; + +function useV7LinkBuilder() { + const tools = useLinkTools(); + return tools.buildHref; +} + +type NavigationLink = () => ( + name: string, + params?: object +) => string | undefined; + +/** + * In React Navigation 7 `useLinkBuilder` was superseded by `useLinkTools` + * https://reactnavigation.org/docs/7.x/upgrading-from-6.x/#the-uselinkto-and-uselinkbuilder-hooks-are-merged-into-uselinktools + **/ + +export const useNavigationLink: NavigationLink = + typeof useLinkTools !== 'undefined' ? useV7LinkBuilder : useLinkBuilder; diff --git a/src/react-navigation/navigators/createMaterialBottomTabNavigator.tsx b/src/react-navigation/navigators/createMaterialBottomTabNavigator.tsx index 975b83172b..5f1b031719 100644 --- a/src/react-navigation/navigators/createMaterialBottomTabNavigator.tsx +++ b/src/react-navigation/navigators/createMaterialBottomTabNavigator.tsx @@ -3,11 +3,14 @@ import * as React from 'react'; import { createNavigatorFactory, DefaultNavigatorOptions, + NavigatorTypeBagBase, ParamListBase, + StaticConfig, TabActionHelpers, TabNavigationState, TabRouter, TabRouterOptions, + TypedNavigator, useNavigationBuilder, } from '@react-navigation/native'; @@ -18,11 +21,14 @@ import type { } from '../types'; import MaterialBottomTabView from '../views/MaterialBottomTabView'; +// Based on: https://github.com/react-navigation/react-navigation/blob/main/packages/bottom-tabs/src/types.tsx#L445-L454 export type MaterialBottomTabNavigatorProps = DefaultNavigatorOptions< ParamListBase, + undefined, TabNavigationState, MaterialBottomTabNavigationOptions, - MaterialBottomTabNavigationEventMap + MaterialBottomTabNavigationEventMap, + typeof MaterialBottomTabView > & TabRouterOptions & MaterialBottomTabNavigationConfig; @@ -64,9 +70,22 @@ function MaterialBottomTabNavigator({ ); } -export default createNavigatorFactory< - TabNavigationState, - MaterialBottomTabNavigationOptions, - MaterialBottomTabNavigationEventMap, - typeof MaterialBottomTabNavigator ->(MaterialBottomTabNavigator); +// Based on: https://github.com/react-navigation/react-navigation/blob/main/packages/material-top-tabs/src/navigators/createMaterialTopTabNavigator.tsx#L65-L86 +export default function < + const ParamList extends ParamListBase, + const NavigatorID extends string | undefined, + const TypeBag extends NavigatorTypeBagBase = { + ParamList: ParamList; + NavigatorID: NavigatorID; + State: TabNavigationState; + ScreenOptions: MaterialBottomTabNavigationOptions; + EventMap: MaterialBottomTabNavigationEventMap; + NavigationList: { + [RouteName in keyof ParamList]: MaterialBottomTabNavigatorProps; + }; + Navigator: typeof MaterialBottomTabNavigator; + }, + const Config extends StaticConfig = StaticConfig +>(config?: Config): TypedNavigator { + return createNavigatorFactory(MaterialBottomTabNavigator)(config); +} diff --git a/src/react-navigation/views/MaterialBottomTabView.tsx b/src/react-navigation/views/MaterialBottomTabView.tsx index df846ae2fb..f4a6da42b1 100644 --- a/src/react-navigation/views/MaterialBottomTabView.tsx +++ b/src/react-navigation/views/MaterialBottomTabView.tsx @@ -7,11 +7,11 @@ import { ParamListBase, Route, TabNavigationState, - useLinkBuilder, } from '@react-navigation/native'; import BottomNavigation from '../../components/BottomNavigation/BottomNavigation'; import MaterialCommunityIcon from '../../components/MaterialCommunityIcon'; +import { useNavigationLink } from '../adapter'; import type { MaterialBottomTabDescriptorMap, MaterialBottomTabNavigationConfig, @@ -29,7 +29,7 @@ export default function MaterialBottomTabView({ descriptors, ...rest }: Props) { - const buildLink = useLinkBuilder(); + const buildLink = useNavigationLink(); return ( =16.8" - checksum: 10c0/514604ad33cb2f040ad3ece8ee787cc549ec92135ee12ebc6857bb309eeacf5d66426b3b94089842e8215a252d5ca6f04b059387f681688fb70ac37b238209f9 - languageName: node - linkType: hard - -"use-latest-callback@npm:^0.2.3": +"use-latest-callback@npm:^0.2.1, use-latest-callback@npm:^0.2.3": version: 0.2.3 resolution: "use-latest-callback@npm:0.2.3" peerDependencies: From b704e451fc54ff61d8c84a61169a3276a7d1fce2 Mon Sep 17 00:00:00 2001 From: lukewalczak Date: Wed, 23 Apr 2025 20:55:35 +0200 Subject: [PATCH 2/8] chore: update example to navigation v7 --- example/package.json | 8 +- .../Examples/ThemingWithReactNavigation.tsx | 2 +- example/src/RootNavigator.tsx | 48 ++--- example/src/index.native.tsx | 65 ++++--- example/src/index.tsx | 63 +++---- example/yarn.lock | 178 +++++++++++------- 6 files changed, 200 insertions(+), 164 deletions(-) diff --git a/example/package.json b/example/package.json index 1fa8e61959..eb785c7900 100644 --- a/example/package.json +++ b/example/package.json @@ -19,10 +19,10 @@ "@pchmn/expo-material3-theme": "^1.3.2", "@react-native-async-storage/async-storage": "1.23.1", "@react-native-masked-view/masked-view": "0.3.2", - "@react-navigation/bottom-tabs": "^6.6.1", - "@react-navigation/drawer": "^6.7.2", - "@react-navigation/native": "^6.1.18", - "@react-navigation/stack": "^6.4.1", + "@react-navigation/bottom-tabs": "^7.3.10", + "@react-navigation/drawer": "^7.3.9", + "@react-navigation/native": "^7.1.6", + "@react-navigation/stack": "^7.2.10", "expo": "^52.0.0", "expo-crypto": "~14.0.1", "expo-dev-client": "~5.0.4", diff --git a/example/src/Examples/ThemingWithReactNavigation.tsx b/example/src/Examples/ThemingWithReactNavigation.tsx index acd9e67e9a..ab78b6a497 100644 --- a/example/src/Examples/ThemingWithReactNavigation.tsx +++ b/example/src/Examples/ThemingWithReactNavigation.tsx @@ -56,7 +56,7 @@ const HomeTab = () => { function ThemingWithReactNavigation() { return ( - + ); diff --git a/example/src/RootNavigator.tsx b/example/src/RootNavigator.tsx index 49280dcff8..26eb57ea84 100644 --- a/example/src/RootNavigator.tsx +++ b/example/src/RootNavigator.tsx @@ -2,7 +2,6 @@ import * as React from 'react'; import { Platform, StyleSheet, View } from 'react-native'; import type { DrawerNavigationProp } from '@react-navigation/drawer'; -import { getHeaderTitle } from '@react-navigation/elements'; import { CardStyleInterpolators, createStackNavigator, @@ -21,33 +20,26 @@ export default function Root() { return ( { - return { - detachPreviousScreen: !navigation.isFocused(), - cardStyleInterpolator, - header: ({ navigation, route, options, back }) => { - const title = getHeaderTitle(options, route.name); - return ( - - {back ? ( - navigation.goBack()} /> - ) : (navigation as any).openDrawer ? ( - - ( - navigation as any as DrawerNavigationProp<{}> - ).openDrawer() - } - /> - ) : null} - - - ); - }, - }; - }} + screenOptions={({ navigation }) => ({ + detachPreviousScreen: !navigation.isFocused(), + cardStyleInterpolator, + header: ({ navigation, route, options, back }) => ( + + {back ? ( + navigation.goBack()} /> + ) : (navigation as any).openDrawer ? ( + + (navigation as any as DrawerNavigationProp<{}>).openDrawer() + } + /> + ) : null} + + + ), + })} > - - - AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state)) - } - > - - {(insets) => { - const { left, right } = insets || { left: 0, right: 0 }; - const collapsedDrawerWidth = 80 + Math.max(left, right); - return ( - } - > - - - ); - }} - - - - + + AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state)) + } + > + + {(insets) => { + const { left, right } = insets || { left: 0, right: 0 }; + const collapsedDrawerWidth = 100 + Math.max(left, right); + return ( + } + > + + + ); + }} + + + ); diff --git a/example/src/index.tsx b/example/src/index.tsx index 750bc815c6..a8d2474699 100644 --- a/example/src/index.tsx +++ b/example/src/index.tsx @@ -156,38 +156,37 @@ export default function PaperExample() { theme={customFontLoaded ? configuredFontTheme : theme} > - - - AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state)) - } - > - - {(insets) => { - const { left, right } = insets || { left: 0, right: 0 }; - const collapsedDrawerWidth = 80 + Math.max(left, right); - return ( - } - > - - - ); - }} - - - + + AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state)) + } + > + + {(insets) => { + const { left, right } = insets || { left: 0, right: 0 }; + const collapsedDrawerWidth = 100 + Math.max(left, right); + return ( + } + > + + + ); + }} + + ); diff --git a/example/yarn.lock b/example/yarn.lock index 25e62483a9..348598e6c1 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -2735,109 +2735,116 @@ __metadata: languageName: node linkType: hard -"@react-navigation/bottom-tabs@npm:^6.6.1": - version: 6.6.1 - resolution: "@react-navigation/bottom-tabs@npm:6.6.1" +"@react-navigation/bottom-tabs@npm:^7.3.10": + version: 7.3.10 + resolution: "@react-navigation/bottom-tabs@npm:7.3.10" dependencies: - "@react-navigation/elements": "npm:^1.3.31" + "@react-navigation/elements": "npm:^2.3.8" color: "npm:^4.2.3" - warn-once: "npm:^0.1.0" peerDependencies: - "@react-navigation/native": ^6.0.0 - react: "*" + "@react-navigation/native": ^7.1.6 + react: ">= 18.2.0" react-native: "*" - react-native-safe-area-context: ">= 3.0.0" - react-native-screens: ">= 3.0.0" - checksum: 10c0/871bd824f8aa210718d8bfdc875cc840dbaeec86da67846cd34de8bbccfff837c7d825e257d2b0b5762b38f01ea785f95c54001e08e086fe7ea9234fefaec21f + react-native-safe-area-context: ">= 4.0.0" + react-native-screens: ">= 4.0.0" + checksum: 10c0/17adb7ebc38fd6d99866cf365f93099e5e83efdc0203051f3bbd7f731791f04c4323901d81ea2af9bb82a7ad22ff547d8a31f395a19de6160b7a3596990b1191 languageName: node linkType: hard -"@react-navigation/core@npm:^6.4.17": - version: 6.4.17 - resolution: "@react-navigation/core@npm:6.4.17" +"@react-navigation/core@npm:^7.8.5": + version: 7.8.5 + resolution: "@react-navigation/core@npm:7.8.5" dependencies: - "@react-navigation/routers": "npm:^6.1.9" + "@react-navigation/routers": "npm:^7.3.5" escape-string-regexp: "npm:^4.0.0" - nanoid: "npm:^3.1.23" + nanoid: "npm:3.3.8" query-string: "npm:^7.1.3" - react-is: "npm:^16.13.0" + react-is: "npm:^18.2.0" use-latest-callback: "npm:^0.2.1" + use-sync-external-store: "npm:^1.2.2" peerDependencies: - react: "*" - checksum: 10c0/f71fc10dd34d0bd13abd9c947833b9f5ac6aa18af58f74aa72bac45a5d348a4881924fec6534e7694d7084d940d7dac368a1b1fe7d86b260ff5cf475498e9d1b + react: ">= 18.2.0" + checksum: 10c0/3c26d39cd1186a8a78805b6a19e9fcea9747b6c0fdeb470b5aced0dc56cd209040880a874df71731cb4b426b31aa6e4efb0e9d3ced0b52009afc5676352f7bb7 languageName: node linkType: hard -"@react-navigation/drawer@npm:^6.7.2": - version: 6.7.2 - resolution: "@react-navigation/drawer@npm:6.7.2" +"@react-navigation/drawer@npm:^7.3.9": + version: 7.3.9 + resolution: "@react-navigation/drawer@npm:7.3.9" dependencies: - "@react-navigation/elements": "npm:^1.3.31" + "@react-navigation/elements": "npm:^2.3.8" color: "npm:^4.2.3" - warn-once: "npm:^0.1.0" + react-native-drawer-layout: "npm:^4.1.6" + use-latest-callback: "npm:^0.2.1" peerDependencies: - "@react-navigation/native": ^6.0.0 - react: "*" + "@react-navigation/native": ^7.1.6 + react: ">= 18.2.0" react-native: "*" - react-native-gesture-handler: ">= 1.0.0" - react-native-reanimated: ">= 1.0.0" - react-native-safe-area-context: ">= 3.0.0" - react-native-screens: ">= 3.0.0" - checksum: 10c0/33d0e1b84b2b630f516d27553a12465a6b28576b79293976200c8da1c3a08ec315c59fbb3a337e8ed736715316e61526c4b0914e35c36449fb4478d7db074282 + react-native-gesture-handler: ">= 2.0.0" + react-native-reanimated: ">= 2.0.0" + react-native-safe-area-context: ">= 4.0.0" + react-native-screens: ">= 4.0.0" + checksum: 10c0/00a793c69628b62e946f1b7f427e9bffcf9cb56793f0990750e045a313bb76997158b03b3161b6f79daf419fc436c82dfbbe438440fa13af367214247bc24b41 languageName: node linkType: hard -"@react-navigation/elements@npm:^1.3.31": - version: 1.3.31 - resolution: "@react-navigation/elements@npm:1.3.31" +"@react-navigation/elements@npm:^2.3.8": + version: 2.3.8 + resolution: "@react-navigation/elements@npm:2.3.8" + dependencies: + color: "npm:^4.2.3" peerDependencies: - "@react-navigation/native": ^6.0.0 - react: "*" + "@react-native-masked-view/masked-view": ">= 0.2.0" + "@react-navigation/native": ^7.1.6 + react: ">= 18.2.0" react-native: "*" - react-native-safe-area-context: ">= 3.0.0" - checksum: 10c0/4a2e6ee67d1954e8424c437fce83c6268cfa8bea22b1a593b4bfe59ed69deeeba3a1d7285880b3b1b100ffe9b542a81e824cddb53550c4dcf6bd6c29ccb16b97 + react-native-safe-area-context: ">= 4.0.0" + peerDependenciesMeta: + "@react-native-masked-view/masked-view": + optional: true + checksum: 10c0/8fc053bc853f2c588f3275b891a1e06d2638881e785bfa0b5086d2c813a2577992eb91cc080a3d723779ce6d8fd21242eaffdd6ac8e474743c2788a93509d6d5 languageName: node linkType: hard -"@react-navigation/native@npm:^6.1.18": - version: 6.1.18 - resolution: "@react-navigation/native@npm:6.1.18" +"@react-navigation/native@npm:^7.1.6": + version: 7.1.6 + resolution: "@react-navigation/native@npm:7.1.6" dependencies: - "@react-navigation/core": "npm:^6.4.17" + "@react-navigation/core": "npm:^7.8.5" escape-string-regexp: "npm:^4.0.0" fast-deep-equal: "npm:^3.1.3" - nanoid: "npm:^3.1.23" + nanoid: "npm:3.3.8" + use-latest-callback: "npm:^0.2.1" peerDependencies: - react: "*" + react: ">= 18.2.0" react-native: "*" - checksum: 10c0/1f7138da298067f537a22c5ab2e8e8529e83df8f87c5c61e84afdcd49d6ba1409f44a33bac3bd08bb11bcfba3f1c84574b7aa0a67b28531e4520d485bd4e3b9b + checksum: 10c0/f0f78885662bb20d48e31a9f7ffae24c90de43b1b902aee87ffe2e9e510e69d0417202e20f97077d41ab42170665eb4c2815a271758134701175ffe39374e166 languageName: node linkType: hard -"@react-navigation/routers@npm:^6.1.9": - version: 6.1.9 - resolution: "@react-navigation/routers@npm:6.1.9" +"@react-navigation/routers@npm:^7.3.5": + version: 7.3.5 + resolution: "@react-navigation/routers@npm:7.3.5" dependencies: - nanoid: "npm:^3.1.23" - checksum: 10c0/5b58014cf29bb71c7dc01201e271d55f0ecfe6d38d064179eeff0fc0b5cb739d4d9906eb133f100d25fc674c72c24aa65d5f6bfc3d036d79f7c5d1936391c605 + nanoid: "npm:3.3.8" + checksum: 10c0/f94f70391b5429707932a5c49014371815da20d715f15911c556bf606a8519d0f43382e0983c6c96cf2a40ddd8c2f8ecf38e33dcb9a65eb8cb02bdf377fccdd9 languageName: node linkType: hard -"@react-navigation/stack@npm:^6.4.1": - version: 6.4.1 - resolution: "@react-navigation/stack@npm:6.4.1" +"@react-navigation/stack@npm:^7.2.10": + version: 7.2.10 + resolution: "@react-navigation/stack@npm:7.2.10" dependencies: - "@react-navigation/elements": "npm:^1.3.31" + "@react-navigation/elements": "npm:^2.3.8" color: "npm:^4.2.3" - warn-once: "npm:^0.1.0" peerDependencies: - "@react-navigation/native": ^6.0.0 - react: "*" + "@react-navigation/native": ^7.1.6 + react: ">= 18.2.0" 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 + react-native-gesture-handler: ">= 2.0.0" + react-native-safe-area-context: ">= 4.0.0" + react-native-screens: ">= 4.0.0" + checksum: 10c0/c73521b38c59bc723e7d9cbf30675c6032c23a02c0974737b9f7b479821f7e7a5378621833664cab2745efd923ac553d46f8566494ed02602bbf155e69ca95cb languageName: node linkType: hard @@ -8583,7 +8590,16 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.1.23, nanoid@npm:^3.3.6": +"nanoid@npm:3.3.8": + version: 3.3.8 + resolution: "nanoid@npm:3.3.8" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/4b1bb29f6cfebf3be3bc4ad1f1296fb0a10a3043a79f34fbffe75d1621b4318319211cd420549459018ea3592f0d2f159247a6f874911d6d26eaaadda2478120 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.6": version: 3.3.6 resolution: "nanoid@npm:3.3.6" bin: @@ -9915,7 +9931,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.13.0, react-is@npm:^16.13.1, react-is@npm:^16.7.0": +"react-is@npm:^16.13.1, react-is@npm:^16.7.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: 10c0/33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1 @@ -9929,6 +9945,27 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^18.2.0": + version: 18.3.1 + resolution: "react-is@npm:18.3.1" + checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 + languageName: node + linkType: hard + +"react-native-drawer-layout@npm:^4.1.6": + version: 4.1.6 + resolution: "react-native-drawer-layout@npm:4.1.6" + dependencies: + use-latest-callback: "npm:^0.2.1" + peerDependencies: + react: ">= 18.2.0" + react-native: "*" + react-native-gesture-handler: ">= 2.0.0" + react-native-reanimated: ">= 2.0.0" + checksum: 10c0/d3089dbcf0fa418d7f675b4c41bd8a10e4a866e73c3b2f3b716baa50876ab4329107f6caa3543331d3ce55edc8767024965b890fbd26893e88e291b27d952c2e + languageName: node + linkType: hard + "react-native-gesture-handler@npm:~2.20.2": version: 2.20.2 resolution: "react-native-gesture-handler@npm:2.20.2" @@ -9954,10 +9991,10 @@ __metadata: "@pchmn/expo-material3-theme": "npm:^1.3.2" "@react-native-async-storage/async-storage": "npm:1.23.1" "@react-native-masked-view/masked-view": "npm:0.3.2" - "@react-navigation/bottom-tabs": "npm:^6.6.1" - "@react-navigation/drawer": "npm:^6.7.2" - "@react-navigation/native": "npm:^6.1.18" - "@react-navigation/stack": "npm:^6.4.1" + "@react-navigation/bottom-tabs": "npm:^7.3.10" + "@react-navigation/drawer": "npm:^7.3.9" + "@react-navigation/native": "npm:^7.1.6" + "@react-navigation/stack": "npm:^7.2.10" babel-plugin-module-resolver: "npm:^5.0.0" babel-preset-expo: "npm:~12.0.0" expo: "npm:^52.0.0" @@ -11817,6 +11854,15 @@ __metadata: languageName: node linkType: hard +"use-sync-external-store@npm:^1.2.2": + version: 1.5.0 + resolution: "use-sync-external-store@npm:1.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/1b8663515c0be34fa653feb724fdcce3984037c78dd4a18f68b2c8be55cc1a1084c578d5b75f158d41b5ddffc2bf5600766d1af3c19c8e329bb20af2ec6f52f4 + languageName: node + linkType: hard + "util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" From c97dd7551923de667af7450ce97d771ac4ed687c Mon Sep 17 00:00:00 2001 From: lukewalczak Date: Tue, 29 Apr 2025 10:37:46 +0200 Subject: [PATCH 3/8] chore: drop support for createMaterialBottomTabNavigator --- docs/docs/guides/09-bottom-navigation.md | 37 +++++++- example/src/ExampleList.tsx | 2 - .../MaterialBottomTabNavigatorExample.tsx | 87 ------------------- .../__tests__/adapter.v6.test.ts | 26 ------ .../__tests__/adapter.v7.test.ts | 26 ------ src/react-navigation/adapter.ts | 24 ----- .../createMaterialBottomTabNavigator.tsx | 36 +++----- .../views/MaterialBottomTabView.tsx | 4 +- 8 files changed, 47 insertions(+), 195 deletions(-) delete mode 100644 example/src/Examples/MaterialBottomTabNavigatorExample.tsx delete mode 100644 src/react-navigation/__tests__/adapter.v6.test.ts delete mode 100644 src/react-navigation/__tests__/adapter.v7.test.ts delete mode 100644 src/react-navigation/adapter.ts diff --git a/docs/docs/guides/09-bottom-navigation.md b/docs/docs/guides/09-bottom-navigation.md index ba3bc33f3a..f7f762212d 100644 --- a/docs/docs/guides/09-bottom-navigation.md +++ b/docs/docs/guides/09-bottom-navigation.md @@ -2,6 +2,39 @@ title: Using BottomNavigation with React Navigation --- +:::caution +`createMaterialBottomTabNavigator` has been deprecated since `react-native-paper@5.14.0`. Please use `@react-navigation/bottom-tabs` version `7.x` or higher and combine it with `BottomNavigation.Bar` for a Material Design look. + +```js +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { BottomNavigation } from 'react-native-paper'; + +const Tab = createBottomTabNavigator(); + +function MyTabs() { + return ( + ( + + )} + > + + + + ); +} +``` + +::: + +> 👉 For a complete example please visit `createBottomTabNavigator` [snack](https://snack.expo.dev/@react-native-paper/createbottomtabnavigator-with-bottomnavigationbar). + +--- + +_For projects using `react-native-paper` < `5.14.0` and `react-navigation` < `7.x`, proceed as follows:_ + A material-design themed tab bar on the bottom of the screen that lets you switch between different routes with animation. Routes are lazily initialized - their screen components are not mounted until they are first focused. This wraps the [`BottomNavigation`](https://callstack.github.io/react-native-paper/docs/components/BottomNavigation/) component from `react-native-paper`, however if you [configure the Babel plugin](https://callstack.github.io/react-native-paper/docs/guides/getting-started/), it won't include the whole library in your bundle. @@ -12,7 +45,7 @@ This wraps the [`BottomNavigation`](https://callstack.github.io/react-native-pap To use this navigator, ensure that you have [`@react-navigation/native` and its dependencies (follow this guide)](https://reactnavigation.org/docs/getting-started): ::: -> For a complete example please visit `createMaterialBottomTabNavigator` [snack](https://snack.expo.dev/@react-native-paper/creatematerialbottomtabnavigator) +> 👉 For a complete example please visit `createMaterialBottomTabNavigator` [snack](https://snack.expo.dev/@react-native-paper/creatematerialbottomtabnavigator) ## API Definition @@ -33,7 +66,7 @@ function MyTabs() { } ``` -> For a complete usage guide please visit [Tab Navigation](https://reactnavigation.org/docs/tab-based-navigation/) +> 👉 For a complete usage guide please visit [Tab Navigation](https://reactnavigation.org/docs/tab-based-navigation/) ### Props diff --git a/example/src/ExampleList.tsx b/example/src/ExampleList.tsx index 3fc4483c63..9ac7c0ae34 100644 --- a/example/src/ExampleList.tsx +++ b/example/src/ExampleList.tsx @@ -28,7 +28,6 @@ import ListAccordionExample from './Examples/ListAccordionExample'; import ListAccordionExampleGroup from './Examples/ListAccordionGroupExample'; import ListItemExample from './Examples/ListItemExample'; import ListSectionExample from './Examples/ListSectionExample'; -import MaterialBottomTabNavigatorExample from './Examples/MaterialBottomTabNavigatorExample'; import MenuExample from './Examples/MenuExample'; import ProgressBarExample from './Examples/ProgressBarExample'; import RadioButtonExample from './Examples/RadioButtonExample'; @@ -80,7 +79,6 @@ export const mainExamples: Record< listAccordionGroup: ListAccordionExampleGroup, listSection: ListSectionExample, listItem: ListItemExample, - materialBottomTabNavigator: MaterialBottomTabNavigatorExample, menu: MenuExample, progressbar: ProgressBarExample, radio: RadioButtonExample, diff --git a/example/src/Examples/MaterialBottomTabNavigatorExample.tsx b/example/src/Examples/MaterialBottomTabNavigatorExample.tsx deleted file mode 100644 index cdddeb8369..0000000000 --- a/example/src/Examples/MaterialBottomTabNavigatorExample.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React from 'react'; -import { StyleSheet, Text, View } from 'react-native'; - -import Icon from '@expo/vector-icons/MaterialCommunityIcons'; - -import { createMaterialBottomTabNavigator } from '../../../src/react-navigation'; - -const Tab = createMaterialBottomTabNavigator(); - -export default function MaterialBottomTabNavigatorExample() { - return ( - - ( - - ), - }} - /> - ( - - ), - }} - /> - ( - - ), - }} - /> - - ); -} - -MaterialBottomTabNavigatorExample.title = 'Material Bottom Tab Navigator'; - -function Feed() { - return ( - - Feed! - - ); -} - -function Profile() { - return ( - - Profile! - - ); -} - -function Notifications() { - return ( - - Notifications! - - ); -} - -const styles = StyleSheet.create({ - screen: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - // eslint-disable-next-line react-native/no-color-literals - tabs: { - backgroundColor: 'tomato', - }, -}); diff --git a/src/react-navigation/__tests__/adapter.v6.test.ts b/src/react-navigation/__tests__/adapter.v6.test.ts deleted file mode 100644 index 37d3525374..0000000000 --- a/src/react-navigation/__tests__/adapter.v6.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -// @ts-ignore -import { useLinkTools, useLinkBuilder } from '@react-navigation/native'; -import { renderHook } from '@testing-library/react-native'; - -import { useNavigationLink } from '../adapter'; - -jest.mock('@react-navigation/native', () => ({ - useLinkBuilder: jest.fn().mockReturnValue(jest.fn()), -})); - -describe('React Navigation adapter', () => { - describe('when v6 is used', () => { - it('should use useLinkBuilder() to create link', () => { - let builder; - - renderHook(() => { - builder = useNavigationLink(); - builder('routeKey'); - }); - - expect(useLinkTools).toBeUndefined(); - expect(useLinkBuilder).toHaveBeenCalled(); - expect(builder).toHaveBeenCalledWith('routeKey'); - }); - }); -}); diff --git a/src/react-navigation/__tests__/adapter.v7.test.ts b/src/react-navigation/__tests__/adapter.v7.test.ts deleted file mode 100644 index 4bc11e678e..0000000000 --- a/src/react-navigation/__tests__/adapter.v7.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -// @ts-ignore -import { useLinkTools, useLinkBuilder } from '@react-navigation/native'; -import { renderHook } from '@testing-library/react-native'; - -import { useNavigationLink } from '../adapter'; - -jest.mock('@react-navigation/native', () => ({ - useLinkTools: jest.fn().mockReturnValue({ buildHref: jest.fn() }), -})); - -describe('React Navigation adapter', () => { - describe('when v7 is used', () => { - it('should use useLinkTools() to create link', () => { - let builder; - - renderHook(() => { - builder = useNavigationLink(); - builder('routeKey'); - }); - - expect(useLinkTools).toHaveBeenCalled(); - expect(useLinkBuilder).toBeUndefined(); - expect(builder).toHaveBeenCalledWith('routeKey'); - }); - }); -}); diff --git a/src/react-navigation/adapter.ts b/src/react-navigation/adapter.ts deleted file mode 100644 index 30131b8012..0000000000 --- a/src/react-navigation/adapter.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - // @ts-ignore: this hook is not available in React Navigation v7 - useLinkBuilder, - // @ts-ignore: this hook is not available in React Navigation v6 - useLinkTools, -} from '@react-navigation/native'; - -function useV7LinkBuilder() { - const tools = useLinkTools(); - return tools.buildHref; -} - -type NavigationLink = () => ( - name: string, - params?: object -) => string | undefined; - -/** - * In React Navigation 7 `useLinkBuilder` was superseded by `useLinkTools` - * https://reactnavigation.org/docs/7.x/upgrading-from-6.x/#the-uselinkto-and-uselinkbuilder-hooks-are-merged-into-uselinktools - **/ - -export const useNavigationLink: NavigationLink = - typeof useLinkTools !== 'undefined' ? useV7LinkBuilder : useLinkBuilder; diff --git a/src/react-navigation/navigators/createMaterialBottomTabNavigator.tsx b/src/react-navigation/navigators/createMaterialBottomTabNavigator.tsx index 5f1b031719..31768650eb 100644 --- a/src/react-navigation/navigators/createMaterialBottomTabNavigator.tsx +++ b/src/react-navigation/navigators/createMaterialBottomTabNavigator.tsx @@ -3,14 +3,11 @@ import * as React from 'react'; import { createNavigatorFactory, DefaultNavigatorOptions, - NavigatorTypeBagBase, ParamListBase, - StaticConfig, TabActionHelpers, TabNavigationState, TabRouter, TabRouterOptions, - TypedNavigator, useNavigationBuilder, } from '@react-navigation/native'; @@ -21,14 +18,11 @@ import type { } from '../types'; import MaterialBottomTabView from '../views/MaterialBottomTabView'; -// Based on: https://github.com/react-navigation/react-navigation/blob/main/packages/bottom-tabs/src/types.tsx#L445-L454 export type MaterialBottomTabNavigatorProps = DefaultNavigatorOptions< ParamListBase, - undefined, TabNavigationState, MaterialBottomTabNavigationOptions, - MaterialBottomTabNavigationEventMap, - typeof MaterialBottomTabView + MaterialBottomTabNavigationEventMap > & TabRouterOptions & MaterialBottomTabNavigationConfig; @@ -70,22 +64,12 @@ function MaterialBottomTabNavigator({ ); } -// Based on: https://github.com/react-navigation/react-navigation/blob/main/packages/material-top-tabs/src/navigators/createMaterialTopTabNavigator.tsx#L65-L86 -export default function < - const ParamList extends ParamListBase, - const NavigatorID extends string | undefined, - const TypeBag extends NavigatorTypeBagBase = { - ParamList: ParamList; - NavigatorID: NavigatorID; - State: TabNavigationState; - ScreenOptions: MaterialBottomTabNavigationOptions; - EventMap: MaterialBottomTabNavigationEventMap; - NavigationList: { - [RouteName in keyof ParamList]: MaterialBottomTabNavigatorProps; - }; - Navigator: typeof MaterialBottomTabNavigator; - }, - const Config extends StaticConfig = StaticConfig ->(config?: Config): TypedNavigator { - return createNavigatorFactory(MaterialBottomTabNavigator)(config); -} +/** + * @deprecated `createMaterialBottomTabNavigator` has been deprecated since `5.14.0`. Please use `@react-navigation/bottom-tabs` version `7.x` or higher instead. + */ +export default createNavigatorFactory< + TabNavigationState, + MaterialBottomTabNavigationOptions, + MaterialBottomTabNavigationEventMap, + typeof MaterialBottomTabNavigator +>(MaterialBottomTabNavigator); diff --git a/src/react-navigation/views/MaterialBottomTabView.tsx b/src/react-navigation/views/MaterialBottomTabView.tsx index f4a6da42b1..df846ae2fb 100644 --- a/src/react-navigation/views/MaterialBottomTabView.tsx +++ b/src/react-navigation/views/MaterialBottomTabView.tsx @@ -7,11 +7,11 @@ import { ParamListBase, Route, TabNavigationState, + useLinkBuilder, } from '@react-navigation/native'; import BottomNavigation from '../../components/BottomNavigation/BottomNavigation'; import MaterialCommunityIcon from '../../components/MaterialCommunityIcon'; -import { useNavigationLink } from '../adapter'; import type { MaterialBottomTabDescriptorMap, MaterialBottomTabNavigationConfig, @@ -29,7 +29,7 @@ export default function MaterialBottomTabView({ descriptors, ...rest }: Props) { - const buildLink = useNavigationLink(); + const buildLink = useLinkBuilder(); return ( Date: Tue, 29 Apr 2025 11:11:28 +0200 Subject: [PATCH 4/8] chore: improve deprecation comment --- .../navigators/createMaterialBottomTabNavigator.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/react-navigation/navigators/createMaterialBottomTabNavigator.tsx b/src/react-navigation/navigators/createMaterialBottomTabNavigator.tsx index 31768650eb..7b162554b3 100644 --- a/src/react-navigation/navigators/createMaterialBottomTabNavigator.tsx +++ b/src/react-navigation/navigators/createMaterialBottomTabNavigator.tsx @@ -65,7 +65,8 @@ function MaterialBottomTabNavigator({ } /** - * @deprecated `createMaterialBottomTabNavigator` has been deprecated since `5.14.0`. Please use `@react-navigation/bottom-tabs` version `7.x` or higher instead. + * @deprecated `createMaterialBottomTabNavigator` has been deprecated since `react-native-paper@5.14.0`. + * Please use `@react-navigation/bottom-tabs` version `7.x` or higher and combine it with `BottomNavigation.Bar` for a Material Design look. */ export default createNavigatorFactory< TabNavigationState, From fc7ad1444353740ae2b6932836ec8268b5a158dd Mon Sep 17 00:00:00 2001 From: lukewalczak Date: Wed, 30 Apr 2025 00:38:44 +0200 Subject: [PATCH 5/8] docs: provide static and dynamic examples --- docs/component-docs-plugin/generatePageMDX.js | 22 +- docs/docs/guides/09-bottom-navigation.md | 31 +-- docs/docusaurus.config.js | 2 + docs/src/components/ExtendedExample.tsx | 36 ++++ docs/src/data/extendedExamples.js | 17 ++ .../extendedExamples/BottomNavigationBar.js | 202 ++++++++++++++++++ .../BottomNavigation/BottomNavigationBar.tsx | 140 +++++------- 7 files changed, 328 insertions(+), 122 deletions(-) create mode 100644 docs/src/components/ExtendedExample.tsx create mode 100644 docs/src/data/extendedExamples.js create mode 100644 docs/src/data/extendedExamples/BottomNavigationBar.js diff --git a/docs/component-docs-plugin/generatePageMDX.js b/docs/component-docs-plugin/generatePageMDX.js index 2a6c05a084..0165ecdb8d 100644 --- a/docs/component-docs-plugin/generatePageMDX.js +++ b/docs/component-docs-plugin/generatePageMDX.js @@ -166,6 +166,22 @@ function generateExtendsAttributes(doc) { return extendsAttributes; } +function generateExtendedExamples(usage, extendedExamplesData) { + if (!extendedExamplesData) { + return usage; + } + + const data = JSON.parse(extendedExamplesData); + const exampleHeader = Object.keys(data)[0]; + + return ` + ${usage} + + ### ${exampleHeader} + + `; +} + function generatePageMDX(doc, link) { const summaryRegex = /([\s\S]*?)## Usage/; @@ -182,6 +198,9 @@ function generatePageMDX(doc, link) { const themeColorsData = JSON.stringify(customFields.themeColors[doc.title]); const screenshotData = JSON.stringify(customFields.screenshots[doc.title]); + const extendedExamplesData = JSON.stringify( + customFields.extendedExamples[doc.title] + ); const extendsAttributes = generateExtendsAttributes(doc); @@ -194,12 +213,13 @@ import PropTable from '@site/src/components/PropTable.tsx'; import ExtendsLink from '@site/src/components/ExtendsLink.tsx'; import ThemeColorsTable from '@site/src/components/ThemeColorsTable.tsx'; import ScreenshotTabs from '@site/src/components/ScreenshotTabs.tsx'; +import ExtendedExample from '@site/src/components/ExtendedExample.tsx'; ${summary} ${generateScreenshots(doc.title, screenshotData)} -${usage} +${generateExtendedExamples(usage, extendedExamplesData)} ${generatePropsTable(doc.data.props, link, extendsAttributes)} diff --git a/docs/docs/guides/09-bottom-navigation.md b/docs/docs/guides/09-bottom-navigation.md index f7f762212d..21776718c9 100644 --- a/docs/docs/guides/09-bottom-navigation.md +++ b/docs/docs/guides/09-bottom-navigation.md @@ -3,38 +3,11 @@ title: Using BottomNavigation with React Navigation --- :::caution -`createMaterialBottomTabNavigator` has been deprecated since `react-native-paper@5.14.0`. Please use `@react-navigation/bottom-tabs` version `7.x` or higher and combine it with `BottomNavigation.Bar` for a Material Design look. - -```js -import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; -import { BottomNavigation } from 'react-native-paper'; - -const Tab = createBottomTabNavigator(); - -function MyTabs() { - return ( - ( - - )} - > - - - - ); -} -``` +The `createMaterialBottomTabNavigator` has been deprecated as of `react-native-paper@5.14.0`. Instead, use `@react-navigation/bottom-tabs` version `7.x` or later, combined with `BottomNavigation.Bar` to achieve a Material Design look. +For implementation details, see the [dedicated example](https://callstack.github.io/react-native-paper/docs/components/BottomNavigation/BottomNavigationBar#with-react-navigation). ::: -> 👉 For a complete example please visit `createBottomTabNavigator` [snack](https://snack.expo.dev/@react-native-paper/createbottomtabnavigator-with-bottomnavigationbar). - ---- - -_For projects using `react-native-paper` < `5.14.0` and `react-navigation` < `7.x`, proceed as follows:_ - A material-design themed tab bar on the bottom of the screen that lets you switch between different routes with animation. Routes are lazily initialized - their screen components are not mounted until they are first focused. This wraps the [`BottomNavigation`](https://callstack.github.io/react-native-paper/docs/components/BottomNavigation/) component from `react-native-paper`, however if you [configure the Babel plugin](https://callstack.github.io/react-native-paper/docs/guides/getting-started/), it won't include the whole library in your bundle. diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 16cd59f009..886044db0e 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -5,6 +5,7 @@ const path = require('path'); const darkCodeTheme = require('prism-react-renderer/themes/dracula'); const lightCodeTheme = require('prism-react-renderer/themes/github'); +const { extendedExamples } = require('./src/data/extendedExamples.js'); const { screenshots } = require('./src/data/screenshots.js'); const { themeColors } = require('./src/data/themeColors.js'); @@ -336,6 +337,7 @@ const config = { }, themeColors, screenshots, + extendedExamples, }, }; diff --git a/docs/src/components/ExtendedExample.tsx b/docs/src/components/ExtendedExample.tsx new file mode 100644 index 0000000000..c3eac15fdf --- /dev/null +++ b/docs/src/components/ExtendedExample.tsx @@ -0,0 +1,36 @@ +import React from 'react'; + +//@ts-ignore +import CodeBlock from '@theme/CodeBlock'; +//@ts-ignore +import TabItem from '@theme/TabItem'; +//@ts-ignore +import Tabs from '@theme/Tabs'; + +interface ExtendedExampleProps { + extendedExamplesData: { + [key: string]: { + [key: string]: string; + }; + }; +} + +const ExtendedExample = ({ extendedExamplesData }: ExtendedExampleProps) => { + const example = Object.values(extendedExamplesData)[0]; + + if (!example) return null; + + const keys = Object.keys(example); + + return ( + + {keys.map((key) => ( + + {example[key]} + + ))} + + ); +}; + +export default ExtendedExample; diff --git a/docs/src/data/extendedExamples.js b/docs/src/data/extendedExamples.js new file mode 100644 index 0000000000..f7b196afff --- /dev/null +++ b/docs/src/data/extendedExamples.js @@ -0,0 +1,17 @@ +const { + staticCode, + dynamicCode, +} = require('./extendedExamples/BottomNavigationBar'); + +const extendedExamples = { + 'BottomNavigation.Bar': { + 'with React Navigation': { + static: staticCode, + dynamic: dynamicCode, + }, + }, +}; + +module.exports = { + extendedExamples, +}; diff --git a/docs/src/data/extendedExamples/BottomNavigationBar.js b/docs/src/data/extendedExamples/BottomNavigationBar.js new file mode 100644 index 0000000000..b694da0cc7 --- /dev/null +++ b/docs/src/data/extendedExamples/BottomNavigationBar.js @@ -0,0 +1,202 @@ +const staticCode = `import { Text, View } from 'react-native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Provider, BottomNavigation } from 'react-native-paper'; +import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons'; +import { + CommonActions, + createStaticNavigation, +} from '@react-navigation/native'; + +function HomeScreen() { + return ( + + Home! + + ); +} + +function SettingsScreen() { + return ( + + Settings! + + ); +} + +const MyTabs = createBottomTabNavigator({ + screenOptions: { + animation: 'shift', + }, + tabBar: ({ navigation, state, descriptors, insets }) => ( + { + const event = navigation.emit({ + type: 'tabPress', + target: route.key, + canPreventDefault: true, + }); + + if (event.defaultPrevented) { + preventDefault(); + } else { + navigation.dispatch({ + ...CommonActions.navigate(route.name, route.params), + target: state.key, + }); + } + }} + renderIcon={({ route, focused, color }) => + descriptors[route.key].options.tabBarIcon?.({ + focused, + color, + size: 24, + }) || null + } + getLabelText={({ route }) => { + const { options } = descriptors[route.key]; + const label = + typeof options.tabBarLabel === 'string' + ? options.tabBarLabel + : typeof options.title === 'string' + ? options.title + : route.name; + + return label; + }} + /> + ), + screens: { + Home: { + screen: HomeScreen, + options: { + tabBarIcon: ({ color }) => ( + + ), + }, + }, + Profile: { + screen: SettingsScreen, + options: { + tabBarIcon: ({ color }) => ( + + ), + }, + }, + }, +}); + +const Navigation = createStaticNavigation(MyTabs); + +export default function App() { + return ( + + + + + + ); +}`; + +const dynamicCode = `import { Text, View } from 'react-native'; +import { NavigationContainer, CommonActions } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Provider, BottomNavigation } from 'react-native-paper'; +import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons'; + +function HomeScreen() { + return ( + + Home! + + ); +} + +function SettingsScreen() { + return ( + + Settings! + + ); +} + +const Tab = createBottomTabNavigator(); + +export default function App() { + return ( + + + ( + { + const event = navigation.emit({ + type: 'tabPress', + target: route.key, + canPreventDefault: true, + }); + + if (event.defaultPrevented) { + preventDefault(); + } else { + navigation.dispatch({ + ...CommonActions.navigate(route.name, route.params), + target: state.key, + }); + } + }} + renderIcon={({ route, focused, color }) => + descriptors[route.key].options.tabBarIcon?.({ + focused, + color, + size: 24, + }) || null + } + getLabelText={({ route }) => { + const { options } = descriptors[route.key]; + const label = + typeof options.tabBarLabel === 'string' + ? options.tabBarLabel + : typeof options.title === 'string' + ? options.title + : route.name; + + return label; + }} + /> + )}> + ( + + ), + }} + /> + ( + + ), + }} + /> + + + + ); +} +`; + +module.exports = { + staticCode, + dynamicCode, +}; diff --git a/src/components/BottomNavigation/BottomNavigationBar.tsx b/src/components/BottomNavigation/BottomNavigationBar.tsx index b782560537..e5fa8f980a 100644 --- a/src/components/BottomNavigation/BottomNavigationBar.tsx +++ b/src/components/BottomNavigation/BottomNavigationBar.tsx @@ -248,112 +248,68 @@ const Touchable = ({ * A navigation bar which can easily be integrated with [React Navigation's Bottom Tabs Navigator](https://reactnavigation.org/docs/bottom-tab-navigator/). * * ## Usage + * ### without React Navigation * ```js * import React from 'react'; - * import { View, StyleSheet } from 'react-native'; - * - * import { CommonActions } from '@react-navigation/native'; - * import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; - * import { Text, BottomNavigation } from 'react-native-paper'; - * import Icon from '@react-native-vector-icons/material-design-icons'; - * - * const Tab = createBottomTabNavigator(); - * - * export default function MyComponent() { - * return ( - * ( - * { - * const event = navigation.emit({ - * type: 'tabPress', - * target: route.key, - * canPreventDefault: true, - * }); - * - * if (event.defaultPrevented) { - * preventDefault(); - * } else { - * navigation.dispatch({ - * ...CommonActions.navigate(route.name, route.params), - * target: state.key, - * }); - * } - * }} - * renderIcon={({ route, focused, color }) => { - * const { options } = descriptors[route.key]; - * if (options.tabBarIcon) { - * return options.tabBarIcon({ focused, color, size: 24 }); - * } - * - * return null; - * }} - * getLabelText={({ route }) => { - * const { options } = descriptors[route.key]; - * const label = - * typeof options.tabBarLabel === 'string' - * ? options.tabBarLabel - * : typeof options.title === 'string' - * ? options.title - * : route.name; - * - * return label; - * }} - * /> - * )} - * > - * { - * return ; - * }, - * }} - * /> - * { - * return ; - * }, - * }} - * /> - * - * ); - * } + * import { useState } from 'react'; + * import { View } from 'react-native'; + * import { BottomNavigation, Text, Provider } from 'react-native-paper'; + * import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons' * * function HomeScreen() { * return ( - * - * Home! + * + * Home! * * ); * } * * function SettingsScreen() { * return ( - * - * Settings! - * + * + * Settings! + * * ); * } * - * const styles = StyleSheet.create({ - * container: { - * flex: 1, - * justifyContent: 'center', - * alignItems: 'center', - * }, - * }); + * export default function MyComponent() { + * const [index, setIndex] = useState(0); + * + * const routes = [ + * { key: 'home', title: 'Home', icon: 'home' }, + * { key: 'settings', title: 'Settings', icon: 'cog' }, + * ]; + + * const renderScene = ({ route }) => { + * switch (route.key) { + * case 'home': + * return ; + * case 'settings': + * return ; + * default: + * return null; + * } + * }; + * + * return ( + * + * {renderScene({ route: routes[index] })} + * { + * const newIndex = routes.findIndex((r) => r.key === route.key); + * if (newIndex !== -1) { + * setIndex(newIndex); + * } + * }} + * renderIcon={({ route, color }) => ( + * + * )} + * getLabelText={({ route }) => route.title} + * /> + * + * ); + * } * ``` */ const BottomNavigationBar = ({ From f59ca03b599a2e7a29b3995253afcafd7733a0c6 Mon Sep 17 00:00:00 2001 From: lukewalczak Date: Wed, 30 Apr 2025 01:02:09 +0200 Subject: [PATCH 6/8] docs: small fixes --- docs/src/data/extendedExamples/BottomNavigationBar.js | 2 +- src/components/BottomNavigation/BottomNavigationBar.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/data/extendedExamples/BottomNavigationBar.js b/docs/src/data/extendedExamples/BottomNavigationBar.js index b694da0cc7..81bb261a96 100644 --- a/docs/src/data/extendedExamples/BottomNavigationBar.js +++ b/docs/src/data/extendedExamples/BottomNavigationBar.js @@ -76,7 +76,7 @@ const MyTabs = createBottomTabNavigator({ ), }, }, - Profile: { + Settings: { screen: SettingsScreen, options: { tabBarIcon: ({ color }) => ( diff --git a/src/components/BottomNavigation/BottomNavigationBar.tsx b/src/components/BottomNavigation/BottomNavigationBar.tsx index e5fa8f980a..1453b07ebf 100644 --- a/src/components/BottomNavigation/BottomNavigationBar.tsx +++ b/src/components/BottomNavigation/BottomNavigationBar.tsx @@ -254,7 +254,7 @@ const Touchable = ({ * import { useState } from 'react'; * import { View } from 'react-native'; * import { BottomNavigation, Text, Provider } from 'react-native-paper'; - * import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons' + * import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons'; * * function HomeScreen() { * return ( From 52a06422bbecd2f284fa85b818888773f0a544a2 Mon Sep 17 00:00:00 2001 From: lukewalczak Date: Wed, 30 Apr 2025 11:37:03 +0200 Subject: [PATCH 7/8] refactor: adapt the latest changes --- example/src/RootNavigator.tsx | 3 +-- example/src/index.native.tsx | 8 ++++++-- example/src/index.tsx | 8 ++++++-- example/utils/themes.ts | 31 +++++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/example/src/RootNavigator.tsx b/example/src/RootNavigator.tsx index 26eb57ea84..dee716b5c5 100644 --- a/example/src/RootNavigator.tsx +++ b/example/src/RootNavigator.tsx @@ -20,8 +20,7 @@ export default function Root() { return ( ({ - detachPreviousScreen: !navigation.isFocused(), + screenOptions={() => ({ cardStyleInterpolator, header: ({ navigation, route, options, back }) => ( diff --git a/example/src/index.native.tsx b/example/src/index.native.tsx index be0003eec9..dfa9358ae2 100644 --- a/example/src/index.native.tsx +++ b/example/src/index.native.tsx @@ -28,6 +28,7 @@ import { CombinedDefaultTheme, CombinedDarkTheme, createConfiguredFontTheme, + createConfiguredFontNavigationTheme, } from '../utils/themes'; const PERSISTENCE_KEY = 'NAVIGATION_STATE'; @@ -192,6 +193,8 @@ export default function PaperExample() { const combinedTheme = isDarkMode ? CombinedDarkTheme : CombinedDefaultTheme; const configuredFontTheme = createConfiguredFontTheme(combinedTheme); + const configuredFontNavigationTheme = + createConfiguredFontNavigationTheme(combinedTheme); return ( AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state)) diff --git a/example/src/index.tsx b/example/src/index.tsx index a8d2474699..a330d90aca 100644 --- a/example/src/index.tsx +++ b/example/src/index.tsx @@ -22,6 +22,7 @@ import App from './RootNavigator'; import { CombinedDarkTheme, CombinedDefaultTheme, + createConfiguredFontNavigationTheme, createConfiguredFontTheme, } from '../utils/themes'; @@ -149,6 +150,8 @@ export default function PaperExample() { const combinedTheme = isDarkMode ? CombinedDarkTheme : CombinedDefaultTheme; const configuredFontTheme = createConfiguredFontTheme(combinedTheme); + const configuredFontNavigationTheme = + createConfiguredFontNavigationTheme(combinedTheme); return ( AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state)) diff --git a/example/utils/themes.ts b/example/utils/themes.ts index 4c3c664f63..85e4bb4f81 100644 --- a/example/utils/themes.ts +++ b/example/utils/themes.ts @@ -21,6 +21,10 @@ export const CombinedDefaultTheme = { ...MD3LightTheme.colors, ...LightTheme.colors, }, + fonts: { + ...MD3LightTheme.fonts, + ...LightTheme.fonts, + }, }; export const CombinedDarkTheme = { @@ -30,6 +34,10 @@ export const CombinedDarkTheme = { ...MD3DarkTheme.colors, ...DarkTheme.colors, }, + fonts: { + ...MD3DarkTheme.fonts, + ...DarkTheme.fonts, + }, }; type CombinedTheme = typeof CombinedDefaultTheme; @@ -42,3 +50,26 @@ export const createConfiguredFontTheme = (theme: CombinedTheme) => ({ }, }), }); + +export const createConfiguredFontNavigationTheme = (theme: CombinedTheme) => ({ + ...theme, + fonts: { + ...theme.fonts, + regular: { + ...theme.fonts.regular, + fontFamily: 'Abel', + }, + medium: { + ...theme.fonts.medium, + fontFamily: 'Abel', + }, + heavy: { + ...theme.fonts.heavy, + fontFamily: 'Abel', + }, + bold: { + ...theme.fonts.bold, + fontFamily: 'Abel', + }, + }, +}); From 70ceddc546c338f8d057a12db0ce60048647da64 Mon Sep 17 00:00:00 2001 From: lukewalczak Date: Wed, 30 Apr 2025 12:25:00 +0200 Subject: [PATCH 8/8] refactor: override ripple in themed navigation example --- example/src/Examples/ThemingWithReactNavigation.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/example/src/Examples/ThemingWithReactNavigation.tsx b/example/src/Examples/ThemingWithReactNavigation.tsx index ab78b6a497..c4edf789ab 100644 --- a/example/src/Examples/ThemingWithReactNavigation.tsx +++ b/example/src/Examples/ThemingWithReactNavigation.tsx @@ -3,6 +3,7 @@ import { View, StyleSheet } from 'react-native'; import Icon from '@expo/vector-icons/MaterialCommunityIcons'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { PlatformPressable } from '@react-navigation/elements'; import { createStackNavigator } from '@react-navigation/stack'; import { Text } from 'react-native-paper'; @@ -30,6 +31,12 @@ const HomeTab = () => { ( + + ), }} >