diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2618dc1431f..123190557ce 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,5 @@ + diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index e890855f305..d8e6daa5fa2 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -1,9 +1,6 @@ name: autofix.ci on: - push: - branches: - - main pull_request: branches: - main @@ -28,4 +25,4 @@ jobs: run: yarn prettier --write . - name: Autofix - uses: autofix-ci/action@8106fde54b877517c9af2c3d68918ddeaa7bed64 + uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27 diff --git a/blog/2020-05-16-web-support.md b/blog/2020-05-16-web-support.md index 599bf8357e9..0f5be8529cc 100644 --- a/blog/2020-05-16-web-support.md +++ b/blog/2020-05-16-web-support.md @@ -34,7 +34,7 @@ Example: ```js const linking = { - prefixes: ['https://mychat.com', 'mychat://'], + prefixes: ['https://example.com', 'example://'], config: { screens: { Home: '', diff --git a/blog/2024-03-25-introducing-static-api.md b/blog/2024-03-25-introducing-static-api.md index fc950b677bd..014d7567e8b 100644 --- a/blog/2024-03-25-introducing-static-api.md +++ b/blog/2024-03-25-introducing-static-api.md @@ -138,7 +138,7 @@ There are 2 improvements to deep linking API: return ( + +## Introduction + +React Navigation comes with many navigators out of the box. We've got Stack, Native Stack, Drawer, and Bottom Tabs, but there were no Native Bottom Tabs until today! + +Both Android and iOS have predefined native components for handling bottom navigation. For iOS it's SwiftUI's `TabView` component and for Android it's `BottomNavigationView`. The native approach gives us an appropriate appearance no matter the platform we are running on. Native Bottom Tabs is a navigator that wraps the native `TabView` and `BottomNavigationView` - so you can use them with React Navigation. + +Let's dive into the details of this navigator. + +Note: Native Bottom Tabs navigator is a standalone package, not released as part of React Navigation. + +## Overview + +You still might be wondering the difference between `@react-navigation/bottom-tabs` and `react-native-bottom-tabs`. + +Let's go over the main differences: + +- JS Bottom Tabs recreate the UI as closely as possible while **Native Bottom Tabs use native platform primitives** to create the tabs. This makes your tab navigation indistinguishable from Native Apps as they use the same components under the hood. +- Native Bottom Tabs **adapt to interfaces of a given platform** for example: tvOS and visionOS show tabs as a sidebar on iPadOS they appear at the top, while JS Bottom Tabs are always at the bottom. + +### Distinctive features of Native Bottom Tabs + +#### Multi-platform support + +Native Bottom tabs adapt to the appearance of multiple platforms. You always get natively-looking tabs! + +Native Tabs on iOS + +Bottom Navigation on iOS, with native blur. + +Native Tabs on Android + +Bottom Navigation on Android, following Material Design 3 styling. + +Native Tabs on iPadOS + +On iPadOS tabs appear at the top with a button allowing you to go into the sidebar mode. + +Native Tabs on visionOS + +On visionOS, the tabs appear on the left side, attached outside of the window. + +Native Tabs on tvOS + +On tvOS tabs appear on the top, making navigation with the TV remote a breeze. + +Native Tabs on macOS + +On macOS, tabs appear on the left side, following the design of the Finder app. + +#### Automatic scroll to the top + +iOS TabView automatically scrolls to the top when ScrollView is embedded inside of it. + +#### Automatic PiP avoidance + +The operating system recognizes navigation in your app making the Picture in Picture window automatically avoid bottom navigation. + +#### Platform-specific styling + +For iOS bottom navigation has a built-in blur making your app stand out. For Android, you can choose between Material 2 and Material 3 and leverage Material You system styling. + +#### Sidebar + +TabView can turn in to a side bar on tvOS, iPadOS and macOS. The `sidebarAdaptable` prop controls this. + +## Getting started + +To get started follow the installation instructions in the `react-native-bottom-tabs` [documentation](https://callstackincubator.github.io/react-native-bottom-tabs/docs/getting-started/quick-start.html). + +Native Bottom Tabs Navigation resembles JavaScript Tabs API as closely as possible. Making your migration straightforward. + +As mentioned before, Native Bottom Tabs use native primitives to create the tabs. This approach also has some downsides: Native components enforce certain constraints that we need to follow. + +There are a few differences between the APIs worth noting. One of the biggest is how native tabs handle images. In JavaScript tabs, you can render React components as icons, in native tabs unfortunately it’s not possible. Instead, you have to provide one of the following options: + +```tsx + require('person.png'), + // SVG is also supported + tabBarIcon: () => require('person.svg'), + // or + tabBarIcon: () => ({ sfSymbol: 'person' }), + // You can also pass a URL + tabBarIcon: () => ({ uri: 'https://example.com/icon.png' }), + }} +/> +``` + +So if you need full customizability like providing custom tab bar icons, and advanced styling that goes beyond what’s possible with native components you should use JavaScript bottom tabs. + +On top of that, the scope of this library doesn’t include the web so for that platform, you should use JavaScript Tabs. + +To get started you can import `createNativeBottomTabNavigator` from `@bottom-tabs/react-navigation` and use it the same way as JavaScript Bottom Tabs. + +### Example usage + +```tsx +import { createNativeBottomTabNavigator } from '@bottom-tabs/react-navigation'; + +const Tabs = createNativeBottomTabNavigator(); + +function NativeBottomTabs() { + return ( + + ({ uri: 'https://example.com/icon.png' }), + }} + /> + ({ uri: 'https://example.com/icon.png' }), + }} + /> + + ); +} +``` + +Native Tabs + +You can check out the project [here](https://github.com/callstackincubator/react-native-bottom-tabs). + +Thanks for reading! diff --git a/blog/authors.yml b/blog/authors.yml index ec3a850bf0d..a184d71c1bb 100644 --- a/blog/authors.yml +++ b/blog/authors.yml @@ -39,3 +39,11 @@ dawid: socials: x: trensik github: Trancever + +oskar: + name: Oskar Kwaśniewski + image_url: https://avatars.githubusercontent.com/u/52801365 + title: Callstack + socials: + x: o_kwasniewski + github: okwasniewsk diff --git a/docusaurus.config.js b/docusaurus.config.js index dd90010233c..d115dc6962f 100755 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -1,5 +1,7 @@ import remarkNpm2Yarn from '@docusaurus/remark-plugin-npm2yarn'; import rehypeCodeblockMeta from './src/plugins/rehype-codeblock-meta.mjs'; +import rehypeStaticToDynamic from './src/plugins/rehype-static-to-dynamic.mjs'; +import rehypeVideoAspectRatio from './src/plugins/rehype-video-aspect-ratio.mjs'; export default { future: { @@ -14,7 +16,11 @@ export default { projectName: 'react-navigation.github.io', onBrokenAnchors: 'throw', onBrokenMarkdownLinks: 'throw', - scripts: ['/js/snack-helpers.js', '/js/toc-fixes.js'], + scripts: [ + '/js/snack-helpers.js', + '/js/toc-fixes.js', + '/js/video-playback.js', + ], themeConfig: { colorMode: { defaultMode: 'light', @@ -148,6 +154,8 @@ export default { rehypeCodeblockMeta, { match: { snack: true, lang: true, tabs: true } }, ], + [rehypeVideoAspectRatio, { staticDir: 'static' }], + rehypeStaticToDynamic, ], }, blog: { diff --git a/package.json b/package.json index 4989ead8672..dc1b26442d5 100755 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "deploy": "DEPLOYMENT_BRANCH=gh-pages docusaurus deploy", "crowdin-upload": "crowdin upload sources --auto-update -b main", "crowdin-download": "crowdin download -b main", - "fetch-sponsors": "node scripts/fetch-sponsors.js" + "fetch-sponsors": "node scripts/fetch-sponsors.js && prettier --write src/data/sponsors.js" }, "dependencies": { "@docusaurus/core": "3.6.1", @@ -31,9 +31,12 @@ "react-simple-code-editor": "^0.14.1" }, "devDependencies": { + "@babel/types": "^7.28.5", + "@ffprobe-installer/ffprobe": "^2.1.2", "markdownlint": "^0.36.1", "markdownlint-cli2": "^0.14.0", - "prettier": "^3.3.3" + "prettier": "^3.6.2", + "recast": "^0.23.11" }, "resolutions": { "@rspack/core": "1.0.14" diff --git a/src/css/custom.css b/src/css/custom.css index 19d5ba9ba0e..77e138f9186 100755 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -10,12 +10,13 @@ --primary-saturation: 53.2%; --primary-lightness: 50.2%; - --ifm-font-family-monospace: ui-monospace, SFMono-Regular, Menlo, Monaco, - Consolas, Liberation Mono, Courier New, monospace; - --ifm-font-family-base: ui-sans-serif, system-ui, -apple-system, - BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, - sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, - Noto Color Emoji; + --ifm-font-family-monospace: + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, + Courier New, monospace; + --ifm-font-family-base: + ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, + Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, + Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; --ifm-code-font-size: 85%; @@ -81,8 +82,12 @@ --ifm-footer-padding-horizontal: var(--ifm-spacing-horizontal); --ifm-footer-padding-vertical: var(--ifm-spacing-vertical); - --ifm-tabs-padding-vertical: 0.5rem; + --ifm-tabs-padding-vertical: 0.375rem; + + --ifm-alert-shadow: none; --ifm-alert-border-left-width: 0; + + --codeblock-background-color: #f6f8fa; } :root[data-theme='dark'] { @@ -133,6 +138,8 @@ --ifm-home-color-border: #f7f7ff; --docusaurus-highlighted-code-line-bg: rgba(255, 255, 255, 0.07); + + --codeblock-background-color: #282a35; } h1 { @@ -388,7 +395,7 @@ p { } .tabs__item { - border-bottom: 1px solid var(--ifm-toc-border-color); + border-bottom: 2px solid var(--ifm-toc-border-color); border-bottom-left-radius: 0; border-bottom-right-radius: 0; } @@ -396,7 +403,29 @@ p { .tabs__item--active, .tabs__item--active:hover { border-bottom-color: var(--ifm-tabs-color-active-border); - background-color: var(--ifm-menu-color-background-active); +} + +.tabs-container:has( + > .margin-top--md > [role='tabpanel'] > .theme-code-block:only-child + ):not(:has(> .margin-top--md > [role='tabpanel'] > :nth-child(2))) { + background-color: var(--codeblock-background-color); + border-radius: var(--ifm-code-border-radius); + + & > .margin-top--md { + margin-top: 0 !important; + } + + & > .tabs { + box-shadow: inset 0 -2px 0 var(--ifm-toc-border-color); + } + + & > .tabs > .tabs__item { + border-top-right-radius: 0; + } + + & > .tabs > .tabs__item:not(:first-child) { + border-top-left-radius: 0; + } } .col:has(.table-of-contents) { @@ -475,6 +504,10 @@ samp { display: none; } +.theme-code-block { + box-shadow: none !important; +} + .theme-code-block:has(+ .snack-sample-link) { margin-bottom: 0; border-bottom-left-radius: 0; @@ -488,17 +521,15 @@ samp { margin-top: 0; margin-bottom: var(--ifm-leading); padding: calc(var(--ifm-pre-padding) / 2) var(--ifm-pre-padding); - background-color: #f6f8fa; + background-color: var(--codeblock-background-color); border-top-width: 1px; border-top-style: solid; border-top-color: var(--ifm-color-gray-200); border-bottom-left-radius: var(--ifm-pre-border-radius); border-bottom-right-radius: var(--ifm-pre-border-radius); - box-shadow: var(--ifm-global-shadow-lw); } [data-theme='dark'] .theme-code-block + .snack-sample-link { - background-color: #282a35; border-top-color: rgba(255, 255, 255, 0.07); } diff --git a/src/data/sponsors.js b/src/data/sponsors.js index ece5c987929..84d0f91f24a 100644 --- a/src/data/sponsors.js +++ b/src/data/sponsors.js @@ -1,37 +1,9 @@ export default [ { avatarUrl: - 'https://avatars.githubusercontent.com/u/9664363?u=a4a9e93dc4305c91ced38b83d4c08186f7254b04&v=4', - username: 'EvanBacon', - name: 'Evan Bacon', - }, - { - avatarUrl: 'https://avatars.githubusercontent.com/u/306134?v=4', - username: 'wcandillon', - name: 'William Candillon', - }, - { - avatarUrl: 'https://avatars.githubusercontent.com/u/476779?v=4', - username: 'Expensify', - name: 'Expensify, Inc', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/916690?u=66482eb2c5bb755553afbcfa219dcacc42fc487a&v=4', - username: 'benevbright', - name: 'Bright Lee', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/980234?u=59bb4c6ac0a23b53225bb2235b69c72a960ba83f&v=4', - username: 'simoncar', - name: 'Simoncar', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/1057756?u=15c3cdff1c715ac27bbc63ccb8f0a1c27eeb3784&v=4', - username: 'zhigang1992', - name: 'Zhigang Fang', + 'https://avatars.githubusercontent.com/u/360412?u=15e7b90eb91a3d2b410f7f47461862cb793398ff&v=4', + username: 'jyc', + name: null, }, { avatarUrl: @@ -41,27 +13,14 @@ export default [ }, { avatarUrl: - 'https://avatars.githubusercontent.com/u/1566403?u=3df07e2ae72e89a3a3509ba6c2f927115b5f38aa&v=4', - username: 'vonovak', - name: 'Vojtech Novak', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/1629785?u=12eb94da6070d00fc924761ce06e3a428d01b7e9&v=4', + 'https://avatars.githubusercontent.com/u/1629785?u=f91613c118bb1fcf442a71008dff1cd5f9b30411&v=4', username: 'JonnyBurger', name: 'Jonny Burger', }, { - avatarUrl: - 'https://avatars.githubusercontent.com/u/1764217?u=f36737d852ffcc50b87e809474faa27eb2ce130a&v=4', - username: 'markholland', - name: 'Mark Holland', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/3584560?u=f54bd481e956c6b3fe88a15f466ff9a3973e4b35&v=4', - username: 'hetmann', - name: 'Hetmann W. Iohan', + avatarUrl: 'https://avatars.githubusercontent.com/u/2443340?v=4', + username: 'toyokumo', + name: 'TOYOKUMO', }, { avatarUrl: @@ -69,29 +28,6 @@ export default [ username: 'itsrifat', name: 'Moinul Hossain', }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/4376835?u=0cf5324a78dd4140ef71943048dedac328be68b9&v=4', - username: 'lnmunhoz', - name: 'Lucas N. Munhoz', - }, - { - avatarUrl: 'https://avatars.githubusercontent.com/u/5605177?v=4', - username: 'Razorholt', - name: null, - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/5967956?u=f7f5ed6b6b399c2953fd0e3be0512c378e9f76c4&v=4', - username: 'codinsonn', - name: 'Thorr Stevens', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/6457344?u=47e100289441b7f4681a7809202ff683886e4f5e&v=4', - username: 'ryo-rm', - name: 'ryo kishida', - }, { avatarUrl: 'https://avatars.githubusercontent.com/u/6936373?u=4edd14e6636c45d10ac6a3eecb4b3ffa6cc2bf5c&v=4', @@ -105,89 +41,38 @@ export default [ name: 'Radek Czemerys', }, { - avatarUrl: 'https://avatars.githubusercontent.com/u/12504344?v=4', - username: 'expo', - name: 'Expo', - }, - { - avatarUrl: 'https://avatars.githubusercontent.com/u/13601619?v=4', - username: 'itiden', - name: 'Itiden', + avatarUrl: + 'https://avatars.githubusercontent.com/u/7910545?u=95ae2c2a40b5f6f63f05fce29ee7c01622019c76&v=4', + username: 'UdaySravanK', + name: 'Uday Sravan K', }, { avatarUrl: - 'https://avatars.githubusercontent.com/u/14099522?u=965e74751a9db0f40a30ace9cd0cb1f82eaf1412&v=4', - username: 'aCanalez', - name: 'Alexi Canales', + 'https://avatars.githubusercontent.com/u/9664363?u=46ba6d5fbd29729df2950b845c9ca2cd085a1c2b&v=4', + username: 'EvanBacon', + name: 'Evan Bacon', }, { avatarUrl: - 'https://avatars.githubusercontent.com/u/15199031?u=46da50e88594eb284cf249485f202d5d43d474d1&v=4', + 'https://avatars.githubusercontent.com/u/15199031?u=5a82dcb32237282ff576c0446567a1e2fe49b868&v=4', username: 'mrousavy', name: 'Marc Rousavy', }, { avatarUrl: - 'https://avatars.githubusercontent.com/u/17621507?u=0ee7f26191d430f4fc0672cef92c2759d948bbb5&v=4', - username: 'dsznajder', - name: 'Damian Sznajder', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/21980965?u=5a571092a83cb71508c60a9c86ab2520fde8a68e&v=4', - username: 'jarvisluong', - name: 'Jarvis Luong', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/26326015?u=33a1afbe11e8f6b962c6267606d59c2b2ef94716&v=4', - username: 'bang9', - name: 'Hyungu Kang | Airen', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/27461460?u=b5860875e26d33fd70fd210f4ea74f81cdf9d99b&v=4', - username: 'hyochan', - name: 'Hyo', - }, - { - avatarUrl: 'https://avatars.githubusercontent.com/u/30735054?v=4', - username: 'endearhq', - name: 'Endear', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/33361399?u=6180514361e35ae42e2401431555c82cc63adda9&v=4', - username: 'oliverloops', - name: 'Oliver Lopez ', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/34658847?u=17fd3603d012d068c060039bcf009095055290a1&v=4', - username: 'RatebSeirawan', - name: 'Rateb Seirawan', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/36824170?u=6f56fa2998ffba6b5a3908c79e2ef9331bad502a&v=4', - username: 'luism3861', - name: 'Luis Medina Huerta', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/45317893?u=253e6eabcb23a5361e93a064914c9b8d136ac888&v=4', - username: 'GandresCoello18', - name: 'Andres Coello', + 'https://avatars.githubusercontent.com/u/23122214?u=7b3231a177e5d661d55b2cee88ea69e1713eb695&v=4', + username: 'sayurimizuguchi', + name: 'Sayuri Mizuguchi', }, { avatarUrl: - 'https://avatars.githubusercontent.com/u/46625943?u=63c9ed9017c34900df8b5ae2ed455ec4c82ef8aa&v=4', - username: 'bowen9284', - name: 'Matt Bowen', + 'https://avatars.githubusercontent.com/u/79333934?u=d18e4c6a4e063534d6fd7e77e0d51c367c91cfa0&v=4', + username: 'finanzguru', + name: 'Finanzguru', }, { - avatarUrl: 'https://avatars.githubusercontent.com/u/49920282?v=4', - username: 'reactrondev', - name: 'Reactron', + avatarUrl: 'https://avatars.githubusercontent.com/u/140319444?v=4', + username: 'jupli-apps', + name: 'Jupli', }, ]; diff --git a/src/pages/home/Sponsors/index.js b/src/pages/home/Sponsors/index.js index 40c3bff4d8d..4bac27a7f4d 100644 --- a/src/pages/home/Sponsors/index.js +++ b/src/pages/home/Sponsors/index.js @@ -1,5 +1,3 @@ -import React from 'react'; - import sponsors from '../../../data/sponsors'; import styles from './styles.module.css'; @@ -8,15 +6,14 @@ export default function Sponsors() {

- React Navigation is built by Expo,{' '} - Software Mansion, and{' '} - Callstack, with contributions - from the{' '} + React Navigation relies on the support from the community. Thanks to{' '} + Software Mansion,{' '} + Callstack,{' '} + Expo, and our amazing{' '} - community + contributors {' '} - and{' '} - sponsors: + & sponsors:

{sponsors.map((sponsor) => ( diff --git a/src/plugins/rehype-static-to-dynamic.mjs b/src/plugins/rehype-static-to-dynamic.mjs new file mode 100644 index 00000000000..46ca91ca70c --- /dev/null +++ b/src/plugins/rehype-static-to-dynamic.mjs @@ -0,0 +1,823 @@ +import * as t from '@babel/types'; +import * as recast from 'recast'; +import { visit } from 'unist-util-visit'; + +/** + * Plugin to automatically convert static config examples to dynamic config + * + * This plugin finds code blocks with the 'static2dynamic' meta tag and generates + * corresponding dynamic configuration examples wrapped in tabs. + */ +export default function rehypeStaticToDynamic() { + return (tree) => { + visit(tree, 'element', (node, index, parent) => { + // Look for code blocks with static2dynamic in meta + if ( + node.tagName === 'pre' && + node.children?.length === 1 && + node.children[0].tagName === 'code' + ) { + const codeNode = node.children[0]; + const meta = codeNode.data?.meta; + + // Check if meta contains 'static2dynamic' + if (!meta || !meta.includes('static2dynamic')) { + return; + } + + // Extract code from the code block + const code = codeNode?.children?.[0]?.value; + + if (!code) { + throw new Error( + 'rehype-static-to-dynamic: Unable to extract code from code block' + ); + } + + const dynamicCode = convertStaticToDynamic(code); + const tabsElement = createTabsWithBothConfigs(code, dynamicCode, node); + + // Replace the current pre element with the tabs + parent.children[index] = tabsElement; + } + }); + }; +} + +/** + * Convert static config code to dynamic config code + */ +function convertStaticToDynamic(code) { + // Parse the code into AST using recast + const ast = recast.parse(code, { + parser: require('recast/parsers/babel-ts'), + }); + + let navigatorInfos = []; + let staticNavigationIndices = []; + + // First pass: collect information and transform imports + recast.visit(ast, { + visitImportDeclaration(path) { + const source = path.node.source.value; + + // Transform @react-navigation/native imports + if (source === '@react-navigation/native') { + const specifiers = path.node.specifiers; + + let hasNavigationContainer = false; + let hasCreateStaticNavigation = false; + + specifiers.forEach((spec) => { + if ( + t.isImportSpecifier(spec) && + spec.imported.name === 'NavigationContainer' + ) { + hasNavigationContainer = true; + } + if ( + t.isImportSpecifier(spec) && + spec.imported.name === 'createStaticNavigation' + ) { + hasCreateStaticNavigation = true; + } + }); + + // Remove createStaticNavigation and add NavigationContainer if needed + if (hasCreateStaticNavigation) { + path.node.specifiers = specifiers.filter( + (spec) => + !( + t.isImportSpecifier(spec) && + spec.imported.name === 'createStaticNavigation' + ) + ); + + if (!hasNavigationContainer) { + path.node.specifiers.push( + t.importSpecifier( + t.identifier('NavigationContainer'), + t.identifier('NavigationContainer') + ) + ); + } + } + } + + this.traverse(path); + }, + + visitProgram(path) { + // Find declarations by index to avoid scope issues + path.node.body.forEach((node, index) => { + if (t.isVariableDeclaration(node)) { + node.declarations.forEach((declarator) => { + if ( + t.isCallExpression(declarator.init) && + t.isIdentifier(declarator.init.callee) && + declarator.init.callee.name.startsWith('create') && + declarator.init.callee.name.endsWith('Navigator') && + declarator.init.arguments.length > 0 && + t.isObjectExpression(declarator.init.arguments[0]) + ) { + const navigatorVariable = declarator.id.name; // e.g., "MyStack" + const navigatorType = declarator.init.callee.name; // e.g., "createStackNavigator" + const config = declarator.init.arguments[0]; + + navigatorInfos.push({ + componentName: navigatorVariable, // Keep original name for the component + type: navigatorType, + config: config, + // Store leading/trailing comments to preserve codeblock-focus + leadingComments: node.comments || [], + trailingComments: node.trailingComments || [], + originalNode: node, // Keep reference to original node + index: index, + }); + } + + // Find createStaticNavigation usage + if ( + t.isCallExpression(declarator.init) && + t.isIdentifier(declarator.init.callee) && + declarator.init.callee.name === 'createStaticNavigation' + ) { + staticNavigationIndices.push(index); + } + }); + } + }); + + this.traverse(path); + }, + + visitJSXElement(path) { + // Find (created by createStaticNavigation) + if ( + t.isJSXIdentifier(path.node.openingElement.name) && + path.node.openingElement.name.name === 'Navigation' && + path.node.children.length === 0 && + navigatorInfos.length > 0 + ) { + // Preserve any props passed to Navigation + const navigationProps = path.node.openingElement.attributes || []; + + // Use the last navigator (which is passed to createStaticNavigation) + const mainNavigator = navigatorInfos[navigatorInfos.length - 1]; + + // Replace with + // Pass the props from Navigation to NavigationContainer + const newElement = t.jsxElement( + t.jsxOpeningElement( + t.jsxIdentifier('NavigationContainer'), + navigationProps + ), + t.jsxClosingElement(t.jsxIdentifier('NavigationContainer')), + [ + t.jsxText('\n '), + t.jsxElement( + t.jsxOpeningElement( + t.jsxIdentifier(mainNavigator.componentName), + [], + true + ), + null, + [], + true + ), + t.jsxText('\n'), + ], + false + ); + + path.replace(newElement); + } + + this.traverse(path); + }, + }); + + // Second pass: manually transform the AST body + // Process all navigators + if (navigatorInfos.length > 0) { + const replacements = []; + const navigatorConstNames = new Map(); // Track usage of navigator constant names + + navigatorInfos.forEach((navigatorInfo) => { + const { + componentName, + type, + config, + leadingComments, + trailingComments, + originalNode, + index, + } = navigatorInfo; + + // Extract navigator constant name from the type + // Get the last word before "Navigator" + // e.g., "createStackNavigator" -> "Stack" + // e.g., "createNativeStackNavigator" -> "Stack" + // e.g., "createBottomTabNavigator" -> "Tab" + // e.g., "createMaterialTopTabNavigator" -> "Tab" + const withoutCreate = type.replace(/^create/, ''); // "StackNavigator" + const withoutNavigator = withoutCreate.replace(/Navigator$/, ''); // "Stack" + // Find the last capitalized word (e.g., "NativeStack" -> "Stack", "MaterialTopTab" -> "Tab") + const match = withoutNavigator.match(/([A-Z][a-z]+)$/); + const baseNavigatorConstName = match ? match[1] : withoutNavigator; + + // Handle multiple navigators of the same type by adding suffixes (A, B, C, etc.) + let navigatorConstName = baseNavigatorConstName; + const currentCount = navigatorConstNames.get(baseNavigatorConstName) || 0; + + if (currentCount > 0) { + // Add suffix: A for second occurrence, B for third, etc. + const suffix = String.fromCharCode(65 + currentCount - 1); // 65 is 'A' + navigatorConstName = baseNavigatorConstName + suffix; + } + + navigatorConstNames.set(baseNavigatorConstName, currentCount + 1); + + // Parse the config object + const parsedConfig = parseNavigatorConfig(config); + + // Create: const Stack = createStackNavigator(); + const navigatorConstDeclaration = t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier(navigatorConstName), + t.callExpression(t.identifier(type), []) + ), + ]); + + // Create the navigator component function (e.g., function MyStack() {...}) + const navigatorComponent = createNavigatorComponent( + componentName, // function name: MyStack + navigatorConstName, // Stack.Navigator, Stack.Screen + parsedConfig + ); + + // Preserve all comments from the original node + if (originalNode.comments && originalNode.comments.length > 0) { + // Separate leading and trailing comments + const leadingComments = []; + const trailingCommentsFromNode = []; + + originalNode.comments.forEach((comment) => { + // Recast marks comments with leading/trailing properties + if (comment.trailing) { + trailingCommentsFromNode.push(comment); + } else { + leadingComments.push(comment); + } + }); + + // Attach leading comments to the const declaration + if (leadingComments.length > 0) { + // Mark as leading comments for proper placement + leadingComments.forEach((c) => { + c.leading = true; + c.trailing = false; + }); + navigatorConstDeclaration.comments = leadingComments; + } + + // Attach trailing comments to the function component (after the function body) + if (trailingCommentsFromNode.length > 0) { + // Mark as trailing comments for proper placement + trailingCommentsFromNode.forEach((c) => { + c.leading = false; + c.trailing = true; + }); + navigatorComponent.comments = trailingCommentsFromNode; + } + } + + // Also check for trailingComments property + if (trailingComments && trailingComments.length > 0) { + trailingComments.forEach((c) => { + c.leading = false; + c.trailing = true; + }); + navigatorComponent.comments = [ + ...(navigatorComponent.comments || []), + ...trailingComments, + ]; + } + + // Store the replacement info + replacements.push({ + index: index, + navigatorConstDeclaration, + navigatorComponent, + }); + }); + + // Replace declarations in reverse order to maintain correct indices + replacements.sort((a, b) => b.index - a.index); + + const programBody = ast.program.body; + let indexShift = 0; + + replacements.forEach( + ({ index, navigatorConstDeclaration, navigatorComponent }) => { + // Replace 1 node with 2 nodes + programBody.splice( + index, + 1, + navigatorConstDeclaration, + navigatorComponent + ); + + // Track the shift for adjusting staticNavigation indices + indexShift++; + } + ); + + // Adjust indices for createStaticNavigation declarations + // Account for the fact that we replaced each navigator (1 node) with 2 nodes + staticNavigationIndices = staticNavigationIndices.map((idx) => { + let shift = 0; + replacements.forEach(({ index }) => { + if (idx > index) shift++; + }); + return idx + shift; + }); + } + + // Remove createStaticNavigation declarations (in reverse order to maintain indices) + staticNavigationIndices.sort((a, b) => b - a); + staticNavigationIndices.forEach((index) => { + ast.program.body.splice(index, 1); + }); + + // Generate code from AST using recast + const output = recast.print(ast, { + tabWidth: 2, + quote: 'single', + trailingComma: true, + }); + + // Parse with Babel to verify syntax + recast.parse(output.code, { + parser: require('recast/parsers/babel-ts'), + }); + + return output.code; +} + +/** + * Parse navigator configuration object + */ +function parseNavigatorConfig(configNode) { + const result = { + screens: {}, + groups: {}, // Store groups + navigatorProps: {}, // Store all navigator-level props + }; + + if (!t.isObjectExpression(configNode)) { + return result; + } + + configNode.properties.forEach((prop) => { + if (!t.isObjectProperty(prop) && !t.isObjectMethod(prop)) { + return; + } + + const keyName = prop.key.name || prop.key.value; + + if (keyName === 'groups' && t.isObjectExpression(prop.value)) { + // Parse groups object + prop.value.properties.forEach((groupProp) => { + if (!t.isObjectProperty(groupProp)) return; + + const groupKey = groupProp.key.name || groupProp.key.value; + const groupValue = groupProp.value; + + if (t.isObjectExpression(groupValue)) { + const groupConfig = { + screens: {}, + groupProps: {}, + }; + + groupValue.properties.forEach((groupConfigProp) => { + if (!t.isObjectProperty(groupConfigProp)) return; + + const configKey = + groupConfigProp.key.name || groupConfigProp.key.value; + + if ( + configKey === 'screens' && + t.isObjectExpression(groupConfigProp.value) + ) { + // Parse screens within the group + groupConfigProp.value.properties.forEach((screenProp) => { + if (!t.isObjectProperty(screenProp)) return; + + const screenName = screenProp.key.name || screenProp.key.value; + const screenValue = screenProp.value; + + if (t.isIdentifier(screenValue)) { + groupConfig.screens[screenName] = { + component: screenValue.name, + screenProps: {}, + }; + } else if (t.isObjectExpression(screenValue)) { + let component = null; + const screenProps = {}; + + screenValue.properties.forEach((screenConfigProp) => { + if (!t.isObjectProperty(screenConfigProp)) return; + + const key = + screenConfigProp.key.name || screenConfigProp.key.value; + + if ( + key === 'screen' && + t.isIdentifier(screenConfigProp.value) + ) { + component = screenConfigProp.value.name; + } else { + screenProps[key] = screenConfigProp.value; + } + }); + + groupConfig.screens[screenName] = { component, screenProps }; + } + }); + } else { + // Store other group-level props (screenOptions, screenLayout, etc.) + groupConfig.groupProps[configKey] = { + key: configKey, + value: groupConfigProp.value, + isStringLiteral: t.isStringLiteral(groupConfigProp.value), + }; + } + }); + + result.groups[groupKey] = groupConfig; + } + }); + } else if (keyName === 'screens' && t.isObjectExpression(prop.value)) { + // Parse screens object + prop.value.properties.forEach((screenProp) => { + if (!t.isObjectProperty(screenProp)) return; + + const screenName = screenProp.key.name || screenProp.key.value; + const screenValue = screenProp.value; + + if (t.isIdentifier(screenValue)) { + // Simple screen: Home: HomeScreen + result.screens[screenName] = { + component: screenValue.name, + screenProps: {}, // No additional props + }; + } else if (t.isObjectExpression(screenValue)) { + // Screen with config: Home: { screen: HomeScreen, options: {...}, listeners: {...} } + let component = null; + const screenProps = {}; + + screenValue.properties.forEach((screenConfigProp) => { + if (!t.isObjectProperty(screenConfigProp)) return; + + const configKey = + screenConfigProp.key.name || screenConfigProp.key.value; + + if ( + configKey === 'screen' && + t.isIdentifier(screenConfigProp.value) + ) { + component = screenConfigProp.value.name; + } else { + // Store all other props (options, listeners, getId, etc.) + screenProps[configKey] = screenConfigProp.value; + } + }); + + result.screens[screenName] = { component, screenProps }; + } + }); + } else { + // All other props are navigator-level props + // Store both the key name and the AST node value + result.navigatorProps[keyName] = { + key: keyName, + value: prop.value, + isStringLiteral: t.isStringLiteral(prop.value), + }; + } + }); + + return result; +} + +/** + * Create navigator component function + */ +function createNavigatorComponent(functionName, componentName, config) { + const navigatorProps = []; + + // Add all navigator-level props dynamically + Object.values(config.navigatorProps).forEach((propInfo) => { + if (propInfo.isStringLiteral) { + // String literals can be used directly as JSX string attributes + navigatorProps.push( + t.jsxAttribute( + t.jsxIdentifier(propInfo.key), + t.stringLiteral(propInfo.value.value) + ) + ); + } else { + // All other values need to be wrapped in JSX expression containers + navigatorProps.push( + t.jsxAttribute( + t.jsxIdentifier(propInfo.key), + t.jsxExpressionContainer(propInfo.value) + ) + ); + } + }); + + // Create screen elements + const screenElements = []; + + // Handle groups if they exist + if (Object.keys(config.groups).length > 0) { + Object.entries(config.groups).forEach(([groupKey, groupConfig]) => { + const groupProps = [ + t.jsxAttribute( + t.jsxIdentifier('navigationKey'), + t.stringLiteral(groupKey) + ), + ]; + + // Add group-level props (screenOptions, screenLayout, etc.) + Object.values(groupConfig.groupProps).forEach((propInfo) => { + if (propInfo.isStringLiteral) { + groupProps.push( + t.jsxAttribute( + t.jsxIdentifier(propInfo.key), + t.stringLiteral(propInfo.value.value) + ) + ); + } else { + groupProps.push( + t.jsxAttribute( + t.jsxIdentifier(propInfo.key), + t.jsxExpressionContainer(propInfo.value) + ) + ); + } + }); + + // Create screens for this group + const groupScreenElements = []; + + Object.entries(groupConfig.screens).forEach( + ([screenName, screenConfig]) => { + const screenProps = [ + t.jsxAttribute( + t.jsxIdentifier('name'), + t.stringLiteral(screenName) + ), + t.jsxAttribute( + t.jsxIdentifier('component'), + t.jsxExpressionContainer(t.identifier(screenConfig.component)) + ), + ]; + + // Add all screen-level props + Object.entries(screenConfig.screenProps).forEach(([key, value]) => { + screenProps.push( + t.jsxAttribute( + t.jsxIdentifier(key), + t.jsxExpressionContainer(value) + ) + ); + }); + + const screenElement = t.jsxElement( + t.jsxOpeningElement( + t.jsxMemberExpression( + t.jsxIdentifier(componentName), + t.jsxIdentifier('Screen') + ), + screenProps, + true + ), + null, + [], + true + ); + + groupScreenElements.push(t.jsxText('\n ')); + groupScreenElements.push(screenElement); + } + ); + + groupScreenElements.push(t.jsxText('\n ')); + + // Create the Group element + const groupElement = t.jsxElement( + t.jsxOpeningElement( + t.jsxMemberExpression( + t.jsxIdentifier(componentName), + t.jsxIdentifier('Group') + ), + groupProps + ), + t.jsxClosingElement( + t.jsxMemberExpression( + t.jsxIdentifier(componentName), + t.jsxIdentifier('Group') + ) + ), + groupScreenElements, + false + ); + + screenElements.push(t.jsxText('\n ')); + screenElements.push(groupElement); + }); + } + + // Handle standalone screens (not in groups) + Object.entries(config.screens).forEach(([screenName, screenConfig]) => { + const screenProps = [ + t.jsxAttribute(t.jsxIdentifier('name'), t.stringLiteral(screenName)), + t.jsxAttribute( + t.jsxIdentifier('component'), + t.jsxExpressionContainer(t.identifier(screenConfig.component)) + ), + ]; + + // Add all screen-level props dynamically (options, listeners, getId, etc.) + Object.entries(screenConfig.screenProps).forEach(([key, value]) => { + screenProps.push( + t.jsxAttribute(t.jsxIdentifier(key), t.jsxExpressionContainer(value)) + ); + }); + + const screenElement = t.jsxElement( + t.jsxOpeningElement( + t.jsxMemberExpression( + t.jsxIdentifier(componentName), + t.jsxIdentifier('Screen') + ), + screenProps, + true + ), + null, + [], + true + ); + + screenElements.push(t.jsxText('\n ')); + screenElements.push(screenElement); + }); + + screenElements.push(t.jsxText('\n')); + + // Create Navigator element + const navigatorElement = t.jsxElement( + t.jsxOpeningElement( + t.jsxMemberExpression( + t.jsxIdentifier(componentName), + t.jsxIdentifier('Navigator') + ), + navigatorProps + ), + t.jsxClosingElement( + t.jsxMemberExpression( + t.jsxIdentifier(componentName), + t.jsxIdentifier('Navigator') + ) + ), + screenElements, + false + ); + + // Create return statement with parentheses for multiline JSX + const returnStatement = t.returnStatement(navigatorElement); + + // Create function declaration + const functionDeclaration = t.functionDeclaration( + t.identifier(functionName), + [], + t.blockStatement([returnStatement]) + ); + + return functionDeclaration; +} + +/** + * Create a Tabs element with both static and dynamic TabItems + */ +function createTabsWithBothConfigs(staticCode, dynamicCode, originalCodeBlock) { + const codeNode = originalCodeBlock.children[0]; + + // Remove 'static2dynamic' from meta for the actual code blocks + const cleanMeta = + codeNode.data?.meta?.replace(/\bstatic2dynamic\b\s*/g, '').trim() || ''; + const cleanData = { ...codeNode.data, meta: cleanMeta }; + + return { + type: 'element', + tagName: 'Tabs', + properties: { + groupId: 'config', + queryString: 'config', + }, + children: [ + { + type: 'text', + value: '\n', + }, + // Static TabItem + { + type: 'element', + tagName: 'TabItem', + properties: { + value: 'static', + label: 'Static', + default: true, + }, + children: [ + { + type: 'text', + value: '\n\n', + }, + { + type: 'element', + tagName: 'pre', + properties: originalCodeBlock.properties || {}, + children: [ + { + type: 'element', + tagName: 'code', + properties: codeNode.properties || {}, + data: cleanData, + children: [ + { + type: 'text', + value: staticCode, + }, + ], + }, + ], + }, + { + type: 'text', + value: '\n\n', + }, + ], + }, + { + type: 'text', + value: '\n', + }, + // Dynamic TabItem + { + type: 'element', + tagName: 'TabItem', + properties: { + value: 'dynamic', + label: 'Dynamic', + }, + children: [ + { + type: 'text', + value: '\n\n', + }, + { + type: 'element', + tagName: 'pre', + properties: originalCodeBlock.properties || {}, + children: [ + { + type: 'element', + tagName: 'code', + properties: codeNode.properties || {}, + data: cleanData, + children: [ + { + type: 'text', + value: dynamicCode, + }, + ], + }, + ], + }, + { + type: 'text', + value: '\n\n', + }, + ], + }, + { + type: 'text', + value: '\n', + }, + ], + }; +} diff --git a/src/plugins/rehype-video-aspect-ratio.mjs b/src/plugins/rehype-video-aspect-ratio.mjs new file mode 100644 index 00000000000..c694e554846 --- /dev/null +++ b/src/plugins/rehype-video-aspect-ratio.mjs @@ -0,0 +1,170 @@ +import ffprobe from '@ffprobe-installer/ffprobe'; +import { exec } from 'child_process'; +import fs from 'fs'; +import path from 'path'; +import { visit } from 'unist-util-visit'; +import { promisify } from 'util'; + +const execAsync = promisify(exec); + +/** + * Rehype plugin to add aspect ratio preservation to video tags + */ +export default function rehypeVideoAspectRatio({ staticDir }) { + return async (tree, file) => { + const promises = []; + + visit(tree, 'mdxJsxFlowElement', (node) => { + if (node.name === 'video') { + // Find video source - check src attribute or source children + let videoSrc = null; + + // Look for src in attributes + if (node.attributes) { + const srcAttr = node.attributes.find( + (attr) => attr.type === 'mdxJsxAttribute' && attr.name === 'src' + ); + + if (srcAttr) { + videoSrc = srcAttr.value; + } + } + + // If no src attribute, look for source children + if (!videoSrc && node.children) { + const sourceNode = node.children.find( + (child) => + child.type === 'mdxJsxFlowElement' && child.name === 'source' + ); + + if (sourceNode?.attributes) { + const srcAttr = sourceNode.attributes.find( + (attr) => attr.type === 'mdxJsxAttribute' && attr.name === 'src' + ); + + if (srcAttr) { + videoSrc = srcAttr.value; + } + } + } + + const isLocalFile = + videoSrc && + !videoSrc.startsWith('http://') && + !videoSrc.startsWith('https://') && + !videoSrc.startsWith('//'); + + if (isLocalFile) { + const videoPath = path.join( + videoSrc.startsWith('/') ? file.cwd : file.dirname, + staticDir, + videoSrc + ); + + if (fs.existsSync(videoPath)) { + const promise = getVideoDimensions(videoPath).then((dimensions) => { + if (dimensions.width && dimensions.height) { + applyAspectRatio(node, dimensions.width, dimensions.height); + } + }); + + promises.push(promise); + } else { + throw new Error(`Video file does not exist (got ${videoPath})`); + } + } + } + }); + + await Promise.all(promises); + }; +} + +/** + * Apply aspect ratio styles to a video node + */ +function applyAspectRatio(node, width, height) { + const data = { + estree: { + type: 'Program', + body: [ + { + type: 'ExpressionStatement', + expression: { + type: 'ObjectExpression', + properties: [ + { + type: 'Property', + key: { type: 'Identifier', name: 'aspectRatio' }, + value: { type: 'Literal', value: width / height }, + kind: 'init', + }, + ], + }, + }, + ], + }, + }; + + node.attributes = node.attributes || []; + + let styleAttr = node.attributes?.find( + (attr) => attr.type === 'mdxJsxAttribute' && attr.name === 'style' + ); + + if (styleAttr) { + const properties = + styleAttr.value?.data?.estree?.body?.[0]?.expression?.properties ?? []; + + data.estree.body[0].expression.properties.push(...properties); + } + + styleAttr = { + type: 'mdxJsxAttribute', + name: 'style', + value: { + type: 'mdxJsxAttributeValueExpression', + data, + }, + }; + + const existingIndex = node.attributes.findIndex( + (attr) => attr.type === 'mdxJsxAttribute' && attr.name === 'style' + ); + + if (existingIndex !== -1) { + node.attributes[existingIndex] = styleAttr; + } else { + node.attributes.push(styleAttr); + } +} + +/** + * Get video dimensions using ffprobe + */ +async function getVideoDimensions(filePath) { + const { stdout } = await execAsync( + `${ffprobe.path} -v error -of flat=s=_ -select_streams v:0 -show_entries stream=height,width "${filePath}"` + ); + + const lines = stdout.trim().split('\n'); + const dimensions = {}; + + for (const line of lines) { + if (line.includes('width')) { + const width = Number(line.split('=')[1]); + + if (Number.isFinite(width) && width > 0) { + dimensions.width = width; + } + } else if (line.includes('height')) { + const height = Number(line.split('=')[1]); + + if (Number.isFinite(height) && height > 0) { + dimensions.height = height; + } + } + } + + return dimensions; +} diff --git a/static/assets/7.x/bottom-tabs-fade.mp4 b/static/assets/7.x/bottom-tabs-fade.mp4 index 07b261b2788..3c03a1f7f08 100644 Binary files a/static/assets/7.x/bottom-tabs-fade.mp4 and b/static/assets/7.x/bottom-tabs-fade.mp4 differ diff --git a/static/assets/7.x/bottom-tabs-shift.mp4 b/static/assets/7.x/bottom-tabs-shift.mp4 index 1a0cb301b86..5089331b569 100644 Binary files a/static/assets/7.x/bottom-tabs-shift.mp4 and b/static/assets/7.x/bottom-tabs-shift.mp4 differ diff --git a/static/assets/7.x/bottom-tabs-side-compact.png b/static/assets/7.x/bottom-tabs-side-compact.png index 5217d8ac77d..e060b1c74c1 100644 Binary files a/static/assets/7.x/bottom-tabs-side-compact.png and b/static/assets/7.x/bottom-tabs-side-compact.png differ diff --git a/static/assets/7.x/bottom-tabs-side-material.png b/static/assets/7.x/bottom-tabs-side-material.png index 9016f94cdfe..01f4e285f5b 100644 Binary files a/static/assets/7.x/bottom-tabs-side-material.png and b/static/assets/7.x/bottom-tabs-side-material.png differ diff --git a/static/assets/7.x/bottom-tabs-side.png b/static/assets/7.x/bottom-tabs-side.png index 9a0fdc7cac7..6e9087cebb8 100644 Binary files a/static/assets/7.x/bottom-tabs-side.png and b/static/assets/7.x/bottom-tabs-side.png differ diff --git a/static/assets/7.x/bottom-tabs.mp4 b/static/assets/7.x/bottom-tabs.mp4 index ed4d3992cdd..7bae95d7bfe 100644 Binary files a/static/assets/7.x/bottom-tabs.mp4 and b/static/assets/7.x/bottom-tabs.mp4 differ diff --git a/static/assets/7.x/bottom-tabs/tabBarActiveTintColor.png b/static/assets/7.x/bottom-tabs/tabBarActiveTintColor.png index e9b274fc92a..9d6d42ab8fc 100644 Binary files a/static/assets/7.x/bottom-tabs/tabBarActiveTintColor.png and b/static/assets/7.x/bottom-tabs/tabBarActiveTintColor.png differ diff --git a/static/assets/7.x/bottom-tabs/tabBarBackground.png b/static/assets/7.x/bottom-tabs/tabBarBackground.png index 650838f1468..7f8bd8d2cde 100644 Binary files a/static/assets/7.x/bottom-tabs/tabBarBackground.png and b/static/assets/7.x/bottom-tabs/tabBarBackground.png differ diff --git a/static/assets/7.x/bottom-tabs/tabBarBadge.png b/static/assets/7.x/bottom-tabs/tabBarBadge.png index e04963dfbcb..aa47a78cdcf 100644 Binary files a/static/assets/7.x/bottom-tabs/tabBarBadge.png and b/static/assets/7.x/bottom-tabs/tabBarBadge.png differ diff --git a/static/assets/7.x/bottom-tabs/tabBarBadgeStyle.png b/static/assets/7.x/bottom-tabs/tabBarBadgeStyle.png index 2b406d9c5e2..ac086c8c232 100644 Binary files a/static/assets/7.x/bottom-tabs/tabBarBadgeStyle.png and b/static/assets/7.x/bottom-tabs/tabBarBadgeStyle.png differ diff --git a/static/assets/7.x/bottom-tabs/tabBarInactiveTintColor.png b/static/assets/7.x/bottom-tabs/tabBarInactiveTintColor.png index b6c7aa3c22e..63db2479aa0 100644 Binary files a/static/assets/7.x/bottom-tabs/tabBarInactiveTintColor.png and b/static/assets/7.x/bottom-tabs/tabBarInactiveTintColor.png differ diff --git a/static/assets/7.x/bottom-tabs/tabBarLabelPosition-below.png b/static/assets/7.x/bottom-tabs/tabBarLabelPosition-below.png index 9b5924cd897..c1c27e6421b 100644 Binary files a/static/assets/7.x/bottom-tabs/tabBarLabelPosition-below.png and b/static/assets/7.x/bottom-tabs/tabBarLabelPosition-below.png differ diff --git a/static/assets/7.x/bottom-tabs/tabBarLabelPosition-beside.png b/static/assets/7.x/bottom-tabs/tabBarLabelPosition-beside.png index 3be06ede805..6340048b67f 100644 Binary files a/static/assets/7.x/bottom-tabs/tabBarLabelPosition-beside.png and b/static/assets/7.x/bottom-tabs/tabBarLabelPosition-beside.png differ diff --git a/static/assets/7.x/bottom-tabs/tabBarLabelStyle.png b/static/assets/7.x/bottom-tabs/tabBarLabelStyle.png index 74d7c3f848b..e7d205d1b6a 100644 Binary files a/static/assets/7.x/bottom-tabs/tabBarLabelStyle.png and b/static/assets/7.x/bottom-tabs/tabBarLabelStyle.png differ diff --git a/static/assets/7.x/devtool-logger.mp4 b/static/assets/7.x/devtool-logger.mp4 index 7a6073f8b1c..8552792039f 100644 Binary files a/static/assets/7.x/devtool-logger.mp4 and b/static/assets/7.x/devtool-logger.mp4 differ diff --git a/static/assets/7.x/drawer-layout.mp4 b/static/assets/7.x/drawer-layout.mp4 index ff076ec0045..4fcbb3f80cd 100644 Binary files a/static/assets/7.x/drawer-layout.mp4 and b/static/assets/7.x/drawer-layout.mp4 differ diff --git a/static/assets/7.x/drawer.mp4 b/static/assets/7.x/drawer.mp4 index 5277da8d162..e56b031d1bb 100644 Binary files a/static/assets/7.x/drawer.mp4 and b/static/assets/7.x/drawer.mp4 differ diff --git a/static/assets/7.x/drawer/drawerActiveBackgroundColor.png b/static/assets/7.x/drawer/drawerActiveBackgroundColor.png index 3340babed15..6e9c66345c2 100644 Binary files a/static/assets/7.x/drawer/drawerActiveBackgroundColor.png and b/static/assets/7.x/drawer/drawerActiveBackgroundColor.png differ diff --git a/static/assets/7.x/drawer/drawerActiveTintColor.png b/static/assets/7.x/drawer/drawerActiveTintColor.png index 5ebfb044b12..ef4b773aa2f 100644 Binary files a/static/assets/7.x/drawer/drawerActiveTintColor.png and b/static/assets/7.x/drawer/drawerActiveTintColor.png differ diff --git a/static/assets/7.x/drawer/drawerItemStyle.png b/static/assets/7.x/drawer/drawerItemStyle.png index 72a239d365d..0ac6ab0121d 100644 Binary files a/static/assets/7.x/drawer/drawerItemStyle.png and b/static/assets/7.x/drawer/drawerItemStyle.png differ diff --git a/static/assets/7.x/drawer/drawerLabelStyle.png b/static/assets/7.x/drawer/drawerLabelStyle.png index cbe5531ed8b..ed844bdf448 100644 Binary files a/static/assets/7.x/drawer/drawerLabelStyle.png and b/static/assets/7.x/drawer/drawerLabelStyle.png differ diff --git a/static/assets/7.x/drawer/drawerStatusBarAnimation-fade.mp4 b/static/assets/7.x/drawer/drawerStatusBarAnimation-fade.mp4 index 4945278107f..5492c31ffdb 100644 Binary files a/static/assets/7.x/drawer/drawerStatusBarAnimation-fade.mp4 and b/static/assets/7.x/drawer/drawerStatusBarAnimation-fade.mp4 differ diff --git a/static/assets/7.x/drawer/drawerStatusBarAnimation-slide.mp4 b/static/assets/7.x/drawer/drawerStatusBarAnimation-slide.mp4 index c26696e06dc..b58a6eee328 100644 Binary files a/static/assets/7.x/drawer/drawerStatusBarAnimation-slide.mp4 and b/static/assets/7.x/drawer/drawerStatusBarAnimation-slide.mp4 differ diff --git a/static/assets/7.x/drawer/drawerStyle.png b/static/assets/7.x/drawer/drawerStyle.png index 6a0f1de808a..a108de30192 100644 Binary files a/static/assets/7.x/drawer/drawerStyle.png and b/static/assets/7.x/drawer/drawerStyle.png differ diff --git a/static/assets/7.x/drawer/drawerType-back.mp4 b/static/assets/7.x/drawer/drawerType-back.mp4 index e4a6b03922b..036fdfa7eb2 100644 Binary files a/static/assets/7.x/drawer/drawerType-back.mp4 and b/static/assets/7.x/drawer/drawerType-back.mp4 differ diff --git a/static/assets/7.x/drawer/drawerType-front.mp4 b/static/assets/7.x/drawer/drawerType-front.mp4 index 3dcf3f70e0b..8e8cac102bc 100644 Binary files a/static/assets/7.x/drawer/drawerType-front.mp4 and b/static/assets/7.x/drawer/drawerType-front.mp4 differ diff --git a/static/assets/7.x/drawer/drawerType-slide.mp4 b/static/assets/7.x/drawer/drawerType-slide.mp4 index 02975a99f28..c766a80d3e7 100644 Binary files a/static/assets/7.x/drawer/drawerType-slide.mp4 and b/static/assets/7.x/drawer/drawerType-slide.mp4 differ diff --git a/static/assets/7.x/drawer/overlayColor.mp4 b/static/assets/7.x/drawer/overlayColor.mp4 index 7d58fa28b13..138d74a4be4 100644 Binary files a/static/assets/7.x/drawer/overlayColor.mp4 and b/static/assets/7.x/drawer/overlayColor.mp4 differ diff --git a/static/assets/7.x/material-top-tabs.mp4 b/static/assets/7.x/material-top-tabs.mp4 index 5717c365909..8de8ed08acb 100644 Binary files a/static/assets/7.x/material-top-tabs.mp4 and b/static/assets/7.x/material-top-tabs.mp4 differ diff --git a/static/assets/7.x/native-bottom-tabs-android.mp4 b/static/assets/7.x/native-bottom-tabs-android.mp4 new file mode 100644 index 00000000000..4c65cf2105c Binary files /dev/null and b/static/assets/7.x/native-bottom-tabs-android.mp4 differ diff --git a/static/assets/7.x/native-bottom-tabs-ios-minimize.mp4 b/static/assets/7.x/native-bottom-tabs-ios-minimize.mp4 new file mode 100644 index 00000000000..2460652d08b Binary files /dev/null and b/static/assets/7.x/native-bottom-tabs-ios-minimize.mp4 differ diff --git a/static/assets/7.x/native-bottom-tabs-ios-search.mp4 b/static/assets/7.x/native-bottom-tabs-ios-search.mp4 new file mode 100644 index 00000000000..e3892bde2f3 Binary files /dev/null and b/static/assets/7.x/native-bottom-tabs-ios-search.mp4 differ diff --git a/static/assets/7.x/native-bottom-tabs-ios.mp4 b/static/assets/7.x/native-bottom-tabs-ios.mp4 new file mode 100644 index 00000000000..7d6d7127fa3 Binary files /dev/null and b/static/assets/7.x/native-bottom-tabs-ios.mp4 differ diff --git a/static/assets/7.x/native-stack-android.mp4 b/static/assets/7.x/native-stack-android.mp4 index 7483dcd8f81..34b6d0501a3 100644 Binary files a/static/assets/7.x/native-stack-android.mp4 and b/static/assets/7.x/native-stack-android.mp4 differ diff --git a/static/assets/7.x/native-stack-ios.mp4 b/static/assets/7.x/native-stack-ios.mp4 index 2eb1e2f4946..875ae9a0134 100644 Binary files a/static/assets/7.x/native-stack-ios.mp4 and b/static/assets/7.x/native-stack-ios.mp4 differ diff --git a/static/assets/7.x/native-stack/animationTypeForReplace-pop.mp4 b/static/assets/7.x/native-stack/animationTypeForReplace-pop.mp4 index eff41148ce0..6d2b62e4e4b 100644 Binary files a/static/assets/7.x/native-stack/animationTypeForReplace-pop.mp4 and b/static/assets/7.x/native-stack/animationTypeForReplace-pop.mp4 differ diff --git a/static/assets/7.x/native-stack/animationTypeForReplace-push.mp4 b/static/assets/7.x/native-stack/animationTypeForReplace-push.mp4 index dc8a3488cd5..bb0159734b3 100644 Binary files a/static/assets/7.x/native-stack/animationTypeForReplace-push.mp4 and b/static/assets/7.x/native-stack/animationTypeForReplace-push.mp4 differ diff --git a/static/assets/7.x/native-stack/formSheet-sheetAllowedDetents.mp4 b/static/assets/7.x/native-stack/formSheet-sheetAllowedDetents.mp4 new file mode 100644 index 00000000000..b3ca999ad1f Binary files /dev/null and b/static/assets/7.x/native-stack/formSheet-sheetAllowedDetents.mp4 differ diff --git a/static/assets/7.x/native-stack/formSheet-sheetCornerRadius.mp4 b/static/assets/7.x/native-stack/formSheet-sheetCornerRadius.mp4 new file mode 100644 index 00000000000..bd4fbf8f060 Binary files /dev/null and b/static/assets/7.x/native-stack/formSheet-sheetCornerRadius.mp4 differ diff --git a/static/assets/7.x/native-stack/formSheet-sheetElevation.mp4 b/static/assets/7.x/native-stack/formSheet-sheetElevation.mp4 new file mode 100644 index 00000000000..430e661ebc5 Binary files /dev/null and b/static/assets/7.x/native-stack/formSheet-sheetElevation.mp4 differ diff --git a/static/assets/7.x/native-stack/formSheet-sheetExpandsWhenScrolledToEdge.mp4 b/static/assets/7.x/native-stack/formSheet-sheetExpandsWhenScrolledToEdge.mp4 new file mode 100644 index 00000000000..ccb7a9b2f82 Binary files /dev/null and b/static/assets/7.x/native-stack/formSheet-sheetExpandsWhenScrolledToEdge.mp4 differ diff --git a/static/assets/7.x/native-stack/formSheet-sheetGrabberVisible.mp4 b/static/assets/7.x/native-stack/formSheet-sheetGrabberVisible.mp4 new file mode 100644 index 00000000000..9887c2a2835 Binary files /dev/null and b/static/assets/7.x/native-stack/formSheet-sheetGrabberVisible.mp4 differ diff --git a/static/assets/7.x/native-stack/formSheet-sheetInitialDetentIndex.mp4 b/static/assets/7.x/native-stack/formSheet-sheetInitialDetentIndex.mp4 new file mode 100644 index 00000000000..e655571c66a Binary files /dev/null and b/static/assets/7.x/native-stack/formSheet-sheetInitialDetentIndex.mp4 differ diff --git a/static/assets/7.x/native-stack/formSheet-sheetLargestUndimmedDetentIndex.mp4 b/static/assets/7.x/native-stack/formSheet-sheetLargestUndimmedDetentIndex.mp4 new file mode 100644 index 00000000000..4abb5dfa5e8 Binary files /dev/null and b/static/assets/7.x/native-stack/formSheet-sheetLargestUndimmedDetentIndex.mp4 differ diff --git a/static/assets/7.x/native-stack/headerBackTitle.jpeg b/static/assets/7.x/native-stack/headerBackTitle.jpeg index 587996408bd..aefb90e65fa 100644 Binary files a/static/assets/7.x/native-stack/headerBackTitle.jpeg and b/static/assets/7.x/native-stack/headerBackTitle.jpeg differ diff --git a/static/assets/7.x/native-stack/headerBackTitleStyle.png b/static/assets/7.x/native-stack/headerBackTitleStyle.png index 2b83888656c..a069fff7c31 100644 Binary files a/static/assets/7.x/native-stack/headerBackTitleStyle.png and b/static/assets/7.x/native-stack/headerBackTitleStyle.png differ diff --git a/static/assets/7.x/native-stack/headerBackground.png b/static/assets/7.x/native-stack/headerBackground.png index bd601236d1e..d1c000ee29d 100644 Binary files a/static/assets/7.x/native-stack/headerBackground.png and b/static/assets/7.x/native-stack/headerBackground.png differ diff --git a/static/assets/7.x/native-stack/headerBlurEffect-dark.png b/static/assets/7.x/native-stack/headerBlurEffect-dark.png index 88da21ccbee..3b60d94f124 100644 Binary files a/static/assets/7.x/native-stack/headerBlurEffect-dark.png and b/static/assets/7.x/native-stack/headerBlurEffect-dark.png differ diff --git a/static/assets/7.x/native-stack/headerBlurEffect-light.png b/static/assets/7.x/native-stack/headerBlurEffect-light.png index 05293d0a790..83ce3a1e01b 100644 Binary files a/static/assets/7.x/native-stack/headerBlurEffect-light.png and b/static/assets/7.x/native-stack/headerBlurEffect-light.png differ diff --git a/static/assets/7.x/native-stack/headerBlurEffect-prominent.png b/static/assets/7.x/native-stack/headerBlurEffect-prominent.png index c6049585f97..e40f59a8e35 100644 Binary files a/static/assets/7.x/native-stack/headerBlurEffect-prominent.png and b/static/assets/7.x/native-stack/headerBlurEffect-prominent.png differ diff --git a/static/assets/7.x/native-stack/headerBlurEffect-regular.png b/static/assets/7.x/native-stack/headerBlurEffect-regular.png index 8c1891ec75f..9cdaa1a7624 100644 Binary files a/static/assets/7.x/native-stack/headerBlurEffect-regular.png and b/static/assets/7.x/native-stack/headerBlurEffect-regular.png differ diff --git a/static/assets/7.x/native-stack/headerBlurEffect-systemChromeMaterial.png b/static/assets/7.x/native-stack/headerBlurEffect-systemChromeMaterial.png index 3da2f20198b..27bfb490e99 100644 Binary files a/static/assets/7.x/native-stack/headerBlurEffect-systemChromeMaterial.png and b/static/assets/7.x/native-stack/headerBlurEffect-systemChromeMaterial.png differ diff --git a/static/assets/7.x/native-stack/headerBlurEffect-systemChromeMaterialDark.png b/static/assets/7.x/native-stack/headerBlurEffect-systemChromeMaterialDark.png index 624ab4660b2..60386773e31 100644 Binary files a/static/assets/7.x/native-stack/headerBlurEffect-systemChromeMaterialDark.png and b/static/assets/7.x/native-stack/headerBlurEffect-systemChromeMaterialDark.png differ diff --git a/static/assets/7.x/native-stack/headerBlurEffect-systemChromeMaterialLight.png b/static/assets/7.x/native-stack/headerBlurEffect-systemChromeMaterialLight.png index 1ecca6cba51..299617ee9b0 100644 Binary files a/static/assets/7.x/native-stack/headerBlurEffect-systemChromeMaterialLight.png and b/static/assets/7.x/native-stack/headerBlurEffect-systemChromeMaterialLight.png differ diff --git a/static/assets/7.x/native-stack/headerBlurEffect-systemMaterial.png b/static/assets/7.x/native-stack/headerBlurEffect-systemMaterial.png index 08dcb1a85b1..4faeef43d5d 100644 Binary files a/static/assets/7.x/native-stack/headerBlurEffect-systemMaterial.png and b/static/assets/7.x/native-stack/headerBlurEffect-systemMaterial.png differ diff --git a/static/assets/7.x/native-stack/headerBlurEffect-systemMaterialDark.png b/static/assets/7.x/native-stack/headerBlurEffect-systemMaterialDark.png index ea4bde41fcb..b62e4bea6ec 100644 Binary files a/static/assets/7.x/native-stack/headerBlurEffect-systemMaterialDark.png and b/static/assets/7.x/native-stack/headerBlurEffect-systemMaterialDark.png differ diff --git a/static/assets/7.x/native-stack/headerBlurEffect-systemMaterialLight.png b/static/assets/7.x/native-stack/headerBlurEffect-systemMaterialLight.png index 1210fce73d8..6d05a945b04 100644 Binary files a/static/assets/7.x/native-stack/headerBlurEffect-systemMaterialLight.png and b/static/assets/7.x/native-stack/headerBlurEffect-systemMaterialLight.png differ diff --git a/static/assets/7.x/native-stack/headerBlurEffect-systemThickMaterial.png b/static/assets/7.x/native-stack/headerBlurEffect-systemThickMaterial.png index e22a48720c7..8cddb001555 100644 Binary files a/static/assets/7.x/native-stack/headerBlurEffect-systemThickMaterial.png and b/static/assets/7.x/native-stack/headerBlurEffect-systemThickMaterial.png differ diff --git a/static/assets/7.x/native-stack/headerBlurEffect-systemThickMaterialDark.png b/static/assets/7.x/native-stack/headerBlurEffect-systemThickMaterialDark.png index 71f4ffc18bf..a2c436de80e 100644 Binary files a/static/assets/7.x/native-stack/headerBlurEffect-systemThickMaterialDark.png and b/static/assets/7.x/native-stack/headerBlurEffect-systemThickMaterialDark.png differ diff --git a/static/assets/7.x/native-stack/headerBlurEffect-systemThickMaterialLight.png b/static/assets/7.x/native-stack/headerBlurEffect-systemThickMaterialLight.png index 47dcdcdee5d..4b082b8bd46 100644 Binary files a/static/assets/7.x/native-stack/headerBlurEffect-systemThickMaterialLight.png and b/static/assets/7.x/native-stack/headerBlurEffect-systemThickMaterialLight.png differ diff --git a/static/assets/7.x/native-stack/headerBlurEffect-systemThinMaterial.png b/static/assets/7.x/native-stack/headerBlurEffect-systemThinMaterial.png index e43196b4f88..5e3d59d202e 100644 Binary files a/static/assets/7.x/native-stack/headerBlurEffect-systemThinMaterial.png and b/static/assets/7.x/native-stack/headerBlurEffect-systemThinMaterial.png differ diff --git a/static/assets/7.x/native-stack/headerBlurEffect-systemThinMaterialDark.png b/static/assets/7.x/native-stack/headerBlurEffect-systemThinMaterialDark.png index e041329c81e..b0adec013f6 100644 Binary files a/static/assets/7.x/native-stack/headerBlurEffect-systemThinMaterialDark.png and b/static/assets/7.x/native-stack/headerBlurEffect-systemThinMaterialDark.png differ diff --git a/static/assets/7.x/native-stack/headerBlurEffect-systemThinMaterialLight.png b/static/assets/7.x/native-stack/headerBlurEffect-systemThinMaterialLight.png index a935bca521e..4455cd408f9 100644 Binary files a/static/assets/7.x/native-stack/headerBlurEffect-systemThinMaterialLight.png and b/static/assets/7.x/native-stack/headerBlurEffect-systemThinMaterialLight.png differ diff --git a/static/assets/7.x/native-stack/headerBlurEffect-systemUltraThinMaterial.png b/static/assets/7.x/native-stack/headerBlurEffect-systemUltraThinMaterial.png index b525154609d..93f284f0f98 100644 Binary files a/static/assets/7.x/native-stack/headerBlurEffect-systemUltraThinMaterial.png and b/static/assets/7.x/native-stack/headerBlurEffect-systemUltraThinMaterial.png differ diff --git a/static/assets/7.x/native-stack/headerBlurEffect-systemUltraThinMaterialDark.png b/static/assets/7.x/native-stack/headerBlurEffect-systemUltraThinMaterialDark.png index 5960598b817..32d97263e1e 100644 Binary files a/static/assets/7.x/native-stack/headerBlurEffect-systemUltraThinMaterialDark.png and b/static/assets/7.x/native-stack/headerBlurEffect-systemUltraThinMaterialDark.png differ diff --git a/static/assets/7.x/native-stack/headerBlurEffect-systemUltraThinMaterialLight.png b/static/assets/7.x/native-stack/headerBlurEffect-systemUltraThinMaterialLight.png index bfe19277db7..44015ab7b2e 100644 Binary files a/static/assets/7.x/native-stack/headerBlurEffect-systemUltraThinMaterialLight.png and b/static/assets/7.x/native-stack/headerBlurEffect-systemUltraThinMaterialLight.png differ diff --git a/static/assets/7.x/native-stack/headerLargeStyle.mp4 b/static/assets/7.x/native-stack/headerLargeStyle.mp4 index 9ff39788224..e0f1147d300 100644 Binary files a/static/assets/7.x/native-stack/headerLargeStyle.mp4 and b/static/assets/7.x/native-stack/headerLargeStyle.mp4 differ diff --git a/static/assets/7.x/native-stack/headerLargeTitleStyle.png b/static/assets/7.x/native-stack/headerLargeTitleStyle.png index 4722cb6c719..b12263d84de 100644 Binary files a/static/assets/7.x/native-stack/headerLargeTitleStyle.png and b/static/assets/7.x/native-stack/headerLargeTitleStyle.png differ diff --git a/static/assets/7.x/native-stack/headerLeft.png b/static/assets/7.x/native-stack/headerLeft.png index d46e1cc63cb..ce87ac31a25 100644 Binary files a/static/assets/7.x/native-stack/headerLeft.png and b/static/assets/7.x/native-stack/headerLeft.png differ diff --git a/static/assets/7.x/native-stack/headerRight.png b/static/assets/7.x/native-stack/headerRight.png index 49af6766bdf..41d0b13c660 100644 Binary files a/static/assets/7.x/native-stack/headerRight.png and b/static/assets/7.x/native-stack/headerRight.png differ diff --git a/static/assets/7.x/native-stack/headerSearchBarOptions-barTintColor.png b/static/assets/7.x/native-stack/headerSearchBarOptions-barTintColor.png index 2aedbec7805..10928943e9e 100644 Binary files a/static/assets/7.x/native-stack/headerSearchBarOptions-barTintColor.png and b/static/assets/7.x/native-stack/headerSearchBarOptions-barTintColor.png differ diff --git a/static/assets/7.x/native-stack/headerSearchBarOptions-headerIconColor.png b/static/assets/7.x/native-stack/headerSearchBarOptions-headerIconColor.png index 85b31a7d766..faaf8434b6b 100644 Binary files a/static/assets/7.x/native-stack/headerSearchBarOptions-headerIconColor.png and b/static/assets/7.x/native-stack/headerSearchBarOptions-headerIconColor.png differ diff --git a/static/assets/7.x/native-stack/headerSearchBarOptions-hintTextColor.png b/static/assets/7.x/native-stack/headerSearchBarOptions-hintTextColor.png index cbeacc6c3ac..f3031cb8f9c 100644 Binary files a/static/assets/7.x/native-stack/headerSearchBarOptions-hintTextColor.png and b/static/assets/7.x/native-stack/headerSearchBarOptions-hintTextColor.png differ diff --git a/static/assets/7.x/native-stack/headerSearchBarOptions-textColor.png b/static/assets/7.x/native-stack/headerSearchBarOptions-textColor.png index f1f7cd2a4ba..10d3742c5ba 100644 Binary files a/static/assets/7.x/native-stack/headerSearchBarOptions-textColor.png and b/static/assets/7.x/native-stack/headerSearchBarOptions-textColor.png differ diff --git a/static/assets/7.x/native-stack/headerSearchBarOptions-tintColor.png b/static/assets/7.x/native-stack/headerSearchBarOptions-tintColor.png index 0f9a84bce27..ba28ec0103c 100644 Binary files a/static/assets/7.x/native-stack/headerSearchBarOptions-tintColor.png and b/static/assets/7.x/native-stack/headerSearchBarOptions-tintColor.png differ diff --git a/static/assets/7.x/native-stack/headerShadowVisible-Android.png b/static/assets/7.x/native-stack/headerShadowVisible-Android.png index c2c9429f282..64cc8028478 100644 Binary files a/static/assets/7.x/native-stack/headerShadowVisible-Android.png and b/static/assets/7.x/native-stack/headerShadowVisible-Android.png differ diff --git a/static/assets/7.x/native-stack/headerShadowVisible-iOS.png b/static/assets/7.x/native-stack/headerShadowVisible-iOS.png index ebbfe7c8667..6b0817015df 100644 Binary files a/static/assets/7.x/native-stack/headerShadowVisible-iOS.png and b/static/assets/7.x/native-stack/headerShadowVisible-iOS.png differ diff --git a/static/assets/7.x/native-stack/headerStyle.mp4 b/static/assets/7.x/native-stack/headerStyle.mp4 index 1723abc731a..118b0d65e7f 100644 Binary files a/static/assets/7.x/native-stack/headerStyle.mp4 and b/static/assets/7.x/native-stack/headerStyle.mp4 differ diff --git a/static/assets/7.x/native-stack/headerTintColor.png b/static/assets/7.x/native-stack/headerTintColor.png index 7044179f71f..dbfbad47a0b 100644 Binary files a/static/assets/7.x/native-stack/headerTintColor.png and b/static/assets/7.x/native-stack/headerTintColor.png differ diff --git a/static/assets/7.x/native-stack/headerTitleAlign-center.png b/static/assets/7.x/native-stack/headerTitleAlign-center.png index 36da4a8204e..909751de625 100644 Binary files a/static/assets/7.x/native-stack/headerTitleAlign-center.png and b/static/assets/7.x/native-stack/headerTitleAlign-center.png differ diff --git a/static/assets/7.x/native-stack/headerTitleAlign-left.png b/static/assets/7.x/native-stack/headerTitleAlign-left.png index 50d4e6c7ab8..99a3443d584 100644 Binary files a/static/assets/7.x/native-stack/headerTitleAlign-left.png and b/static/assets/7.x/native-stack/headerTitleAlign-left.png differ diff --git a/static/assets/7.x/native-stack/headerTitleStyle.png b/static/assets/7.x/native-stack/headerTitleStyle.png index edf0ceb27c5..528e31887b0 100644 Binary files a/static/assets/7.x/native-stack/headerTitleStyle.png and b/static/assets/7.x/native-stack/headerTitleStyle.png differ diff --git a/static/assets/7.x/native-stack/native-stack-animation-fade-from-bottom.mp4 b/static/assets/7.x/native-stack/native-stack-animation-fade-from-bottom.mp4 index cec56aa0a66..1a494c2d6f3 100644 Binary files a/static/assets/7.x/native-stack/native-stack-animation-fade-from-bottom.mp4 and b/static/assets/7.x/native-stack/native-stack-animation-fade-from-bottom.mp4 differ diff --git a/static/assets/7.x/native-stack/native-stack-animation-none.mp4 b/static/assets/7.x/native-stack/native-stack-animation-none.mp4 index c4768eda32a..02c1a493acf 100644 Binary files a/static/assets/7.x/native-stack/native-stack-animation-none.mp4 and b/static/assets/7.x/native-stack/native-stack-animation-none.mp4 differ diff --git a/static/assets/7.x/native-stack/presentation-formSheet-android.mp4 b/static/assets/7.x/native-stack/presentation-formSheet-android.mp4 new file mode 100644 index 00000000000..b8241be19dc Binary files /dev/null and b/static/assets/7.x/native-stack/presentation-formSheet-android.mp4 differ diff --git a/static/assets/7.x/native-stack/presentation-formSheet-ios.mp4 b/static/assets/7.x/native-stack/presentation-formSheet-ios.mp4 new file mode 100644 index 00000000000..609acd17722 Binary files /dev/null and b/static/assets/7.x/native-stack/presentation-formSheet-ios.mp4 differ diff --git a/static/assets/7.x/native-stack/presentation-formSheet.mp4 b/static/assets/7.x/native-stack/presentation-formSheet.mp4 deleted file mode 100644 index 52de749077c..00000000000 Binary files a/static/assets/7.x/native-stack/presentation-formSheet.mp4 and /dev/null differ diff --git a/static/assets/7.x/stack-android.mp4 b/static/assets/7.x/stack-android.mp4 index 25cb6e78c04..b11ee727277 100644 Binary files a/static/assets/7.x/stack-android.mp4 and b/static/assets/7.x/stack-android.mp4 differ diff --git a/static/assets/7.x/stack-ios.mp4 b/static/assets/7.x/stack-ios.mp4 index 63a81e742ce..677c8ed7d81 100644 Binary files a/static/assets/7.x/stack-ios.mp4 and b/static/assets/7.x/stack-ios.mp4 differ diff --git a/static/assets/blog/native-bottom-tabs/android.png b/static/assets/blog/native-bottom-tabs/android.png new file mode 100644 index 00000000000..8ac1fcb0bc1 Binary files /dev/null and b/static/assets/blog/native-bottom-tabs/android.png differ diff --git a/static/assets/blog/native-bottom-tabs/ios.png b/static/assets/blog/native-bottom-tabs/ios.png new file mode 100644 index 00000000000..78792f1b4bc Binary files /dev/null and b/static/assets/blog/native-bottom-tabs/ios.png differ diff --git a/static/assets/blog/native-bottom-tabs/ipados.png b/static/assets/blog/native-bottom-tabs/ipados.png new file mode 100644 index 00000000000..5804af44bfc Binary files /dev/null and b/static/assets/blog/native-bottom-tabs/ipados.png differ diff --git a/static/assets/blog/native-bottom-tabs/macos.png b/static/assets/blog/native-bottom-tabs/macos.png new file mode 100644 index 00000000000..cb704aa3c7e Binary files /dev/null and b/static/assets/blog/native-bottom-tabs/macos.png differ diff --git a/static/assets/blog/native-bottom-tabs/result.png b/static/assets/blog/native-bottom-tabs/result.png new file mode 100644 index 00000000000..dd62cdbc222 Binary files /dev/null and b/static/assets/blog/native-bottom-tabs/result.png differ diff --git a/static/assets/blog/native-bottom-tabs/tvos.png b/static/assets/blog/native-bottom-tabs/tvos.png new file mode 100644 index 00000000000..45e3840b5d8 Binary files /dev/null and b/static/assets/blog/native-bottom-tabs/tvos.png differ diff --git a/static/assets/blog/native-bottom-tabs/visionos.png b/static/assets/blog/native-bottom-tabs/visionos.png new file mode 100644 index 00000000000..4660c2769bb Binary files /dev/null and b/static/assets/blog/native-bottom-tabs/visionos.png differ diff --git a/static/assets/deep-linking/xcode-linking.png b/static/assets/deep-linking/xcode-linking.png old mode 100755 new mode 100644 index 9f5170bf9a0..2cea12f4854 Binary files a/static/assets/deep-linking/xcode-linking.png and b/static/assets/deep-linking/xcode-linking.png differ diff --git a/static/assets/header-items/header-items-menu.png b/static/assets/header-items/header-items-menu.png new file mode 100644 index 00000000000..c900fdb84f7 Binary files /dev/null and b/static/assets/header-items/header-items-menu.png differ diff --git a/static/assets/header-items/header-items.png b/static/assets/header-items/header-items.png new file mode 100644 index 00000000000..cb7430b9128 Binary files /dev/null and b/static/assets/header-items/header-items.png differ diff --git a/static/examples/7.x/animated-drawer-content.js b/static/examples/7.x/animated-drawer-content.js deleted file mode 100755 index 0a2bf175eca..00000000000 --- a/static/examples/7.x/animated-drawer-content.js +++ /dev/null @@ -1,66 +0,0 @@ -import * as React from 'react'; -import { View, Text } from 'react-native'; -import { NavigationContainer } from '@react-navigation/native'; -import { - createDrawerNavigator, - DrawerContentScrollView, - DrawerItemList, - DrawerItem, - useDrawerProgress, -} from '@react-navigation/drawer'; -import Animated from 'react-native-reanimated'; - -function Feed() { - return ( - - Feed Screen - - ); -} - -function Article() { - return ( - - Article Screen - - ); -} - -function CustomDrawerContent(props) { - const progress = useDrawerProgress(); - - const translateX = Animated.interpolateNode(progress, { - inputRange: [0, 1], - outputRange: [-100, 0], - }); - - return ( - - - - alert('Link to help')} /> - - - ); -} - -const Drawer = createDrawerNavigator(); - -function MyDrawer() { - return ( - } - > - - - - ); -} - -export default function App() { - return ( - - - - ); -} diff --git a/static/examples/7.x/api-group.js b/static/examples/7.x/api-group.js deleted file mode 100644 index 6219b4bf7d6..00000000000 --- a/static/examples/7.x/api-group.js +++ /dev/null @@ -1,41 +0,0 @@ -import * as React from 'react'; -import { View, Text, Button } from 'react-native'; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import { NavigationContainer } from '@react-navigation/native'; - -const Stack = createNativeStackNavigator(); - -function HomeScreen({ navigation }) { - return ( - - Home Screen - - - ); -}; - -const Stack = createStackNavigator(); - -export default function App() { - return ( - - - - - - - ); -} - -const styles = StyleSheet.create({ - content: { - flex: 1, - padding: 16, - }, - input: { - margin: 8, - padding: 10, - borderRadius: 3, - borderWidth: StyleSheet.hairlineWidth, - borderColor: 'rgba(0, 0, 0, 0.08)', - backgroundColor: 'white', - }, - buttons: { - flex: 1, - justifyContent: 'center', - padding: 8, - }, - button: { - margin: 8, - }, -}); diff --git a/static/examples/7.x/redux-integration-nav-param.js b/static/examples/7.x/redux-integration-nav-param.js deleted file mode 100755 index 32153332d11..00000000000 --- a/static/examples/7.x/redux-integration-nav-param.js +++ /dev/null @@ -1,100 +0,0 @@ -import * as React from 'react'; -import { View, Text, Button, StyleSheet } from 'react-native'; -import { Provider, connect } from 'react-redux'; -import { createStore, combineReducers } from 'redux'; -import { NavigationContainer } from '@react-navigation/native'; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; - -// A very simple reducer -function counter(state, action) { - if (typeof state === 'undefined') { - return 0; - } - - switch (action.type) { - case 'INCREMENT': - return state + 1; - case 'DECREMENT': - return state - 1; - default: - return state; - } -} - -// A very simple store -let store = createStore(combineReducers({ count: counter })); - -// A screen! -function Counter({ count, dispatch, navigation }) { - return ( - - {count} - - - ); -} - -function ProfileScreen() { - const navigation = useNavigation(); - - return ( - - Profile Screen - - - ); -} - -export default function App() { - return ( - - - - ); -} -``` - - - - ## API Definition ### Props @@ -152,6 +90,7 @@ It supports the following values: - `initialRoute` - return to initial screen passed in `initialRouteName` prop, if not passed, defaults to the first screen - `order` - return to screen defined before the focused screen - `history` - return to last visited screen in the navigator; if the same screen is visited multiple times, the older entries are dropped from the history +- `fullHistory` - return to last visited screen in the navigator; doesn't drop duplicate entries unlike `history` - this behavior is useful to match how web pages work - `none` - do not handle back button #### `detachInactiveScreens` @@ -325,7 +264,7 @@ function MyTabBar({ navigation }) { ### Options -The following [options](screen-options.md) can be used to configure the screens in the navigator. These can be specified under `screenOptions` prop of `Tab.navigator` or `options` prop of `Tab.Screen`. +The following [options](screen-options.md) can be used to configure the screens in the navigator. These can be specified under `screenOptions` prop of `Tab.Navigator` or `options` prop of `Tab.Screen`. #### `title` @@ -495,7 +434,7 @@ When the tab bar is positioned on the `left` or `right`, it is styled as a sideb ```js const Tabs = createBottomTabNavigator({ screenOptions: { - tabBarPosition: isLargeScreen ? 'left' ? 'bottom', + tabBarPosition: isLargeScreen ? 'left' : 'bottom', }, // ... @@ -586,7 +525,7 @@ Style object for the component wrapping the screen content. ### Header related options -You can find the list of header related options [here](elements.md#header). These [options](screen-options.md) can be specified under `screenOptions` prop of `Tab.navigator` or `options` prop of `Tab.Screen`. You don't have to be using `@react-navigation/elements` directly to use these options, they are just documented in that page. +You can find the list of header related options [here](elements.md#header). These [options](screen-options.md) can be specified under `screenOptions` prop of `Tab.Navigator` or `options` prop of `Tab.Screen`. You don't have to be using `@react-navigation/elements` directly to use these options, they are just documented in that page. In addition to those, the following options are also supported in bottom tabs: @@ -650,24 +589,72 @@ This event is fired when the user presses the tab button for the current screen To prevent the default behavior, you can call `event.preventDefault`: - +```js name="Tab Press Event" snack static2dynamic +import * as React from 'react'; +import { Alert, Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; -```js -React.useEffect(() => { - const unsubscribe = navigation.addListener('tabPress', (e) => { - // Prevent default behavior - e.preventDefault(); +function HomeScreen() { + const navigation = useNavigation(); - // Do something manually - // ... - }); + // codeblock-focus-start + React.useEffect(() => { + const unsubscribe = navigation.addListener('tabPress', (e) => { + // Prevent default behavior + e.preventDefault(); - return unsubscribe; -}, [navigation]); + // Do something manually + // ... + }); + + return unsubscribe; + }, [navigation]); + // codeblock-focus-end + + return ( + + Home Screen + + Tab press event is prevented + + + ); +} + +function SettingsScreen() { + return ( + + Settings Screen + + ); +} + +const MyTabs = createBottomTabNavigator({ + screens: { + Home: HomeScreen, + Settings: SettingsScreen, + }, +}); + +const Navigation = createStaticNavigation(MyTabs); + +export default function App() { + return ; +} ``` If you have a custom tab bar, make sure to emit this event. +:::note + +By default, tabs are rendered lazily. So if you add a listener inside a screen component, it won't receive the event until the screen is focused for the first time. If you need to listen to this event before the screen is focused, you can specify the [listener in the screen config](navigation-events.md#listeners-prop-on-screen) instead. + +::: + #### `tabLongPress` This event is fired when the user presses the tab button for the current screen in the tab bar for an extended period. If you have a custom tab bar, make sure to emit this event. @@ -695,10 +682,59 @@ Navigates to an existing screen in the tab navigator. The method accepts followi - `name` - _string_ - Name of the route to jump to. - `params` - _object_ - Screen params to use for the destination route. - +```js name="Tab Navigator - jumpTo" snack static2dynamic +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { Button } from '@react-navigation/elements'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; + +function HomeScreen() { + const navigation = useNavigation(); -```js -navigation.jumpTo('Profile', { owner: 'Michaś' }); + return ( + + Home Screen + + + ); +} + +function ProfileScreen({ route }) { + return ( + + Profile Screen + {route.params?.owner && ( + Owner: {route.params.owner} + )} + + ); +} + +const MyTabs = createBottomTabNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(MyTabs); + +export default function App() { + return ; +} ``` ### Hooks @@ -759,10 +795,7 @@ Supported values for `animation` are: - `none` - The screen transition doesn't have any animation. This is the default value. - - - -```js name="Bottom Tabs animation" snack +```js name="Bottom Tabs animation" snack static2dynamic import * as React from 'react'; import { View, Text, Easing } from 'react-native'; import { createStaticNavigation } from '@react-navigation/native'; @@ -805,62 +838,6 @@ export default function App() { } ``` - - - -```js name="Bottom Tabs animation" snack -import * as React from 'react'; -import { Text, View, Easing } from 'react-native'; -import { NavigationContainer } from '@react-navigation/native'; -import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; - -function HomeScreen() { - return ( - - Home! - - ); -} - -function ProfileScreen() { - return ( - - Profile! - - ); -} - -const Tab = createBottomTabNavigator(); - -// codeblock-focus-start -function RootTabs() { - return ( - - - - - ); -} -// codeblock-focus-end - -export default function App() { - return ( - - - - ); -} -``` - - - - If you need more control over the animation, you can customize individual parts of the animation using the various animation-related options: ### Animation related options @@ -868,7 +845,6 @@ If you need more control over the animation, you can customize individual parts Bottom Tab Navigator exposes various options to configure the transition animation when switching tabs. These transition animations can be customized on a per-screen basis by specifying the options in the `options` for each screen, or for all screens in the tab navigator by specifying them in the `screenOptions`. - `transitionSpec` - An object that specifies the animation type (`timing` or `spring`) and its options (such as `duration` for `timing`). It contains 2 properties: - - `animation` - The animation function to use for the animation. Supported values are `timing` and `spring`. - `config` - The configuration object for the timing function. For `timing`, it can be `duration` and `easing`. For `spring`, it can be `stiffness`, `damping`, `mass`, `overshootClamping`, `restDisplacementThreshold` and `restSpeedThreshold`. @@ -933,11 +909,9 @@ Bottom Tab Navigator exposes various options to configure the transition animati - `sceneStyleInterpolator` - This is a function that specifies interpolated styles for various parts of the scene. It currently supports style for the view containing the screen: - - `sceneStyle` - Style for the container view wrapping the screen content. The function receives the following properties in its argument: - - `current` - Animation values for the current screen: - `progress` - Animated node representing the progress value of the current screen. @@ -955,7 +929,6 @@ Bottom Tab Navigator exposes various options to configure the transition animati ``` The value of `current.progress` is as follows: - - -1 if the index is lower than the active tab, - 0 if they're active, - 1 if the index is higher than the active tab @@ -1012,10 +985,9 @@ Bottom Tab Navigator exposes various options to configure the transition animati Putting these together, you can customize the transition animation for a screen: - - +Putting these together, you can customize the transition animation for a screen: -```js name="Bottom Tabs custom animation" snack +```js name="Bottom Tabs custom animation" snack static2dynamic import * as React from 'react'; import { View, Text, Easing } from 'react-native'; import { createStaticNavigation } from '@react-navigation/native'; @@ -1070,74 +1042,6 @@ export default function App() { } ``` - - - -```js name="Bottom Tabs custom animation" snack -import * as React from 'react'; -import { Text, View, Easing } from 'react-native'; -import { NavigationContainer } from '@react-navigation/native'; -import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; - -function HomeScreen() { - return ( - - Home! - - ); -} - -function ProfileScreen() { - return ( - - Profile! - - ); -} - -const Tab = createBottomTabNavigator(); - -// codeblock-focus-start -function RootTabs() { - return ( - ({ - sceneStyle: { - opacity: current.progress.interpolate({ - inputRange: [-1, 0, 1], - outputRange: [0, 1, 0], - }), - }, - }), - }} - > - - - - ); -} -// codeblock-focus-end - -export default function App() { - return ( - - - - ); -} -``` - - - - ### Pre-made configs We also export various configs from the library with ready-made configs that you can use to customize the animations: diff --git a/versioned_docs/version-7.x/combine-static-with-dynamic.md b/versioned_docs/version-7.x/combine-static-with-dynamic.md index 6b9c738e350..bfb8c80b619 100644 --- a/versioned_docs/version-7.x/combine-static-with-dynamic.md +++ b/versioned_docs/version-7.x/combine-static-with-dynamic.md @@ -198,7 +198,7 @@ import { createPathConfigForStaticNavigation } from '@react-navigation/native'; const feedScreens = createPathConfigForStaticNavigation(FeedTabs); const linking = { - prefixes: ['https://mychat.com', 'mychat://'], + prefixes: ['https://example.com', 'example://'], config: { screens: { Home: '', diff --git a/versioned_docs/version-7.x/community-libraries.md b/versioned_docs/version-7.x/community-libraries.md new file mode 100755 index 00000000000..74b8fc2e3e0 --- /dev/null +++ b/versioned_docs/version-7.x/community-libraries.md @@ -0,0 +1,31 @@ +--- +id: community-libraries +title: Community libraries +sidebar_label: Community libraries +--- + +This guide lists various community libraries that can be used alongside React Navigation to enhance its functionality. + +:::note + +Please refer to the library's documentation to see which version of React Navigation it supports. + +::: + +## react-native-screen-transitions + +A library that provides customizable screen transition animations for React Navigation's [Native Stack Navigator](native-stack-navigator.md). + +[Repository](https://github.com/eds2002/react-native-screen-transitions) + +### react-navigation-header-buttons + +Helps you to render buttons in the navigation bar and handle the styling so you don't have to. It tries to mimic the appearance of native navbar buttons and attempts to offer a simple interface for you to interact with. + +[Repository](https://github.com/vonovak/react-navigation-header-buttons) + +### react-navigation-props-mapper + +Provides simple HOCs that map react-navigation props to your screen components directly - ie. instead of `const user = this.props.route.params.activeUser`, you'd write `const user = this.props.activeUser`. + +[Repository](https://github.com/vonovak/react-navigation-props-mapper) diff --git a/versioned_docs/version-7.x/community-navigators.md b/versioned_docs/version-7.x/community-navigators.md new file mode 100755 index 00000000000..74c1a3278c8 --- /dev/null +++ b/versioned_docs/version-7.x/community-navigators.md @@ -0,0 +1,31 @@ +--- +id: community-navigators +title: Community navigators +sidebar_label: Community navigators +--- + +This guide lists various community navigators for React Navigation. These navigators offer provide UI components for navigation with the familiar React Navigation API. + +If you're looking to build your own navigator, check out the [custom navigators guide](custom-navigators.md). + +:::note + +Please refer to the library's documentation to see which version of React Navigation it supports. + +::: + +## React Native Bottom Tabs + +This project aims to expose the native Bottom Tabs component to React Native. It exposes SwiftUI's TabView on iOS and the material design tab bar on Android. Using `react-native-bottom-tabs` can bring several benefits, including multi-platform support and a native-feeling tab bar. + +[Documentation](https://oss.callstack.com/react-native-bottom-tabs/) + +[Repository](https://github.com/callstackincubator/react-native-bottom-tabs) + +## BottomNavigation - React Native Paper + +The library provides React Navigation integration for its Material Bottom Tabs. Material Bottom Tabs is a material-design themed tab bar on the bottom of the screen that lets you switch between different routes with animation. + +[Documentation](https://callstack.github.io/react-native-paper/docs/guides/bottom-navigation/) + +[Repository](https://github.com/callstack/react-native-paper) diff --git a/versioned_docs/version-7.x/community-solutions.md b/versioned_docs/version-7.x/community-solutions.md new file mode 100755 index 00000000000..18b8f2c81c6 --- /dev/null +++ b/versioned_docs/version-7.x/community-solutions.md @@ -0,0 +1,35 @@ +--- +id: community-solutions +title: Community solutions +sidebar_label: Community solutions +--- + +This guide lists various community navigation solutions built on top of React Navigation that offer a different API or complement React Navigation in some way. + +:::note + +Please refer to the library's documentation to see which version of React Navigation it supports. + +::: + +## Expo Router + +Expo Router is a file-based router for React Native and web applications built by the Expo team. + +[Documentation](https://docs.expo.dev/router/introduction/) + +[Repository](https://github.com/expo/expo/tree/main/packages/expo-router) + +## Solito + +A wrapper around React Navigation and Next.js that lets you share navigation code across platforms. Also, it provides a set of patterns and examples for building cross-platform apps with React Native + Next.js. + +[Documentation](https://solito.dev/) + +[Repository](https://github.com/nandorojo/solito) + +## Navio + +Navio provides a different API for React Navigation. It's main goal is to improve DX by building the app layout in one place and using the power of TypeScript to provide route names autocompletion. + +[Repository](https://github.com/kanzitelli/rn-navio) diff --git a/versioned_docs/version-7.x/configuring-links.md b/versioned_docs/version-7.x/configuring-links.md index 1f68858f904..7b98cbee23c 100644 --- a/versioned_docs/version-7.x/configuring-links.md +++ b/versioned_docs/version-7.x/configuring-links.md @@ -92,13 +92,13 @@ You can also pass a [`fallback`](navigation-container.md#fallback) prop that con ## Prefixes -The `prefixes` option can be used to specify custom schemes (e.g. `mychat://`) as well as host & domain names (e.g. `https://mychat.com`) if you have configured [Universal Links](https://developer.apple.com/ios/universal-links/) or [Android App Links](https://developer.android.com/training/app-links). +The `prefixes` option can be used to specify custom schemes (e.g. `example://`) as well as host & domain names (e.g. `https://example.com`) if you have configured [Universal Links](https://developer.apple.com/ios/universal-links/) or [Android App Links](https://developer.android.com/training/app-links). For example: ```js const linking = { - prefixes: ['mychat://', 'https://mychat.com'], + prefixes: ['example://', 'https://example.com'], }; ``` @@ -106,11 +106,11 @@ Note that the `prefixes` option is not supported on Web. The host & domain names ### Multiple subdomains​ -To match all subdomains of an associated domain, you can specify a wildcard by prefixing `*`. before the beginning of a specific domain. Note that an entry for `*.mychat.com` does not match `mychat.com` because of the period after the asterisk. To enable matching for both `*.mychat.com` and `mychat.com`, you need to provide a separate prefix entry for each. +To match all subdomains of an associated domain, you can specify a wildcard by prefixing `*`. before the beginning of a specific domain. Note that an entry for `*.example.com` does not match `example.com` because of the period after the asterisk. To enable matching for both `*.example.com` and `example.com`, you need to provide a separate prefix entry for each. ```js const linking = { - prefixes: ['mychat://', 'https://mychat.com', 'https://*.mychat.com'], + prefixes: ['example://', 'https://example.com', 'https://*.example.com'], }; ``` @@ -122,7 +122,7 @@ To achieve this, you can use the `filter` option: ```js const linking = { - prefixes: ['mychat://', 'https://mychat.com'], + prefixes: ['example://', 'https://example.com'], // highlight-next-line filter: (url) => !url.includes('+expo-auth-session'), }; @@ -132,11 +132,11 @@ This is not supported on Web as we always need to handle the URL of the page. ## Apps under subpaths -If your app is hosted under a subpath, you can specify the subpath at the top-level of the `config`. For example, if your app is hosted at `https://mychat.com/app`, you can specify the `path` as `app`: +If your app is hosted under a subpath, you can specify the subpath at the top-level of the `config`. For example, if your app is hosted at `https://example.com/app`, you can specify the `path` as `app`: ```js const linking = { - prefixes: ['mychat://', 'https://mychat.com'], + prefixes: ['example://', 'https://example.com'], config: { // highlight-next-line path: 'app', @@ -322,7 +322,7 @@ const config = { }; const linking = { - prefixes: ['https://mychat.com', 'mychat://'], + prefixes: ['https://example.com', 'example://'], config, }; @@ -1357,7 +1357,7 @@ Example: ```js const linking = { - prefixes: ['https://mychat.com', 'mychat://'], + prefixes: ['https://example.com', 'example://'], getStateFromPath: (path, options) => { // Return a state object here // You can also reuse the default logic by importing `getStateFromPath` from `@react-navigation/native` diff --git a/versioned_docs/version-7.x/contributing.md b/versioned_docs/version-7.x/contributing.md index aa31a3cb0b6..ec05c128ca0 100755 --- a/versioned_docs/version-7.x/contributing.md +++ b/versioned_docs/version-7.x/contributing.md @@ -15,23 +15,9 @@ Here are some of the ways to contribute to the project: - [Bug Fixes](#bug-fixes) - [Suggesting a Feature](#suggesting-a-feature) - [Big Pull Requests](#big-pull-requests) -- [Information](#information) - - [Issue Template](#issue-template) - - [Pull Request Template](#pull-request-template) - - [Forking the Repository](#forking-the-repository) - - [Code Review Guidelines](#code-review-guidelines) - - [Run the Example App](#run-the-example-app) - - [Run Tests](#run-tests) And here are a few helpful resources to aid in getting started: -- [Contributing](#contributing) - - [Reporting Bugs](#reporting-bugs) - - [Improving the Documentation](#improving-the-documentation) - - [Responding to Issues](#responding-to-issues) - - [Bug Fixes](#bug-fixes) - - [Suggesting a Feature](#suggesting-a-feature) - - [Big Pull Requests](#big-pull-requests) - [Information](#information) - [Issue Template](#issue-template) - [Pull Request Template](#pull-request-template) @@ -47,7 +33,7 @@ And here are a few helpful resources to aid in getting started: You can't write code without writing the occasional bug. Especially as React Navigation is moving quickly, bugs happen. When you think you've found one here's what to do: 1. Search the existing issues for one like what you're seeing. If you see one, add a 👍 reaction (please no +1 comments). Read through the comments and see if you can provide any more valuable information to the thread -2. If there are no other issues like yours then create a new one. Be sure to follow the [issue template](https://github.com/react-navigation/react-navigation/blob/main/.github/ISSUE_TEMPLATE.md). +2. If there are no other issues like yours then create a new one. Be sure to follow the [issue template](https://github.com/react-navigation/react-navigation/blob/main/.github/ISSUE_TEMPLATE/bug-report.yml). Creating a high quality reproduction is critical. Without it we likely can't fix the bug and, in an ideal situation, you'll find out that it's not actually a bug of the library but simply done incorrectly in your project. Instant bug fix! @@ -96,7 +82,7 @@ The reason we want to do this is to save everyone time. Maybe that feature alrea ### Issue Template -Before submitting an issue, please take a look at the [issue template](https://github.com/react-navigation/react-navigation/blob/main/.github/ISSUE_TEMPLATE.md) and follow it. This is in place to help everyone better understand the issue you're having and reduce the back and forth to get the necessary information. +Before submitting an issue, please take a look at the [issue template](https://github.com/react-navigation/react-navigation/blob/main/.github/ISSUE_TEMPLATE/bug-report.yml) and follow it. This is in place to help everyone better understand the issue you're having and reduce the back and forth to get the necessary information. Yes, it takes time and effort to complete the issue template. But that's the only way to ask high quality questions that actually get responses. @@ -104,7 +90,7 @@ Would you rather take 1 minute to create an incomplete issue report and wait mon ### Pull Request Template -Much like the issue template, the [pull request template](https://github.com/react-navigation/react-navigation/blob/main/.github/PULL_REQUEST.md) lays out instructions to ensure your pull request gets reviewed in a timely manner and reduces the back and forth. Make sure to look it over before you start writing any code. +Much like the issue template, the [pull request template](https://github.com/react-navigation/react-navigation/blob/main/.github/PULL_REQUEST_TEMPLATE.md) lays out instructions to ensure your pull request gets reviewed in a timely manner and reduces the back and forth. Make sure to look it over before you start writing any code. ### Forking the Repository diff --git a/versioned_docs/version-7.x/custom-navigators.md b/versioned_docs/version-7.x/custom-navigators.md index 3d3e05397d8..e50e11714d4 100755 --- a/versioned_docs/version-7.x/custom-navigators.md +++ b/versioned_docs/version-7.x/custom-navigators.md @@ -4,30 +4,63 @@ title: Custom navigators sidebar_label: Custom navigators --- -Navigators allow you to define your application's navigation structure. Navigators also render common elements such as headers and tab bars which you can configure. +In essence, a navigator is a React component that takes a set of screens and options, and renders them based on its [navigation state](navigation-state.md), generally with additional UI such as headers, tab bars, or drawers. -Under the hood, navigators are plain React components. +React Navigation provides a few built-in navigators, but they might not always fit your needs if you want a very custom behavior or UI. In such cases, you can build your own custom navigators using React Navigation's APIs. -## Built-in Navigators +A custom navigator behaves just like a built-in navigator, and can be used in the same way. This means you can define screens the same way, use [route](route-object.md) and [navigation](navigation-object.md) objects in your screens, and navigate between screens with familiar API. The navigator will also be able to handle deep linking, state persistence, and other features that built-in navigators support. -We include some commonly needed navigators such as: +## Overview -- [`createStackNavigator`](stack-navigator.md) - Renders one screen at a time and provides transitions between screens. When a new screen is opened it is placed on top of the stack. -- [`createDrawerNavigator`](drawer-navigator.md) - Provides a drawer that slides in from the left of the screen by default. -- [`createBottomTabNavigator`](bottom-tab-navigator.md) - Renders a tab bar that lets the user switch between several screens. -- [`createMaterialTopTabNavigator`](material-top-tab-navigator.md) - Renders tab view which lets the user switch between several screens using swipe gesture or the tab bar. +Under the hood, navigators are plain React components that use the [`useNavigationBuilder`](#usenavigationbuilder) hook. -## API for building custom navigators +The navigator component then uses this state to layout the screens appropriately with any additional UI based on the use case. This component is then wrapped in [`createNavigatorFactory`](#createnavigatorfactory) to create the API for the navigator. -A navigator bundles a router and a view which takes the [navigation state](navigation-state.md) and decides how to render it. We export a `useNavigationBuilder` hook to build custom navigators that integrate with rest of React Navigation. +A very basic example looks like this: + +```js +function MyStackNavigator(props) { + const { state, descriptors, NavigationContent } = useNavigationBuilder( + StackRouter, + props + ); + + const focusedRoute = state.routes[state.index]; + const descriptor = descriptors[focusedRoute.key]; + + return {descriptor.render()}; +} + +export const createMyStackNavigator = createNavigatorFactory(MyStackNavigator); +``` + +Now, we have an already working navigator, even though it doesn't do anything special yet. + +Let's break this down: + +- We define a `MyNavigator` component that contains our navigator logic. This is the component that's rendered when you render `` in your app with the `createMyStackNavigator` factory function. +- We use the `useNavigationBuilder` hook and pass it [`StackRouter`](custom-routers.md#built-in-routers), which would make our navigator behave like a stack navigator. Any other router such as `TabRouter`, `DrawerRouter`, or a custom router can be used here as well. +- The hook returns the [navigation state](navigation-state.md) in the `state` property. This is the current state of the navigator. There's also a `descriptors` object which contains the data and helpers for each screen in the navigator. +- We get the focused route from the state with `state.routes[state.index]` - as `state.index` is the index of the currently focused route in the `state.routes` array. +- Then we get the corresponding descriptor for the focused route with `descriptors[focusedRoute.key]` and call the `render()` method on it to get the React element for the screen. +- The content of the navigator is wrapped in `NavigationContent` to provide appropriate context and wrappers. + +With this, we have a basic stack navigator that renders only the focused screen. Unlike the built-in stack navigator, this doesn't keep unfocused screens rendered. But you can loop through `state.routes` and render all of the screens if you want to keep them mounted. You can also read `descriptor.options` to get the [options](screen-options.md) to handle the screen's title, header, and other options. + +This also doesn't have any additional UI apart from the screen content. There are no gestures or animations. So you're free to add any additional UI, gestures, animations etc. as needed. You can also layout the screens in any way you want, such as rendering them side-by-side or in a grid, instead of stacking them on top of each other like the built-in stack navigator does. + +You can see a more complete example of a custom navigator later in this document. + +## API Definition ### `useNavigationBuilder` -This hook allows a component to hook into React Navigation. It accepts the following arguments: +This hook contains the core logic of a navigator, and is responsible for storing and managing the [navigation state](navigation-state.md). It takes a [router](custom-routers.md) as an argument to know how to handle various navigation actions. It then returns the state and helper methods for the navigator component to use. + +It accepts the following arguments: - `createRouter` - A factory method which returns a router object (e.g. `StackRouter`, `TabRouter`). - `options` - Options for the hook and the router. The navigator should forward its props here so that user can provide props to configure the navigator. By default, the following options are accepted: - - `children` (required) - The `children` prop should contain route configurations as `Screen` components. - `screenOptions` - The `screenOptions` prop should contain default options for all of the screens. - `initialRouteName` - The `initialRouteName` prop determines the screen to focus on initial render. This prop is forwarded to the router. @@ -39,7 +72,6 @@ The hook returns an object with following properties: - `state` - The [navigation state](navigation-state.md) for the navigator. The component can take this state and decide how to render it. - `navigation` - The navigation object containing various helper methods for the navigator to manipulate the [navigation state](navigation-state.md). This isn't the same as the navigation object for the screen and includes some helpers such as `emit` to emit events to the screens. - `descriptors` - This is an object containing descriptors for each route with the route keys as its properties. The descriptor for a route can be accessed by `descriptors[route.key]`. Each descriptor contains the following properties: - - `navigation` - The navigation object for the screen. You don't need to pass this to the screen manually. But it's useful if we're rendering components outside the screen that need to receive `navigation` prop as well, such as a header component. - `options` - A getter which returns the options such as `title` for the screen if they are specified. - `render` - A function which can be used to render the actual screen. Calling `descriptors[route.key].render()` will return a React element containing the screen content. It's important to use this method to render a screen, otherwise any child navigators won't be connected to the navigation tree properly. @@ -56,32 +88,14 @@ import { TabActions, } from '@react-navigation/native'; -function TabNavigator({ - id, - initialRouteName, - children, - layout, - screenListeners, - screenOptions, - screenLayout, - tabBarStyle, - contentStyle, -}) { +function TabNavigator({ tabBarStyle, contentStyle, ...rest }) { const { state, navigation, descriptors, NavigationContent } = - useNavigationBuilder(TabRouter, { - id, - initialRouteName, - children, - layout, - screenListeners, - screenOptions, - screenLayout, - }); + useNavigationBuilder(TabRouter, rest); return ( - {state.routes.map((route) => ( + {state.routes.map((route, index) => ( { @@ -158,6 +172,12 @@ export function createMyNavigator(config) { } ``` +:::note + +We can also do `export const createMyNavigator = createNavigatorFactory(MyNavigator)` directly instead of wrapping in another function. However, the wrapper function is necessary to have proper [TypeScript support](#type-checking-navigators) for the navigator. + +::: + Then it can be used like this: ```js @@ -210,7 +230,7 @@ import { useNavigationBuilder, } from '@react-navigation/native'; -// Props accepted by the view +// Additional props accepted by the view type TabNavigationConfig = { tabBarStyle: StyleProp; contentStyle: StyleProp; @@ -222,7 +242,6 @@ type TabNavigationOptions = { }; // Map of event name and the type of data (in event.data) -// // canPreventDefault: true adds the defaultPrevented property to the // emitted events. type TabNavigationEventMap = { @@ -232,28 +251,34 @@ type TabNavigationEventMap = { }; }; +// The type of the navigation object for each screen +type TabNavigationProp< + ParamList extends ParamListBase, + RouteName extends keyof ParamList = keyof ParamList, + NavigatorID extends string | undefined = undefined, +> = NavigationProp< + ParamList, + RouteName, + NavigatorID, + TabNavigationState, + TabNavigationOptions, + TabNavigationEventMap +> & + TabActionHelpers; + // The props accepted by the component is a combination of 3 things type Props = DefaultNavigatorOptions< ParamListBase, + string | undefined, TabNavigationState, TabNavigationOptions, - TabNavigationEventMap + TabNavigationEventMap, + TabNavigationProp > & TabRouterOptions & TabNavigationConfig; -function TabNavigator({ - id, - initialRouteName, - children, - layout, - screenListeners, - screenOptions, - screenLayout, - backBehavior, - tabBarStyle, - contentStyle, -}: Props) { +function TabNavigator({ tabBarStyle, contentStyle, ...rest }: Props) { const { state, navigation, descriptors, NavigationContent } = useNavigationBuilder< TabNavigationState, @@ -261,16 +286,7 @@ function TabNavigator({ TabActionHelpers, TabNavigationOptions, TabNavigationEventMap - >(TabRouter, { - id, - initialRouteName, - children, - layout, - screenListeners, - screenOptions, - screenLayout, - backBehavior, - }); + >(TabRouter, rest); return ( @@ -321,6 +337,7 @@ function TabNavigator({ ); } +// The factory function with generic types for type-inference export function createMyNavigator< const ParamList extends ParamListBase, const NavigatorID extends string | undefined = undefined, @@ -419,3 +436,14 @@ const { state, descriptors, navigation, NavigationContent } = // ... ``` + +:::note + +Customizing built-in navigators like this is an advanced use case and generally not necessary. Consider alternatives such as: + +- [`layout`](navigator.md#layout) prop on navigators to add a wrapper around the navigator +- [`UNSTABLE_router`](navigator.md#router) prop on navigators to customize the router behavior + +Also refer to the navigator's documentation to see if any existing API meets your needs. + +::: diff --git a/versioned_docs/version-7.x/custom-routers.md b/versioned_docs/version-7.x/custom-routers.md index 584e13662a0..86b66d3a1f9 100755 --- a/versioned_docs/version-7.x/custom-routers.md +++ b/versioned_docs/version-7.x/custom-routers.md @@ -150,9 +150,10 @@ The library ships with a few standard routers: ## Customizing Routers -You can reuse a router and override the router functions as per your needs, such as customizing how existing actions are handled, adding additional actions etc. +There are two main ways to customize routers: -See [custom navigators](custom-navigators.md) for details on how to override the router with a custom router in an existing navigator. +- Override an existing router with the [`UNSTABLE_router`](navigator.md#router) prop on navigators +- Customized navigators with a custom router, see [extending navigators](custom-navigators.md#extending-navigators) ### Custom Navigation Actions diff --git a/versioned_docs/version-7.x/deep-linking.md b/versioned_docs/version-7.x/deep-linking.md index 0d955f7332a..2dcac6d9f96 100755 --- a/versioned_docs/version-7.x/deep-linking.md +++ b/versioned_docs/version-7.x/deep-linking.md @@ -18,14 +18,19 @@ While you don't need to use the `linking` prop from React Navigation, and can ha Below, we'll go through required configurations so that the deep link integration works. -## Setup with Expo projects +## Setting up deep links -First, you will want to specify a URL scheme for your app. This corresponds to the string before `://` in a URL, so if your scheme is `mychat` then a link to your app would be `mychat://`. You can register for a scheme in your `app.json` by adding a string under the scheme key: + + + +### Configuring URL scheme + +First, you will want to specify a URL scheme for your app. This corresponds to the string before `://` in a URL, so if your scheme is `example` then a link to your app would be `example://`. You can register for a scheme in your `app.json` by adding a string under the scheme key: ```json { "expo": { - "scheme": "mychat" + "scheme": "example" } } ``` @@ -36,76 +41,108 @@ Next, install `expo-linking` which we'd need to get the deep link prefix: npx expo install expo-linking ``` -Then, let's configure React Navigation to use the `scheme` for parsing incoming deep links: - - - +Then you can use `Linking.createURL` to get the prefix for your app: ```js -import * as Linking from 'expo-linking'; +const linking = { + prefixes: [Linking.createURL('/'), +}; +``` -const prefix = Linking.createURL('/'); +See more details below at [Configuring React Navigation](#configuring-react-navigation). -/* content */ +
+Why use `Linking.createURL`? -function App() { - const linking = { - prefixes: [prefix], - }; +It is necessary to use `Linking.createURL` since the scheme differs between the [Expo Dev Client](https://docs.expo.dev/versions/latest/sdk/dev-client/) and standalone apps. - return ; -} -``` +The scheme specified in `app.json` only applies to standalone apps. In the Expo client app you can deep link using `exp://ADDRESS:PORT/--/` where `ADDRESS` is often `127.0.0.1` and `PORT` is often `19000` - the URL is printed when you run `expo start`. The `Linking.createURL` function abstracts it out so that you don't need to specify them manually. - - +
+ +If you are using universal links, you need to add your domain to the prefixes as well: ```js -import * as Linking from 'expo-linking'; +const linking = { + prefixes: [Linking.createURL('/'), 'https://app.example.com'], +}; +``` -const prefix = Linking.createURL('/'); +### Universal Links on iOS -function App() { - const linking = { - prefixes: [prefix], - }; +To set up iOS universal Links in your Expo app, you need to configure your [app config](https://docs.expo.dev/workflow/configuration) to include the associated domains and entitlements: - return ( - Loading...}> - {/* content */} - - ); +```json +{ + "expo": { + "ios": { + "associatedDomains": ["applinks:app.example.com"], + "entitlements": { + "com.apple.developer.associated-domains": ["applinks:app.example.com"] + } + } + } } ``` -
-
+You will also need to setup [Associated Domains](https://developer.apple.com/documentation/Xcode/supporting-associated-domains) on your server. -The reason that is necessary to use `Linking.createURL` is that the scheme will differ depending on whether you're in the client app or in a standalone app. +See [Expo's documentation on iOS Universal Links](https://docs.expo.dev/linking/ios-universal-links/) for more details. -The scheme specified in `app.json` only applies to standalone apps. In the Expo client app you can deep link using `exp://ADDRESS:PORT/--/` where `ADDRESS` is often `127.0.0.1` and `PORT` is often `19000` - the URL is printed when you run `expo start`. The `Linking.createURL` function abstracts it out so that you don't need to specify them manually. +### App Links on Android -If you are using universal links, you need to add your domain to the prefixes as well: +To set up Android App Links in your Expo app, you need to configure your [app config](https://docs.expo.dev/workflow/configuration) to include the `intentFilters`: -```js -const linking = { - prefixes: [Linking.createURL('/'), 'https://app.example.com'], -}; +```json +{ + "expo": { + "android": { + "intentFilters": [ + { + "action": "VIEW", + "autoVerify": true, + "data": [ + { + "scheme": "https", + "host": "app.example.com" + } + ], + "category": ["BROWSABLE", "DEFAULT"] + } + ] + } + } +} ``` -## Set up with bare React Native projects +You will also need to [declare the association](https://developer.android.com/training/app-links/verify-android-applinks#web-assoc) between your website and your intent filters by hosting a Digital Asset Links JSON file. + +See [Expo's documentation on Android App Links](https://docs.expo.dev/linking/android-app-links/) for more details. + +
+ ### Setup on iOS -Let's configure the native iOS app to open based on the `mychat://` URI scheme. +Let's configure the native iOS app to open based on the `example://` URI scheme. + +You'll need to add the `LinkingIOS` folder into your header search paths as described [here](https://reactnative.dev/docs/linking-libraries-ios#step-3). Then you'll need to add the following lines to your or `AppDelegate.swift` or `AppDelegate.mm` file: -You'll need to link `RCTLinking` to your project by following the steps described here. To be able to listen to incoming app links, you'll need to add the following lines to `AppDelegate.m` in your project: + + + +```swift +func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + return RCTLinkingManager.application(app, open: url, options: options) +} +``` + + + ```objc -// Add the header at the top of the file: #import -// Add this inside `@implementation AppDelegate` above `@end`: - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options @@ -114,10 +151,31 @@ You'll need to link `RCTLinking` to your project by following the steps describe } ``` + + + If your app is using [Universal Links](https://developer.apple.com/ios/universal-links/), you'll need to add the following code as well: + + + +```swift +func application( + _ application: UIApplication, + continue userActivity: NSUserActivity, + restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + return RCTLinkingManager.application( + application, + continue: userActivity, + restorationHandler: restorationHandler + ) + } +``` + + + + ```objc -// Add this inside `@implementation AppDelegate` above `@end`: - (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray> * _Nullable))restorationHandler { @@ -127,17 +185,20 @@ If your app is using [Universal Links](https://developer.apple.com/ios/universal } ``` + + + Now you need to add the scheme to your project configuration. The easiest way to do this is with the `uri-scheme` package by running the following: ```bash -npx uri-scheme add mychat --ios +npx uri-scheme add example --ios ``` If you want to do it manually, open the project (e.g. `SimpleApp/ios/SimpleApp.xcworkspace`) in Xcode. Select the project in sidebar and navigate to the info tab. Scroll down to "URL Types" and add one. In the new URL type, set the identifier and the URL scheme to your desired URL scheme. -![Xcode project info URL types with mychat added](/assets/deep-linking/xcode-linking.png) +![Xcode project info URL types with example added](/assets/deep-linking/xcode-linking.png) To make sure Universal Links work in your app, you also need to setup [Associated Domains](https://developer.apple.com/documentation/Xcode/supporting-associated-domains) on your server. @@ -157,7 +218,7 @@ If you're using React Navigation within a hybrid app - an iOS app that has both To configure the external linking in Android, you can create a new intent in the manifest. -The easiest way to do this is with the `uri-scheme` package: `npx uri-scheme add mychat --android`. +The easiest way to do this is with the `uri-scheme` package: `npx uri-scheme add example --android`. If you want to add it manually, open up `SimpleApp/android/app/src/main/AndroidManifest.xml`, and make the following adjustments: @@ -176,7 +237,7 @@ If you want to add it manually, open up `SimpleApp/android/app/src/main/AndroidM - + ``` @@ -192,7 +253,7 @@ After adding them, it should look like this: - + @@ -200,21 +261,78 @@ After adding them, it should look like this: - + - + - + ``` Then, you need to [declare the association](https://developer.android.com/training/app-links/verify-android-applinks#web-assoc) between your website and your intent filters by hosting a Digital Asset Links JSON file. + +
+ +## Configuring React Navigation + +To handle deep links, you need to configure React Navigation to use the `scheme` for parsing incoming deep links: + + + + +```js +const linking = { + prefixes: [ + 'example://', // Or `Linking.createURL('/')` for Expo apps + ], +}; + +function App() { + return ; +} +``` + + + + +```js +const linking = { + prefixes: [ + 'example://', // Or `Linking.createURL('/')` for Expo apps + ], +}; + +function App() { + return ( + Loading...}> + {/* content */} + + ); +} +``` + + + + +If you are using universal links, you need to add your domain to the prefixes as well: + +```js +const linking = { + prefixes: [ + 'example://', // Or `Linking.createURL('/')` for Expo apps + 'https://app.example.com', + ], +}; +``` + +See [configuring links](configuring-links.md) to see further details on how to configure links in React Navigation. + ## Testing deep links Before testing deep links, make sure that you rebuild and install the app in your emulator/simulator/device. @@ -231,7 +349,7 @@ If you're testing on Android, run: npx react-native run-android ``` -If you're using Expo managed workflow and testing on Expo client, you don't need to rebuild the app. However, you will need to use the correct address and port that's printed when you run `expo start` ([see above](#setup-with-expo-projects)), e.g. `exp://127.0.0.1:19000/--/`. +If you're using Expo managed workflow and testing on Expo client, you don't need to rebuild the app. However, you will need to use the correct address and port that's printed when you run `expo start`, e.g. `exp://127.0.0.1:19000/--/`. If you want to test with your custom scheme in your Expo app, you will need rebuild your standalone app by running `expo build:ios -t simulator` or `expo build:android` and install the resulting binaries. @@ -246,7 +364,7 @@ npx uri-scheme open [your deep link] --[ios|android] For example: ```bash -npx uri-scheme open "mychat://chat/jane" --ios +npx uri-scheme open "example://chat/jane" --ios ``` Or if using Expo client: @@ -266,7 +384,7 @@ xcrun simctl openurl booted [your deep link] For example: ```bash -xcrun simctl openurl booted "mychat://chat/jane" +xcrun simctl openurl booted "example://chat/jane" ``` ### Testing with `adb` on Android @@ -280,7 +398,7 @@ adb shell am start -W -a android.intent.action.VIEW -d [your deep link] [your an For example: ```bash -adb shell am start -W -a android.intent.action.VIEW -d "mychat://chat/jane" com.simpleapp +adb shell am start -W -a android.intent.action.VIEW -d "example://chat/jane" com.simpleapp ``` Or if using Expo client: @@ -289,56 +407,55 @@ Or if using Expo client: adb shell am start -W -a android.intent.action.VIEW -d "exp://127.0.0.1:19000/--/chat/jane" host.exp.exponent ``` -## Third-party integrations +## Integrating with other tools + +In addition to deep links and universal links with React Native's `Linking` API, you may also want to integrate other tools for handling incoming links, e.g. Push Notifications - so that tapping on a notification can open the app to a specific screen. -React Native's `Linking` isn't the only way to handle deep linking. You might also want to integrate other services such as [Firebase Dynamic Links](https://firebase.google.com/docs/dynamic-links), [Branch](https://help.branch.io/developers-hub/docs/react-native) etc. which provide their own API for getting notified of incoming links. +To achieve this, you'd need to override how React Navigation subscribes to incoming links. To do so, you can provide your own [`getInitialURL`](navigation-container.md#linkinggetinitialurl) and [`subscribe`](navigation-container.md#linkingsubscribe) functions. -To achieve this, you'd need to override how React Navigation subscribes to incoming links. To do so, you can provide your own [`getInitialURL`](navigation-container.md#linkinggetinitialurl) and [`subscribe`](navigation-container.md#linkingsubscribe) functions: +Here is an example integration with [expo-notifications](https://docs.expo.dev/versions/latest/sdk/notifications): -```js name="Third-party integrations" +```js name="Expo Notifications" const linking = { - prefixes: ['myapp://', 'https://myapp.com'], + prefixes: ['example://', 'https://app.example.com'], // Custom function to get the URL which was used to open the app async getInitialURL() { - // First, you would need to get the initial URL from your third-party integration - // The exact usage depend on the third-party SDK you use - // For example, to get the initial URL for Firebase Dynamic Links: - const { isAvailable } = utils().playServicesAvailability; - - if (isAvailable) { - const initialLink = await dynamicLinks().getInitialLink(); + // First, handle deep links + const url = await Linking.getInitialURL(); - if (initialLink) { - return initialLink.url; - } + if (url != null) { + return url; } - // As a fallback, you may want to do the default deep link handling - const url = await Linking.getInitialURL(); + // Handle URL from expo push notifications + const response = await Notifications.getLastNotificationResponseAsync(); - return url; + return response?.notification.request.content.data.url; }, // Custom function to subscribe to incoming links subscribe(listener) { - // Listen to incoming links from Firebase Dynamic Links - const unsubscribeFirebase = dynamicLinks().onLink(({ url }) => { - listener(url); - }); - - // Listen to incoming links from deep linking + // Listen to incoming links for deep links const linkingSubscription = Linking.addEventListener('url', ({ url }) => { listener(url); }); + // Listen to expo push notifications when user interacts with them + const pushNotificationSubscription = + Notifications.addNotificationResponseReceivedListener((response) => { + const url = response.notification.request.content.data.url; + + listener(url); + }); + return () => { // Clean up the event listeners - unsubscribeFirebase(); linkingSubscription.remove(); + pushNotificationSubscription.remove(); }; }, }; @@ -347,47 +464,44 @@ const linking = { -```js name="Third-party integrations" +```js name="Expo Notifications" const linking = { - prefixes: ['myapp://', 'https://myapp.com'], + prefixes: ['example://', 'https://app.example.com'], // Custom function to get the URL which was used to open the app async getInitialURL() { - // First, you would need to get the initial URL from your third-party integration - // The exact usage depend on the third-party SDK you use - // For example, to get the initial URL for Firebase Dynamic Links: - const { isAvailable } = utils().playServicesAvailability; - - if (isAvailable) { - const initialLink = await dynamicLinks().getInitialLink(); + // First, handle deep links + const url = await Linking.getInitialURL(); - if (initialLink) { - return initialLink.url; - } + if (url != null) { + return url; } - // As a fallback, you may want to do the default deep link handling - const url = await Linking.getInitialURL(); + // Handle URL from expo push notifications + const response = await Notifications.getLastNotificationResponseAsync(); - return url; + return response?.notification.request.content.data.url; }, // Custom function to subscribe to incoming links subscribe(listener) { - // Listen to incoming links from Firebase Dynamic Links - const unsubscribeFirebase = dynamicLinks().onLink(({ url }) => { - listener(url); - }); - - // Listen to incoming links from deep linking + // Listen to incoming links for deep links const linkingSubscription = Linking.addEventListener('url', ({ url }) => { listener(url); }); + // Listen to expo push notifications when user interacts with them + const pushNotificationSubscription = + Notifications.addNotificationResponseReceivedListener((response) => { + const url = response.notification.request.content.data.url; + + listener(url); + }); + return () => { // Clean up the event listeners - unsubscribeFirebase(); linkingSubscription.remove(); + pushNotificationSubscription.remove(); }; }, diff --git a/versioned_docs/version-7.x/drawer-actions.md b/versioned_docs/version-7.x/drawer-actions.md index 0500a6312c1..ed8e3d62c5c 100755 --- a/versioned_docs/version-7.x/drawer-actions.md +++ b/versioned_docs/version-7.x/drawer-actions.md @@ -15,28 +15,18 @@ The following actions are supported: The `openDrawer` action can be used to open the drawer pane. - - - -```js name="Drawer Actions - openDrawer" snack +```js name="Drawer Actions - openDrawer" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { Button } from '@react-navigation/elements'; import { createStaticNavigation, useNavigation, - DrawerActions, } from '@react-navigation/native'; -import { - createDrawerNavigator, - DrawerContentScrollView, - DrawerItemList, - DrawerItem, -} from '@react-navigation/drawer'; +import { createDrawerNavigator } from '@react-navigation/drawer'; function HomeScreen() { const navigation = useNavigation(); - const jumpToAction = DrawerActions.jumpTo('Profile', { user: 'Satya' }); return ( @@ -50,153 +40,28 @@ function HomeScreen() { > Open Drawer - - ); } -function ProfileScreen({ route }) { - return ( - - Profile! - {route?.params?.user ? route.params.user : 'Noone'}'s profile - - ); -} - -function CustomDrawerContent(props) { - return ( - - - props.navigation.dispatch(DrawerActions.closeDrawer())} - /> - props.navigation.dispatch(DrawerActions.toggleDrawer())} - /> - - ); -} - -const Drawer = createDrawerNavigator({ - drawerContent: (props) => , +const MyDrawer = createDrawerNavigator({ screens: { Home: HomeScreen, - Profile: ProfileScreen, }, }); -const Navigation = createStaticNavigation(Drawer); +const Navigation = createStaticNavigation(MyDrawer); export default function App() { return ; } ``` - - - -```js name="Drawer Actions - openDrawer" snack -import * as React from 'react'; -import { View, Text } from 'react-native'; -import { Button } from '@react-navigation/elements'; -import { - NavigationContainer, - DrawerActions, - useNavigation, -} from '@react-navigation/native'; -import { - createDrawerNavigator, - DrawerContentScrollView, - DrawerItemList, - DrawerItem, -} from '@react-navigation/drawer'; - -function HomeScreen() { - const navigation = useNavigation(); - const jumpToAction = DrawerActions.jumpTo('Profile', { user: 'Satya' }); - - return ( - - Home! - - - - - ); -} - -function ProfileScreen({ route }) { - return ( - - Profile! - {route?.params?.user ? route.params.user : 'Noone'}'s profile - - ); -} - -function CustomDrawerContent(props) { - return ( - - - props.navigation.dispatch(DrawerActions.closeDrawer())} - /> - props.navigation.dispatch(DrawerActions.toggleDrawer())} - /> - - ); -} - -const Drawer = createDrawerNavigator(); - -export default function App() { - return ( - - } - > - - - - - ); -} -``` - - - - ### closeDrawer The `closeDrawer` action can be used to close the drawer pane. - - - -```js name="Drawer Actions - closeDrawer" snack +```js name="Drawer Actions - closeDrawer" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { Button } from '@react-navigation/elements'; @@ -214,7 +79,6 @@ import { function HomeScreen() { const navigation = useNavigation(); - const jumpToAction = DrawerActions.jumpTo('Profile', { user: 'Satya' }); return ( @@ -222,26 +86,13 @@ function HomeScreen() { - - ); } -function ProfileScreen({ route }) { - return ( - - Profile! - {route?.params?.user ? route.params.user : 'Noone'}'s profile - - ); -} +function CustomDrawerContent(props) { + const { navigation } = props; -function CustomDrawerContent({ navigation }) { return ( @@ -253,40 +104,36 @@ function CustomDrawerContent({ navigation }) { // codeblock-focus-end }} /> - props.navigation.dispatch(DrawerActions.toggleDrawer())} - /> ); } -const Drawer = createDrawerNavigator({ +const MyDrawer = createDrawerNavigator({ drawerContent: (props) => , screens: { Home: HomeScreen, - Profile: ProfileScreen, }, }); -const Navigation = createStaticNavigation(Drawer); +const Navigation = createStaticNavigation(MyDrawer); export default function App() { return ; } ``` - - +### toggleDrawer + +The `toggleDrawer` action can be used to toggle the drawer pane. -```js name="Drawer Actions - closeDrawer" snack +```js name="Drawer Actions - toggleDrawer" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { Button } from '@react-navigation/elements'; import { - NavigationContainer, - DrawerActions, + createStaticNavigation, useNavigation, + DrawerActions, } from '@react-navigation/native'; import { createDrawerNavigator, @@ -297,249 +144,49 @@ import { function HomeScreen() { const navigation = useNavigation(); - const jumpToAction = DrawerActions.jumpTo('Profile', { user: 'Satya' }); return ( Home! - - ); } -function ProfileScreen({ route }) { - return ( - - Profile! - {route?.params?.user ? route.params.user : 'Noone'}'s profile - - ); -} +function CustomDrawerContent(props) { + const { navigation } = props; -function CustomDrawerContent({ navigation }) { return ( - { - // codeblock-focus-start - navigation.dispatch(DrawerActions.closeDrawer()); - // codeblock-focus-end - }} - /> props.navigation.dispatch(DrawerActions.toggleDrawer())} - /> - - ); -} - -const Drawer = createDrawerNavigator(); - -export default function App() { - return ( - - } - > - - - - - ); -} -``` - - - - -### toggleDrawer - -The `toggleDrawer` action can be used to open the drawer pane if closed, or close if open. - - - - -```js name="Drawer Actions - toggleDrawer" snack -import * as React from 'react'; -import { View, Text } from 'react-native'; -import { Button } from '@react-navigation/elements'; -import { - createStaticNavigation, - useNavigation, - DrawerActions, -} from '@react-navigation/native'; -import { - createDrawerNavigator, - DrawerContentScrollView, - DrawerItemList, - DrawerItem, -} from '@react-navigation/drawer'; - -function HomeScreen() { - const navigation = useNavigation(); - const jumpToAction = DrawerActions.jumpTo('Profile', { user: 'Satya' }); - - return ( - - Home! - - - - - ); -} - -function ProfileScreen({ route }) { - return ( - - Profile! - {route?.params?.user ? route.params.user : 'Noone'}'s profile - - ); -} - -function CustomDrawerContent(props) { - return ( - - - props.navigation.dispatch(DrawerActions.closeDrawer())} - /> - props.navigation.dispatch(DrawerActions.toggleDrawer())} /> ); } -const Drawer = createDrawerNavigator({ +const MyDrawer = createDrawerNavigator({ drawerContent: (props) => , screens: { Home: HomeScreen, - Profile: ProfileScreen, }, }); -const Navigation = createStaticNavigation(Drawer); +const Navigation = createStaticNavigation(MyDrawer); export default function App() { return ; } ``` - - - -```js name="Drawer Actions - toggleDrawer" snack -import * as React from 'react'; -import { View, Text } from 'react-native'; -import { Button } from '@react-navigation/elements'; -import { - NavigationContainer, - DrawerActions, - useNavigation, -} from '@react-navigation/native'; -import { - createDrawerNavigator, - DrawerContentScrollView, - DrawerItemList, - DrawerItem, -} from '@react-navigation/drawer'; - -function HomeScreen({ navigation }) { - const jumpToAction = DrawerActions.jumpTo('Profile', { user: 'Satya' }); - - return ( - - Home! - - - - - ); -} - -function ProfileScreen({ route }) { - return ( - - Profile! - {route?.params?.user ? route.params.user : 'Noone'}'s profile - - ); -} - -function CustomDrawerContent(props) { - return ( - - - props.navigation.dispatch(DrawerActions.closeDrawer())} - /> - props.navigation.dispatch(DrawerActions.toggleDrawer())} - /> - - ); -} - -const Drawer = createDrawerNavigator(); - -export default function App() { - return ( - - } - > - - - - - ); -} -``` - - - - ### jumpTo The `jumpTo` action can be used to jump to an existing route in the drawer navigator. @@ -547,10 +194,7 @@ The `jumpTo` action can be used to jump to an existing route in the drawer navig - `name` - _string_ - Name of the route to jump to. - `params` - _object_ - Screen params to pass to the destination route. - - - -```js name="Drawer Actions - jumpTo" snack +```js name="Drawer Actions - jumpTo" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { Button } from '@react-navigation/elements'; @@ -559,30 +203,20 @@ import { useNavigation, DrawerActions, } from '@react-navigation/native'; -import { - createDrawerNavigator, - DrawerContentScrollView, - DrawerItemList, - DrawerItem, -} from '@react-navigation/drawer'; +import { createDrawerNavigator } from '@react-navigation/drawer'; function HomeScreen() { const navigation = useNavigation(); - const jumpToAction = DrawerActions.jumpTo('Profile', { user: 'Satya' }); return ( Home! - - - - - - ); -} - -function ProfileScreen({ route }) { - return ( - - Profile! - {route?.params?.user ? route.params.user : 'Noone'}'s profile - - ); -} - -function CustomDrawerContent(props) { - return ( - - - props.navigation.dispatch(DrawerActions.closeDrawer())} - /> - props.navigation.dispatch(DrawerActions.toggleDrawer())} - /> - - ); -} - -const Drawer = createDrawerNavigator(); - -export default function App() { - return ( - - } - > - - - - - ); -} -``` - - - diff --git a/versioned_docs/version-7.x/drawer-layout.md b/versioned_docs/version-7.x/drawer-layout.md index 045b88280f2..0a5d8ddb7d1 100644 --- a/versioned_docs/version-7.x/drawer-layout.md +++ b/versioned_docs/version-7.x/drawer-layout.md @@ -20,56 +20,36 @@ To use this package, open a Terminal in the project root and run: npm install react-native-drawer-layout ``` -Then, you need to install and configure the libraries that are required by the drawer: +The library depends on [`react-native-gesture-handler`](https://docs.swmansion.com/react-native-gesture-handler/) for gestures and [`react-native-reanimated`](https://docs.swmansion.com/react-native-reanimated/) for animations. -1. First, install [`react-native-gesture-handler`](https://docs.swmansion.com/react-native-gesture-handler/) and [`react-native-reanimated`](https://docs.swmansion.com/react-native-reanimated/) (at least version 2 or 3). + + - If you have a Expo managed project, in your project directory, run: +If you have a Expo managed project, in your project directory, run: - ```bash - npx expo install react-native-gesture-handler react-native-reanimated - ``` - - If you have a bare React Native project, in your project directory, run: - - ```bash npm2yarn - npm install react-native-gesture-handler react-native-reanimated - ``` - -2. Configure the Reanimated Babel Plugin in your project following the [installation guide](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started). - -3. To finalize the installation of `react-native-gesture-handler`, we need to conditionally import it. To do this, create 2 files: - - ```js title="gesture-handler.native.js" - // Only import react-native-gesture-handler on native platforms - import 'react-native-gesture-handler'; - ``` - - ```js title="gesture-handler.js" - // Don't import react-native-gesture-handler on web - ``` - - Now, add the following at the **top** (make sure it's at the top and there's nothing else before it) of your entry file, such as `index.js` or `App.js`: - - ```js - import './gesture-handler'; - ``` +```bash +npx expo install react-native-gesture-handler react-native-reanimated react-native-worklets +``` - Since the drawer layout doesn't use `react-native-gesture-handler` on Web, this avoids unnecessarily increasing the bundle size. + + - :::warning +If you have a bare React Native project, in your project directory, run: - If you are building for Android or iOS, do not skip this step, or your app may crash in production even if it works fine in development. This is not applicable to other platforms. +```bash npm2yarn +npm install react-native-gesture-handler react-native-reanimated react-native-worklets +``` - ::: +After installation, configure the Reanimated Babel Plugin in your project following the [installation guide](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started). -4. If you're on a Mac and developing for iOS, you also need to install the pods (via [Cocoapods](https://cocoapods.org/)) to complete the linking. + + - ```bash - npx pod-install ios - ``` +If you're on a Mac and developing for iOS, you also need to install [pods](https://cocoapods.org/) to complete the linking. -We're done! Now you can build and run the app on your device/simulator. +```bash +npx pod-install ios +``` ## Quick start diff --git a/versioned_docs/version-7.x/drawer-navigator.md b/versioned_docs/version-7.x/drawer-navigator.md index 78fd52c3e05..594f49b0c16 100644 --- a/versioned_docs/version-7.x/drawer-navigator.md +++ b/versioned_docs/version-7.x/drawer-navigator.md @@ -20,63 +20,42 @@ To use this navigator, ensure that you have [`@react-navigation/native` and its npm install @react-navigation/drawer ``` -Then, you need to install and configure the libraries that are required by the drawer navigator: +The navigator depends on [`react-native-gesture-handler`](https://docs.swmansion.com/react-native-gesture-handler/) for gestures and [`react-native-reanimated`](https://docs.swmansion.com/react-native-reanimated/) for animations. -1. First, install [`react-native-gesture-handler`](https://docs.swmansion.com/react-native-gesture-handler/) and [`react-native-reanimated`](https://docs.swmansion.com/react-native-reanimated/) (at least version 2 or 3). + + - If you have a Expo managed project, in your project directory, run: +If you have a Expo managed project, in your project directory, run: - ```bash - npx expo install react-native-gesture-handler react-native-reanimated - ``` - - If you have a bare React Native project, in your project directory, run: - - ```bash npm2yarn - npm install react-native-gesture-handler react-native-reanimated - ``` - -2. Configure the Reanimated Babel Plugin in your project following the [installation guide](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started). - -3. To finalize the installation of `react-native-gesture-handler`, we need to conditionally import it. To do this, create 2 files: - - ```js title="gesture-handler.native.js" - // Only import react-native-gesture-handler on native platforms - import 'react-native-gesture-handler'; - ``` - - ```js title="gesture-handler.js" - // Don't import react-native-gesture-handler on web - ``` - - Now, add the following at the **top** (make sure it's at the top and there's nothing else before it) of your entry file, such as `index.js` or `App.js`: +```bash +npx expo install react-native-gesture-handler react-native-reanimated react-native-worklets +``` - ```js - import './gesture-handler'; - ``` + + - Since the drawer navigator doesn't use `react-native-gesture-handler` on Web, this avoids unnecessarily increasing the bundle size. +If you have a bare React Native project, in your project directory, run: - :::warning +```bash npm2yarn +npm install react-native-gesture-handler react-native-reanimated react-native-worklets +``` - If you are building for Android or iOS, do not skip this step, or your app may crash in production even if it works fine in development. This is not applicable to other platforms. +After installation, configure the Reanimated Babel Plugin in your project following the [installation guide](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started). - ::: + + -4. If you're on a Mac and developing for iOS, you also need to install the pods (via [Cocoapods](https://cocoapods.org/)) to complete the linking. +If you're on a Mac and developing for iOS, you also need to install [pods](https://cocoapods.org/) to complete the linking. - ```bash - npx pod-install ios - ``` +```bash +npx pod-install ios +``` ## Usage To use this navigator, import it from `@react-navigation/drawer`: - - - -```js name="Drawer Navigator" snack +```js name="Drawer Navigator" snack static2dynamic import * as React from 'react'; import { Text, View } from 'react-native'; import { @@ -128,65 +107,6 @@ export default function App() { } ``` - - - -```js name="Drawer Navigator" snack -import * as React from 'react'; -import { Text, View } from 'react-native'; -import { NavigationContainer, useNavigation } from '@react-navigation/native'; -import { Button } from '@react-navigation/elements'; -// codeblock-focus-start -import { createDrawerNavigator } from '@react-navigation/drawer'; - -const Drawer = createDrawerNavigator(); - -function MyDrawer() { - return ( - - - - - ); -} -// codeblock-focus-end - -function HomeScreen() { - const navigation = useNavigation(); - - return ( - - Home Screen - - - ); -} - -function ProfileScreen() { - const navigation = useNavigation(); - - return ( - - Profile Screen - - - ); -} - -export default function App() { - return ( - - - - ); -} -``` - - - - ## API Definition ### Props @@ -203,6 +123,7 @@ It supports the following values: - `initialRoute` - return to initial screen passed in `initialRouteName` prop, if not passed, defaults to the first screen - `order` - return to screen defined before the focused screen - `history` - return to last visited screen in the navigator; if the same screen is visited multiple times, the older entries are dropped from the history +- `fullHistory` - return to last visited screen in the navigator; doesn't drop duplicate entries unlike `history` - this behavior is useful to match how web pages work - `none` - do not handle back button #### `defaultStatus` @@ -248,9 +169,34 @@ function CustomDrawerContent(props) { To add additional items in the drawer, you can use the `DrawerItem` component: - +```js name="Custom Drawer Content" snack static2dynamic +import * as React from 'react'; +import { Text, View, Linking } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { + createDrawerNavigator, + DrawerContentScrollView, + DrawerItemList, + DrawerItem, +} from '@react-navigation/drawer'; + +function HomeScreen() { + return ( + + Home Screen + + ); +} -```js +function SettingsScreen() { + return ( + + Settings Screen + + ); +} + +// codeblock-focus-start function CustomDrawerContent(props) { return ( @@ -262,6 +208,21 @@ function CustomDrawerContent(props) { ); } +// codeblock-focus-end + +const MyDrawer = createDrawerNavigator({ + drawerContent: (props) => , + screens: { + Home: HomeScreen, + Settings: SettingsScreen, + }, +}); + +const Navigation = createStaticNavigation(MyDrawer); + +export default function App() { + return ; +} ``` The `DrawerItem` component accepts the following props: @@ -304,7 +265,7 @@ To use the custom component, we need to pass it in the `drawerContent` prop: ### Options -The following [options](screen-options.md) can be used to configure the screens in the navigator. These can be specified under `screenOptions` prop of `Drawer.navigator` or `options` prop of `Drawer.Screen`. +The following [options](screen-options.md) can be used to configure the screens in the navigator. These can be specified under `screenOptions` prop of `Drawer.Navigator` or `options` prop of `Drawer.Screen`. #### `title` @@ -581,7 +542,7 @@ It only works when there is a stack navigator (e.g. [stack navigator](stack-navi ### Header related options -You can find the list of header related options [here](elements.md#header). These [options](screen-options.md) can be specified under `screenOptions` prop of `Drawer.navigator` or `options` prop of `Drawer.Screen`. You don't have to be using `@react-navigation/elements` directly to use these options, they are just documented in that page. +You can find the list of header related options [here](elements.md#header). These [options](screen-options.md) can be specified under `screenOptions` prop of `Drawer.Navigator` or `options` prop of `Drawer.Screen`. You don't have to be using `@react-navigation/elements` directly to use these options, they are just documented in that page. In addition to those, the following options are also supported in drawer: @@ -667,18 +628,69 @@ The drawer navigator adds the following methods to the navigation object: Opens the drawer pane. - +```js name="Drawer Helper Methods" snack static2dynamic +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { Button } from '@react-navigation/elements'; +import { createDrawerNavigator } from '@react-navigation/drawer'; -```js -navigation.openDrawer(); +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function SettingsScreen() { + return ( + + Settings Screen + + ); +} + +const MyDrawer = createDrawerNavigator({ + screens: { + Home: HomeScreen, + Settings: SettingsScreen, + }, +}); + +const Navigation = createStaticNavigation(MyDrawer); + +export default function App() { + return ; +} ``` #### `closeDrawer` Closes the drawer pane. - - ```js navigation.closeDrawer(); ``` @@ -687,8 +699,6 @@ navigation.closeDrawer(); Opens the drawer pane if closed, closes the drawer pane if opened. - - ```js navigation.toggleDrawer(); ``` @@ -700,65 +710,51 @@ Navigates to an existing screen in the drawer navigator. The method accepts the - `name` - _string_ - Name of the route to jump to. - `params` - _object_ - Screen params to pass to the destination route. - - -```js -navigation.jumpTo('Profile', { owner: 'Satya' }); -``` - -### Hooks - -The drawer navigator exports the following hooks: - -#### `useDrawerProgress` - -This hook returns the progress of the drawer. It is available in the screen components rendered by the drawer navigator as well as in the [custom drawer content](#drawercontent). - -The `progress` object is a `SharedValue` that represents the animated position of the drawer (`0` is closed; `1` is open). It can be used to animate elements based on the animation of the drawer with [Reanimated](https://docs.swmansion.com/react-native-reanimated/): - - - - -```js name="Drawer animation progress" snack +```js name="Drawer Navigator - jumpTo" snack static2dynamic import * as React from 'react'; import { Text, View } from 'react-native'; -import { createStaticNavigation } from '@react-navigation/native'; -import { Button } from '@react-navigation/elements'; -// codeblock-focus-start import { - createDrawerNavigator, - useDrawerProgress, -} from '@react-navigation/drawer'; -import Animated, { useAnimatedStyle } from 'react-native-reanimated'; + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { Button } from '@react-navigation/elements'; +import { createDrawerNavigator } from '@react-navigation/drawer'; function HomeScreen() { - // highlight-next-line - const progress = useDrawerProgress(); + const navigation = useNavigation(); - const animatedStyle = useAnimatedStyle(() => ({ - transform: [{ translateX: progress.value * -100 }], - })); + return ( + + Home Screen + + + ); +} +function ProfileScreen({ route }) { return ( - + Profile Screen + {route.params?.owner && ( + Owner: {route.params.owner} + )} ); } -// codeblock-focus-end const MyDrawer = createDrawerNavigator({ screens: { Home: HomeScreen, + Profile: ProfileScreen, }, }); @@ -769,13 +765,20 @@ export default function App() { } ``` - - +### Hooks -```js name="Drawer animation progress" snack +The drawer navigator exports the following hooks: + +#### `useDrawerProgress` + +This hook returns the progress of the drawer. It is available in the screen components rendered by the drawer navigator as well as in the [custom drawer content](#drawercontent). + +The `progress` object is a `SharedValue` that represents the animated position of the drawer (`0` is closed; `1` is open). It can be used to animate elements based on the animation of the drawer with [Reanimated](https://docs.swmansion.com/react-native-reanimated/): + +```js name="Drawer animation progress" snack static2dynamic import * as React from 'react'; import { Text, View } from 'react-native'; -import { NavigationContainer } from '@react-navigation/native'; +import { createStaticNavigation } from '@react-navigation/native'; import { Button } from '@react-navigation/elements'; // codeblock-focus-start import { @@ -809,28 +812,19 @@ function HomeScreen() { } // codeblock-focus-end -const Drawer = createDrawerNavigator(); +const MyDrawer = createDrawerNavigator({ + screens: { + Home: HomeScreen, + }, +}); -function MyDrawer() { - return ( - - - - ); -} +const Navigation = createStaticNavigation(MyDrawer); export default function App() { - return ( - - - - ); + return ; } ``` - - - If you are using class components, you can use the `DrawerProgressContext` to get the progress value. :::warning diff --git a/versioned_docs/version-7.x/getting-started.md b/versioned_docs/version-7.x/getting-started.md index 1d0971acf8a..2e7d1b8dc5b 100755 --- a/versioned_docs/version-7.x/getting-started.md +++ b/versioned_docs/version-7.x/getting-started.md @@ -15,10 +15,10 @@ If you're already familiar with JavaScript, React and React Native, then you'll Here are some resources to help you out: -1. [React Native](https://reactnative.dev/docs/getting-started) -2. [Main Concepts of React](https://react.dev/learn) -3. [React Hooks](https://react.dev/reference/react) -4. [React Context](https://react.dev/learn/passing-data-deeply-with-context) (Advanced) +1. [Main Concepts of React](https://react.dev/learn) +2. [Getting started with React Native](https://reactnative.dev/docs/getting-started) +3. [React Hooks](https://react.dev/reference/react/hooks) +4. [React Context](https://react.dev/learn/passing-data-deeply-with-context) ## Minimum requirements @@ -42,17 +42,20 @@ Otherwise, you can follow the instructions below to install React Navigation int ## Installation -Install the required packages in your React Native project: +The `@react-navigation/native` package contains the core functionality of React Navigation. + +In your project directory, run: ```bash npm2yarn npm install @react-navigation/native ``` -React Navigation is made up of some core utilities and those are then used by navigators to create the navigation structure in your app. Don't worry too much about this for now, it'll become clear soon enough! To frontload the installation work, let's also install and configure dependencies used by most navigators, then we can move forward with starting to write some code. +### Installing dependencies -The libraries we will install now are [`react-native-screens`](https://github.com/software-mansion/react-native-screens) and [`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context). If you already have these libraries installed and at the latest version, you are done here! Otherwise, read on. +Let's also install and configure dependencies used by most navigators. The libraries we will install now are [`react-native-screens`](https://github.com/software-mansion/react-native-screens) and [`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context). -### Installing dependencies into an Expo managed project + + In your project directory, run: @@ -60,11 +63,10 @@ In your project directory, run: npx expo install react-native-screens react-native-safe-area-context ``` -This will install versions of these libraries that are compatible. - -You can now continue to ["Hello React Navigation"](hello-react-navigation.md) to start writing some code. +This will install versions of these libraries that are compatible with your Expo SDK version. -### Installing dependencies into a bare React Native project + + In your project directory, run: @@ -72,50 +74,61 @@ In your project directory, run: npm install react-native-screens react-native-safe-area-context ``` -:::note - -You might get warnings related to peer dependencies after installation. They are usually caused by incorrect version ranges specified in some packages. You can safely ignore most warnings as long as your app builds. - -::: - If you're on a Mac and developing for iOS, you need to install the pods (via [Cocoapods](https://cocoapods.org/)) to complete the linking. ```bash npx pod-install ios ``` -`react-native-screens` package requires one additional configuration step to properly -work on Android devices. Edit `MainActivity.kt` or `MainActivity.java` file which is located under `android/app/src/main/java//`. +#### Configuring `react-native-screens` on Android -Add the highlighted code to the body of `MainActivity` class: +[`react-native-screens`](https://github.com/software-mansion/react-native-screens) requires one additional configuration to properly work on Android. + +Edit `MainActivity.kt` or `MainActivity.java` file under `android/app/src/main/java//`, and add the highlighted code to the body of `MainActivity` class: ```kotlin +// highlight-start +import android.os.Bundle +import com.swmansion.rnscreens.fragment.restoration.RNScreensFragmentFactory +// highlight-end + +// ... + class MainActivity: ReactActivity() { // ... - // highlight-start override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(null) + // highlight-start + supportFragmentManager.fragmentFactory = RNScreensFragmentFactory() + super.onCreate(savedInstanceState) + // highlight-end } - // highlight-end // ... } ``` - - + + ```java +// highlight-start +import android.os.Bundle; +import com.swmansion.rnscreens.fragment.restoration.RNScreensFragmentFactory; +// highlight-end + +// ... + public class MainActivity extends ReactActivity { // ... - // highlight-start @Override protected void onCreate(Bundle savedInstanceState) { - super.onCreate(null); + // highlight-start + getSupportFragmentManager().setFragmentFactory(new RNScreensFragmentFactory()); + super.onCreate(savedInstanceState); + // highlight-end } - // highlight-end // ... } ``` @@ -123,19 +136,25 @@ public class MainActivity extends ReactActivity { -and make sure to add the following import statement at the top of this file below your package statement: +This change is required to avoid crashes related to View state being not persisted consistently across Activity restarts. -```java -import android.os.Bundle; -``` +#### Opting-out of predictive back on Android -This change is required to avoid crashes related to View state being not persisted consistently across Activity restarts. +React Navigation doesn't yet support Android's predictive back gesture. Disabling it is necessary for the system back gesture to work properly with React Navigation. -:::info +To opt out, in `AndroidManifest.xml`, in the `` tag (or `` tag to opt-out at activity level), set the `android:enableOnBackInvokedCallback` flag to `false`: -When you use a navigator (such as stack navigator), you'll need to follow the installation instructions of that navigator for any additional dependencies. If you're getting an error "Unable to resolve module", you need to install that module in your project. +```xml + + + +``` -::: + + ## Setting up React Navigation @@ -143,36 +162,24 @@ Once you've installed and configured the dependencies, you can move on to settin When using React Navigation, you configure [**navigators**](glossary-of-terms.md#navigator) in your app. Navigators handle the transition between screens in your app and provide UI such as header, tab bar etc. -There are 2 primary ways to configure the navigators: - -### Static configuration - -The static configuration API has reduced boilerplate and simplifies things such as TypeScript types and deep linking. If you're starting a new project or are new to React Navigation, this is the **recommended way** to set up your app. If you need more flexibility in the future, you can always mix and match with the dynamic configuration. +:::info -Continue to ["Hello React Navigation"](hello-react-navigation.md?config=static) to start writing some code with the static API. +When you use a navigator (such as stack navigator), you'll need to follow the installation instructions of that navigator for any additional dependencies. -### Dynamic configuration +::: -The dynamic configuration allows for more flexibility but requires more boilerplate and configuration (e.g. for deep links, typescript etc.). +There are 2 primary ways to configure the navigators: -To get started with dynamic configuration, first, we need to wrap your app in `NavigationContainer`. Usually, you'd do this in your entry file, such as `index.js` or `App.js`: +### Static configuration -```js -import * as React from 'react'; -// highlight-next-line -import { NavigationContainer } from '@react-navigation/native'; +The static configuration API lets you write your configuration in an object, and is defined statically, though some aspects of the configuration can still can be changed dynamically. This has reduced boilerplate and simplifies things such as TypeScript types and deep linking. -export default function App() { - return ( - {/* Rest of your app code */} - ); -} -``` +If you're starting a new project or are new to React Navigation, this is the **recommended way** to set up your app. If you need more flexibility in the future, you can always mix and match with the dynamic configuration. -:::warning +Continue to ["Hello React Navigation"](hello-react-navigation.md?config=static) to start writing some code with the static API. -In a typical React Native app, the `NavigationContainer` should be only used once in your app at the root. You shouldn't nest multiple `NavigationContainer`s unless you have a specific use case for them. +### Dynamic configuration -::: +The dynamic configuration API lets you write your configuration in React components, and can change at runtime based on state or props. This allows for more flexibility but requires significantly more boilerplate and configuration for Typescript types, deep linking etc. Continue to ["Hello React Navigation"](hello-react-navigation.md?config=dynamic) to start writing some code with the dynamic API. diff --git a/versioned_docs/version-7.x/group.md b/versioned_docs/version-7.x/group.md index eecb2620ac7..4a7b6583abb 100644 --- a/versioned_docs/version-7.x/group.md +++ b/versioned_docs/version-7.x/group.md @@ -225,7 +225,9 @@ See [Options for screens](screen-options.md) for more details and examples. ### Screen layout -A screen layout is a wrapper around each screen in the group. It makes it easier to provide things such as a common error boundary and suspense fallback for all screens in a group: +A screen layout is a wrapper around each screen in the group. It makes it easier to provide things such as an error boundary and suspense fallback for all screens in the group, or wrap each screen with additional UI. + +It takes a function that returns a React element: diff --git a/versioned_docs/version-7.x/handling-safe-area.md b/versioned_docs/version-7.x/handling-safe-area.md index 207dbbc5002..225bfa87c1d 100755 --- a/versioned_docs/version-7.x/handling-safe-area.md +++ b/versioned_docs/version-7.x/handling-safe-area.md @@ -28,7 +28,7 @@ While React Native exports a `SafeAreaView` component, this component only suppo :::warning -The `react-native-safe-area-context` library also exports a `SafeAreaView` component. While it works on Android, it also has the same issues related to jumpy behavior when animating. So we recommend always using the `useSafeAreaInsets` hook instead and avoid using the `SafeAreaView` component. +The `react-native-safe-area-context` library also exports a `SafeAreaView` component. While it works on Android, it also has the same issues with jumpy behavior on vertical animations. In addition, the `SafeAreaView` component and `useSafeAreaInsets` hook can update at different times, resulting in flickering when using them together. So we recommend always using the `useSafeAreaInsets` hook instead and avoid using the `SafeAreaView` component for consistent behavior. ::: @@ -473,6 +473,6 @@ Similarly, you could apply these paddings in `contentContainerStyle` of `FlatLis ## Summary -- Use `useSafeAreaInsets` hook from `react-native-safe-area-context` instead of `SafeAreaView` component +- Use [`useSafeAreaInsets`](https://appandflow.github.io/react-native-safe-area-context/api/use-safe-area-insets) hook from `react-native-safe-area-context` instead of [`SafeAreaView`](https://reactnative.dev/docs/safeareaview) component - Don't wrap your whole app in `SafeAreaView`, instead apply the styles to content inside your screens - Apply only specific insets using the `useSafeAreaInsets` hook for more control diff --git a/versioned_docs/version-7.x/header-buttons.md b/versioned_docs/version-7.x/header-buttons.md index 025baecf061..6de8a233717 100755 --- a/versioned_docs/version-7.x/header-buttons.md +++ b/versioned_docs/version-7.x/header-buttons.md @@ -248,7 +248,7 @@ Here we update the `headerRight` with a button with `onPress` handler that has a You can change the label behavior with `headerBackTitle` and style it with `headerBackTitleStyle` ([read more](native-stack-navigator.md#headerbacktitle)). -To customize the back button image, you can use `headerBackImageSource` ([read more](native-stack-navigator.md#headerbackimagesource)). +To customize the back button icon, you can use [`headerBackIcon`](native-stack-navigator.md#headerbackicon). @@ -297,6 +297,6 @@ Generally, this is what you want. But it's possible that in some circumstances t ## Summary -- You can set buttons in the header through the `headerLeft` and `headerRight` properties in `options`. -- The back button is fully customizable with `headerLeft`, but if you just want to change the title or image, there are other `options` for that — `headerBackTitle`, `headerBackTitleStyle`, and `headerBackImageSource`. -- You can use a callback for the options prop to access `navigation` and `route` objects. +- You can set buttons in the header through the [`headerLeft`](elements.md#headerleft) and [`headerRight`](elements.md#headerright) properties in [`options`](screen-options.md). +- The back button is fully customizable with `headerLeft`, but if you only want to change the title or image, there are other `options` for that — [`headerBackTitle`](native-stack-navigator.md#headerbacktitle), [`headerBackTitleStyle`](native-stack-navigator.md#headerbacktitlestyle), and [`headerBackIcon`](native-stack-navigator.md#headerbackicon). +- You can use a callback for the options prop to access [`navigation`](navigation-object.md) and [`route`](route-object.md) objects. diff --git a/versioned_docs/version-7.x/headers.md b/versioned_docs/version-7.x/headers.md index 9bfd242ea5e..04f18811603 100755 --- a/versioned_docs/version-7.x/headers.md +++ b/versioned_docs/version-7.x/headers.md @@ -689,6 +689,6 @@ You can read the full list of available `options` for screens inside of a native ## Summary -- You can customize the header inside of the `options` property of your screens. Read the full list of options [in the API reference](native-stack-navigator.md#options). -- The `options` property can be an object or a function. When it is a function, it is provided with an object with the `navigation` and `route` objects. -- You can also specify shared `screenOptions` in the stack navigator configuration when you initialize it. This will apply to all screens in the navigator. +- You can customize the header inside of the [`options`](screen-options.md) property of your screens. Read the full list of options [in the API reference](native-stack-navigator.md#options). +- The `options` property can be an object or a function. When it is a function, it is provided with an object with the [`navigation`](navigation-object.md) and [`route`](route-object.md) objects. +- You can also specify shared [`screenOptions`](screen-options.md#screenoptions-prop-on-the-navigator) in the stack navigator configuration when you initialize it. This will apply to all screens in the navigator. diff --git a/versioned_docs/version-7.x/hello-react-navigation.md b/versioned_docs/version-7.x/hello-react-navigation.md index 5f4bfeaf0db..74e4e98f602 100755 --- a/versioned_docs/version-7.x/hello-react-navigation.md +++ b/versioned_docs/version-7.x/hello-react-navigation.md @@ -42,7 +42,7 @@ npm install @react-navigation/elements `createNativeStackNavigator` is a function that takes a configuration object containing the screens and customization options. The screens are React Components that render the content displayed by the navigator. -`createStaticNavigation` is a function that takes the navigator defined earlier and returns a component that can be rendered in the app. It's only called once in the app. +`createStaticNavigation` is a function that takes the navigator defined earlier and returns a component that can be rendered in the app. It's only called once in the app. Usually, we'd render the returned component at the root of our app, which is usually the component exported from `App.js`, `App.tsx` etc., or used with `AppRegistry.registerComponent`, `Expo.registerRootComponent` etc. ```js name="Native Stack Example" snack // In App.js in a new project @@ -73,12 +73,18 @@ export default function App() { } ``` +:::warning + +In a typical React Native app, the `createStaticNavigation` function should be only used once in your app at the root. + +::: + `createNativeStackNavigator` is a function that returns an object containing 2 properties: `Screen` and `Navigator`. Both of them are React components used for configuring the navigator. The `Navigator` should contain `Screen` elements as its children to define the configuration for routes. -`NavigationContainer` is a component that manages our navigation tree and contains the [navigation state](navigation-state.md). This component must wrap all the navigators in the app. Usually, we'd render this component at the root of our app, which is usually the component exported from `App.js`. +`NavigationContainer` is a component that manages our navigation tree and contains the [navigation state](navigation-state.md). This component must wrap all the navigators in the app. Usually, we'd render this component at the root of our app, which is usually the component exported from `App.js`, `App.tsx` etc., or used with `AppRegistry.registerComponent`, `Expo.registerRootComponent` etc. ```js name="Native Stack Example" snack // In App.js in a new project @@ -115,6 +121,12 @@ export default function App() { } ``` +:::warning + +In a typical React Native app, the `NavigationContainer` should be only used once in your app at the root. You shouldn't nest multiple `NavigationContainer`s unless you have a specific use case for them. + +::: + @@ -525,19 +537,19 @@ If you are using TypeScript, you will need to specify the types accordingly. You - React Native doesn't have a built-in API for navigation like a web browser does. React Navigation provides this for you, along with the iOS and Android gestures and animations to transition between screens. -- `createNativeStackNavigator` is a function that takes the screens configuration and renders our content. +- [`createNativeStackNavigator`](native-stack-navigator.md) is a function that takes the screens configuration and renders our content. - Each property under screens refers to the name of the route, and the value is the component to render for the route. -- To specify what the initial route in a stack is, provide an `initialRouteName` option for the navigator. -- To specify screen-specific options, we can specify an `options` property, and for common options, we can specify `screenOptions`. +- To specify what the initial route in a stack is, provide an [`initialRouteName`](navigator.md#initial-route-name) option for the navigator. +- To specify screen-specific options, we can specify an [`options`](screen-options.md#options-prop-on-screen) property, and for common options, we can specify [`screenOptions`](screen-options.md#screenoptions-prop-on-the-navigator). - React Native doesn't have a built-in API for navigation like a web browser does. React Navigation provides this for you, along with the iOS and Android gestures and animations to transition between screens. -- `Stack.Navigator` is a component that takes route configuration as its children with additional props for configuration and renders our content. -- Each `Stack.Screen` component takes a `name` prop which refers to the name of the route and `component` prop which specifies the component to render for the route. These are the 2 required props. -- To specify what the initial route in a stack is, provide an `initialRouteName` as the prop for the navigator. -- To specify screen-specific options, we can pass an `options` prop to `Stack.Screen`, and for common options, we can pass `screenOptions` to `Stack.Navigator`. +- [`Stack.Navigator`](native-stack-navigator.md) is a component that takes route configuration as its children with additional props for configuration and renders our content. +- Each [`Stack.Screen`](screen.md) component takes a [`name`](screen.md#name) prop which refers to the name of the route and [`component`](screen.md#component) prop which specifies the component to render for the route. These are the 2 required props. +- To specify what the initial route in a stack is, provide an [`initialRouteName`](navigator.md#initial-route-name) as the prop for the navigator. +- To specify screen-specific options, we can pass an [`options`](screen-options.md#options-prop-on-screen) prop to `Stack.Screen`, and for common options, we can pass [`screenOptions`](screen-options.md#screenoptions-prop-on-the-navigator) to `Stack.Navigator`. diff --git a/versioned_docs/version-7.x/material-top-tab-navigator.md b/versioned_docs/version-7.x/material-top-tab-navigator.md index 7324ed4a8a7..2942f68776f 100755 --- a/versioned_docs/version-7.x/material-top-tab-navigator.md +++ b/versioned_docs/version-7.x/material-top-tab-navigator.md @@ -20,7 +20,10 @@ To use this navigator, ensure that you have [`@react-navigation/native` and its npm install @react-navigation/material-top-tabs ``` -Then, you need to install [`react-native-pager-view`](https://github.com/callstack/react-native-pager-view) which is required by the navigator. +The navigator depends on [`react-native-pager-view`](https://github.com/callstack/react-native-pager-view) for rendering the pages. + + + If you have a Expo managed project, in your project directory, run: @@ -28,13 +31,19 @@ If you have a Expo managed project, in your project directory, run: npx expo install react-native-pager-view ``` + + + If you have a bare React Native project, in your project directory, run: ```bash npm2yarn npm install react-native-pager-view ``` -If you're on a Mac and developing for iOS, you also need to install the pods (via [Cocoapods](https://cocoapods.org/)) to complete the linking. + + + +If you're on a Mac and developing for iOS, you also need to install [pods](https://cocoapods.org/) to complete the linking. ```bash npx pod-install ios @@ -44,10 +53,7 @@ npx pod-install ios To use this navigator, import it from `@react-navigation/material-top-tabs`: - - - -```js name="Material Top Tab Navigator" snack +```js name="Material Top Tab Navigator" snack static2dynamic import * as React from 'react'; import { Text, View } from 'react-native'; import { @@ -99,65 +105,6 @@ export default function App() { } ``` - - - -```js name="Material Top Tab Navigator" snack -import * as React from 'react'; -import { Text, View } from 'react-native'; -import { NavigationContainer, useNavigation } from '@react-navigation/native'; -import { Button } from '@react-navigation/elements'; -// codeblock-focus-start -import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; - -const Tab = createMaterialTopTabNavigator(); - -function MyTabs() { - return ( - - - - - ); -} -// codeblock-focus-end - -function HomeScreen() { - const navigation = useNavigation(); - - return ( - - Home Screen - - - ); -} - -function ProfileScreen() { - const navigation = useNavigation(); - - return ( - - Profile Screen - - - ); -} - -export default function App() { - return ( - - - - ); -} -``` - - - - ## API Definition ### Props @@ -208,12 +155,34 @@ Function that returns a React element to display as the tab bar. Example: - +```js name="Custom Tab Bar" snack static2dynamic +import * as React from 'react'; +import { Animated, View, Platform, Text } from 'react-native'; +import { + createStaticNavigation, + useLinkBuilder, + useTheme, +} from '@react-navigation/native'; +import { PlatformPressable } from '@react-navigation/elements'; +import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; -```js -import { Animated, View, TouchableOpacity, Platform } from 'react-native'; -import { useLinkBuilder, useTheme } from '@react-navigation/native'; +function HomeScreen() { + return ( + + Home Screen + + ); +} + +function SettingsScreen() { + return ( + + Settings Screen + + ); +} +// codeblock-focus-start function MyTabBar({ state, descriptors, navigation, position }) { const { colors } = useTheme(); const { buildHref } = useLinkBuilder(); @@ -253,35 +222,44 @@ function MyTabBar({ state, descriptors, navigation, position }) { const inputRange = state.routes.map((_, i) => i); const opacity = position.interpolate({ inputRange, - outputRange: inputRange.map((i) => (i === index ? 1 : 0)), + outputRange: inputRange.map((i) => (i === index ? 1 : 0.5)), }); return ( - {label} - + ); })}
); } -// ... +const MyTabs = createMaterialTopTabNavigator({ + tabBar: (props) => , + screens: { + Home: HomeScreen, + Settings: SettingsScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyTabs); - }> - {/* ... */} -; +export default function App() { + return ; +} ``` This example will render a basic tab bar with labels. @@ -310,18 +288,58 @@ The following [options](screen-options.md) can be used to configure the screens Example: - +```js name="Tab Navigator Options" snack static2dynamic +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; -```js - + Home Screen + + ); +} + +function SettingsScreen() { + return ( + + Settings Screen + + ); +} + +function ProfileScreen() { + return ( + + Profile Screen + + ); +} + +// codeblock-focus-start +const MyTabs = createMaterialTopTabNavigator({ + // highlight-start + screenOptions: { tabBarLabelStyle: { fontSize: 12 }, tabBarItemStyle: { width: 100 }, tabBarStyle: { backgroundColor: 'powderblue' }, - }} -> - {/* ... */} - + }, + // highlight-end + screens: { + Home: HomeScreen, + Settings: SettingsScreen, + Profile: ProfileScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyTabs); + +export default function App() { + return ; +} ``` #### `title` @@ -455,22 +473,72 @@ This event is fired when the user presses the tab button for the current screen To prevent the default behavior, you can call `event.preventDefault`: - +```js name="Tab Press Event" snack static2dynamic +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; + +function HomeScreen() { + const navigation = useNavigation(); -```js -React.useEffect(() => { - const unsubscribe = navigation.addListener('tabPress', (e) => { - // Prevent default behavior - e.preventDefault(); + // codeblock-focus-start + React.useEffect(() => { + const unsubscribe = navigation.addListener('tabPress', (e) => { + // Prevent default behavior + e.preventDefault(); - // Do something manually - // ... - }); + // Do something manually + // ... + }); - return unsubscribe; -}, [navigation]); + return unsubscribe; + }, [navigation]); + // codeblock-focus-end + + return ( + + Home Screen + + Tab press event is prevented + + + ); +} + +function SettingsScreen() { + return ( + + Settings Screen + + ); +} + +const MyTabs = createMaterialTopTabNavigator({ + screens: { + Home: HomeScreen, + Settings: SettingsScreen, + }, +}); + +const Navigation = createStaticNavigation(MyTabs); + +export default function App() { + return ; +} ``` +If you have a custom tab bar, make sure to emit this event. + +:::note + +By default, tabs are rendered lazily. So if you add a listener inside a screen component, it won't receive the event until the screen is focused for the first time. If you need to listen to this event before the screen is focused, you can specify the [listener in the screen config](navigation-events.md#listeners-prop-on-screen) instead. + +::: + #### `tabLongPress` This event is fired when the user presses the tab button for the current screen in the tab bar for an extended period. @@ -498,10 +566,59 @@ Navigates to an existing screen in the tab navigator. The method accepts followi - `name` - _string_ - Name of the route to jump to. - `params` - _object_ - Screen params to pass to the destination route. - +```js name="Tab Navigator - jumpTo" snack static2dynamic +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { Button } from '@react-navigation/elements'; +import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; -```js -navigation.jumpTo('Profile', { name: 'Michaś' }); +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function ProfileScreen({ route }) { + return ( + + Profile Screen + {route.params?.name && ( + Name: {route.params.name} + )} + + ); +} + +const MyTabs = createMaterialTopTabNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(MyTabs); + +export default function App() { + return ; +} ``` ### Hooks diff --git a/versioned_docs/version-7.x/modal.md b/versioned_docs/version-7.x/modal.md index b55c05b78d5..b97ab63ad42 100755 --- a/versioned_docs/version-7.x/modal.md +++ b/versioned_docs/version-7.x/modal.md @@ -15,10 +15,7 @@ A modal is like a popup — it usually has a different transition animation, ## Creating a stack with modal screens - - - -```js name="Modal" snack +```js name="Modal" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { @@ -28,7 +25,6 @@ import { import { createStackNavigator } from '@react-navigation/stack'; import { Button } from '@react-navigation/elements'; -// codeblock-focus-start function HomeScreen() { const navigation = useNavigation(); @@ -59,6 +55,7 @@ function DetailsScreen() { ); } +// codeblock-focus-start const HomeStack = createStackNavigator({ screens: { Home: { @@ -107,74 +104,6 @@ export default function App() { // codeblock-focus-end ``` - - - -```js name="Modal" snack -import * as React from 'react'; -import { View, Text } from 'react-native'; -import { NavigationContainer, useNavigation } from '@react-navigation/native'; -import { createStackNavigator } from '@react-navigation/stack'; -import { Button } from '@react-navigation/elements'; - -// codeblock-focus-start -function HomeScreen() { - const navigation = useNavigation(); - - return ( - - This is the home screen! - - - ); -} - -function ModalScreen() { - const navigation = useNavigation(); - - return ( - - This is a modal! - - - ); -} - -function DetailsScreen() { - return ( - - Details - - ); -} - -const RootStack = createStackNavigator(); - -function App() { - return ( - - - - - - - // highlight-start - - - - // highlight-end - - - ); -} -// codeblock-focus-end - -export default App; -``` - - - - @@ -185,7 +114,7 @@ Instead of specifying this option for a group, it's also possible to specify it ## Summary -- To change the type of transition on a stack navigator you can use the `presentation` option. +- To change the type of transition on a stack navigator you can use the [`presentation`](native-stack-navigator.md#presentation) option. - When `presentation` is set to `modal`, the screens behave like a modal, i.e. they have a bottom to top transition and may show part of the previous screen in the background. - Setting `presentation: 'modal'` on a group makes all the screens in the group modals, so to use non-modal transitions on other screens, we add another group with the default configuration. diff --git a/versioned_docs/version-7.x/more-resources.md b/versioned_docs/version-7.x/more-resources.md index b76b7398f9d..8e71db8cf3a 100755 --- a/versioned_docs/version-7.x/more-resources.md +++ b/versioned_docs/version-7.x/more-resources.md @@ -1,7 +1,7 @@ --- id: more-resources -title: More Resources -sidebar_label: More Resources +title: More resources +sidebar_label: More resources --- ## Talks diff --git a/versioned_docs/version-7.x/native-bottom-tab-navigator.md b/versioned_docs/version-7.x/native-bottom-tab-navigator.md new file mode 100755 index 00000000000..9fddb223207 --- /dev/null +++ b/versioned_docs/version-7.x/native-bottom-tab-navigator.md @@ -0,0 +1,445 @@ +--- +id: native-bottom-tab-navigator +title: Native Bottom Tabs Navigator +sidebar_label: Native Bottom Tabs +--- + +:::warning + +This navigator is currently experimental. The API will change in future releases. + +Currently only iOS and Android are supported. Use [`createBottomTabNavigator`](bottom-tab-navigator.md) for web support. + +::: + +Native Bottom Tabs displays screens with a tab bar to switch between them. + + + + + +The navigator uses native components on iOS and Android for better platform integration. On iOS, it uses `UITabBarController` and on Android, it uses `BottomNavigationView`. + +## Installation + +To use this navigator, ensure that you have [`@react-navigation/native` and its dependencies (follow this guide)](getting-started.md), then install [`@react-navigation/bottom-tabs`](https://github.com/react-navigation/react-navigation/tree/main/packages/bottom-tabs): + +```bash npm2yarn +npm install @react-navigation/bottom-tabs +``` + +The navigator requires React Native 0.79 or above is required. If you're using [Expo](https://expo.dev/), it requires SDK 53 or above. + +## Usage + +To use this navigator, import it from `@react-navigation/bottom-tabs/unstable`: + +```js name="Bottom Tab Navigator" static2dynamic +import { createNativeBottomTabNavigator } from '@react-navigation/bottom-tabs/unstable'; + +const MyTabs = createNativeBottomTabNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +``` + +## Notes + +- Liquid Glass effect on iOS 26+ requires your app to be built with Xcode 26 or above. +- On Android, at most 5 tabs are supported. This is a limitation of the underlying native component. + +## API Definition + +### Props + +In addition to the [common props](navigator.md#configuration) shared by all navigators, the bottom tab navigator accepts the following additional props: + +#### `backBehavior` + +This controls what happens when `goBack` is called in the navigator. This includes pressing the device's back button or back gesture on Android. + +It supports the following values: + +- `firstRoute` - return to the first screen defined in the navigator (default) +- `initialRoute` - return to initial screen passed in `initialRouteName` prop, if not passed, defaults to the first screen +- `order` - return to screen defined before the focused screen +- `history` - return to last visited screen in the navigator; if the same screen is visited multiple times, the older entries are dropped from the history +- `fullHistory` - return to last visited screen in the navigator; doesn't drop duplicate entries unlike `history` - this behavior is useful to match how web pages work +- `none` - do not handle back button + +### Options + +The following [options](screen-options.md) can be used to configure the screens in the navigator. These can be specified under `screenOptions` prop of `Tab.Navigator` or `options` prop of `Tab.Screen`. + +#### `title` + +Generic title that can be used as a fallback for `headerTitle` and `tabBarLabel`. + +#### `tabBarSystemItem` + +Uses iOS built-in tab bar items with standard iOS styling and localized titles. Supported values: + +- `bookmarks` +- `contacts` +- `downloads` +- `favorites` +- `featured` +- `history` +- `more` +- `mostRecent` +- `mostViewed` +- `recents` +- `search` +- `topRated` + +The [`tabBarIcon`](#tabbaricon) and [`tabBarLabel`](#tabbarlabel) options will override the icon and label from the system item. If you want to keep the system behavior on iOS, but need to provide icon and label for other platforms, use `Platform.OS` or `Platform.select` to conditionally set `undefined` for `tabBarIcon` and `tabBarLabel` on iOS. + +##### Search tab on iOS 26+ + +The `tabBarSystemItem` option has special styling and behavior when set to `search` on iOS 26+. + +Additionally, when the `search` tab is selected, the tab bar transforms into a search field if the screen in the tab navigator or a nested [native stack navigator](native-stack-navigator.md) has [`headerSearchBarOptions`](native-stack-navigator.md#headersearchbaroptions) configured and the native header is shown with [`headerShown: true`](native-stack-navigator.md#headershown). This won't work if a custom header is provided with the `header` option. + +Example: + +```js +tabBarSystemItem: 'search', +headerShown: true, +headerSearchBarOptions: { + placeholder: 'Search', +}, +``` + + + +#### `tabBarLabel` + +Title string of a tab displayed in the tab bar. + +Overrides the label provided by [`tabBarSystemItem`](#tabbarsystemitem) on iOS. + +If not provided, or set to `undefined`: + +- The system values are used if [`tabBarSystemItem`](#tabbarsystemitem) is set on iOS. +- Otherwise, it falls back to the [`title`](#title) or route name. + +#### `tabBarLabelVisibilityMode` + +The label visibility mode for the tab bar items. Supported values: + +- `auto` - the system decides when to show or hide labels +- `selected` - labels are shown only for the selected tab +- `labeled` - labels are always shown +- `unlabeled` - labels are never shown + +Only supported on Android. + +#### `tabBarLabelStyle` + +Style object for the tab label. Supported properties: + +- `fontFamily` +- `fontSize` +- `fontWeight` +- `fontStyle` + +Example: + +```js +tabBarLabelStyle: { + fontSize: 16, + fontFamily: 'Georgia', + fontWeight: 300, +}, +``` + +#### `tabBarIcon` + +Icon to display for the tab. It overrides the icon provided by [`tabBarSystemItem`](#tabbarsystemitem) on iOS. + +It can be an icon object or a function that given `{ focused: boolean, color: string, size: number }` returns an icon object. + +The icon can be of following types: + +- Local image - Supported on iOS and Android + + ```js + tabBarIcon: { + type: 'image', + source: require('./path/to/icon.png'), + } + ``` + + On iOS, you can additionally pass a `tinted` property to control whether the icon should be tinted with the active/inactive color: + + ```js + tabBarIcon: { + type: 'image', + source: require('./path/to/icon.png'), + tinted: false, + } + ``` + + The image is tinted by default. + +- [SF Symbols](https://developer.apple.com/sf-symbols/) name - Supported on iOS + + ```js + tabBarIcon: { + type: 'sfSymbol', + name: 'heart', + } + ``` + +- [Drawable resource](https://developer.android.com/guide/topics/resources/drawable-resource) name - Supported on Android + + ```js + tabBarIcon: { + type: 'drawableResource', + name: 'sunny', + } + ``` + +To render different icons for active and inactive states, you can use a function: + +```js +tabBarIcon: ({ focused }) => { + return { + type: 'sfSymbol', + name: focused ? 'heart' : 'heart-outline', + }; +}, +``` + +This is only supported on iOS. On Android, the icon specified for inactive state will be used for both active and inactive states. + +To provide different icons for different platforms, you can use [`Platform.select`](https://reactnative.dev/docs/platform-specific-code): + +```js +tabBarIcon: Platform.select({ + ios: { + type: 'sfSymbol', + name: 'heart', + }, + android: { + type: 'drawableResource', + name: 'heart_icon', + }, +}); +``` + +#### `tabBarBadge` + +Text to show in a badge on the tab icon. Accepts a `string` or a `number`. + +#### `tabBarBadgeStyle` + +Style for the badge on the tab icon. Supported properties: + +- `backgroundColor` +- `color` + +Example: + +```js +tabBarBadgeStyle: { + backgroundColor: 'yellow', + color: 'black', +}, +``` + +Only supported on Android. + +#### `tabBarActiveTintColor` + +Color for the icon and label in the active tab. + +#### `tabBarInactiveTintColor` + +Color for the icon and label in the inactive tabs. + +Only supported on Android. + +#### `tabBarActiveIndicatorColor` + +Background color of the active indicator. + +Only supported on Android. + +#### `tabBarActiveIndicatorEnabled` + +Whether the active indicator should be used. Defaults to `true`. + +Only supported on Android. + +#### `tabBarRippleColor` + +Color of the ripple effect when pressing a tab. + +Only supported on Android. + +#### `tabBarStyle` + +Style object for the tab bar. Supported properties: + +- `backgroundColor` - Only supported on Android and iOS 18 and below. +- `shadowColor` - Only supported on iOS 18 and below. + +On iOS 26+, the background color automatically changes based on the content behind the tab bar and can't be overridden. + +#### `tabBarBlurEffect` + +Blur effect applied to the tab bar on iOS 18 and lower when tab screen is selected. + +Supported values: + +- `none` - no blur effect +- `systemDefault` - default blur effect applied by the system +- `extraLight` +- `light` +- `dark` +- `regular` +- `prominent` +- `systemUltraThinMaterial` +- `systemThinMaterial` +- `systemMaterial` +- `systemThickMaterial` +- `systemChromeMaterial` +- `systemUltraThinMaterialLight` +- `systemThinMaterialLight` +- `systemMaterialLight` +- `systemThickMaterialLight` +- `systemChromeMaterialLight` +- `systemUltraThinMaterialDark` +- `systemThinMaterialDark` +- `systemMaterialDark` +- `systemThickMaterialDark` +- `systemChromeMaterialDark` + +Defaults to `systemDefault`. + +Only supported on iOS 18 and below. + +#### `tabBarControllerMode` + +The display mode for the tab bar. Supported values: + +- `auto` - the system sets the display mode based on the tab’s content +- `tabBar` - the system displays the content only as a tab bar +- `tabSidebar` - the tab bar is displayed as a sidebar + +Only supported on iOS 18 and above. Not supported on tvOS. + +#### `tabBarMinimizeBehavior` + +The minimize behavior for the tab bar. Supported values: + +- `auto` - resolves to the system default minimize behavior +- `never` - the tab bar does not minimize +- `onScrollDown` - the tab bar minimizes when scrolling down and + expands when scrolling back up +- `onScrollUp` - the tab bar minimizes when scrolling up and expands + when scrolling back down + +Only supported on iOS 26 and above. + + + +#### `lazy` + +Whether this screen should render only after the first time it's accessed. Defaults to `true`. Set it to `false` if you want to render the screen on the initial render of the navigator. + +#### `popToTopOnBlur` + +Boolean indicating whether any nested stack should be popped to the top of the stack when navigating away from this tab. Defaults to `false`. + +It only works when there is a stack navigator (e.g. [stack navigator](stack-navigator.md) or [native stack navigator](native-stack-navigator.md)) nested under the tab navigator. + +### Header related options + +The navigator does not show a header by default. It renders a native stack header if `headerShown` is set to `true` in the screen options explicitly, or if a custom header is provided with the `header` option. Header related options require a header to be shown. + +It supports most of the [header related options supported in `@react-navigation/native-stack`](native-stack-navigator.md#header-related-options) apart from the options related to the back button (prefixed with `headerBack`). + +### Events + +The navigator can [emit events](navigation-events.md) on certain actions. Supported events are: + +#### `tabPress` + +This event is fired when the user presses the tab button for the current screen in the tab bar. By default a tab press does several things: + +- If the tab is not focused, tab press will focus that tab +- If the tab is already focused: + - If the screen for the tab renders a scroll view, you can use [`useScrollToTop`](use-scroll-to-top.md) to scroll it to top + - If the screen for the tab renders a stack navigator, a `popToTop` action is performed on the stack + +The default behavior of the tab press is controlled natively and cannot be prevented. + +```js +React.useEffect(() => { + const unsubscribe = navigation.addListener('tabPress', (e) => { + // Do something manually + // ... + }); + + return unsubscribe; +}, [navigation]); +``` + +#### `transitionStart` + +This event is fired when the transition animation starts for the current screen. + +Example: + +```js +React.useEffect(() => { + const unsubscribe = navigation.addListener('transitionStart', (e) => { + // Do something + }); + + return unsubscribe; +}, [navigation]); +``` + +#### `transitionEnd` + +This event is fired when the transition animation ends for the current screen. + +Example: + +```js +React.useEffect(() => { + const unsubscribe = navigation.addListener('transitionEnd', (e) => { + // Do something + }); + + return unsubscribe; +}, [navigation]); +``` + +### Helpers + +The tab navigator adds the following methods to the navigation object: + +#### `jumpTo` + +Navigates to an existing screen in the tab navigator. The method accepts following arguments: + +- `name` - _string_ - Name of the route to jump to. +- `params` - _object_ - Screen params to use for the destination route. + +```js +navigation.jumpTo('Profile', { owner: 'Michaś' }); +``` diff --git a/versioned_docs/version-7.x/native-stack-navigator.md b/versioned_docs/version-7.x/native-stack-navigator.md index 552fb221d48..6c878957966 100755 --- a/versioned_docs/version-7.x/native-stack-navigator.md +++ b/versioned_docs/version-7.x/native-stack-navigator.md @@ -30,10 +30,7 @@ npm install @react-navigation/native-stack To use this navigator, import it from `@react-navigation/native-stack`: - - - -```js name="Native Stack Navigator" snack +```js name="Native Stack Navigator" snack static2dynamic import * as React from 'react'; import { Text, View } from 'react-native'; import { @@ -82,27 +79,256 @@ export default function App() { } ``` - - +:::info + +If you encounter any bugs while using `createNativeStackNavigator`, please open issues on [`react-native-screens`](https://github.com/software-mansion/react-native-screens) rather than the `react-navigation` repository! + +::: + +## API Definition + +### Props + +The native stack navigator accepts the [common props](navigator.md#configuration) shared by all navigators. + +### Options + +The following [options](screen-options.md) can be used to configure the screens in the navigator: + +#### `title` + +String that can be used as a fallback for `headerTitle`. + +#### `statusBarAnimation` + +Sets the status bar animation (similar to the `StatusBar` component). Defaults to `fade` on iOS and `none` on Android. + +Supported values: + +- `"fade"` +- `"none"` +- `"slide"` + +On Android, setting either `fade` or `slide` will set the transition of status bar color. On iOS, this option applies to appereance animation of the status bar. + +Requires setting `View controller-based status bar appearance -> YES` (or removing the config) in your `Info.plist` file. + +Only supported on Android and iOS. + +#### `statusBarHidden` + +Whether the status bar should be hidden on this screen. + +Requires setting `View controller-based status bar appearance -> YES` (or removing the config) in your `Info.plist` file. + +Only supported on Android and iOS. + +#### `statusBarStyle` + +Sets the status bar color (similar to the `StatusBar` component). + +Supported values: + +- `"auto"` (iOS only) +- `"inverted"` (iOS only) +- `"dark"` +- `"light"` + +Defaults to `auto` on iOS and `light` on Android. + +Requires setting `View controller-based status bar appearance -> YES` (or removing the config) in your `Info.plist` file. + +Only supported on Android and iOS. + +#### `statusBarBackgroundColor` + +:::warning + +This option is deprecated and will be removed in a future release (for apps targeting Android SDK 35 or above edge-to-edge mode is enabled by default +and it is expected that the edge-to-edge will be enforced in future SDKs, see [here](https://developer.android.com/about/versions/15/behavior-changes-15#ux) for more information). + +::: + +Sets the background color of the status bar (similar to the `StatusBar` component). + +Only supported on Android. + +#### `statusBarTranslucent` + +:::warning + +This option is deprecated and will be removed in a future release (for apps targeting Android SDK 35 or above edge-to-edge mode is enabled by default +and it is expected that the edge-to-edge will be enforced in future SDKs, see [here](https://developer.android.com/about/versions/15/behavior-changes-15#ux) for more information). + +::: + +Sets the translucency of the status bar (similar to the `StatusBar` component). Defaults to `false`. + +Only supported on Android. + +#### `contentStyle` + +Style object for the scene content. + +#### `animationMatchesGesture` + +Whether the gesture to dismiss should use animation provided to `animation` prop. Defaults to `false`. + +Doesn't affect the behavior of screens presented modally. + +Only supported on iOS. + +#### `fullScreenGestureEnabled` + +Whether the gesture to dismiss should work on the whole screen. Using gesture to dismiss with this option results in the same transition animation as `simple_push`. This behavior can be changed by setting `customAnimationOnGesture` prop. Achieving the default iOS animation isn't possible due to platform limitations. Defaults to `false`. + +Doesn't affect the behavior of screens presented modally. + +Only supported on iOS. + +#### `fullScreenGestureShadowEnabled` + +Whether the full screen dismiss gesture has shadow under view during transition. Defaults to `true`. + +This does not affect the behavior of transitions that don't use gestures enabled by `fullScreenGestureEnabled` prop. + +#### `gestureEnabled` + +Whether you can use gestures to dismiss this screen. Defaults to `true`. Only supported on iOS. + +#### `animationTypeForReplace` + +The type of animation to use when this screen replaces another screen. Defaults to `push`. + +Supported values: + +- `push`: the new screen will perform push animation. + + + +- `pop`: the new screen will perform pop animation. + + + +#### `animation` + +How the screen should animate when pushed or popped. + +Only supported on Android and iOS. + +Supported values: + +- `default`: use the platform default animation + + +- `fade`: fade screen in or out + + +- `fade_from_bottom`: fade the new screen from bottom + + +- `flip`: flip the screen, requires `presentation: "modal"` (iOS only) + + +- `simple_push`: default animation, but without shadow and native header transition (iOS only, uses default animation on Android) + + +- `slide_from_bottom`: slide in the new screen from bottom + + +- `slide_from_right`: slide in the new screen from right (Android only, uses default animation on iOS) + + +- `slide_from_left`: slide in the new screen from left (Android only, uses default animation on iOS) + + +- `none`: don't animate the screen + + +#### `presentation` + +How should the screen be presented. + +Only supported on Android and iOS. + +Supported values: + +- `card`: the new screen will be pushed onto a stack, which means the default animation will be slide from the side on iOS, the animation on Android will vary depending on the OS version and theme. + + +- `modal`: the new screen will be presented modally. this also allows for a nested stack to be rendered inside the screen. + + +- `transparentModal`: the new screen will be presented modally, but in addition, the previous screen will stay so that the content below can still be seen if the screen has translucent background. + + +- `containedModal`: will use "UIModalPresentationCurrentContext" modal style on iOS and will fallback to "modal" on Android. + + +- `containedTransparentModal`: will use "UIModalPresentationOverCurrentContext" modal style on iOS and will fallback to "transparentModal" on Android. + + +- `fullScreenModal`: will use "UIModalPresentationFullScreen" modal style on iOS and will fallback to "modal" on Android. A screen using this presentation style can't be dismissed by gesture. + + +- `formSheet`: will use "BottomSheetBehavior" on Android and "UIModalPresentationFormSheet" modal style on iOS. + + + +##### Using Form Sheet -```js name="Native Stack Navigator" snack +To use Form Sheet for your screen, add `presentation: 'formSheet'` to the `options`. + +```js name="Form Sheet" snack static2dynamic import * as React from 'react'; import { Text, View } from 'react-native'; -import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; import { Button } from '@react-navigation/elements'; + // codeblock-focus-start import { createNativeStackNavigator } from '@react-navigation/native-stack'; -const Stack = createNativeStackNavigator(); - -function MyStack() { - return ( - - - - - ); -} // codeblock-focus-end function HomeScreen() { @@ -119,44 +345,321 @@ function HomeScreen() { } function ProfileScreen() { + const navigation = useNavigation(); + return ( - - Profile Screen + + Profile Screen + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam accumsan + euismod enim, quis porta ligula egestas sed. Maecenas vitae consequat + odio, at dignissim lorem. Ut euismod eros ac mi ultricies, vel pharetra + tortor commodo. Interdum et malesuada fames ac ante ipsum primis in + faucibus. Nullam at urna in metus iaculis aliquam at sed quam. In + ullamcorper, ex ut facilisis commodo, urna diam posuere urna, at + condimentum mi orci ac ipsum. In hac habitasse platea dictumst. Donec + congue pharetra ipsum in finibus. Nulla blandit finibus turpis, non + vulputate elit viverra a. Curabitur in laoreet nisl. + + ); } +// codeblock-focus-start +const MyStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + }, + Profile: { + screen: ProfileScreen, + options: { + presentation: 'formSheet', + headerShown: false, + sheetAllowedDetents: 'fitToContents', + }, + }, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyStack); + export default function App() { - return ( - - - - ); + return ; } ``` - - +:::warning -:::info +Due to technical issues in platform component integration with `react-native`, `presentation: 'formSheet'` has limited support for `flex: 1`. + +On Android, using `flex: 1` on a top-level content container passed to a `formSheet` with `showAllowedDetents: 'fitToContents'` causes the sheet to not display at all, leaving only the dimmed background visible. This is because it is the sheet, not the parent who is source of the size. Setting fixed values for `sheetAllowedDetents`, e.g. `[0.4, 0.9]`, works correctly (content is aligned for the highest detent). + +On iOS, `flex: 1` with `showAllowedDetents: 'fitToContents'` works properly but setting a fixed value for `showAllowedDetents` causes the screen to not respect the `flex: 1` style - the height of the container does not fill the `formSheet` fully, but rather inherits intrinsic size of its contents. This tradeoff is _currently_ necessary to prevent ["sheet flickering" problem on iOS](https://github.com/software-mansion/react-native-screens/issues/1722). + +If you don't use `flex: 1` but the content's height is less than max screen height, the rest of the sheet might become translucent or use the default theme background color (you can see this happening on the screenshots in the descrption of [this PR](https://github.com/software-mansion/react-native-screens/pull/2462)). To match the sheet to the background of your content, set `backgroundColor` in the `contentStyle` prop of the given screen. + +On Android, there are also some problems with getting nested ScrollViews to work properly. The solution is to set `nestedScrollEnabled` on the `ScrollView`, but this does not work if the content's height is less than the `ScrollView`'s height. Please see [this PR](https://github.com/facebook/react-native/pull/44099) for details and suggested [workaround](https://github.com/facebook/react-native/pull/44099#issuecomment-2058469661). + +On Android, nested stack and `headerShown` prop are not currently supported for screens with `presentation: 'formSheet'`. + +::: + +#### `sheetAllowedDetents` + +:::note + +Works only when `presentation` is set to `formSheet`. + +::: + + + +Describes heights where a sheet can rest. + +Supported values: + +- `fitToContents` - intents to set the sheet height to the height of its contents. +- Array of fractions, e.g. `[0.25, 0.5, 0.75]`: + - Heights should be described as fraction (a number from `[0, 1]` interval) of screen height / maximum detent height. + - The array **must** be sorted in ascending order. This invariant is verified only in developement mode, where violation results in error. + - iOS accepts any number of detents, while **Android is limited to three** - any surplus values, beside first three are ignored. + +Defaults to `[1.0]`. + +Only supported on Android and iOS. + +#### `sheetElevation` + +:::note + +Works only when `presentation` is set to `formSheet`. + +::: + + + +Integer value describing elevation of the sheet, impacting shadow on the top edge of the sheet. + +Not dynamic - changing it after the component is rendered won't have an effect. + +Defaults to `24`. + +Only supported on Android. + +#### `sheetExpandsWhenScrolledToEdge` + +:::note + +Works only when `presentation` is set to `formSheet`. + +::: + + + +Whether the sheet should expand to larger detent when scrolling. + +Defaults to `true`. + +Only supported on iOS. + +:::warning + +Please note that for this interaction to work, the ScrollView must be "first-subview-chain" descendant of the Screen component. This restriction is due to platform requirements. + +::: + +#### `sheetCornerRadius` + +:::note + +Works only when `presentation` is set to `formSheet`. + +::: + + + +The corner radius that the sheet will try to render with. + +If set to non-negative value it will try to render sheet with provided radius, else it will apply system default. + +If left unset, system default is used. + +Only supported on Android and iOS. + +#### `sheetInitialDetentIndex` + +:::note + +Works only when `presentation` is set to `formSheet`. + +::: + + + +**Index** of the detent the sheet should expand to after being opened. + +If the specified index is out of bounds of `sheetAllowedDetents` array, in dev environment more errors will be thrown, in production the value will be reset to default value. + +Additionaly there is `last` value available, when set the sheet will expand initially to last (largest) detent. + +Defaults to `0` - which represents first detent in the detents array. + +Only supported on Android and iOS. + +#### `sheetGrabberVisible` + +:::note + +Works only when `presentation` is set to `formSheet`. + +::: + + + +Boolean indicating whether the sheet shows a grabber at the top. + +Defaults to `false`. + +Only supported on iOS. + +#### `sheetLargestUndimmedDetentIndex` + +:::note + +Works only when `presentation` is set to `formSheet`. + +::: + + + +The largest sheet detent for which a view underneath won't be dimmed. + +This prop can be set to an number, which indicates index of detent in `sheetAllowedDetents` array for which there won't be a dimming view beneath the sheet. + +Additionaly there are following options available: + +- `none` - there will be dimming view for all detents levels, +- `last` - there won't be a dimming view for any detent level. + +Defaults to `none`, indicating that the dimming view should be always present. + +Only supported on Android and iOS. + +#### `orientation` + +The display orientation to use for the screen. + +Supported values: + +- `default` - resolves to "all" without "portrait_down" on iOS. On Android, this lets the system decide the best orientation. +- `all`: all orientations are permitted. +- `portrait`: portrait orientations are permitted. +- `portrait_up`: right-side portrait orientation is permitted. +- `portrait_down`: upside-down portrait orientation is permitted. +- `landscape`: landscape orientations are permitted. +- `landscape_left`: landscape-left orientation is permitted. +- `landscape_right`: landscape-right orientation is permitted. + +Only supported on Android and iOS. + +#### `autoHideHomeIndicator` + +Boolean indicating whether the home indicator should prefer to stay hidden. Defaults to `false`. + +Only supported on iOS. + +#### `gestureDirection` + +Sets the direction in which you should swipe to dismiss the screen. + +Supported values: + +- `vertical` – dismiss screen vertically +- `horizontal` – dismiss screen horizontally (default) + +When using `vertical` option, options `fullScreenGestureEnabled: true`, `customAnimationOnGesture: true` and `animation: 'slide_from_bottom'` are set by default. + +Only supported on iOS. + +#### `animationDuration` + +Changes the duration (in milliseconds) of `slide_from_bottom`, `fade_from_bottom`, `fade` and `simple_push` transitions on iOS. Defaults to `350`. + +The duration of `default` and `flip` transitions isn't customizable. + +Only supported on iOS. + +#### `navigationBarColor` + +:::warning + +This option is deprecated and will be removed in a future release (for apps targeting Android SDK 35 or above edge-to-edge mode is enabled by default +and it is expected that the edge-to-edge will be enforced in future SDKs, see [here](https://developer.android.com/about/versions/15/behavior-changes-15#ux) for more information). + +::: + +Sets the navigation bar color. Defaults to initial status bar color. + +Only supported on Android. + +#### `navigationBarHidden` + +Boolean indicating whether the navigation bar should be hidden. Defaults to `false`. + +Only supported on Android. + +#### `freezeOnBlur` + +Boolean indicating whether to prevent inactive screens from re-rendering. Defaults to `false`. +Defaults to `true` when `enableFreeze()` from `react-native-screens` package is run at the top of the application. + +Only supported on iOS and Android. + +#### `scrollEdgeEffects` + +Configures the scroll edge effect for the _content ScrollView_ (the ScrollView that is present in first descendants chain of the Screen). +Depending on values set, it will blur the scrolling content below certain UI elements (e.g. header items, search bar) for the specified edge of the ScrollView. +When set in nested containers, i.e. Native Stack inside Native Bottom Tabs, or the other way around, the ScrollView will use only the innermost one's config. -If you encounter any bugs while using `createNativeStackNavigator`, please open issues on [`react-native-screens`](https://github.com/software-mansion/react-native-screens) rather than the `react-navigation` repository! +Edge effects can be configured for each edge separately. The following values are currently supported: -::: +- `automatic` - the automatic scroll edge effect style, +- `hard` - a scroll edge effect with a hard cutoff and dividing line, +- `soft` - a soft-edged scroll edge effect, +- `hidden` - no scroll edge effect. -## API Definition +Defaults to `automatic` for each edge. -### Props +:::note -The native stack navigator accepts the [common props](navigator.md#configuration) shared by all navigators. +Using both `blurEffect` and `scrollEdgeEffects` (>= iOS 26) simultaneously may cause overlapping effects. -### Options +::: -The following [options](screen-options.md) can be used to configure the screens in the navigator: +Only supported on iOS, starting from iOS 26. -#### `title` +### Header related options -String that can be used as a fallback for `headerTitle`. +The navigator supports following options to configure the header: #### `headerBackButtonMenuEnabled` @@ -174,7 +677,9 @@ This will have no effect on the first screen in the stack. #### `headerBackTitle` -Title string used by the back button on iOS. Defaults to the previous scene's title, or "Back" if there's not enough space. Use `headerBackButtonDisplayMode` to customize the behavior. +Title string used by the back button on iOS. Defaults to the previous scene's title, "Back" or arrow icon depending on the available space. See `headerBackButtonDisplayMode` to read about limitations and customize the behavior. + +Use `headerBackButtonDisplayMode: "minimal"` to hide it. Only supported on iOS. @@ -186,9 +691,17 @@ How the back button displays icon and title. Supported values: -- `default`: Displays one of the following depending on the available space: previous screen's title, generic title (e.g. 'Back') or no title (only icon). -- `generic`: Displays one of the following depending on the available space: generic title (e.g. 'Back') or no title (only icon). iOS >= 14 only, falls back to "default" on older iOS versions. -- `minimal`: Always displays only the icon without a title. +- "default" - Displays one of the following depending on the available space: previous screen's title, generic title (e.g. 'Back') or no title (only icon). +- "generic" – Displays one of the following depending on the available space: generic title (e.g. 'Back') or no title (only icon). +- "minimal" – Always displays only the icon without a title. + +The space-aware behavior is disabled when: + +- The iOS version is 13 or lower +- Custom font family or size is set (e.g. with `headerBackTitleStyle`) +- Back button menu is disabled (e.g. with `headerBackButtonMenuEnabled`) + +In such cases, a static title and icon are always displayed. Only supported on iOS. @@ -206,19 +719,30 @@ Only supported on iOS. Example: ```js - headerBackTitleStyle: { - fontSize: 14, - fontFamily: 'Georgia', - }, +headerBackTitleStyle: { + fontSize: 14, + fontFamily: 'Georgia', +}, ``` -#### `headerBackImageSource` +#### `headerBackIcon` -Image to display in the header as the icon in the back button. Defaults to back icon image for the platform +Icon to display in the header as the icon in the back button. Defaults to back icon image for the platform: - A chevron on iOS - An arrow on Android +Currently only supports image sources. + +Example: + +```js +headerBackIcon: { + type: 'image', + source: require('./path/to/icon.png'), +} +``` + #### `headerLargeStyle` Style of the header when a large title is shown. The large title is shown if `headerLargeTitle` is `true` and the edge of any scrollable content reaches the matching edge of the header. @@ -276,10 +800,6 @@ Example: }, ``` -#### `headerShown` - -Whether to show the header. The header is shown by default. Setting this to `false` hides the header. - #### `headerStyle` Style object for header. Supported properties: @@ -376,6 +896,12 @@ Supported values: - `systemChromeMaterialDark` Header blur effect systemChromeMaterialDark +:::note + +Using both `blurEffect` and `scrollEdgeEffects` (>= iOS 26) simultaneously may cause overlapping effects. + +::: + Only supported on iOS. #### `headerBackground` @@ -405,7 +931,12 @@ Tint color for the header. Changes the color of back button and title. #### `headerLeft` -Function which returns a React Element to display on the left side of the header. This replaces the back button. See `headerBackVisible` to show the back button along side left element. +Function which returns a React Element to display on the left side of the header. This replaces the back button. See `headerBackVisible` to show the back button along side left element. It receives the following properties in the arguments: + +- `tintColor` - The tint color to apply. Defaults to the [theme](themes.md)'s primary color. +- `canGoBack` - Boolean indicating whether there is a screen to go back to. +- `label` - Label text for the button. Usually the title of the previous screen. +- `href` - The `href` to use for the anchor tag on web Header right @@ -419,9 +950,43 @@ Example: headerBackTitle: 'Back', ``` +#### `unstable_headerLeftItems` + +:::warning + +This option is experimental and may change in a minor release. + +::: + +Function which returns an array of items to display as on the left side of the header. This will override `headerLeft` if both are specified. It receives the following properties in the arguments: + +- `tintColor` - The tint color to apply. Defaults to the [theme](themes.md)'s primary color. +- `canGoBack` - Boolean indicating whether there is a screen to go back to. + +Example: + +```js +unstable_headerRightItems: () => [ + { + type: 'button', + title: 'Edit', + onPress: () => { + // Do something + }, + }, +], +``` + +See [Header items](#header-items) for more information. + +Only supported on iOS. + #### `headerRight` -Function which returns a React Element to display on the right side of the header. +Function which returns a React Element to display on the right side of the header. It receives the following properties in the arguments: + +- `tintColor` - The tint color to apply. Defaults to the [theme](themes.md)'s primary color. +- `canGoBack` - Boolean indicating whether there is a screen to go back to. Header right @@ -431,6 +996,37 @@ Example: headerRight: () => ; ``` +#### `unstable_headerRightItems` + +:::warning + +This option is experimental and may change in a minor release. + +::: + +Function which returns an array of items to display as on the right side of the header. This will override `headerRight` if both are specified. It receives the following properties in the arguments: + +- `tintColor` - The tint color to apply. Defaults to the [theme](themes.md)'s primary color. +- `canGoBack` - Boolean indicating whether there is a screen to go back to. + +Example: + +```js +unstable_headerRightItems: () => [ + { + type: 'button', + title: 'Edit', + onPress: () => { + // Do something + }, + }, +], +``` + +See [Header items](#header-items) for more information. + +Only supported on iOS. + #### `headerTitle` String or a function that returns a React Element to be used by the header. Defaults to `title` or name of the screen. @@ -477,9 +1073,9 @@ Example: #### `headerSearchBarOptions` -Options to render a native search bar on iOS. Search bars are rarely static so normally it is controlled by passing an object to `headerSearchBarOptions` navigation option in the component's body. +Options to render a native search bar. Search bars are rarely static so normally it is controlled by passing an object to `headerSearchBarOptions` navigation option in the component's body. -You also need to specify `contentInsetAdjustmentBehavior="automatic"` in your `ScrollView`, `FlatList` etc. If you don't have a `ScrollView`, specify `headerTransparent: false`. +On iOS, you also need to specify `contentInsetAdjustmentBehavior="automatic"` in your `ScrollView`, `FlatList` etc. If you don't have a `ScrollView`, specify `headerTransparent: false`. Example: @@ -504,18 +1100,20 @@ Ref to manipulate the search input imperatively. It contains the following metho - `setText` - sets the search bar's content to given value - `clearText` - removes any text present in the search bar input field - `cancelSearch` - cancel the search and close the search bar +- `toggleCancelButton` - depending on passed boolean value, hides or shows cancel button (only supported on iOS) ##### `autoCapitalize` Controls whether the text is automatically auto-capitalized as it is entered by the user. Possible values: +- `systemDefault` - `none` - `words` - `sentences` - `characters` -Defaults to `sentences`. +Defaults to `systemDefault` which is the same as `sentences` on iOS and `none` on Android. ##### `autoFocus` @@ -543,7 +1141,7 @@ Only supported on iOS. The text to be used instead of default `Cancel` button text. -Only supported on iOS. +Only supported on iOS. **Deprecated** starting from iOS 26. ##### `disableBackButtonOverride` @@ -553,384 +1151,162 @@ Only supported on Android. ##### `hideNavigationBar` -Boolean indicating whether to hide the navigation bar during searching. Defaults to `true`. - -Only supported on iOS. - -##### `hideWhenScrolling` - -Boolean indicating whether to hide the search bar when scrolling. Defaults to `true`. - -Only supported on iOS. - -##### `inputType` - -The type of the input. Defaults to `"text"`. - -Supported values: - -- `"text"` -- `"phone"` -- `"number"` -- `"email"` - -Only supported on Android. - -##### `obscureBackground` - -Boolean indicating whether to obscure the underlying content with semi-transparent overlay. Defaults to `true`. - -##### `placeholder` - -Text displayed when search field is empty. - -##### `textColor` - -The color of the text in the search field. - -Header search bar options - Text color - -##### `hintTextColor` - -The color of the hint text in the search field. - -Only supported on Android. - -Header search bar options - Hint text color - -##### `headerIconColor` - -The color of the search and close icons shown in the header - -Only supported on Android. - -Header search bar options - Header icon color - -##### `shouldShowHintSearchIcon` - -Whether to show the search hint icon when search bar is focused. Defaults to `true`. - -Only supported on Android. - -##### `onBlur` - -A callback that gets called when search bar has lost focus. - -##### `onCancelButtonPress` - -A callback that gets called when the cancel button is pressed. - -##### `onChangeText` - -A callback that gets called when the text changes. It receives the current text value of the search bar. - -Example: - -```js -const [search, setSearch] = React.useState(''); - -React.useLayoutEffect(() => { - navigation.setOptions({ - headerSearchBarOptions: { - onChangeText: (event) => setSearch(event.nativeEvent.text), - }, - }); -}, [navigation]); -``` - -#### `header` - -Custom header to use instead of the default header. - -This accepts a function that returns a React Element to display as a header. The function receives an object containing the following properties as the argument: - -- `navigation` - The navigation object for the current screen. -- `route` - The route object for the current screen. -- `options` - The options for the current screen -- `back` - Options for the back button, contains an object with a `title` property to use for back button label. - -Example: - -```js -import { getHeaderTitle } from '@react-navigation/elements'; - -// .. - -header: ({ navigation, route, options, back }) => { - const title = getHeaderTitle(options, route.name); - - return ( - : undefined - } - style={options.headerStyle} - /> - ); -}; -``` - -To set a custom header for all the screens in the navigator, you can specify this option in the `screenOptions` prop of the navigator. - -Note that if you specify a custom header, the native functionality such as large title, search bar etc. won't work. - -#### `statusBarAnimation` - -Sets the status bar animation (similar to the `StatusBar` component). Defaults to `fade` on iOS and `none` on Android. - -Supported values: - -- `"fade"` -- `"none"` -- `"slide"` - -On Android, setting either `fade` or `slide` will set the transition of status bar color. On iOS, this option applies to appereance animation of the status bar. - -Requires setting `View controller-based status bar appearance -> YES` (or removing the config) in your `Info.plist` file. - -Only supported on Android and iOS. - -#### `statusBarHidden` - -Whether the status bar should be hidden on this screen. - -Requires setting `View controller-based status bar appearance -> YES` (or removing the config) in your `Info.plist` file. - -Only supported on Android and iOS. - -#### `statusBarStyle` - -Sets the status bar color (similar to the `StatusBar` component). Defaults to `auto`. - -Supported values: - -- `"auto"` -- `"inverted"` (iOS only) -- `"dark"` -- `"light"` - -Requires setting `View controller-based status bar appearance -> YES` (or removing the config) in your `Info.plist` file. - -Only supported on Android and iOS. - -#### `statusBarBackgroundColor` - -Sets the background color of the status bar (similar to the `StatusBar` component). - -Only supported on Android. - -#### `statusBarTranslucent` - -Sets the translucency of the status bar (similar to the `StatusBar` component). Defaults to `false`. - -Only supported on Android. - -#### `contentStyle` - -Style object for the scene content. - -#### `animationMatchesGesture` - -Whether the gesture to dismiss should use animation provided to `animation` prop. Defaults to `false`. - -Doesn't affect the behavior of screens presented modally. - -Only supported on iOS. - -#### `fullScreenGestureEnabled` - -Whether the gesture to dismiss should work on the whole screen. Using gesture to dismiss with this option results in the same transition animation as `simple_push`. This behavior can be changed by setting `customAnimationOnGesture` prop. Achieving the default iOS animation isn't possible due to platform limitations. Defaults to `false`. - -Doesn't affect the behavior of screens presented modally. - -Only supported on iOS. +Boolean indicating whether to hide the navigation bar during searching. -#### `fullScreenGestureShadowEnabled` +If left unset, system default is used. -Whether the full screen dismiss gesture has shadow under view during transition. Defaults to `true`. +Only supported on iOS. -This does not affect the behavior of transitions that don't use gestures enabled by `fullScreenGestureEnabled` prop. +##### `hideWhenScrolling` -#### `gestureEnabled` +Boolean indicating whether to hide the search bar when scrolling. Defaults to `true`. -Whether you can use gestures to dismiss this screen. Defaults to `true`. Only supported on iOS. +Only supported on iOS. -#### `animationTypeForReplace` +##### `inputType` -The type of animation to use when this screen replaces another screen. Defaults to `push`. +The type of the input. Defaults to `"text"`. Supported values: -- `push`: the new screen will perform push animation. +- `"text"` +- `"phone"` +- `"number"` +- `"email"` - +Only supported on Android. -- `pop`: the new screen will perform pop animation. +##### `obscureBackground` - +Boolean indicating whether to obscure the underlying content with semi-transparent overlay. -#### `animation` +If left unset, system default is used. -How the screen should animate when pushed or popped. +Only supported on iOS. -Only supported on Android and iOS. +##### `placement` -Supported values: +Controls preferred placement of the search bar. Defaults to `automatic`. -- `default`: use the platform default animation - +Supported values: -- `fade`: fade screen in or out - +- `automatic` +- `stacked` +- `inline` (**deprecated** starting from iOS 26, it is mapped to `integrated`) +- `integrated` (available starting from iOS 26, on prior versions it is mapped to `inline`) +- `integratedButton` (available starting from iOS 26, on prior versions it is mapped to `inline`) +- `integratedCentered` (available starting from iOS 26, on prior versions it is mapped to `inline`) -- `fade_from_bottom`: fade the new screen from bottom - +Only supported on iOS. -- `flip`: flip the screen, requires `presentation: "modal"` (iOS only) - +##### `allowToolbarIntegration` -- `simple_push`: default animation, but without shadow and native header transition (iOS only, uses default animation on Android) - +Boolean indicating whether the system can place the search bar among other toolbar items on iPhone. -- `slide_from_bottom`: slide in the new screen from bottom - +Set this prop to `false` to prevent the search bar from appearing in the toolbar when `placement` is `automatic`, `integrated`, `integratedButton` or `integratedCentered`. -- `slide_from_right`: slide in the new screen from right (Android only, uses default animation on iOS) - +Defaults to `true`. If `placement` is set to `stacked`, the value of this prop will be overridden with `false`. -- `slide_from_left`: slide in the new screen from left (Android only, uses default animation on iOS) - +Only supported on iOS, starting from iOS 26. -- `none`: don't animate the screen - +##### `placeholder` -#### `presentation` +Text displayed when search field is empty. -How should the screen be presented. +##### `textColor` -Only supported on Android and iOS. +The color of the text in the search field. -Supported values: +Header search bar options - Text color -- `card`: the new screen will be pushed onto a stack, which means the default animation will be slide from the side on iOS, the animation on Android will vary depending on the OS version and theme. - +##### `hintTextColor` -- `modal`: the new screen will be presented modally. this also allows for a nested stack to be rendered inside the screen. - +The color of the hint text in the search field. -- `transparentModal`: the new screen will be presented modally, but in addition, the previous screen will stay so that the content below can still be seen if the screen has translucent background. - +Only supported on Android. -- `containedModal`: will use "UIModalPresentationCurrentContext" modal style on iOS and will fallback to "modal" on Android. - +Header search bar options - Hint text color -- `containedTransparentModal`: will use "UIModalPresentationOverCurrentContext" modal style on iOS and will fallback to "transparentModal" on Android. - +##### `headerIconColor` -- `fullScreenModal`: will use "UIModalPresentationFullScreen" modal style on iOS and will fallback to "modal" on Android. A screen using this presentation style can't be dismissed by gesture. - -- `formSheet`: will use "UIModalPresentationFormSheet" modal style on iOS and will fallback to "modal" on Android. - +The color of the search and close icons shown in the header -#### `orientation` +Only supported on Android. -The display orientation to use for the screen. +Header search bar options - Header icon color -Supported values: +##### `shouldShowHintSearchIcon` -- `default` - resolves to "all" without "portrait_down" on iOS. On Android, this lets the system decide the best orientation. -- `all`: all orientations are permitted. -- `portrait`: portrait orientations are permitted. -- `portrait_up`: right-side portrait orientation is permitted. -- `portrait_down`: upside-down portrait orientation is permitted. -- `landscape`: landscape orientations are permitted. -- `landscape_left`: landscape-left orientation is permitted. -- `landscape_right`: landscape-right orientation is permitted. +Whether to show the search hint icon when search bar is focused. Defaults to `true`. -Only supported on Android and iOS. +Only supported on Android. -#### `autoHideHomeIndicator` +##### `onBlur` -Boolean indicating whether the home indicator should prefer to stay hidden. Defaults to `false`. +A callback that gets called when search bar has lost focus. -Only supported on iOS. +##### `onCancelButtonPress` -#### `gestureDirection` +A callback that gets called when the cancel button is pressed. -Sets the direction in which you should swipe to dismiss the screen. +##### `onChangeText` -Supported values: +A callback that gets called when the text changes. It receives the current text value of the search bar. -- `vertical` – dismiss screen vertically -- `horizontal` – dismiss screen horizontally (default) +Example: -When using `vertical` option, options `fullScreenGestureEnabled: true`, `customAnimationOnGesture: true` and `animation: 'slide_from_bottom'` are set by default. +```js +const [search, setSearch] = React.useState(''); -Only supported on iOS. +React.useLayoutEffect(() => { + navigation.setOptions({ + headerSearchBarOptions: { + onChangeText: (event) => setSearch(event.nativeEvent.text), + }, + }); +}, [navigation]); +``` -#### `animationDuration` +#### `headerShown` -Changes the duration (in milliseconds) of `slide_from_bottom`, `fade_from_bottom`, `fade` and `simple_push` transitions on iOS. Defaults to `350`. +Whether to show the header. The header is shown by default. Setting this to `false` hides the header. -The duration of `default` and `flip` transitions isn't customizable. +#### `header` -Only supported on iOS. +Custom header to use instead of the default header. -#### `navigationBarColor` +This accepts a function that returns a React Element to display as a header. The function receives an object containing the following properties as the argument: -Sets the navigation bar color. Defaults to initial status bar color. +- `navigation` - The navigation object for the current screen. +- `route` - The route object for the current screen. +- `options` - The options for the current screen +- `back` - Options for the back button, contains an object with a `title` property to use for back button label. -Only supported on Android. +Example: -#### `navigationBarHidden` +```js +import { getHeaderTitle } from '@react-navigation/elements'; -Boolean indicating whether the navigation bar should be hidden. Defaults to `false`. +// .. -Only supported on Android. +header: ({ navigation, route, options, back }) => { + const title = getHeaderTitle(options, route.name); -#### `freezeOnBlur` + return ( + : undefined + } + style={options.headerStyle} + /> + ); +}; +``` -Boolean indicating whether to prevent inactive screens from re-rendering. Defaults to `false`. -Defaults to `true` when `enableFreeze()` from `react-native-screens` package is run at the top of the application. +To set a custom header for all the screens in the navigator, you can specify this option in the `screenOptions` prop of the navigator. -Only supported on iOS and Android. +Note that if you specify a custom header, the native functionality such as large title, search bar etc. won't work. ### Events @@ -1016,7 +1392,8 @@ Navigates back to a previous screen in the stack by popping screens after it. Th - `name` - _string_ - Name of the route to navigate to. - `params` - _object_ - Screen params to pass to the destination route. -- `merge` - _boolean_ - Whether params should be merged with the existing route params, or replace them (when navigating to an existing screen). Defaults to `false`. +- `options` - Options object containing the following properties: + - `merge` - _boolean_ - Whether params should be merged with the existing route params, or replace them (when navigating to an existing screen). Defaults to `false`. If a matching screen is not found in the stack, this will pop the current screen and add a new screen with the specified name and params. @@ -1061,3 +1438,234 @@ const MyView = () => { ); }; ``` + +## Header items + +The [`unstable_headerLeftItems`](#unstable_headerleftitems) and [`unstable_headerRightItems`](#unstable_headerrightitems) options allow you to add header items to the left and right side of the header respectively. This items can show native buttons, menus or custom React elements. + +On iOS 26+, the header right items can also be collapsed into an overflow menu by the system when there is not enough space to show all items. Note that custom elements (with `type: 'custom'`) won't be collapsed into the overflow menu. + +Header items + +Header item with menu + +There are 3 categories of items that can be displayed in the header: + +### Action + +A regular button that performs an action when pressed, or shows a menu. + +Common properties: + +- `type`: Must be `button` or `menu`. +- `label`: Label of the item. The label is not shown if `icon` is specified. However, it is used by screen readers, or if the header items get collapsed due to lack of space. +- `labelStyle`: Style object for the label. Supported properties: + - `fontFamily` + - `fontSize` + - `fontWeight` + - `color` +- `icon`: Optional icon to show instead of the label. + + The icon can be an image: + + ```js + { + type: 'image', + source: require('./path/to/image.png'), + } + ``` + + Or a [SF Symbols](https://developer.apple.com/sf-symbols/) name: + + ```js + { + type: 'sfSymbol', + name: 'heart', + } + ``` + +- `variant`: Visual variant of the button. Supported values: + - `plain` (default) + - `done` + - `prominent` +- `tintColor`: Tint color to apply to the item. +- `disabled`: Whether the item is disabled. +- `width`: Width of the item. +- `hidesSharedBackground` (iOS 26+): Whether the background this item may share with other items should be hidden. Setting this to `true` hides the liquid glass background. +- `sharesBackground` (iOS 26+): Whether this item can share a background with other items. +- `identifier` (iOS 26+) - An identifier used to match items across transitions. +- `badge` (iOS 26+): An optional badge to display alongside the item. Supported properties: + - `value`: The value to display in the badge. It can be a string or a number. + - `style`: Style object for the badge. Supported properties: + - `fontFamily` + - `fontSize` + - `fontWeight` + - `color` +- `accessibilityLabel`: Accessibility label for the item. +- `accessibilityHint`: Accessibility hint for the item. + +Supported properties when `type` is `button`: + +- `onPress`: Function to call when the button is pressed. +- `selected`: Whether the button is in a selected state. + +Example: + +```js +unstable_headerRightItems: () => [ + { + type: 'button', + label: 'Edit', + icon: { + type: 'sfSymbol', + name: 'pencil', + }, + onPress: () => { + // Do something + }, + }, +], +``` + +Supported properties when `type` is `menu`: + +- `changesSelectionAsPrimaryAction`: Whether the menu is a selection menu. Tapping an item in a selection menu will add a checkmark to the selected item. Defaults to `false`. +- `menu`: An object containing the menu items. It contains the following properties: + - `title`: Optional title to show on top of the menu. + - `items`: An array of menu items. A menu item can be either an `action` or a `submenu`. + - `action`: An object with the following properties: + - `type`: Must be `action`. + - `label`: Label of the menu item. + - `icon`: Optional icon to show alongside the label. The icon can be a [SF Symbols](https://developer.apple.com/sf-symbols/) name: + + ```js + { + type: 'sfSymbol', + name: 'trash', + } + ``` + + - `onPress`: Function to call when the menu item is pressed. + - `state`: Optional state of the menu item. Supported values: + - `on` + - `off` + - `mixed` + - `disabled`: Whether the menu item is disabled. + - `destructive`: Whether the menu item is styled as destructive. + - `hidden`: Whether the menu item is hidden. + - `keepsMenuPresented`: Whether to keep the menu open after selecting this item. Defaults to `false`. + - `discoverabilityLabel`: An elaborated title that explains the purpose of the action. + + - `submenu`: An object with the following properties: + - `type`: Must be `submenu`. + - `label`: Label of the submenu item. + - `icon`: Optional icon to show alongside the label. The icon can be a [SF Symbols](https://developer.apple.com/sf-symbols/) name: + + ```js + { + type: 'sfSymbol', + name: 'pencil', + } + ``` + + - `items`: An array of menu items (can be either `action` or `submenu`). + +Example: + +```js +unstable_headerRightItems: () => [ + { + type: 'menu', + label: 'Options', + icon: { + type: 'sfSymbol', + name: 'ellipsis', + }, + menu: { + title: 'Options', + items: [ + { + type: 'action', + label: 'Edit', + icon: { + type: 'sfSymbol', + name: 'pencil', + }, + onPress: () => { + // Do something + }, + }, + { + type: 'submenu', + label: 'More', + items: [ + { + type: 'action', + label: 'Delete', + destructive: true, + onPress: () => { + // Do something + }, + }, + ], + }, + ], + }, + }, +], +``` + +### Spacing + +An item to add spacing between other items in the header. + +Supported properties: + +- `type`: Must be `spacing`. +- `spacing`: Amount of spacing to add. + +```js +unstable_headerRightItems: () => [ + { + type: 'button', + label: 'Edit', + onPress: () => { + // Do something + }, + }, + { + type: 'spacing', + spacing: 10, + }, + { + type: 'button', + label: 'Delete', + onPress: () => { + // Do something + }, + }, +], +``` + +### Custom + +A custom item to display any React Element in the header. + +Supported properties: + +- `type`: Must be `custom`. +- `element`: A React Element to display as the item. +- `hidesSharedBackground`: Whether the background this item may share with other items in the bar should be hidden. Setting this to `true` hides the liquid glass background on iOS 26+. + +Example: + +```js +unstable_headerRightItems: () => [ + { + type: 'custom', + element: , + }, +], +``` + +The advantage of using this over [`headerLeft`](#headerleft) or [`headerRight`](#headerright) options is that it supports features like shared background on iOS 26+. diff --git a/versioned_docs/version-7.x/navigating-without-navigation-prop.md b/versioned_docs/version-7.x/navigating-without-navigation-prop.md index e01cb26729f..0200c83209a 100755 --- a/versioned_docs/version-7.x/navigating-without-navigation-prop.md +++ b/versioned_docs/version-7.x/navigating-without-navigation-prop.md @@ -13,7 +13,7 @@ Sometimes you need to trigger a navigation action from places where you do not h - You need to navigate from inside a component without needing to pass the `navigation` prop down, see [`useNavigation`](use-navigation.md) instead. The `ref` behaves differently, and many helper methods specific to screens aren't available. - You need to handle deep links or universal links. Doing this with the `ref` has many edge cases. See [configuring links](configuring-links.md) for more information on handling deep linking. -- You need to integrate with third party libraries, such as push notifications, branch etc. See [third party integrations for deep linking](deep-linking.md#third-party-integrations) instead. +- You need to integrate with third party libraries, such as push notifications, branch etc. See [Integrating with other tools](deep-linking.md#integrating-with-other-tools) instead. **Do** use the `ref` if: diff --git a/versioned_docs/version-7.x/navigating.md b/versioned_docs/version-7.x/navigating.md index 8babb1a7e98..73057ef5193 100755 --- a/versioned_docs/version-7.x/navigating.md +++ b/versioned_docs/version-7.x/navigating.md @@ -358,8 +358,8 @@ export default function App() { ## Summary -- `navigation.navigate('RouteName')` pushes a new route to the native stack navigator if you're not already on that route. -- We can call `navigation.push('RouteName')` as many times as we like and it will continue pushing routes. -- The header bar will automatically show a back button, but you can programmatically go back by calling `navigation.goBack()`. On Android, the hardware back button just works as expected. -- You can go back to an existing screen in the stack with `navigation.popTo('RouteName')`, and you can go back to the first screen in the stack with `navigation.popToTop()`. -- The `navigation` object is available to all screen components with the [`useNavigation`](use-navigation.md) hook. +- [`navigation.navigate('RouteName')`](navigation-object.md#navigate) pushes a new route to the native stack navigator if you're not already on that route. +- We can call [`navigation.push('RouteName')`](stack-actions.md#push) as many times as we like and it will continue pushing routes. +- The header bar will automatically show a back button, but you can programmatically go back by calling [`navigation.goBack()`](navigation-object.md#goback). On Android, the hardware back button just works as expected. +- You can go back to an existing screen in the stack with [`navigation.popTo('RouteName')`](stack-actions.md#popto), and you can go back to the first screen in the stack with [`navigation.popToTop()`](stack-actions.md#poptotop). +- The [`navigation`](navigation-object.md) object is available to all screen components with the [`useNavigation`](use-navigation.md) hook. diff --git a/versioned_docs/version-7.x/navigation-actions.md b/versioned_docs/version-7.x/navigation-actions.md index 3a4f80000c8..a4480e69e77 100755 --- a/versioned_docs/version-7.x/navigation-actions.md +++ b/versioned_docs/version-7.x/navigation-actions.md @@ -28,12 +28,11 @@ The `navigate` action allows to navigate to a specific route. It takes the follo - `name` - _string_ - A destination name of the screen in the current or a parent navigator. - `params` - _object_ - Params to use for the destination route. -- `merge` - _boolean_ - Whether params should be merged with the existing route params, or replace them (when navigating to an existing screen). Defaults to `false`. - - - +- `options` - Options object containing the following properties: + - `merge` - _boolean_ - Whether params should be merged with the existing route params, or replace them (when navigating to an existing screen). Defaults to `false`. + - `pop` - _boolean_ - Whether screens should be popped to navigate to a matching screen in the stack. Defaults to `false`. -```js name="Common actions navigate" snack +```js name="Common actions navigate" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { Button } from '@react-navigation/elements'; @@ -68,9 +67,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -88,201 +84,29 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - - ); } -const Stack = createStackNavigator({ +const RootStack = createStackNavigator({ screens: { Home: HomeScreen, Profile: ProfileScreen, }, }); -const Navigation = createStaticNavigation(Stack); +const Navigation = createStaticNavigation(RootStack); export default function App() { return ; } ``` - - - -```js name="Common actions navigate" snack -import * as React from 'react'; -import { Button } from '@react-navigation/elements'; -import { View, Text } from 'react-native'; -import { - NavigationContainer, - CommonActions, - useNavigation, -} from '@react-navigation/native'; -import { createStackNavigator } from '@react-navigation/stack'; - -function HomeScreen() { - const navigation = useNavigation(); - - return ( - - Home! - - - - ); -} - -function ProfileScreen({ route }) { - const navigation = useNavigation(); - - return ( - - Profile! - {route.params.user}'s profile - - - - - - ); -} - -const Stack = createStackNavigator(); - -export default function App() { - return ( - - - - - - - ); -} -``` - - - - In a stack navigator ([stack](stack-navigator.md) or [native stack](native-stack-navigator.md)), calling `navigate` with a screen name will have the following behavior: - If you're already on a screen with the same name, it will update its params and not push a new screen. - If you're on a different screen, it will push the new screen onto the stack. -- If the [`getId`](screen.md#id) prop is specified, and another screen in the stack has the same ID, it will navigate to that screen and update its params instead. +- If the [`getId`](screen.md#id) prop is specified, and another screen in the stack has the same ID, it will bring that screen to focus and update its params instead.
Advanced usage @@ -292,6 +116,7 @@ The `navigate` action can also accepts an object as the argument with the follow - `name` - _string_ - A destination name of the screen in the current or a parent navigator - `params` - _object_ - Params to use for the destination route. - `merge` - _boolean_ - Whether params should be merged with the existing route params, or replace them (when navigating to an existing screen). Defaults to `false`. +- `pop` - _boolean_ - Whether screens should be popped to navigate to a matching screen in the stack. Defaults to `false`. - `path` - _string_ - The path (from deep link or universal link) to associate with the screen. This is primarily used internally to associate a path with a screen when it's from a URL. @@ -304,10 +129,7 @@ The `reset` action allows to reset the [navigation state](navigation-state.md) t - `state` - _object_ - The new [navigation state](navigation-state.md) object to use. - - - -```js name="Common actions reset" snack +```js name="Common actions reset" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { Button } from '@react-navigation/elements'; @@ -340,9 +162,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -360,13 +179,6 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - ); } -const Stack = createStackNavigator({ +const RootStack = createStackNavigator({ screens: { Home: HomeScreen, Profile: ProfileScreen, }, }); -const Navigation = createStaticNavigation(Stack); +const Navigation = createStaticNavigation(RootStack); export default function App() { return ; } ``` - - +The state object specified in `reset` replaces the existing [navigation state](navigation-state.md) with the new one. This means that if you provide new route objects without a key, or route objects with a different key, it'll remove the existing screens for those routes and add new screens. + +If you want to preserve the existing screens but only want to modify the state, you can pass a function to `dispatch` where you can get the existing state. Then you can change it as you like (make sure not to mutate the existing state, but create new state object for your changes). and return a `reset` action with the desired state: + +```js +import { CommonActions } from '@react-navigation/native'; + +navigation.dispatch((state) => { + // Remove all the screens after `Profile` + const index = state.routes.findIndex((r) => r.name === 'Profile'); + const routes = state.routes.slice(0, index + 1); + + return CommonActions.reset({ + ...state, + routes, + index: routes.length - 1, + }); +}); +``` + +:::warning + +Consider the navigator's state object to be internal and subject to change in a minor release. Avoid using properties from the [navigation state](navigation-state.md) state object except `index` and `routes`, unless you really need it. If there is some functionality you cannot achieve without relying on the structure of the state object, please open an issue. + +::: + +#### Rewriting the history with `reset` + +Since the `reset` action can update the navigation state with a new state object, it can be used to rewrite the navigation history. However, rewriting the history to alter the back stack is not recommended in most cases: + +- It can lead to a confusing user experience, as users expect to be able to go back to the screen they were on before. +- When supporting the Web platform, the browser's history will still reflect the old navigation state, so users will see the old screen if they use the browser's back button - resulting in 2 different experiences depending on which back button the user presses. -```js name="Common actions reset" snack +So if you have such a use case, consider a different approach - e.g. updating the history once the user navigates back to the screen that has changed. + +### goBack + +The `goBack` action creator allows to go back to the previous route in history. It doesn't take any arguments. + +```js name="Common actions goBack" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { Button } from '@react-navigation/elements'; import { - NavigationContainer, - CommonActions, + createStaticNavigation, useNavigation, + CommonActions, } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; @@ -462,16 +289,12 @@ function HomeScreen() { > Navigate to Profile - ); } function ProfileScreen({ route }) { const navigation = useNavigation(); - return ( Profile! {route.params.user}'s profile - - - - - ); -} - -const Stack = createStackNavigator(); - -export default function App() { - return ( - - - - - - - ); -} -``` - - - - -The state object specified in `reset` replaces the existing [navigation state](navigation-state.md) with the new one. This means that if you provide new route objects without a key, or route objects with a different key, it'll remove the existing screens for those routes and add new screens. - -If you want to preserve the existing screens but only want to modify the state, you can pass a function to `dispatch` where you can get the existing state. Then you can change it as you like (make sure not to mutate the existing state, but create new state object for your changes). and return a `reset` action with the desired state: - -```js -import { CommonActions } from '@react-navigation/native'; - -navigation.dispatch((state) => { - // Remove all the screens after `Profile` - const index = state.routes.findIndex((r) => r.name === 'Profile'); - const routes = state.routes.slice(0, index + 1); - - return CommonActions.reset({ - ...state, - routes, - index: routes.length - 1, - }); -}); -``` - -:::warning - -Consider the navigator's state object to be internal and subject to change in a minor release. Avoid using properties from the [navigation state](navigation-state.md) state object except `index` and `routes`, unless you really need it. If there is some functionality you cannot achieve without relying on the structure of the state object, please open an issue. - -::: - -#### Rewriting the history with `reset` - -Since the `reset` action can update the navigation state with a new state object, it can be used to rewrite the navigation history. However, rewriting the history to alter the back stack is not recommended in most cases: - -- It can lead to a confusing user experience, as users expect to be able to go back to the screen they were on before. -- When supporting the Web platform, the browser's history will still reflect the old navigation state, so users will see the old screen if they use the browser's back button - resulting in 2 different experiences depending on which back button the user presses. - -So if you have such a use case, consider a different approach - e.g. updating the history once the user navigates back to the screen that has changed. - -### goBack - -The `goBack` action creator allows to go back to the previous route in history. It doesn't take any arguments. - - - - -```js name="Common actions goBack" snack -import * as React from 'react'; -import { View, Text } from 'react-native'; -import { Button } from '@react-navigation/elements'; -import { - createStaticNavigation, - useNavigation, - CommonActions, -} from '@react-navigation/native'; -import { createStackNavigator } from '@react-navigation/stack'; - -function HomeScreen() { - const navigation = useNavigation(); - - return ( - - Home! - - - - ); -} - -function ProfileScreen({ route }) { - const navigation = useNavigation(); - return ( - - Profile! - {route.params.user}'s profile - - - - - - ); -} - -const Stack = createStackNavigator({ - screens: { - Home: HomeScreen, - Profile: ProfileScreen, - }, -}); - -const Navigation = createStaticNavigation(Stack); - -export default function App() { - return ; -} -``` - - - - -```js name="Common actions goBack" snack -import * as React from 'react'; -import { View, Text } from 'react-native'; -import { Button } from '@react-navigation/elements'; -import { - NavigationContainer, - CommonActions, - useNavigation, -} from '@react-navigation/native'; -import { createStackNavigator } from '@react-navigation/stack'; - -function HomeScreen() { - const navigation = useNavigation(); - - return ( - - Home! - - - - ); -} - -function ProfileScreen({ route }) { - const navigation = useNavigation(); - - return ( - - Profile! - {route.params.user}'s profile - - - - ); } -const Stack = createStackNavigator(); - -export default function App() { - return ( - - - - - - - ); -} -``` - - - - -If you want to go back from a particular route, you can add a `source` property referring to the route key and a `target` property referring to the `key` of the navigator which contains the route: - - - - -```js name="Common actions goBack" snack -import * as React from 'react'; -import { View, Text } from 'react-native'; -import { Button } from '@react-navigation/elements'; -import { - createStaticNavigation, - useNavigation, - CommonActions, -} from '@react-navigation/native'; -import { createStackNavigator } from '@react-navigation/stack'; - -function HomeScreen() { - const navigation = useNavigation(); - - return ( - - Home! - - - - ); -} - -function ProfileScreen({ route }) { - const navigation = useNavigation(); - return ( - - Profile! - {route.params.user}'s profile - - - - - - ); -} - -const Stack = createStackNavigator({ +const RootStack = createStackNavigator({ screens: { Home: HomeScreen, Profile: ProfileScreen, }, -}); - -const Navigation = createStaticNavigation(Stack); - -export default function App() { - return ; -} -``` - - - - -```js name="Common actions goBack" snack -import * as React from 'react'; -import { View, Text } from 'react-native'; -import { Button } from '@react-navigation/elements'; -import { - NavigationContainer, - CommonActions, - useNavigation, -} from '@react-navigation/native'; -import { createStackNavigator } from '@react-navigation/stack'; - -function HomeScreen() { - const navigation = useNavigation(); - - return ( - - Home! - - - - ); -} - -function ProfileScreen({ route }) { - const navigation = useNavigation(); - - return ( - - Profile! - {route.params.user}'s profile - - - - - - ); -} +}); -const Stack = createStackNavigator(); +const Navigation = createStaticNavigation(RootStack); export default function App() { - return ( - - - - - - - ); + return ; } ``` - - - -By default, the key of the route that dispatched the action is passed as the `source` property and the `target` property is `undefined`. - -### preload - -The `preload` action allows preloading a screen in the background before navigating to it. It takes the following arguments: - -- `name` - _string_ - A destination name of the screen in the current or a parent navigator. -- `params` - _object_ - Params to use for the destination route. - - - +If you want to go back from a particular route, you can add a `source` property referring to the route key and a `target` property referring to the `key` of the navigator which contains the route: -```js name="Common actions preload" snack +```js name="Common actions goBack" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; import { createStaticNavigation, useNavigation, CommonActions, } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; -import { Button } from '@react-navigation/elements'; function HomeScreen() { const navigation = useNavigation(); @@ -1141,17 +359,6 @@ function HomeScreen() { }} > Home! - ); } -const Stack = createStackNavigator({ +const RootStack = createStackNavigator({ screens: { Home: HomeScreen, Profile: ProfileScreen, }, }); -const Navigation = createStaticNavigation(Stack); +const Navigation = createStaticNavigation(RootStack); export default function App() { return ; } ``` - - +By default, the key of the route that dispatched the action is passed as the `source` property and the `target` property is `undefined`. + +### preload + +The `preload` action allows preloading a screen in the background before navigating to it. It takes the following arguments: + +- `name` - _string_ - A destination name of the screen in the current or a parent navigator. +- `params` - _object_ - Params to use for the destination route. -```js name="Common actions preload" snack +```js name="Common actions preload" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { - NavigationContainer, - CommonActions, + createStaticNavigation, useNavigation, + CommonActions, } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import { Button } from '@react-navigation/elements'; @@ -1292,23 +504,20 @@ function ProfileScreen({ route }) { ); } -const Stack = createStackNavigator(); +const RootStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); export default function App() { - return ( - - - - - - - ); + return ; } ``` - - - Preloading a screen means that the screen will be rendered in the background. All the components in the screen will be mounted and the `useEffect` hooks will be called. This can be useful when you want to improve the perceived performance by hiding the delay in mounting heavy components or loading data. Depending on the navigator, `preload` may work differently: @@ -1346,14 +555,11 @@ if (navigation.isFocused()) { ### setParams -The `setParams` action allows to update params for a certain route. It takes the following arguments: +The `setParams` action allows to replace params for a certain route. It takes the following arguments: - `params` - _object_ - required - New params to be merged into existing route params. - - - -```js name="Common actions setParams" snack +```js name="Common actions setParams" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { Button } from '@react-navigation/elements'; @@ -1386,9 +592,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -1406,83 +609,43 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - ); } -const Stack = createStackNavigator({ +const RootStack = createStackNavigator({ screens: { Home: HomeScreen, Profile: ProfileScreen, }, }); -const Navigation = createStaticNavigation(Stack); +const Navigation = createStaticNavigation(RootStack); export default function App() { return ; } ``` - - +If you want to replace params for a particular route, you can add a `source` property referring to the route key: -```js name="Common actions setParams" snack +```js name="Common actions setParams" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { Button } from '@react-navigation/elements'; import { - NavigationContainer, - CommonActions, + createStaticNavigation, useNavigation, + CommonActions, } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; @@ -1508,16 +671,12 @@ function HomeScreen() { > Navigate to Profile - ); } function ProfileScreen({ route }) { const navigation = useNavigation(); - return ( Profile! {route.params.user}'s profile - - - ); } -const Stack = createStackNavigator(); +const RootStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); export default function App() { - return ( - - - - - - - ); + return ; } ``` - - +If the `source` property is explicitly set to `undefined`, it'll replace the params for the focused route. + +### replaceParams -If you want to set params for a particular route, you can add a `source` property referring to the route key: +The `replaceParams` action allows to replace params for a certain route. It takes the following arguments: - - +- `params` - _object_ - required - New params to use for the route. -```js name="Common actions setParams" snack +```js name="Common actions replaceParams" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { Button } from '@react-navigation/elements'; @@ -1636,9 +759,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -1656,83 +776,43 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - ); } -const Stack = createStackNavigator({ +const RootStack = createStackNavigator({ screens: { Home: HomeScreen, Profile: ProfileScreen, }, }); -const Navigation = createStaticNavigation(Stack); +const Navigation = createStaticNavigation(RootStack); export default function App() { return ; } ``` - - +If you want to replace params for a particular route, you can add a `source` property referring to the route key: -```js name="Common actions setParams" snack +```js name="Common actions replaceParams" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { Button } from '@react-navigation/elements'; import { - NavigationContainer, - CommonActions, + createStaticNavigation, useNavigation, + CommonActions, } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; @@ -1767,7 +847,6 @@ function HomeScreen() { function ProfileScreen({ route }) { const navigation = useNavigation(); - return ( Profile! {route.params.user}'s profile - - - ); } -const Stack = createStackNavigator(); +const RootStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); export default function App() { - return ( - - - - - - - ); + return ; } ``` - - - -If the `source` property is explicitly set to `undefined`, it'll set the params for the focused route. +If the `source` property is explicitly set to `undefined`, it'll replace the params for the focused route. diff --git a/versioned_docs/version-7.x/navigation-container.md b/versioned_docs/version-7.x/navigation-container.md index 5b11e4516e4..536dd908ae2 100644 --- a/versioned_docs/version-7.x/navigation-container.md +++ b/versioned_docs/version-7.x/navigation-container.md @@ -237,6 +237,19 @@ Note that the returned `options` object will be `undefined` if there are no navi The `addListener` method lets you listen to the following events: +##### `ready` + +The event is triggered when the navigation tree is ready. This is useful for cases where you want to wait until the navigation tree is mounted: + +```js +const unsubscribe = navigationRef.addListener('ready', () => { + // Get the initial state of the navigation tree + console.log(navigationRef.getRootState()); +}); +``` + +This is analogous to the [`onReady`](#onready) method. + ##### `state` The event is triggered whenever the [navigation state](navigation-state.md) changes in any navigator in the navigation tree: @@ -450,7 +463,7 @@ const Navigation = createStaticNavigation(RootStack); function App() { const linking = { - prefixes: ['https://mychat.com', 'mychat://'], + prefixes: ['https://example.com', 'example://'], }; return ( @@ -471,7 +484,7 @@ import { NavigationContainer } from '@react-navigation/native'; function App() { const linking = { - prefixes: ['https://mychat.com', 'mychat://'], + prefixes: ['https://example.com', 'example://'], config: { screens: { Home: 'feed/:sort', @@ -512,7 +525,7 @@ Example: Loading...} /> @@ -525,7 +538,7 @@ Example: listener(url); @@ -688,7 +701,7 @@ import messaging from '@react-native-firebase/messaging'; ```js +##### `linking.getActionFromState` + +The state parsed with [`getStateFromPath`](#linkinggetstatefrompath) is used as the initial state of the navigator. But for subsequent deep links and URLs, the state is converted to a navigation action. Typically it is a [`navigate`](navigation-actions.md#navigate) action. + +You can provide a custom `getActionFromState` function to customize how the state is converted to an action. + +Example: + + + + +```js + +``` + + + + +```js + + {/* content */} + +``` + + + + ### `fallback` React Element to use as a fallback while we resolve deep links. Defaults to `null`. diff --git a/versioned_docs/version-7.x/navigation-context.md b/versioned_docs/version-7.x/navigation-context.md index 90db5663241..afb0b0153f1 100755 --- a/versioned_docs/version-7.x/navigation-context.md +++ b/versioned_docs/version-7.x/navigation-context.md @@ -13,10 +13,7 @@ Most of the time, you won't use `NavigationContext` directly, as the provided `u Example: - - - -```js name="Navigation context" snack +```js name="Navigation context" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { Button } from '@react-navigation/elements'; @@ -48,7 +45,9 @@ function SomeComponent() { ); + // codeblock-focus-start } +// codeblock-focus-end function ProfileScreen() { const navigation = useNavigation(); @@ -60,7 +59,7 @@ function ProfileScreen() { ); } -const Stack = createNativeStackNavigator({ +const RootStack = createNativeStackNavigator({ initialRouteName: 'Home', screens: { Home: HomeScreen, @@ -68,7 +67,7 @@ const Stack = createNativeStackNavigator({ }, }); -const Navigation = createStaticNavigation(Stack); +const Navigation = createStaticNavigation(RootStack); function App() { return ; @@ -76,66 +75,3 @@ function App() { export default App; ``` - - - - -```js name="Navigation context" snack -import * as React from 'react'; -import { View, Text } from 'react-native'; -import { Button } from '@react-navigation/elements'; -// codeblock-focus-start -import { NavigationContext } from '@react-navigation/native'; -// codeblock-focus-end -import { NavigationContainer } from '@react-navigation/native'; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; - -function HomeScreen() { - return ; -} - -// codeblock-focus-start - -function SomeComponent() { - // We can access navigation object via context - const navigation = React.useContext(NavigationContext); - // codeblock-focus-end - - return ( - - Some component inside HomeScreen - - - ); -} - -function ProfileScreen() { - const navigation = React.useContext(NavigationContext); - - return ( - - - - ); -} - -const Stack = createNativeStackNavigator(); - -function App() { - return ( - - - - - - - ); -} - -export default App; -``` - - - diff --git a/versioned_docs/version-7.x/navigation-events.md b/versioned_docs/version-7.x/navigation-events.md index 279b918b801..1d3c6430a14 100644 --- a/versioned_docs/version-7.x/navigation-events.md +++ b/versioned_docs/version-7.x/navigation-events.md @@ -25,6 +25,12 @@ For most cases, the [`useFocusEffect`](use-focus-effect.md) hook might be approp This event is emitted when the screen goes out of focus. +:::note + +In some cases, such as going back from a screen in [native-stack navigator](native-stack-navigator.md), the screen may not receive the `blur` event as the screen is unmounted immediately. For cleaning up resources, it's recommended to use the cleanup function of [`useFocusEffect`](use-focus-effect.md) hook instead that considers both blur and unmounting of the screen. + +::: + ### `state` This event is emitted when the navigator's state changes. This event receives the navigator's state in the event data (`event.data.state`). @@ -70,7 +76,7 @@ React.useEffect( ); ``` -:::note +:::warning Preventing the action in this event doesn't work properly with [`@react-navigation/native-stack`](native-stack-navigator.md). We recommend using the [`usePreventRemove` hook](preventing-going-back.md) instead. @@ -263,7 +269,7 @@ class Profile extends React.Component { } ``` -One thing to keep in mind is that you can only listen to events from the immediate navigator with `addListener`. For example, if you try to add a listener in a screen that's inside a stack that's nested in a tab, it won't get the `tabPress` event. If you need to listen to an event from a parent navigator, you may use [`navigation.getParent`](navigation-object.md#getparent) to get a reference to the parent screen's navigation object and add a listener. +Keep in mind that you can only listen to events from the immediate navigator with `addListener`. For example, if you try to add a listener in a screen that's inside a stack that's nested in a tab, it won't get the `tabPress` event. If you need to listen to an event from a parent navigator, you may use [`navigation.getParent`](navigation-object.md#getparent) to get a reference to the parent screen's navigation object and add a listener. ```js const unsubscribe = navigation @@ -275,6 +281,12 @@ const unsubscribe = navigation Here `'MyTabs'` refers to the value you pass in the `id` prop of the parent `Tab.Navigator` whose event you want to listen to. +:::warning + +The component needs to be rendered for the listeners to be added in `useEffect` or `componentDidMount`. Navigators such as [bottom tabs](bottom-tab-navigator.md) and [drawer](drawer-navigator.md) lazily render the screen after navigating to it. So if your listener is not being called, double check that the component is rendered. + +::: + ### `listeners` prop on `Screen` Sometimes you might want to add a listener from the component where you defined the navigator rather than inside the screen. You can use the `listeners` prop on the `Screen` component to add listeners. The `listeners` prop takes an object with the event names as keys and the listener callbacks as values. diff --git a/versioned_docs/version-7.x/navigation-lifecycle.md b/versioned_docs/version-7.x/navigation-lifecycle.md index 4119652a4e4..d2a2317652b 100755 --- a/versioned_docs/version-7.x/navigation-lifecycle.md +++ b/versioned_docs/version-7.x/navigation-lifecycle.md @@ -474,7 +474,7 @@ export default function App() { See [Navigation events](navigation-events.md) for more details on the available events and the API usage. -Instead of adding event listeners manually, we can use the [`useFocusEffect`](use-focus-effect.md) hook to perform side effects. It's like React's `useEffect` hook, but it ties into the navigation lifecycle. +For performing side effects, we can use the [`useFocusEffect`](use-focus-effect.md) hook instead of subscribing to events. It's like React's `useEffect` hook, but it ties into the navigation lifecycle. Example: @@ -622,7 +622,11 @@ export default function App() { If you want to render different things based on if the screen is focused or not, you can use the [`useIsFocused`](use-is-focused.md) hook which returns a boolean indicating whether the screen is focused. +If you want to know if the screen is focused or not inside of an event listener, you can use the [`navigation.isFocused()`](navigation-object.md#isfocused) method. Note that using this method doesn't trigger a re-render like the `useIsFocused` hook does, so it is not suitable for rendering different things based on focus state. + ## Summary -- While React's lifecycle methods are still valid, React Navigation adds more events that you can subscribe to through the `navigation` object. -- You may also use the `useFocusEffect` or `useIsFocused` hooks. +- React Navigation does not unmount screens when navigating away from them +- The [`useFocusEffect`](use-focus-effect.md) hook is analogous to React's [`useEffect`](https://react.dev/reference/react/useEffect) but is tied to the navigation lifecycle instead of the component lifecycle. +- The [`useIsFocused`](use-is-focused.md) hook and [`navigation.isFocused()`](navigation-object.md#isfocused) method can be used to determine if a screen is currently focused. +- React Navigation emits [`focus`](navigation-events.md#focus) and [`blur`](navigation-events.md#blur) events that can be listened to when a screen comes into focus or goes out of focus. diff --git a/versioned_docs/version-7.x/navigation-object.md b/versioned_docs/version-7.x/navigation-object.md index c61d575c806..42af46e61b2 100755 --- a/versioned_docs/version-7.x/navigation-object.md +++ b/versioned_docs/version-7.x/navigation-object.md @@ -76,7 +76,9 @@ The `navigate` method lets us navigate to another screen in your app. It takes t - `name` - _string_ - A destination name of the screen in the current or a parent navigator. - `params` - _object_ - Params to use for the destination route. -- `merge` - _boolean_ - Whether params should be merged with the existing route params, or replace them (when navigating to an existing screen). Defaults to `false`. +- `options` - Options object containing the following properties: + - `merge` - _boolean_ - Whether params should be merged with the existing route params, or replace them (when navigating to an existing screen). Defaults to `false`. + - `pop` - _boolean_ - Whether screens should be popped to navigate to a matching screen in the stack. Defaults to `false`. @@ -248,40 +250,6 @@ In a stack navigator ([stack](stack-navigator.md) or [native stack](native-stack - If the [`getId`](screen.md#id) prop is specified, and another screen in the stack has the same ID, it will bring that screen to focus and update its params instead. - If none of the above conditions match, it'll push a new screen to the stack. -By default, the screen is identified by its name. But you can also customize it to take the params into account by using the [`getId`](screen.md#id) prop. - -For example, say you have specified a `getId` prop for `Profile` screen: - - - - -```js -const Tabs = createBottomTabNavigator({ - screens: { - Profile: { - screen: ProfileScreen, - getId: ({ params }) => params.userId, - }, - }, -}); -``` - - - - -```js - params.userId} -/> -``` - - - - -Now, if you have a stack with the history `Home > Profile (userId: bob) > Settings` and you call `navigate(Profile, { userId: 'alice' })`, the resulting screens will be `Home > Profile (userId: bob) > Settings > Profile (userId: alice)` since it'll add a new `Profile` screen as no matching screen was found. - In a tab or drawer navigator, calling `navigate` will switch to the relevant screen if it's not focused already and update the params of the screen. ### `navigateDeprecated` @@ -304,9 +272,8 @@ It takes the following arguments: In a stack navigator ([stack](stack-navigator.md) or [native stack](native-stack-navigator.md)), calling `navigate` with a screen name will have the following behavior: - If you're already on a screen with the same name, it will update its params and not push a new screen. -- If a screen with the same name already exists in the stack, it will pop all the screens after it to go back to the existing screen. -- If the [`getId`](screen.md#id) prop is specified, and another screen in the stack has the same ID, it will pop any screens to navigate to that screen and update its params instead. -- If none of the above conditions match, it'll push a new screen to the stack. +- If you're on a different screen, it will push the new screen onto the stack. +- If the [`getId`](screen.md#id) prop is specified, and another screen in the stack has the same ID, it will bring that screen to focus and update its params instead. In a tab or drawer navigator, calling `navigate` will switch to the relevant screen if it's not focused already and update the params of the screen. @@ -484,7 +451,7 @@ The `reset` method lets us replace the navigator state with a new state: -```js name="Navigate - replace and reset" snack +```js name="Navigation object replace and reset" snack import * as React from 'react'; import { Button } from '@react-navigation/elements'; import { View, Text } from 'react-native'; @@ -623,7 +590,7 @@ export default App; -```js name="Navigate - replace and reset" snack +```js name="Navigation object replace and reset" snack import * as React from 'react'; import { Button } from '@react-navigation/elements'; import { View, Text } from 'react-native'; @@ -1005,7 +972,7 @@ The `setParams` method lets us update the params (`route.params`) of the current -```js name="Navigate - setParams" snack +```js name="Navigation object setParams" snack import * as React from 'react'; import { Button } from '@react-navigation/elements'; import { View, Text } from 'react-native'; @@ -1107,7 +1074,7 @@ export default App; -```js name="Navigate - setParams" snack +```js name="Navigation object setParams" snack import * as React from 'react'; import { Button } from '@react-navigation/elements'; import { View, Text } from 'react-native'; @@ -1206,6 +1173,214 @@ export default App; +### `replaceParams` + +The `replaceParams` method lets us replace the params (`route.params`) of the current screen with a new params object. + + + + +```js name="Navigation object replaceParams" snack +import * as React from 'react'; +import { Button } from '@react-navigation/elements'; +import { View, Text } from 'react-native'; +import { + useNavigation, + createStaticNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + This is the home screen of the app + + + ); +} + +// codeblock-focus-start +function ProfileScreen({ route }) { + const navigation = useNavigation(); + + return ( + + Profile Screen + Friends: + {route.params.friends[0]} + {route.params.friends[1]} + {route.params.friends[2]} + + + + ); +} +// codeblock-focus-end + +const Stack = createNativeStackNavigator({ + initialRouteName: 'Home', + screens: { + Home: HomeScreen, + Profile: { + screen: ProfileScreen, + options: ({ route }) => ({ title: route.params.title }), + }, + }, +}); + +const Navigation = createStaticNavigation(Stack); + +function App() { + return ; +} + +export default App; +``` + + + + +```js name="Navigation object replaceParams" snack +import * as React from 'react'; +import { Button } from '@react-navigation/elements'; +import { View, Text } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + This is the home screen of the app + + + ); +} + +// codeblock-focus-start +function ProfileScreen({ route }) { + const navigation = useNavigation(); + + return ( + + Profile Screen + Friends: + {route.params.friends[0]} + {route.params.friends[1]} + {route.params.friends[2]} + + + + ); +} +// codeblock-focus-end + +const Stack = createNativeStackNavigator(); + +function App() { + return ( + + + + ({ title: route.params.title })} + /> + + + ); +} + +export default App; +``` + + + + ### `setOptions` The `setOptions` method lets us set screen options from within the component. This is useful if we need to use the component's props, state or context to configure our screen. @@ -1213,7 +1388,7 @@ The `setOptions` method lets us set screen options from within the component. Th -```js name="Navigate - setOptions" snack +```js name="Navigation object setOptions" snack import * as React from 'react'; import { View, Text, TextInput } from 'react-native'; import { Button } from '@react-navigation/elements'; @@ -1303,7 +1478,7 @@ export default App; -```js name="Navigate - setOptions" snack +```js name="Navigation object setOptions" snack import * as React from 'react'; import { View, Text, TextInput } from 'react-native'; import { Button } from '@react-navigation/elements'; diff --git a/versioned_docs/version-7.x/navigation-solutions-and-community-libraries.md b/versioned_docs/version-7.x/navigation-solutions-and-community-libraries.md deleted file mode 100755 index 1891a0afcf4..00000000000 --- a/versioned_docs/version-7.x/navigation-solutions-and-community-libraries.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -id: navigation-solutions-and-community-libraries -title: Navigation Solutions and Community Libraries -sidebar_label: Community Libraries ---- - -:::note - -Libraries listed in this guide may not have been updated to work with the latest version of React Navigation. Please refer to the library's documentation to see which version of React Navigation it supports. - -::: - -## Solutions built on top of React Navigation - -### Solito - -A tiny wrapper around React Navigation and Next.js that lets you share navigation code across platforms. Also, it provides a set of patterns and examples for building cross-platform apps with React Native + Next.js. - -[Documentation](https://solito.dev/) - -[github.com/nandorojo/solito](https://github.com/nandorojo/solito) - -### Expo Router - -File-based router for React Native apps. With Expo Router pages are automatically generated by simply creating files in a project. - -[Documentation](https://expo.github.io/router/docs) - -[github.com/expo/router](https://github.com/expo/router) - -### Navio - -A navigation library built on top of React Navigation. It's main goal is to improve DX by building the app layout in one place and using the power of TypeScript to provide route names autocompletion. - -[github.com/kanzitelli/rn-navio](https://github.com/kanzitelli/rn-navio) - -[Demo on Snack](https://snack.expo.dev/@kanzitelli/rn-navio-snack) - -## Community libraries - -### react-native-paper - -The [React Native Paper](https://callstack.github.io/react-native-paper/) library provides React Navigation integration for its Material Bottom Tabs. Material Bottom Tabs is a material-design themed tab bar on the bottom of the screen that lets you switch between different routes with animation. - -[callstack.github.io/react-native-paper/docs/guides/bottom-navigation](https://callstack.github.io/react-native-paper/docs/guides/bottom-navigation/) - -### react-native-screens - -This project aims to expose native navigation container components to React Native and React Navigation can integrate with it since version 2.14.0. Using `react-native-screens` brings several benefits, such as support for the ["reachability feature"](https://www.cnet.com/how-to/how-to-use-reachability-on-iphone-6-6-plus/) on iOS, and improved memory consumption on both platforms. - -[github.com/software-mansion/react-native-screens](https://github.com/software-mansion/react-native-screens) - -### react-navigation-header-buttons - -Helps you to render buttons in the navigation bar and handle the styling so you don't have to. It tries to mimic the appearance of native navbar buttons and attempts to offer a simple interface for you to interact with. - -[github.com/vonovak/react-navigation-header-buttons](https://github.com/vonovak/react-navigation-header-buttons) - -[Demo on expo](https://expo.io/@vonovak/navbar-buttons-demo) - -### react-navigation-props-mapper - -Provides simple HOCs that map react-navigation props to your screen components directly - ie. instead of `const user = this.props.route.params.activeUser`, you'd write `const user = this.props.activeUser`. - -[github.com/vonovak/react-navigation-props-mapper](https://github.com/vonovak/react-navigation-props-mapper) - -## react-native-bottom-tabs - -This project aims to expose the native Bottom Tabs component to React Native. It exposes SwiftUI's TabView on iOS and the material design tab bar on Android. Using `react-native-bottom-tabs` can bring several benefits, including multi-platform support and a native-feeling tab bar. - -[github.com/okwasniewski/react-native-bottom-tabs](https://github.com/okwasniewski/react-native-bottom-tabs) diff --git a/versioned_docs/version-7.x/navigation-state.md b/versioned_docs/version-7.x/navigation-state.md index 46d2bf13a2c..43d2a59a7b5 100644 --- a/versioned_docs/version-7.x/navigation-state.md +++ b/versioned_docs/version-7.x/navigation-state.md @@ -30,14 +30,14 @@ There are few properties present in every navigation state object: - `routes` - List of route objects (screens) which are rendered in the navigator. It also represents the history in a stack navigator. There should be at least one item present in this array. - `index` - Index of the focused route object in the `routes` array. - `history` - A list of visited items. This is an optional property and not present in all navigators. For example, it's only present in tab and drawer navigators in the core. The shape of the items in the `history` array can vary depending on the navigator. There should be at least one item present in this array. -- `stale` - A navigation state is assumed to be stale unless the `stale` property is explicitly set to `false`. This means that the state object needs to be ["rehydrated"](#partial-state-objects). +- `stale` - A navigation state is assumed to be stale unless the `stale` property is explicitly set to `false`. This means that the state object needs to be ["rehydrated"](#stale-state-objects). Each route object in a `routes` array may contain the following properties: - `key` - Unique key of the screen. Created automatically or added while navigating to this screen. - `name` - Name of the screen. Defined in navigator component hierarchy. - `params` - An optional object containing params which is defined while navigating e.g. `navigate('Home', { sortBy: 'latest' })`. -- `state` - An optional object containing the navigation state of a child navigator nested inside this screen. +- `state` - An optional object containing the [stale navigation state](#stale-state-objects) of a child navigator nested inside this screen. For example, a stack navigator containing a tab navigator nested inside it's home screen may have a navigation state object like this: @@ -69,11 +69,17 @@ const state = { It's important to note that even if there's a nested navigator, the `state` property on the `route` object is not added until a navigation happens, hence it's not guaranteed to exist. -## Partial state objects +## Stale state objects -Earlier there was a mention of `stale` property in the navigation state. A stale navigation state means that the state object needs to be rehydrated or fixed or fixed up, such as adding missing keys, removing invalid screens etc. before being used. As a user, you don't need to worry about it, React Navigation will fix up any issues in a state object automatically unless `stale` is set to `false`. If you're writing a [custom router](custom-routers.md), the `getRehydratedState` method let's you write custom rehydration logic to fix up state objects. +Earlier there was a mention of `stale` property in the navigation state. If the `stale` property is set to `true` or is missing, the state is assumed to be stale. A stale navigation state means that the state object may be partial, such as missing keys or routes, contain invalid routes, or may not be up-to-date. A stale state can be a result of [deep linking](deep-linking.md), r[estoring from a persisted state](state-persistence.md) etc. -This also applies to the `index` property: `index` should be the last route in a stack, and if a different value was specified, React Navigation fixes it. For example, if you wanted to reset your app's navigation state to have it display the `Profile` route, and have the `Home` route displayed upon going back, and did the below, +If you're accessing the navigation state of a navigator using the built-in APIs such as [`useNavigationState()`](use-navigation-state.md), [`navigation.getState()`](navigation-object.md#getstate) etc., the state object is guaranteed to be complete and not stale. However, if you try to access a child navigator's state with the `state` property on the `route` object, it maybe a stale or partial state object. So it's not recommended to use this property directly. + +Using the [`ref.getRootState()`](navigation-container.md#getrootstate) API will always return a complete and up-to-date state object for the current navigation tree, including any nested child navigators. + +When React Navigation encounters stale or partial state, it will automatically fix it up before using it. This includes adding missing keys, removing any invalid routes, ensuring the `index` is correct etc. This process of fixing stale state is called **rehydration**. If you're writing a [custom router](custom-routers.md), the `getRehydratedState` method lets you write custom rehydration logic to fix up state objects. + +For example, `index` should be the last route in a stack, and if a different value was specified, React Navigation fixes it. For example, if you wanted to reset your app's navigation state to have it display the `Profile` route, and have the `Home` route displayed upon going back, and dispatched the following action: ```js navigation.reset({ @@ -82,7 +88,7 @@ navigation.reset({ }); ``` -React Navigation would correct `index` to 1, and display the route and perform navigation as intended. +React Navigation would correct `index` to `1` before the routes are displayed. This feature comes handy when doing operations such as [reset](navigation-actions.md#reset), [providing a initial state](navigation-container.md#initialstate) etc., as you can safely omit many properties from the navigation state object and relying on React Navigation to add those properties for you, making your code simpler. For example, you can only provide a `routes` array without any keys and React Navigation will automatically add everything that's needed to make it work: @@ -118,4 +124,4 @@ If you want React Navigation to fix invalid state, you need to make sure that yo ::: -When you're providing a state object in [`initialState`](navigation-container.md#initialstate), React Navigation will always assume that it's a stale state object, which makes sure that things like state persistence work smoothly without extra manipulation of the state object. +When you're providing a state object in [`initialState`](navigation-container.md#initialstate), React Navigation will always assume that it's a stale state object, since navigation configuration may have changed since the last time. This makes sure that things like [state persistence](state-persistence.md) work smoothly without extra manipulation of the state object. diff --git a/versioned_docs/version-7.x/navigator.md b/versioned_docs/version-7.x/navigator.md index 6d10993cae9..f34638f4be2 100644 --- a/versioned_docs/version-7.x/navigator.md +++ b/versioned_docs/version-7.x/navigator.md @@ -132,7 +132,9 @@ function MyStack() { A layout is a wrapper around the navigator. It can be useful for augmenting the navigators with additional UI with a wrapper. -The difference from adding a wrapper around the navigator manually is that the code in a layout callback has access to the navigator's state, options etc.: +The difference from adding a wrapper around the navigator manually is that the code in a layout callback has access to the navigator's state, options etc. + +It takes a function that returns a React element: @@ -242,7 +244,9 @@ Event listeners can be used to subscribe to various events emitted for the scree ### Screen layout -A screen layout is a wrapper around each screen in the navigator. It makes it easier to provide things such as a common error boundary and suspense fallback for all screens in the navigator: +A screen layout is a wrapper around each screen in the navigator. It makes it easier to provide things such as an error boundary and suspense fallback for all screens in the navigator, or wrap each screen with additional UI. + +It takes a function that returns a React element: @@ -305,3 +309,155 @@ function MyStack() { + +### Router + +:::warning + +This API is experimental and may change in a minor release. + +::: + +Routers can be customized with the `UNSTABLE_router` prop on navigator to override how navigation actions are handled. + +It takes a function that receives the original router and returns an object with overrides: + + + + +```js +const MyStack = createNativeStackNavigator({ + // highlight-start + UNSTABLE_router: (original) => ({ + getStateForAction(state, action) { + if (action.type === 'SOME_ACTION') { + // Custom logic + } + + // Fallback to original behavior + return original.getStateForAction(state, action); + }, + }), + // highlight-end + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +``` + + + + +```js +const Stack = createNativeStackNavigator(); + +function MyStack() { + return ( + ({ + getStateForAction(state, action) { + if (action.type === 'SOME_ACTION') { + // Custom logic + } + + // Fallback to original behavior + return original.getStateForAction(state, action); + }, + })} + // highlight-end + > + + + + ); +} +``` + + + + +The function passed to `UNSTABLE_router` **must be a pure function and cannot reference outside dynamic variables**. + +The overrides object is shallow merged with the original router. So you don't need to specify all properties of the router, only the ones you want to override. + +See [custom routers](custom-routers.md) for more details on routers. + +### Route names change behavior + +:::warning + +This API is experimental and may change in a minor release. + +::: + +When the list of available routes in a navigator changes dynamically, e.g. based on conditional rendering, looping over data from an API etc., the navigator needs to update the [navigation state](navigation-state.md) according to the new list of routes. + +By default, it works as follows: + +- Any routes not present in the new available list of routes are removed from the navigation state +- If the currently focused route is still present in the new available list of routes, it remains focused. +- If the currently focused route has been removed, but the navigation state has other routes that are present in the new available list, the first route in from the list of rendered routes becomes focused. +- If none of the routes in the navigation state are present in the new available list of routes, one of the following things can happen based on the `UNSTABLE_routeNamesChangeBehavior` prop: + - `'firstMatch'` - The first route defined in the new list of routes becomes focused. This is the default behavior based on [`getStateForRouteNamesChange`](custom-routers.md) in the router. + - `'lastUnhandled'` - The last state that was unhandled due to conditional rendering is restored. + +Example cases where state might have been unhandled: + +- Opened a deep link to a screen, but a login screen was shown. +- Navigated to a screen containing a navigator, but a different screen was shown. +- Reset the navigator to a state with different routes not matching the available list of routes. + +In these cases, specifying `'lastUnhandled'` will reuse the unhandled state if present. If there's no unhandled state, it will fallback to `'firstMatch'` behavior. + +Caveats: + +- Direct navigation is only handled for `NAVIGATE` actions. +- Unhandled state is restored only if the current state becomes invalid, i.e. it doesn't contain any currently defined screens. + +Example usage: + + + + +```js +const RootStack = createNativeStackNavigator({ + // highlight-next-line + UNSTABLE_routeNamesChangeBehavior: 'lastUnhandled', + screens: { + Home: { + if: useIsSignedIn, + screen: HomeScreen, + }, + SignIn: { + if: useIsSignedOut, + screen: SignInScreen, + options: { + title: 'Sign in', + }, + }, + }, +}); +``` + + + + +```js + + {isSignedIn ? ( + + ) : ( + + )} + +``` + + + + +The most common use case for this is to [show the correct screen based on authentication based on deep link](auth-flow.md#handling-deep-links-after-auth). diff --git a/versioned_docs/version-7.x/params.md b/versioned_docs/version-7.x/params.md index c5c5b26d1c6..9bd5d6fb88a 100755 --- a/versioned_docs/version-7.x/params.md +++ b/versioned_docs/version-7.x/params.md @@ -73,6 +73,7 @@ function DetailsScreen({ route }) { () => // highlight-start navigation.push('Details', { + // Randomly generate an ID for demonstration purposes itemId: Math.floor(Math.random() * 100), }) // highlight-end @@ -193,9 +194,11 @@ export default function App() { } ``` +The `setParams` method merges the new params with the existing ones. To replace the existing params, you can use [`replaceParams`](navigation-object.md#replaceparams) instead. + :::note -Avoid using `setParams` to update screen options such as `title` etc. If you need to update options, use [`setOptions`](navigation-object.md#setoptions) instead. +Avoid using `setParams` or `replaceParams` to update screen options such as `title` etc. If you need to update options, use [`setOptions`](navigation-object.md#setoptions) instead. ::: @@ -304,12 +307,12 @@ import { Button } from '@react-navigation/elements'; function SettingsScreen({ route }) { const navigation = useNavigation(); - const { user } = route.params; + const { userId } = route.params; return ( Settings Screen - userParam: {JSON.stringify(user)} + User ID: {JSON.stringify(userId)} @@ -337,7 +340,7 @@ function HomeScreen() { // codeblock-focus-start navigation.navigate('More', { screen: 'Settings', - params: { user: 'jane' }, + params: { userId: 'jane' }, }) // codeblock-focus-end } @@ -371,11 +374,27 @@ export default function App() { See [Nesting navigators](nesting-navigators.md) for more details on nesting. +## Reserved param names + +Some param names are reserved by React Navigation as part of the API for nested navigators. The list of the reserved param names are as follows: + +- `screen` +- `params` +- `initial` +- `state` + +You should avoid using these param names in your code unless navigating to a screen containing a nested navigator. Otherwise it will result in unexpected behavior, such as the screen not being able to access the params you passed. If you need to pass data to a nested screen, use a different names for the param. + ## What should be in params -Params are essentially options for a screen. They should contain the minimal data required to show a screen, nothing more. If the data is used by multiple screens, it should be in a global store or global cache. Params is not designed for state management. +Params serve two main purposes: -You can think of the route object as a URL. If your screen had a URL, what should be in the URL? The same principles apply to params. Think of visiting a shopping website; when you see product listings, the URL usually contains category name, type of sort, any filters etc., not the actual list of products displayed on the screen. +- Information required to identify and display data on a screen (e.g. id of an object to be displayed on the screen) +- State specific to a screen (e.g. sort order, filters, page numbers etc. that can also be changed on the screen) + +Params should contain the minimal information required to show a screen, nothing more. The actual data (e.g. user objects) should be in a global store or global cache. + +You can think of the route object as a URL. The same principles apply to params. Think of visiting a shopping website; when you see product listings, the URL usually contains category name, type of sort, any filters etc., not the actual list of products displayed on the screen. For example, say if you have a `Profile` screen. When navigating to it, you might be tempted to pass the user object in the params: @@ -398,7 +417,6 @@ However, this is an anti-pattern. There are many reasons why this is a bad idea: - The same data is duplicated in multiple places. This can lead to bugs such as the profile screen showing outdated data even if the user object has changed after navigation. - Each screen that navigates to the `Profile` screen now needs to know how to fetch the user object - which increases the complexity of the code. - URLs to the screen (browser URL on the web, or deep links on native) will contain the user object. This is problematic: - 1. Since the user object is in the URL, it's possible to pass a random user object representing a user that doesn't exist or has incorrect data in the profile. 2. If the user object isn't passed or improperly formatted, this could result in crashes as the screen won't know how to handle it. 3. The URL can become very long and unreadable. @@ -409,21 +427,22 @@ A better way is to pass only the ID of the user in params: navigation.navigate('Profile', { userId: 'jane' }); ``` -Now, you can use the passed `userId` to grab the user from your global store. This eliminates a host of issues such as outdated data, or problematic URLs. +Now, you can use the passed `userId` to grab the user from your global cache or fetch it from the API. Using a library such as [React Query](https://tanstack.com/query/) can simplify this process since it makes it easy to fetch and cache your data. This approach helps to avoid the problems mentioned above. Some examples of what should be in params are: -1. IDs like user id, item id etc., e.g. `navigation.navigate('Profile', { userId: 'Jane' })` -2. Params for sorting, filtering data etc. when you have a list of items, e.g. `navigation.navigate('Feeds', { sortBy: 'latest' })` -3. Timestamps, page numbers or cursors for pagination, e.g. `navigation.navigate('Chat', { beforeTime: 1603897152675 })` -4. Data to fill inputs on a screen to compose something, e.g. `navigation.navigate('ComposeTweet', { title: 'Hello world!' })` - -In essence, pass the least amount of data required to identify a screen in params, for a lot of cases, this simply means passing the ID of an object instead of passing a full object. Keep your application data separate from the navigation state. +- IDs such as user id, item id etc., e.g. `navigation.navigate('Profile', { userId: 'Jane' })` +- State for sorting, filtering data etc. when you have a list of items, e.g. `navigation.navigate('Feeds', { sortBy: 'latest' })` +- Timestamps, page numbers or cursors for pagination, e.g. `navigation.navigate('Chat', { beforeTime: 1603897152675 })` +- Data to fill inputs on a screen to compose something, e.g. `navigation.navigate('ComposeTweet', { title: 'Hello world!' })` ## Summary -- `navigate` and `push` accept an optional second argument to let you pass parameters to the route you are navigating to. For example: `navigation.navigate('RouteName', { paramName: 'value' })`. -- You can read the params through `route.params` inside a screen -- You can update the screen's params with `navigation.setParams` -- Initial params can be passed via the `initialParams` prop on `Screen` -- Params should contain the minimal data required to show a screen, nothing more +- [`navigate`](navigation-actions.md#navigate) and [`push`](stack-actions.md#push) accept an optional second argument to let you pass parameters to the route you are navigating to. For example: `navigation.navigate('RouteName', { paramName: 'value' })`. +- You can read the params through [`route.params`](route-object.md) inside a screen +- You can update the screen's params with [`navigation.setParams`](navigation-object.md#setparams) or [`navigation.replaceParams`](navigation-object.md#replaceparams) +- Initial params can be passed via the [`initialParams`](screen.md#initial-params) prop on `Screen` or in the navigator config +- State such as sort order, filters etc. should be kept in params so that the state is reflected in the URL and can be shared/bookmarked. +- Params should contain the least amount of data required to identify a screen; for most cases, this means passing the ID of an object instead of passing a full object. +- Don't keep application specific data or cached data in params; instead, use a global store or cache. +- Some [param names are reserved](#reserved-param-names) by React Navigation and should be avoided diff --git a/versioned_docs/version-7.x/screen-options.md b/versioned_docs/version-7.x/screen-options.md index 1f95d0f3487..9463726e457 100644 --- a/versioned_docs/version-7.x/screen-options.md +++ b/versioned_docs/version-7.x/screen-options.md @@ -19,13 +19,10 @@ There are 3 ways of specifying options for screens: You can pass a prop named `options` to the `Screen` component to configure a screen, where you can specify an object with different options for that screen: - - - -```js name="Screen title option" snack +```js name="Screen title option" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; -import Button from '@react-navigation/elements'; +import { Button } from '@react-navigation/elements'; import { createStaticNavigation, useNavigation, @@ -57,7 +54,7 @@ function ProfileScreen() { } // codeblock-focus-start -const Stack = createNativeStackNavigator({ +const RootStack = createNativeStackNavigator({ screens: { Home: { screen: HomeScreen, @@ -74,7 +71,7 @@ const Stack = createNativeStackNavigator({ }, }); -const Navigation = createStaticNavigation(Stack); +const Navigation = createStaticNavigation(RootStack); export default function App() { return ; @@ -82,74 +79,10 @@ export default function App() { // codeblock-focus-end ``` - - - -```js name="Screen title option" snack -import * as React from 'react'; -import { View, Text } from 'react-native'; -import Button from '@react-navigation/elements'; -import { NavigationContainer, useNavigation } from '@react-navigation/native'; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; - -const Stack = createNativeStackNavigator(); - -function HomeScreen() { - const navigation = useNavigation(); - - return ( - - Home Screen - - - ); -} - -function ProfileScreen() { - const navigation = useNavigation(); - - return ( - - Profile Screen - - - ); -} - -export default function App() { - return ( - - // codeblock-focus-start - - - - - // codeblock-focus-end - - ); -} -``` - - - - You can also pass a function to `options`. The function will receive the [`navigation` object](navigation-object.md) and the [`route` object](route-object.md) for that screen, as well as the [`theme` object](themes.md). This can be useful if you want to perform navigation in your options: - - - -```js -const Stack = createNativeStackNavigator({ +```js static2dynamic +const RootStack = createNativeStackNavigator({ screens: { Home: { screen: HomeScreen, @@ -164,25 +97,6 @@ const Stack = createNativeStackNavigator({ }); ``` - - - -```js - ({ - title: 'Awesome app', - headerLeft: () => ( - navigation.toggleDrawer()} /> - ), - })} -/> -``` - - - - ### `screenOptions` prop on `Group` You can pass a prop named `screenOptions` to the `Group` component to configure screens inside the group, where you can specify an object with different options. The options specified in `screenOptions` apply to all of the screens in the group. @@ -203,7 +117,7 @@ import { import { createNativeStackNavigator } from '@react-navigation/native-stack'; // codeblock-focus-start -const Stack = createNativeStackNavigator({ +const RootStack = createNativeStackNavigator({ groups: { App: { screenOptions: { @@ -244,7 +158,9 @@ function ScreenWithButton(screenName, navigateTo) { ); }; } -const Navigation = createStaticNavigation(Stack); + +const Navigation = createStaticNavigation(RootStack); + export default function App() { return ; } @@ -363,11 +279,8 @@ You can pass a prop named `screenOptions` to the navigator component, where you Example: - - - -```js -const Stack = createNativeStackNavigator({ +```js static2dynamic +const RootStack = createNativeStackNavigator({ screenOptions: { headerStyle: { backgroundColor: 'papayawhip', @@ -380,27 +293,9 @@ const Stack = createNativeStackNavigator({ }); ``` - - - -```js - - - - -``` - - - - Similar to `options`, you can also pass a function to `screenOptions`. The function will receive the [`navigation` object](navigation-object.md) and the [`route` object](route-object.md) for each screen. This can be useful if you want to configure options for all the screens in one place based on the route: - - - -```js name="Screen options for tab navigator" snack dependencies=@expo/vector-icons +```js name="Screen options for tab navigator" snack dependencies=@expo/vector-icons static2dynamic import * as React from 'react'; import { View } from 'react-native'; import { createStaticNavigation } from '@react-navigation/native'; @@ -408,7 +303,7 @@ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { MaterialCommunityIcons } from '@expo/vector-icons'; // codeblock-focus-start -const Tab = createBottomTabNavigator({ +const MyTabs = createBottomTabNavigator({ screenOptions: ({ route }) => ({ tabBarIcon: ({ color, size }) => { const icons = { @@ -436,63 +331,13 @@ function EmptyScreen() { return ; } -const Navigation = createStaticNavigation(Tab); +const Navigation = createStaticNavigation(MyTabs); export default function App() { return ; } ``` - - - -```js name="Screen options for tab navigator" snack dependencies=@expo/vector-icons -import * as React from 'react'; -import { View } from 'react-native'; -import { NavigationContainer } from '@react-navigation/native'; -import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; -import { MaterialCommunityIcons } from '@expo/vector-icons'; - -const Tab = createBottomTabNavigator(); - -function EmptyScreen() { - return ; -} - -export default function App() { - return ( - - // codeblock-focus-start - ({ - tabBarIcon: ({ color, size }) => { - const icons = { - Home: 'home', - Profile: 'account', - }; - - return ( - - ); - }, - })} - > - - - - // codeblock-focus-end - - ); -} -``` - - - - ### `navigation.setOptions` method The `navigation` object has a `setOptions` method that lets you update the options for a screen from within a component. See [navigation object's docs](navigation-object.md#setoptions) for more details. diff --git a/versioned_docs/version-7.x/screen-tracking.md b/versioned_docs/version-7.x/screen-tracking.md index 26e6a0970ec..87786b0bfe4 100644 --- a/versioned_docs/version-7.x/screen-tracking.md +++ b/versioned_docs/version-7.x/screen-tracking.md @@ -181,3 +181,9 @@ export default function App() { + +:::note + +If you are building a library that wants to provide screen tracking integration with React Navigation, you can accept a [`ref`](navigation-container.md#ref) to the navigation container and use the [`ready`](navigation-container.md#ready) and [`state`](navigation-container.md#state) events instead of `onReady` and `onStateChange` props to keep your logic self-contained. + +::: diff --git a/versioned_docs/version-7.x/screen.md b/versioned_docs/version-7.x/screen.md index 8589e630686..354b1a00ba4 100644 --- a/versioned_docs/version-7.x/screen.md +++ b/versioned_docs/version-7.x/screen.md @@ -218,7 +218,7 @@ This can be done by specifying the `getId` callback. It receives an object with ```js -const Stack = createNativeStackNavigator({ +const Stack = createStackNavigator({ screens: { Profile: { screen: ProfileScreen, @@ -244,49 +244,22 @@ const Stack = createNativeStackNavigator({ -By default, `navigate('ScreenName', params)` updates the current screen if the screen name matches, otherwise adds a new screen in a stack navigator. So if you're on `ScreenName` and navigate to `ScreenName` again, it won't add a new screen even if the params are different - it'll update the current screen with the new params instead: - -```js -// Let's say you're on `Home` screen -// Then you navigate to `Profile` screen with `userId: 1` -navigation.navigate('Profile', { userId: 1 }); - -// Now the stack will have: `Home` -> `Profile` with `userId: 1` - -// Then you navigate to `Profile` screen again with `userId: 2` -navigation.navigate('Profile', { userId: 2 }); - -// The stack will now have: `Home` -> `Profile` with `userId: 2` -``` - -If you specify `getId` and it doesn't return `undefined`, the screen is identified by both the screen name and the returned ID. That means that if you're on `ScreenName` and navigate to `ScreenName` again with different params - and return a different ID from the `getId` callback, it'll add a new screen to the stack: - -```js -// Let's say you're on `Home` screen -// Then you navigate to `Profile` screen with `userId: 1` -navigation.navigate('Profile', { userId: 1 }); - -// Now the stack will have: `Home` -> `Profile` with `userId: 1` - -// Then you navigate to `Profile` screen again with `userId: 2` -navigation.navigate('Profile', { userId: 2 }); +In the above example, `params.userId` is used as an ID for the `Profile` screen with `getId`. This changes how the navigation works to ensure that the screen with the same ID appears only once in the stack. -// The stack will now have: `Home` -> `Profile` with `userId: 1` -> `Profile` with `userId: 2` -``` +Let's say you have a stack with the history `Home > Profile (userId: bob) > Settings`, consider following scenarios: -The `getId` callback can also be used to ensure that the screen with the same ID doesn't appear multiple times in the stack: +- You call `navigate(Profile, { userId: 'bob' })`: + The resulting screens will be `Home > Settings > Profile (userId: bob)` since the existing `Profile` screen matches the ID. +- You call `navigate(Profile, { userId: 'alice' })`: + The resulting screens will be `Home > Profile (userId: bob) > Settings > Profile (userId: alice)` since it'll add a new `Profile` screen as no matching screen was found. -```js -// Let's say you have a stack with the screens: `Home` -> `Profile` with `userId: 1` -> `Settings` -// Then you navigate to `Profile` screen with `userId: 1` again -navigation.navigate('Profile', { userId: 1 }); +If `getId` is specified in a tab or drawer navigator, the screen will remount if the ID changes. -// Now the stack will have: `Home` -> `Profile` with `userId: 1` -``` +:::warning -In the above examples, `params.userId` is used as an ID, subsequent navigation to the screen with the same `userId` will navigate to the existing screen instead of adding a new one to the stack. If the navigation was with a different `userId`, then it'll add a new screen. +If you're using [`@react-navigation/native-stack`](native-stack-navigator.md), it doesn't work correctly with the `getId` callback. So it's recommended to avoid using it in that case. -If `getId` is specified in a tab or drawer navigator, the screen will remount if the ID changes. +::: ### Component @@ -361,7 +334,9 @@ By default, React Navigation applies optimizations to screen components to preve ### Layout -A layout is a wrapper around the screen. It makes it easier to provide things such as an error boundary and suspense fallback for a screen: +A layout is a wrapper around the screen. It makes it easier to provide things such as an error boundary and suspense fallback for a screen, or wrap the screen with additional UI. + +It takes a function that returns a React element: diff --git a/versioned_docs/version-7.x/server-rendering.md b/versioned_docs/version-7.x/server-rendering.md index 0c388ffcb38..4dddb2019b0 100644 --- a/versioned_docs/version-7.x/server-rendering.md +++ b/versioned_docs/version-7.x/server-rendering.md @@ -12,7 +12,7 @@ This guide will cover how to server render your React Native app using React Nat 1. Rendering the correct layout depending on the request URL 2. Setting appropriate page metadata based on the focused screen -::: warning +:::warning Server rendering support is currently limited. It's not possible to provide a seamless SSR experience due to a lack of APIs such as media queries. In addition, many third-party libraries often don't work well with server rendering. diff --git a/versioned_docs/version-7.x/shared-element-transitions.md b/versioned_docs/version-7.x/shared-element-transitions.md index 7b4f50d6888..18d5fd917f0 100644 --- a/versioned_docs/version-7.x/shared-element-transitions.md +++ b/versioned_docs/version-7.x/shared-element-transitions.md @@ -7,12 +7,14 @@ sidebar_label: Shared element transitions import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -This guide covers how to animate elements between screens. This feature is known as a [Shared Element Transition](https://docs.swmansion.com/react-native-reanimated/docs/api/sharedElementTransitions) and it's implemented in the [`@react-navigation/native-stack`](native-stack-navigator.md) with [React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/). +This guide covers how to animate elements between screens. This feature is known as a [Shared Element Transition](https://docs.swmansion.com/react-native-reanimated/docs/shared-element-transitions/overview/) and it's implemented in the [`@react-navigation/native-stack`](native-stack-navigator.md) with [React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/). :::warning As of writing this guide, Shared Element Transitions are considered an experimental feature not recommended for production use. +Shared Element Transitions are currently only supported on **old React Native architecture** (Paper). + ::: ); } @@ -303,4 +311,4 @@ if (!isReady) { Each param, route, and navigation state must be fully serializable for this feature to work. Typically, you would serialize the state as a JSON string. This means that your routes and params must contain no functions, class instances, or recursive data structures. React Navigation already [warns you during development](troubleshooting.md#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state) if it encounters non-serializable data, so watch out for the warning if you plan to persist navigation state. -You can modify the initial state object before passing it to container, but note that if your `initialState` isn't a [valid navigation state](navigation-state.md#partial-state-objects), React Navigation may not be able to handle the situation gracefully. +You can modify the initial state object before passing it to container, but note that if your `initialState` isn't a [valid navigation state](navigation-state.md#stale-state-objects), React Navigation may not be able to handle the situation gracefully in some scenarios. diff --git a/versioned_docs/version-7.x/tab-actions.md b/versioned_docs/version-7.x/tab-actions.md index 3e8fd06add7..0b3f0a07801 100755 --- a/versioned_docs/version-7.x/tab-actions.md +++ b/versioned_docs/version-7.x/tab-actions.md @@ -18,10 +18,7 @@ The `jumpTo` action can be used to jump to an existing route in the tab navigato - `name` - _string_ - Name of the route to jump to. - `params` - _object_ - Screen params to pass to the destination route. - - - -```js name="Tab Actions - jumpTo" snack +```js name="Tab Actions - jumpTo" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { Button } from '@react-navigation/elements'; @@ -63,78 +60,16 @@ function ProfileScreen({ route }) { ); } -const Tab = createBottomTabNavigator({ +const MyTabs = createBottomTabNavigator({ screens: { Home: HomeScreen, Profile: ProfileScreen, }, }); -const Navigation = createStaticNavigation(Tab); +const Navigation = createStaticNavigation(MyTabs); export default function App() { return ; } ``` - - - - -```js name="Tab Actions - jumpTo" snack -import * as React from 'react'; -import { View, Text } from 'react-native'; -import { Button } from '@react-navigation/elements'; -import { - NavigationContainer, - TabActions, - useNavigation, -} from '@react-navigation/native'; -import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; - -// codeblock-focus-start -function HomeScreen() { - const navigation = useNavigation(); - // highlight-next-line - const jumpToAction = TabActions.jumpTo('Profile', { user: 'Satya' }); - - return ( - - Home! - - - ); -} -// codeblock-focus-end - -function ProfileScreen({ route }) { - return ( - - Profile! - {route?.params?.user ? route.params.user : 'Noone'}'s profile - - ); -} - -const Tab = createBottomTabNavigator(); - -export default function App() { - return ( - - - - - - - ); -} -``` - - - diff --git a/versioned_docs/version-7.x/tab-view.md b/versioned_docs/version-7.x/tab-view.md index d30320ea3c9..b1e13b3d2c2 100644 --- a/versioned_docs/version-7.x/tab-view.md +++ b/versioned_docs/version-7.x/tab-view.md @@ -22,21 +22,34 @@ To use this package, open a Terminal in the project root and run: npm install react-native-tab-view ``` -Next, install [`react-native-pager-view`](https://github.com/callstack/react-native-viewpager) if you plan to support iOS and Android. +The library depends on [`react-native-pager-view`](https://github.com/callstack/react-native-pager-view) for rendering the pages. -If you are using Expo, to ensure that you get the compatible versions of the libraries, run: + + + +If you have a Expo managed project, in your project directory, run: ```bash -expo install react-native-pager-view +npx expo install react-native-pager-view ``` -If you are not using Expo, run the following: + + + +If you have a bare React Native project, in your project directory, run: ```bash npm2yarn npm install react-native-pager-view ``` -We're done! Now you can build and run the app on your device/simulator. + + + +If you're on a Mac and developing for iOS, you also need to install [pods](https://cocoapods.org/) to complete the linking. + +```bash +npx pod-install ios +``` ## Quick start diff --git a/versioned_docs/version-7.x/testing.md b/versioned_docs/version-7.x/testing.md index 07b86a0e892..a39c33b1fdb 100644 --- a/versioned_docs/version-7.x/testing.md +++ b/versioned_docs/version-7.x/testing.md @@ -1,115 +1,957 @@ --- id: testing -title: Testing with Jest -sidebar_label: Testing with Jest +title: Writing tests +sidebar_label: Writing tests --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Testing code using React Navigation may require some setup since we need to mock native dependencies used in the navigators. We recommend using [Jest](https://jestjs.io) to write unit tests. +React Navigation components can be tested in a similar way to other React components. This guide will cover how to write tests for components using React Navigation using [Jest](https://jestjs.io). -## Mocking native modules +## Guiding principles + +When writing tests, it's encouraged to write tests that closely resemble how users interact with your app. Keeping this in mind, here are some guiding principles to follow: + +- **Test the result, not the action**: Instead of checking if a specific navigation action was called, check if the expected components are rendered after navigation. +- **Avoid mocking React Navigation**: Mocking React Navigation components can lead to tests that don't match the actual logic. Instead, use a real navigator in your tests. + +Following these principles will help you write tests that are more reliable and easier to maintain by avoiding testing implementation details. + +## Setting up Jest + +### Compiling React Navigation + +React Navigation ships [ES modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). However, Jest does not support ES modules natively. + +It's necessary to transform the code to CommonJS to use them in tests. The `react-native` preset for Jest does not transform the code in `node_modules` by default. To enable this, you need to add the [`transformIgnorePatterns`](https://jestjs.io/docs/configuration#transformignorepatterns-arraystring) option in your Jest configuration where you can specify a regexp pattern. To compile React Navigation packages, you can add `@react-navigation` to the regexp. + +This is usually done in a `jest.config.js` file or the `jest` key in `package.json`: + +```diff lang=json +{ + "preset": "react-native", ++ "transformIgnorePatterns": [ ++ "node_modules/(?!(@react-native|react-native|@react-navigation)/)" ++ ] +} +``` + +### Mocking native dependencies To be able to test React Navigation components, certain dependencies will need to be mocked depending on which components are being used. -If you're using `@react-navigation/drawer`, you will need to mock: +If you're using `@react-navigation/stack`, you will need to mock: -- `react-native-reanimated` - `react-native-gesture-handler` -If you're using `@react-navigation/stack`, you will only need to mock: +If you're using `@react-navigation/drawer`, you will need to mock: +- `react-native-reanimated` - `react-native-gesture-handler` To add the mocks, create a file `jest/setup.js` (or any other file name of your choice) and paste the following code in it: ```js -// include this line for mocking react-native-gesture-handler +// Include this line for mocking react-native-gesture-handler import 'react-native-gesture-handler/jestSetup'; -// include this section and the NativeAnimatedHelper section for mocking react-native-reanimated -jest.mock('react-native-reanimated', () => { - const Reanimated = require('react-native-reanimated/mock'); - - // The mock for `call` immediately calls the callback which is incorrect - // So we override it with a no-op - Reanimated.default.call = () => {}; +// Include this section for mocking react-native-reanimated +import { setUpTests } from 'react-native-reanimated'; - return Reanimated; -}); +setUpTests(); // Silence the warning: Animated: `useNativeDriver` is not supported because the native animated module is missing +import { jest } from '@jest/globals'; + jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper'); ``` -Then we need to use this setup file in our jest config. You can add it under `setupFiles` option in a `jest.config.js` file or the `jest` key in `package.json`: +Then we need to use this setup file in our jest config. You can add it under [`setupFilesAfterEnv`](https://jestjs.io/docs/configuration#setupfilesafterenv-array) option in a `jest.config.js` file or the `jest` key in `package.json`: -```json +```diff lang=json { "preset": "react-native", - "setupFiles": ["/jest/setup.js"] + "transformIgnorePatterns": [ + "node_modules/(?!(@react-native|react-native|@react-navigation)/)" + ], ++ "setupFilesAfterEnv": ["/jest/setup.js"] } ``` -Make sure that the path to the file in `setupFiles` is correct. Jest will run these files before running your tests, so it's the best place to put your global mocks. +Jest will run the files specified in `setupFilesAfterEnv` before running your tests, so it's a good place to put your global mocks. + +
+Mocking `react-native-screens` + +This shouldn't be necessary in most cases. However, if you find yourself in a need to mock `react-native-screens` component for some reason, you should do it by adding following code in `jest/setup.js` file: + +```js +// Include this section for mocking react-native-screens +jest.mock('react-native-screens', () => { + // Require actual module instead of a mock + let screens = jest.requireActual('react-native-screens'); + + // All exports in react-native-screens are getters + // We cannot use spread for cloning as it will call the getters + // So we need to clone it with Object.create + screens = Object.create( + Object.getPrototypeOf(screens), + Object.getOwnPropertyDescriptors(screens) + ); + + // Add mock of the component you need + // Here is the example of mocking the Screen component as a View + Object.defineProperty(screens, 'Screen', { + value: require('react-native').View, + }); + + return screens; +}); +``` + +
If you're not using Jest, then you'll need to mock these modules according to the test framework you are using. -## Writing tests +## Fake timers + +When writing tests containing navigation with animations, you need to wait until the animations finish. In such cases, we recommend using [`Fake Timers`](https://jestjs.io/docs/timer-mocks) to simulate the passage of time in your tests. This can be done by adding the following line at the beginning of your test file: + +```js +jest.useFakeTimers(); +``` + +Fake timers replace real implementation of the native timer functions (e.g. `setTimeout()`, `setInterval()` etc,) with a custom implementation that uses a fake clock. This lets you instantly skip animations and reduce the time needed to run your tests by calling methods such as `jest.runAllTimers()`. + +Often, component state is updated after an animation completes. To avoid getting an error in such cases, wrap `jest.runAllTimers()` in `act`: + +```js +import { act } from 'react-test-renderer'; + +// ... + +act(() => jest.runAllTimers()); +``` + +See the examples below for more details on how to use fake timers in tests involving navigation. + +## Navigation and visibility + +In React Navigation, the previous screen is not unmounted when navigating to a new screen. This means that the previous screen is still present in the component tree, but it's not visible. + +When writing tests, you should assert that the expected component is visible or hidden instead of checking if it's rendered or not. React Native Testing Library provides a `toBeVisible` matcher that can be used to check if an element is visible to the user. + +```js +expect(screen.getByText('Settings screen')).toBeVisible(); +``` + +This is in contrast to the `toBeOnTheScreen` matcher, which checks if the element is rendered in the component tree. This matcher is not recommended when writing tests involving navigation. + +By default, the queries from React Native Testing Library (e.g. `getByRole`, `getByText`, `getByLabelText` etc.) [only return visible elements](https://callstack.github.io/react-native-testing-library/docs/api/queries#includehiddenelements-option). So you don't need to do anything special. However, if you're using a different library for your tests, you'll need to account for this behavior. + +## Example tests + +We recommend using [React Native Testing Library](https://callstack.github.io/react-native-testing-library/) to write your tests. + +In this guide, we will go through some example scenarios and show you how to write tests for them using Jest and React Native Testing Library: + +### Navigation between tabs + +In this example, we have a bottom tab navigator with two tabs: Home and Settings. We will write a test that asserts that we can navigate between these tabs by pressing the tab bar buttons. + + + + +```js title="MyTabs.js" +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Text, View } from 'react-native'; + +const HomeScreen = () => { + return ( + + Home screen + + ); +}; + +const SettingsScreen = () => { + return ( + + Settings screen + + ); +}; + +export const MyTabs = createBottomTabNavigator({ + screens: { + Home: HomeScreen, + Settings: SettingsScreen, + }, +}); +``` + + + + +```js title="MyTabs.js" +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Text, View } from 'react-native'; + +const HomeScreen = () => { + return ( + + Home screen + + ); +}; + +const SettingsScreen = () => { + return ( + + Settings screen + + ); +}; + +const Tab = createBottomTabNavigator(); + +export const MyTabs = () => { + return ( + + + + + ); +}; +``` + + + + + + + +```js title="MyTabs.test.js" +import { expect, jest, test } from '@jest/globals'; +import { createStaticNavigation } from '@react-navigation/native'; +import { act, render, screen, userEvent } from '@testing-library/react-native'; + +import { MyTabs } from './MyTabs'; -We recommend using [React Native Testing Library](https://callstack.github.io/react-native-testing-library/) along with [`jest-native`](https://github.com/testing-library/jest-native) to write your tests. +jest.useFakeTimers(); -Example: +test('navigates to settings by tab bar button press', async () => { + const user = userEvent.setup(); - + const Navigation = createStaticNavigation(MyTabs); + + render(); + + const button = screen.getByRole('button', { name: 'Settings, tab, 2 of 2' }); + + await user.press(button); + + act(() => jest.runAllTimers()); + + expect(screen.getByText('Settings screen')).toBeVisible(); +}); +``` + + + + +```js title="MyTabs.test.js" +import { expect, jest, test } from '@jest/globals'; +import { NavigationContainer } from '@react-navigation/native'; +import { act, render, screen, userEvent } from '@testing-library/react-native'; + +import { MyTabs } from './MyTabs'; + +jest.useFakeTimers(); + +test('navigates to settings by tab bar button press', async () => { + const user = userEvent.setup(); + + render( + + + + ); + + const button = screen.getByLabelText('Settings, tab, 2 of 2'); + + await user.press(button); + + act(() => jest.runAllTimers()); + + expect(screen.getByText('Settings screen')).toBeVisible(); +}); +``` + + + + +In the above test, we: + +- Render the `MyTabs` navigator within a [NavigationContainer](navigation-container.md) in our test. +- Get the tab bar button using the `getByLabelText` query that matches its accessibility label. +- Press the button using `userEvent.press(button)` to simulate a user interaction. +- Run all timers using `jest.runAllTimers()` to skip animations (e.g. animations in the `Pressable` for the button). +- Assert that the `Settings screen` is visible after the navigation. + +### Reacting to a navigation event + +In this example, we have a stack navigator with two screens: Home and Surprise. We will write a test that asserts that the text "Surprise!" is displayed after navigating to the Surprise screen. + + + + +```js title="MyStack.js" +import { useNavigation } from '@react-navigation/native'; +import { createStackNavigator } from '@react-navigation/stack'; +import { Button, Text, View } from 'react-native'; +import { useEffect, useState } from 'react'; + +const HomeScreen = () => { + const navigation = useNavigation(); + + return ( + + Home screen + - - ); -} - -function ProfileScreen({ navigation }) { - const isFirstRoute = useIsFirstRouteInParent(); - const previousRouteName = usePreviousRouteName(); - return ( - - It is {isFirstRoute ? '' : 'not '}first route in navigator - Previous route name: {previousRouteName} - - - - ); -} - -function SettingsScreen({ navigation }) { - const isFirstRoute = useIsFirstRouteInParent(); - const previousRouteName = usePreviousRouteName(); - return ( - - It is {isFirstRoute ? '' : 'not '}first route in navigator - Previous route name: {previousRouteName} - - - ); -} - -const Stack = createNativeStackNavigator(); - -function MyStack() { - return ( - - - - - - ); -} - -export default function App() { - return ( - - - - ); -} -``` - - - - So when do you use `navigation.getState()`? It's mostly useful within event listeners where you don't care about what's rendered. In most cases, using the hook should be preferred. ## Using with class component diff --git a/versioned_docs/version-7.x/use-navigation.md b/versioned_docs/version-7.x/use-navigation.md index b0a6e35396e..03c019158df 100755 --- a/versioned_docs/version-7.x/use-navigation.md +++ b/versioned_docs/version-7.x/use-navigation.md @@ -11,10 +11,7 @@ import TabItem from '@theme/TabItem'; The `useNavigation` hook returns the `navigation` object of the screen where it's used: - - - -```js name="useNavigation hook" snack +```js name="useNavigation hook" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { Button } from '@react-navigation/elements'; @@ -61,7 +58,7 @@ function ProfileScreen() { ); } -const Stack = createNativeStackNavigator({ +const RootStack = createNativeStackNavigator({ initialRouteName: 'Home', screens: { Home: HomeScreen, @@ -69,7 +66,7 @@ const Stack = createNativeStackNavigator({ }, }); -const Navigation = createStaticNavigation(Stack); +const Navigation = createStaticNavigation(RootStack); function App() { return ; @@ -78,71 +75,6 @@ function App() { export default App; ``` - - - -```js name="useNavigation hook" snack -import * as React from 'react'; -import { View, Text } from 'react-native'; -import { Button } from '@react-navigation/elements'; -import { NavigationContainer } from '@react-navigation/native'; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; -// codeblock-focus-start -import { useNavigation } from '@react-navigation/native'; - -function MyBackButton() { - // highlight-next-line - const navigation = useNavigation(); - - return ( - - ); -} -// codeblock-focus-end - -function HomeScreen({ navigation: { navigate } }) { - return ( - - This is the home screen of the app - - - ); -} - -function ProfileScreen() { - return ( - - Profile Screen - - - ); -} - -const Stack = createNativeStackNavigator(); - -function App() { - return ( - - - - - - - ); -} - -export default App; -``` - - - - Check how to setup `useNavigation` with TypeScript [here](typescript.md#annotating-usenavigation). See the documentation for the [`navigation` object](navigation-object.md) for more info. diff --git a/versioned_docs/version-7.x/use-prevent-remove.md b/versioned_docs/version-7.x/use-prevent-remove.md index d13b5af31f3..a37168eb9c6 100644 --- a/versioned_docs/version-7.x/use-prevent-remove.md +++ b/versioned_docs/version-7.x/use-prevent-remove.md @@ -18,10 +18,7 @@ The callback receives a `data` object with the `action` that triggered the remov Example: - - - -```js name="usePreventRemove hook" snack +```js name="usePreventRemove hook" snack static2dynamic import * as React from 'react'; import { Alert, View, TextInput, Platform, StyleSheet } from 'react-native'; import { @@ -127,126 +124,6 @@ const styles = StyleSheet.create({ }); ``` - - - -```js name="usePreventRemove hook" snack -import * as React from 'react'; -import { Alert, View, TextInput, Platform, StyleSheet } from 'react-native'; -import { - NavigationContainer, - useNavigation, - usePreventRemove, -} from '@react-navigation/native'; -import { createStackNavigator } from '@react-navigation/stack'; -import { Button } from '@react-navigation/elements'; - -// codeblock-focus-start -const EditTextScreen = () => { - const navigation = useNavigation(); - const [text, setText] = React.useState(''); - - const hasUnsavedChanges = Boolean(text); - - usePreventRemove(hasUnsavedChanges, ({ data }) => { - if (Platform.OS === 'web') { - // Alert is not supported on web, so we can use confirm - const discard = confirm( - 'You have unsaved changes. Discard them and leave the screen?' - ); - - if (discard) { - navigation.dispatch(data.action); - } - } else { - // Prompt the user before leaving the screen - Alert.alert( - 'Discard changes?', - 'You have unsaved changes. Discard them and leave the screen?', - [ - { - text: "Don't leave", - style: 'cancel', - onPress: () => { - // Do nothingP - }, - }, - { - text: 'Discard', - style: 'destructive', - onPress: () => navigation.dispatch(data.action), - }, - ] - ); - } - }); - - return ( - - - - ); -}; -// codeblock-focus-end - -const HomeScreen = () => { - const navigation = useNavigation(); - - return ( - - - - ); -}; - -const Stack = createStackNavigator(); - -export default function App() { - return ( - - - - - - - ); -} - -const styles = StyleSheet.create({ - content: { - flex: 1, - padding: 16, - }, - input: { - margin: 8, - padding: 10, - borderRadius: 3, - borderWidth: StyleSheet.hairlineWidth, - borderColor: 'rgba(0, 0, 0, 0.08)', - backgroundColor: 'white', - }, - buttons: { - flex: 1, - justifyContent: 'center', - padding: 8, - }, - button: { - margin: 8, - }, -}); -``` - - - - diff --git a/versioned_docs/version-7.x/use-route-path.md b/versioned_docs/version-7.x/use-route-path.md new file mode 100644 index 00000000000..530bc324cb7 --- /dev/null +++ b/versioned_docs/version-7.x/use-route-path.md @@ -0,0 +1,25 @@ +--- +id: use-route-path +title: useRoutePath +sidebar_label: useRoutePath +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The `useRoutePath` hook can be used to get the path of a route based on the [`linking` configuration](configuring-links.md). This can be useful if you need to generate a URL for a specific route in your app to share as a deep link. + +## Example + +```js +import { useRoutePath } from '@react-navigation/native'; + +function MyComponent() { + const path = useRoutePath(); + + // Construct a URL using the path and app's base URL + const url = new URL(path, 'https://example.com'); + + return Shareable URL: {url.href}; +} +``` diff --git a/versioned_docs/version-7.x/use-route.md b/versioned_docs/version-7.x/use-route.md index 05cba0813e8..0a94305c5e2 100755 --- a/versioned_docs/version-7.x/use-route.md +++ b/versioned_docs/version-7.x/use-route.md @@ -13,10 +13,7 @@ import TabItem from '@theme/TabItem'; ## Example - - - -```js name="useRoute hook" snack +```js name="useRoute hook" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { Button } from '@react-navigation/elements'; @@ -62,7 +59,7 @@ function ProfileScreen() { ); } -const Stack = createNativeStackNavigator({ +const RootStack = createNativeStackNavigator({ initialRouteName: 'Home', screens: { Home: HomeScreen, @@ -70,7 +67,7 @@ const Stack = createNativeStackNavigator({ }, }); -const Navigation = createStaticNavigation(Stack); +const Navigation = createStaticNavigation(RootStack); function App() { return ; @@ -79,69 +76,6 @@ function App() { export default App; ``` - - - -```js name="useRoute hook" snack -import * as React from 'react'; -import { View, Text } from 'react-native'; -import { Button } from '@react-navigation/elements'; -import { NavigationContainer, useNavigation } from '@react-navigation/native'; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; -// codeblock-focus-start -import { useRoute } from '@react-navigation/native'; - -function MyText() { - // highlight-next-line - const route = useRoute(); - - return {route.params.caption}; -} -// codeblock-focus-end - -function HomeScreen({ navigation }) { - return ( - - This is the home screen of the app - - - ); -} - -function ProfileScreen() { - return ( - - Profile Screen - - - ); -} - -const Stack = createNativeStackNavigator(); - -function App() { - return ( - - - - - - - ); -} - -export default App; -``` - - - - Check how to setup `useRoute` with TypeScript [here](typescript.md#annotating-useroute). See the documentation for the [`route` object](route-object.md) for more info. diff --git a/versioned_docs/version-7.x/use-scroll-to-top.md b/versioned_docs/version-7.x/use-scroll-to-top.md index 02e89c808bf..9c714c15cbf 100755 --- a/versioned_docs/version-7.x/use-scroll-to-top.md +++ b/versioned_docs/version-7.x/use-scroll-to-top.md @@ -13,10 +13,7 @@ In order to achieve it we export `useScrollToTop` which accept ref to scrollable Example: - - - -```js name="useScrollToTop hook" snack +```js name="useScrollToTop hook" snack static2dynamic import * as React from 'react'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { createStaticNavigation } from '@react-navigation/native'; @@ -65,87 +62,20 @@ function HomeScreen() { return ; } -const Tab = createBottomTabNavigator({ - Home: HomeScreen, - Albums: Albums, +const MyTabs = createBottomTabNavigator({ + screens: { + Home: HomeScreen, + Albums: Albums, + }, }); -const Navigation = createStaticNavigation(Tab); +const Navigation = createStaticNavigation(MyTabs); export default function App() { return ; } ``` - - - -```js name="useScrollToTop hook" snack -import * as React from 'react'; -import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; -import { View, Image } from 'react-native'; -import { NavigationContainer } from '@react-navigation/native'; -// codeblock-focus-start -import { ScrollView } from 'react-native'; -import { useScrollToTop } from '@react-navigation/native'; - -function Albums() { - const ref = React.useRef(null); - - // highlight-next-line - useScrollToTop(ref); - - return ( - - {/* content */} - // codeblock-focus-end - - - - - // codeblock-focus-start - - ); -} -// codeblock-focus-end - -function HomeScreen() { - return ; -} - -const Tab = createBottomTabNavigator(); - -export default function App() { - return ( - - - - - - - ); -} -``` - - - - ## Using with class component You can wrap your class component in a function component to use the hook: @@ -171,10 +101,7 @@ export default function (props) { If you require offset to scroll position you can wrap and decorate passed reference: - - - -```js name="useScrollToTop hook - providing scroll offset" snack +```js name="useScrollToTop hook - providing scroll offset" snack static2dynamic import * as React from 'react'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { View, Image } from 'react-native'; @@ -227,88 +154,16 @@ function HomeScreen() { return ; } -const Tab = createBottomTabNavigator({ +const MyTab = createBottomTabNavigator({ screens: { Home: HomeScreen, Albums: Albums, }, }); -const Navigation = createStaticNavigation(Tab); +const Navigation = createStaticNavigation(MyTab); export default function App() { return ; } ``` - - - - -```js name="useScrollToTop hook - providing scroll offset" snack -import * as React from 'react'; -import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; -import { View, Image } from 'react-native'; -import { NavigationContainer } from '@react-navigation/native'; -// codeblock-focus-start -import { ScrollView } from 'react-native'; -import { useScrollToTop } from '@react-navigation/native'; - -function Albums() { - const ref = React.useRef(null); - - useScrollToTop( - React.useRef({ - scrollToTop: () => ref.current?.scrollTo({ y: 100 }), - }) - ); - - return ( - - {/* content */} - // codeblock-focus-end - - - - - // codeblock-focus-start - - ); -} -// codeblock-focus-end - -function HomeScreen() { - return ; -} - -const Tab = createBottomTabNavigator(); - -export default function App() { - return ( - - - - - - - ); -} -``` - - - diff --git a/versioned_docs/version-7.x/use-theme.md b/versioned_docs/version-7.x/use-theme.md index 13df9b4640f..d4b4b374f2e 100644 --- a/versioned_docs/version-7.x/use-theme.md +++ b/versioned_docs/version-7.x/use-theme.md @@ -9,10 +9,7 @@ import TabItem from '@theme/TabItem'; The `useTheme` hook lets us access the currently active theme. You can use it in your own components to have them respond to changes in the theme. - - - -```js name="useTheme hook" snack +```js name="useTheme hook" snack static2dynamic import * as React from 'react'; import { useNavigation, @@ -99,7 +96,7 @@ const PanelStack = createNativeStackNavigator({ }, }); -const Drawer = createDrawerNavigator({ +const MyDrawer = createDrawerNavigator({ initialRouteName: 'Panel', screens: { Home: HomeScreen, @@ -112,7 +109,7 @@ const Drawer = createDrawerNavigator({ }, }); -const Navigation = createStaticNavigation(Drawer); +const Navigation = createStaticNavigation(MyDrawer); export default function App() { const scheme = useColorScheme(); @@ -120,120 +117,6 @@ export default function App() { } ``` - - - -```js name="useTheme hook" snack -import * as React from 'react'; -import { View, Text, TouchableOpacity, useColorScheme } from 'react-native'; -import { - NavigationContainer, - DefaultTheme, - DarkTheme, - useNavigation, -} from '@react-navigation/native'; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import { createDrawerNavigator } from '@react-navigation/drawer'; -import { Button } from '@react-navigation/elements'; -// codeblock-focus-start -import { useTheme } from '@react-navigation/native'; - -// codeblock-focus-end -function SettingsScreen({ route }) { - const navigation = useNavigation(); - const { user } = route.params; - const { colors } = useTheme(); - - return ( - - Settings Screen - - userParam: {JSON.stringify(user)} - - - - ); -} -function ProfileScreen() { - const { colors } = useTheme(); - - return ( - - Profile Screen - - ); -} - -// codeblock-focus-start -function MyButton() { - // highlight-next-line - const { colors } = useTheme(); - - return ( - - Button! - - ); -} -// codeblock-focus-end - -function HomeScreen() { - const navigation = useNavigation(); - const { colors } = useTheme(); - - return ( - - Home Screen - - - - ); -} - -const Drawer = createDrawerNavigator(); -const Stack = createNativeStackNavigator(); - -function Root() { - return ( - - - - - ); -} - -export default function App() { - const scheme = useColorScheme(); - - return ( - - - - - - - ); -} -``` - - - - See [theming guide](themes.md) for more details and usage guide around how to configure themes. ## Using with class component diff --git a/versioned_docs/version-7.x/web-support.md b/versioned_docs/version-7.x/web-support.md index 66b0a3d0882..2bb89461c8e 100755 --- a/versioned_docs/version-7.x/web-support.md +++ b/versioned_docs/version-7.x/web-support.md @@ -25,6 +25,12 @@ While Web support works out of the box, there are some things to configure to en Currently, React Navigation works best with fully client-side rendered apps. However, minimal server-side rendering support is available. So you can optionally choose to server render your app. +4. **Adapt to web-specific behavior** + + Depending on your app's requirements and design, you may also want to tweak some of the navigators' behavior on the web. For example: + - Change `backBehavior` to `fullHistory` for [tabs](bottom-tab-navigator.md#backbehavior) and [drawer](drawer-navigator.md#backbehavior) on the web to always push a new entry to the browser history. + - Use sidebars on larger screens instead of [bottom tabs](bottom-tab-navigator.md#tabbarposition) - while not specific to web, responsive design much more important on the web. + :::note In React Navigation 4, it was necessary to install a separate package called `@react-navigation/web` to use web integration. This package is no longer needed in recent versions of React Navigation. If you have it installed, make sure to uninstall it to avoid conflicts. diff --git a/versioned_sidebars/version-7.x-sidebars.json b/versioned_sidebars/version-7.x-sidebars.json index d15b206605b..2467400dbfe 100644 --- a/versioned_sidebars/version-7.x-sidebars.json +++ b/versioned_sidebars/version-7.x-sidebars.json @@ -42,6 +42,7 @@ "stack-navigator", "native-stack-navigator", "bottom-tab-navigator", + "native-bottom-tab-navigator", "drawer-navigator", "material-top-tab-navigator" ], @@ -71,6 +72,7 @@ "use-focus-effect", "use-is-focused", "use-prevent-remove", + "use-route-path", "use-link-to", "use-link-props", "use-link-builder", @@ -91,12 +93,14 @@ } ], "Build your own Navigator": ["custom-routers", "custom-navigators"], - "Additional resources": [ - "migration-guides", - "navigation-solutions-and-community-libraries", + "Ecosystem": [ + "community-solutions", + "community-navigators", + "community-libraries", "more-resources" ], "Meta": [ + "migration-guides", "glossary-of-terms", "pitch", "limitations", diff --git a/yarn.lock b/yarn.lock index 99dfa2460c5..3259bd9475a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -454,6 +454,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 8bda3448e07b5583727c103560bcf9c4c24b3c1051a4c516d4050ef69df37bb9a4734a585fe12725b8c2763de0a265aa1e909b485a4e3270b7cfd3e4dbe4b602 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.22.20": version: 7.22.20 resolution: "@babel/helper-validator-identifier@npm:7.22.20" @@ -468,6 +475,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847 + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.25.9": version: 7.25.9 resolution: "@babel/helper-validator-option@npm:7.25.9" @@ -1520,6 +1534,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/types@npm:7.28.5" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.28.5" + checksum: a5a483d2100befbf125793640dec26b90b95fd233a94c19573325898a5ce1e52cdfa96e495c7dcc31b5eca5b66ce3e6d4a0f5a4a62daec271455959f208ab08a + languageName: node + linkType: hard + "@babel/types@npm:^7.4.4": version: 7.23.4 resolution: "@babel/types@npm:7.23.4" @@ -2178,6 +2202,95 @@ __metadata: languageName: node linkType: hard +"@ffprobe-installer/darwin-arm64@npm:5.0.1": + version: 5.0.1 + resolution: "@ffprobe-installer/darwin-arm64@npm:5.0.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@ffprobe-installer/darwin-x64@npm:5.1.0": + version: 5.1.0 + resolution: "@ffprobe-installer/darwin-x64@npm:5.1.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@ffprobe-installer/ffprobe@npm:^2.1.2": + version: 2.1.2 + resolution: "@ffprobe-installer/ffprobe@npm:2.1.2" + dependencies: + "@ffprobe-installer/darwin-arm64": "npm:5.0.1" + "@ffprobe-installer/darwin-x64": "npm:5.1.0" + "@ffprobe-installer/linux-arm": "npm:5.2.0" + "@ffprobe-installer/linux-arm64": "npm:5.2.0" + "@ffprobe-installer/linux-ia32": "npm:5.2.0" + "@ffprobe-installer/linux-x64": "npm:5.2.0" + "@ffprobe-installer/win32-ia32": "npm:5.1.0" + "@ffprobe-installer/win32-x64": "npm:5.1.0" + dependenciesMeta: + "@ffprobe-installer/darwin-arm64": + optional: true + "@ffprobe-installer/darwin-x64": + optional: true + "@ffprobe-installer/linux-arm": + optional: true + "@ffprobe-installer/linux-arm64": + optional: true + "@ffprobe-installer/linux-ia32": + optional: true + "@ffprobe-installer/linux-x64": + optional: true + "@ffprobe-installer/win32-ia32": + optional: true + "@ffprobe-installer/win32-x64": + optional: true + checksum: d3a0e472c9a31bffe10facf6ee0ce4520b4df13d1cf3fdcf7e6ead367f895a348633481c6010c0d9f30a950fe89f791aade9755360047c8dd48d098dccd8414b + languageName: node + linkType: hard + +"@ffprobe-installer/linux-arm64@npm:5.2.0": + version: 5.2.0 + resolution: "@ffprobe-installer/linux-arm64@npm:5.2.0" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@ffprobe-installer/linux-arm@npm:5.2.0": + version: 5.2.0 + resolution: "@ffprobe-installer/linux-arm@npm:5.2.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@ffprobe-installer/linux-ia32@npm:5.2.0": + version: 5.2.0 + resolution: "@ffprobe-installer/linux-ia32@npm:5.2.0" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@ffprobe-installer/linux-x64@npm:5.2.0": + version: 5.2.0 + resolution: "@ffprobe-installer/linux-x64@npm:5.2.0" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@ffprobe-installer/win32-ia32@npm:5.1.0": + version: 5.1.0 + resolution: "@ffprobe-installer/win32-ia32@npm:5.1.0" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@ffprobe-installer/win32-x64@npm:5.1.0": + version: 5.1.0 + resolution: "@ffprobe-installer/win32-x64@npm:5.1.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@hapi/hoek@npm:^9.0.0": version: 9.3.0 resolution: "@hapi/hoek@npm:9.3.0" @@ -4056,6 +4169,15 @@ __metadata: languageName: node linkType: hard +"ast-types@npm:^0.16.1": + version: 0.16.1 + resolution: "ast-types@npm:0.16.1" + dependencies: + tslib: "npm:^2.0.1" + checksum: abcc49e42eb921a7ebc013d5bec1154651fb6dbc3f497541d488859e681256901b2990b954d530ba0da4d0851271d484f7057d5eff5e07cb73e8b10909f711bf + languageName: node + linkType: hard + "astring@npm:^1.8.0": version: 1.8.6 resolution: "astring@npm:1.8.6" @@ -5689,7 +5811,7 @@ __metadata: languageName: node linkType: hard -"esprima@npm:^4.0.0": +"esprima@npm:^4.0.0, esprima@npm:~4.0.0": version: 4.0.1 resolution: "esprima@npm:4.0.1" bin: @@ -10109,12 +10231,12 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^3.3.3": - version: 3.3.3 - resolution: "prettier@npm:3.3.3" +"prettier@npm:^3.6.2": + version: 3.6.2 + resolution: "prettier@npm:3.6.2" bin: prettier: bin/prettier.cjs - checksum: b85828b08e7505716324e4245549b9205c0cacb25342a030ba8885aba2039a115dbcf75a0b7ca3b37bc9d101ee61fab8113fc69ca3359f2a226f1ecc07ad2e26 + checksum: 488cb2f2b99ec13da1e50074912870217c11edaddedeadc649b1244c749d15ba94e846423d062e2c4c9ae683e2d65f754de28889ba06e697ac4f988d44f45812 languageName: node linkType: hard @@ -10477,12 +10599,14 @@ __metadata: version: 0.0.0-use.local resolution: "react-navigation-website-next@workspace:." dependencies: + "@babel/types": "npm:^7.28.5" "@docusaurus/core": "npm:3.6.1" "@docusaurus/faster": "npm:3.6.1" "@docusaurus/plugin-client-redirects": "npm:3.6.1" "@docusaurus/plugin-google-analytics": "npm:3.6.1" "@docusaurus/preset-classic": "npm:3.6.1" "@docusaurus/remark-plugin-npm2yarn": "npm:3.6.1" + "@ffprobe-installer/ffprobe": "npm:^2.1.2" "@octokit/graphql": "npm:^7.1.0" "@react-navigation/core": "npm:^7.0.4" escape-html: "npm:^1.0.3" @@ -10490,11 +10614,12 @@ __metadata: markdownlint-cli2: "npm:^0.14.0" mkdirp: "npm:^3.0.1" netlify-plugin-cache: "npm:^1.0.3" - prettier: "npm:^3.3.3" + prettier: "npm:^3.6.2" prism-react-renderer: "npm:^2.4.0" react: "npm:^18.3.1" react-dom: "npm:^18.3.1" react-simple-code-editor: "npm:^0.14.1" + recast: "npm:^0.23.11" languageName: unknown linkType: soft @@ -10607,6 +10732,19 @@ __metadata: languageName: node linkType: hard +"recast@npm:^0.23.11": + version: 0.23.11 + resolution: "recast@npm:0.23.11" + dependencies: + ast-types: "npm:^0.16.1" + esprima: "npm:~4.0.0" + source-map: "npm:~0.6.1" + tiny-invariant: "npm:^1.3.3" + tslib: "npm:^2.0.1" + checksum: 45b520a8f0868a5a24ecde495be9de3c48e69a54295d82a7331106554b75cfba75d16c909959d056e9ceed47a1be5e061e2db8b9ecbcd6ba44c2f3ef9a47bd18 + languageName: node + linkType: hard + "rechoir@npm:^0.6.2": version: 0.6.2 resolution: "rechoir@npm:0.6.2" @@ -11425,7 +11563,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.6.0, source-map@npm:~0.6.0": +"source-map@npm:^0.6.0, source-map@npm:~0.6.0, source-map@npm:~0.6.1": version: 0.6.1 resolution: "source-map@npm:0.6.1" checksum: ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 @@ -11850,6 +11988,13 @@ __metadata: languageName: node linkType: hard +"tiny-invariant@npm:^1.3.3": + version: 1.3.3 + resolution: "tiny-invariant@npm:1.3.3" + checksum: 65af4a07324b591a059b35269cd696aba21bef2107f29b9f5894d83cc143159a204b299553435b03874ebb5b94d019afa8b8eff241c8a4cfee95872c2e1c1c4a + languageName: node + linkType: hard + "tiny-warning@npm:^1.0.0": version: 1.0.3 resolution: "tiny-warning@npm:1.0.3" @@ -11901,6 +12046,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^2.0.1": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 + languageName: node + linkType: hard + "tslib@npm:^2.0.3, tslib@npm:^2.6.0": version: 2.6.2 resolution: "tslib@npm:2.6.2"