From f77e94139727acdfd2ea744534893ebfdd75133b Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Sat, 6 Dec 2025 22:00:36 +0100 Subject: [PATCH 1/7] Don't show version badge for latest version --- docusaurus.config.js | 5 +++ src/css/custom.css | 94 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 83 insertions(+), 16 deletions(-) diff --git a/docusaurus.config.js b/docusaurus.config.js index d115dc6962..35d9717a15 100755 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -146,6 +146,11 @@ export default { 'https://github.com/react-navigation/react-navigation.github.io/edit/main/', includeCurrentVersion: false, lastVersion: '7.x', + versions: { + '7.x': { + badge: false, + }, + }, breadcrumbs: false, sidebarCollapsed: false, remarkPlugins: [[remarkNpm2Yarn, { sync: true }]], diff --git a/src/css/custom.css b/src/css/custom.css index 271a01d95d..643d78fbc7 100755 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -14,14 +14,18 @@ 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; - + -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', + Arial, sans-serif; + --ifm-heading-font-family: + -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', + Arial, sans-serif; + + --ifm-font-size-base: 100%; + --ifm-line-height-base: 1.75; --ifm-code-font-size: 85%; --ifm-heading-font-weight: 700; - --ifm-heading-line-height: 1.618; + --ifm-heading-line-height: 1.25; --ifm-heading-margin-top: 1.618rem; --ifm-color-primary: hsl( @@ -143,38 +147,82 @@ } h1 { - font-size: 2.618rem; - letter-spacing: -0.02em; + font-family: var(--ifm-heading-font-family); + font-size: 2.75rem; + letter-spacing: -0.022em; margin-top: 0; - margin-left: -0.09rem; + margin-bottom: 0.5rem; + margin-left: -0.05rem; + font-weight: 700; + line-height: 1.15; } h2 { - font-size: 1.618rem; - letter-spacing: -0.02em; - margin-left: -0.09rem; + font-family: var(--ifm-heading-font-family); + font-size: 2rem; + letter-spacing: -0.018em; + margin-top: 2.25rem; + margin-bottom: 0.5rem; + margin-left: -0.04rem; + font-weight: 700; + line-height: 1.22; + border-bottom: none; + padding-bottom: 0; } h3 { - font-size: 1.382rem; - letter-spacing: -0.01em; + font-family: var(--ifm-heading-font-family); + font-size: 1.5rem; + letter-spacing: -0.014em; + margin-top: 2rem; + margin-bottom: 0.5rem; + font-weight: 700; + line-height: 1.3; } h4 { - font-size: 1.145rem; + font-family: var(--ifm-heading-font-family); + font-size: 1.25rem; + letter-spacing: -0.012em; + margin-top: 1.75rem; + margin-bottom: 0.5rem; + font-weight: 700; + line-height: 1.35; } h5 { - font-size: 1rem; + font-family: var(--ifm-heading-font-family); + font-size: 1.125rem; + letter-spacing: -0.01em; + margin-top: 1.5rem; + margin-bottom: 0.5rem; + font-weight: 700; + line-height: 1.4; } h6 { - font-size: 0.85rem; + font-family: var(--ifm-heading-font-family); + font-size: 1rem; + letter-spacing: -0.008em; + margin-top: 1.25rem; + margin-bottom: 0.5rem; + font-weight: 700; + line-height: 1.45; } p { font-size: 1rem; line-height: 1.75; + margin-bottom: 1.5rem; + letter-spacing: -0.003em; +} + +article p { + font-size: 1rem; + line-height: 1.75; + color: var(--ifm-font-color-base); + margin-bottom: 1.5rem; + letter-spacing: -0.003em; } @media (min-width: 90rem) { @@ -459,6 +507,20 @@ article { text-wrap: pretty; } +article > div > * + h2 { + margin-top: 3.5rem; +} + +article > div > * + h3 { + margin-top: 3rem; +} + +article h2 + p, +article h3 + p, +article h4 + p { + margin-top: 1rem; +} + article img, article video:not(.video-player video), .video-player { From b639acca025e36f5f76c6c3d13ca064817c3fbc9 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Sat, 6 Dec 2025 22:08:13 +0100 Subject: [PATCH 2/7] Upgrade docusaurus --- docusaurus.config.js | 21 +- package.json | 28 +- yarn.lock | 4816 +++++++++++++++++++++++++++--------------- 3 files changed, 3196 insertions(+), 1669 deletions(-) diff --git a/docusaurus.config.js b/docusaurus.config.js index 35d9717a15..4cecc4974f 100755 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -4,9 +4,6 @@ import rehypeStaticToDynamic from './src/plugins/rehype-static-to-dynamic.mjs'; import rehypeVideoAspectRatio from './src/plugins/rehype-video-aspect-ratio.mjs'; export default { - future: { - experimental_faster: true, - }, title: 'React Navigation', tagline: 'Routing and navigation for your React Native apps', url: 'https://reactnavigation.org/', @@ -14,13 +11,14 @@ export default { favicon: 'img/favicon.ico', organizationName: 'react-navigation', projectName: 'react-navigation.github.io', + onBrokenLinks: 'throw', onBrokenAnchors: 'throw', - onBrokenMarkdownLinks: 'throw', - scripts: [ - '/js/snack-helpers.js', - '/js/toc-fixes.js', - '/js/video-playback.js', - ], + onDuplicateRoutes: 'throw', + markdown: { + hooks: { + onBrokenMarkdownLinks: 'throw', + }, + }, themeConfig: { colorMode: { defaultMode: 'light', @@ -178,4 +176,9 @@ export default { }, ], ], + scripts: [ + '/js/snack-helpers.js', + '/js/toc-fixes.js', + '/js/video-playback.js', + ], }; diff --git a/package.json b/package.json index dc1b26442d..833fb6dfc0 100755 --- a/package.json +++ b/package.json @@ -14,28 +14,28 @@ "fetch-sponsors": "node scripts/fetch-sponsors.js && prettier --write src/data/sponsors.js" }, "dependencies": { - "@docusaurus/core": "3.6.1", - "@docusaurus/faster": "3.6.1", - "@docusaurus/plugin-client-redirects": "3.6.1", - "@docusaurus/plugin-google-analytics": "3.6.1", - "@docusaurus/preset-classic": "3.6.1", - "@docusaurus/remark-plugin-npm2yarn": "3.6.1", - "@octokit/graphql": "^7.1.0", - "@react-navigation/core": "^7.0.4", + "@docusaurus/core": "3.9.2", + "@docusaurus/faster": "3.9.2", + "@docusaurus/plugin-client-redirects": "3.9.2", + "@docusaurus/plugin-google-analytics": "3.9.2", + "@docusaurus/preset-classic": "3.9.2", + "@docusaurus/remark-plugin-npm2yarn": "3.9.2", + "@octokit/graphql": "^9.0.3", + "@react-navigation/core": "^7.13.5", "escape-html": "^1.0.3", "mkdirp": "^3.0.1", "netlify-plugin-cache": "^1.0.3", - "prism-react-renderer": "^2.4.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "prism-react-renderer": "^2.4.1", + "react": "^19.2.1", + "react-dom": "^19.2.1", "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.6.2", + "markdownlint": "^0.40.0", + "markdownlint-cli2": "^0.19.1", + "prettier": "^3.7.4", "recast": "^0.23.11" }, "resolutions": { diff --git a/yarn.lock b/yarn.lock index 3259bd9475..58a58cd967 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,126 +5,178 @@ __metadata: version: 8 cacheKey: 10c0 -"@algolia/autocomplete-core@npm:1.9.3": - version: 1.9.3 - resolution: "@algolia/autocomplete-core@npm:1.9.3" +"@ai-sdk/gateway@npm:2.0.18": + version: 2.0.18 + resolution: "@ai-sdk/gateway@npm:2.0.18" dependencies: - "@algolia/autocomplete-plugin-algolia-insights": "npm:1.9.3" - "@algolia/autocomplete-shared": "npm:1.9.3" - checksum: a751b20f15c9a30b8b2d5a4f1f62fb4dbd012fb7ffec1b12308d6e7388b5a4dc83af52176634f17facb57a7727204843c5aa2f6e80efafaaf244275f44af11d9 + "@ai-sdk/provider": "npm:2.0.0" + "@ai-sdk/provider-utils": "npm:3.0.18" + "@vercel/oidc": "npm:3.0.5" + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + checksum: 6e51159d58f2e54fbfb7b50ab2be28c890474cf72f00e199e056aa24d5ee092ed6922b452b638cddc6ddcb344b18e7e5deb734acfca8532fa4ce694377f452c3 languageName: node linkType: hard -"@algolia/autocomplete-plugin-algolia-insights@npm:1.9.3": - version: 1.9.3 - resolution: "@algolia/autocomplete-plugin-algolia-insights@npm:1.9.3" +"@ai-sdk/provider-utils@npm:3.0.18": + version: 3.0.18 + resolution: "@ai-sdk/provider-utils@npm:3.0.18" dependencies: - "@algolia/autocomplete-shared": "npm:1.9.3" + "@ai-sdk/provider": "npm:2.0.0" + "@standard-schema/spec": "npm:^1.0.0" + eventsource-parser: "npm:^3.0.6" peerDependencies: - search-insights: ">= 1 < 3" - checksum: 574196f66fe828be1029439032376685020524d6c729dea99caef336cc7be244d2539fa91b3fe80db80efe3420c2c05063cab3534514be6c637bf1914b17a6f6 + zod: ^3.25.76 || ^4.1.8 + checksum: 209c15b0dceef0ba95a7d3de544be0a417ad4a0bd5143496b3966a35fedf144156d93a42ff8c3d7db56781b9836bafc8c132c98978c49240e55bc1a36e18a67f languageName: node linkType: hard -"@algolia/autocomplete-preset-algolia@npm:1.9.3": - version: 1.9.3 - resolution: "@algolia/autocomplete-preset-algolia@npm:1.9.3" +"@ai-sdk/provider@npm:2.0.0": + version: 2.0.0 + resolution: "@ai-sdk/provider@npm:2.0.0" dependencies: - "@algolia/autocomplete-shared": "npm:1.9.3" - peerDependencies: - "@algolia/client-search": ">= 4.9.1 < 6" - algoliasearch: ">= 4.9.1 < 6" - checksum: 38c1872db4dae69b4eec622db940c7a992d8530e33fbac7df593473ef404312076d9933b4a7ea25c2d401ea5b62ebd64b56aa25b5cdd8e8ba3fd309a39d9d816 + json-schema: "npm:^0.4.0" + checksum: e50e520016c9fc0a8b5009cadd47dae2f1c81ec05c1792b9e312d7d15479f024ca8039525813a33425c884e3449019fed21043b1bfabd6a2626152ca9a388199 languageName: node linkType: hard -"@algolia/autocomplete-shared@npm:1.9.3": - version: 1.9.3 - resolution: "@algolia/autocomplete-shared@npm:1.9.3" +"@ai-sdk/react@npm:^2.0.30": + version: 2.0.108 + resolution: "@ai-sdk/react@npm:2.0.108" + dependencies: + "@ai-sdk/provider-utils": "npm:3.0.18" + ai: "npm:5.0.108" + swr: "npm:^2.2.5" + throttleit: "npm:2.1.0" peerDependencies: - "@algolia/client-search": ">= 4.9.1 < 6" - algoliasearch: ">= 4.9.1 < 6" - checksum: 1aa926532c32be6bb5384c8c0ae51a312c9d79ed7486371218dfcb61c8ea1ed46171bdc9f9b596a266aece104a0ef76d6aac2f9a378a5a6eb4460e638d59f6ae + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.25.76 || ^4.1.8 + peerDependenciesMeta: + zod: + optional: true + checksum: bb43713cb60304baf170e04cd7c6800448af35f8e9ccc388aa33838a8c52bb7fc3d91ae33040aa921882cde513f6b03554c72090abf8fdc4d6beb382b60fec17 languageName: node linkType: hard -"@algolia/cache-browser-local-storage@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/cache-browser-local-storage@npm:4.20.0" +"@algolia/abtesting@npm:1.12.0": + version: 1.12.0 + resolution: "@algolia/abtesting@npm:1.12.0" dependencies: - "@algolia/cache-common": "npm:4.20.0" - checksum: efab9b8535d9cf2fc9d1e382541e87797946da0953dc02809aab181161b40ce419b4fa71bc35883e02eb34176185200e0ce1e887109a24233c0e9e6ef5f2a340 + "@algolia/client-common": "npm:5.46.0" + "@algolia/requester-browser-xhr": "npm:5.46.0" + "@algolia/requester-fetch": "npm:5.46.0" + "@algolia/requester-node-http": "npm:5.46.0" + checksum: 72c237a262932fcb642405236bc48cc4264f3b25b77fe944b558401a4cb4ab276bf599e9a5f0e5a69216f658bd7e37047f4f3e7c30a3c03b74534992a75adae1 languageName: node linkType: hard -"@algolia/cache-common@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/cache-common@npm:4.20.0" - checksum: e2e55945dc9d3cafaace828afe28cb944a14efa645d588968e8bf88360efd7dc35f95f2b2392fc84dc697d115ada2a253b34f384ec5d82a191df77eb2125e43e +"@algolia/autocomplete-core@npm:1.19.2": + version: 1.19.2 + resolution: "@algolia/autocomplete-core@npm:1.19.2" + dependencies: + "@algolia/autocomplete-plugin-algolia-insights": "npm:1.19.2" + "@algolia/autocomplete-shared": "npm:1.19.2" + checksum: 383952bc43a31f0771987416c350471824e480fcd15e1db8ae13386cd387879f1c81eadafceffa69f87e6b8e59fb1aa713da375fc07a30c5d8edb16a157b5f45 + languageName: node + linkType: hard + +"@algolia/autocomplete-plugin-algolia-insights@npm:1.19.2": + version: 1.19.2 + resolution: "@algolia/autocomplete-plugin-algolia-insights@npm:1.19.2" + dependencies: + "@algolia/autocomplete-shared": "npm:1.19.2" + peerDependencies: + search-insights: ">= 1 < 3" + checksum: 8548b6514004dbf6fb34d6da176ac911371f3e84724ef6b94600cd84d29339d2f44cead03d7c0d507b130da0d9acc61f6e4c9a0fba6f967a5ae2a42eea93f0c1 + languageName: node + linkType: hard + +"@algolia/autocomplete-shared@npm:1.19.2": + version: 1.19.2 + resolution: "@algolia/autocomplete-shared@npm:1.19.2" + peerDependencies: + "@algolia/client-search": ">= 4.9.1 < 6" + algoliasearch: ">= 4.9.1 < 6" + checksum: eee6615e6d9e6db7727727e442b876a554a6eda6f14c1d55d667ed2d14702c4c888a34b9bfb18f66ccc6d402995b2c7c37ace9f19ce9fc9c83bbb623713efbc4 languageName: node linkType: hard -"@algolia/cache-in-memory@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/cache-in-memory@npm:4.20.0" +"@algolia/client-abtesting@npm:5.46.0": + version: 5.46.0 + resolution: "@algolia/client-abtesting@npm:5.46.0" dependencies: - "@algolia/cache-common": "npm:4.20.0" - checksum: 9dd67c93fb97d0055ec6d487bae922a5ccbf2adcdf2692c737b85d50b5aa2b57f3fb21ead0202a570e7feb1096fb48ea56a2309259998c17d7c82bc8ae170818 + "@algolia/client-common": "npm:5.46.0" + "@algolia/requester-browser-xhr": "npm:5.46.0" + "@algolia/requester-fetch": "npm:5.46.0" + "@algolia/requester-node-http": "npm:5.46.0" + checksum: 88d869ec1933fae5aa4cccfb21ee66a75073060a986ea7cc9b08bd07ecbd88d4b83075ff575df45e67a8db8719912f53e8d4f66d2a4653bf5ed06c0b2a753768 languageName: node linkType: hard -"@algolia/client-account@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/client-account@npm:4.20.0" +"@algolia/client-analytics@npm:5.46.0": + version: 5.46.0 + resolution: "@algolia/client-analytics@npm:5.46.0" dependencies: - "@algolia/client-common": "npm:4.20.0" - "@algolia/client-search": "npm:4.20.0" - "@algolia/transporter": "npm:4.20.0" - checksum: 6ff0cd7834d48988ec748ed504a28e4de8842526881c7d0f875f9702e037f51c9ec70195489e5d189995d0901212f844b0555d6df0b423d0b846b300a321b6a2 + "@algolia/client-common": "npm:5.46.0" + "@algolia/requester-browser-xhr": "npm:5.46.0" + "@algolia/requester-fetch": "npm:5.46.0" + "@algolia/requester-node-http": "npm:5.46.0" + checksum: 96d20a0560c9261520968d7ebcc740118118c7ae56d9a23100d5cc893bbd855ebe2bf4b14ea3e0563b3f54fb2f5f6a8dc35d87f1eb79b81cdc1b17a12ba07fb8 + languageName: node + linkType: hard + +"@algolia/client-common@npm:5.46.0": + version: 5.46.0 + resolution: "@algolia/client-common@npm:5.46.0" + checksum: c6f7c1f5b74b4d5056248f6f53ef59c468b5a7cee9d9edfcf93e32e9e71f4b3f186c5a4b2c6fcae631589ac67fdb50ce8bc577aeb8adf8b8e09d453e868b63f5 languageName: node linkType: hard -"@algolia/client-analytics@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/client-analytics@npm:4.20.0" +"@algolia/client-insights@npm:5.46.0": + version: 5.46.0 + resolution: "@algolia/client-insights@npm:5.46.0" dependencies: - "@algolia/client-common": "npm:4.20.0" - "@algolia/client-search": "npm:4.20.0" - "@algolia/requester-common": "npm:4.20.0" - "@algolia/transporter": "npm:4.20.0" - checksum: ebc20c90461a05c1bfbdf152953904afde749d70fe7008857c4f5a96510f0bb1895420a400decfcb3dce3137be880b4360e8ac09a8249b4fe8426dd0a3042a09 + "@algolia/client-common": "npm:5.46.0" + "@algolia/requester-browser-xhr": "npm:5.46.0" + "@algolia/requester-fetch": "npm:5.46.0" + "@algolia/requester-node-http": "npm:5.46.0" + checksum: f2497813ede9e194c2fe36e745fafd3f8e45066be10a784dbf55f52eb8fefe7fc26013feba21e7a7ade01912056cc85d85247699c003eb4756608576bd549c06 languageName: node linkType: hard -"@algolia/client-common@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/client-common@npm:4.20.0" +"@algolia/client-personalization@npm:5.46.0": + version: 5.46.0 + resolution: "@algolia/client-personalization@npm:5.46.0" dependencies: - "@algolia/requester-common": "npm:4.20.0" - "@algolia/transporter": "npm:4.20.0" - checksum: b0cf4d127dd5712867e3e7dbb10465d960d5ad2ca491321c0d1908c3b4d2f485bc54926c1b3de2b9d5b5f3438a1e91d50f02318fc164770d4e78bd7828a3e2f6 + "@algolia/client-common": "npm:5.46.0" + "@algolia/requester-browser-xhr": "npm:5.46.0" + "@algolia/requester-fetch": "npm:5.46.0" + "@algolia/requester-node-http": "npm:5.46.0" + checksum: 521c0764f921c3eba894df1cfeb2e439cd149c1baff9c06e110dd34628b0bc55961e065bfd309658d5824808d440e5d96faa83e356c639d505286204b92ab19d languageName: node linkType: hard -"@algolia/client-personalization@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/client-personalization@npm:4.20.0" +"@algolia/client-query-suggestions@npm:5.46.0": + version: 5.46.0 + resolution: "@algolia/client-query-suggestions@npm:5.46.0" dependencies: - "@algolia/client-common": "npm:4.20.0" - "@algolia/requester-common": "npm:4.20.0" - "@algolia/transporter": "npm:4.20.0" - checksum: a4ffff168daee0495192c7aa1b155827d10ba408d352d01e112552e048a18244a2a446df47706639dc67a6b4061bdc084f35e47b5a75b0f1a722427abe131fe8 + "@algolia/client-common": "npm:5.46.0" + "@algolia/requester-browser-xhr": "npm:5.46.0" + "@algolia/requester-fetch": "npm:5.46.0" + "@algolia/requester-node-http": "npm:5.46.0" + checksum: d371cdaf97079186f8185015189d6fc7587200f0dc7414c10048d9c62b715e47af61949fbf8737bc44c7d2c293484621347187ea03f797e8ca73b971bbc182b3 languageName: node linkType: hard -"@algolia/client-search@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/client-search@npm:4.20.0" +"@algolia/client-search@npm:5.46.0": + version: 5.46.0 + resolution: "@algolia/client-search@npm:5.46.0" dependencies: - "@algolia/client-common": "npm:4.20.0" - "@algolia/requester-common": "npm:4.20.0" - "@algolia/transporter": "npm:4.20.0" - checksum: 79b75fbfddf41bd65d1d028236b249e81672a5a5aea9e84fa5586ee5d1f0d78966dacdc464109e14a46f6c78c8d68d5a8a50ad7b571247ac64fc2d1399072332 + "@algolia/client-common": "npm:5.46.0" + "@algolia/requester-browser-xhr": "npm:5.46.0" + "@algolia/requester-fetch": "npm:5.46.0" + "@algolia/requester-node-http": "npm:5.46.0" + checksum: 0aa388d5174b05d86582d6d86d1fe184563cfd7c33140147ab5cce275565ac6382562178b5a502acabe8b035295a80aea2e9c25d5dc0221f999db5014ea2f56a languageName: node linkType: hard @@ -135,55 +187,66 @@ __metadata: languageName: node linkType: hard -"@algolia/logger-common@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/logger-common@npm:4.20.0" - checksum: 03fffc0e021097836d4588f130fc22b3fd6ca55e286e8fc59f0525330754bc49edcf7c5d2f0016fd1c4aa3ad3432005ba3a17b5c41880c16258e98bcf4746b4d +"@algolia/ingestion@npm:1.46.0": + version: 1.46.0 + resolution: "@algolia/ingestion@npm:1.46.0" + dependencies: + "@algolia/client-common": "npm:5.46.0" + "@algolia/requester-browser-xhr": "npm:5.46.0" + "@algolia/requester-fetch": "npm:5.46.0" + "@algolia/requester-node-http": "npm:5.46.0" + checksum: 072d31f47895079637b376549a3ff3a81de7576c5578715dabb136da48557cb087af26e041ddc19d05dabd4550d641f7d8e1581cd4565de0d4b3b2e2d169ba4e languageName: node linkType: hard -"@algolia/logger-console@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/logger-console@npm:4.20.0" +"@algolia/monitoring@npm:1.46.0": + version: 1.46.0 + resolution: "@algolia/monitoring@npm:1.46.0" dependencies: - "@algolia/logger-common": "npm:4.20.0" - checksum: 2b1f32f3344613de3ae949df57ef5b794ec247572dfbdd04b2c5d1b250b224fef2ce7292c9ce56f45ef491afe90a7505ec21ad3b00f645ef07516c8cbe200fae + "@algolia/client-common": "npm:5.46.0" + "@algolia/requester-browser-xhr": "npm:5.46.0" + "@algolia/requester-fetch": "npm:5.46.0" + "@algolia/requester-node-http": "npm:5.46.0" + checksum: 9ca69cc53efc58ee5443d706abe8db3cd48974fda53a8c9732cc30ac39ab96e67fb67913b1b6f090a230b17347a6a755ebaf583176eb6195f96ed81459632ed2 languageName: node linkType: hard -"@algolia/requester-browser-xhr@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/requester-browser-xhr@npm:4.20.0" +"@algolia/recommend@npm:5.46.0": + version: 5.46.0 + resolution: "@algolia/recommend@npm:5.46.0" dependencies: - "@algolia/requester-common": "npm:4.20.0" - checksum: 0e3fba53b05805bb9801b3b0e8cc4c95b8f55c6e23f43f4c5f1cd01ce277aa71fb08d935be97f921af0d60d83d23b83e2bbdd35f79bd2657c9aaa8604a838d62 + "@algolia/client-common": "npm:5.46.0" + "@algolia/requester-browser-xhr": "npm:5.46.0" + "@algolia/requester-fetch": "npm:5.46.0" + "@algolia/requester-node-http": "npm:5.46.0" + checksum: 7aa0c91f2cdd061fcd29e1f8821b3832c2c35812d4785b8426196778a697f38cc99efe799b850da1ad22ff81f49edb76cb6399afd1eb43f451aedc7fe09eb1da languageName: node linkType: hard -"@algolia/requester-common@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/requester-common@npm:4.20.0" - checksum: 3d69d781549a58784fdf34d79c46e15a9010185757112953e58f987a814e9645b47a2d7202148fb644edfacc73df783041b274eac58de06936dfc9c6f1ac88e5 +"@algolia/requester-browser-xhr@npm:5.46.0": + version: 5.46.0 + resolution: "@algolia/requester-browser-xhr@npm:5.46.0" + dependencies: + "@algolia/client-common": "npm:5.46.0" + checksum: f0cb29e8e2b6c52c7929803e6ff203c577b506830f1ff5b87110a42d9752a75b2d14c10a2bcb6a8931188614a722ae23e674b24fb735def81ea8c0ca7fbf1cf0 languageName: node linkType: hard -"@algolia/requester-node-http@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/requester-node-http@npm:4.20.0" +"@algolia/requester-fetch@npm:5.46.0": + version: 5.46.0 + resolution: "@algolia/requester-fetch@npm:5.46.0" dependencies: - "@algolia/requester-common": "npm:4.20.0" - checksum: b774593ad9a4f04215481ed3e18a4870275c0586a9106280137e53fd6278a6e4eea9fed15fb086e5989c436097552b4884c35a2f2ec49999105ab697b20831a2 + "@algolia/client-common": "npm:5.46.0" + checksum: 9613974b2940b2725fe0e18725091136f953dc1c4ffdcea1eecc0964a8b64a28810c5becd42043fad5fbbc93daf1cf39e83fba96ef7898cbca1e7730d0c3dc52 languageName: node linkType: hard -"@algolia/transporter@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/transporter@npm:4.20.0" +"@algolia/requester-node-http@npm:5.46.0": + version: 5.46.0 + resolution: "@algolia/requester-node-http@npm:5.46.0" dependencies: - "@algolia/cache-common": "npm:4.20.0" - "@algolia/logger-common": "npm:4.20.0" - "@algolia/requester-common": "npm:4.20.0" - checksum: 769c5d61a280283e2dc4c4a49fcd92e6489bc0267a290438f56311dacc213c0ff4979d063fb8dd5b7bcc888d819ec00eef5d24eb2768946696f4297b757ff527 + "@algolia/client-common": "npm:5.46.0" + checksum: 141516a2c3437217a4fd9842fa2366d7fc9e8c93e1d70229dce6e8990f008002a50db16170688bfc7fea71e158759ff82d479d0cabeef6477219ae18370bf40c languageName: node linkType: hard @@ -197,7 +260,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.8.3": +"@babel/code-frame@npm:^7.0.0": version: 7.23.4 resolution: "@babel/code-frame@npm:7.23.4" dependencies: @@ -1562,626 +1625,1254 @@ __metadata: languageName: node linkType: hard -"@discoveryjs/json-ext@npm:0.5.7": - version: 0.5.7 - resolution: "@discoveryjs/json-ext@npm:0.5.7" - checksum: e10f1b02b78e4812646ddf289b7d9f2cb567d336c363b266bd50cd223cf3de7c2c74018d91cd2613041568397ef3a4a2b500aba588c6e5bd78c38374ba68f38c +"@csstools/cascade-layer-name-parser@npm:^2.0.5": + version: 2.0.5 + resolution: "@csstools/cascade-layer-name-parser@npm:2.0.5" + peerDependencies: + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 + checksum: b6c73d5c8132f922edc88b9df5272c93c9753945f1e1077b80d03b314076ffe03c2cc9bf6cbc85501ee7c7f27e477263df96997c9125fd2fd0cfe82fe2d7c141 languageName: node linkType: hard -"@docsearch/css@npm:3.5.2": - version: 3.5.2 - resolution: "@docsearch/css@npm:3.5.2" - checksum: 736e029b65dba3b2fafb98b4bc4e6f7f411863fed4ef2798c82be8dcdcbdcb9dea6a75376b19d013e9d2f8607b2e3f8d8353938343b08b382894d8b16883ccb3 +"@csstools/color-helpers@npm:^5.1.0": + version: 5.1.0 + resolution: "@csstools/color-helpers@npm:5.1.0" + checksum: b7f99d2e455cf1c9b41a67a5327d5d02888cd5c8802a68b1887dffef537d9d4bc66b3c10c1e62b40bbed638b6c1d60b85a232f904ed7b39809c4029cb36567db languageName: node linkType: hard -"@docsearch/react@npm:^3.5.2": - version: 3.5.2 - resolution: "@docsearch/react@npm:3.5.2" - dependencies: - "@algolia/autocomplete-core": "npm:1.9.3" - "@algolia/autocomplete-preset-algolia": "npm:1.9.3" - "@docsearch/css": "npm:3.5.2" - algoliasearch: "npm:^4.19.1" +"@csstools/css-calc@npm:^2.1.4": + version: 2.1.4 + resolution: "@csstools/css-calc@npm:2.1.4" peerDependencies: - "@types/react": ">= 16.8.0 < 19.0.0" - react: ">= 16.8.0 < 19.0.0" - react-dom: ">= 16.8.0 < 19.0.0" - search-insights: ">= 1 < 3" - peerDependenciesMeta: - "@types/react": - optional: true - react: - optional: true - react-dom: - optional: true - search-insights: - optional: true - checksum: 1dc22a4364be89bc4139bbcc4c90ea240a701961eb698101f53067fd6e0ca014fc12bb6577b67dc108e0ef5e8484866df8e08eb681f9bcafc898a822ce2f42d8 - languageName: node - linkType: hard - -"@docusaurus/babel@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/babel@npm:3.6.1" - dependencies: - "@babel/core": "npm:^7.25.9" - "@babel/generator": "npm:^7.25.9" - "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" - "@babel/plugin-transform-runtime": "npm:^7.25.9" - "@babel/preset-env": "npm:^7.25.9" - "@babel/preset-react": "npm:^7.25.9" - "@babel/preset-typescript": "npm:^7.25.9" - "@babel/runtime": "npm:^7.25.9" - "@babel/runtime-corejs3": "npm:^7.25.9" - "@babel/traverse": "npm:^7.25.9" - "@docusaurus/logger": "npm:3.6.1" - "@docusaurus/utils": "npm:3.6.1" - babel-plugin-dynamic-import-node: "npm:^2.3.3" - fs-extra: "npm:^11.1.1" - tslib: "npm:^2.6.0" - checksum: 66e3ce0e837e6d507539c1cf9754ff5e7e0d0198c9d5cf0b99c5de48bccdf747b6bc3459f267c0123587a8497cbe2b7241bd49dfeb8a5a5e2b015088e4e2a908 + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 + checksum: 42ce5793e55ec4d772083808a11e9fb2dfe36db3ec168713069a276b4c3882205b3507c4680224c28a5d35fe0bc2d308c77f8f2c39c7c09aad8747708eb8ddd8 languageName: node linkType: hard -"@docusaurus/bundler@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/bundler@npm:3.6.1" +"@csstools/css-color-parser@npm:^3.1.0": + version: 3.1.0 + resolution: "@csstools/css-color-parser@npm:3.1.0" dependencies: - "@babel/core": "npm:^7.25.9" - "@docusaurus/babel": "npm:3.6.1" - "@docusaurus/cssnano-preset": "npm:3.6.1" - "@docusaurus/logger": "npm:3.6.1" - "@docusaurus/types": "npm:3.6.1" - "@docusaurus/utils": "npm:3.6.1" - autoprefixer: "npm:^10.4.14" - babel-loader: "npm:^9.2.1" - clean-css: "npm:^5.3.2" - copy-webpack-plugin: "npm:^11.0.0" - css-loader: "npm:^6.8.1" - css-minimizer-webpack-plugin: "npm:^5.0.1" - cssnano: "npm:^6.1.2" - file-loader: "npm:^6.2.0" - html-minifier-terser: "npm:^7.2.0" - mini-css-extract-plugin: "npm:^2.9.1" - null-loader: "npm:^4.0.1" - postcss: "npm:^8.4.26" - postcss-loader: "npm:^7.3.3" - react-dev-utils: "npm:^12.0.1" - terser-webpack-plugin: "npm:^5.3.9" - tslib: "npm:^2.6.0" - url-loader: "npm:^4.1.1" - webpack: "npm:^5.95.0" - webpackbar: "npm:^6.0.1" + "@csstools/color-helpers": "npm:^5.1.0" + "@csstools/css-calc": "npm:^2.1.4" peerDependencies: - "@docusaurus/faster": "*" - peerDependenciesMeta: - "@docusaurus/faster": - optional: true - checksum: 03f7b04cb9bd9e0877eeadda1ccf5d097b952aebad4840f4ce170f477652e8a30d37c04a9d9c3dd857ece193e9ce35cccff144e7bf521f35fee2e6a053dd9bc2 + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 + checksum: 0e0c670ad54ec8ec4d9b07568b80defd83b9482191f5e8ca84ab546b7be6db5d7cc2ba7ac9fae54488b129a4be235d6183d3aab4416fec5e89351f73af4222c5 languageName: node linkType: hard -"@docusaurus/core@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/core@npm:3.6.1" - dependencies: - "@docusaurus/babel": "npm:3.6.1" - "@docusaurus/bundler": "npm:3.6.1" - "@docusaurus/logger": "npm:3.6.1" - "@docusaurus/mdx-loader": "npm:3.6.1" - "@docusaurus/utils": "npm:3.6.1" - "@docusaurus/utils-common": "npm:3.6.1" - "@docusaurus/utils-validation": "npm:3.6.1" - boxen: "npm:^6.2.1" - chalk: "npm:^4.1.2" - chokidar: "npm:^3.5.3" - cli-table3: "npm:^0.6.3" - combine-promises: "npm:^1.1.0" - commander: "npm:^5.1.0" - core-js: "npm:^3.31.1" - del: "npm:^6.1.1" - detect-port: "npm:^1.5.1" - escape-html: "npm:^1.0.3" - eta: "npm:^2.2.0" - eval: "npm:^0.1.8" - fs-extra: "npm:^11.1.1" - html-tags: "npm:^3.3.1" - html-webpack-plugin: "npm:^5.6.0" - leven: "npm:^3.1.0" - lodash: "npm:^4.17.21" - p-map: "npm:^4.0.0" - prompts: "npm:^2.4.2" - react-dev-utils: "npm:^12.0.1" - react-helmet-async: "npm:^1.3.0" - react-loadable: "npm:@docusaurus/react-loadable@6.0.0" - react-loadable-ssr-addon-v5-slorber: "npm:^1.0.1" - react-router: "npm:^5.3.4" - react-router-config: "npm:^5.1.1" - react-router-dom: "npm:^5.3.4" - rtl-detect: "npm:^1.0.4" - semver: "npm:^7.5.4" - serve-handler: "npm:^6.1.6" - shelljs: "npm:^0.8.5" - tslib: "npm:^2.6.0" - update-notifier: "npm:^6.0.2" - webpack: "npm:^5.95.0" - webpack-bundle-analyzer: "npm:^4.10.2" - webpack-dev-server: "npm:^4.15.2" - webpack-merge: "npm:^6.0.1" +"@csstools/css-parser-algorithms@npm:^3.0.5": + version: 3.0.5 + resolution: "@csstools/css-parser-algorithms@npm:3.0.5" peerDependencies: - "@mdx-js/react": ^3.0.0 - react: ^18.0.0 - react-dom: ^18.0.0 - bin: - docusaurus: bin/docusaurus.mjs - checksum: e23c6e84b499737f32a3ca8f97c0b91947eff9da410d925eafbf79f3c426cd1ad54fb24970883e2e04c95290a0685ed33db7df296ea1ca8fe8bb92897f247b9a + "@csstools/css-tokenizer": ^3.0.4 + checksum: d9a1c888bd43849ae3437ca39251d5c95d2c8fd6b5ccdb7c45491dfd2c1cbdc3075645e80901d120e4d2c1993db9a5b2d83793b779dbbabcfb132adb142eb7f7 languageName: node linkType: hard -"@docusaurus/cssnano-preset@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/cssnano-preset@npm:3.6.1" - dependencies: - cssnano-preset-advanced: "npm:^6.1.2" - postcss: "npm:^8.4.38" - postcss-sort-media-queries: "npm:^5.2.0" - tslib: "npm:^2.6.0" - checksum: e6aa2f8d08f0e1b8b4db80a52ffbcccb5fa7940ec85be4dd072bcc15d7730ea2343a2df6ecfa3df0a2ad7f2354f811b67306871281f78930a7927d5ad2184dcd +"@csstools/css-tokenizer@npm:^3.0.4": + version: 3.0.4 + resolution: "@csstools/css-tokenizer@npm:3.0.4" + checksum: 3b589f8e9942075a642213b389bab75a2d50d05d203727fcdac6827648a5572674caff07907eff3f9a2389d86a4ee47308fafe4f8588f4a77b7167c588d2559f languageName: node linkType: hard -"@docusaurus/faster@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/faster@npm:3.6.1" - dependencies: - "@docusaurus/types": "npm:3.6.1" - "@rspack/core": "npm:^1.0.14" - "@swc/core": "npm:^1.7.39" - "@swc/html": "npm:^1.7.39" - browserslist: "npm:^4.24.2" - lightningcss: "npm:^1.27.0" - swc-loader: "npm:^0.2.6" - tslib: "npm:^2.6.0" - webpack: "npm:^5.95.0" - checksum: 57982aaaa8d9d0c2d6c6339fa62c0167f3e4d2ab4d0786eb68e3144634971d6f1697641ed44f688ca6c434892976b54b88ef4344c0151eec8747fe2e6226a556 +"@csstools/media-query-list-parser@npm:^4.0.3": + version: 4.0.3 + resolution: "@csstools/media-query-list-parser@npm:4.0.3" + peerDependencies: + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 + checksum: e29d856d57e9a036694662163179fc061a99579f05e7c3c35438b3e063790ae8a9ee9f1fb4b4693d8fc7672ae0801764fe83762ab7b9df2921fcc6172cfd5584 languageName: node linkType: hard -"@docusaurus/logger@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/logger@npm:3.6.1" +"@csstools/postcss-alpha-function@npm:^1.0.1": + version: 1.0.1 + resolution: "@csstools/postcss-alpha-function@npm:1.0.1" dependencies: - chalk: "npm:^4.1.2" - tslib: "npm:^2.6.0" - checksum: 536dd850331465891994a12926ef0b9387b28f8b007180d4fc9d7ddb6f306dad2e5157e9dda8138613842357db1227dd0ba41c2d1da5e8e8d29166347b9d8899 + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 35ca209e572534ade21ac5c18aad702aa492eb39e2d0e475f441371063418fe9650554e6a59b1318d3a615da83ef54d9a588faa27063ecc0a568ef7290a6b488 languageName: node linkType: hard -"@docusaurus/mdx-loader@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/mdx-loader@npm:3.6.1" +"@csstools/postcss-cascade-layers@npm:^5.0.2": + version: 5.0.2 + resolution: "@csstools/postcss-cascade-layers@npm:5.0.2" dependencies: - "@docusaurus/logger": "npm:3.6.1" - "@docusaurus/utils": "npm:3.6.1" - "@docusaurus/utils-validation": "npm:3.6.1" - "@mdx-js/mdx": "npm:^3.0.0" - "@slorber/remark-comment": "npm:^1.0.0" - escape-html: "npm:^1.0.3" - estree-util-value-to-estree: "npm:^3.0.1" - file-loader: "npm:^6.2.0" - fs-extra: "npm:^11.1.1" - image-size: "npm:^1.0.2" - mdast-util-mdx: "npm:^3.0.0" - mdast-util-to-string: "npm:^4.0.0" - rehype-raw: "npm:^7.0.0" - remark-directive: "npm:^3.0.0" - remark-emoji: "npm:^4.0.0" - remark-frontmatter: "npm:^5.0.0" - remark-gfm: "npm:^4.0.0" - stringify-object: "npm:^3.3.0" - tslib: "npm:^2.6.0" - unified: "npm:^11.0.3" - unist-util-visit: "npm:^5.0.0" - url-loader: "npm:^4.1.1" - vfile: "npm:^6.0.1" - webpack: "npm:^5.88.1" + "@csstools/selector-specificity": "npm:^5.0.0" + postcss-selector-parser: "npm:^7.0.0" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 569c90a643507fe6032fc08e88a1dcfba3207239de3bb0e3e4d24d5efe86099353e2471ead3241d1ba23f22afeb6d4c054091158e7bc600a27e0945b87a865d7 + postcss: ^8.4 + checksum: dd8e29cfd3a93932fa35e3a59aa62fd2e720772d450f40f38f65ce1e736e2fe839635eb6f033abcc8ee8bc2856161a297f4458b352b26d2216856feb03176612 languageName: node linkType: hard -"@docusaurus/module-type-aliases@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/module-type-aliases@npm:3.6.1" +"@csstools/postcss-color-function-display-p3-linear@npm:^1.0.1": + version: 1.0.1 + resolution: "@csstools/postcss-color-function-display-p3-linear@npm:1.0.1" dependencies: - "@docusaurus/types": "npm:3.6.1" - "@types/history": "npm:^4.7.11" - "@types/react": "npm:*" - "@types/react-router-config": "npm:*" - "@types/react-router-dom": "npm:*" - react-helmet-async: "npm:*" - react-loadable: "npm:@docusaurus/react-loadable@6.0.0" + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" peerDependencies: - react: "*" - react-dom: "*" - checksum: 006a5414234bb26300e9240de519ba5bf53198869df953f090d9a28ccee99850e1a7384dffccb865ccd513bf1d090a8ab5887fa7583b921df1279b99613aa0b7 + postcss: ^8.4 + checksum: d02d45410c9257f5620c766f861f8fa3762b74ef01fdba8060b33a4c98f929e2219cd476b25bd4181ac186158a4d99a0da555c0b6ba45a7ac4a3a5885baad1f5 languageName: node linkType: hard -"@docusaurus/plugin-client-redirects@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/plugin-client-redirects@npm:3.6.1" +"@csstools/postcss-color-function@npm:^4.0.12": + version: 4.0.12 + resolution: "@csstools/postcss-color-function@npm:4.0.12" dependencies: - "@docusaurus/core": "npm:3.6.1" - "@docusaurus/logger": "npm:3.6.1" - "@docusaurus/utils": "npm:3.6.1" - "@docusaurus/utils-common": "npm:3.6.1" - "@docusaurus/utils-validation": "npm:3.6.1" - eta: "npm:^2.2.0" - fs-extra: "npm:^11.1.1" - lodash: "npm:^4.17.21" - tslib: "npm:^2.6.0" + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 274f5e13806c766e90e7fa001c166d6063318cc5e166f77804b27afd2beb75405166bf0d37322b3adb1ffe3c3090a81b3ac4e07d4e8bca9559906eaff13dec35 + postcss: ^8.4 + checksum: a355b04d90f89c8e37a4a23543151558060acc68fb2e7d1c3549bebeeae2b147eec26af1fbc6ee690f0ba4830263f2d181f5331d16d3483b5542be46996fa755 languageName: node linkType: hard -"@docusaurus/plugin-content-blog@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/plugin-content-blog@npm:3.6.1" +"@csstools/postcss-color-mix-function@npm:^3.0.12": + version: 3.0.12 + resolution: "@csstools/postcss-color-mix-function@npm:3.0.12" dependencies: - "@docusaurus/core": "npm:3.6.1" - "@docusaurus/logger": "npm:3.6.1" - "@docusaurus/mdx-loader": "npm:3.6.1" - "@docusaurus/theme-common": "npm:3.6.1" - "@docusaurus/types": "npm:3.6.1" - "@docusaurus/utils": "npm:3.6.1" - "@docusaurus/utils-common": "npm:3.6.1" - "@docusaurus/utils-validation": "npm:3.6.1" - cheerio: "npm:1.0.0-rc.12" - feed: "npm:^4.2.2" - fs-extra: "npm:^11.1.1" - lodash: "npm:^4.17.21" - reading-time: "npm:^1.5.0" - srcset: "npm:^4.0.0" - tslib: "npm:^2.6.0" - unist-util-visit: "npm:^5.0.0" - utility-types: "npm:^3.10.0" - webpack: "npm:^5.88.1" + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" peerDependencies: - "@docusaurus/plugin-content-docs": "*" - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: baf9b30cba055367ee91f9cb2db918ab311a17614f51b4aaf7bde67764d0ec61cdd7fb39b0720f87cf781dcf67fcbf79ad19c7db120fbdcad9fe2d0b9d2e961d + postcss: ^8.4 + checksum: 3e98a5118852083d1f87a3f842f78088192b1f9f08fdf1f3b3ef1e8969e18fdadc1e3bcac3d113a07c8917a7e8fa65fdec55a31df9a1b726c8d7ae89db86e8e5 languageName: node linkType: hard -"@docusaurus/plugin-content-docs@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/plugin-content-docs@npm:3.6.1" +"@csstools/postcss-color-mix-variadic-function-arguments@npm:^1.0.2": + version: 1.0.2 + resolution: "@csstools/postcss-color-mix-variadic-function-arguments@npm:1.0.2" dependencies: - "@docusaurus/core": "npm:3.6.1" - "@docusaurus/logger": "npm:3.6.1" - "@docusaurus/mdx-loader": "npm:3.6.1" - "@docusaurus/module-type-aliases": "npm:3.6.1" - "@docusaurus/theme-common": "npm:3.6.1" - "@docusaurus/types": "npm:3.6.1" - "@docusaurus/utils": "npm:3.6.1" - "@docusaurus/utils-common": "npm:3.6.1" - "@docusaurus/utils-validation": "npm:3.6.1" - "@types/react-router-config": "npm:^5.0.7" - combine-promises: "npm:^1.1.0" - fs-extra: "npm:^11.1.1" - js-yaml: "npm:^4.1.0" - lodash: "npm:^4.17.21" - tslib: "npm:^2.6.0" - utility-types: "npm:^3.10.0" - webpack: "npm:^5.88.1" + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: f072637f2af821df47912057b7de2dbbd85abe0a4ed2ae16a226ab60641851203700250b2b2a1b6559d844ea790e34143709a3ca1c53a88769740dd636466881 + postcss: ^8.4 + checksum: 34073f0f0d33e4958f90763e692955a8e8c678b74284234497c4aa0d2143756e1b3616e0c09832caad498870e227ca0a681316afe3a71224fc40ade0ead1bdd9 languageName: node linkType: hard -"@docusaurus/plugin-content-pages@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/plugin-content-pages@npm:3.6.1" +"@csstools/postcss-content-alt-text@npm:^2.0.8": + version: 2.0.8 + resolution: "@csstools/postcss-content-alt-text@npm:2.0.8" dependencies: - "@docusaurus/core": "npm:3.6.1" - "@docusaurus/mdx-loader": "npm:3.6.1" - "@docusaurus/types": "npm:3.6.1" - "@docusaurus/utils": "npm:3.6.1" - "@docusaurus/utils-validation": "npm:3.6.1" - fs-extra: "npm:^11.1.1" - tslib: "npm:^2.6.0" - webpack: "npm:^5.88.1" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 439d696158b4aa549739e28915604dff64623eb445e77f633b33fb644d995a875d93fd93505ec534b88c97c94eda45256b9112c0f438d95118c0a0a3359deafd + postcss: ^8.4 + checksum: 4c330cc2a1e434688a62613ecceb1434cd725ce024c1ad8d4a4c76b9839d1f3ea8566a8c6494921e2b46ec7feef6af8ed6548c216dcb8f0feab4b1d52c96228e languageName: node linkType: hard -"@docusaurus/plugin-debug@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/plugin-debug@npm:3.6.1" +"@csstools/postcss-contrast-color-function@npm:^2.0.12": + version: 2.0.12 + resolution: "@csstools/postcss-contrast-color-function@npm:2.0.12" dependencies: - "@docusaurus/core": "npm:3.6.1" - "@docusaurus/types": "npm:3.6.1" - "@docusaurus/utils": "npm:3.6.1" - fs-extra: "npm:^11.1.1" - react-json-view-lite: "npm:^1.2.0" - tslib: "npm:^2.6.0" + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 447daab549cd76fcdd5b1a8a113748b0e7f18bc5d30c03ee8d401a02c5d44145c02d47833b3c16174426db956a102d11f57b2dd37c1d9c5c46bf51c083b11237 + postcss: ^8.4 + checksum: b783ce948cdf1513ee238e9115b42881a8d3e5d13c16038601b1c470d661cfaeeece4eea29904fb9fcae878bad86f766810fa798a703ab9ad4b0cf276b173f8f languageName: node linkType: hard -"@docusaurus/plugin-google-analytics@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/plugin-google-analytics@npm:3.6.1" +"@csstools/postcss-exponential-functions@npm:^2.0.9": + version: 2.0.9 + resolution: "@csstools/postcss-exponential-functions@npm:2.0.9" dependencies: - "@docusaurus/core": "npm:3.6.1" - "@docusaurus/types": "npm:3.6.1" - "@docusaurus/utils-validation": "npm:3.6.1" - tslib: "npm:^2.6.0" + "@csstools/css-calc": "npm:^2.1.4" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: ab84513ceab61cb281959cae090ff8e8c88951908f55b6cba4dfbc7d9a73ae99b35b7f17f202f65200d36b65bb602447f7aec4b6ce5b01183e9c8388ae817d44 + postcss: ^8.4 + checksum: 78ea627a87fb23e12616c4e54150363b0e8793064634983dbe0368a0aca1ff73206c2d1f29845773daaf42787e7d1f180ce1b57c43e2b0d10da450101f9f34b6 languageName: node linkType: hard -"@docusaurus/plugin-google-gtag@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/plugin-google-gtag@npm:3.6.1" +"@csstools/postcss-font-format-keywords@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/postcss-font-format-keywords@npm:4.0.0" dependencies: - "@docusaurus/core": "npm:3.6.1" - "@docusaurus/types": "npm:3.6.1" - "@docusaurus/utils-validation": "npm:3.6.1" - "@types/gtag.js": "npm:^0.0.12" - tslib: "npm:^2.6.0" + "@csstools/utilities": "npm:^2.0.0" + postcss-value-parser: "npm:^4.2.0" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: c3328c6ab28410c89d825722039ab843c1a8b9d2523be03aef2cc5238ce203d8b1082eb4f25206219181f8f46ca64129f8dd27775154692a69180dda44454f8b + postcss: ^8.4 + checksum: eb794fb95fefcac75e606d185255e601636af177866a317b0c6b6c375055e7240be53918229fd8d4bba00df01bedd2256bdac2b0ad4a4c2ec64f9d27cd6ff639 languageName: node linkType: hard -"@docusaurus/plugin-google-tag-manager@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/plugin-google-tag-manager@npm:3.6.1" +"@csstools/postcss-gamut-mapping@npm:^2.0.11": + version: 2.0.11 + resolution: "@csstools/postcss-gamut-mapping@npm:2.0.11" dependencies: - "@docusaurus/core": "npm:3.6.1" - "@docusaurus/types": "npm:3.6.1" - "@docusaurus/utils-validation": "npm:3.6.1" - tslib: "npm:^2.6.0" + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 7e011681c83a64abed7b243a8a16c98bbfde59da5c2e62c4ee878405344eb89c126fb6949371a3542aca3cb6df5824e5ce39cedd9de8cfc9f23c3b46b632832d + postcss: ^8.4 + checksum: 490b8ccf10e30879a4415afbdd3646e1cdac3671586b7916855cf47a536f3be75eed014396056bde6528e0cb76d904e79bad78afc0b499e837264cf22519d145 languageName: node linkType: hard -"@docusaurus/plugin-sitemap@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/plugin-sitemap@npm:3.6.1" +"@csstools/postcss-gradients-interpolation-method@npm:^5.0.12": + version: 5.0.12 + resolution: "@csstools/postcss-gradients-interpolation-method@npm:5.0.12" dependencies: - "@docusaurus/core": "npm:3.6.1" - "@docusaurus/logger": "npm:3.6.1" - "@docusaurus/types": "npm:3.6.1" - "@docusaurus/utils": "npm:3.6.1" - "@docusaurus/utils-common": "npm:3.6.1" - "@docusaurus/utils-validation": "npm:3.6.1" - fs-extra: "npm:^11.1.1" - sitemap: "npm:^7.1.1" - tslib: "npm:^2.6.0" + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: a11bac07a8aef01b696a879a2de64bccb80ca32c44809a2e58419a5d7d007d6c20cdbd7302e651d70b845a03175c9bd34d2620a50f0c5a7bab2e3e060abdf805 + postcss: ^8.4 + checksum: 70b3d6c7050ce882ed2281e71eb4493531ae8d55d21899920eeeb6c205d90aaf430419a66235484ccce3a1a1891367dfc0ef772f3866ae3a9d8ec5ddd0cfe894 languageName: node linkType: hard -"@docusaurus/preset-classic@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/preset-classic@npm:3.6.1" +"@csstools/postcss-hwb-function@npm:^4.0.12": + version: 4.0.12 + resolution: "@csstools/postcss-hwb-function@npm:4.0.12" dependencies: - "@docusaurus/core": "npm:3.6.1" - "@docusaurus/plugin-content-blog": "npm:3.6.1" - "@docusaurus/plugin-content-docs": "npm:3.6.1" - "@docusaurus/plugin-content-pages": "npm:3.6.1" - "@docusaurus/plugin-debug": "npm:3.6.1" - "@docusaurus/plugin-google-analytics": "npm:3.6.1" - "@docusaurus/plugin-google-gtag": "npm:3.6.1" - "@docusaurus/plugin-google-tag-manager": "npm:3.6.1" - "@docusaurus/plugin-sitemap": "npm:3.6.1" - "@docusaurus/theme-classic": "npm:3.6.1" - "@docusaurus/theme-common": "npm:3.6.1" - "@docusaurus/theme-search-algolia": "npm:3.6.1" - "@docusaurus/types": "npm:3.6.1" + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: abac722eb29816561f1c27e19a42bb90f0d2cde2a24c877c8d5d6c9dd1f28a11746027ad61b15b108cc1731a89f7d6fd9ebe8967a3639a3dd0bd0fe7f0e08740 + postcss: ^8.4 + checksum: d0dac34da9d7ac654060b6b27690a419718e990b21ff3e63266ea59934a865bc6aeae8eb8e1ca3e227a8b2a208657e3ab70ccdf0437f1f09d21ab848bbffcaa2 languageName: node linkType: hard -"@docusaurus/remark-plugin-npm2yarn@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/remark-plugin-npm2yarn@npm:3.6.1" +"@csstools/postcss-ic-unit@npm:^4.0.4": + version: 4.0.4 + resolution: "@csstools/postcss-ic-unit@npm:4.0.4" dependencies: - mdast-util-mdx: "npm:^3.0.0" - npm-to-yarn: "npm:^3.0.0" - tslib: "npm:^2.6.0" - unified: "npm:^11.0.3" - unist-util-visit: "npm:^5.0.0" - checksum: d85d98eb5657418bea2c0eeb4e45fbf8ed0b32f2e9ca5b2766a5fd07039227e1514b8c542846e49a0c23b33456594d391b0ff5fc4caa5dde8e41a6d0bd4b42db - languageName: node - linkType: hard - -"@docusaurus/theme-classic@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/theme-classic@npm:3.6.1" - dependencies: - "@docusaurus/core": "npm:3.6.1" - "@docusaurus/logger": "npm:3.6.1" - "@docusaurus/mdx-loader": "npm:3.6.1" - "@docusaurus/module-type-aliases": "npm:3.6.1" - "@docusaurus/plugin-content-blog": "npm:3.6.1" - "@docusaurus/plugin-content-docs": "npm:3.6.1" - "@docusaurus/plugin-content-pages": "npm:3.6.1" - "@docusaurus/theme-common": "npm:3.6.1" - "@docusaurus/theme-translations": "npm:3.6.1" - "@docusaurus/types": "npm:3.6.1" - "@docusaurus/utils": "npm:3.6.1" - "@docusaurus/utils-common": "npm:3.6.1" - "@docusaurus/utils-validation": "npm:3.6.1" - "@mdx-js/react": "npm:^3.0.0" - clsx: "npm:^2.0.0" - copy-text-to-clipboard: "npm:^3.2.0" - infima: "npm:0.2.0-alpha.45" - lodash: "npm:^4.17.21" - nprogress: "npm:^0.2.0" - postcss: "npm:^8.4.26" - prism-react-renderer: "npm:^2.3.0" - prismjs: "npm:^1.29.0" - react-router-dom: "npm:^5.3.4" - rtlcss: "npm:^4.1.0" - tslib: "npm:^2.6.0" - utility-types: "npm:^3.10.0" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + postcss-value-parser: "npm:^4.2.0" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 4836ebf9db4b6044af0e20435a5778bc001868a20c9a54fd2b194559755c454d03552e3c46edba1329d7cd7d89ea6a5c13ded19206531899974b8ee193551dd6 + postcss: ^8.4 + checksum: 20168e70ecb4abf7a69e407d653b6c7c9c82f2c7b1da0920e1d035f62b5ef8552cc7f1b62e0dca318df13c348e79fba862e1a4bb0e9432119a82b10aeb511752 languageName: node linkType: hard -"@docusaurus/theme-common@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/theme-common@npm:3.6.1" - dependencies: - "@docusaurus/mdx-loader": "npm:3.6.1" - "@docusaurus/module-type-aliases": "npm:3.6.1" - "@docusaurus/utils": "npm:3.6.1" - "@docusaurus/utils-common": "npm:3.6.1" - "@types/history": "npm:^4.7.11" - "@types/react": "npm:*" - "@types/react-router-config": "npm:*" - clsx: "npm:^2.0.0" - parse-numeric-range: "npm:^1.3.0" - prism-react-renderer: "npm:^2.3.0" - tslib: "npm:^2.6.0" - utility-types: "npm:^3.10.0" - peerDependencies: - "@docusaurus/plugin-content-docs": "*" - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 4f3739fe3af5292b389b811f617a4da7f3617d940f9e09349bfaf5ce823941e732651fe45a8f9d9f3c6cb444cd788b4e1952d18f56c78531299635df840974ce - languageName: node - linkType: hard - -"@docusaurus/theme-search-algolia@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/theme-search-algolia@npm:3.6.1" - dependencies: - "@docsearch/react": "npm:^3.5.2" - "@docusaurus/core": "npm:3.6.1" - "@docusaurus/logger": "npm:3.6.1" - "@docusaurus/plugin-content-docs": "npm:3.6.1" - "@docusaurus/theme-common": "npm:3.6.1" - "@docusaurus/theme-translations": "npm:3.6.1" - "@docusaurus/utils": "npm:3.6.1" - "@docusaurus/utils-validation": "npm:3.6.1" - algoliasearch: "npm:^4.18.0" - algoliasearch-helper: "npm:^3.13.3" - clsx: "npm:^2.0.0" - eta: "npm:^2.2.0" - fs-extra: "npm:^11.1.1" - lodash: "npm:^4.17.21" - tslib: "npm:^2.6.0" - utility-types: "npm:^3.10.0" +"@csstools/postcss-initial@npm:^2.0.1": + version: 2.0.1 + resolution: "@csstools/postcss-initial@npm:2.0.1" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 5295f416ce10a68e0f71e4ecfad5f4f6071a3d1d799220654fb8981ac66322a5c1f5c9bb7871d62b6bc6b73d7656d4cbd2b165cb51f1a7cb6ca65136ac1a57ba + postcss: ^8.4 + checksum: dbff7084ef4f1c4647efe2b147001daf172003c15b5e22689f0540d03c8d362f2a332cd9cf136e6c8dcda7564ee30492a4267ea188f72cb9c1000fb9bcfbfef8 languageName: node linkType: hard -"@docusaurus/theme-translations@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/theme-translations@npm:3.6.1" +"@csstools/postcss-is-pseudo-class@npm:^5.0.3": + version: 5.0.3 + resolution: "@csstools/postcss-is-pseudo-class@npm:5.0.3" dependencies: - fs-extra: "npm:^11.1.1" - tslib: "npm:^2.6.0" - checksum: ed8b2665a0fb62b8d338aeae177d4661294e4c6e9ba3d19d78dbea8f40a374658838d8be9d6f7271205265ddeea20399db6427f9d20db226dc3d5bb58b736b8a + "@csstools/selector-specificity": "npm:^5.0.0" + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 7980f1cabf32850bac72552e4e9de47412359e36e259a92b9b9af25dae4cce42bbcc5fdca8f384a589565bf383ecb23dec3af9f084d8df18b82552318b2841b6 languageName: node linkType: hard -"@docusaurus/types@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/types@npm:3.6.1" +"@csstools/postcss-light-dark-function@npm:^2.0.11": + version: 2.0.11 + resolution: "@csstools/postcss-light-dark-function@npm:2.0.11" dependencies: - "@mdx-js/mdx": "npm:^3.0.0" - "@types/history": "npm:^4.7.11" - "@types/react": "npm:*" - commander: "npm:^5.1.0" - joi: "npm:^17.9.2" - react-helmet-async: "npm:^1.3.0" - utility-types: "npm:^3.10.0" - webpack: "npm:^5.95.0" - webpack-merge: "npm:^5.9.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: d73e118e0fbcbb15fcb908dddaf81c977c5ded53dacce181f7736a234b1b0b930b9dae8847e7951245754b3c5f5b9c0d8002f020a56ad8ba32587f8723209776 + postcss: ^8.4 + checksum: 0175be41bb0044a48bc98d5c55cce41ed6b9ada88253c5f20d0ca17287cba4b429742b458ac5744675b9a286109e13ac51d64e226ab16040d7b051ba64c0c77b languageName: node linkType: hard -"@docusaurus/utils-common@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/utils-common@npm:3.6.1" +"@csstools/postcss-logical-float-and-clear@npm:^3.0.0": + version: 3.0.0 + resolution: "@csstools/postcss-logical-float-and-clear@npm:3.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 71a20e8c37877bf68ae615d7bb93fc11b4f8da8be8b1dc1a6e0fc69e27f189712ed71436b8ed51fa69fdb98b8e6718df2b5f42f246c4d39badaf0e43020fcfd4 + languageName: node + linkType: hard + +"@csstools/postcss-logical-overflow@npm:^2.0.0": + version: 2.0.0 + resolution: "@csstools/postcss-logical-overflow@npm:2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 0e103343d3ff8b34eef01b02355c5e010d272fd12d149a242026bb13ab1577b7f3a11fd4514be9342d96f73d61dac1f093a9bd36ece591753ed09a84eb7fca0a + languageName: node + linkType: hard + +"@csstools/postcss-logical-overscroll-behavior@npm:^2.0.0": + version: 2.0.0 + resolution: "@csstools/postcss-logical-overscroll-behavior@npm:2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 1649601bb26f04d760fb5ebc42cdf414fa2a380b8ec22fe1c117f664c286665a786bd7bbda01b7e7567eaf3cc018a4f36a5c9805f6751cc497da223e0ffe9524 + languageName: node + linkType: hard + +"@csstools/postcss-logical-resize@npm:^3.0.0": + version: 3.0.0 + resolution: "@csstools/postcss-logical-resize@npm:3.0.0" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 4f12efcaf5468ff359bb3f32f0f66034b9acc9b3ac21fcd2f30a1c8998fc653ebac0091f35c8b7e8dbfe6ccf595aee67f9b06a67adf45a8844e49a82d98b4386 + languageName: node + linkType: hard + +"@csstools/postcss-logical-viewport-units@npm:^3.0.4": + version: 3.0.4 + resolution: "@csstools/postcss-logical-viewport-units@npm:3.0.4" + dependencies: + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: f0b5ba38acde3bf0ca880c6e0a883950c99fa9919b0e6290c894d5716569663590f26aa1170fd9483ce14544e46afac006ab3b02781410d5e7c8dd1467c674ce + languageName: node + linkType: hard + +"@csstools/postcss-media-minmax@npm:^2.0.9": + version: 2.0.9 + resolution: "@csstools/postcss-media-minmax@npm:2.0.9" + dependencies: + "@csstools/css-calc": "npm:^2.1.4" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/media-query-list-parser": "npm:^4.0.3" + peerDependencies: + postcss: ^8.4 + checksum: d82622ee9de6eacba1abbf31718cd58759d158ed8a575f36f08e982d07a7d83e51fb184178b96c6f7b76cb333bb33cac04d06a750b6b9c5c43ae1c56232880f9 + languageName: node + linkType: hard + +"@csstools/postcss-media-queries-aspect-ratio-number-values@npm:^3.0.5": + version: 3.0.5 + resolution: "@csstools/postcss-media-queries-aspect-ratio-number-values@npm:3.0.5" + dependencies: + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/media-query-list-parser": "npm:^4.0.3" + peerDependencies: + postcss: ^8.4 + checksum: a47abdaa7f4b26596bd9d6bb77aed872a232fc12bd144d2c062d9da626e8dfd8336e2fff67617dba61a1666c2b8027145b390d70d5cd4d4f608604e077cfb04e + languageName: node + linkType: hard + +"@csstools/postcss-nested-calc@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/postcss-nested-calc@npm:4.0.0" + dependencies: + "@csstools/utilities": "npm:^2.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: fb61512fa4909bdf0ee32a23e771145086c445f2208a737b52093c8adfab7362c56d3aeaf2a6e33ffcec067e99a07219775465d2fbb1a3ac30cdcfb278b218b7 + languageName: node + linkType: hard + +"@csstools/postcss-normalize-display-values@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/postcss-normalize-display-values@npm:4.0.0" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: d3a3a362b532163bd791f97348ef28b7a43baf01987c7702b06285e751cdc5ea3e3a2553f088260515b4d28263d5c475923d4d4780ecb4078ec66dff50c9e638 + languageName: node + linkType: hard + +"@csstools/postcss-oklab-function@npm:^4.0.12": + version: 4.0.12 + resolution: "@csstools/postcss-oklab-function@npm:4.0.12" + dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 40d4f51b568c8299c054f8971d0e85fa7da609ba23ce6c84dc17e16bc3838640ed6da75c3886dc9a96a11005773c6e23cba13a5510c781b2d633d07ad7bda6b7 + languageName: node + linkType: hard + +"@csstools/postcss-position-area-property@npm:^1.0.0": + version: 1.0.0 + resolution: "@csstools/postcss-position-area-property@npm:1.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 38f770454d46bfed01d43a3f5e7ac07d3111399b374a7198ae6503cdb6288e410c7b4199f5a7af8f16aeb688216445ade97be417c084313d6c56f55e50d34559 + languageName: node + linkType: hard + +"@csstools/postcss-progressive-custom-properties@npm:^4.2.1": + version: 4.2.1 + resolution: "@csstools/postcss-progressive-custom-properties@npm:4.2.1" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 56e9a147799719fd5c550c035437693dd50cdfef46d66a4f2ce8f196e1006a096aa47d412710a89c3dc9808068a0a101c7f607a507ed68e925580c6f921e84d5 + languageName: node + linkType: hard + +"@csstools/postcss-random-function@npm:^2.0.1": + version: 2.0.1 + resolution: "@csstools/postcss-random-function@npm:2.0.1" + dependencies: + "@csstools/css-calc": "npm:^2.1.4" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + peerDependencies: + postcss: ^8.4 + checksum: 475bacf685b8bb82942d388e9e3b95f4156800f370299f19f5acc490475dc2813100de81a5a6bf48b696b4d83247622005b616af3166a668556b4b1aceded70d + languageName: node + linkType: hard + +"@csstools/postcss-relative-color-syntax@npm:^3.0.12": + version: 3.0.12 + resolution: "@csstools/postcss-relative-color-syntax@npm:3.0.12" + dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 11af386c8193e22c148ac034eee94c56da3060bdbde3196d2d641b088e12de35bef187bcd7d421f9e4d49c4f1cfc28b24e136e62107e02ed7007a3a28f635d06 + languageName: node + linkType: hard + +"@csstools/postcss-scope-pseudo-class@npm:^4.0.1": + version: 4.0.1 + resolution: "@csstools/postcss-scope-pseudo-class@npm:4.0.1" + dependencies: + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 6a0ca50fae655f4498200d1ce298ca794c85fbe2e3fd5d6419843254f055df5007a973e09b5f1e78e376c02b54278e411516c8d824300c68b265d3e5b311d7ee + languageName: node + linkType: hard + +"@csstools/postcss-sign-functions@npm:^1.1.4": + version: 1.1.4 + resolution: "@csstools/postcss-sign-functions@npm:1.1.4" + dependencies: + "@csstools/css-calc": "npm:^2.1.4" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + peerDependencies: + postcss: ^8.4 + checksum: ff58108b2527832a84c571a1f40224b5c8d2afa8db2fe3b1e3599ff6f3469d9f4c528a70eb3c25c5d7801e30474fabfec04e7c23bfdad8572ad492053cd4f899 + languageName: node + linkType: hard + +"@csstools/postcss-stepped-value-functions@npm:^4.0.9": + version: 4.0.9 + resolution: "@csstools/postcss-stepped-value-functions@npm:4.0.9" + dependencies: + "@csstools/css-calc": "npm:^2.1.4" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + peerDependencies: + postcss: ^8.4 + checksum: f143ca06338c30abb2aa37adc3d7e43a78f3b4493093160cb5babe3ec8cf6b86d83876746ee8e162db87b5e9af6e0066958d89fe8b4a503a29568e5c57c1bf8a + languageName: node + linkType: hard + +"@csstools/postcss-system-ui-font-family@npm:^1.0.0": + version: 1.0.0 + resolution: "@csstools/postcss-system-ui-font-family@npm:1.0.0" + dependencies: + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + peerDependencies: + postcss: ^8.4 + checksum: 6a81761ae3cae643659b1416a7a892cf1505474896193b8abc26cff319cb6b1a20b64c5330d64019fba458e058da3abc9407d0ebf0c102289c0b79ef99b4c6d6 + languageName: node + linkType: hard + +"@csstools/postcss-text-decoration-shorthand@npm:^4.0.3": + version: 4.0.3 + resolution: "@csstools/postcss-text-decoration-shorthand@npm:4.0.3" + dependencies: + "@csstools/color-helpers": "npm:^5.1.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: f6af7d5dcf599edcf76c5e396ef2d372bbe1c1f3fbaaccd91e91049e64b6ff68b44f459277aef0a8110baca3eaa21275012adc52ccb8c0fc526a4c35577f8fce + languageName: node + linkType: hard + +"@csstools/postcss-trigonometric-functions@npm:^4.0.9": + version: 4.0.9 + resolution: "@csstools/postcss-trigonometric-functions@npm:4.0.9" + dependencies: + "@csstools/css-calc": "npm:^2.1.4" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + peerDependencies: + postcss: ^8.4 + checksum: 6ba3d381c977c224f01d47a36f78c9b99d3b89d060a357a9f8840537fdf497d9587a28165dc74e96abdf02f8db0a277d3558646355085a74c8915ee73c6780d1 + languageName: node + linkType: hard + +"@csstools/postcss-unset-value@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/postcss-unset-value@npm:4.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 8424ac700ded5bf59d49310335896f10c069e2c3fc6a676b5d13ca5a6fb78689b948f50494df875da284c4c76651deb005eafba70d87e693274628c5a685abfa + languageName: node + linkType: hard + +"@csstools/selector-resolve-nested@npm:^3.1.0": + version: 3.1.0 + resolution: "@csstools/selector-resolve-nested@npm:3.1.0" + peerDependencies: + postcss-selector-parser: ^7.0.0 + checksum: c2b1a930ad03c1427ab90b28c4940424fb39e8175130148f16209be3a3937f7a146d5483ca1da1dfc100aa7ae86df713f0ee82d4bbaa9b986e7f47f35cb67cca + languageName: node + linkType: hard + +"@csstools/selector-specificity@npm:^5.0.0": + version: 5.0.0 + resolution: "@csstools/selector-specificity@npm:5.0.0" + peerDependencies: + postcss-selector-parser: ^7.0.0 + checksum: 186b444cabcdcdeb553bfe021f80c58bfe9ef38dcc444f2b1f34a5aab9be063ab4e753022b2d5792049c041c28cfbb78e4b707ec398459300e402030d35c07eb + languageName: node + linkType: hard + +"@csstools/utilities@npm:^2.0.0": + version: 2.0.0 + resolution: "@csstools/utilities@npm:2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: be5c31437b726928f64cd4bb3e47f5b90bfd2e2a69a8eaabd8e89cc6c0977e4f0f7ee48de50c8ed8b07e04e3956a02293247e0da3236d521fb2e836f88f65822 + languageName: node + linkType: hard + +"@discoveryjs/json-ext@npm:0.5.7": + version: 0.5.7 + resolution: "@discoveryjs/json-ext@npm:0.5.7" + checksum: e10f1b02b78e4812646ddf289b7d9f2cb567d336c363b266bd50cd223cf3de7c2c74018d91cd2613041568397ef3a4a2b500aba588c6e5bd78c38374ba68f38c + languageName: node + linkType: hard + +"@docsearch/core@npm:4.3.1": + version: 4.3.1 + resolution: "@docsearch/core@npm:4.3.1" + peerDependencies: + "@types/react": ">= 16.8.0 < 20.0.0" + react: ">= 16.8.0 < 20.0.0" + react-dom: ">= 16.8.0 < 20.0.0" + peerDependenciesMeta: + "@types/react": + optional: true + react: + optional: true + react-dom: + optional: true + checksum: e43d62b4b339fa9d7780703f6c98e63fa57e8e6ceb749fee5eed4f6649e84cddd13c513471c09405c8082540ae0b0045a4196b93edf5fa873410a2b110a6ea12 + languageName: node + linkType: hard + +"@docsearch/css@npm:4.3.2": + version: 4.3.2 + resolution: "@docsearch/css@npm:4.3.2" + checksum: da0899de4ac77330423bcee6fcd584cba11a745cadd26b8d36c49bcb7dbb2de9e3e665df3553ef0b919315f7dc85fe164baf95fc8cda72ae4d3c6727019ad002 + languageName: node + linkType: hard + +"@docsearch/react@npm:^3.9.0 || ^4.1.0": + version: 4.3.2 + resolution: "@docsearch/react@npm:4.3.2" + dependencies: + "@ai-sdk/react": "npm:^2.0.30" + "@algolia/autocomplete-core": "npm:1.19.2" + "@docsearch/core": "npm:4.3.1" + "@docsearch/css": "npm:4.3.2" + ai: "npm:^5.0.30" + algoliasearch: "npm:^5.28.0" + marked: "npm:^16.3.0" + zod: "npm:^4.1.8" + peerDependencies: + "@types/react": ">= 16.8.0 < 20.0.0" + react: ">= 16.8.0 < 20.0.0" + react-dom: ">= 16.8.0 < 20.0.0" + search-insights: ">= 1 < 3" + peerDependenciesMeta: + "@types/react": + optional: true + react: + optional: true + react-dom: + optional: true + search-insights: + optional: true + checksum: 09773f095e6b0b7bafde495914366a6751c2be479a8088ec3b68b735b085f9565fd22c99f0f2c328a6d9ef128fbe3cef469ebde14047900d222d1139707cc28f + languageName: node + linkType: hard + +"@docusaurus/babel@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/babel@npm:3.9.2" + dependencies: + "@babel/core": "npm:^7.25.9" + "@babel/generator": "npm:^7.25.9" + "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" + "@babel/plugin-transform-runtime": "npm:^7.25.9" + "@babel/preset-env": "npm:^7.25.9" + "@babel/preset-react": "npm:^7.25.9" + "@babel/preset-typescript": "npm:^7.25.9" + "@babel/runtime": "npm:^7.25.9" + "@babel/runtime-corejs3": "npm:^7.25.9" + "@babel/traverse": "npm:^7.25.9" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + babel-plugin-dynamic-import-node: "npm:^2.3.3" + fs-extra: "npm:^11.1.1" + tslib: "npm:^2.6.0" + checksum: 8147451a8ba79d35405ec8720c1cded7e84643867cb32877827799e5d36932cf56beaefd9fe4b25b9d855b38a9c08bc5397faddf73b63d7c52b05bf24ca99ee8 + languageName: node + linkType: hard + +"@docusaurus/bundler@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/bundler@npm:3.9.2" + dependencies: + "@babel/core": "npm:^7.25.9" + "@docusaurus/babel": "npm:3.9.2" + "@docusaurus/cssnano-preset": "npm:3.9.2" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + babel-loader: "npm:^9.2.1" + clean-css: "npm:^5.3.3" + copy-webpack-plugin: "npm:^11.0.0" + css-loader: "npm:^6.11.0" + css-minimizer-webpack-plugin: "npm:^5.0.1" + cssnano: "npm:^6.1.2" + file-loader: "npm:^6.2.0" + html-minifier-terser: "npm:^7.2.0" + mini-css-extract-plugin: "npm:^2.9.2" + null-loader: "npm:^4.0.1" + postcss: "npm:^8.5.4" + postcss-loader: "npm:^7.3.4" + postcss-preset-env: "npm:^10.2.1" + terser-webpack-plugin: "npm:^5.3.9" + tslib: "npm:^2.6.0" + url-loader: "npm:^4.1.1" + webpack: "npm:^5.95.0" + webpackbar: "npm:^6.0.1" + peerDependencies: + "@docusaurus/faster": "*" + peerDependenciesMeta: + "@docusaurus/faster": + optional: true + checksum: dcbb7d51eef3fcd57161cb356f63487dbc5a433eea02bc0dfb2a59439884543e76efa3c311ca01c582c2ca33caff19e887303bf72aad04ee374fd013fdcca31f + languageName: node + linkType: hard + +"@docusaurus/core@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/core@npm:3.9.2" + dependencies: + "@docusaurus/babel": "npm:3.9.2" + "@docusaurus/bundler": "npm:3.9.2" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/mdx-loader": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-common": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" + boxen: "npm:^6.2.1" + chalk: "npm:^4.1.2" + chokidar: "npm:^3.5.3" + cli-table3: "npm:^0.6.3" + combine-promises: "npm:^1.1.0" + commander: "npm:^5.1.0" + core-js: "npm:^3.31.1" + detect-port: "npm:^1.5.1" + escape-html: "npm:^1.0.3" + eta: "npm:^2.2.0" + eval: "npm:^0.1.8" + execa: "npm:5.1.1" + fs-extra: "npm:^11.1.1" + html-tags: "npm:^3.3.1" + html-webpack-plugin: "npm:^5.6.0" + leven: "npm:^3.1.0" + lodash: "npm:^4.17.21" + open: "npm:^8.4.0" + p-map: "npm:^4.0.0" + prompts: "npm:^2.4.2" + react-helmet-async: "npm:@slorber/react-helmet-async@1.3.0" + react-loadable: "npm:@docusaurus/react-loadable@6.0.0" + react-loadable-ssr-addon-v5-slorber: "npm:^1.0.1" + react-router: "npm:^5.3.4" + react-router-config: "npm:^5.1.1" + react-router-dom: "npm:^5.3.4" + semver: "npm:^7.5.4" + serve-handler: "npm:^6.1.6" + tinypool: "npm:^1.0.2" + tslib: "npm:^2.6.0" + update-notifier: "npm:^6.0.2" + webpack: "npm:^5.95.0" + webpack-bundle-analyzer: "npm:^4.10.2" + webpack-dev-server: "npm:^5.2.2" + webpack-merge: "npm:^6.0.1" + peerDependencies: + "@mdx-js/react": ^3.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + bin: + docusaurus: bin/docusaurus.mjs + checksum: 6058e2ca596ba0225f26f15baaf0c8fa5e91ddf794c3b942161702c44833baaf15be3acb71d42cf6e359a83e80be609485b6c1080802927591fe38bfc915aa11 + languageName: node + linkType: hard + +"@docusaurus/cssnano-preset@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/cssnano-preset@npm:3.9.2" + dependencies: + cssnano-preset-advanced: "npm:^6.1.2" + postcss: "npm:^8.5.4" + postcss-sort-media-queries: "npm:^5.2.0" + tslib: "npm:^2.6.0" + checksum: 98ca8939ba9c7c6d45cccdaa4028412cd84ea04c39b641d14e3870ee880d83cef8e04cdb485327b36e40550676ee1d614f1e89c9aa822b78e7d0c7dc0321f8db + languageName: node + linkType: hard + +"@docusaurus/faster@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/faster@npm:3.9.2" + dependencies: + "@docusaurus/types": "npm:3.9.2" + "@rspack/core": "npm:^1.5.0" + "@swc/core": "npm:^1.7.39" + "@swc/html": "npm:^1.13.5" + browserslist: "npm:^4.24.2" + lightningcss: "npm:^1.27.0" + swc-loader: "npm:^0.2.6" + tslib: "npm:^2.6.0" + webpack: "npm:^5.95.0" + peerDependencies: + "@docusaurus/types": "*" + checksum: 0cd43f0138dfb1da2b39b159e97a7746c58a0bc5bd2c2d66e8541b0f87e75684fe9ea43e133acc99d2dfbd0bb32414a170fd1e0d74f24613dd22f9351997d85b + languageName: node + linkType: hard + +"@docusaurus/logger@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/logger@npm:3.9.2" + dependencies: + chalk: "npm:^4.1.2" + tslib: "npm:^2.6.0" + checksum: a21e0796873386a9be56f25906092a5d67c9bba5e52abf88e4c3c69d7c1e21467c04b3650c2ff2b9a803507aa4946c4173612791a87f04480d63ed87207b124a + languageName: node + linkType: hard + +"@docusaurus/mdx-loader@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/mdx-loader@npm:3.9.2" + dependencies: + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" + "@mdx-js/mdx": "npm:^3.0.0" + "@slorber/remark-comment": "npm:^1.0.0" + escape-html: "npm:^1.0.3" + estree-util-value-to-estree: "npm:^3.0.1" + file-loader: "npm:^6.2.0" + fs-extra: "npm:^11.1.1" + image-size: "npm:^2.0.2" + mdast-util-mdx: "npm:^3.0.0" + mdast-util-to-string: "npm:^4.0.0" + rehype-raw: "npm:^7.0.0" + remark-directive: "npm:^3.0.0" + remark-emoji: "npm:^4.0.0" + remark-frontmatter: "npm:^5.0.0" + remark-gfm: "npm:^4.0.0" + stringify-object: "npm:^3.3.0" + tslib: "npm:^2.6.0" + unified: "npm:^11.0.3" + unist-util-visit: "npm:^5.0.0" + url-loader: "npm:^4.1.1" + vfile: "npm:^6.0.1" + webpack: "npm:^5.88.1" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 4f3afa817f16fd04dd338a35c04be59fdc0e799a93c6d56dc99b1f42f9a5156691737df62751e14466acbbd65c932e1f77d06a915c9c4ad8f2ad24b2f5479269 + languageName: node + linkType: hard + +"@docusaurus/module-type-aliases@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/module-type-aliases@npm:3.9.2" + dependencies: + "@docusaurus/types": "npm:3.9.2" + "@types/history": "npm:^4.7.11" + "@types/react": "npm:*" + "@types/react-router-config": "npm:*" + "@types/react-router-dom": "npm:*" + react-helmet-async: "npm:@slorber/react-helmet-async@1.3.0" + react-loadable: "npm:@docusaurus/react-loadable@6.0.0" + peerDependencies: + react: "*" + react-dom: "*" + checksum: 60f163ff9004bb1fcbbad94b18200b6bca967da14576f78f5c533f8535aae0a3a723245cb28e1ca93f9d5881d3f1077e03ebf12bbad59d0e1c6916300d086642 + languageName: node + linkType: hard + +"@docusaurus/plugin-client-redirects@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-client-redirects@npm:3.9.2" + dependencies: + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-common": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" + eta: "npm:^2.2.0" + fs-extra: "npm:^11.1.1" + lodash: "npm:^4.17.21" + tslib: "npm:^2.6.0" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 5fe827fa5f3b3e4634034eb63ff73d8bd2e83852ada6ae989ad5b566aaef4937476c20403e1ef05f2090dad92c2e6b384e0981d393a783b49e8b45fa464d282b + languageName: node + linkType: hard + +"@docusaurus/plugin-content-blog@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-content-blog@npm:3.9.2" + dependencies: + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/mdx-loader": "npm:3.9.2" + "@docusaurus/theme-common": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-common": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" + cheerio: "npm:1.0.0-rc.12" + feed: "npm:^4.2.2" + fs-extra: "npm:^11.1.1" + lodash: "npm:^4.17.21" + schema-dts: "npm:^1.1.2" + srcset: "npm:^4.0.0" + tslib: "npm:^2.6.0" + unist-util-visit: "npm:^5.0.0" + utility-types: "npm:^3.10.0" + webpack: "npm:^5.88.1" + peerDependencies: + "@docusaurus/plugin-content-docs": "*" + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 98f82d76d248407a4c53f922f8953a7519a57d18c45f71e41bfb6380d7f801ba063068c9dec2a48b79f10fd4d4f4a909af4c70e4874223db19d9654d651982dd + languageName: node + linkType: hard + +"@docusaurus/plugin-content-docs@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-content-docs@npm:3.9.2" + dependencies: + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/mdx-loader": "npm:3.9.2" + "@docusaurus/module-type-aliases": "npm:3.9.2" + "@docusaurus/theme-common": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-common": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" + "@types/react-router-config": "npm:^5.0.7" + combine-promises: "npm:^1.1.0" + fs-extra: "npm:^11.1.1" + js-yaml: "npm:^4.1.0" + lodash: "npm:^4.17.21" + schema-dts: "npm:^1.1.2" + tslib: "npm:^2.6.0" + utility-types: "npm:^3.10.0" + webpack: "npm:^5.88.1" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: f2df62f6e03a383a8e7f81b29bea81de9b69e918dfaa668cef15a6f787943d3c148bfd8ba120d89cd96a3bbb23cd3d29ce0658f8dee07380ad612db66e835fa4 + languageName: node + linkType: hard + +"@docusaurus/plugin-content-pages@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-content-pages@npm:3.9.2" + dependencies: + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/mdx-loader": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" + fs-extra: "npm:^11.1.1" + tslib: "npm:^2.6.0" + webpack: "npm:^5.88.1" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 294cbd3d127b9a777ab75c13be30e2a559b544bc96798ac6b6d479130f66b95dd6beaf1ca63991f78c279add23ffe16ea14454d3547d558196e747bdb85cb753 + languageName: node + linkType: hard + +"@docusaurus/plugin-css-cascade-layers@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-css-cascade-layers@npm:3.9.2" + dependencies: + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" + tslib: "npm:^2.6.0" + checksum: 3a56f6f4eaa3c1ea014ba25b8d16e2a7ffb144ebf5726b5ec531b4df0a9f7bb33ced4de7ca31f9663a65358852d0635c584244c05f07e9d4c9172f80ba21a5ca + languageName: node + linkType: hard + +"@docusaurus/plugin-debug@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-debug@npm:3.9.2" + dependencies: + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + fs-extra: "npm:^11.1.1" + react-json-view-lite: "npm:^2.3.0" + tslib: "npm:^2.6.0" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 46819f1c22b31b3fbf30243dc5c0439b35a35f8cbbae835becf1e6992ff490ddbd91e4a7448b367ad76aaf20064ed739be07f0e664bb582b4dab39513996d7ba + languageName: node + linkType: hard + +"@docusaurus/plugin-google-analytics@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-google-analytics@npm:3.9.2" + dependencies: + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" + tslib: "npm:^2.6.0" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 6fb787132170f731c1ab66c854fcab6d0c4f7919d60c336185942c8f80dc93b286e64e0bfb22f5f770e7d77fd02000fb5a54b35a357258a0cc6a59468778199e + languageName: node + linkType: hard + +"@docusaurus/plugin-google-gtag@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-google-gtag@npm:3.9.2" + dependencies: + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" + "@types/gtag.js": "npm:^0.0.12" + tslib: "npm:^2.6.0" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 34d4b9c6787e3656dc1af42ecb31a41b766735c89f7a719db40c34a8695aa36825e070923a84639ae3dc42b64a41ee656bd4b2728621c1493952c4efa04b3927 + languageName: node + linkType: hard + +"@docusaurus/plugin-google-tag-manager@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-google-tag-manager@npm:3.9.2" + dependencies: + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" + tslib: "npm:^2.6.0" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 536cb63dc4a22a456e5b7f1d8b53acf0c45b16ba8fb7474c93d5ab7afec60682feccea65c39685dcbc568fccefd6629264e9b979e0f7069fb4c9dc816048659b + languageName: node + linkType: hard + +"@docusaurus/plugin-sitemap@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-sitemap@npm:3.9.2" + dependencies: + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-common": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" + fs-extra: "npm:^11.1.1" + sitemap: "npm:^7.1.1" + tslib: "npm:^2.6.0" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: a1bcbb8ab2531eaa810e74a7c5800942d89a11cfaf544d6d72941c7e37c29eaef609dcaff368ee92cf759e03be7c258c6e5e4cfc6046d77e727a63f84e63a045 + languageName: node + linkType: hard + +"@docusaurus/plugin-svgr@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-svgr@npm:3.9.2" + dependencies: + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" + "@svgr/core": "npm:8.1.0" + "@svgr/webpack": "npm:^8.1.0" + tslib: "npm:^2.6.0" + webpack: "npm:^5.88.1" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: d6a7a1aa0c05b759d6094969d31d05cb7840ee514a60812f8e841e13c2cf319a46d046c0903417e9072b8bc26a9fd0d63e7e5a75255ed7d6b08a9a0466f6cb1a + languageName: node + linkType: hard + +"@docusaurus/preset-classic@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/preset-classic@npm:3.9.2" + dependencies: + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/plugin-content-blog": "npm:3.9.2" + "@docusaurus/plugin-content-docs": "npm:3.9.2" + "@docusaurus/plugin-content-pages": "npm:3.9.2" + "@docusaurus/plugin-css-cascade-layers": "npm:3.9.2" + "@docusaurus/plugin-debug": "npm:3.9.2" + "@docusaurus/plugin-google-analytics": "npm:3.9.2" + "@docusaurus/plugin-google-gtag": "npm:3.9.2" + "@docusaurus/plugin-google-tag-manager": "npm:3.9.2" + "@docusaurus/plugin-sitemap": "npm:3.9.2" + "@docusaurus/plugin-svgr": "npm:3.9.2" + "@docusaurus/theme-classic": "npm:3.9.2" + "@docusaurus/theme-common": "npm:3.9.2" + "@docusaurus/theme-search-algolia": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 94e6f3948592209bd68b797591f21daee8543c6c9a4eac5ae498f5c6b8d1c7579b23173f8554a3430d0dff1cce90b953be0d5f2d53b6b4729116000f61e3dab2 + languageName: node + linkType: hard + +"@docusaurus/remark-plugin-npm2yarn@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/remark-plugin-npm2yarn@npm:3.9.2" + dependencies: + mdast-util-mdx: "npm:^3.0.0" + npm-to-yarn: "npm:^3.0.0" + tslib: "npm:^2.6.0" + unified: "npm:^11.0.3" + unist-util-visit: "npm:^5.0.0" + checksum: f84f1831724abeae358ad657fca647c16c6094370765f120c2a0511e2d3cf4ea32a88ba8d5f1159d67e09164fe679814aa4eed0237b21a14449ef14c630d4d0f + languageName: node + linkType: hard + +"@docusaurus/theme-classic@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/theme-classic@npm:3.9.2" + dependencies: + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/mdx-loader": "npm:3.9.2" + "@docusaurus/module-type-aliases": "npm:3.9.2" + "@docusaurus/plugin-content-blog": "npm:3.9.2" + "@docusaurus/plugin-content-docs": "npm:3.9.2" + "@docusaurus/plugin-content-pages": "npm:3.9.2" + "@docusaurus/theme-common": "npm:3.9.2" + "@docusaurus/theme-translations": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-common": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" + "@mdx-js/react": "npm:^3.0.0" + clsx: "npm:^2.0.0" + infima: "npm:0.2.0-alpha.45" + lodash: "npm:^4.17.21" + nprogress: "npm:^0.2.0" + postcss: "npm:^8.5.4" + prism-react-renderer: "npm:^2.3.0" + prismjs: "npm:^1.29.0" + react-router-dom: "npm:^5.3.4" + rtlcss: "npm:^4.1.0" + tslib: "npm:^2.6.0" + utility-types: "npm:^3.10.0" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: aa6442ac2e65539f083a0ed1e70030443bf61422d5cca24fc8b91c2c4192bcd4d8abdbf4b71536e2ae6afd413fd3f4be1379f2dc45e224173500577ebfa1c346 + languageName: node + linkType: hard + +"@docusaurus/theme-common@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/theme-common@npm:3.9.2" + dependencies: + "@docusaurus/mdx-loader": "npm:3.9.2" + "@docusaurus/module-type-aliases": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-common": "npm:3.9.2" + "@types/history": "npm:^4.7.11" + "@types/react": "npm:*" + "@types/react-router-config": "npm:*" + clsx: "npm:^2.0.0" + parse-numeric-range: "npm:^1.3.0" + prism-react-renderer: "npm:^2.3.0" + tslib: "npm:^2.6.0" + utility-types: "npm:^3.10.0" + peerDependencies: + "@docusaurus/plugin-content-docs": "*" + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 4ecb8570e1fee75a6048ddb43065252e7b5b058f075867b541219830fb01bdc4b41b8f5f0251d6e9e7ffbe3704fd23d16ef90f92a3e2511ecc7ff6d9a2d5bfd6 + languageName: node + linkType: hard + +"@docusaurus/theme-search-algolia@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/theme-search-algolia@npm:3.9.2" + dependencies: + "@docsearch/react": "npm:^3.9.0 || ^4.1.0" + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/plugin-content-docs": "npm:3.9.2" + "@docusaurus/theme-common": "npm:3.9.2" + "@docusaurus/theme-translations": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" + algoliasearch: "npm:^5.37.0" + algoliasearch-helper: "npm:^3.26.0" + clsx: "npm:^2.0.0" + eta: "npm:^2.2.0" + fs-extra: "npm:^11.1.1" + lodash: "npm:^4.17.21" + tslib: "npm:^2.6.0" + utility-types: "npm:^3.10.0" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 676206059771d13c2268c4f8a20630288ac043aa1042090c259de434f8f833e1e95c0cf7de304880149ace3d084c901d3d01cfbfea63a48dc71aaa6726166621 + languageName: node + linkType: hard + +"@docusaurus/theme-translations@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/theme-translations@npm:3.9.2" + dependencies: + fs-extra: "npm:^11.1.1" + tslib: "npm:^2.6.0" + checksum: 543ee40933a8805357575c14d4fc8f8d504f6464796f5fa27ec13d8b0cec669617961edb206d5b74ba1d776d9486656fefdb1c777e2908cb1752ee6fbe28686c + languageName: node + linkType: hard + +"@docusaurus/types@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/types@npm:3.9.2" + dependencies: + "@mdx-js/mdx": "npm:^3.0.0" + "@types/history": "npm:^4.7.11" + "@types/mdast": "npm:^4.0.2" + "@types/react": "npm:*" + commander: "npm:^5.1.0" + joi: "npm:^17.9.2" + react-helmet-async: "npm:@slorber/react-helmet-async@1.3.0" + utility-types: "npm:^3.10.0" + webpack: "npm:^5.95.0" + webpack-merge: "npm:^5.9.0" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: e50a9931e97944d39375a97a45ded13bc35baf3c9c14fe66d30944ebe1203df7748a7631291f937bef1a7a98db73c23505620cd8f03d109fbbdfa83725fb2857 + languageName: node + linkType: hard + +"@docusaurus/utils-common@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/utils-common@npm:3.9.2" dependencies: - "@docusaurus/types": "npm:3.6.1" + "@docusaurus/types": "npm:3.9.2" tslib: "npm:^2.6.0" - checksum: 3a548139b5be365e380df2b90ae3763372f81f78e72ec078f15753477d2cc31218360dcdac0137f435203dff1292b26bfee5f72f867c4f6bc2064a8e07c06ef8 + checksum: 0e34186ca66cf3c537935d998cfb2ce59beaad31ccb9b41c2288618f386d72dc4359e15e8cb012525211d1f1d753fc439d6c7e9701d6ac801e1121cfa3223d69 languageName: node linkType: hard -"@docusaurus/utils-validation@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/utils-validation@npm:3.6.1" +"@docusaurus/utils-validation@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/utils-validation@npm:3.9.2" dependencies: - "@docusaurus/logger": "npm:3.6.1" - "@docusaurus/utils": "npm:3.6.1" - "@docusaurus/utils-common": "npm:3.6.1" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-common": "npm:3.9.2" fs-extra: "npm:^11.2.0" joi: "npm:^17.9.2" js-yaml: "npm:^4.1.0" lodash: "npm:^4.17.21" tslib: "npm:^2.6.0" - checksum: fd8852c4c1ce0fe5765f8c00ceaee683dacc07a8a54e9ac63a75902f2673c5b595798ef5c530f7a3bc22ae3b5619b553077f82c81c825fd035b46f4c2298a3d3 + checksum: 681b8c7fe0e2930affa388340f3db596a894affdb390e058277edd230181edca6f5593d37b48fb19c5077bbd5438549d944591f366b9f21ffff81feac1e1ae66 languageName: node linkType: hard -"@docusaurus/utils@npm:3.6.1": - version: 3.6.1 - resolution: "@docusaurus/utils@npm:3.6.1" +"@docusaurus/utils@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/utils@npm:3.9.2" dependencies: - "@docusaurus/logger": "npm:3.6.1" - "@docusaurus/types": "npm:3.6.1" - "@docusaurus/utils-common": "npm:3.6.1" - "@svgr/webpack": "npm:^8.1.0" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils-common": "npm:3.9.2" escape-string-regexp: "npm:^4.0.0" + execa: "npm:5.1.1" file-loader: "npm:^6.2.0" fs-extra: "npm:^11.1.1" github-slugger: "npm:^1.5.0" @@ -2191,14 +2882,14 @@ __metadata: js-yaml: "npm:^4.1.0" lodash: "npm:^4.17.21" micromatch: "npm:^4.0.5" + p-queue: "npm:^6.6.2" prompts: "npm:^2.4.2" resolve-pathname: "npm:^3.0.0" - shelljs: "npm:^0.8.5" tslib: "npm:^2.6.0" url-loader: "npm:^4.1.1" utility-types: "npm:^3.10.0" webpack: "npm:^5.88.1" - checksum: a97131caa6622df9d86378394d334f0751ea330361ed88b286a1accb3e5106030839d353eab9ab2ee88a615da1730da851c34783587b994480fc9f514413fc4a + checksum: 9796b2e7bc93e47cb27ce81185264c6390b56cd9e68831f6013e4418af512a736f1baf9b97e5df8d646ef4da0650151512abf598f5d58793a3e6c0833c80e06a languageName: node linkType: hard @@ -2406,6 +3097,75 @@ __metadata: languageName: node linkType: hard +"@jsonjoy.com/base64@npm:^1.1.2": + version: 1.1.2 + resolution: "@jsonjoy.com/base64@npm:1.1.2" + peerDependencies: + tslib: 2 + checksum: 88717945f66dc89bf58ce75624c99fe6a5c9a0c8614e26d03e406447b28abff80c69fb37dabe5aafef1862cf315071ae66e5c85f6018b437d95f8d13d235e6eb + languageName: node + linkType: hard + +"@jsonjoy.com/buffers@npm:^1.0.0, @jsonjoy.com/buffers@npm:^1.2.0": + version: 1.2.1 + resolution: "@jsonjoy.com/buffers@npm:1.2.1" + peerDependencies: + tslib: 2 + checksum: 5edaf761b78b730ae0598824adb37473fef5b40a8fc100625159700eb36e00057c5129c7ad15fc0e3178e8de58a044da65728e8d7b05fd3eed58e9b9a0d02b5a + languageName: node + linkType: hard + +"@jsonjoy.com/codegen@npm:^1.0.0": + version: 1.0.0 + resolution: "@jsonjoy.com/codegen@npm:1.0.0" + peerDependencies: + tslib: 2 + checksum: 54686352248440ad1484ce7db0270a5a72424fb9651b090e5f1c8e2cd8e55e6c7a3f67dfe4ed90c689cf01ed949e794764a8069f5f52510eaf0a2d0c41d324cd + languageName: node + linkType: hard + +"@jsonjoy.com/json-pack@npm:^1.11.0": + version: 1.21.0 + resolution: "@jsonjoy.com/json-pack@npm:1.21.0" + dependencies: + "@jsonjoy.com/base64": "npm:^1.1.2" + "@jsonjoy.com/buffers": "npm:^1.2.0" + "@jsonjoy.com/codegen": "npm:^1.0.0" + "@jsonjoy.com/json-pointer": "npm:^1.0.2" + "@jsonjoy.com/util": "npm:^1.9.0" + hyperdyperid: "npm:^1.2.0" + thingies: "npm:^2.5.0" + tree-dump: "npm:^1.1.0" + peerDependencies: + tslib: 2 + checksum: 0183eccccf2ab912389a6784ae81c1a7da48cf178902efe093fb60c457359c7c75da2803f869e0a1489f1342dfa4f8ab9b27b65adc9f44fd9646823773b71e9d + languageName: node + linkType: hard + +"@jsonjoy.com/json-pointer@npm:^1.0.2": + version: 1.0.2 + resolution: "@jsonjoy.com/json-pointer@npm:1.0.2" + dependencies: + "@jsonjoy.com/codegen": "npm:^1.0.0" + "@jsonjoy.com/util": "npm:^1.9.0" + peerDependencies: + tslib: 2 + checksum: 8d959c0fdd77d937d2a829270de51533bb9e3b887b3f6f02943884dc33dd79225071218c93f4bafdee6a3412fd5153264997953a86de444d85c1fff67915af54 + languageName: node + linkType: hard + +"@jsonjoy.com/util@npm:^1.9.0": + version: 1.9.0 + resolution: "@jsonjoy.com/util@npm:1.9.0" + dependencies: + "@jsonjoy.com/buffers": "npm:^1.0.0" + "@jsonjoy.com/codegen": "npm:^1.0.0" + peerDependencies: + tslib: 2 + checksum: a720a6accaae71fa9e7fa06e93e382702aa5760ef2bdc3bc45c19dc2228a01cc735d36cb970c654bc5e88f1328d55d1f0d5eceef0b76bcc327a2ce863e7b0021 + languageName: node + linkType: hard + "@leichtgewicht/ip-codec@npm:^2.0.1": version: 2.0.4 resolution: "@leichtgewicht/ip-codec@npm:2.0.4" @@ -2541,63 +3301,69 @@ __metadata: languageName: node linkType: hard -"@octokit/endpoint@npm:^9.0.1": - version: 9.0.5 - resolution: "@octokit/endpoint@npm:9.0.5" +"@octokit/endpoint@npm:^11.0.2": + version: 11.0.2 + resolution: "@octokit/endpoint@npm:11.0.2" dependencies: - "@octokit/types": "npm:^13.1.0" - universal-user-agent: "npm:^6.0.0" - checksum: e9bbb2111abe691c146075abb1b6f724a9b77fa8bfefdaaa82b8ebad6c8790e949f2367bb0b79800fef93ad72807513333e83e8ffba389bc85215535f63534d9 + "@octokit/types": "npm:^16.0.0" + universal-user-agent: "npm:^7.0.2" + checksum: 878ac12fbccff772968689b4744590677c5a3f12bebe31544832c84761bf1c6be521e8a3af07abffc9455a74dd4d1f350d714fc46fd7ce14a0a2b5f2d4e3a84c languageName: node linkType: hard -"@octokit/graphql@npm:^7.1.0": - version: 7.1.0 - resolution: "@octokit/graphql@npm:7.1.0" +"@octokit/graphql@npm:^9.0.3": + version: 9.0.3 + resolution: "@octokit/graphql@npm:9.0.3" dependencies: - "@octokit/request": "npm:^8.3.0" - "@octokit/types": "npm:^13.0.0" - universal-user-agent: "npm:^6.0.0" - checksum: 6d50a013d151f416fc837644e394e8b8872da7b17b181da119842ca569b0971e4dfacda55af6c329b51614e436945415dd5bd75eb3652055fdb754bbcd20d9d1 + "@octokit/request": "npm:^10.0.6" + "@octokit/types": "npm:^16.0.0" + universal-user-agent: "npm:^7.0.0" + checksum: 58588d3fb2834f64244fa5376ca7922a30117b001b621e141fab0d52806370803ab0c046ac99b120fa5f45b770f52a815157fb6ffc147fc6c1da4047c1f1af49 languageName: node linkType: hard -"@octokit/openapi-types@npm:^22.2.0": - version: 22.2.0 - resolution: "@octokit/openapi-types@npm:22.2.0" - checksum: a45bfc735611e836df0729f5922bbd5811d401052b972d1e3bc1278a2d2403e00f4552ce9d1f2793f77f167d212da559c5cb9f1b02c935114ad6d898779546ee +"@octokit/openapi-types@npm:^27.0.0": + version: 27.0.0 + resolution: "@octokit/openapi-types@npm:27.0.0" + checksum: 602d1de033da180a2e982cdbd3646bd5b2e16ecf36b9955a0f23e37ae9e6cb086abb48ff2ae6f2de000fce03e8ae9051794611ae4a95a8f5f6fb63276e7b8e31 languageName: node linkType: hard -"@octokit/request-error@npm:^5.1.0": - version: 5.1.0 - resolution: "@octokit/request-error@npm:5.1.0" +"@octokit/request-error@npm:^7.0.2": + version: 7.1.0 + resolution: "@octokit/request-error@npm:7.1.0" dependencies: - "@octokit/types": "npm:^13.1.0" - deprecation: "npm:^2.0.0" - once: "npm:^1.4.0" - checksum: 61e688abce17dd020ea1e343470b9758f294bfe5432c5cb24bdb5b9b10f90ecec1ecaaa13b48df9288409e0da14252f6579a20f609af155bd61dc778718b7738 + "@octokit/types": "npm:^16.0.0" + checksum: 62b90a54545c36a30b5ffdda42e302c751be184d85b68ffc7f1242c51d7ca54dbd185b7d0027b491991776923a910c85c9c51269fe0d86111bac187507a5abc4 languageName: node linkType: hard -"@octokit/request@npm:^8.3.0": - version: 8.4.0 - resolution: "@octokit/request@npm:8.4.0" +"@octokit/request@npm:^10.0.6": + version: 10.0.7 + resolution: "@octokit/request@npm:10.0.7" dependencies: - "@octokit/endpoint": "npm:^9.0.1" - "@octokit/request-error": "npm:^5.1.0" - "@octokit/types": "npm:^13.1.0" - universal-user-agent: "npm:^6.0.0" - checksum: b857782ac2ff5387e9cc502759de73ea642c498c97d06ad2ecd8a395e4b9532d9f3bc3fc460e0d3d0e8f0d43c917a90c493e43766d37782b3979d3afffbf1b4b + "@octokit/endpoint": "npm:^11.0.2" + "@octokit/request-error": "npm:^7.0.2" + "@octokit/types": "npm:^16.0.0" + fast-content-type-parse: "npm:^3.0.0" + universal-user-agent: "npm:^7.0.2" + checksum: f789a75bf681b204ccd3d538921db662e148ed980005158d80ec4f16811e9ab73f375d4f30ef697852abd748a62f025060ea1b0c5198ec9c2e8d04e355064390 languageName: node linkType: hard -"@octokit/types@npm:^13.0.0, @octokit/types@npm:^13.1.0": - version: 13.6.1 - resolution: "@octokit/types@npm:13.6.1" +"@octokit/types@npm:^16.0.0": + version: 16.0.0 + resolution: "@octokit/types@npm:16.0.0" dependencies: - "@octokit/openapi-types": "npm:^22.2.0" - checksum: 891334b5786ba6aef953384cec05d53e05132dd577c0c22db124d55eaa69609362d1e3147853b46e91bf226e046ba24d615c55214c8f8f4e7c3a5c38429b38e9 + "@octokit/openapi-types": "npm:^27.0.0" + checksum: b8d41098ba6fc194d13d641f9441347e3a3b96c0efabac0e14f57319340a2d4d1c8676e4cb37ab3062c5c323c617e790b0126916e9bf7b201b0cced0826f8ae2 + languageName: node + linkType: hard + +"@opentelemetry/api@npm:1.9.0": + version: 1.9.0 + resolution: "@opentelemetry/api@npm:1.9.0" + checksum: 9aae2fe6e8a3a3eeb6c1fdef78e1939cf05a0f37f8a4fae4d6bf2e09eb1e06f966ece85805626e01ba5fab48072b94f19b835449e58b6d26720ee19a58298add languageName: node linkType: hard @@ -2642,29 +3408,30 @@ __metadata: languageName: node linkType: hard -"@react-navigation/core@npm:^7.0.4": - version: 7.0.4 - resolution: "@react-navigation/core@npm:7.0.4" +"@react-navigation/core@npm:^7.13.5": + version: 7.13.5 + resolution: "@react-navigation/core@npm:7.13.5" dependencies: - "@react-navigation/routers": "npm:^7.0.0" + "@react-navigation/routers": "npm:^7.5.2" escape-string-regexp: "npm:^4.0.0" - nanoid: "npm:3.3.7" + fast-deep-equal: "npm:^3.1.3" + nanoid: "npm:^3.3.11" query-string: "npm:^7.1.3" - react-is: "npm:^18.2.0" - use-latest-callback: "npm:^0.2.1" - use-sync-external-store: "npm:^1.2.2" + react-is: "npm:^19.1.0" + use-latest-callback: "npm:^0.2.4" + use-sync-external-store: "npm:^1.5.0" peerDependencies: react: ">= 18.2.0" - checksum: 8df3fb6f3883b1e079460db4512f8e5d9f5fce0c1fa15f073837c5d38c06ccbe98495be90ad157191698b80c3745b0fc0ef6a2d648755cc07ab561c2b6376909 + checksum: b0c7b7b99dc3ccbf1efdcd6f50446511c26dd1989fbc183cc51839e8d86115dd06aba4fca0ee292e61735e66a1c4a874b163f7e02114a149b12b24b0259e1b60 languageName: node linkType: hard -"@react-navigation/routers@npm:^7.0.0": - version: 7.0.0 - resolution: "@react-navigation/routers@npm:7.0.0" +"@react-navigation/routers@npm:^7.5.2": + version: 7.5.2 + resolution: "@react-navigation/routers@npm:7.5.2" dependencies: - nanoid: "npm:3.3.7" - checksum: 942041a477c3597be6f50188d2eba03bfdd489acf67f9ec89db577bf6ece10dfff872b890a58c9d31d92d25fa75e04f6f1cb3033e70ababedef8ebefe0c47e60 + nanoid: "npm:^3.3.11" + checksum: eb22a8ce464595fc78d2f748d4397dfce5acae5f9b6afa9e0361142e69399ad8346019d1f6af7ba7a4404d54f71fc99e120ae113f5c827a4a6435bf201e349b5 languageName: node linkType: hard @@ -2835,10 +3602,10 @@ __metadata: languageName: node linkType: hard -"@sindresorhus/merge-streams@npm:^2.1.0": - version: 2.3.0 - resolution: "@sindresorhus/merge-streams@npm:2.3.0" - checksum: 69ee906f3125fb2c6bb6ec5cdd84e8827d93b49b3892bce8b62267116cc7e197b5cccf20c160a1d32c26014ecd14470a72a5e3ee37a58f1d6dadc0db1ccf3894 +"@sindresorhus/merge-streams@npm:^4.0.0": + version: 4.0.0 + resolution: "@sindresorhus/merge-streams@npm:4.0.0" + checksum: 482ee543629aa1933b332f811a1ae805a213681ecdd98c042b1c1b89387df63e7812248bb4df3910b02b3cc5589d3d73e4393f30e197c9dde18046ccd471fc6b languageName: node linkType: hard @@ -2853,6 +3620,13 @@ __metadata: languageName: node linkType: hard +"@standard-schema/spec@npm:^1.0.0": + version: 1.0.0 + resolution: "@standard-schema/spec@npm:1.0.0" + checksum: a1ab9a8bdc09b5b47aa8365d0e0ec40cc2df6437be02853696a0e377321653b0d3ac6f079a8c67d5ddbe9821025584b1fb71d9cc041a6666a96f1fadf2ece15f + languageName: node + linkType: hard + "@svgr/babel-plugin-add-jsx-attribute@npm:8.0.0": version: 8.0.0 resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:8.0.0" @@ -3132,91 +3906,91 @@ __metadata: languageName: node linkType: hard -"@swc/html-darwin-arm64@npm:1.9.1": - version: 1.9.1 - resolution: "@swc/html-darwin-arm64@npm:1.9.1" +"@swc/html-darwin-arm64@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/html-darwin-arm64@npm:1.15.3" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/html-darwin-x64@npm:1.9.1": - version: 1.9.1 - resolution: "@swc/html-darwin-x64@npm:1.9.1" +"@swc/html-darwin-x64@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/html-darwin-x64@npm:1.15.3" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/html-linux-arm-gnueabihf@npm:1.9.1": - version: 1.9.1 - resolution: "@swc/html-linux-arm-gnueabihf@npm:1.9.1" +"@swc/html-linux-arm-gnueabihf@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/html-linux-arm-gnueabihf@npm:1.15.3" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/html-linux-arm64-gnu@npm:1.9.1": - version: 1.9.1 - resolution: "@swc/html-linux-arm64-gnu@npm:1.9.1" +"@swc/html-linux-arm64-gnu@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/html-linux-arm64-gnu@npm:1.15.3" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/html-linux-arm64-musl@npm:1.9.1": - version: 1.9.1 - resolution: "@swc/html-linux-arm64-musl@npm:1.9.1" +"@swc/html-linux-arm64-musl@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/html-linux-arm64-musl@npm:1.15.3" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/html-linux-x64-gnu@npm:1.9.1": - version: 1.9.1 - resolution: "@swc/html-linux-x64-gnu@npm:1.9.1" +"@swc/html-linux-x64-gnu@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/html-linux-x64-gnu@npm:1.15.3" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/html-linux-x64-musl@npm:1.9.1": - version: 1.9.1 - resolution: "@swc/html-linux-x64-musl@npm:1.9.1" +"@swc/html-linux-x64-musl@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/html-linux-x64-musl@npm:1.15.3" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/html-win32-arm64-msvc@npm:1.9.1": - version: 1.9.1 - resolution: "@swc/html-win32-arm64-msvc@npm:1.9.1" +"@swc/html-win32-arm64-msvc@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/html-win32-arm64-msvc@npm:1.15.3" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/html-win32-ia32-msvc@npm:1.9.1": - version: 1.9.1 - resolution: "@swc/html-win32-ia32-msvc@npm:1.9.1" +"@swc/html-win32-ia32-msvc@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/html-win32-ia32-msvc@npm:1.15.3" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/html-win32-x64-msvc@npm:1.9.1": - version: 1.9.1 - resolution: "@swc/html-win32-x64-msvc@npm:1.9.1" +"@swc/html-win32-x64-msvc@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/html-win32-x64-msvc@npm:1.15.3" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@swc/html@npm:^1.7.39": - version: 1.9.1 - resolution: "@swc/html@npm:1.9.1" +"@swc/html@npm:^1.13.5": + version: 1.15.3 + resolution: "@swc/html@npm:1.15.3" dependencies: "@swc/counter": "npm:^0.1.3" - "@swc/html-darwin-arm64": "npm:1.9.1" - "@swc/html-darwin-x64": "npm:1.9.1" - "@swc/html-linux-arm-gnueabihf": "npm:1.9.1" - "@swc/html-linux-arm64-gnu": "npm:1.9.1" - "@swc/html-linux-arm64-musl": "npm:1.9.1" - "@swc/html-linux-x64-gnu": "npm:1.9.1" - "@swc/html-linux-x64-musl": "npm:1.9.1" - "@swc/html-win32-arm64-msvc": "npm:1.9.1" - "@swc/html-win32-ia32-msvc": "npm:1.9.1" - "@swc/html-win32-x64-msvc": "npm:1.9.1" + "@swc/html-darwin-arm64": "npm:1.15.3" + "@swc/html-darwin-x64": "npm:1.15.3" + "@swc/html-linux-arm-gnueabihf": "npm:1.15.3" + "@swc/html-linux-arm64-gnu": "npm:1.15.3" + "@swc/html-linux-arm64-musl": "npm:1.15.3" + "@swc/html-linux-x64-gnu": "npm:1.15.3" + "@swc/html-linux-x64-musl": "npm:1.15.3" + "@swc/html-win32-arm64-msvc": "npm:1.15.3" + "@swc/html-win32-ia32-msvc": "npm:1.15.3" + "@swc/html-win32-x64-msvc": "npm:1.15.3" dependenciesMeta: "@swc/html-darwin-arm64": optional: true @@ -3238,7 +4012,7 @@ __metadata: optional: true "@swc/html-win32-x64-msvc": optional: true - checksum: 44c69f1f4cd8b37f315338b2e9ea1c5afa0dd474dcf5a75df6e9a70ed3ed80481c14019307fd00fd104ad4c4c52bbe1e07adf617a478cfb835485ed0fc9f6fd8 + checksum: 79d1488dc260db238aa3442360acb8e9768e950f3b3e3e8fa8de479e5198fc90237a939aac5deb69dc541bdd404872de2e4c3f9d18611ad95096de5a979434e2 languageName: node linkType: hard @@ -3286,7 +4060,7 @@ __metadata: languageName: node linkType: hard -"@types/bonjour@npm:^3.5.9": +"@types/bonjour@npm:^3.5.13": version: 3.5.13 resolution: "@types/bonjour@npm:3.5.13" dependencies: @@ -3295,7 +4069,7 @@ __metadata: languageName: node linkType: hard -"@types/connect-history-api-fallback@npm:^1.3.5": +"@types/connect-history-api-fallback@npm:^1.5.4": version: 1.5.4 resolution: "@types/connect-history-api-fallback@npm:1.5.4" dependencies: @@ -3371,7 +4145,19 @@ __metadata: languageName: node linkType: hard -"@types/express@npm:*, @types/express@npm:^4.17.13": +"@types/express-serve-static-core@npm:^4.17.21": + version: 4.19.7 + resolution: "@types/express-serve-static-core@npm:4.19.7" + dependencies: + "@types/node": "npm:*" + "@types/qs": "npm:*" + "@types/range-parser": "npm:*" + "@types/send": "npm:*" + checksum: c239df87863b8515e68dcb18203a9e2ba6108f86fdc385090284464a57a6dca6abb60a961cb6a73fea2110576f4f8acefa1cb06b60d14b6b0e5104478e7d57d1 + languageName: node + linkType: hard + +"@types/express@npm:*": version: 4.17.21 resolution: "@types/express@npm:4.17.21" dependencies: @@ -3383,6 +4169,18 @@ __metadata: languageName: node linkType: hard +"@types/express@npm:^4.17.21": + version: 4.17.25 + resolution: "@types/express@npm:4.17.25" + dependencies: + "@types/body-parser": "npm:*" + "@types/express-serve-static-core": "npm:^4.17.33" + "@types/qs": "npm:*" + "@types/serve-static": "npm:^1" + checksum: f42b616d2c9dbc50352c820db7de182f64ebbfa8dba6fb6c98e5f8f0e2ef3edde0131719d9dc6874803d25ad9ca2d53471d0fec2fbc60a6003a43d015bab72c4 + languageName: node + linkType: hard + "@types/gtag.js@npm:^0.0.12": version: 0.0.12 resolution: "@types/gtag.js@npm:0.0.12" @@ -3461,13 +4259,20 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.4, @types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": +"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": version: 7.0.15 resolution: "@types/json-schema@npm:7.0.15" checksum: a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db languageName: node linkType: hard +"@types/katex@npm:^0.16.0": + version: 0.16.7 + resolution: "@types/katex@npm:0.16.7" + checksum: 68dcb9f68a90513ec78ca0196a142e15c2a2c270b1520d752bafd47a99207115085a64087b50140359017d7e9c870b3c68e7e4d36668c9e348a9ef0c48919b5a + languageName: node + linkType: hard + "@types/mdast@npm:^4.0.0, @types/mdast@npm:^4.0.2": version: 4.0.3 resolution: "@types/mdast@npm:4.0.3" @@ -3530,13 +4335,6 @@ __metadata: languageName: node linkType: hard -"@types/parse-json@npm:^4.0.0": - version: 4.0.2 - resolution: "@types/parse-json@npm:4.0.2" - checksum: b1b863ac34a2c2172fbe0807a1ec4d5cb684e48d422d15ec95980b81475fac4fdb3768a8b13eef39130203a7c04340fc167bae057c7ebcafd7dec9fe6c36aeb1 - languageName: node - linkType: hard - "@types/prismjs@npm:^1.26.0": version: 1.26.3 resolution: "@types/prismjs@npm:1.26.3" @@ -3608,10 +4406,10 @@ __metadata: languageName: node linkType: hard -"@types/retry@npm:0.12.0": - version: 0.12.0 - resolution: "@types/retry@npm:0.12.0" - checksum: 7c5c9086369826f569b83a4683661557cab1361bac0897a1cefa1a915ff739acd10ca0d62b01071046fe3f5a3f7f2aec80785fe283b75602dc6726781ea3e328 +"@types/retry@npm:0.12.2": + version: 0.12.2 + resolution: "@types/retry@npm:0.12.2" + checksum: 07481551a988cc90b423351919928b9ddcd14e3f5591cac3ab950851bb20646e55a10e89141b38bc3093d2056d4df73700b22ff2612976ac86a6367862381884 languageName: node linkType: hard @@ -3641,7 +4439,17 @@ __metadata: languageName: node linkType: hard -"@types/serve-index@npm:^1.9.1": +"@types/send@npm:<1": + version: 0.17.6 + resolution: "@types/send@npm:0.17.6" + dependencies: + "@types/mime": "npm:^1" + "@types/node": "npm:*" + checksum: a9d76797f0637738062f1b974e0fcf3d396a28c5dc18c3f95ecec5dabda82e223afbc2d56a0bca46b6326fd7bb229979916cea40de2270a98128fd94441b87c2 + languageName: node + linkType: hard + +"@types/serve-index@npm:^1.9.4": version: 1.9.4 resolution: "@types/serve-index@npm:1.9.4" dependencies: @@ -3650,7 +4458,7 @@ __metadata: languageName: node linkType: hard -"@types/serve-static@npm:*, @types/serve-static@npm:^1.13.10": +"@types/serve-static@npm:*": version: 1.15.5 resolution: "@types/serve-static@npm:1.15.5" dependencies: @@ -3661,7 +4469,18 @@ __metadata: languageName: node linkType: hard -"@types/sockjs@npm:^0.3.33": +"@types/serve-static@npm:^1, @types/serve-static@npm:^1.15.5": + version: 1.15.10 + resolution: "@types/serve-static@npm:1.15.10" + dependencies: + "@types/http-errors": "npm:*" + "@types/node": "npm:*" + "@types/send": "npm:<1" + checksum: 842fca14c9e80468f89b6cea361773f2dcd685d4616a9f59013b55e1e83f536e4c93d6d8e3ba5072d40c4e7e64085210edd6646b15d538ded94512940a23021f + languageName: node + linkType: hard + +"@types/sockjs@npm:^0.3.36": version: 0.3.36 resolution: "@types/sockjs@npm:0.3.36" dependencies: @@ -3684,12 +4503,12 @@ __metadata: languageName: node linkType: hard -"@types/ws@npm:^8.5.5": - version: 8.5.10 - resolution: "@types/ws@npm:8.5.10" +"@types/ws@npm:^8.5.10": + version: 8.18.1 + resolution: "@types/ws@npm:8.18.1" dependencies: "@types/node": "npm:*" - checksum: e9af279b984c4a04ab53295a40aa95c3e9685f04888df5c6920860d1dd073fcc57c7bd33578a04b285b2c655a0b52258d34bee0a20569dca8defb8393e1e5d29 + checksum: 61aff1129143fcc4312f083bc9e9e168aa3026b7dd6e70796276dcfb2c8211c4292603f9c4864fae702f2ed86e4abd4d38aa421831c2fd7f856c931a481afbab languageName: node linkType: hard @@ -3716,6 +4535,13 @@ __metadata: languageName: node linkType: hard +"@vercel/oidc@npm:3.0.5": + version: 3.0.5 + resolution: "@vercel/oidc@npm:3.0.5" + checksum: a63f0ab226f9070f974334014bd2676611a2d13473c10b867e3d9db8a2cc83637ae7922db26b184dd97b5945e144fc211c8f899642d205517e5b4e0e34f05b0e + languageName: node + linkType: hard + "@webassemblyjs/ast@npm:1.14.1, @webassemblyjs/ast@npm:^1.12.1": version: 1.14.1 resolution: "@webassemblyjs/ast@npm:1.14.1" @@ -3932,7 +4758,7 @@ __metadata: languageName: node linkType: hard -"address@npm:^1.0.1, address@npm:^1.1.2": +"address@npm:^1.0.1": version: 1.2.2 resolution: "address@npm:1.2.2" checksum: 1c8056b77fb124456997b78ed682ecc19d2fd7ea8bd5850a2aa8c3e3134c913847c57bcae418622efd32ba858fa1e242a40a251ac31da0515664fc0ac03a047d @@ -3958,6 +4784,20 @@ __metadata: languageName: node linkType: hard +"ai@npm:5.0.108, ai@npm:^5.0.30": + version: 5.0.108 + resolution: "ai@npm:5.0.108" + dependencies: + "@ai-sdk/gateway": "npm:2.0.18" + "@ai-sdk/provider": "npm:2.0.0" + "@ai-sdk/provider-utils": "npm:3.0.18" + "@opentelemetry/api": "npm:1.9.0" + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + checksum: 5eab03a516d611f528600b13709470082e1e62838cba436a09ece1dd8a07473db481ab636471a5e3d69608806a46e7db44a2b4b0d48df93d8d24bb3c82de9a96 + languageName: node + linkType: hard + "ajv-formats@npm:^2.1.1": version: 2.1.1 resolution: "ajv-formats@npm:2.1.1" @@ -3972,7 +4812,7 @@ __metadata: languageName: node linkType: hard -"ajv-keywords@npm:^3.4.1, ajv-keywords@npm:^3.5.2": +"ajv-keywords@npm:^3.5.2": version: 3.5.2 resolution: "ajv-keywords@npm:3.5.2" peerDependencies: @@ -3992,7 +4832,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^6.12.2, ajv@npm:^6.12.5": +"ajv@npm:^6.12.5": version: 6.12.6 resolution: "ajv@npm:6.12.6" dependencies: @@ -4016,36 +4856,36 @@ __metadata: languageName: node linkType: hard -"algoliasearch-helper@npm:^3.13.3": - version: 3.15.0 - resolution: "algoliasearch-helper@npm:3.15.0" +"algoliasearch-helper@npm:^3.26.0": + version: 3.26.1 + resolution: "algoliasearch-helper@npm:3.26.1" dependencies: "@algolia/events": "npm:^4.0.1" peerDependencies: algoliasearch: ">= 3.1 < 6" - checksum: a277f6d9d98184ea94c166995990f0b12e25d5af69fad50f1014658382408985afd31bd3d6d1ae1b68afe0af7c0e6b7e397172c08d1cd5f82af5b312cd515f15 + checksum: 506bbd63041ee593eacd02cc88294961eb8b4fbd63a45bb6aeb5795de79c243ab5a0c2f121ed056d344cad0146bba8a40fe3729d12315803d895335790dba059 languageName: node linkType: hard -"algoliasearch@npm:^4.18.0, algoliasearch@npm:^4.19.1": - version: 4.20.0 - resolution: "algoliasearch@npm:4.20.0" +"algoliasearch@npm:^5.28.0, algoliasearch@npm:^5.37.0": + version: 5.46.0 + resolution: "algoliasearch@npm:5.46.0" dependencies: - "@algolia/cache-browser-local-storage": "npm:4.20.0" - "@algolia/cache-common": "npm:4.20.0" - "@algolia/cache-in-memory": "npm:4.20.0" - "@algolia/client-account": "npm:4.20.0" - "@algolia/client-analytics": "npm:4.20.0" - "@algolia/client-common": "npm:4.20.0" - "@algolia/client-personalization": "npm:4.20.0" - "@algolia/client-search": "npm:4.20.0" - "@algolia/logger-common": "npm:4.20.0" - "@algolia/logger-console": "npm:4.20.0" - "@algolia/requester-browser-xhr": "npm:4.20.0" - "@algolia/requester-common": "npm:4.20.0" - "@algolia/requester-node-http": "npm:4.20.0" - "@algolia/transporter": "npm:4.20.0" - checksum: 39c1e5391560ba019a845440c00f770e41b3462860214f45b678f976e3de61108eb7abafab610f26adde7d3057df1f8f65d465bcd114612546b935880e43f1dd + "@algolia/abtesting": "npm:1.12.0" + "@algolia/client-abtesting": "npm:5.46.0" + "@algolia/client-analytics": "npm:5.46.0" + "@algolia/client-common": "npm:5.46.0" + "@algolia/client-insights": "npm:5.46.0" + "@algolia/client-personalization": "npm:5.46.0" + "@algolia/client-query-suggestions": "npm:5.46.0" + "@algolia/client-search": "npm:5.46.0" + "@algolia/ingestion": "npm:1.46.0" + "@algolia/monitoring": "npm:1.46.0" + "@algolia/recommend": "npm:5.46.0" + "@algolia/requester-browser-xhr": "npm:5.46.0" + "@algolia/requester-fetch": "npm:5.46.0" + "@algolia/requester-node-http": "npm:5.46.0" + checksum: 0feeec48ae4cb4a9bc9d49fbd2a227f9ece3505840a6a8f6abba772eec97ba4e027c2fa784e4a56e218a8b3d5b0d94159d7d179de08bc2d40c46c862ebafef99 languageName: node linkType: hard @@ -4155,13 +4995,6 @@ __metadata: languageName: node linkType: hard -"array-flatten@npm:^2.1.2": - version: 2.1.2 - resolution: "array-flatten@npm:2.1.2" - checksum: bdc1cee68e41bec9cfc1161408734e2269428ef371445606bce4e6241001e138a94b9a617cc9a5b4b7fe6a3a51e3d5a942646975ce82a2e202ccf3e9b478c82f - languageName: node - linkType: hard - "array-union@npm:^2.1.0": version: 2.1.0 resolution: "array-union@npm:2.1.0" @@ -4187,14 +5020,21 @@ __metadata: languageName: node linkType: hard -"at-least-node@npm:^1.0.0": +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 669a32c2cb7e45091330c680e92eaeb791bc1d4132d827591e499cd1f776ff5a873e77e5f92d0ce795a8d60f10761dec9ddfe7225a5de680f5d357f67b1aac73 + languageName: node + linkType: hard + +"async-generator-function@npm:^1.0.0": version: 1.0.0 - resolution: "at-least-node@npm:1.0.0" - checksum: 4c058baf6df1bc5a1697cf182e2029c58cd99975288a13f9e70068ef5d6f4e1f1fd7c4d2c3c4912eae44797d1725be9700995736deca441b39f3e66d8dee97ef + resolution: "async-generator-function@npm:1.0.0" + checksum: 2c50ef856c543ad500d8d8777d347e3c1ba623b93e99c9263ecc5f965c1b12d2a140e2ab6e43c3d0b85366110696f28114649411cbcd10b452a92a2318394186 languageName: node linkType: hard -"autoprefixer@npm:^10.4.14, autoprefixer@npm:^10.4.19": +"autoprefixer@npm:^10.4.19": version: 10.4.20 resolution: "autoprefixer@npm:10.4.20" dependencies: @@ -4212,6 +5052,24 @@ __metadata: languageName: node linkType: hard +"autoprefixer@npm:^10.4.22": + version: 10.4.22 + resolution: "autoprefixer@npm:10.4.22" + dependencies: + browserslist: "npm:^4.27.0" + caniuse-lite: "npm:^1.0.30001754" + fraction.js: "npm:^5.3.4" + normalize-range: "npm:^0.1.2" + picocolors: "npm:^1.1.1" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.1.0 + bin: + autoprefixer: bin/autoprefixer + checksum: 2ae8d135af2deaaa5065a3a466c877787373c0ed766b8a8e8259d7871db79c1a7e1d9f6c9541c54fa95647511d3c2066bb08a30160e58c9bfa75506f9c18f3aa + languageName: node + linkType: hard + "babel-loader@npm:^9.2.1": version: 9.2.1 resolution: "babel-loader@npm:9.2.1" @@ -4284,6 +5142,15 @@ __metadata: languageName: node linkType: hard +"baseline-browser-mapping@npm:^2.9.0": + version: 2.9.4 + resolution: "baseline-browser-mapping@npm:2.9.4" + bin: + baseline-browser-mapping: dist/cli.js + checksum: 5f33f2505130a234d70b332e3dbedf79655fa2be8287c4f4ea2dd0caca9e1c0c2598d8f5c3798e632382404a414de2b47710ec06b6741deb5e1236eb0b62803b + languageName: node + linkType: hard + "batch@npm:0.6.1": version: 0.6.1 resolution: "batch@npm:0.6.1" @@ -4305,35 +5172,33 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:1.20.1": - version: 1.20.1 - resolution: "body-parser@npm:1.20.1" +"body-parser@npm:~1.20.3": + version: 1.20.4 + resolution: "body-parser@npm:1.20.4" dependencies: - bytes: "npm:3.1.2" - content-type: "npm:~1.0.4" + bytes: "npm:~3.1.2" + content-type: "npm:~1.0.5" debug: "npm:2.6.9" depd: "npm:2.0.0" - destroy: "npm:1.2.0" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.4.24" - on-finished: "npm:2.4.1" - qs: "npm:6.11.0" - raw-body: "npm:2.5.1" + destroy: "npm:~1.2.0" + http-errors: "npm:~2.0.1" + iconv-lite: "npm:~0.4.24" + on-finished: "npm:~2.4.1" + qs: "npm:~6.14.0" + raw-body: "npm:~2.5.3" type-is: "npm:~1.6.18" - unpipe: "npm:1.0.0" - checksum: a202d493e2c10a33fb7413dac7d2f713be579c4b88343cd814b6df7a38e5af1901fc31044e04de176db56b16d9772aa25a7723f64478c20f4d91b1ac223bf3b8 + unpipe: "npm:~1.0.0" + checksum: 569c1e896297d1fcd8f34026c8d0ab70b90d45343c15c5d8dff5de2bad08125fc1e2f8c2f3f4c1ac6c0caaad115218202594d37dcb8d89d9b5dcae1c2b736aa9 languageName: node linkType: hard -"bonjour-service@npm:^1.0.11": - version: 1.1.1 - resolution: "bonjour-service@npm:1.1.1" +"bonjour-service@npm:^1.2.1": + version: 1.3.0 + resolution: "bonjour-service@npm:1.3.0" dependencies: - array-flatten: "npm:^2.1.2" - dns-equal: "npm:^1.0.0" fast-deep-equal: "npm:^3.1.3" multicast-dns: "npm:^7.2.5" - checksum: 8dd3fef3ff8a11678d8f586be03c85004a45bae4353c55d7dbffe288cad73ddb38dee08b57425b9945c9a3a840d50bd40ae5aeda0066186dabe4b84a315b4e05 + checksum: 5721fd9f9bb968e9cc16c1e8116d770863dd2329cb1f753231de1515870648c225142b7eefa71f14a5c22bc7b37ddd7fdeb018700f28a8c936d50d4162d433c7 languageName: node linkType: hard @@ -4404,7 +5269,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.0.0, browserslist@npm:^4.18.1, browserslist@npm:^4.23.0, browserslist@npm:^4.23.3, browserslist@npm:^4.24.0, browserslist@npm:^4.24.2": +"browserslist@npm:^4.0.0, browserslist@npm:^4.23.0, browserslist@npm:^4.23.3, browserslist@npm:^4.24.0, browserslist@npm:^4.24.2": version: 4.24.2 resolution: "browserslist@npm:4.24.2" dependencies: @@ -4418,6 +5283,21 @@ __metadata: languageName: node linkType: hard +"browserslist@npm:^4.27.0, browserslist@npm:^4.28.0": + version: 4.28.1 + resolution: "browserslist@npm:4.28.1" + dependencies: + baseline-browser-mapping: "npm:^2.9.0" + caniuse-lite: "npm:^1.0.30001759" + electron-to-chromium: "npm:^1.5.263" + node-releases: "npm:^2.0.27" + update-browserslist-db: "npm:^1.2.0" + bin: + browserslist: cli.js + checksum: 545a5fa9d7234e3777a7177ec1e9134bb2ba60a69e6b95683f6982b1473aad347c77c1264ccf2ac5dea609a9731fbfbda6b85782bdca70f80f86e28a402504bd + languageName: node + linkType: hard + "buffer-from@npm:^1.0.0": version: 1.1.2 resolution: "buffer-from@npm:1.1.2" @@ -4425,6 +5305,15 @@ __metadata: languageName: node linkType: hard +"bundle-name@npm:^4.1.0": + version: 4.1.0 + resolution: "bundle-name@npm:4.1.0" + dependencies: + run-applescript: "npm:^7.0.0" + checksum: 8e575981e79c2bcf14d8b1c027a3775c095d362d1382312f444a7c861b0e21513c0bd8db5bd2b16e50ba0709fa622d4eab6b53192d222120305e68359daece29 + languageName: node + linkType: hard + "bytes@npm:3.0.0": version: 3.0.0 resolution: "bytes@npm:3.0.0" @@ -4432,7 +5321,7 @@ __metadata: languageName: node linkType: hard -"bytes@npm:3.1.2": +"bytes@npm:~3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" checksum: 76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e @@ -4481,7 +5370,17 @@ __metadata: languageName: node linkType: hard -"call-bind@npm:^1.0.0, call-bind@npm:^1.0.2": +"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938 + languageName: node + linkType: hard + +"call-bind@npm:^1.0.2": version: 1.0.5 resolution: "call-bind@npm:1.0.5" dependencies: @@ -4492,6 +5391,16 @@ __metadata: languageName: node linkType: hard +"call-bound@npm:^1.0.2": + version: 1.0.4 + resolution: "call-bound@npm:1.0.4" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + get-intrinsic: "npm:^1.3.0" + checksum: f4796a6a0941e71c766aea672f63b72bc61234c4f4964dc6d7606e3664c307e7d77845328a8f3359ce39ddb377fed67318f9ee203dea1d47e46165dcf2917644 + languageName: node + linkType: hard + "callsites@npm:^3.0.0": version: 3.1.0 resolution: "callsites@npm:3.1.0" @@ -4535,17 +5444,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.0": - version: 1.0.30001677 - resolution: "caniuse-lite@npm:1.0.30001677" - checksum: 22b4aa738b213b5d0bc820c26ba23fa265ca90a5c59776e1a686b9ab6fff9120d0825fd920c0a601a4b65056ef40d01548405feb95c8dd6083255f50c71a0864 - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001616, caniuse-lite@npm:^1.0.30001646, caniuse-lite@npm:^1.0.30001669": - version: 1.0.30001679 - resolution: "caniuse-lite@npm:1.0.30001679" - checksum: 87fb89c5cb5130e40fa97b110fe175ea1104c359e4882aa5e277f824fbd33aa024f26d41a25f7d214db985f43d5b148c44e363965d17b36660b126a03e75e6e0 +"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001616, caniuse-lite@npm:^1.0.30001646, caniuse-lite@npm:^1.0.30001669, caniuse-lite@npm:^1.0.30001754, caniuse-lite@npm:^1.0.30001759": + version: 1.0.30001759 + resolution: "caniuse-lite@npm:1.0.30001759" + checksum: b0f415960ba34995cda18e0d25c4e602f6917b9179290a76bdd0311423505b78cc93e558a90c98a22a1cc6b1781ab720ef6beea24ec7e29a1c1164ca72eac3a2 languageName: node linkType: hard @@ -4567,7 +5469,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2": +"chalk@npm:^4.0.0, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -4648,7 +5550,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^3.4.2, chokidar@npm:^3.5.3": +"chokidar@npm:^3.5.3": version: 3.5.3 resolution: "chokidar@npm:3.5.3" dependencies: @@ -4667,6 +5569,25 @@ __metadata: languageName: node linkType: hard +"chokidar@npm:^3.6.0": + version: 3.6.0 + resolution: "chokidar@npm:3.6.0" + dependencies: + anymatch: "npm:~3.1.2" + braces: "npm:~3.0.2" + fsevents: "npm:~2.3.2" + glob-parent: "npm:~5.1.2" + is-binary-path: "npm:~2.1.0" + is-glob: "npm:~4.0.1" + normalize-path: "npm:~3.0.0" + readdirp: "npm:~3.6.0" + dependenciesMeta: + fsevents: + optional: true + checksum: 8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462 + languageName: node + linkType: hard + "chownr@npm:^2.0.0": version: 2.0.0 resolution: "chownr@npm:2.0.0" @@ -4688,7 +5609,7 @@ __metadata: languageName: node linkType: hard -"clean-css@npm:^5.2.2, clean-css@npm:^5.3.2, clean-css@npm:~5.3.2": +"clean-css@npm:^5.2.2, clean-css@npm:~5.3.2": version: 5.3.2 resolution: "clean-css@npm:5.3.2" dependencies: @@ -4697,6 +5618,15 @@ __metadata: languageName: node linkType: hard +"clean-css@npm:^5.3.3": + version: 5.3.3 + resolution: "clean-css@npm:5.3.3" + dependencies: + source-map: "npm:~0.6.0" + checksum: 381de7523e23f3762eb180e327dcc0cedafaf8cb1cd8c26b7cc1fc56e0829a92e734729c4f955394d65ed72fb62f82d8baf78af34b33b8a7d41ebad2accdd6fb + languageName: node + linkType: hard + "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -4926,7 +5856,7 @@ __metadata: languageName: node linkType: hard -"content-disposition@npm:0.5.4": +"content-disposition@npm:~0.5.4": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" dependencies: @@ -4935,7 +5865,7 @@ __metadata: languageName: node linkType: hard -"content-type@npm:~1.0.4": +"content-type@npm:~1.0.4, content-type@npm:~1.0.5": version: 1.0.5 resolution: "content-type@npm:1.0.5" checksum: b76ebed15c000aee4678c3707e0860cb6abd4e680a598c0a26e17f0bfae723ec9cc2802f0ff1bc6e4d80603719010431d2231018373d4dde10f9ccff9dadf5af @@ -4949,24 +5879,17 @@ __metadata: languageName: node linkType: hard -"cookie-signature@npm:1.0.6": - version: 1.0.6 - resolution: "cookie-signature@npm:1.0.6" - checksum: b36fd0d4e3fef8456915fcf7742e58fbfcc12a17a018e0eb9501c9d5ef6893b596466f03b0564b81af29ff2538fd0aa4b9d54fe5ccbfb4c90ea50ad29fe2d221 - languageName: node - linkType: hard - -"cookie@npm:0.5.0": - version: 0.5.0 - resolution: "cookie@npm:0.5.0" - checksum: c01ca3ef8d7b8187bae434434582288681273b5a9ed27521d4d7f9f7928fe0c920df0decd9f9d3bbd2d14ac432b8c8cf42b98b3bdd5bfe0e6edddeebebe8b61d +"cookie-signature@npm:~1.0.6": + version: 1.0.7 + resolution: "cookie-signature@npm:1.0.7" + checksum: e7731ad2995ae2efeed6435ec1e22cdd21afef29d300c27281438b1eab2bae04ef0d1a203928c0afec2cee72aa36540b8747406ebe308ad23c8e8cc3c26c9c51 languageName: node linkType: hard -"copy-text-to-clipboard@npm:^3.2.0": - version: 3.2.0 - resolution: "copy-text-to-clipboard@npm:3.2.0" - checksum: d60fdadc59d526e19d56ad23cec2b292d33c771a5091621bd322d138804edd3c10eb2367d46ec71b39f5f7f7116a2910b332281aeb36a5b679199d746a8a5381 +"cookie@npm:~0.7.1": + version: 0.7.2 + resolution: "cookie@npm:0.7.2" + checksum: 9596e8ccdbf1a3a88ae02cf5ee80c1c50959423e1022e4e60b91dd87c622af1da309253d8abdb258fb5e3eacb4f08e579dc58b4897b8087574eee0fd35dfa5d2 languageName: node linkType: hard @@ -5016,20 +5939,7 @@ __metadata: languageName: node linkType: hard -"cosmiconfig@npm:^6.0.0": - version: 6.0.0 - resolution: "cosmiconfig@npm:6.0.0" - dependencies: - "@types/parse-json": "npm:^4.0.0" - import-fresh: "npm:^3.1.0" - parse-json: "npm:^5.0.0" - path-type: "npm:^4.0.0" - yaml: "npm:^1.7.2" - checksum: 666ed8732d0bf7d7fe6f8516c8ee6041e0622032e8fa26201577b883d2767ad105d03f38b34b93d1f02f26b22a89e7bab4443b9d2e7f931f48d0e944ffa038b5 - languageName: node - linkType: hard - -"cosmiconfig@npm:^8.1.3, cosmiconfig@npm:^8.2.0": +"cosmiconfig@npm:^8.1.3, cosmiconfig@npm:^8.3.5": version: 8.3.6 resolution: "cosmiconfig@npm:8.3.6" dependencies: @@ -5066,6 +5976,17 @@ __metadata: languageName: node linkType: hard +"css-blank-pseudo@npm:^7.0.1": + version: 7.0.1 + resolution: "css-blank-pseudo@npm:7.0.1" + dependencies: + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 46c3d3a611972fdb0c264db7c0b34fe437bc4300961d11945145cf04962f52a545a6ef55bc8ff4afd82b605bd692b4970f2b54582616dea00441105e725d4618 + languageName: node + linkType: hard + "css-declaration-sorter@npm:^7.2.0": version: 7.2.0 resolution: "css-declaration-sorter@npm:7.2.0" @@ -5075,21 +5996,40 @@ __metadata: languageName: node linkType: hard -"css-loader@npm:^6.8.1": - version: 6.8.1 - resolution: "css-loader@npm:6.8.1" +"css-has-pseudo@npm:^7.0.3": + version: 7.0.3 + resolution: "css-has-pseudo@npm:7.0.3" + dependencies: + "@csstools/selector-specificity": "npm:^5.0.0" + postcss-selector-parser: "npm:^7.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: c89f68e17bed229e9a3e98da5032e1360c83d45d974bc3fb8d6b5358399bca80cce7929e4a621a516a75536edb78678dc486eb41841eeed28cca79e3be4bdc27 + languageName: node + linkType: hard + +"css-loader@npm:^6.11.0": + version: 6.11.0 + resolution: "css-loader@npm:6.11.0" dependencies: icss-utils: "npm:^5.1.0" - postcss: "npm:^8.4.21" - postcss-modules-extract-imports: "npm:^3.0.0" - postcss-modules-local-by-default: "npm:^4.0.3" - postcss-modules-scope: "npm:^3.0.0" + postcss: "npm:^8.4.33" + postcss-modules-extract-imports: "npm:^3.1.0" + postcss-modules-local-by-default: "npm:^4.0.5" + postcss-modules-scope: "npm:^3.2.0" postcss-modules-values: "npm:^4.0.0" postcss-value-parser: "npm:^4.2.0" - semver: "npm:^7.3.8" + semver: "npm:^7.5.4" peerDependencies: + "@rspack/core": 0.x || 1.x webpack: ^5.0.0 - checksum: a6e23de4ec1d2832f10b8ca3cfec6b6097a97ca3c73f64338ae5cd110ac270f1b218ff0273d39f677a7a561f1a9d9b0d332274664d0991bcfafaae162c2669c4 + peerDependenciesMeta: + "@rspack/core": + optional: true + webpack: + optional: true + checksum: bb52434138085fed06a33e2ffbdae9ee9014ad23bf60f59d6b7ee67f28f26c6b1764024d3030bd19fd884d6ee6ee2224eaed64ad19eb18fbbb23d148d353a965 languageName: node linkType: hard @@ -5122,6 +6062,15 @@ __metadata: languageName: node linkType: hard +"css-prefers-color-scheme@npm:^10.0.0": + version: 10.0.0 + resolution: "css-prefers-color-scheme@npm:10.0.0" + peerDependencies: + postcss: ^8.4 + checksum: a66c727bb2455328b18862f720819fc98ff5c1486b69f758bdb5c66f46cc6d484f9fc0bfa4f00f2693c5da6707ad136ca789496982f713ade693f08af624930e + languageName: node + linkType: hard + "css-select@npm:^4.1.3": version: 4.3.0 resolution: "css-select@npm:4.3.0" @@ -5175,6 +6124,13 @@ __metadata: languageName: node linkType: hard +"cssdb@npm:^8.5.2": + version: 8.5.2 + resolution: "cssdb@npm:8.5.2" + checksum: 12f7ed29dda0d74b209d6470acd246b335aac507c2786c17f20709f856eabb24e6d43ff44507898f5a1b0a101b286b997d95682e44b06f4c7cb4bd7081db7c32 + languageName: node + linkType: hard + "cssesc@npm:^3.0.0": version: 3.0.0 resolution: "cssesc@npm:3.0.0" @@ -5285,7 +6241,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:2.6.9, debug@npm:^2.6.0": +"debug@npm:2.6.9": version: 2.6.9 resolution: "debug@npm:2.6.9" dependencies: @@ -5350,19 +6306,27 @@ __metadata: languageName: node linkType: hard -"deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.1": +"deepmerge@npm:^4.3.1": version: 4.3.1 resolution: "deepmerge@npm:4.3.1" checksum: e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044 languageName: node linkType: hard -"default-gateway@npm:^6.0.3": - version: 6.0.3 - resolution: "default-gateway@npm:6.0.3" +"default-browser-id@npm:^5.0.0": + version: 5.0.1 + resolution: "default-browser-id@npm:5.0.1" + checksum: 5288b3094c740ef3a86df9b999b04ff5ba4dee6b64e7b355c0fff5217752c8c86908d67f32f6cba9bb4f9b7b61a1b640c0a4f9e34c57e0ff3493559a625245ee + languageName: node + linkType: hard + +"default-browser@npm:^5.2.1": + version: 5.4.0 + resolution: "default-browser@npm:5.4.0" dependencies: - execa: "npm:^5.0.0" - checksum: 5184f9e6e105d24fb44ade9e8741efa54bb75e84625c1ea78c4ef8b81dff09ca52d6dbdd1185cf0dc655bb6b282a64fffaf7ed2dd561b8d9ad6f322b1f039aba + bundle-name: "npm:^4.1.0" + default-browser-id: "npm:^5.0.0" + checksum: a49ddd0c7b1a319163f64a5fc68ebb45a98548ea23a3155e04518f026173d85cfa2f451b646366c36c8f70b01e4cb773e23d1d22d2c61d8b84e5fbf151b4b609 languageName: node linkType: hard @@ -5391,6 +6355,13 @@ __metadata: languageName: node linkType: hard +"define-lazy-prop@npm:^3.0.0": + version: 3.0.0 + resolution: "define-lazy-prop@npm:3.0.0" + checksum: 5ab0b2bf3fa58b3a443140bbd4cd3db1f91b985cc8a246d330b9ac3fc0b6a325a6d82bddc0b055123d745b3f9931afeea74a5ec545439a1630b9c8512b0eeb49 + languageName: node + linkType: hard + "define-properties@npm:^1.1.4": version: 1.2.1 resolution: "define-properties@npm:1.2.1" @@ -5402,23 +6373,7 @@ __metadata: languageName: node linkType: hard -"del@npm:^6.1.1": - version: 6.1.1 - resolution: "del@npm:6.1.1" - dependencies: - globby: "npm:^11.0.1" - graceful-fs: "npm:^4.2.4" - is-glob: "npm:^4.0.1" - is-path-cwd: "npm:^2.2.0" - is-path-inside: "npm:^3.0.2" - p-map: "npm:^4.0.0" - rimraf: "npm:^3.0.2" - slash: "npm:^3.0.0" - checksum: 8a095c5ccade42c867a60252914ae485ec90da243d735d1f63ec1e64c1cfbc2b8810ad69a29ab6326d159d4fddaa2f5bad067808c42072351ec458efff86708f - languageName: node - linkType: hard - -"depd@npm:2.0.0": +"depd@npm:2.0.0, depd@npm:~2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" checksum: 58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c @@ -5432,21 +6387,14 @@ __metadata: languageName: node linkType: hard -"deprecation@npm:^2.0.0": - version: 2.3.1 - resolution: "deprecation@npm:2.3.1" - checksum: 23d688ba66b74d09b908c40a76179418acbeeb0bfdf218c8075c58ad8d0c315130cb91aa3dffb623aa3a411a3569ce56c6460de6c8d69071c17fe6dd2442f032 - languageName: node - linkType: hard - -"dequal@npm:^2.0.0": +"dequal@npm:^2.0.0, dequal@npm:^2.0.3": version: 2.0.3 resolution: "dequal@npm:2.0.3" checksum: f98860cdf58b64991ae10205137c0e97d384c3a4edc7f807603887b7c4b850af1224a33d88012009f150861cbee4fa2d322c4cc04b9313bee312e47f6ecaa888 languageName: node linkType: hard -"destroy@npm:1.2.0": +"destroy@npm:1.2.0, destroy@npm:~1.2.0": version: 1.2.0 resolution: "destroy@npm:1.2.0" checksum: bd7633942f57418f5a3b80d5cb53898127bcf53e24cdf5d5f4396be471417671f0fee48a4ebe9a1e9defbde2a31280011af58a57e090ff822f589b443ed4e643 @@ -5469,19 +6417,6 @@ __metadata: languageName: node linkType: hard -"detect-port-alt@npm:^1.1.6": - version: 1.1.6 - resolution: "detect-port-alt@npm:1.1.6" - dependencies: - address: "npm:^1.0.1" - debug: "npm:^2.6.0" - bin: - detect: ./bin/detect-port - detect-port: ./bin/detect-port - checksum: 7269e6aef7b782d98c77505c07a7a0f5e2ee98a9607dc791035fc0192fc58aa03cc833fae605e10eaf239a2a5a55cd938e0bb141dea764ac6180ca082fd62b23 - languageName: node - linkType: hard - "detect-port@npm:^1.5.1": version: 1.5.1 resolution: "detect-port@npm:1.5.1" @@ -5513,13 +6448,6 @@ __metadata: languageName: node linkType: hard -"dns-equal@npm:^1.0.0": - version: 1.0.0 - resolution: "dns-equal@npm:1.0.0" - checksum: da966e5275ac50546e108af6bc29aaae2164d2ae96d60601b333c4a3aff91f50b6ca14929cf91f20a9cad1587b356323e300cea3ff6588a6a816988485f445f1 - languageName: node - linkType: hard - "dns-packet@npm:^5.2.2": version: 5.6.1 resolution: "dns-packet@npm:5.6.1" @@ -5626,6 +6554,17 @@ __metadata: languageName: node linkType: hard +"dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031 + languageName: node + linkType: hard + "duplexer@npm:^0.1.2": version: 0.1.2 resolution: "duplexer@npm:0.1.2" @@ -5647,6 +6586,13 @@ __metadata: languageName: node linkType: hard +"electron-to-chromium@npm:^1.5.263": + version: 1.5.266 + resolution: "electron-to-chromium@npm:1.5.266" + checksum: 74ada92ada1ace76ec5b7da8a9cc2d7f03db122a64ac8e12ae30eba3e358ffec443c0c5265bc6edcdeebfa73f449b21c361080c064eb1eec437db2d71fc03248 + languageName: node + linkType: hard + "electron-to-chromium@npm:^1.5.41": version: 1.5.55 resolution: "electron-to-chromium@npm:1.5.55" @@ -5696,6 +6642,13 @@ __metadata: languageName: node linkType: hard +"encodeurl@npm:~2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: 5d317306acb13e6590e28e27924c754163946a2480de11865c991a3a7eed4315cd3fba378b543ca145829569eefe9b899f3d84bb09870f675ae60bc924b01ceb + languageName: node + linkType: hard + "encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" @@ -5752,6 +6705,20 @@ __metadata: languageName: node linkType: hard +"es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 + languageName: node + linkType: hard + "es-module-lexer@npm:^1.2.1": version: 1.4.1 resolution: "es-module-lexer@npm:1.4.1" @@ -5759,6 +6726,15 @@ __metadata: languageName: node linkType: hard +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.1 + resolution: "es-object-atoms@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 65364812ca4daf48eb76e2a3b7a89b3f6a2e62a1c420766ce9f692665a29d94fe41fe88b65f24106f449859549711e4b40d9fb8002d862dfd7eb1c512d10be0c + languageName: node + linkType: hard + "escalade@npm:^3.1.1, escalade@npm:^3.2.0": version: 3.2.0 resolution: "escalade@npm:3.2.0" @@ -5943,7 +6919,7 @@ __metadata: languageName: node linkType: hard -"eventemitter3@npm:^4.0.0": +"eventemitter3@npm:^4.0.0, eventemitter3@npm:^4.0.4": version: 4.0.7 resolution: "eventemitter3@npm:4.0.7" checksum: 5f6d97cbcbac47be798e6355e3a7639a84ee1f7d9b199a07017f1d2f1e2fe236004d14fa5dfaeba661f94ea57805385e326236a6debbc7145c8877fbc0297c6b @@ -5957,7 +6933,14 @@ __metadata: languageName: node linkType: hard -"execa@npm:^5.0.0": +"eventsource-parser@npm:^3.0.6": + version: 3.0.6 + resolution: "eventsource-parser@npm:3.0.6" + checksum: 70b8ccec7dac767ef2eca43f355e0979e70415701691382a042a2df8d6a68da6c2fca35363669821f3da876d29c02abe9b232964637c1b6635c940df05ada78a + languageName: node + linkType: hard + +"execa@npm:5.1.1": version: 5.1.1 resolution: "execa@npm:5.1.1" dependencies: @@ -5981,42 +6964,42 @@ __metadata: languageName: node linkType: hard -"express@npm:^4.17.3": - version: 4.18.2 - resolution: "express@npm:4.18.2" +"express@npm:^4.21.2": + version: 4.22.1 + resolution: "express@npm:4.22.1" dependencies: accepts: "npm:~1.3.8" array-flatten: "npm:1.1.1" - body-parser: "npm:1.20.1" - content-disposition: "npm:0.5.4" + body-parser: "npm:~1.20.3" + content-disposition: "npm:~0.5.4" content-type: "npm:~1.0.4" - cookie: "npm:0.5.0" - cookie-signature: "npm:1.0.6" + cookie: "npm:~0.7.1" + cookie-signature: "npm:~1.0.6" debug: "npm:2.6.9" depd: "npm:2.0.0" - encodeurl: "npm:~1.0.2" + encodeurl: "npm:~2.0.0" escape-html: "npm:~1.0.3" etag: "npm:~1.8.1" - finalhandler: "npm:1.2.0" - fresh: "npm:0.5.2" - http-errors: "npm:2.0.0" - merge-descriptors: "npm:1.0.1" + finalhandler: "npm:~1.3.1" + fresh: "npm:~0.5.2" + http-errors: "npm:~2.0.0" + merge-descriptors: "npm:1.0.3" methods: "npm:~1.1.2" - on-finished: "npm:2.4.1" + on-finished: "npm:~2.4.1" parseurl: "npm:~1.3.3" - path-to-regexp: "npm:0.1.7" + path-to-regexp: "npm:~0.1.12" proxy-addr: "npm:~2.0.7" - qs: "npm:6.11.0" + qs: "npm:~6.14.0" range-parser: "npm:~1.2.1" safe-buffer: "npm:5.2.1" - send: "npm:0.18.0" - serve-static: "npm:1.15.0" + send: "npm:~0.19.0" + serve-static: "npm:~1.16.2" setprototypeof: "npm:1.2.0" - statuses: "npm:2.0.1" + statuses: "npm:~2.0.1" type-is: "npm:~1.6.18" utils-merge: "npm:1.0.1" vary: "npm:~1.1.2" - checksum: 75af556306b9241bc1d7bdd40c9744b516c38ce50ae3210658efcbf96e3aed4ab83b3432f06215eae5610c123bc4136957dc06e50dfc50b7d4d775af56c4c59c + checksum: ea57f512ab1e05e26b53a14fd432f65a10ec735ece342b37d0b63a7bcb8d337ffbb830ecb8ca15bcdfe423fbff88cea09786277baff200e8cde3ab40faa665cd languageName: node linkType: hard @@ -6036,6 +7019,13 @@ __metadata: languageName: node linkType: hard +"fast-content-type-parse@npm:^3.0.0": + version: 3.0.0 + resolution: "fast-content-type-parse@npm:3.0.0" + checksum: 06251880c83b7118af3a5e66e8bcee60d44f48b39396fc60acc2b4630bd5f3e77552b999b5c8e943d45a818854360e5e97164c374ec4b562b4df96a2cdf2e188 + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -6043,7 +7033,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0, fast-glob@npm:^3.3.2": +"fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0": version: 3.3.2 resolution: "fast-glob@npm:3.3.2" dependencies: @@ -6056,6 +7046,19 @@ __metadata: languageName: node linkType: hard +"fast-glob@npm:^3.3.3": + version: 3.3.3 + resolution: "fast-glob@npm:3.3.3" + dependencies: + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.8" + checksum: f6aaa141d0d3384cf73cbcdfc52f475ed293f6d5b65bfc5def368b09163a9f7e5ec2b3014d80f733c405f58e470ee0cc451c2937685045cddcdeaa24199c43fe + languageName: node + linkType: hard + "fast-json-stable-stringify@npm:^2.0.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" @@ -6120,13 +7123,6 @@ __metadata: languageName: node linkType: hard -"filesize@npm:^8.0.6": - version: 8.0.7 - resolution: "filesize@npm:8.0.7" - checksum: 82072d94816484df5365d4d5acbb2327a65dc49704c64e403e8c40d8acb7364de1cf1e65cb512c77a15d353870f73e4fed46dad5c6153d0618d9ce7a64d09cfc - languageName: node - linkType: hard - "fill-range@npm:^7.1.1": version: 7.1.1 resolution: "fill-range@npm:7.1.1" @@ -6143,18 +7139,18 @@ __metadata: languageName: node linkType: hard -"finalhandler@npm:1.2.0": - version: 1.2.0 - resolution: "finalhandler@npm:1.2.0" +"finalhandler@npm:~1.3.1": + version: 1.3.2 + resolution: "finalhandler@npm:1.3.2" dependencies: debug: "npm:2.6.9" - encodeurl: "npm:~1.0.2" + encodeurl: "npm:~2.0.0" escape-html: "npm:~1.0.3" - on-finished: "npm:2.4.1" + on-finished: "npm:~2.4.1" parseurl: "npm:~1.3.3" - statuses: "npm:2.0.1" + statuses: "npm:~2.0.2" unpipe: "npm:~1.0.0" - checksum: 64b7e5ff2ad1fcb14931cd012651631b721ce657da24aedb5650ddde9378bf8e95daa451da43398123f5de161a81e79ff5affe4f9f2a6d2df4a813d6d3e254b7 + checksum: 435a4fd65e4e4e4c71bb5474980090b73c353a123dd415583f67836bdd6516e528cf07298e219a82b94631dee7830eae5eece38d3c178073cf7df4e8c182f413 languageName: node linkType: hard @@ -6168,25 +7164,6 @@ __metadata: languageName: node linkType: hard -"find-up@npm:^3.0.0": - version: 3.0.0 - resolution: "find-up@npm:3.0.0" - dependencies: - locate-path: "npm:^3.0.0" - checksum: 2c2e7d0a26db858e2f624f39038c74739e38306dee42b45f404f770db357947be9d0d587f1cac72d20c114deb38aa57316e879eb0a78b17b46da7dab0a3bd6e3 - languageName: node - linkType: hard - -"find-up@npm:^5.0.0": - version: 5.0.0 - resolution: "find-up@npm:5.0.0" - dependencies: - locate-path: "npm:^6.0.0" - path-exists: "npm:^4.0.0" - checksum: 062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a - languageName: node - linkType: hard - "find-up@npm:^6.3.0": version: 6.3.0 resolution: "find-up@npm:6.3.0" @@ -6226,37 +7203,6 @@ __metadata: languageName: node linkType: hard -"fork-ts-checker-webpack-plugin@npm:^6.5.0": - version: 6.5.3 - resolution: "fork-ts-checker-webpack-plugin@npm:6.5.3" - dependencies: - "@babel/code-frame": "npm:^7.8.3" - "@types/json-schema": "npm:^7.0.5" - chalk: "npm:^4.1.0" - chokidar: "npm:^3.4.2" - cosmiconfig: "npm:^6.0.0" - deepmerge: "npm:^4.2.2" - fs-extra: "npm:^9.0.0" - glob: "npm:^7.1.6" - memfs: "npm:^3.1.2" - minimatch: "npm:^3.0.4" - schema-utils: "npm:2.7.0" - semver: "npm:^7.3.2" - tapable: "npm:^1.0.0" - peerDependencies: - eslint: ">= 6" - typescript: ">= 2.7" - vue-template-compiler: "*" - webpack: ">= 4" - peerDependenciesMeta: - eslint: - optional: true - vue-template-compiler: - optional: true - checksum: 0885ea75474de011d4068ca3e2d3ca6e4cd318f5cfa018e28ff8fef23ef3a1f1c130160ef192d3e5d31ef7b6fe9f8fb1d920eab5e9e449fb30ce5cc96647245c - languageName: node - linkType: hard - "form-data-encoder@npm:^2.1.2": version: 2.1.4 resolution: "form-data-encoder@npm:2.1.4" @@ -6285,7 +7231,14 @@ __metadata: languageName: node linkType: hard -"fresh@npm:0.5.2": +"fraction.js@npm:^5.3.4": + version: 5.3.4 + resolution: "fraction.js@npm:5.3.4" + checksum: f90079fe9bfc665e0a07079938e8ff71115bce9462f17b32fc283f163b0540ec34dc33df8ed41bb56f028316b04361b9a9995b9ee9258617f8338e0b05c5f95a + languageName: node + linkType: hard + +"fresh@npm:0.5.2, fresh@npm:~0.5.2": version: 0.5.2 resolution: "fresh@npm:0.5.2" checksum: c6d27f3ed86cc5b601404822f31c900dd165ba63fff8152a3ef714e2012e7535027063bc67ded4cb5b3a49fa596495d46cacd9f47d6328459cf570f08b7d9e5a @@ -6303,18 +7256,6 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^9.0.0": - version: 9.1.0 - resolution: "fs-extra@npm:9.1.0" - dependencies: - at-least-node: "npm:^1.0.0" - graceful-fs: "npm:^4.2.0" - jsonfile: "npm:^6.0.1" - universalify: "npm:^2.0.0" - checksum: 9b808bd884beff5cb940773018179a6b94a966381d005479f00adda6b44e5e3d4abf765135773d849cc27efe68c349e4a7b86acd7d3306d5932c14f3a4b17a92 - languageName: node - linkType: hard - "fs-minipass@npm:^2.0.0": version: 2.1.0 resolution: "fs-minipass@npm:2.1.0" @@ -6333,20 +7274,6 @@ __metadata: languageName: node linkType: hard -"fs-monkey@npm:^1.0.4": - version: 1.0.5 - resolution: "fs-monkey@npm:1.0.5" - checksum: 815025e75549fb1ac6c403413b82fd631eded862ae27694a515c0f666069e95874ab34e79c33d1b3b8c87d1e54350d5e4262090d0aa5bd7130143cbc627537e4 - languageName: node - linkType: hard - -"fs.realpath@npm:^1.0.0": - version: 1.0.0 - resolution: "fs.realpath@npm:1.0.0" - checksum: 444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948 - languageName: node - linkType: hard - "fsevents@npm:~2.3.2": version: 2.3.3 resolution: "fsevents@npm:2.3.3" @@ -6373,6 +7300,13 @@ __metadata: languageName: node linkType: hard +"generator-function@npm:^2.0.0": + version: 2.0.1 + resolution: "generator-function@npm:2.0.1" + checksum: 8a9f59df0f01cfefafdb3b451b80555e5cf6d76487095db91ac461a0e682e4ff7a9dbce15f4ecec191e53586d59eece01949e05a4b4492879600bbbe8e28d6b8 + languageName: node + linkType: hard + "gensync@npm:^1.0.0-beta.2": version: 1.0.0-beta.2 resolution: "gensync@npm:1.0.0-beta.2" @@ -6380,7 +7314,14 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2": +"get-east-asian-width@npm:^1.3.0": + version: 1.4.0 + resolution: "get-east-asian-width@npm:1.4.0" + checksum: 4e481d418e5a32061c36fbb90d1b225a254cc5b2df5f0b25da215dcd335a3c111f0c2023ffda43140727a9cafb62dac41d022da82c08f31083ee89f714ee3b83 + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2": version: 1.2.2 resolution: "get-intrinsic@npm:1.2.2" dependencies: @@ -6392,6 +7333,27 @@ __metadata: languageName: node linkType: hard +"get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.3.0": + version: 1.3.1 + resolution: "get-intrinsic@npm:1.3.1" + dependencies: + async-function: "npm:^1.0.0" + async-generator-function: "npm:^1.0.0" + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + generator-function: "npm:^2.0.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 9f4ab0cf7efe0fd2c8185f52e6f637e708f3a112610c88869f8f041bb9ecc2ce44bf285dfdbdc6f4f7c277a5b88d8e94a432374d97cca22f3de7fc63795deb5d + languageName: node + linkType: hard + "get-own-enumerable-property-symbols@npm:^3.0.0": version: 3.0.2 resolution: "get-own-enumerable-property-symbols@npm:3.0.2" @@ -6399,6 +7361,16 @@ __metadata: languageName: node linkType: hard +"get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c + languageName: node + linkType: hard + "get-stream@npm:^6.0.0, get-stream@npm:^6.0.1": version: 6.0.1 resolution: "get-stream@npm:6.0.1" @@ -6431,6 +7403,15 @@ __metadata: languageName: node linkType: hard +"glob-to-regex.js@npm:^1.0.1": + version: 1.2.0 + resolution: "glob-to-regex.js@npm:1.2.0" + peerDependencies: + tslib: 2 + checksum: 011c81ae2a4d7ac5fd617038209fd9639d54c76211cc88fe8dd85d1a0850bc683a63cf5b1eae370141fca7dd2c834dfb9684dfdd8bf7472f2c1e4ef6ab6e34f9 + languageName: node + linkType: hard + "glob-to-regexp@npm:^0.4.1": version: 0.4.1 resolution: "glob-to-regexp@npm:0.4.1" @@ -6453,20 +7434,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:^7.0.0, glob@npm:^7.1.3, glob@npm:^7.1.6": - version: 7.2.3 - resolution: "glob@npm:7.2.3" - dependencies: - fs.realpath: "npm:^1.0.0" - inflight: "npm:^1.0.4" - inherits: "npm:2" - minimatch: "npm:^3.1.1" - once: "npm:^1.3.0" - path-is-absolute: "npm:^1.0.0" - checksum: 65676153e2b0c9095100fe7f25a778bf45608eeb32c6048cf307f579649bcc30353277b3b898a3792602c65764e5baa4f643714dfbdfd64ea271d210c7a425fe - languageName: node - linkType: hard - "global-dirs@npm:^3.0.0": version: 3.0.1 resolution: "global-dirs@npm:3.0.1" @@ -6476,26 +7443,6 @@ __metadata: languageName: node linkType: hard -"global-modules@npm:^2.0.0": - version: 2.0.0 - resolution: "global-modules@npm:2.0.0" - dependencies: - global-prefix: "npm:^3.0.0" - checksum: 43b770fe24aa6028f4b9770ea583a47f39750be15cf6e2578f851e4ccc9e4fa674b8541928c0b09c21461ca0763f0d36e4068cec86c914b07fd6e388e66ba5b9 - languageName: node - linkType: hard - -"global-prefix@npm:^3.0.0": - version: 3.0.0 - resolution: "global-prefix@npm:3.0.0" - dependencies: - ini: "npm:^1.3.5" - kind-of: "npm:^6.0.2" - which: "npm:^1.3.1" - checksum: 510f489fb68d1cc7060f276541709a0ee6d41356ef852de48f7906c648ac223082a1cc8fce86725ca6c0e032bcdc1189ae77b4744a624b29c34a9d0ece498269 - languageName: node - linkType: hard - "globals@npm:^11.1.0": version: 11.12.0 resolution: "globals@npm:11.12.0" @@ -6503,21 +7450,21 @@ __metadata: languageName: node linkType: hard -"globby@npm:14.0.2": - version: 14.0.2 - resolution: "globby@npm:14.0.2" +"globby@npm:15.0.0": + version: 15.0.0 + resolution: "globby@npm:15.0.0" dependencies: - "@sindresorhus/merge-streams": "npm:^2.1.0" - fast-glob: "npm:^3.3.2" - ignore: "npm:^5.2.4" - path-type: "npm:^5.0.0" + "@sindresorhus/merge-streams": "npm:^4.0.0" + fast-glob: "npm:^3.3.3" + ignore: "npm:^7.0.5" + path-type: "npm:^6.0.0" slash: "npm:^5.1.0" - unicorn-magic: "npm:^0.1.0" - checksum: 3f771cd683b8794db1e7ebc8b6b888d43496d93a82aad4e9d974620f578581210b6c5a6e75ea29573ed16a1345222fab6e9b877a8d1ed56eeb147e09f69c6f78 + unicorn-magic: "npm:^0.3.0" + checksum: e4107be0579bcdd9642b8dff86aeafeaf62b2b9dd116669ab6e02e0e0c07ada0d972c2db182dee7588b460fe8c8919ddcc6b1cc4db405ca3a2adc9d35fa6eb21 languageName: node linkType: hard -"globby@npm:^11.0.1, globby@npm:^11.0.4, globby@npm:^11.1.0": +"globby@npm:^11.1.0": version: 11.1.0 resolution: "globby@npm:11.1.0" dependencies: @@ -6553,6 +7500,13 @@ __metadata: languageName: node linkType: hard +"gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead + languageName: node + linkType: hard + "got@npm:^12.1.0": version: 12.6.1 resolution: "got@npm:12.6.1" @@ -6651,6 +7605,13 @@ __metadata: languageName: node linkType: hard +"has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e + languageName: node + linkType: hard + "has-yarn@npm:^3.0.0": version: 3.0.0 resolution: "has-yarn@npm:3.0.0" @@ -6667,6 +7628,15 @@ __metadata: languageName: node linkType: hard +"hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + languageName: node + linkType: hard + "hast-util-from-parse5@npm:^8.0.0": version: 8.0.1 resolution: "hast-util-from-parse5@npm:8.0.1" @@ -6835,13 +7805,6 @@ __metadata: languageName: node linkType: hard -"html-entities@npm:^2.3.2": - version: 2.4.0 - resolution: "html-entities@npm:2.4.0" - checksum: 42bbd5d91f451625d7e35aaed41c8cd110054c0d0970764cb58df467b3f27f20199e8cf7b4aebc8d4eeaf17a27c0d1fb165f2852db85de200995d0f009c9011d - languageName: node - linkType: hard - "html-escaper@npm:^2.0.2": version: 2.0.2 resolution: "html-escaper@npm:2.0.2" @@ -6981,6 +7944,19 @@ __metadata: languageName: node linkType: hard +"http-errors@npm:~2.0.0, http-errors@npm:~2.0.1": + version: 2.0.1 + resolution: "http-errors@npm:2.0.1" + dependencies: + depd: "npm:~2.0.0" + inherits: "npm:~2.0.4" + setprototypeof: "npm:~1.2.0" + statuses: "npm:~2.0.2" + toidentifier: "npm:~1.0.1" + checksum: fb38906cef4f5c83952d97661fe14dc156cb59fe54812a42cd448fa57b5c5dfcb38a40a916957737bd6b87aab257c0648d63eb5b6a9ca9f548e105b6072712d4 + languageName: node + linkType: hard + "http-parser-js@npm:>=0.5.1": version: 0.5.8 resolution: "http-parser-js@npm:0.5.8" @@ -6998,9 +7974,9 @@ __metadata: languageName: node linkType: hard -"http-proxy-middleware@npm:^2.0.3": - version: 2.0.6 - resolution: "http-proxy-middleware@npm:2.0.6" +"http-proxy-middleware@npm:^2.0.9": + version: 2.0.9 + resolution: "http-proxy-middleware@npm:2.0.9" dependencies: "@types/http-proxy": "npm:^1.17.8" http-proxy: "npm:^1.18.1" @@ -7012,7 +7988,7 @@ __metadata: peerDependenciesMeta: "@types/express": optional: true - checksum: 25a0e550dd1900ee5048a692e0e9b2b6339d06d487a705d90c47e359e9c6561d648cd7862d001d090e651c9efffa1b6e5160fcf1f299b5fa4935f76e9754eb11 + checksum: 8e9032af625f7c9f2f0d318f6cdb14eb725cc16ffe7b4ccccea25cf591fa819bb7c3bb579e0b543e0ae9c73059b505a6d728290c757bff27bae526a6ed11c05e languageName: node linkType: hard @@ -7054,12 +8030,10 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.4.24": - version: 0.4.24 - resolution: "iconv-lite@npm:0.4.24" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3" - checksum: c6886a24cc00f2a059767440ec1bc00d334a89f250db8e0f7feb4961c8727118457e27c495ba94d082e51d3baca378726cd110aaf7ded8b9bbfd6a44760cf1d4 +"hyperdyperid@npm:^1.2.0": + version: 1.2.0 + resolution: "hyperdyperid@npm:1.2.0" + checksum: 885ba3177c7181d315a856ee9c0005ff8eb5dcb1ce9e9d61be70987895d934d84686c37c981cceeb53216d4c9c15c1cc25f1804e84cc6a74a16993c5d7fd0893 languageName: node linkType: hard @@ -7072,6 +8046,15 @@ __metadata: languageName: node linkType: hard +"iconv-lite@npm:~0.4.24": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3" + checksum: c6886a24cc00f2a059767440ec1bc00d334a89f250db8e0f7feb4961c8727118457e27c495ba94d082e51d3baca378726cd110aaf7ded8b9bbfd6a44760cf1d4 + languageName: node + linkType: hard + "icss-utils@npm:^5.0.0, icss-utils@npm:^5.1.0": version: 5.1.0 resolution: "icss-utils@npm:5.1.0" @@ -7088,25 +8071,23 @@ __metadata: languageName: node linkType: hard -"image-size@npm:^1.0.2": - version: 1.0.2 - resolution: "image-size@npm:1.0.2" - dependencies: - queue: "npm:6.0.2" - bin: - image-size: bin/image-size.js - checksum: df518606c75d0ee12a6d7e822a64ef50d9eabbb303dcee8c9df06bad94e49b4d4680b9003968203f239ff39a9cc51d4ff1781cd331cc0a4b3b858d9fc9836c68 +"ignore@npm:^7.0.5": + version: 7.0.5 + resolution: "ignore@npm:7.0.5" + checksum: ae00db89fe873064a093b8999fe4cc284b13ef2a178636211842cceb650b9c3e390d3339191acb145d81ed5379d2074840cf0c33a20bdbd6f32821f79eb4ad5d languageName: node linkType: hard -"immer@npm:^9.0.7": - version: 9.0.21 - resolution: "immer@npm:9.0.21" - checksum: 03ea3ed5d4d72e8bd428df4a38ad7e483ea8308e9a113d3b42e0ea2cc0cc38340eb0a6aca69592abbbf047c685dbda04e3d34bf2ff438ab57339ed0a34cc0a05 +"image-size@npm:^2.0.2": + version: 2.0.2 + resolution: "image-size@npm:2.0.2" + bin: + image-size: bin/image-size.js + checksum: f09dd0f7cf8511cd20e4f756bdb5a7cb6d2240de3323f41bde266bed8373392a293892bf12e907e2995f52833fd88dd27cf6b1a52ab93968afc716cb78cd7b79 languageName: node linkType: hard -"import-fresh@npm:^3.1.0, import-fresh@npm:^3.3.0": +"import-fresh@npm:^3.3.0": version: 3.3.0 resolution: "import-fresh@npm:3.3.0" dependencies: @@ -7144,30 +8125,20 @@ __metadata: languageName: node linkType: hard -"inflight@npm:^1.0.4": - version: 1.0.6 - resolution: "inflight@npm:1.0.6" - dependencies: - once: "npm:^1.3.0" - wrappy: "npm:1" - checksum: 7faca22584600a9dc5b9fca2cd5feb7135ac8c935449837b315676b4c90aa4f391ec4f42240178244b5a34e8bede1948627fda392ca3191522fc46b34e985ab2 +"inherits@npm:2.0.3": + version: 2.0.3 + resolution: "inherits@npm:2.0.3" + checksum: 6e56402373149ea076a434072671f9982f5fad030c7662be0332122fe6c0fa490acb3cc1010d90b6eff8d640b1167d77674add52dfd1bb85d545cf29e80e73e7 languageName: node linkType: hard -"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:~2.0.3": +"inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:~2.0.3, inherits@npm:~2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 languageName: node linkType: hard -"inherits@npm:2.0.3": - version: 2.0.3 - resolution: "inherits@npm:2.0.3" - checksum: 6e56402373149ea076a434072671f9982f5fad030c7662be0332122fe6c0fa490acb3cc1010d90b6eff8d640b1167d77674add52dfd1bb85d545cf29e80e73e7 - languageName: node - linkType: hard - "ini@npm:2.0.0": version: 2.0.0 resolution: "ini@npm:2.0.0" @@ -7175,7 +8146,7 @@ __metadata: languageName: node linkType: hard -"ini@npm:^1.3.4, ini@npm:^1.3.5, ini@npm:~1.3.0": +"ini@npm:^1.3.4, ini@npm:~1.3.0": version: 1.3.8 resolution: "ini@npm:1.3.8" checksum: ec93838d2328b619532e4f1ff05df7909760b6f66d9c9e2ded11e5c1897d6f2f9980c54dd638f88654b00919ce31e827040631eab0a3969e4d1abefa0719516a @@ -7189,13 +8160,6 @@ __metadata: languageName: node linkType: hard -"interpret@npm:^1.0.0": - version: 1.4.0 - resolution: "interpret@npm:1.4.0" - checksum: 08c5ad30032edeec638485bc3f6db7d0094d9b3e85e0f950866600af3c52e9fd69715416d29564731c479d9f4d43ff3e4d302a178196bdc0e6837ec147640450 - languageName: node - linkType: hard - "invariant@npm:^2.2.4": version: 2.2.4 resolution: "invariant@npm:2.2.4" @@ -7219,10 +8183,10 @@ __metadata: languageName: node linkType: hard -"ipaddr.js@npm:^2.0.1": - version: 2.1.0 - resolution: "ipaddr.js@npm:2.1.0" - checksum: 9aa43ff99771e3d14ab3683df3909b3b033fe81337646bc63780b00ec9bc51d4a696a047c0b261c05867c0a25086ab03f0ce32ea444a6b39e10fac1315d53cab +"ipaddr.js@npm:^2.1.0": + version: 2.3.0 + resolution: "ipaddr.js@npm:2.3.0" + checksum: 084bab99e2f6875d7a62adc3325e1c64b038a12c9521e35fb967b5e263a8b3afb1b8884dd77c276092331f5d63298b767491e10997ef147c62da01b143780bbd languageName: node linkType: hard @@ -7295,6 +8259,15 @@ __metadata: languageName: node linkType: hard +"is-docker@npm:^3.0.0": + version: 3.0.0 + resolution: "is-docker@npm:3.0.0" + bin: + is-docker: cli.js + checksum: d2c4f8e6d3e34df75a5defd44991b6068afad4835bb783b902fa12d13ebdb8f41b2a199dcb0b5ed2cb78bfee9e4c0bbdb69c2d9646f4106464674d3e697a5856 + languageName: node + linkType: hard + "is-extendable@npm:^0.1.0": version: 0.1.1 resolution: "is-extendable@npm:0.1.1" @@ -7332,6 +8305,17 @@ __metadata: languageName: node linkType: hard +"is-inside-container@npm:^1.0.0": + version: 1.0.0 + resolution: "is-inside-container@npm:1.0.0" + dependencies: + is-docker: "npm:^3.0.0" + bin: + is-inside-container: cli.js + checksum: a8efb0e84f6197e6ff5c64c52890fa9acb49b7b74fed4da7c95383965da6f0fa592b4dbd5e38a79f87fc108196937acdbcd758fcefc9b140e479b39ce1fcd1cd + languageName: node + linkType: hard + "is-installed-globally@npm:^0.4.0": version: 0.4.0 resolution: "is-installed-globally@npm:0.4.0" @@ -7349,6 +8333,13 @@ __metadata: languageName: node linkType: hard +"is-network-error@npm:^1.0.0": + version: 1.3.0 + resolution: "is-network-error@npm:1.3.0" + checksum: 3e85a69e957988db66d5af5412efdd531a5a63e150d1bdd5647cfd4dc54fd89b1dbdd472621f8915233c3176ba1e6922afa8a51a9e363ba4693edf96a294f898 + languageName: node + linkType: hard + "is-npm@npm:^6.0.0": version: 6.0.0 resolution: "is-npm@npm:6.0.0" @@ -7377,13 +8368,6 @@ __metadata: languageName: node linkType: hard -"is-path-cwd@npm:^2.2.0": - version: 2.2.0 - resolution: "is-path-cwd@npm:2.2.0" - checksum: afce71533a427a759cd0329301c18950333d7589533c2c90205bd3fdcf7b91eb92d1940493190567a433134d2128ec9325de2fd281e05be1920fbee9edd22e0a - languageName: node - linkType: hard - "is-path-inside@npm:^3.0.2": version: 3.0.3 resolution: "is-path-inside@npm:3.0.3" @@ -7430,13 +8414,6 @@ __metadata: languageName: node linkType: hard -"is-root@npm:^2.1.0": - version: 2.1.0 - resolution: "is-root@npm:2.1.0" - checksum: 83d3f5b052c3f28fbdbdf0d564bdd34fa14933f5694c78704f85cd1871255bc017fbe3fe2bc2fff2d227c6be5927ad2149b135c0a7c0060e7ac4e610d81a4f01 - languageName: node - linkType: hard - "is-stream@npm:^2.0.0": version: 2.0.1 resolution: "is-stream@npm:2.0.1" @@ -7460,6 +8437,15 @@ __metadata: languageName: node linkType: hard +"is-wsl@npm:^3.1.0": + version: 3.1.0 + resolution: "is-wsl@npm:3.1.0" + dependencies: + is-inside-container: "npm:^1.0.0" + checksum: d3317c11995690a32c362100225e22ba793678fe8732660c6de511ae71a0ff05b06980cf21f98a6bf40d7be0e9e9506f859abe00a1118287d63e53d0a3d06947 + languageName: node + linkType: hard + "is-yarn-global@npm:^0.4.0": version: 0.4.1 resolution: "is-yarn-global@npm:0.4.1" @@ -7552,7 +8538,7 @@ __metadata: languageName: node linkType: hard -"jiti@npm:^1.18.2, jiti@npm:^1.20.0": +"jiti@npm:^1.20.0": version: 1.21.0 resolution: "jiti@npm:1.21.0" bin: @@ -7581,14 +8567,14 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:4.1.0, js-yaml@npm:^4.1.0": - version: 4.1.0 - resolution: "js-yaml@npm:4.1.0" +"js-yaml@npm:4.1.1": + version: 4.1.1 + resolution: "js-yaml@npm:4.1.1" dependencies: argparse: "npm:^2.0.1" bin: js-yaml: bin/js-yaml.js - checksum: 184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f + checksum: 561c7d7088c40a9bb53cc75becbfb1df6ae49b34b5e6e5a81744b14ae8667ec564ad2527709d1a6e7d5e5fa6d483aa0f373a50ad98d42fde368ec4a190d4fae7 languageName: node linkType: hard @@ -7604,6 +8590,17 @@ __metadata: languageName: node linkType: hard +"js-yaml@npm:^4.1.0": + version: 4.1.0 + resolution: "js-yaml@npm:4.1.0" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f + languageName: node + linkType: hard + "jsesc@npm:^3.0.2, jsesc@npm:~3.0.2": version: 3.0.2 resolution: "jsesc@npm:3.0.2" @@ -7641,6 +8638,13 @@ __metadata: languageName: node linkType: hard +"json-schema@npm:^0.4.0": + version: 0.4.0 + resolution: "json-schema@npm:0.4.0" + checksum: d4a637ec1d83544857c1c163232f3da46912e971d5bf054ba44fdb88f07d8d359a462b4aec46f2745efbc57053365608d88bc1d7b1729f7b4fc3369765639ed3 + languageName: node + linkType: hard + "json5@npm:^2.1.2, json5@npm:^2.2.3": version: 2.2.3 resolution: "json5@npm:2.2.3" @@ -7670,6 +8674,17 @@ __metadata: languageName: node linkType: hard +"katex@npm:^0.16.0": + version: 0.16.25 + resolution: "katex@npm:0.16.25" + dependencies: + commander: "npm:^8.3.0" + bin: + katex: cli.js + checksum: 5bb4b1cd914b76d5efb01ee054c1a221ac723be1e38fb260264c6e253036943d301c1741cbf64f840689c6b3942bce21a6da6637de846a428e4c661dc8ee46ab + languageName: node + linkType: hard + "keyv@npm:^4.5.3": version: 4.5.4 resolution: "keyv@npm:4.5.4" @@ -7702,13 +8717,13 @@ __metadata: languageName: node linkType: hard -"launch-editor@npm:^2.6.0": - version: 2.6.1 - resolution: "launch-editor@npm:2.6.1" +"launch-editor@npm:^2.6.1": + version: 2.12.0 + resolution: "launch-editor@npm:2.12.0" dependencies: - picocolors: "npm:^1.0.0" - shell-quote: "npm:^1.8.1" - checksum: 82d0bd9a44e7a972157719e63dac1b8196db6ec7066c1ec57a495f6c3d6e734f3c4da89549e7b33eb3b0356668ad02a9e7782b6733f5ebd7a61b7c5f635a3ee9 + picocolors: "npm:^1.1.1" + shell-quote: "npm:^1.8.3" + checksum: fac5e7ad90bf185594cad4c831a52419eef50e667c4eddb5b0a58eb5f944e16d947636ee767b9896ffd46a51db34925edd3b854c48efb47f6d767ffd7d904e71 languageName: node linkType: hard @@ -7870,32 +8885,6 @@ __metadata: languageName: node linkType: hard -"loader-utils@npm:^3.2.0": - version: 3.2.1 - resolution: "loader-utils@npm:3.2.1" - checksum: d3e1f217d160e8e894a0385a33500d4ce14065e8ffb250f5a81ae65bc2c3baa50625ec34182ba4417b46b4ac6725aed64429e1104d6401e074af2aa1dd018394 - languageName: node - linkType: hard - -"locate-path@npm:^3.0.0": - version: 3.0.0 - resolution: "locate-path@npm:3.0.0" - dependencies: - p-locate: "npm:^3.0.0" - path-exists: "npm:^3.0.0" - checksum: 3db394b7829a7fe2f4fbdd25d3c4689b85f003c318c5da4052c7e56eed697da8f1bce5294f685c69ff76e32cba7a33629d94396976f6d05fb7f4c755c5e2ae8b - languageName: node - linkType: hard - -"locate-path@npm:^6.0.0": - version: 6.0.0 - resolution: "locate-path@npm:6.0.0" - dependencies: - p-locate: "npm:^5.0.0" - checksum: d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 - languageName: node - linkType: hard - "locate-path@npm:^7.1.0": version: 7.2.0 resolution: "locate-path@npm:7.2.0" @@ -7940,7 +8929,7 @@ __metadata: languageName: node linkType: hard -"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.2.0, loose-envify@npm:^1.3.1, loose-envify@npm:^1.4.0": +"loose-envify@npm:^1.0.0, loose-envify@npm:^1.2.0, loose-envify@npm:^1.3.1, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" dependencies: @@ -8050,62 +9039,78 @@ __metadata: languageName: node linkType: hard -"markdownlint-cli2-formatter-default@npm:0.0.5": - version: 0.0.5 - resolution: "markdownlint-cli2-formatter-default@npm:0.0.5" +"markdownlint-cli2-formatter-default@npm:0.0.6": + version: 0.0.6 + resolution: "markdownlint-cli2-formatter-default@npm:0.0.6" peerDependencies: markdownlint-cli2: ">=0.0.4" - checksum: 7041a5833846d895054cf273c8e75efc13a03bbc88679cd48ea77240318232e883846037e984358a3ad3825fbb602d83492417ed6e36051bc5b603c4bedb3622 + checksum: 58aeec03bd3624e1db7ac456d84a19cb155956e4d74ca6d13c9405c555ef9e6347308599a2c245cfbdf8f3b09b0bcc52f21bde5e9af4231655a051d9ddefabd9 languageName: node linkType: hard -"markdownlint-cli2@npm:^0.14.0": - version: 0.14.0 - resolution: "markdownlint-cli2@npm:0.14.0" +"markdownlint-cli2@npm:^0.19.1": + version: 0.19.1 + resolution: "markdownlint-cli2@npm:0.19.1" dependencies: - globby: "npm:14.0.2" - js-yaml: "npm:4.1.0" + globby: "npm:15.0.0" + js-yaml: "npm:4.1.1" jsonc-parser: "npm:3.3.1" - markdownlint: "npm:0.35.0" - markdownlint-cli2-formatter-default: "npm:0.0.5" + markdown-it: "npm:14.1.0" + markdownlint: "npm:0.39.0" + markdownlint-cli2-formatter-default: "npm:0.0.6" micromatch: "npm:4.0.8" bin: - markdownlint-cli2: markdownlint-cli2.js - checksum: cf2c65de97887a51e2ad756779fd90cabc6b26d09c07aed43422c142e0b2406a8a1e84e17cbf8590a72812e65d5a16e106d7110e392bb7f5fb154a09245bb985 + markdownlint-cli2: markdownlint-cli2-bin.mjs + checksum: fdac00f87a0a4ad155a332f5926d6ce4be153f4fac0362722d9620821bb638f2def29991ede102d245975a62084323f1a3032dfc4f39f17899b38f11f79619e5 languageName: node linkType: hard -"markdownlint-micromark@npm:0.1.10": - version: 0.1.10 - resolution: "markdownlint-micromark@npm:0.1.10" - checksum: 1a392a3d92c01093244c15f69a658e68eeffe26b96218189509184246e19be9f64c7937241a5911f1ff2bd721f0d60f32242a46a9b3b6ab505ef22b8dbb66918 +"markdownlint@npm:0.39.0": + version: 0.39.0 + resolution: "markdownlint@npm:0.39.0" + dependencies: + micromark: "npm:4.0.2" + micromark-core-commonmark: "npm:2.0.3" + micromark-extension-directive: "npm:4.0.0" + micromark-extension-gfm-autolink-literal: "npm:2.1.0" + micromark-extension-gfm-footnote: "npm:2.1.0" + micromark-extension-gfm-table: "npm:2.1.1" + micromark-extension-math: "npm:3.1.0" + micromark-util-types: "npm:2.0.2" + checksum: aaa079902fa1585e3769a0f699478cdcca0e2bf205916e5cc90a362746548b5530aa8e0a762e255b5e05dc481d86f9e8859780d4e5943e3ace8a5fb48023b6af languageName: node linkType: hard -"markdownlint-micromark@npm:0.1.12": - version: 0.1.12 - resolution: "markdownlint-micromark@npm:0.1.12" - checksum: 6833c2b9eb6fd63c43dde30aff8bc526cb97638b287535f8277e2c97015602df0670becdece639986dbe3ac2b279d7c1d308c0a65cb5d2aacc331ea768afa86e +"markdownlint@npm:^0.40.0": + version: 0.40.0 + resolution: "markdownlint@npm:0.40.0" + dependencies: + micromark: "npm:4.0.2" + micromark-core-commonmark: "npm:2.0.3" + micromark-extension-directive: "npm:4.0.0" + micromark-extension-gfm-autolink-literal: "npm:2.1.0" + micromark-extension-gfm-footnote: "npm:2.1.0" + micromark-extension-gfm-table: "npm:2.1.1" + micromark-extension-math: "npm:3.1.0" + micromark-util-types: "npm:2.0.2" + string-width: "npm:8.1.0" + checksum: 1543fcf4a433bc54e0e565cb1c8111e5e3d0df3742df0cc840d470bced21a1e3b5593e4e380ad0d8d5e490d9b399699d48aeabed33719f3fbdc6d00128138f20 languageName: node linkType: hard -"markdownlint@npm:0.35.0": - version: 0.35.0 - resolution: "markdownlint@npm:0.35.0" - dependencies: - markdown-it: "npm:14.1.0" - markdownlint-micromark: "npm:0.1.10" - checksum: 32330f6e6c2a35e3505f4bdae5211c0b8bd9130604a0d93bb22506835447065a4efd9d3ecefaa7f7c6744628f8427fe98296b71948d2d1502a46917863235717 +"marked@npm:^16.3.0": + version: 16.4.2 + resolution: "marked@npm:16.4.2" + bin: + marked: bin/marked.js + checksum: fc6051142172454f2023f3d6b31cca92879ec8e1b96457086a54c70354c74b00e1b6543a76a1fad6d399366f52b90a848f6ffb8e1d65a5baff87f3ba9b8f1847 languageName: node linkType: hard -"markdownlint@npm:^0.36.1": - version: 0.36.1 - resolution: "markdownlint@npm:0.36.1" - dependencies: - markdown-it: "npm:14.1.0" - markdownlint-micromark: "npm:0.1.12" - checksum: 1f93a010c5421a9c9cbd51aeb4046c674b4797ef1c6ad55b0344e7a6ecec46dd38d526e623fd6a3d4c486fe594119c5161dad6033b6468f7719d3c69b5d0cea6 +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f languageName: node linkType: hard @@ -8389,19 +9394,24 @@ __metadata: languageName: node linkType: hard -"memfs@npm:^3.1.2, memfs@npm:^3.4.3": - version: 3.5.3 - resolution: "memfs@npm:3.5.3" +"memfs@npm:^4.43.1": + version: 4.51.1 + resolution: "memfs@npm:4.51.1" dependencies: - fs-monkey: "npm:^1.0.4" - checksum: 038fc81bce17ea92dde15aaa68fa0fdaf4960c721ce3ffc7c2cb87a259333f5159784ea48b3b72bf9e054254d9d0d0d5209d0fdc3d07d08653a09933b168fbd7 + "@jsonjoy.com/json-pack": "npm:^1.11.0" + "@jsonjoy.com/util": "npm:^1.9.0" + glob-to-regex.js: "npm:^1.0.1" + thingies: "npm:^2.5.0" + tree-dump: "npm:^1.0.3" + tslib: "npm:^2.0.0" + checksum: b039121dd2c6a93b2b3835042a1780d70347d25d3f983998a91e38a07e9ea1838ace3a5b0b7b8437efef6c64eea668f62efb25aeeed72a595055f6c449ada402 languageName: node linkType: hard -"merge-descriptors@npm:1.0.1": - version: 1.0.1 - resolution: "merge-descriptors@npm:1.0.1" - checksum: b67d07bd44cfc45cebdec349bb6e1f7b077ee2fd5beb15d1f7af073849208cb6f144fe403e29a36571baf3f4e86469ac39acf13c318381e958e186b2766f54ec +"merge-descriptors@npm:1.0.3": + version: 1.0.3 + resolution: "merge-descriptors@npm:1.0.3" + checksum: 866b7094afd9293b5ea5dcd82d71f80e51514bed33b4c4e9f516795dc366612a4cbb4dc94356e943a8a6914889a914530badff27f397191b9b75cda20b6bae93 languageName: node linkType: hard @@ -8426,6 +9436,30 @@ __metadata: languageName: node linkType: hard +"micromark-core-commonmark@npm:2.0.3": + version: 2.0.3 + resolution: "micromark-core-commonmark@npm:2.0.3" + dependencies: + decode-named-character-reference: "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-factory-destination: "npm:^2.0.0" + micromark-factory-label: "npm:^2.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-factory-title: "npm:^2.0.0" + micromark-factory-whitespace: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-chunked: "npm:^2.0.0" + micromark-util-classify-character: "npm:^2.0.0" + micromark-util-html-tag-name: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + micromark-util-resolve-all: "npm:^2.0.0" + micromark-util-subtokenize: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: bd4a794fdc9e88dbdf59eaf1c507ddf26e5f7ddf4e52566c72239c0f1b66adbcd219ba2cd42350debbe24471434d5f5e50099d2b3f4e5762ca222ba8e5b549ee + languageName: node + linkType: hard + "micromark-core-commonmark@npm:^2.0.0": version: 2.0.0 resolution: "micromark-core-commonmark@npm:2.0.0" @@ -8450,6 +9484,21 @@ __metadata: languageName: node linkType: hard +"micromark-extension-directive@npm:4.0.0": + version: 4.0.0 + resolution: "micromark-extension-directive@npm:4.0.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-factory-whitespace: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + parse-entities: "npm:^4.0.0" + checksum: b4aef0f44339543466ae186130a4514985837b6b12d0c155bd1162e740f631e58f0883a39d0c723206fa0ff53a9b579965c79116f902236f6f123c3340b5fefb + languageName: node + linkType: hard + "micromark-extension-directive@npm:^3.0.0": version: 3.0.0 resolution: "micromark-extension-directive@npm:3.0.0" @@ -8477,6 +9526,18 @@ __metadata: languageName: node linkType: hard +"micromark-extension-gfm-autolink-literal@npm:2.1.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-autolink-literal@npm:2.1.0" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-sanitize-uri: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 84e6fbb84ea7c161dfa179665dc90d51116de4c28f3e958260c0423e5a745372b7dcbc87d3cde98213b532e6812f847eef5ae561c9397d7f7da1e59872ef3efe + languageName: node + linkType: hard + "micromark-extension-gfm-autolink-literal@npm:^2.0.0": version: 2.0.0 resolution: "micromark-extension-gfm-autolink-literal@npm:2.0.0" @@ -8489,6 +9550,22 @@ __metadata: languageName: node linkType: hard +"micromark-extension-gfm-footnote@npm:2.1.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-footnote@npm:2.1.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-core-commonmark: "npm:^2.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + micromark-util-sanitize-uri: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: d172e4218968b7371b9321af5cde8c77423f73b233b2b0fcf3ff6fd6f61d2e0d52c49123a9b7910612478bf1f0d5e88c75a3990dd68f70f3933fe812b9f77edc + languageName: node + linkType: hard + "micromark-extension-gfm-footnote@npm:^2.0.0": version: 2.0.0 resolution: "micromark-extension-gfm-footnote@npm:2.0.0" @@ -8519,6 +9596,19 @@ __metadata: languageName: node linkType: hard +"micromark-extension-gfm-table@npm:2.1.1": + version: 2.1.1 + resolution: "micromark-extension-gfm-table@npm:2.1.1" + dependencies: + devlop: "npm:^1.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 04bc00e19b435fa0add62cd029d8b7eb6137522f77832186b1d5ef34544a9bd030c9cf85e92ddfcc5c31f6f0a58a43d4b96dba4fc21316037c734630ee12c912 + languageName: node + linkType: hard + "micromark-extension-gfm-table@npm:^2.0.0": version: 2.0.0 resolution: "micromark-extension-gfm-table@npm:2.0.0" @@ -8570,6 +9660,21 @@ __metadata: languageName: node linkType: hard +"micromark-extension-math@npm:3.1.0": + version: 3.1.0 + resolution: "micromark-extension-math@npm:3.1.0" + dependencies: + "@types/katex": "npm:^0.16.0" + devlop: "npm:^1.0.0" + katex: "npm:^0.16.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 56e6f2185a4613f9d47e7e98cf8605851c990957d9229c942b005e286c8087b61dc9149448d38b2f8be6d42cc6a64aad7e1f2778ddd86fbbb1a2f48a3ca1872f + languageName: node + linkType: hard + "micromark-extension-mdx-expression@npm:^3.0.0": version: 3.0.0 resolution: "micromark-extension-mdx-expression@npm:3.0.0" @@ -8885,6 +9990,13 @@ __metadata: languageName: node linkType: hard +"micromark-util-types@npm:2.0.2": + version: 2.0.2 + resolution: "micromark-util-types@npm:2.0.2" + checksum: c8c15b96c858db781c4393f55feec10004bf7df95487636c9a9f7209e51002a5cca6a047c5d2a5dc669ff92da20e57aaa881e81a268d9ccadb647f9dce305298 + languageName: node + linkType: hard + "micromark-util-types@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-types@npm:1.1.0" @@ -8899,6 +10011,31 @@ __metadata: languageName: node linkType: hard +"micromark@npm:4.0.2": + version: 4.0.2 + resolution: "micromark@npm:4.0.2" + dependencies: + "@types/debug": "npm:^4.0.0" + debug: "npm:^4.0.0" + decode-named-character-reference: "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-core-commonmark: "npm:^2.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-chunked: "npm:^2.0.0" + micromark-util-combine-extensions: "npm:^2.0.0" + micromark-util-decode-numeric-character-reference: "npm:^2.0.0" + micromark-util-encode: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + micromark-util-resolve-all: "npm:^2.0.0" + micromark-util-sanitize-uri: "npm:^2.0.0" + micromark-util-subtokenize: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 07462287254219d6eda6eac8a3cebaff2994e0575499e7088027b825105e096e4f51e466b14b2a81b71933a3b6c48ee069049d87bc2c2127eee50d9cc69e8af6 + languageName: node + linkType: hard + "micromark@npm:^4.0.0": version: 4.0.0 resolution: "micromark@npm:4.0.0" @@ -8924,7 +10061,7 @@ __metadata: languageName: node linkType: hard -"micromatch@npm:4.0.8, micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5": +"micromatch@npm:4.0.8, micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5, micromatch@npm:^4.0.8": version: 4.0.8 resolution: "micromatch@npm:4.0.8" dependencies: @@ -8941,6 +10078,13 @@ __metadata: languageName: node linkType: hard +"mime-db@npm:^1.54.0": + version: 1.54.0 + resolution: "mime-db@npm:1.54.0" + checksum: 8d907917bc2a90fa2df842cdf5dfeaf509adc15fe0531e07bb2f6ab15992416479015828d6a74200041c492e42cce3ebf78e5ce714388a0a538ea9c53eece284 + languageName: node + linkType: hard + "mime-db@npm:~1.33.0": version: 1.33.0 resolution: "mime-db@npm:1.33.0" @@ -8957,7 +10101,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": +"mime-types@npm:^2.1.27, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -8966,6 +10110,15 @@ __metadata: languageName: node linkType: hard +"mime-types@npm:^3.0.1": + version: 3.0.2 + resolution: "mime-types@npm:3.0.2" + dependencies: + mime-db: "npm:^1.54.0" + checksum: 35a0dd1035d14d185664f346efcdb72e93ef7a9b6e9ae808bd1f6358227010267fab52657b37562c80fc888ff76becb2b2938deb5e730818b7983bf8bd359767 + languageName: node + linkType: hard + "mime@npm:1.6.0": version: 1.6.0 resolution: "mime@npm:1.6.0" @@ -8996,15 +10149,15 @@ __metadata: languageName: node linkType: hard -"mini-css-extract-plugin@npm:^2.9.1": - version: 2.9.2 - resolution: "mini-css-extract-plugin@npm:2.9.2" +"mini-css-extract-plugin@npm:^2.9.2": + version: 2.9.4 + resolution: "mini-css-extract-plugin@npm:2.9.4" dependencies: schema-utils: "npm:^4.0.0" tapable: "npm:^2.2.1" peerDependencies: webpack: ^5.0.0 - checksum: 5d3218dbd7db48b572925ddac05162a7415bf81b321f1a0c07016ec643cb5720c8a836ae68d45f5de826097a3013b601706c9c5aacb7f610dc2041b271de2ce0 + checksum: 76f9e471784d52435ea766ce576ad23d37d0ea51c32ddc56414c8fdf14f7de44202dbc772cdf7549b7e54a5e56f569af93cfbd036d62d13ff8fd9571e53353b7 languageName: node linkType: hard @@ -9015,7 +10168,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:3.1.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1": +"minimatch@npm:3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" dependencies: @@ -9182,7 +10335,16 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:3.3.7, nanoid@npm:^3.3.6, nanoid@npm:^3.3.7": +"nanoid@npm:^3.3.11": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + languageName: node + linkType: hard + +"nanoid@npm:^3.3.6, nanoid@npm:^3.3.7": version: 3.3.7 resolution: "nanoid@npm:3.3.7" bin: @@ -9268,6 +10430,13 @@ __metadata: languageName: node linkType: hard +"node-releases@npm:^2.0.27": + version: 2.0.27 + resolution: "node-releases@npm:2.0.27" + checksum: f1e6583b7833ea81880627748d28a3a7ff5703d5409328c216ae57befbced10ce2c991bea86434e8ec39003bd017f70481e2e5f8c1f7e0a7663241f81d6e00e2 + languageName: node + linkType: hard + "nopt@npm:^7.0.0": version: 7.2.0 resolution: "nopt@npm:7.2.0" @@ -9351,10 +10520,10 @@ __metadata: languageName: node linkType: hard -"object-inspect@npm:^1.9.0": - version: 1.13.1 - resolution: "object-inspect@npm:1.13.1" - checksum: fad603f408e345c82e946abdf4bfd774260a5ed3e5997a0b057c44153ac32c7271ff19e3a5ae39c858da683ba045ccac2f65245c12763ce4e8594f818f4a648d +"object-inspect@npm:^1.13.3": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: d7f8711e803b96ea3191c745d6f8056ce1f2496e530e6a19a0e92d89b0fa3c76d910c31f0aa270432db6bd3b2f85500a376a83aaba849a8d518c8845b3211692 languageName: node linkType: hard @@ -9384,7 +10553,7 @@ __metadata: languageName: node linkType: hard -"on-finished@npm:2.4.1": +"on-finished@npm:2.4.1, on-finished@npm:^2.4.1, on-finished@npm:~2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" dependencies: @@ -9400,15 +10569,6 @@ __metadata: languageName: node linkType: hard -"once@npm:^1.3.0, once@npm:^1.4.0": - version: 1.4.0 - resolution: "once@npm:1.4.0" - dependencies: - wrappy: "npm:1" - checksum: 5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 - languageName: node - linkType: hard - "onetime@npm:^5.1.2": version: 5.1.2 resolution: "onetime@npm:5.1.2" @@ -9418,7 +10578,19 @@ __metadata: languageName: node linkType: hard -"open@npm:^8.0.9, open@npm:^8.4.0": +"open@npm:^10.0.3": + version: 10.2.0 + resolution: "open@npm:10.2.0" + dependencies: + default-browser: "npm:^5.2.1" + define-lazy-prop: "npm:^3.0.0" + is-inside-container: "npm:^1.0.0" + wsl-utils: "npm:^0.1.0" + checksum: 5a36d0c1fd2f74ce553beb427ca8b8494b623fc22c6132d0c1688f246a375e24584ea0b44c67133d9ab774fa69be8e12fbe1ff12504b1142bd960fb09671948f + languageName: node + linkType: hard + +"open@npm:^8.4.0": version: 8.4.2 resolution: "open@npm:8.4.2" dependencies: @@ -9445,21 +10617,10 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^2.0.0": - version: 2.3.0 - resolution: "p-limit@npm:2.3.0" - dependencies: - p-try: "npm:^2.0.0" - checksum: 8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12 - languageName: node - linkType: hard - -"p-limit@npm:^3.0.2": - version: 3.1.0 - resolution: "p-limit@npm:3.1.0" - dependencies: - yocto-queue: "npm:^0.1.0" - checksum: 9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a +"p-finally@npm:^1.0.0": + version: 1.0.0 + resolution: "p-finally@npm:1.0.0" + checksum: 6b8552339a71fe7bd424d01d8451eea92d379a711fc62f6b2fe64cad8a472c7259a236c9a22b4733abca0b5666ad503cb497792a0478c5af31ded793d00937e7 languageName: node linkType: hard @@ -9472,24 +10633,6 @@ __metadata: languageName: node linkType: hard -"p-locate@npm:^3.0.0": - version: 3.0.0 - resolution: "p-locate@npm:3.0.0" - dependencies: - p-limit: "npm:^2.0.0" - checksum: 7b7f06f718f19e989ce6280ed4396fb3c34dabdee0df948376483032f9d5ec22fdf7077ec942143a75827bb85b11da72016497fc10dac1106c837ed593969ee8 - languageName: node - linkType: hard - -"p-locate@npm:^5.0.0": - version: 5.0.0 - resolution: "p-locate@npm:5.0.0" - dependencies: - p-limit: "npm:^3.0.2" - checksum: 2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a - languageName: node - linkType: hard - "p-locate@npm:^6.0.0": version: 6.0.0 resolution: "p-locate@npm:6.0.0" @@ -9508,20 +10651,33 @@ __metadata: languageName: node linkType: hard -"p-retry@npm:^4.5.0": - version: 4.6.2 - resolution: "p-retry@npm:4.6.2" +"p-queue@npm:^6.6.2": + version: 6.6.2 + resolution: "p-queue@npm:6.6.2" + dependencies: + eventemitter3: "npm:^4.0.4" + p-timeout: "npm:^3.2.0" + checksum: 5739ecf5806bbeadf8e463793d5e3004d08bb3f6177bd1a44a005da8fd81bb90f80e4633e1fb6f1dfd35ee663a5c0229abe26aebb36f547ad5a858347c7b0d3e + languageName: node + linkType: hard + +"p-retry@npm:^6.2.0": + version: 6.2.1 + resolution: "p-retry@npm:6.2.1" dependencies: - "@types/retry": "npm:0.12.0" + "@types/retry": "npm:0.12.2" + is-network-error: "npm:^1.0.0" retry: "npm:^0.13.1" - checksum: d58512f120f1590cfedb4c2e0c42cb3fa66f3cea8a4646632fcb834c56055bb7a6f138aa57b20cc236fb207c9d694e362e0b5c2b14d9b062f67e8925580c73b0 + checksum: 10d014900107da2c7071ad60fffe4951675f09930b7a91681643ea224ae05649c05001d9e78436d902fe8b116d520dd1f60e72e091de097e2640979d56f3fb60 languageName: node linkType: hard -"p-try@npm:^2.0.0": - version: 2.2.0 - resolution: "p-try@npm:2.2.0" - checksum: c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f +"p-timeout@npm:^3.2.0": + version: 3.2.0 + resolution: "p-timeout@npm:3.2.0" + dependencies: + p-finally: "npm:^1.0.0" + checksum: 524b393711a6ba8e1d48137c5924749f29c93d70b671e6db761afa784726572ca06149c715632da8f70c090073afb2af1c05730303f915604fd38ee207b70a61 languageName: node linkType: hard @@ -9572,7 +10728,7 @@ __metadata: languageName: node linkType: hard -"parse-json@npm:^5.0.0, parse-json@npm:^5.2.0": +"parse-json@npm:^5.2.0": version: 5.2.0 resolution: "parse-json@npm:5.2.0" dependencies: @@ -9627,20 +10783,6 @@ __metadata: languageName: node linkType: hard -"path-exists@npm:^3.0.0": - version: 3.0.0 - resolution: "path-exists@npm:3.0.0" - checksum: 17d6a5664bc0a11d48e2b2127d28a0e58822c6740bde30403f08013da599182289c56518bec89407e3f31d3c2b6b296a4220bc3f867f0911fee6952208b04167 - languageName: node - linkType: hard - -"path-exists@npm:^4.0.0": - version: 4.0.0 - resolution: "path-exists@npm:4.0.0" - checksum: 8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b - languageName: node - linkType: hard - "path-exists@npm:^5.0.0": version: 5.0.0 resolution: "path-exists@npm:5.0.0" @@ -9648,13 +10790,6 @@ __metadata: languageName: node linkType: hard -"path-is-absolute@npm:^1.0.0": - version: 1.0.1 - resolution: "path-is-absolute@npm:1.0.1" - checksum: 127da03c82172a2a50099cddbf02510c1791fc2cc5f7713ddb613a56838db1e8168b121a920079d052e0936c23005562059756d653b7c544c53185efe53be078 - languageName: node - linkType: hard - "path-is-inside@npm:1.0.2": version: 1.0.2 resolution: "path-is-inside@npm:1.0.2" @@ -9686,13 +10821,6 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:0.1.7": - version: 0.1.7 - resolution: "path-to-regexp@npm:0.1.7" - checksum: 50a1ddb1af41a9e68bd67ca8e331a705899d16fb720a1ea3a41e310480948387daf603abb14d7b0826c58f10146d49050a1291ba6a82b78a382d1c02c0b8f905 - languageName: node - linkType: hard - "path-to-regexp@npm:3.3.0": version: 3.3.0 resolution: "path-to-regexp@npm:3.3.0" @@ -9709,6 +10837,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:~0.1.12": + version: 0.1.12 + resolution: "path-to-regexp@npm:0.1.12" + checksum: 1c6ff10ca169b773f3bba943bbc6a07182e332464704572962d277b900aeee81ac6aa5d060ff9e01149636c30b1f63af6e69dd7786ba6e0ddb39d4dee1f0645b + languageName: node + linkType: hard + "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -9716,10 +10851,10 @@ __metadata: languageName: node linkType: hard -"path-type@npm:^5.0.0": - version: 5.0.0 - resolution: "path-type@npm:5.0.0" - checksum: e8f4b15111bf483900c75609e5e74e3fcb79f2ddb73e41470028fcd3e4b5162ec65da9907be077ee5012c18801ff7fffb35f9f37a077f3f81d85a0b7d6578efd +"path-type@npm:^6.0.0": + version: 6.0.0 + resolution: "path-type@npm:6.0.0" + checksum: 55baa8b1187d6dc683d5a9cfcc866168d6adff58e5db91126795376d818eee46391e00b2a4d53e44d844c7524a7d96aa68cc68f4f3e500d3d069a39e6535481c languageName: node linkType: hard @@ -9734,7 +10869,7 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.0.0, picocolors@npm:^1.0.1, picocolors@npm:^1.1.0": +"picocolors@npm:^1.0.0, picocolors@npm:^1.0.1, picocolors@npm:^1.1.0, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" checksum: e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 @@ -9757,12 +10892,14 @@ __metadata: languageName: node linkType: hard -"pkg-up@npm:^3.1.0": - version: 3.1.0 - resolution: "pkg-up@npm:3.1.0" +"postcss-attribute-case-insensitive@npm:^7.0.1": + version: 7.0.1 + resolution: "postcss-attribute-case-insensitive@npm:7.0.1" dependencies: - find-up: "npm:^3.0.0" - checksum: ecb60e1f8e1f611c0bdf1a0b6a474d6dfb51185567dc6f29cdef37c8d480ecba5362e006606bb290519bbb6f49526c403fabea93c3090c20368d98bb90c999ab + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 48945abe2024e2d2e4c37d30b8c1aaf37af720f24f6a996f7ea7e7ed33621f5c22cf247ed22028c0c922de040c58c0802729bc39b903cb1693f4b63c0b49da34 languageName: node linkType: hard @@ -9778,6 +10915,56 @@ __metadata: languageName: node linkType: hard +"postcss-clamp@npm:^4.1.0": + version: 4.1.0 + resolution: "postcss-clamp@npm:4.1.0" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4.6 + checksum: 701261026b38a4c27b3c3711635fac96005f36d3270adb76dbdb1eebc950fc841db45283ee66068a7121565592e9d7967d5534e15b6e4dd266afcabf9eafa905 + languageName: node + linkType: hard + +"postcss-color-functional-notation@npm:^7.0.12": + version: 7.0.12 + resolution: "postcss-color-functional-notation@npm:7.0.12" + dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: dc80ba1a956ae9b396596bda72d9bdb92de96874378a38ba4e2177ffa35339dc76d894920bb013b6f10c9b75cfb41778e09956a438c2e9ea41b684f766c55f4a + languageName: node + linkType: hard + +"postcss-color-hex-alpha@npm:^10.0.0": + version: 10.0.0 + resolution: "postcss-color-hex-alpha@npm:10.0.0" + dependencies: + "@csstools/utilities": "npm:^2.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 8a6dcb27403d04b55d6de88bf3074622bcea537fc4436bbcb346e92289c4d17059444e2e6c3554c325e7a777bb4cdc711e764a83123b4000aec211052e957d5b + languageName: node + linkType: hard + +"postcss-color-rebeccapurple@npm:^10.0.0": + version: 10.0.0 + resolution: "postcss-color-rebeccapurple@npm:10.0.0" + dependencies: + "@csstools/utilities": "npm:^2.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 308e33f76f2b48c1c2121d4502fc053e869f3415898de7d30314353df680e79b37497e7b628e3447edc1049091da3672f7d891e45604f238598e846e06b893ed + languageName: node + linkType: hard + "postcss-colormin@npm:^6.1.0": version: 6.1.0 resolution: "postcss-colormin@npm:6.1.0" @@ -9804,6 +10991,60 @@ __metadata: languageName: node linkType: hard +"postcss-custom-media@npm:^11.0.6": + version: 11.0.6 + resolution: "postcss-custom-media@npm:11.0.6" + dependencies: + "@csstools/cascade-layer-name-parser": "npm:^2.0.5" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/media-query-list-parser": "npm:^4.0.3" + peerDependencies: + postcss: ^8.4 + checksum: 62dcb2858fd490d90aab32062621d58892a7b2a54948ee63af81a2cd61807a11815d28d4ef6bc800c5e142ac73098f7e56822c7cc63192eb20d5b16071543a73 + languageName: node + linkType: hard + +"postcss-custom-properties@npm:^14.0.6": + version: 14.0.6 + resolution: "postcss-custom-properties@npm:14.0.6" + dependencies: + "@csstools/cascade-layer-name-parser": "npm:^2.0.5" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/utilities": "npm:^2.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 0eeef77bc713551f5cb8fa5982d24da4e854075f3af020f1c94366c47a23a4cc225ebfecc978bdb17f00ee0bdee9d2c784e0d01adc64a447321e408abbe2c83b + languageName: node + linkType: hard + +"postcss-custom-selectors@npm:^8.0.5": + version: 8.0.5 + resolution: "postcss-custom-selectors@npm:8.0.5" + dependencies: + "@csstools/cascade-layer-name-parser": "npm:^2.0.5" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: bd8f2f85bbec4bd56ff408cb699d9fe649e2af0db82d5752eee05481ae522f06f5a47950ca22fcb4c8601071c03346df67cf20b0b0bcade32ce58d07ebaf9b32 + languageName: node + linkType: hard + +"postcss-dir-pseudo-class@npm:^9.0.1": + version: 9.0.1 + resolution: "postcss-dir-pseudo-class@npm:9.0.1" + dependencies: + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: da9d3387648c5c3161a653d354c8f3e70a299108df3977e8aa65cf10793e4dd58a2711b3426cd63716245b13584ca8d95adcd6e10e3c9adbc61d08743e2d8690 + languageName: node + linkType: hard + "postcss-discard-comments@npm:^6.0.2": version: 6.0.2 resolution: "postcss-discard-comments@npm:6.0.2" @@ -9851,17 +11092,108 @@ __metadata: languageName: node linkType: hard -"postcss-loader@npm:^7.3.3": - version: 7.3.3 - resolution: "postcss-loader@npm:7.3.3" +"postcss-double-position-gradients@npm:^6.0.4": + version: 6.0.4 + resolution: "postcss-double-position-gradients@npm:6.0.4" + dependencies: + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 6dbbe7a3855e84a9319df434e210225f6dfa7262e5959611355f1769c2c9d30d37a19737712f20eac6354876fff4ba556d8d0b12a90c78d8ab97c9a8da534a7c + languageName: node + linkType: hard + +"postcss-focus-visible@npm:^10.0.1": + version: 10.0.1 + resolution: "postcss-focus-visible@npm:10.0.1" + dependencies: + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: c5ecc8536a708a49a99d0abd68a88a160664e6c832c808db8edd9f0221e7017a258daa87e49daf2cb098cb037005d46cf492403c8c9c92ad8835d30adaccf665 + languageName: node + linkType: hard + +"postcss-focus-within@npm:^9.0.1": + version: 9.0.1 + resolution: "postcss-focus-within@npm:9.0.1" + dependencies: + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: d6ab49d2a7f33485a9e137dc77ec92c5619a3ec92e1e672734fc604853ff1f3c0c189085c12461614be4fcb03ea0347d91791a45986a18d50b5228d161eda57a + languageName: node + linkType: hard + +"postcss-font-variant@npm:^5.0.0": + version: 5.0.0 + resolution: "postcss-font-variant@npm:5.0.0" + peerDependencies: + postcss: ^8.1.0 + checksum: ccc96460cf6a52b5439c26c9a5ea0589882e46161e3c2331d4353de7574448f5feef667d1a68f7f39b9fe3ee75d85957383ae82bbfcf87c3162c7345df4a444e + languageName: node + linkType: hard + +"postcss-gap-properties@npm:^6.0.0": + version: 6.0.0 + resolution: "postcss-gap-properties@npm:6.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 4e07e0d3927d0e65d67eaf047ac39e08d39cb1bf74e16e10c7df7f0d01b184a77ea59f63fd5691b5ed6df159970b972db28cb784d883e26e981137696460897d + languageName: node + linkType: hard + +"postcss-image-set-function@npm:^7.0.0": + version: 7.0.0 + resolution: "postcss-image-set-function@npm:7.0.0" + dependencies: + "@csstools/utilities": "npm:^2.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 913fd9492f00122aa0c2550fb0d72130428cbe1e6465bc65e8fe71e9deb10ac0c01d7caceb68b560da759139e8cbc6c90ed22dfe6cf34949af49bb86bcbf4d3a + languageName: node + linkType: hard + +"postcss-lab-function@npm:^7.0.12": + version: 7.0.12 + resolution: "postcss-lab-function@npm:7.0.12" + dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: de39b59da3b97c18d055d81fba68993e93253184ed76f103c888273584f868c551d047814dd54445980a1bdc5987e8f8af141383d84ecc641e5a6ee7bd901095 + languageName: node + linkType: hard + +"postcss-loader@npm:^7.3.4": + version: 7.3.4 + resolution: "postcss-loader@npm:7.3.4" dependencies: - cosmiconfig: "npm:^8.2.0" - jiti: "npm:^1.18.2" - semver: "npm:^7.3.8" + cosmiconfig: "npm:^8.3.5" + jiti: "npm:^1.20.0" + semver: "npm:^7.5.4" peerDependencies: postcss: ^7.0.0 || ^8.0.1 webpack: ^5.0.0 - checksum: d039654273f858be1f75dfdf8b550869d88905b73a7684b3e48a2937a6087619e84fd1a3551cdef78685a965a2573e985b29a532c3878d834071ecd2da0eb304 + checksum: 1bf7614aeea9ad1f8ee6be3a5451576c059391688ea67f825aedc2674056369597faeae4e4a81fe10843884c9904a71403d9a54197e1f560e8fbb9e61f2a2680 + languageName: node + linkType: hard + +"postcss-logical@npm:^8.1.0": + version: 8.1.0 + resolution: "postcss-logical@npm:8.1.0" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 0e2e9e901d8a550db7f682d46b1f7e4f363c1ada061dc8e4548e2b563c5e39f3684a2d7c3f11fe061188782bca37874e34967fc6179fa6d98a49ff66a0076d27 languageName: node linkType: hard @@ -9951,36 +11283,36 @@ __metadata: languageName: node linkType: hard -"postcss-modules-extract-imports@npm:^3.0.0": - version: 3.0.0 - resolution: "postcss-modules-extract-imports@npm:3.0.0" +"postcss-modules-extract-imports@npm:^3.1.0": + version: 3.1.0 + resolution: "postcss-modules-extract-imports@npm:3.1.0" peerDependencies: postcss: ^8.1.0 - checksum: f8879d66d8162fb7a3fcd916d37574006c584ea509107b1cfb798a5e090175ef9470f601e46f0a305070d8ff2500e07489a5c1ac381c29a1dc1120e827ca7943 + checksum: 402084bcab376083c4b1b5111b48ec92974ef86066f366f0b2d5b2ac2b647d561066705ade4db89875a13cb175b33dd6af40d16d32b2ea5eaf8bac63bd2bf219 languageName: node linkType: hard -"postcss-modules-local-by-default@npm:^4.0.3": - version: 4.0.3 - resolution: "postcss-modules-local-by-default@npm:4.0.3" +"postcss-modules-local-by-default@npm:^4.0.5": + version: 4.2.0 + resolution: "postcss-modules-local-by-default@npm:4.2.0" dependencies: icss-utils: "npm:^5.0.0" - postcss-selector-parser: "npm:^6.0.2" + postcss-selector-parser: "npm:^7.0.0" postcss-value-parser: "npm:^4.1.0" peerDependencies: postcss: ^8.1.0 - checksum: be49b86efbfb921f42287e227584aac91af9826fc1083db04958ae283dfe215ca539421bfba71f9da0f0b10651f28e95a64b5faca7166f578a1933b8646051f7 + checksum: b0b83feb2a4b61f5383979d37f23116c99bc146eba1741ca3cf1acca0e4d0dbf293ac1810a6ab4eccbe1ee76440dd0a9eb2db5b3bba4f99fc1b3ded16baa6358 languageName: node linkType: hard -"postcss-modules-scope@npm:^3.0.0": - version: 3.0.0 - resolution: "postcss-modules-scope@npm:3.0.0" +"postcss-modules-scope@npm:^3.2.0": + version: 3.2.1 + resolution: "postcss-modules-scope@npm:3.2.1" dependencies: - postcss-selector-parser: "npm:^6.0.4" + postcss-selector-parser: "npm:^7.0.0" peerDependencies: postcss: ^8.1.0 - checksum: 60af503910363689568c2c3701cb019a61b58b3d739391145185eec211bea5d50ccb6ecbe6955b39d856088072fd50ea002e40a52b50e33b181ff5c41da0308a + checksum: bd2d81f79e3da0ef6365b8e2c78cc91469d05b58046b4601592cdeef6c4050ed8fe1478ae000a1608042fc7e692cb51fecbd2d9bce3f4eace4d32e883ffca10b languageName: node linkType: hard @@ -9995,6 +11327,19 @@ __metadata: languageName: node linkType: hard +"postcss-nesting@npm:^13.0.2": + version: 13.0.2 + resolution: "postcss-nesting@npm:13.0.2" + dependencies: + "@csstools/selector-resolve-nested": "npm:^3.1.0" + "@csstools/selector-specificity": "npm:^5.0.0" + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: bfa0578b3b686c6374f5a7b2f6ef955cb7e13400de95a919975a982ae43c1e25db37385618f210715ff15393dc7ff8c26c7b156f06b8fb3118a426099cf7f1f2 + languageName: node + linkType: hard + "postcss-normalize-charset@npm:^6.0.2": version: 6.0.2 resolution: "postcss-normalize-charset@npm:6.0.2" @@ -10093,6 +11438,15 @@ __metadata: languageName: node linkType: hard +"postcss-opacity-percentage@npm:^3.0.0": + version: 3.0.0 + resolution: "postcss-opacity-percentage@npm:3.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 15c7d66036fa966d265c8737196646b3f93deb83d4eea0b17ed5033460599afc31d3a989345e4d7c472963b2a2bb75c83d06979d5d30d6a60fcc7f74cb6d8d40 + languageName: node + linkType: hard + "postcss-ordered-values@npm:^6.0.2": version: 6.0.2 resolution: "postcss-ordered-values@npm:6.0.2" @@ -10105,6 +11459,127 @@ __metadata: languageName: node linkType: hard +"postcss-overflow-shorthand@npm:^6.0.0": + version: 6.0.0 + resolution: "postcss-overflow-shorthand@npm:6.0.0" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 6598321b2ed0b68461135395bba9c7f76a4672617770df1e8487f459bc975f4ded6c3d37b6f72a44f4f77f7b6789e0c6f927e66dbbf1bcde1537167dbea39968 + languageName: node + linkType: hard + +"postcss-page-break@npm:^3.0.4": + version: 3.0.4 + resolution: "postcss-page-break@npm:3.0.4" + peerDependencies: + postcss: ^8 + checksum: eaaf4d8922b35f2acd637eb059f7e2510b24d65eb8f31424799dd5a98447b6ef010b41880c26e78f818e00f842295638ec75f89d5d489067f53e3dd3db74a00f + languageName: node + linkType: hard + +"postcss-place@npm:^10.0.0": + version: 10.0.0 + resolution: "postcss-place@npm:10.0.0" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: ebb13deaac7648ba6042622375a31f78fbcc5209b7d196e478debbdf94525963fe621c932f4737a5b6b3d487af3b5ed6d059ed6193fdcbff6d3d5b150886ccc1 + languageName: node + linkType: hard + +"postcss-preset-env@npm:^10.2.1": + version: 10.5.0 + resolution: "postcss-preset-env@npm:10.5.0" + dependencies: + "@csstools/postcss-alpha-function": "npm:^1.0.1" + "@csstools/postcss-cascade-layers": "npm:^5.0.2" + "@csstools/postcss-color-function": "npm:^4.0.12" + "@csstools/postcss-color-function-display-p3-linear": "npm:^1.0.1" + "@csstools/postcss-color-mix-function": "npm:^3.0.12" + "@csstools/postcss-color-mix-variadic-function-arguments": "npm:^1.0.2" + "@csstools/postcss-content-alt-text": "npm:^2.0.8" + "@csstools/postcss-contrast-color-function": "npm:^2.0.12" + "@csstools/postcss-exponential-functions": "npm:^2.0.9" + "@csstools/postcss-font-format-keywords": "npm:^4.0.0" + "@csstools/postcss-gamut-mapping": "npm:^2.0.11" + "@csstools/postcss-gradients-interpolation-method": "npm:^5.0.12" + "@csstools/postcss-hwb-function": "npm:^4.0.12" + "@csstools/postcss-ic-unit": "npm:^4.0.4" + "@csstools/postcss-initial": "npm:^2.0.1" + "@csstools/postcss-is-pseudo-class": "npm:^5.0.3" + "@csstools/postcss-light-dark-function": "npm:^2.0.11" + "@csstools/postcss-logical-float-and-clear": "npm:^3.0.0" + "@csstools/postcss-logical-overflow": "npm:^2.0.0" + "@csstools/postcss-logical-overscroll-behavior": "npm:^2.0.0" + "@csstools/postcss-logical-resize": "npm:^3.0.0" + "@csstools/postcss-logical-viewport-units": "npm:^3.0.4" + "@csstools/postcss-media-minmax": "npm:^2.0.9" + "@csstools/postcss-media-queries-aspect-ratio-number-values": "npm:^3.0.5" + "@csstools/postcss-nested-calc": "npm:^4.0.0" + "@csstools/postcss-normalize-display-values": "npm:^4.0.0" + "@csstools/postcss-oklab-function": "npm:^4.0.12" + "@csstools/postcss-position-area-property": "npm:^1.0.0" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/postcss-random-function": "npm:^2.0.1" + "@csstools/postcss-relative-color-syntax": "npm:^3.0.12" + "@csstools/postcss-scope-pseudo-class": "npm:^4.0.1" + "@csstools/postcss-sign-functions": "npm:^1.1.4" + "@csstools/postcss-stepped-value-functions": "npm:^4.0.9" + "@csstools/postcss-system-ui-font-family": "npm:^1.0.0" + "@csstools/postcss-text-decoration-shorthand": "npm:^4.0.3" + "@csstools/postcss-trigonometric-functions": "npm:^4.0.9" + "@csstools/postcss-unset-value": "npm:^4.0.0" + autoprefixer: "npm:^10.4.22" + browserslist: "npm:^4.28.0" + css-blank-pseudo: "npm:^7.0.1" + css-has-pseudo: "npm:^7.0.3" + css-prefers-color-scheme: "npm:^10.0.0" + cssdb: "npm:^8.5.2" + postcss-attribute-case-insensitive: "npm:^7.0.1" + postcss-clamp: "npm:^4.1.0" + postcss-color-functional-notation: "npm:^7.0.12" + postcss-color-hex-alpha: "npm:^10.0.0" + postcss-color-rebeccapurple: "npm:^10.0.0" + postcss-custom-media: "npm:^11.0.6" + postcss-custom-properties: "npm:^14.0.6" + postcss-custom-selectors: "npm:^8.0.5" + postcss-dir-pseudo-class: "npm:^9.0.1" + postcss-double-position-gradients: "npm:^6.0.4" + postcss-focus-visible: "npm:^10.0.1" + postcss-focus-within: "npm:^9.0.1" + postcss-font-variant: "npm:^5.0.0" + postcss-gap-properties: "npm:^6.0.0" + postcss-image-set-function: "npm:^7.0.0" + postcss-lab-function: "npm:^7.0.12" + postcss-logical: "npm:^8.1.0" + postcss-nesting: "npm:^13.0.2" + postcss-opacity-percentage: "npm:^3.0.0" + postcss-overflow-shorthand: "npm:^6.0.0" + postcss-page-break: "npm:^3.0.4" + postcss-place: "npm:^10.0.0" + postcss-pseudo-class-any-link: "npm:^10.0.1" + postcss-replace-overflow-wrap: "npm:^4.0.0" + postcss-selector-not: "npm:^8.0.1" + peerDependencies: + postcss: ^8.4 + checksum: 4e9881478b465e8eb7493c1240cb2df8523944135728672e8feeb8bb3f6a48b00d67d007ee8fbdcee648ab9ebdfca10a7591f42e3c6b9076cbf7f355f8ad1574 + languageName: node + linkType: hard + +"postcss-pseudo-class-any-link@npm:^10.0.1": + version: 10.0.1 + resolution: "postcss-pseudo-class-any-link@npm:10.0.1" + dependencies: + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 95e883996e87baf14fc09d25f9a763a2e9d599eb3b9c6b736e83a8c3d0b55841bcb886bccdf51b5b7fefc128cbd0187ad8841f59878f85bd1613642e592d7673 + languageName: node + linkType: hard + "postcss-reduce-idents@npm:^6.0.3": version: 6.0.3 resolution: "postcss-reduce-idents@npm:6.0.3" @@ -10139,6 +11614,26 @@ __metadata: languageName: node linkType: hard +"postcss-replace-overflow-wrap@npm:^4.0.0": + version: 4.0.0 + resolution: "postcss-replace-overflow-wrap@npm:4.0.0" + peerDependencies: + postcss: ^8.0.3 + checksum: 451361b714528cd3632951256ef073769cde725a46cda642a6864f666fb144921fa55e614aec1bcf5946f37d6ffdcca3b932b76f3d997c07b076e8db152b128d + languageName: node + linkType: hard + +"postcss-selector-not@npm:^8.0.1": + version: 8.0.1 + resolution: "postcss-selector-not@npm:8.0.1" + dependencies: + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 491ea3dcc421cd90135be786078521605e2062fb93624ea8813cfd5ba0d35143f931e2e608d5f20effd5ea7d3f4786d2afea2afa42d117779a0288e135f132b6 + languageName: node + linkType: hard + "postcss-selector-parser@npm:^6.0.11, postcss-selector-parser@npm:^6.0.16": version: 6.1.2 resolution: "postcss-selector-parser@npm:6.1.2" @@ -10149,13 +11644,13 @@ __metadata: languageName: node linkType: hard -"postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4": - version: 6.0.13 - resolution: "postcss-selector-parser@npm:6.0.13" +"postcss-selector-parser@npm:^7.0.0": + version: 7.1.1 + resolution: "postcss-selector-parser@npm:7.1.1" dependencies: cssesc: "npm:^3.0.0" util-deprecate: "npm:^1.0.2" - checksum: 51f099b27f7c7198ea1826470ef0adfa58b3bd3f59b390fda123baa0134880a5fa9720137b6009c4c1373357b144f700b0edac73335d0067422063129371444e + checksum: 02d3b1589ddcddceed4b583b098b95a7266dacd5135f041e5d913ebb48e874fd333a36e564cc9a2ec426a464cb18db11cb192ac76247aced5eba8c951bf59507 languageName: node linkType: hard @@ -10209,7 +11704,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.4.21, postcss@npm:^8.4.26": +"postcss@npm:^8.4.21": version: 8.4.31 resolution: "postcss@npm:8.4.31" dependencies: @@ -10220,7 +11715,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.4.24, postcss@npm:^8.4.38": +"postcss@npm:^8.4.24": version: 8.4.47 resolution: "postcss@npm:8.4.47" dependencies: @@ -10231,12 +11726,23 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^3.6.2": - version: 3.6.2 - resolution: "prettier@npm:3.6.2" +"postcss@npm:^8.4.33, postcss@npm:^8.5.4": + version: 8.5.6 + resolution: "postcss@npm:8.5.6" + dependencies: + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 5127cc7c91ed7a133a1b7318012d8bfa112da9ef092dddf369ae699a1f10ebbd89b1b9f25f3228795b84585c72aabd5ced5fc11f2ba467eedf7b081a66fad024 + languageName: node + linkType: hard + +"prettier@npm:^3.7.4": + version: 3.7.4 + resolution: "prettier@npm:3.7.4" bin: prettier: bin/prettier.cjs - checksum: 488cb2f2b99ec13da1e50074912870217c11edaddedeadc649b1244c749d15ba94e846423d062e2c4c9ae683e2d65f754de28889ba06e697ac4f988d44f45812 + checksum: 9675d2cd08eacb1faf1d1a2dbfe24bfab6a912b059fc9defdb380a408893d88213e794a40a2700bd29b140eb3172e0b07c852853f6e22f16f3374659a1a13389 languageName: node linkType: hard @@ -10257,7 +11763,7 @@ __metadata: languageName: node linkType: hard -"prism-react-renderer@npm:^2.3.0, prism-react-renderer@npm:^2.4.0": +"prism-react-renderer@npm:^2.3.0": version: 2.4.0 resolution: "prism-react-renderer@npm:2.4.0" dependencies: @@ -10269,6 +11775,18 @@ __metadata: languageName: node linkType: hard +"prism-react-renderer@npm:^2.4.1": + version: 2.4.1 + resolution: "prism-react-renderer@npm:2.4.1" + dependencies: + "@types/prismjs": "npm:^1.26.0" + clsx: "npm:^2.0.0" + peerDependencies: + react: ">=16.0.0" + checksum: ebbe8feb975224344bbdd046b3a937d121592dbe4b8f22ba0be31f5af37b9a8219f441138ef6cab1c5b96f2aa6b529015200959f7e5e85b60ca69c81d35edcd4 + languageName: node + linkType: hard + "prismjs@npm:^1.29.0": version: 1.29.0 resolution: "prismjs@npm:1.29.0" @@ -10368,12 +11886,12 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.11.0": - version: 6.11.0 - resolution: "qs@npm:6.11.0" +"qs@npm:~6.14.0": + version: 6.14.0 + resolution: "qs@npm:6.14.0" dependencies: - side-channel: "npm:^1.0.4" - checksum: 4e4875e4d7c7c31c233d07a448e7e4650f456178b9dd3766b7cfa13158fdb24ecb8c4f059fa91e820dc6ab9f2d243721d071c9c0378892dcdad86e9e9a27c68f + side-channel: "npm:^1.1.0" + checksum: 8ea5d91bf34f440598ee389d4a7d95820e3b837d3fd9f433871f7924801becaa0cd3b3b4628d49a7784d06a8aea9bc4554d2b6d8d584e2d221dc06238a42909c languageName: node linkType: hard @@ -10396,15 +11914,6 @@ __metadata: languageName: node linkType: hard -"queue@npm:6.0.2": - version: 6.0.2 - resolution: "queue@npm:6.0.2" - dependencies: - inherits: "npm:~2.0.3" - checksum: cf987476cc72e7d3aaabe23ccefaab1cd757a2b5e0c8d80b67c9575a6b5e1198807ffd4f0948a3f118b149d1111d810ee773473530b77a5c606673cac2c9c996 - languageName: node - linkType: hard - "quick-lru@npm:^5.1.1": version: 5.1.1 resolution: "quick-lru@npm:5.1.1" @@ -10435,15 +11944,15 @@ __metadata: languageName: node linkType: hard -"raw-body@npm:2.5.1": - version: 2.5.1 - resolution: "raw-body@npm:2.5.1" +"raw-body@npm:~2.5.3": + version: 2.5.3 + resolution: "raw-body@npm:2.5.3" dependencies: - bytes: "npm:3.1.2" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.4.24" - unpipe: "npm:1.0.0" - checksum: 5dad5a3a64a023b894ad7ab4e5c7c1ce34d3497fc7138d02f8c88a3781e68d8a55aa7d4fd3a458616fa8647cc228be314a1c03fb430a07521de78b32c4dd09d2 + bytes: "npm:~3.1.2" + http-errors: "npm:~2.0.1" + iconv-lite: "npm:~0.4.24" + unpipe: "npm:~1.0.0" + checksum: 449844344fc90547fb994383a494b83300e4f22199f146a79f68d78a199a8f2a923ea9fd29c3be979bfd50291a3884733619ffc15ba02a32e703b612f8d3f74a languageName: node linkType: hard @@ -10461,81 +11970,27 @@ __metadata: languageName: node linkType: hard -"react-dev-utils@npm:^12.0.1": - version: 12.0.1 - resolution: "react-dev-utils@npm:12.0.1" - dependencies: - "@babel/code-frame": "npm:^7.16.0" - address: "npm:^1.1.2" - browserslist: "npm:^4.18.1" - chalk: "npm:^4.1.2" - cross-spawn: "npm:^7.0.3" - detect-port-alt: "npm:^1.1.6" - escape-string-regexp: "npm:^4.0.0" - filesize: "npm:^8.0.6" - find-up: "npm:^5.0.0" - fork-ts-checker-webpack-plugin: "npm:^6.5.0" - global-modules: "npm:^2.0.0" - globby: "npm:^11.0.4" - gzip-size: "npm:^6.0.0" - immer: "npm:^9.0.7" - is-root: "npm:^2.1.0" - loader-utils: "npm:^3.2.0" - open: "npm:^8.4.0" - pkg-up: "npm:^3.1.0" - prompts: "npm:^2.4.2" - react-error-overlay: "npm:^6.0.11" - recursive-readdir: "npm:^2.2.2" - shell-quote: "npm:^1.7.3" - strip-ansi: "npm:^6.0.1" - text-table: "npm:^0.2.0" - checksum: 94bc4ee5014290ca47a025e53ab2205c5dc0299670724d46a0b1bacbdd48904827b5ae410842d0a3a92481509097ae032e4a9dc7ca70db437c726eaba6411e82 - languageName: node - linkType: hard - -"react-dom@npm:^18.3.1": - version: 18.3.1 - resolution: "react-dom@npm:18.3.1" +"react-dom@npm:^19.2.1": + version: 19.2.1 + resolution: "react-dom@npm:19.2.1" dependencies: - loose-envify: "npm:^1.1.0" - scheduler: "npm:^0.23.2" + scheduler: "npm:^0.27.0" peerDependencies: - react: ^18.3.1 - checksum: a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85 - languageName: node - linkType: hard - -"react-error-overlay@npm:^6.0.11": - version: 6.0.11 - resolution: "react-error-overlay@npm:6.0.11" - checksum: 8fc93942976e0c704274aec87dbc8e21f62a2cc78d1c93f9bcfff9f7494b00c60f7a2f0bd48d832bcd3190627c0255a1df907373f61f820371373a65ec4b2d64 + react: ^19.2.1 + checksum: e56b6b3d72314df580ca800b70a69a21c6372703c8f45d9b5451ca6519faefb2496d76ffa9c5adb94136d2bbf2fd303d0dfc208a2cd77ede3132877471af9470 languageName: node linkType: hard -"react-fast-compare@npm:^3.2.0, react-fast-compare@npm:^3.2.2": +"react-fast-compare@npm:^3.2.0": version: 3.2.2 resolution: "react-fast-compare@npm:3.2.2" checksum: 0bbd2f3eb41ab2ff7380daaa55105db698d965c396df73e6874831dbafec8c4b5b08ba36ff09df01526caa3c61595247e3269558c284e37646241cba2b90a367 languageName: node linkType: hard -"react-helmet-async@npm:*": - version: 2.0.1 - resolution: "react-helmet-async@npm:2.0.1" - dependencies: - invariant: "npm:^2.2.4" - react-fast-compare: "npm:^3.2.2" - shallowequal: "npm:^1.1.0" - peerDependencies: - react: ^16.6.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0 - checksum: f1fec97ae4d8d37fc040446adc473c26422b32fa9c73efa5ac6cb234090d1b51b33c603fefa6ec21332b3277f544235d56f2567c65afe1d349fc784fd3d99cd7 - languageName: node - linkType: hard - -"react-helmet-async@npm:^1.3.0": +"react-helmet-async@npm:@slorber/react-helmet-async@1.3.0": version: 1.3.0 - resolution: "react-helmet-async@npm:1.3.0" + resolution: "@slorber/react-helmet-async@npm:1.3.0" dependencies: "@babel/runtime": "npm:^7.12.5" invariant: "npm:^2.2.4" @@ -10543,9 +11998,9 @@ __metadata: react-fast-compare: "npm:^3.2.0" shallowequal: "npm:^1.1.0" peerDependencies: - react: ^16.6.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0 - checksum: 8f3e6d26beff61d2ed18f7b41561df3e4d83a7582914c7196aa65158c7f3cce939276547d7a0b8987952d9d44131406df74efba02d1f8fa8a3940b49e6ced70b + react: ^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 7a13470a0d27d6305657c7fa6b066443c94acdb22bd0decca772298bc852ce04fdc65f1207f0d546995bf7d4ca09e21c81f96b4954544937c01eda82e2caa142 languageName: node linkType: hard @@ -10556,19 +12011,19 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^18.2.0": - version: 18.2.0 - resolution: "react-is@npm:18.2.0" - checksum: 6eb5e4b28028c23e2bfcf73371e72cd4162e4ac7ab445ddae2afe24e347a37d6dc22fae6e1748632cd43c6d4f9b8f86dcf26bf9275e1874f436d129952528ae0 +"react-is@npm:^19.1.0": + version: 19.2.1 + resolution: "react-is@npm:19.2.1" + checksum: 0ebeaedb4ff615055cbcd758c7e22ba9644e21110adbd293dd1aada3591abf7399152a786cd120e324c10706d75e28c2130c27d1b9b5ae637aef4c52f4d17a91 languageName: node linkType: hard -"react-json-view-lite@npm:^1.2.0": - version: 1.5.0 - resolution: "react-json-view-lite@npm:1.5.0" +"react-json-view-lite@npm:^2.3.0": + version: 2.5.0 + resolution: "react-json-view-lite@npm:2.5.0" peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - checksum: e707717cb6b9d6cca5b138cdfb066e35ee7e493d1c88d4497e3a3a42b7651c8ff924ff53ad2da142a12b23b11379d39f38d8eee278c98c46cd6bc8844864b285 + react: ^18.0.0 || ^19.0.0 + checksum: 8ecaa23d2fddea03f84892ca96577c5416d60a59ed2cad01dff648a60d25b799dac75dea1771e2b9b639ad026ce1efa7b44e6e636bf497b1d6ea0bac5962b96d languageName: node linkType: hard @@ -10600,24 +12055,24 @@ __metadata: 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" + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/faster": "npm:3.9.2" + "@docusaurus/plugin-client-redirects": "npm:3.9.2" + "@docusaurus/plugin-google-analytics": "npm:3.9.2" + "@docusaurus/preset-classic": "npm:3.9.2" + "@docusaurus/remark-plugin-npm2yarn": "npm:3.9.2" "@ffprobe-installer/ffprobe": "npm:^2.1.2" - "@octokit/graphql": "npm:^7.1.0" - "@react-navigation/core": "npm:^7.0.4" + "@octokit/graphql": "npm:^9.0.3" + "@react-navigation/core": "npm:^7.13.5" escape-html: "npm:^1.0.3" - markdownlint: "npm:^0.36.1" - markdownlint-cli2: "npm:^0.14.0" + markdownlint: "npm:^0.40.0" + markdownlint-cli2: "npm:^0.19.1" mkdirp: "npm:^3.0.1" netlify-plugin-cache: "npm:^1.0.3" - prettier: "npm:^3.6.2" - prism-react-renderer: "npm:^2.4.0" - react: "npm:^18.3.1" - react-dom: "npm:^18.3.1" + prettier: "npm:^3.7.4" + prism-react-renderer: "npm:^2.4.1" + react: "npm:^19.2.1" + react-dom: "npm:^19.2.1" react-simple-code-editor: "npm:^0.14.1" recast: "npm:^0.23.11" languageName: unknown @@ -10681,12 +12136,10 @@ __metadata: languageName: node linkType: hard -"react@npm:^18.3.1": - version: 18.3.1 - resolution: "react@npm:18.3.1" - dependencies: - loose-envify: "npm:^1.1.0" - checksum: 283e8c5efcf37802c9d1ce767f302dd569dd97a70d9bb8c7be79a789b9902451e0d16334b05d73299b20f048cbc3c7d288bbbde10b701fa194e2089c237dbea3 +"react@npm:^19.2.1": + version: 19.2.1 + resolution: "react@npm:19.2.1" + checksum: 2b5eaf407abb3db84090434c20d6c5a8e447ab7abcd8fe9eaf1ddc299babcf31284ee9db7ea5671d21c85ac5298bd632fa1a7da1ed78d5b368a537f5e1cd5d62 languageName: node linkType: hard @@ -10725,13 +12178,6 @@ __metadata: languageName: node linkType: hard -"reading-time@npm:^1.5.0": - version: 1.5.0 - resolution: "reading-time@npm:1.5.0" - checksum: 0f730852fd4fb99e5f78c5b0cf36ab8c3fa15db96f87d9563843f6fd07a47864273ade539ebb184b785b728cde81a70283aa2d9b80cba5ca03b81868be03cabc - languageName: node - linkType: hard - "recast@npm:^0.23.11": version: 0.23.11 resolution: "recast@npm:0.23.11" @@ -10745,24 +12191,6 @@ __metadata: languageName: node linkType: hard -"rechoir@npm:^0.6.2": - version: 0.6.2 - resolution: "rechoir@npm:0.6.2" - dependencies: - resolve: "npm:^1.1.6" - checksum: 22c4bb32f4934a9468468b608417194f7e3ceba9a508512125b16082c64f161915a28467562368eeb15dc16058eb5b7c13a20b9eb29ff9927d1ebb3b5aa83e84 - languageName: node - linkType: hard - -"recursive-readdir@npm:^2.2.2": - version: 2.2.3 - resolution: "recursive-readdir@npm:2.2.3" - dependencies: - minimatch: "npm:^3.0.5" - checksum: d0238f137b03af9cd645e1e0b40ae78b6cda13846e3ca57f626fcb58a66c79ae018a10e926b13b3a460f1285acc946a4e512ea8daa2e35df4b76a105709930d1 - languageName: node - linkType: hard - "regenerate-unicode-properties@npm:^10.2.0": version: 10.2.0 resolution: "regenerate-unicode-properties@npm:10.2.0" @@ -11022,7 +12450,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.1.6, resolve@npm:^1.14.2": +"resolve@npm:^1.14.2": version: 1.22.8 resolution: "resolve@npm:1.22.8" dependencies: @@ -11035,7 +12463,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.1.6#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin": +"resolve@patch:resolve@npm%3A^1.14.2#optional!builtin": version: 1.22.8 resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" dependencies: @@ -11078,24 +12506,6 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^3.0.2": - version: 3.0.2 - resolution: "rimraf@npm:3.0.2" - dependencies: - glob: "npm:^7.1.3" - bin: - rimraf: bin.js - checksum: 9cb7757acb489bd83757ba1a274ab545eafd75598a9d817e0c3f8b164238dd90eba50d6b848bd4dcc5f3040912e882dc7ba71653e35af660d77b25c381d402e8 - languageName: node - linkType: hard - -"rtl-detect@npm:^1.0.4": - version: 1.1.2 - resolution: "rtl-detect@npm:1.1.2" - checksum: 1b92888aafca1593314f837e83fdf02eb208faae3e713ab87c176804728efd3b1980d53b64f65f1fa593348087e852c5cd729b7b9372950f6e9b7be489afc0ca - languageName: node - linkType: hard - "rtlcss@npm:^4.1.0": version: 4.1.1 resolution: "rtlcss@npm:4.1.1" @@ -11110,6 +12520,13 @@ __metadata: languageName: node linkType: hard +"run-applescript@npm:^7.0.0": + version: 7.1.0 + resolution: "run-applescript@npm:7.1.0" + checksum: ab826c57c20f244b2ee807704b1ef4ba7f566aa766481ae5922aac785e2570809e297c69afcccc3593095b538a8a77d26f2b2e9a1d9dffee24e0e039502d1a03 + languageName: node + linkType: hard + "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -11147,23 +12564,17 @@ __metadata: languageName: node linkType: hard -"scheduler@npm:^0.23.2": - version: 0.23.2 - resolution: "scheduler@npm:0.23.2" - dependencies: - loose-envify: "npm:^1.1.0" - checksum: 26383305e249651d4c58e6705d5f8425f153211aef95f15161c151f7b8de885f24751b377e4a0b3dd42cce09aad3f87a61dab7636859c0d89b7daf1a1e2a5c78 +"scheduler@npm:^0.27.0": + version: 0.27.0 + resolution: "scheduler@npm:0.27.0" + checksum: 4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452 languageName: node linkType: hard -"schema-utils@npm:2.7.0": - version: 2.7.0 - resolution: "schema-utils@npm:2.7.0" - dependencies: - "@types/json-schema": "npm:^7.0.4" - ajv: "npm:^6.12.2" - ajv-keywords: "npm:^3.4.1" - checksum: 723c3c856a0313a89aa81c5fb2c93d4b11225f5cdd442665fddd55d3c285ae72e079f5286a3a9a1a973affe888f6c33554a2cf47b79b24cd8de2f1f756a6fb1b +"schema-dts@npm:^1.1.2": + version: 1.1.5 + resolution: "schema-dts@npm:1.1.5" + checksum: babe23a1577c75c5df79d73acf34af3399e60928eab46f2236a0c4212061f5778d613a31c9e9ec86a2807d20b1ea460673d72d3fe1f64fb7543867460e607f76 languageName: node linkType: hard @@ -11190,6 +12601,18 @@ __metadata: languageName: node linkType: hard +"schema-utils@npm:^4.2.0": + version: 4.3.3 + resolution: "schema-utils@npm:4.3.3" + dependencies: + "@types/json-schema": "npm:^7.0.9" + ajv: "npm:^8.9.0" + ajv-formats: "npm:^2.1.1" + ajv-keywords: "npm:^5.1.0" + checksum: 1c8d2c480a026d7c02ab2ecbe5919133a096d6a721a3f201fa50663e4f30f6d6ba020dfddd93cb828b66b922e76b342e103edd19a62c95c8f60e9079cc403202 + languageName: node + linkType: hard + "section-matter@npm:^1.0.0": version: 1.0.0 resolution: "section-matter@npm:1.0.0" @@ -11207,7 +12630,7 @@ __metadata: languageName: node linkType: hard -"selfsigned@npm:^2.1.1": +"selfsigned@npm:^2.4.1": version: 2.4.1 resolution: "selfsigned@npm:2.4.1" dependencies: @@ -11235,7 +12658,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.4": +"semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.4": version: 7.5.4 resolution: "semver@npm:7.5.4" dependencies: @@ -11246,9 +12669,9 @@ __metadata: languageName: node linkType: hard -"send@npm:0.18.0": - version: 0.18.0 - resolution: "send@npm:0.18.0" +"send@npm:0.19.0": + version: 0.19.0 + resolution: "send@npm:0.19.0" dependencies: debug: "npm:2.6.9" depd: "npm:2.0.0" @@ -11263,7 +12686,28 @@ __metadata: on-finished: "npm:2.4.1" range-parser: "npm:~1.2.1" statuses: "npm:2.0.1" - checksum: 0eb134d6a51fc13bbcb976a1f4214ea1e33f242fae046efc311e80aff66c7a43603e26a79d9d06670283a13000e51be6e0a2cb80ff0942eaf9f1cd30b7ae736a + checksum: ea3f8a67a8f0be3d6bf9080f0baed6d2c51d11d4f7b4470de96a5029c598a7011c497511ccc28968b70ef05508675cebff27da9151dd2ceadd60be4e6cf845e3 + languageName: node + linkType: hard + +"send@npm:~0.19.0": + version: 0.19.1 + resolution: "send@npm:0.19.1" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:2.0.1" + checksum: ceb859859822bf55e705b96db9a909870626d1a6bfcf62a88648b9681048a7840c0ff1f4afd7babea4ccfabff7d64a7dda68a6f6c63c255cc83f40a412a1db8e languageName: node linkType: hard @@ -11306,15 +12750,15 @@ __metadata: languageName: node linkType: hard -"serve-static@npm:1.15.0": - version: 1.15.0 - resolution: "serve-static@npm:1.15.0" +"serve-static@npm:~1.16.2": + version: 1.16.2 + resolution: "serve-static@npm:1.16.2" dependencies: - encodeurl: "npm:~1.0.2" + encodeurl: "npm:~2.0.0" escape-html: "npm:~1.0.3" parseurl: "npm:~1.3.3" - send: "npm:0.18.0" - checksum: fa9f0e21a540a28f301258dfe1e57bb4f81cd460d28f0e973860477dd4acef946a1f41748b5bd41c73b621bea2029569c935faa38578fd34cd42a9b4947088ba + send: "npm:0.19.0" + checksum: 528fff6f5e12d0c5a391229ad893910709bc51b5705962b09404a1d813857578149b8815f35d3ee5752f44cd378d0f31669d4b1d7e2d11f41e08283d5134bd1f languageName: node linkType: hard @@ -11337,7 +12781,7 @@ __metadata: languageName: node linkType: hard -"setprototypeof@npm:1.2.0": +"setprototypeof@npm:1.2.0, setprototypeof@npm:~1.2.0": version: 1.2.0 resolution: "setprototypeof@npm:1.2.0" checksum: 68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc @@ -11376,34 +12820,58 @@ __metadata: languageName: node linkType: hard -"shell-quote@npm:^1.7.3, shell-quote@npm:^1.8.1": - version: 1.8.1 - resolution: "shell-quote@npm:1.8.1" - checksum: 8cec6fd827bad74d0a49347057d40dfea1e01f12a6123bf82c4649f3ef152fc2bc6d6176e6376bffcd205d9d0ccb4f1f9acae889384d20baff92186f01ea455a +"shell-quote@npm:^1.8.3": + version: 1.8.3 + resolution: "shell-quote@npm:1.8.3" + checksum: bee87c34e1e986cfb4c30846b8e6327d18874f10b535699866f368ade11ea4ee45433d97bf5eada22c4320c27df79c3a6a7eb1bf3ecfc47f2c997d9e5e2672fd languageName: node linkType: hard -"shelljs@npm:^0.8.5": - version: 0.8.5 - resolution: "shelljs@npm:0.8.5" +"side-channel-list@npm:^1.0.0": + version: 1.0.0 + resolution: "side-channel-list@npm:1.0.0" dependencies: - glob: "npm:^7.0.0" - interpret: "npm:^1.0.0" - rechoir: "npm:^0.6.2" - bin: - shjs: bin/shjs - checksum: feb25289a12e4bcd04c40ddfab51aff98a3729f5c2602d5b1a1b95f6819ec7804ac8147ebd8d9a85dfab69d501bcf92d7acef03247320f51c1552cec8d8e2382 + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + checksum: 644f4ac893456c9490ff388bf78aea9d333d5e5bfc64cfb84be8f04bf31ddc111a8d4b83b85d7e7e8a7b845bc185a9ad02c052d20e086983cf59f0be517d9b3d languageName: node linkType: hard -"side-channel@npm:^1.0.4": - version: 1.0.4 - resolution: "side-channel@npm:1.0.4" +"side-channel-map@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-map@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + checksum: 010584e6444dd8a20b85bc926d934424bd809e1a3af941cace229f7fdcb751aada0fb7164f60c2e22292b7fa3c0ff0bce237081fd4cdbc80de1dc68e95430672 + languageName: node + linkType: hard + +"side-channel-weakmap@npm:^1.0.2": + version: 1.0.2 + resolution: "side-channel-weakmap@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + side-channel-map: "npm:^1.0.1" + checksum: 71362709ac233e08807ccd980101c3e2d7efe849edc51455030327b059f6c4d292c237f94dc0685031dd11c07dd17a68afde235d6cf2102d949567f98ab58185 + languageName: node + linkType: hard + +"side-channel@npm:^1.1.0": + version: 1.1.0 + resolution: "side-channel@npm:1.1.0" dependencies: - call-bind: "npm:^1.0.0" - get-intrinsic: "npm:^1.0.2" - object-inspect: "npm:^1.9.0" - checksum: 054a5d23ee35054b2c4609b9fd2a0587760737782b5d765a9c7852264710cc39c6dcb56a9bbd6c12cd84071648aea3edb2359d2f6e560677eedadce511ac1da5 + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + side-channel-list: "npm:^1.0.0" + side-channel-map: "npm:^1.0.1" + side-channel-weakmap: "npm:^1.0.2" + checksum: cb20dad41eb032e6c24c0982e1e5a24963a28aa6122b4f05b3f3d6bf8ae7fd5474ef382c8f54a6a3ab86e0cac4d41a23bd64ede3970e5bfb50326ba02a7996e6 languageName: node linkType: hard @@ -11655,6 +13123,13 @@ __metadata: languageName: node linkType: hard +"statuses@npm:~2.0.1, statuses@npm:~2.0.2": + version: 2.0.2 + resolution: "statuses@npm:2.0.2" + checksum: a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f + languageName: node + linkType: hard + "std-env@npm:^3.7.0": version: 3.7.0 resolution: "std-env@npm:3.7.0" @@ -11680,6 +13155,16 @@ __metadata: languageName: node linkType: hard +"string-width@npm:8.1.0": + version: 8.1.0 + resolution: "string-width@npm:8.1.0" + dependencies: + get-east-asian-width: "npm:^1.3.0" + strip-ansi: "npm:^7.1.0" + checksum: 749b5d0dab2532b4b6b801064230f4da850f57b3891287023117ab63a464ad79dd208f42f793458f48f3ad121fe2e1f01dd525ff27ead957ed9f205e27406593 + languageName: node + linkType: hard + "string-width@npm:^5.0.1, string-width@npm:^5.1.2": version: 5.1.2 resolution: "string-width@npm:5.1.2" @@ -11748,6 +13233,15 @@ __metadata: languageName: node linkType: hard +"strip-ansi@npm:^7.1.0": + version: 7.1.2 + resolution: "strip-ansi@npm:7.1.2" + dependencies: + ansi-regex: "npm:^6.0.1" + checksum: 0d6d7a023de33368fd042aab0bf48f4f4077abdfd60e5393e73c7c411e85e1b3a83507c11af2e656188511475776215df9ca589b4da2295c9455cc399ce1858b + languageName: node + linkType: hard + "strip-bom-string@npm:^1.0.0": version: 1.0.0 resolution: "strip-bom-string@npm:1.0.0" @@ -11867,10 +13361,15 @@ __metadata: languageName: node linkType: hard -"tapable@npm:^1.0.0": - version: 1.1.3 - resolution: "tapable@npm:1.1.3" - checksum: c9f0265e55e45821ec672b9b9ee8a35d95bf3ea6b352199f8606a2799018e89cfe4433c554d424b31fc67c4be26b05d4f36dc3c607def416fdb2514cd63dba50 +"swr@npm:^2.2.5": + version: 2.3.7 + resolution: "swr@npm:2.3.7" + dependencies: + dequal: "npm:^2.0.3" + use-sync-external-store: "npm:^1.4.0" + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: a3f3fbf86f0734d38005d08e7c1401019a752d1a0f831a0c631f1e8a2cfa34a9945e4ae842cfc5a7aefa002a458b296f48f7f2d6e921ddc7465b91542df97e78 languageName: node linkType: hard @@ -11967,10 +13466,19 @@ __metadata: languageName: node linkType: hard -"text-table@npm:^0.2.0": - version: 0.2.0 - resolution: "text-table@npm:0.2.0" - checksum: 02805740c12851ea5982686810702e2f14369a5f4c5c40a836821e3eefc65ffeec3131ba324692a37608294b0fd8c1e55a2dd571ffed4909822787668ddbee5c +"thingies@npm:^2.5.0": + version: 2.5.0 + resolution: "thingies@npm:2.5.0" + peerDependencies: + tslib: ^2 + checksum: 52194642c129615b6af15648621be9a2784ad25526e3facca6c28aa1a36ea32245ef146ebc3fbaf64a3605b8301a5335da505d0c314f851ff293b184e0de7fb9 + languageName: node + linkType: hard + +"throttleit@npm:2.1.0": + version: 2.1.0 + resolution: "throttleit@npm:2.1.0" + checksum: 1696ae849522cea6ba4f4f3beac1f6655d335e51b42d99215e196a718adced0069e48deaaf77f7e89f526ab31de5b5c91016027da182438e6f9280be2f3d5265 languageName: node linkType: hard @@ -12002,6 +13510,13 @@ __metadata: languageName: node linkType: hard +"tinypool@npm:^1.0.2": + version: 1.1.1 + resolution: "tinypool@npm:1.1.1" + checksum: bf26727d01443061b04fa863f571016950888ea994ba0cd8cba3a1c51e2458d84574341ab8dbc3664f1c3ab20885c8cf9ff1cc4b18201f04c2cde7d317fff69b + languageName: node + linkType: hard + "to-fast-properties@npm:^2.0.0": version: 2.0.0 resolution: "to-fast-properties@npm:2.0.0" @@ -12018,7 +13533,7 @@ __metadata: languageName: node linkType: hard -"toidentifier@npm:1.0.1": +"toidentifier@npm:1.0.1, toidentifier@npm:~1.0.1": version: 1.0.1 resolution: "toidentifier@npm:1.0.1" checksum: 93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1 @@ -12032,6 +13547,15 @@ __metadata: languageName: node linkType: hard +"tree-dump@npm:^1.0.3, tree-dump@npm:^1.1.0": + version: 1.1.0 + resolution: "tree-dump@npm:1.1.0" + peerDependencies: + tslib: 2 + checksum: 079f0f0163b68ee2eedc65cab1de6fb121487eba9ae135c106a8bc5e4ab7906ae0b57d86016e4a7da8c0ee906da1eae8c6a1490cd6e2a5e5ccbca321e1f959ca + languageName: node + linkType: hard + "trim-lines@npm:^3.0.0": version: 3.0.1 resolution: "trim-lines@npm:3.0.1" @@ -12046,7 +13570,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.1": +"tslib@npm:^2.0.0, tslib@npm:^2.0.1": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 @@ -12152,10 +13676,10 @@ __metadata: languageName: node linkType: hard -"unicorn-magic@npm:^0.1.0": - version: 0.1.0 - resolution: "unicorn-magic@npm:0.1.0" - checksum: e4ed0de05b0a05e735c7d8a2930881e5efcfc3ec897204d5d33e7e6247f4c31eac92e383a15d9a6bccb7319b4271ee4bea946e211bf14951fec6ff2cbbb66a92 +"unicorn-magic@npm:^0.3.0": + version: 0.3.0 + resolution: "unicorn-magic@npm:0.3.0" + checksum: 0a32a997d6c15f1c2a077a15b1c4ca6f268d574cf5b8975e778bb98e6f8db4ef4e86dfcae4e158cd4c7e38fb4dd383b93b13eefddc7f178dea13d3ac8a603271 languageName: node linkType: hard @@ -12268,10 +13792,10 @@ __metadata: languageName: node linkType: hard -"universal-user-agent@npm:^6.0.0": - version: 6.0.1 - resolution: "universal-user-agent@npm:6.0.1" - checksum: 5c9c46ffe19a975e11e6443640ed4c9e0ce48fcc7203325757a8414ac49940ebb0f4667f2b1fa561489d1eb22cb2d05a0f7c82ec20c5cba42e58e188fb19b187 +"universal-user-agent@npm:^7.0.0, universal-user-agent@npm:^7.0.2": + version: 7.0.3 + resolution: "universal-user-agent@npm:7.0.3" + checksum: 6043be466a9bb96c0ce82392842d9fddf4c37e296f7bacc2cb25f47123990eb436c82df824644f9c5070a94dbdb117be17f66d54599ab143648ec57ef93dbcc8 languageName: node linkType: hard @@ -12282,7 +13806,7 @@ __metadata: languageName: node linkType: hard -"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": +"unpipe@npm:~1.0.0": version: 1.0.0 resolution: "unpipe@npm:1.0.0" checksum: 193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c @@ -12303,6 +13827,20 @@ __metadata: languageName: node linkType: hard +"update-browserslist-db@npm:^1.2.0": + version: 1.2.2 + resolution: "update-browserslist-db@npm:1.2.2" + dependencies: + escalade: "npm:^3.2.0" + picocolors: "npm:^1.1.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 39c3ea08b397ffc8dc3a1c517f5c6ed5cc4179b5e185383dab9bf745879623c12062a2e6bf4f9427cc59389c7bfa0010e86858b923c1e349e32fdddd9b043bb2 + languageName: node + linkType: hard + "update-notifier@npm:^6.0.2": version: 6.0.2 resolution: "update-notifier@npm:6.0.2" @@ -12351,21 +13889,21 @@ __metadata: languageName: node linkType: hard -"use-latest-callback@npm:^0.2.1": - version: 0.2.3 - resolution: "use-latest-callback@npm:0.2.3" +"use-latest-callback@npm:^0.2.4": + version: 0.2.6 + resolution: "use-latest-callback@npm:0.2.6" peerDependencies: react: ">=16.8" - checksum: dc87503f6279ce2980f78e1019231ba20d7509e9d17adac05285babe4d6ba6f68c52f4ef7b5ad777cbc2af9fbaaa09d7adb664ca556da0aebab9f020022880be + checksum: 6523747b2d76f12a91cf80a3cd9803449571e9defa8db69e9a03b8199b211127d88c038063714fe31d3c2e63ca51a491bd05f4e34203795a1c692a5a44416610 languageName: node linkType: hard -"use-sync-external-store@npm:^1.2.2": - version: 1.2.2 - resolution: "use-sync-external-store@npm:1.2.2" +"use-sync-external-store@npm:^1.4.0, use-sync-external-store@npm:^1.5.0": + version: 1.6.0 + resolution: "use-sync-external-store@npm:1.6.0" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 23b1597c10adf15b26ade9e8c318d8cc0abc9ec0ab5fc7ca7338da92e89c2536abd150a5891bf076836c352fdfa104fc7231fb48f806fd9960e0cbe03601abaf + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 35e1179f872a53227bdf8a827f7911da4c37c0f4091c29b76b1e32473d1670ebe7bcd880b808b7549ba9a5605c233350f800ffab963ee4a4ee346ee983b6019b languageName: node linkType: hard @@ -12499,57 +14037,59 @@ __metadata: languageName: node linkType: hard -"webpack-dev-middleware@npm:^5.3.4": - version: 5.3.4 - resolution: "webpack-dev-middleware@npm:5.3.4" +"webpack-dev-middleware@npm:^7.4.2": + version: 7.4.5 + resolution: "webpack-dev-middleware@npm:7.4.5" dependencies: colorette: "npm:^2.0.10" - memfs: "npm:^3.4.3" - mime-types: "npm:^2.1.31" + memfs: "npm:^4.43.1" + mime-types: "npm:^3.0.1" + on-finished: "npm:^2.4.1" range-parser: "npm:^1.2.1" schema-utils: "npm:^4.0.0" peerDependencies: - webpack: ^4.0.0 || ^5.0.0 - checksum: 257df7d6bc5494d1d3cb66bba70fbdf5a6e0423e39b6420f7631aeb52435afbfbff8410a62146dcdf3d2f945c62e03193aae2ac1194a2f7d5a2523b9d194e9e1 + webpack: ^5.0.0 + peerDependenciesMeta: + webpack: + optional: true + checksum: e72fa7de3b1589c0c518976358f946d9ec97699a3eb90bfd40718f4be3e9d5d13dc80f748c5c16662efbf1400cedbb523c79f56a778e6e8ffbdf1bd93be547eb languageName: node linkType: hard -"webpack-dev-server@npm:^4.15.2": - version: 4.15.2 - resolution: "webpack-dev-server@npm:4.15.2" - dependencies: - "@types/bonjour": "npm:^3.5.9" - "@types/connect-history-api-fallback": "npm:^1.3.5" - "@types/express": "npm:^4.17.13" - "@types/serve-index": "npm:^1.9.1" - "@types/serve-static": "npm:^1.13.10" - "@types/sockjs": "npm:^0.3.33" - "@types/ws": "npm:^8.5.5" +"webpack-dev-server@npm:^5.2.2": + version: 5.2.2 + resolution: "webpack-dev-server@npm:5.2.2" + dependencies: + "@types/bonjour": "npm:^3.5.13" + "@types/connect-history-api-fallback": "npm:^1.5.4" + "@types/express": "npm:^4.17.21" + "@types/express-serve-static-core": "npm:^4.17.21" + "@types/serve-index": "npm:^1.9.4" + "@types/serve-static": "npm:^1.15.5" + "@types/sockjs": "npm:^0.3.36" + "@types/ws": "npm:^8.5.10" ansi-html-community: "npm:^0.0.8" - bonjour-service: "npm:^1.0.11" - chokidar: "npm:^3.5.3" + bonjour-service: "npm:^1.2.1" + chokidar: "npm:^3.6.0" colorette: "npm:^2.0.10" compression: "npm:^1.7.4" connect-history-api-fallback: "npm:^2.0.0" - default-gateway: "npm:^6.0.3" - express: "npm:^4.17.3" + express: "npm:^4.21.2" graceful-fs: "npm:^4.2.6" - html-entities: "npm:^2.3.2" - http-proxy-middleware: "npm:^2.0.3" - ipaddr.js: "npm:^2.0.1" - launch-editor: "npm:^2.6.0" - open: "npm:^8.0.9" - p-retry: "npm:^4.5.0" - rimraf: "npm:^3.0.2" - schema-utils: "npm:^4.0.0" - selfsigned: "npm:^2.1.1" + http-proxy-middleware: "npm:^2.0.9" + ipaddr.js: "npm:^2.1.0" + launch-editor: "npm:^2.6.1" + open: "npm:^10.0.3" + p-retry: "npm:^6.2.0" + schema-utils: "npm:^4.2.0" + selfsigned: "npm:^2.4.1" serve-index: "npm:^1.9.1" sockjs: "npm:^0.3.24" spdy: "npm:^4.0.2" - webpack-dev-middleware: "npm:^5.3.4" - ws: "npm:^8.13.0" + webpack-dev-middleware: "npm:^7.4.2" + ws: "npm:^8.18.0" peerDependencies: - webpack: ^4.37.0 || ^5.0.0 + webpack: ^5.0.0 peerDependenciesMeta: webpack: optional: true @@ -12557,7 +14097,7 @@ __metadata: optional: true bin: webpack-dev-server: bin/webpack-dev-server.js - checksum: 625bd5b79360afcf98782c8b1fd710b180bb0e96d96b989defff550c546890010ceea82ffbecb2a0a23f7f018bc72f2dee7b3070f7b448fb0110df6657fb2904 + checksum: 58d7ddb054cdbba22ddfa3d6644194abf6197c14530e1e64ccd7f0b670787245eea966ee72e95abd551c54313627bde0d227a0d2a1e2557102b1a3504ac0b7f1 languageName: node linkType: hard @@ -12662,17 +14202,6 @@ __metadata: languageName: node linkType: hard -"which@npm:^1.3.1": - version: 1.3.1 - resolution: "which@npm:1.3.1" - dependencies: - isexe: "npm:^2.0.0" - bin: - which: ./bin/which - checksum: e945a8b6bbf6821aaaef7f6e0c309d4b615ef35699576d5489b4261da9539f70393c6b2ce700ee4321c18f914ebe5644bc4631b15466ffbaad37d83151f6af59 - languageName: node - linkType: hard - "which@npm:^2.0.1": version: 2.0.2 resolution: "which@npm:2.0.2" @@ -12733,13 +14262,6 @@ __metadata: languageName: node linkType: hard -"wrappy@npm:1": - version: 1.0.2 - resolution: "wrappy@npm:1.0.2" - checksum: 56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0 - languageName: node - linkType: hard - "write-file-atomic@npm:^3.0.3": version: 3.0.3 resolution: "write-file-atomic@npm:3.0.3" @@ -12767,9 +14289,9 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.13.0": - version: 8.14.2 - resolution: "ws@npm:8.14.2" +"ws@npm:^8.18.0": + version: 8.18.3 + resolution: "ws@npm:8.18.3" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -12778,7 +14300,16 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 35b4c2da048b8015c797fd14bcb5a5766216ce65c8a5965616a5440ca7b6c3681ee3cbd0ea0c184a59975556e9d58f2002abf8485a14d11d3371770811050a16 + checksum: eac918213de265ef7cb3d4ca348b891a51a520d839aa51cdb8ca93d4fa7ff9f6ccb339ccee89e4075324097f0a55157c89fa3f7147bde9d8d7e90335dc087b53 + languageName: node + linkType: hard + +"wsl-utils@npm:^0.1.0": + version: 0.1.0 + resolution: "wsl-utils@npm:0.1.0" + dependencies: + is-wsl: "npm:^3.1.0" + checksum: 44318f3585eb97be994fc21a20ddab2649feaf1fbe893f1f866d936eea3d5f8c743bec6dc02e49fbdd3c0e69e9b36f449d90a0b165a4f47dd089747af4cf2377 languageName: node linkType: hard @@ -12814,20 +14345,6 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^1.7.2": - version: 1.10.2 - resolution: "yaml@npm:1.10.2" - checksum: 5c28b9eb7adc46544f28d9a8d20c5b3cb1215a886609a2fd41f51628d8aaa5878ccd628b755dbcd29f6bb4921bd04ffbc6dcc370689bb96e594e2f9813d2605f - languageName: node - linkType: hard - -"yocto-queue@npm:^0.1.0": - version: 0.1.0 - resolution: "yocto-queue@npm:0.1.0" - checksum: dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f - languageName: node - linkType: hard - "yocto-queue@npm:^1.0.0": version: 1.0.0 resolution: "yocto-queue@npm:1.0.0" @@ -12835,6 +14352,13 @@ __metadata: languageName: node linkType: hard +"zod@npm:^4.1.8": + version: 4.1.13 + resolution: "zod@npm:4.1.13" + checksum: d7e74e82dba81a91ffc3239cd85bc034abe193a28f7087a94ab258a3e48e9a7ca4141920cac979a0d781495b48fc547777394149f26be04c3dc642f58bbc3941 + languageName: node + linkType: hard + "zwitch@npm:^2.0.0": version: 2.0.4 resolution: "zwitch@npm:2.0.4" From 677ded8bf97aaaf07b03a6b4674bde4c70e13920 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Sat, 6 Dec 2025 23:06:02 +0100 Subject: [PATCH 3/7] Tweak styling --- src/css/custom.css | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/css/custom.css b/src/css/custom.css index 643d78fbc7..9cd0266db2 100755 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -82,14 +82,18 @@ --ifm-toc-border-color: var(--ifm-color-gray-200); --ifm-menu-color-background-active: rgba(107, 82, 174, 0.1); - --docusaurus-highlighted-code-line-bg: rgba(107, 82, 174, 0.1); --ifm-footer-padding-horizontal: var(--ifm-spacing-horizontal); --ifm-footer-padding-vertical: var(--ifm-spacing-vertical); --ifm-tabs-padding-vertical: 0.375rem; --ifm-alert-shadow: none; - --ifm-alert-border-left-width: 0; + --ifm-alert-border-width: 1px; + --ifm-alert-border-left-width: 1px; + + --ifm-code-padding-horizontal: 0.3em; + + --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.07); --codeblock-background-color: #f6f8fa; } @@ -464,7 +468,11 @@ article p { } & > .tabs { - box-shadow: inset 0 -2px 0 var(--ifm-toc-border-color); + border-width: 1px 1px 0 1px; + border-style: solid; + border-color: var(--ifm-toc-border-color); + border-top-left-radius: var(--ifm-code-border-radius); + border-top-right-radius: var(--ifm-code-border-radius); } & > .tabs > .tabs__item { @@ -476,6 +484,11 @@ article p { } } +[role='tabpanel'] > .theme-code-block:only-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + .col:has(.table-of-contents) { padding-left: 0; } @@ -503,6 +516,10 @@ article p { transform: scale(0.8); } +.theme-code-block { + border: 1px solid var(--ifm-toc-border-color); +} + article { text-wrap: pretty; } @@ -620,9 +637,9 @@ samp { margin-bottom: var(--ifm-leading); padding: calc(var(--ifm-pre-padding) / 2) var(--ifm-pre-padding); background-color: var(--codeblock-background-color); - border-top-width: 1px; - border-top-style: solid; - border-top-color: var(--ifm-color-gray-200); + border-width: 0 1px 1px 1px; + border-style: solid; + border-color: var(--ifm-toc-border-color); border-bottom-left-radius: var(--ifm-pre-border-radius); border-bottom-right-radius: var(--ifm-pre-border-radius); } @@ -681,3 +698,7 @@ pre code:has(.code-block-diff-add-line) { pre code:has(.code-block-diff-remove-line) { padding-left: 2em !important; } + +code { + border-width: 1px; +} From a68f91a88b1efea88bcff47e4b598a4be9454879 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Sat, 6 Dec 2025 23:21:02 +0100 Subject: [PATCH 4/7] Add blur to the header --- src/css/custom.css | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/css/custom.css b/src/css/custom.css index 9cd0266db2..36ae35ff85 100755 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -264,6 +264,19 @@ article p { .navbar { padding: 0; + background-color: transparent; +} + +.navbar::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 100%; + backdrop-filter: blur(12px) saturate(180%); + background-color: rgba(255, 255, 255, 0.7); + z-index: -1; } .navbar__inner { From 121630b7ef52d31f6827449ab42fb64da012599a Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Sun, 7 Dec 2025 00:13:26 +0100 Subject: [PATCH 5/7] Tweak blog styling --- src/css/custom.css | 61 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/src/css/custom.css b/src/css/custom.css index 36ae35ff85..9305af354f 100755 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -229,17 +229,23 @@ article p { letter-spacing: -0.003em; } +.blog-wrapper .container { + max-width: 100%; + padding: 0 1.25rem; +} + @media (min-width: 90rem) { .main-wrapper:not(.full-width), .navbar__inner, - .footer .container { + .footer .container, + .blog-wrapper .container { max-width: 90rem; margin: auto; } } @media (min-width: 85rem) { - .main-wrapper:not(.full-width) main { + .main-wrapper:not(.full-width) main:not(.blog-list-page main) { padding: 2rem 0 2rem 2rem; } @@ -249,7 +255,7 @@ article p { } @media (min-width: 95rem) { - .main-wrapper:not(.full-width) main { + .main-wrapper:not(.full-width) main:not(.blog-list-page main) { padding: 3rem 0 3rem 4rem; } @@ -715,3 +721,52 @@ pre code:has(.code-block-diff-remove-line) { code { border-width: 1px; } + +.blog-wrapper .container aside nav { + margin-top: 2.25rem; +} + +.blog-wrapper article h2 { + font-size: 2rem; + line-height: 1.3; + letter-spacing: -0.02em; +} + +.blog-wrapper article h2 a { + color: var(--ifm-font-color-base); + text-decoration: none; +} + +.blog-wrapper article h2 a:hover { + color: var(--ifm-color-primary); +} + +.blog-wrapper .avatar__photo { + margin: calc(var(--ifm-leading) / 2) 0; +} + +.blog-wrapper .avatar__intro { + display: grid; + grid-template-areas: + 'name socials' + 'subtitle .'; + justify-content: start; + align-items: center; + gap: 0; + font-size: 0.875rem; + margin: calc(var(--ifm-leading) / 2) 0; +} + +.blog-wrapper .avatar__intro > .avatar__name { + font-weight: 500; + grid-area: name; +} + +.blog-wrapper .avatar__intro > .avatar__name + small { + grid-area: subtitle; +} + +.blog-wrapper .avatar__intro > .avatar__name + small + div { + grid-area: socials; + margin-left: var(--ifm-leading); +} From 6afae5f09ee92a1da76e8b0bcaad7f23d052a6b0 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Thu, 4 Dec 2025 14:24:22 +0100 Subject: [PATCH 6/7] Cut 8.x docs --- versioned_docs/version-8.x/auth-flow.md | 1002 +++++++++ .../version-8.x/bottom-tab-navigator.md | 1198 +++++++++++ .../combine-static-with-dynamic.md | 215 ++ .../version-8.x/community-libraries.md | 31 + .../version-8.x/community-navigators.md | 31 + .../version-8.x/community-solutions.md | 35 + .../version-8.x/configuring-links.md | 1391 ++++++++++++ versioned_docs/version-8.x/contributing.md | 159 ++ .../custom-android-back-button-handling.md | 254 +++ .../version-8.x/custom-navigators.md | 449 ++++ versioned_docs/version-8.x/custom-routers.md | 224 ++ .../version-8.x/customizing-bottom-tabs.md | 290 +++ versioned_docs/version-8.x/deep-linking.md | 517 +++++ versioned_docs/version-8.x/devtools.md | 141 ++ versioned_docs/version-8.x/drawer-actions.md | 250 +++ .../version-8.x/drawer-based-navigation.md | 627 ++++++ versioned_docs/version-8.x/drawer-layout.md | 248 +++ .../version-8.x/drawer-navigator.md | 883 ++++++++ versioned_docs/version-8.x/elements.md | 847 ++++++++ .../function-after-focusing-screen.md | 332 +++ versioned_docs/version-8.x/getting-started.md | 189 ++ .../version-8.x/glossary-of-terms.md | 106 + versioned_docs/version-8.x/group.md | 335 +++ .../version-8.x/handling-safe-area.md | 478 ++++ versioned_docs/version-8.x/header-buttons.md | 302 +++ versioned_docs/version-8.x/headers.md | 694 ++++++ .../version-8.x/hello-react-navigation.md | 555 +++++ .../version-8.x/hiding-tabbar-in-screens.md | 197 ++ versioned_docs/version-8.x/limitations.md | 17 + versioned_docs/version-8.x/link.md | 29 + .../version-8.x/material-top-tab-navigator.md | 650 ++++++ .../version-8.x/migration-guides.md | 14 + versioned_docs/version-8.x/modal.md | 127 ++ versioned_docs/version-8.x/more-resources.md | 15 + .../version-8.x/multiple-drawers.md | 560 +++++ .../native-bottom-tab-navigator.md | 445 ++++ .../version-8.x/native-stack-navigator.md | 1671 ++++++++++++++ .../navigating-without-navigation-prop.md | 359 ++++ versioned_docs/version-8.x/navigating.md | 365 ++++ .../version-8.x/navigation-actions.md | 891 ++++++++ .../version-8.x/navigation-container.md | 1122 ++++++++++ .../version-8.x/navigation-context.md | 77 + .../version-8.x/navigation-events.md | 473 ++++ .../version-8.x/navigation-lifecycle.md | 632 ++++++ .../version-8.x/navigation-object.md | 1913 +++++++++++++++++ .../version-8.x/navigation-state.md | 127 ++ versioned_docs/version-8.x/navigator.md | 463 ++++ .../version-8.x/nesting-navigators.md | 1124 ++++++++++ versioned_docs/version-8.x/next-steps.md | 24 + versioned_docs/version-8.x/params.md | 448 ++++ versioned_docs/version-8.x/pitch.md | 23 + .../version-8.x/preventing-going-back.md | 96 + versioned_docs/version-8.x/route-object.md | 85 + .../version-8.x/screen-options-resolution.md | 763 +++++++ versioned_docs/version-8.x/screen-options.md | 349 +++ versioned_docs/version-8.x/screen-tracking.md | 189 ++ versioned_docs/version-8.x/screen.md | 439 ++++ .../version-8.x/server-container.md | 76 + .../version-8.x/server-rendering.md | 256 +++ .../version-8.x/shared-element-transitions.md | 229 ++ versioned_docs/version-8.x/stack-actions.md | 469 ++++ versioned_docs/version-8.x/stack-navigator.md | 1350 ++++++++++++ .../version-8.x/state-persistence.md | 314 +++ .../version-8.x/static-configuration.md | 260 +++ versioned_docs/version-8.x/status-bar.md | 453 ++++ versioned_docs/version-8.x/tab-actions.md | 75 + versioned_docs/version-8.x/tab-view.md | 668 ++++++ versioned_docs/version-8.x/testing.md | 957 +++++++++ versioned_docs/version-8.x/themes.md | 808 +++++++ versioned_docs/version-8.x/troubleshooting.md | 491 +++++ versioned_docs/version-8.x/typescript.md | 560 +++++ .../version-8.x/upgrading-from-6.x.md | 1023 +++++++++ .../version-8.x/use-focus-effect.md | 189 ++ versioned_docs/version-8.x/use-is-focused.md | 71 + .../version-8.x/use-link-builder.md | 64 + versioned_docs/version-8.x/use-link-props.md | 110 + versioned_docs/version-8.x/use-link-to.md | 51 + .../version-8.x/use-navigation-state.md | 161 ++ versioned_docs/version-8.x/use-navigation.md | 100 + .../version-8.x/use-prevent-remove.md | 158 ++ versioned_docs/version-8.x/use-route-path.md | 25 + versioned_docs/version-8.x/use-route.md | 101 + .../version-8.x/use-scroll-to-top.md | 169 ++ versioned_docs/version-8.x/use-theme.md | 140 ++ versioned_docs/version-8.x/used-by.md | 55 + versioned_docs/version-8.x/web-support.md | 159 ++ versioned_sidebars/version-8.x-sidebars.json | 111 + versions.json | 2 +- 88 files changed, 35124 insertions(+), 1 deletion(-) create mode 100755 versioned_docs/version-8.x/auth-flow.md create mode 100755 versioned_docs/version-8.x/bottom-tab-navigator.md create mode 100644 versioned_docs/version-8.x/combine-static-with-dynamic.md create mode 100755 versioned_docs/version-8.x/community-libraries.md create mode 100755 versioned_docs/version-8.x/community-navigators.md create mode 100755 versioned_docs/version-8.x/community-solutions.md create mode 100644 versioned_docs/version-8.x/configuring-links.md create mode 100755 versioned_docs/version-8.x/contributing.md create mode 100755 versioned_docs/version-8.x/custom-android-back-button-handling.md create mode 100755 versioned_docs/version-8.x/custom-navigators.md create mode 100755 versioned_docs/version-8.x/custom-routers.md create mode 100755 versioned_docs/version-8.x/customizing-bottom-tabs.md create mode 100755 versioned_docs/version-8.x/deep-linking.md create mode 100644 versioned_docs/version-8.x/devtools.md create mode 100755 versioned_docs/version-8.x/drawer-actions.md create mode 100755 versioned_docs/version-8.x/drawer-based-navigation.md create mode 100644 versioned_docs/version-8.x/drawer-layout.md create mode 100644 versioned_docs/version-8.x/drawer-navigator.md create mode 100644 versioned_docs/version-8.x/elements.md create mode 100755 versioned_docs/version-8.x/function-after-focusing-screen.md create mode 100755 versioned_docs/version-8.x/getting-started.md create mode 100755 versioned_docs/version-8.x/glossary-of-terms.md create mode 100644 versioned_docs/version-8.x/group.md create mode 100755 versioned_docs/version-8.x/handling-safe-area.md create mode 100755 versioned_docs/version-8.x/header-buttons.md create mode 100755 versioned_docs/version-8.x/headers.md create mode 100755 versioned_docs/version-8.x/hello-react-navigation.md create mode 100644 versioned_docs/version-8.x/hiding-tabbar-in-screens.md create mode 100755 versioned_docs/version-8.x/limitations.md create mode 100644 versioned_docs/version-8.x/link.md create mode 100755 versioned_docs/version-8.x/material-top-tab-navigator.md create mode 100644 versioned_docs/version-8.x/migration-guides.md create mode 100755 versioned_docs/version-8.x/modal.md create mode 100755 versioned_docs/version-8.x/more-resources.md create mode 100644 versioned_docs/version-8.x/multiple-drawers.md create mode 100755 versioned_docs/version-8.x/native-bottom-tab-navigator.md create mode 100755 versioned_docs/version-8.x/native-stack-navigator.md create mode 100755 versioned_docs/version-8.x/navigating-without-navigation-prop.md create mode 100755 versioned_docs/version-8.x/navigating.md create mode 100755 versioned_docs/version-8.x/navigation-actions.md create mode 100644 versioned_docs/version-8.x/navigation-container.md create mode 100755 versioned_docs/version-8.x/navigation-context.md create mode 100644 versioned_docs/version-8.x/navigation-events.md create mode 100755 versioned_docs/version-8.x/navigation-lifecycle.md create mode 100755 versioned_docs/version-8.x/navigation-object.md create mode 100644 versioned_docs/version-8.x/navigation-state.md create mode 100644 versioned_docs/version-8.x/navigator.md create mode 100755 versioned_docs/version-8.x/nesting-navigators.md create mode 100755 versioned_docs/version-8.x/next-steps.md create mode 100755 versioned_docs/version-8.x/params.md create mode 100755 versioned_docs/version-8.x/pitch.md create mode 100644 versioned_docs/version-8.x/preventing-going-back.md create mode 100755 versioned_docs/version-8.x/route-object.md create mode 100755 versioned_docs/version-8.x/screen-options-resolution.md create mode 100644 versioned_docs/version-8.x/screen-options.md create mode 100644 versioned_docs/version-8.x/screen-tracking.md create mode 100644 versioned_docs/version-8.x/screen.md create mode 100644 versioned_docs/version-8.x/server-container.md create mode 100644 versioned_docs/version-8.x/server-rendering.md create mode 100644 versioned_docs/version-8.x/shared-element-transitions.md create mode 100755 versioned_docs/version-8.x/stack-actions.md create mode 100755 versioned_docs/version-8.x/stack-navigator.md create mode 100755 versioned_docs/version-8.x/state-persistence.md create mode 100644 versioned_docs/version-8.x/static-configuration.md create mode 100755 versioned_docs/version-8.x/status-bar.md create mode 100755 versioned_docs/version-8.x/tab-actions.md create mode 100644 versioned_docs/version-8.x/tab-view.md create mode 100644 versioned_docs/version-8.x/testing.md create mode 100755 versioned_docs/version-8.x/themes.md create mode 100755 versioned_docs/version-8.x/troubleshooting.md create mode 100755 versioned_docs/version-8.x/typescript.md create mode 100755 versioned_docs/version-8.x/upgrading-from-6.x.md create mode 100755 versioned_docs/version-8.x/use-focus-effect.md create mode 100755 versioned_docs/version-8.x/use-is-focused.md create mode 100644 versioned_docs/version-8.x/use-link-builder.md create mode 100644 versioned_docs/version-8.x/use-link-props.md create mode 100644 versioned_docs/version-8.x/use-link-to.md create mode 100755 versioned_docs/version-8.x/use-navigation-state.md create mode 100755 versioned_docs/version-8.x/use-navigation.md create mode 100644 versioned_docs/version-8.x/use-prevent-remove.md create mode 100644 versioned_docs/version-8.x/use-route-path.md create mode 100755 versioned_docs/version-8.x/use-route.md create mode 100755 versioned_docs/version-8.x/use-scroll-to-top.md create mode 100644 versioned_docs/version-8.x/use-theme.md create mode 100644 versioned_docs/version-8.x/used-by.md create mode 100755 versioned_docs/version-8.x/web-support.md create mode 100644 versioned_sidebars/version-8.x-sidebars.json diff --git a/versioned_docs/version-8.x/auth-flow.md b/versioned_docs/version-8.x/auth-flow.md new file mode 100755 index 0000000000..c3d4e71772 --- /dev/null +++ b/versioned_docs/version-8.x/auth-flow.md @@ -0,0 +1,1002 @@ +--- +id: auth-flow +title: Authentication flows +sidebar_label: Authentication flows +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Most apps require that a user authenticates in some way to have access to data associated with a user or other private content. Typically the flow will look like this: + +- The user opens the app. +- The app loads some authentication state from encrypted persistent storage (for example, [`SecureStore`](https://docs.expo.io/versions/latest/sdk/securestore/)). +- When the state has loaded, the user is presented with either authentication screens or the main app, depending on whether valid authentication state was loaded. +- When the user signs out, we clear the authentication state and send them back to authentication screens. + +:::note + +We say "authentication screens" because usually there is more than one. You may have a main screen with a username and password field, another for "forgot password", and another set for sign up. + +::: + +## What we need + +We want the following behavior from our authentication flow: + +- When the user is signed in, we want to show the main app screens and not the authentication-related screens. +- When the user is signed out, we want to show the authentication screens and not the main app screens. +- After the user goes through the authentication flow and signs in, we want to unmount all of the screens related to authentication, and when we press the hardware back button, we expect to not be able to go back to the authentication flow. + +## How it will work + +We can configure different screens to be available based on some condition. For example, if the user is signed in, we want `Home` to be available. If the user is not signed in, we want `SignIn` to be available. + + + + +```js name="Authentication flow" snack +import * as React from 'react'; +import { View } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +const useIsSignedIn = () => { + return true; +}; + +const useIsSignedOut = () => { + return !useIsSignedIn(); +}; + +// codeblock-focus-start +const RootStack = createNativeStackNavigator({ + screens: { + Home: { + if: useIsSignedIn, + screen: HomeScreen, + }, + SignIn: { + if: useIsSignedOut, + screen: SignInScreen, + }, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} + +function HomeScreen() { + return ; +} + +function SignInScreen() { + return ; +} +``` + +Here, for each screen, we have defined a condition using the `if` property which takes a hook. The hook returns a boolean value indicating whether the user is signed in or not. If the hook returns `true`, the screen will be available, otherwise it won't. + +This means: + +- When `useIsSignedIn` returns `true`, React Navigation will only use the `Home` screen, since it's the only screen matching the condition. +- Similarly, when `useIsSignedOut` returns `true`, React Navigation will use the `SignIn` screen. + +This makes it impossible to navigate to the `Home` when the user is not signed in, and to `SignIn` when the user is signed in. + +When the values returned by `useIsSignedin` and `useIsSignedOut` change, the screens matching the condition will change: + +- Let's say, initially `useIsSignedOut` returns `true`. This means that `SignIn` screens is shown. +- After the user signs in, the return value of `useIsSignedIn` will change to `true` and `useIsSignedOut` will change to `false`, which means: + - React Navigation will see that the `SignIn` screen is no longer matches the condition, so it will remove the screen. + - Then it'll show the `Home` screen automatically because that's the first screen available when `useIsSignedIn` returns `true`. + +The order of the screens matters when there are multiple screens matching the condition. For example, if there are two screens matching `useIsSignedIn`, the first screen will be shown when the condition is `true`. + +## Define the hooks + +To implement the `useIsSignedIn` and `useIsSignedOut` hooks, we can start by creating a context to store the authentication state. Let's call it `SignInContext`: + +```js +import * as React from 'react'; + +const SignInContext = React.createContext(); +``` + +Then we can implement the `useIsSignedIn` and `useIsSignedOut` hooks as follows: + +```js +function useIsSignedIn() { + const isSignedIn = React.useContext(SignInContext); + return isSignedIn; +} + +function useIsSignedOut() { + return !useIsSignedIn(); +} +``` + +We'll discuss how to provide the context value later. + + + + + +```js name="Authentication flow" snack +import * as React from 'react'; +import { View } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +const Stack = createNativeStackNavigator(); + +export default function App() { + const isSignedIn = true; + + return ( + + + // codeblock-focus-start + {isSignedIn ? ( + + ) : ( + + )} + // codeblock-focus-end + + + ); +} + +function HomeScreen() { + return ; +} + +function SignInScreen() { + return ; +} +``` + +Here, we have conditionally defined the screens based on the value of `isSignedIn`. + +This means: + +- When `isSignedIn` is `true`, React Navigation will only see the `Home` screen, since it's the only screen defined based on the condition. +- Similarly, when `isSignedIn` is `false`, React Navigation will only see the `SignIn` screen. + +This makes it impossible to navigate to the `Home` when the user is not signed in, and to `SignIn` when the user is signed in. + +When the value of `isSignedin` changes, the screens defined based on the condition will change: + +- Let's say, initially `isSignedin` is `false`. This means that `SignIn` screens is shown. +- After the user signs in, the value of `isSignedin` will change to `true`, which means: + - React Navigation will see that the `SignIn` screen is no longer defined, so it will remove the screen. + - Then it'll show the `Home` screen automatically because that's the first screen defined when `isSignedin` returns `true`. + +The order of the screens matters when there are multiple screens matching the condition. For example, if there are two screens defined based on `isSignedin`, the first screen will be shown when the condition is `true`. + + + + +## Add more screens + +For our case, let's say we have 3 screens: + +- `SplashScreen` - This will show a splash or loading screen when we're restoring the token. +- `SignIn` - This is the screen we show if the user isn't signed in already (we couldn't find a token). +- `Home` - This is the screen we show if the user is already signed in. + +So our navigator will look like: + + + + +```js +const RootStack = createNativeStackNavigator({ + screens: { + Home: { + if: useIsSignedIn, + screen: HomeScreen, + }, + SignIn: { + if: useIsSignedOut, + screen: SignInScreen, + options: { + title: 'Sign in', + }, + }, + }, +}); + +const Navigation = createStaticNavigation(RootStack); +``` + + + + +```js +const Stack = createNativeStackNavigator(); + +export default function App() { + const isSignedIn = true; + + return ( + + + {isSignedIn ? ( + + ) : ( + + )} + + + ); +} +``` + + + + +Notice how we have only defined the `Home` and `SignIn` screens here, and not the `SplashScreen`. The `SplashScreen` should be rendered before we render any navigators so that we don't render incorrect screens before we know whether the user is signed in or not. + +When we use this in our component, it'd look something like this: + + + + +```js +if (isLoading) { + // We haven't finished checking for the token yet + return ; +} + +const isSignedIn = userToken != null; + +return ( + + + +); +``` + + + + +```js +if (isLoading) { + // We haven't finished checking for the token yet + return ; +} + +const isSignedIn = userToken != null; + +return ( + + + {isSignedIn ? ( + + ) : ( + + )} + + +); +``` + + + + +In the above snippet, `isLoading` means that we're still checking if we have a token. This can usually be done by checking if we have a token in `SecureStore` and validating the token. + +Next, we're exposing the sign in status via the `SignInContext` so that it's available to the `useIsSignedIn` and `useIsSignedOut` hooks. + +In the above example, we have one screen for each case. But you could also define multiple screens. For example, you probably want to define password reset, signup, etc screens as well when the user isn't signed in. Similarly for the screens accessible after sign in, you probably have more than one screen. + + + + +We can use [`groups`](static-configuration.md#groups) to define multiple screens: + +```js +const RootStack = createNativeStackNavigator({ + screens: { + // Common screens + }, + groups: { + SignedIn: { + if: useIsSignedIn, + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, + }, + SignedOut: { + if: useIsSignedOut, + screens: { + SignIn: SignInScreen, + SignUp: SignUpScreen, + ResetPassword: ResetPasswordScreen, + }, + }, + }, +}); +``` + + + + +We can use [`React.Fragment`](https://react.dev/reference/react/Fragment) or [`Group`](group.md) to define multiple screens: + +```js +isSignedIn ? ( + <> + + + + +) : ( + <> + + + +); +``` + +:::tip + +Instead of having your login-related screens and rest of the screens in two different Stack navigators and render them conditionally, we recommend to use a single Stack navigator and place the conditional inside. This makes it possible to have a proper transition animation during login/logout. + +::: + + + + +## Implement the logic for restoring the token + +:::note + +The following is just an example of how you might implement the logic for authentication in your app. You don't need to follow it as is. + +::: + +From the previous snippet, we can see that we need 3 state variables: + +- `isLoading` - We set this to `true` when we're trying to check if we already have a token saved in `SecureStore`. +- `isSignout` - We set this to `true` when user is signing out, otherwise set it to `false`. This can be used to customize the animation when signing out. +- `userToken` - The token for the user. If it's non-null, we assume the user is logged in, otherwise not. + +So we need to: + +- Add some logic for restoring token, signing in and signing out +- Expose methods for signing in and signing out to other components + +We'll use `React.useReducer` and `React.useContext` in this guide. But if you're using a state management library such as Redux or Mobx, you can use them for this functionality instead. In fact, in bigger apps, a global state management library is more suitable for storing authentication tokens. You can adapt the same approach to your state management library. + +First we'll need to create a context for auth where we can expose the necessary methods: + +```js +import * as React from 'react'; + +const AuthContext = React.createContext(); +``` + +In our component, we will: + +- Store the token and loading state in `useReducer` +- Persist it to `SecureStore` and read it from there on app launch +- Expose the methods for sign in and sign out to child components using `AuthContext` + +So our component will look like this: + + + +```js name="Signing in and signing out with AuthContext" snack dependencies=expo-secure-store +// codeblock-focus-start +import * as React from 'react'; +import * as SecureStore from 'expo-secure-store'; + +// codeblock-focus-end +import { Text, TextInput, View } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +const AuthContext = React.createContext(); + +const SignInContext = React.createContext(); + +function useIsSignedIn() { + const isSignedIn = React.useContext(SignInContext); + return isSignedIn; +} + +function useIsSignedOut() { + return !useIsSignedIn(); +} + +function SplashScreen() { + return ( + + Loading... + + ); +} + +function HomeScreen() { + const { signOut } = React.useContext(AuthContext); + + return ( + + Signed in! + + + ); +} + +function SignInScreen() { + const [username, setUsername] = React.useState(''); + const [password, setPassword] = React.useState(''); + + const { signIn } = React.useContext(AuthContext); + + return ( + + + + + + ); +} + +// codeblock-focus-start +export default function App() { + const [state, dispatch] = React.useReducer( + (prevState, action) => { + switch (action.type) { + case 'RESTORE_TOKEN': + return { + ...prevState, + userToken: action.token, + isLoading: false, + }; + case 'SIGN_IN': + return { + ...prevState, + isSignout: false, + userToken: action.token, + }; + case 'SIGN_OUT': + return { + ...prevState, + isSignout: true, + userToken: null, + }; + } + }, + { + isLoading: true, + isSignout: false, + userToken: null, + } + ); + + React.useEffect(() => { + // Fetch the token from storage then navigate to our appropriate place + const bootstrapAsync = async () => { + let userToken; + + try { + // Restore token stored in `SecureStore` or any other encrypted storage + userToken = await SecureStore.getItemAsync('userToken'); + } catch (e) { + // Restoring token failed + } + + // After restoring token, we may need to validate it in production apps + + // This will switch to the App screen or Auth screen and this loading + // screen will be unmounted and thrown away. + dispatch({ type: 'RESTORE_TOKEN', token: userToken }); + }; + + bootstrapAsync(); + }, []); + + const authContext = React.useMemo( + () => ({ + signIn: async (data) => { + // In a production app, we need to send some data (usually username, password) to server and get a token + // We will also need to handle errors if sign in failed + // After getting token, we need to persist the token using `SecureStore` or any other encrypted storage + // In the example, we'll use a dummy token + + dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' }); + }, + signOut: () => dispatch({ type: 'SIGN_OUT' }), + signUp: async (data) => { + // In a production app, we need to send user data to server and get a token + // We will also need to handle errors if sign up failed + // After getting token, we need to persist the token using `SecureStore` or any other encrypted storage + // In the example, we'll use a dummy token + + dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' }); + }, + }), + [] + ); + + if (state.isLoading) { + // We haven't finished checking for the token yet + return ; + } + + const isSignedIn = state.userToken != null; + + return ( + + + + + + ); +} + +const RootStack = createNativeStackNavigator({ + screens: { + Home: { + if: useIsSignedIn, + screen: HomeScreen, + }, + SignIn: { + if: useIsSignedOut, + screen: SignInScreen, + options: { + title: 'Sign in', + }, + }, + }, +}); + +const Navigation = createStaticNavigation(RootStack); +// codeblock-focus-end +``` + + + + +```js name="Signing in and signing out with AuthContext" snack dependencies=expo-secure-store +// codeblock-focus-start +import * as React from 'react'; +import * as SecureStore from 'expo-secure-store'; + +// codeblock-focus-end +import { Text, TextInput, View } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +const AuthContext = React.createContext(); + +function SplashScreen() { + return ( + + Loading... + + ); +} + +function HomeScreen() { + const { signOut } = React.useContext(AuthContext); + + return ( + + Signed in! + + + ); +} + +function SignInScreen() { + const [username, setUsername] = React.useState(''); + const [password, setPassword] = React.useState(''); + + const { signIn } = React.useContext(AuthContext); + + return ( + + + + + + ); +} + +const Stack = createNativeStackNavigator(); + +// codeblock-focus-start +export default function App() { + const [state, dispatch] = React.useReducer( + (prevState, action) => { + switch (action.type) { + case 'RESTORE_TOKEN': + return { + ...prevState, + userToken: action.token, + isLoading: false, + }; + case 'SIGN_IN': + return { + ...prevState, + isSignout: false, + userToken: action.token, + }; + case 'SIGN_OUT': + return { + ...prevState, + isSignout: true, + userToken: null, + }; + } + }, + { + isLoading: true, + isSignout: false, + userToken: null, + } + ); + + React.useEffect(() => { + // Fetch the token from storage then navigate to our appropriate place + const bootstrapAsync = async () => { + let userToken; + + try { + // Restore token stored in `SecureStore` or any other encrypted storage + userToken = await SecureStore.getItemAsync('userToken'); + } catch (e) { + // Restoring token failed + } + + // After restoring token, we may need to validate it in production apps + + // This will switch to the App screen or Auth screen and this loading + // screen will be unmounted and thrown away. + dispatch({ type: 'RESTORE_TOKEN', token: userToken }); + }; + + bootstrapAsync(); + }, []); + + const authContext = React.useMemo( + () => ({ + signIn: async (data) => { + // In a production app, we need to send some data (usually username, password) to server and get a token + // We will also need to handle errors if sign in failed + // After getting token, we need to persist the token using `SecureStore` or any other encrypted storage + // In the example, we'll use a dummy token + + dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' }); + }, + signOut: () => dispatch({ type: 'SIGN_OUT' }), + signUp: async (data) => { + // In a production app, we need to send user data to server and get a token + // We will also need to handle errors if sign up failed + // After getting token, we need to persist the token using `SecureStore` or any other encrypted storage + // In the example, we'll use a dummy token + + dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' }); + }, + }), + [] + ); + + return ( + + + + {state.isLoading ? ( + // We haven't finished checking for the token yet + + ) : state.userToken == null ? ( + // No token found, user isn't signed in + + ) : ( + // User is signed in + + )} + + + + ); +} +// codeblock-focus-end +``` + + + + +## Fill in other components + +We won't talk about how to implement the text inputs and buttons for the authentication screen, that is outside of the scope of navigation. We'll just fill in some placeholder content. + +```js +function SignInScreen() { + const [username, setUsername] = React.useState(''); + const [password, setPassword] = React.useState(''); + + const { signIn } = React.useContext(AuthContext); + + return ( + + + + + + ); +} +``` + +You can similarly fill in the other screens according to your requirements. + +## Removing shared screens when auth state changes + +Consider the following example: + + + + +```js +const RootStack = createNativeStackNavigator({ + groups: { + LoggedIn: { + if: useIsSignedIn, + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, + }, + LoggedOut: { + if: useIsSignedOut, + screens: { + SignIn: SignInScreen, + SignUp: SignUpScreen, + }, + }, + }, + screens: { + Help: HelpScreen, + }, +}); +``` + + + + +```js +isSignedIn ? ( + <> + + + + +) : ( + <> + + + + +); +``` + + + + +Here we have specific screens such as `SignIn`, `Home` etc. which are only shown depending on the sign in state. But we also have the `Help` screen which can be shown regardless of the login status. This also means that if the sign in state changes when the user is in the `Help` screen, they'll stay on the `Help` screen. + +This can be a problem, we probably want the user to be taken to the `SignIn` screen or `Home` screen instead of keeping them on the `Help` screen. + + + + +To make this work, we can move the `Help` screen to both of the groups instead of keeping it outside. This will ensure that the [`navigationKey`](screen.md#navigation-key) (the name of the group) for the screen changes when the sign in state changes. + +So our updated code will look like the following: + +```js +const RootStack = createNativeStackNavigator({ + groups: { + LoggedIn: { + if: useIsSignedIn, + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + Help: HelpScreen, + }, + }, + LoggedOut: { + if: useIsSignedOut, + screens: { + SignIn: SignInScreen, + SignUp: SignUpScreen, + Help: HelpScreen, + }, + }, + }, +}); +``` + + + + +To make this work, we can use [`navigationKey`](screen.md#navigation-key). When the `navigationKey` changes, React Navigation will remove all the screen. + +So our updated code will look like the following: + +```js +<> + {isSignedIn ? ( + <> + + + + ) : ( + <> + + + + )} + + +``` + +If you have a bunch of shared screens, you can also use [`navigationKey` with a `Group`](group.md#navigation-key) to remove all of the screens in the group. For example: + +```js +<> + {isSignedIn ? ( + <> + + + + ) : ( + <> + + + + )} + + + + + +``` + + + + +The examples above show stack navigator, but you can use the same approach with any navigator. + +By specifying a condition for our screens, we can implement auth flow in a simple way that doesn't require additional logic to make sure that the correct screen is shown. + +## Don't manually navigate when conditionally rendering screens + +It's important to note that when using such a setup, you **don't manually navigate** to the `Home` screen by calling `navigation.navigate('Home')` or any other method. **React Navigation will automatically navigate to the correct screen** when `isSignedIn` changes - `Home` screen when `isSignedIn` becomes `true`, and to `SignIn` screen when `isSignedIn` becomes `false`. You'll get an error if you attempt to navigate manually. + +## Handling deep links after auth + +When using deep links, you may want to handle the case where the user opens a deep link that requires authentication. + +Example scenario: + +- User opens a deep link to `myapp://profile` but is not signed in. +- The app shows the `SignIn` screen. +- After the user signs in, you want to navigate them to the `Profile` screen. + +To achieve this, you can set [`UNSTABLE_routeNamesChangeBehavior`](navigator.md#route-names-change-behavior) to `"lastUnhandled"`: + +:::warning + +This API is experimental and may change in a minor release. + +::: + + + + +```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 `UNSTABLE_routeNamesChangeBehavior` option allows you to control how React Navigation handles navigation when the available screens change because of conditions such as authentication state. When `lastUnhandled` is specified, React Navigation will remember the last screen that couldn't be handled, and after the condition changes, it'll automatically navigate to that screen if it's now available. diff --git a/versioned_docs/version-8.x/bottom-tab-navigator.md b/versioned_docs/version-8.x/bottom-tab-navigator.md new file mode 100755 index 0000000000..cfaf478245 --- /dev/null +++ b/versioned_docs/version-8.x/bottom-tab-navigator.md @@ -0,0 +1,1198 @@ +--- +id: bottom-tab-navigator +title: Bottom Tabs Navigator +sidebar_label: Bottom Tabs +--- + +A simple tab bar on the bottom of the screen that lets you switch between different routes. Routes are lazily initialized -- their screen components are not mounted until they are first focused. + + + +## 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 +``` + +## Usage + +To use this navigator, import it from `@react-navigation/bottom-tabs`: + +```js name="Bottom Tab Navigator" 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'; +// codeblock-focus-start +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; + +// codeblock-focus-end +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function ProfileScreen() { + const navigation = useNavigation(); + + return ( + + Profile Screen + + + ); +} + +// codeblock-focus-start +const MyTabs = createBottomTabNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyTabs); + +export default function App() { + return ; +} +``` + +## 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 + +#### `detachInactiveScreens` + +Boolean used to indicate whether inactive screens should be detached from the view hierarchy to save memory. This enables integration with [react-native-screens](https://github.com/software-mansion/react-native-screens). Defaults to `true`. + +#### `tabBar` + +Function that returns a React element to display as the tab bar. + +The function receives an object containing the following properties as the argument: + +- `state` - The state object for the tab navigator. +- `descriptors` - The descriptors object containing options for the tab navigator. +- `navigation` - The navigation object for the tab navigator. + +The `state.routes` array contains all the routes defined in the navigator. Each route's options can be accessed using `descriptors[route.key].options`. + +Example: + +```js name="Custom tab bar" snack tabs=config +import * as React from 'react'; +import { + createStaticNavigation, + NavigationContainer, +} from '@react-navigation/native'; +// codeblock-focus-start +import { View, Platform } from 'react-native'; +import { useLinkBuilder, useTheme } from '@react-navigation/native'; +import { Text, PlatformPressable } from '@react-navigation/elements'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; + +function MyTabBar({ state, descriptors, navigation }) { + const { colors } = useTheme(); + const { buildHref } = useLinkBuilder(); + + return ( + + {state.routes.map((route, index) => { + const { options } = descriptors[route.key]; + const label = + options.tabBarLabel !== undefined + ? options.tabBarLabel + : options.title !== undefined + ? options.title + : route.name; + + const isFocused = state.index === index; + + const onPress = () => { + const event = navigation.emit({ + type: 'tabPress', + target: route.key, + canPreventDefault: true, + }); + + if (!isFocused && !event.defaultPrevented) { + navigation.navigate(route.name, route.params); + } + }; + + const onLongPress = () => { + navigation.emit({ + type: 'tabLongPress', + target: route.key, + }); + }; + + return ( + + + {label} + + + ); + })} + + ); +} + +// codeblock-focus-end + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +function ProfileScreen() { + return ( + + Profile Screen + + ); +} + +// codeblock-tabs=static +// codeblock-focus-start +const MyTabs = createBottomTabNavigator({ + // highlight-next-line + tabBar: (props) => , + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyTabs); + +export default function App() { + return ; +} +// codeblock-tabs-end + +// codeblock-tabs=dynamic +const Tab = createBottomTabNavigator(); + +// codeblock-focus-start +function MyTabs() { + return ( + } + > + + + + ); +} +// codeblock-focus-end + +export default function App() { + return ( + + + + ); +} +// codeblock-tabs-end +``` + +This example will render a basic tab bar with labels. + +Note that you **cannot** use the `useNavigation` hook inside the `tabBar` since `useNavigation` is only available inside screens. You get a `navigation` prop for your `tabBar` which you can use instead: + +```js +function MyTabBar({ navigation }) { + return ( + + ); +} +``` + +### 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`. + +#### `tabBarLabel` + +Title string of a tab displayed in the tab bar or a function that given `{ focused: boolean, color: string }` returns a React.Node, to display in tab bar. When undefined, scene `title` is used. To hide, see `tabBarShowLabel`. + +#### `tabBarShowLabel` + +Whether the tab label should be visible. Defaults to `true`. + +#### `tabBarLabelPosition` + +Whether the label is shown below the icon or beside the icon. + +By default, the position is chosen automatically based on device width. + +- `below-icon`: the label is shown below the icon (typical for iPhones) + Tab bar label position - below + +- `beside-icon` the label is shown next to the icon (typical for iPad) + Tab bar label position - beside + +#### `tabBarLabelStyle` + +Style object for the tab label. +Tab bar label style + +Example: + +```js + tabBarLabelStyle: { + fontSize: 16, + fontFamily: 'Georgia', + fontWeight: 300, + }, +``` + +#### `tabBarIcon` + +Function that given `{ focused: boolean, color: string, size: number }` returns a React.Node, to display in the tab bar. + +#### `tabBarIconStyle` + +Style object for the tab icon. + +#### `tabBarBadge` + +Text to show in a badge on the tab icon. Accepts a `string` or a `number`. + +Tab bar badge + +#### `tabBarBadgeStyle` + +Style for the badge on the tab icon. You can specify a background color or text color here. + +Tab bar badge style + +Example: + +```js + tabBarBadgeStyle: { + color: 'black', + backgroundColor: 'yellow', + }, +``` + +#### `tabBarAccessibilityLabel` + +Accessibility label for the tab button. This is read by the screen reader when the user taps the tab. It's recommended to set this if you don't have a label for the tab. + +#### `tabBarButton` + +Function which returns a React element to render as the tab bar button. It wraps the icon and label. Renders `Pressable` by default. + +You can specify a custom implementation here: + +```js +tabBarButton: (props) => ; +``` + +#### `tabBarButtonTestID` + +ID to locate this tab button in tests. + +#### `tabBarActiveTintColor` + +Color for the icon and label in the active tab. +Tab bar active tint color + +#### `tabBarInactiveTintColor` + +Color for the icon and label in the inactive tabs. +Tab bar inactive tint color + +#### `tabBarActiveBackgroundColor` + +Background color for the active tab. + +#### `tabBarInactiveBackgroundColor` + +Background color for the inactive tabs. + +#### `tabBarHideOnKeyboard` + +Whether the tab bar is hidden when the keyboard opens. Defaults to `false`. + +#### `tabBarItemStyle` + +Style object for the tab item container. + +#### `tabBarStyle` + +Style object for the tab bar. You can configure styles such as background color here. + +To show your screen under the tab bar, you can set the `position` style to absolute: + +```js + +``` + +You also might need to add a bottom margin to your content if you have an absolutely positioned tab bar. React Navigation won't do it automatically. See [`useBottomTabBarHeight`](#usebottomtabbarheight) for more details. + +#### `tabBarBackground` + +Function which returns a React Element to use as background for the tab bar. You could render an image, a gradient, blur view etc.: + +```js +import { BlurView } from 'expo-blur'; + +// ... + + ( + + ), + }} +> +``` + +When using `BlurView`, make sure to set `position: 'absolute'` in `tabBarStyle` as well. You'd also need to use [`useBottomTabBarHeight`](#usebottomtabbarheight) to add bottom padding to your content. + +Tab bar background + +#### `tabBarPosition` + +Position of the tab bar. Available values are: + +- `bottom` (Default) +- `top` +- `left` +- `right` + +When the tab bar is positioned on the `left` or `right`, it is styled as a sidebar. This can be useful when you want to show a sidebar on larger screens and a bottom tab bar on smaller screens: + + + + +```js +const Tabs = createBottomTabNavigator({ + screenOptions: { + tabBarPosition: isLargeScreen ? 'left' : 'bottom', + }, + + // ... +}); +``` + + + + +```js + +``` + + + + +Sidebar + +You can also render a compact sidebar by placing the label below the icon. This is only supported when the [`tabBarVariant`](#tabbarvariant) is set to `material`: + + + + +```js +const Tabs = createBottomTabNavigator({ + screenOptions: { + tabBarPosition: isLargeScreen ? 'left' ? 'bottom', + tabBarVariant: isLargeScreen ? 'material' : 'uikit', + tabBarLabelPosition: 'below-icon', + }, + + // ... +}); +``` + + + + +```js + +``` + + + + +![Compact sidebar](/assets/7.x/bottom-tabs-side-compact.png) + +#### `tabBarVariant` + +Variant of the tab bar. Available values are: + +- `uikit` (Default) - The tab bar will be styled according to the iOS UIKit guidelines. +- `material` - The tab bar will be styled according to the Material Design guidelines. + +The `material` variant is currently only supported when the [`tabBarPosition`](#tabbarposition) is set to `left` or `right`. + +![Material sidebar](/assets/7.x/bottom-tabs-side-material.png) + +#### `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. + +#### `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. + +#### `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. + +#### `sceneStyle` + +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. + +In addition to those, the following options are also supported in bottom tabs: + +#### `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 +- `layout` - Dimensions of the screen, contains `height` and `width` properties. + +Example: + +```js +import { getHeaderTitle } from '@react-navigation/elements'; + +// .. + +header: ({ navigation, route, options }) => { + const title = getHeaderTitle(options, route.name); + + return ; +}; +``` + +To set a custom header for all the screens in the navigator, you can specify this option in the `screenOptions` prop of the navigator. + +##### Specify a `height` in `headerStyle` + +If your custom header's height differs from the default header height, then you might notice glitches due to measurement being async. Explicitly specifying the height will avoid such glitches. + +Example: + +```js +headerStyle: { + height: 80, // Specify the height of your custom header +}; +``` + +Note that this style is not applied to the header by default since you control the styling of your custom header. If you also want to apply this style to your header, use `options.headerStyle` from the props. + +#### `headerShown` + +Whether to show or hide the header for the screen. The header is shown by default. Setting this to `false` hides the header. + +### 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 + +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'; + +function HomeScreen() { + const navigation = useNavigation(); + + // codeblock-focus-start + React.useEffect(() => { + const unsubscribe = navigation.addListener('tabPress', (e) => { + // Prevent default behavior + e.preventDefault(); + + // 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. + +Example: + +```js +React.useEffect(() => { + const unsubscribe = navigation.addListener('tabLongPress', (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 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(); + + 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 + +The bottom tab navigator exports the following hooks: + +#### `useBottomTabBarHeight` + +This hook returns the height of the bottom tab bar. By default, the screen content doesn't go under the tab bar. However, if you want to make the tab bar absolutely positioned and have the content go under it (e.g. to show a blur effect), it's necessary to adjust the content to take the tab bar height into account. + +Example: + +```js +import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'; + +function MyComponent() { + const tabBarHeight = useBottomTabBarHeight(); + + return ( + + {/* Content */} + + ); +} +``` + +Alternatively, you can use the `BottomTabBarHeightContext` directly if you are using a class component or need it in a reusable component that can be used outside the bottom tab navigator: + +```js +import { BottomTabBarHeightContext } from '@react-navigation/bottom-tabs'; + +// ... + + + {tabBarHeight => ( + /* render something */ + )} + +``` + +## Animations + +By default, switching between tabs doesn't have any animation. You can specify the `animation` option to customize the transition animation. + +Supported values for `animation` are: + +- `fade` - Cross-fade animation for the screen transition where the new screen fades in and the old screen fades out. + + + +- `shift` - Shifting animation for the screen transition where the screens slightly shift to left/right. + + + +- `none` - The screen transition doesn't have any animation. This is the default value. + +```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'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; + +function HomeScreen() { + return ( + + Home! + + ); +} + +function ProfileScreen() { + return ( + + Profile! + + ); +} + +// codeblock-focus-start +const RootTabs = createBottomTabNavigator({ + screenOptions: { + // highlight-start + animation: 'fade', + // highlight-end + }, + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(RootTabs); + +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 + +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`. + + A config that uses a timing animation looks like this: + + ```js + const config = { + animation: 'timing', + config: { + duration: 150, + easing: Easing.inOut(Easing.ease), + }, + }; + ``` + + We can pass this config in the `transitionSpec` option: + + + + + ```js + { + Profile: { + screen: Profile, + options: { + // highlight-start + transitionSpec: { + animation: 'timing', + config: { + duration: 150, + easing: Easing.inOut(Easing.ease), + }, + }, + // highlight-end + }, + }, + } + ``` + + + + + ```js + + ``` + + + + +- `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. + + A config that fades the screen looks like this: + + ```js + const forFade = ({ current }) => ({ + sceneStyle: { + opacity: current.progress.interpolate({ + inputRange: [-1, 0, 1], + outputRange: [0, 1, 0], + }), + }, + }); + ``` + + 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 + + We can pass this function in `sceneStyleInterpolator` option: + + + + + ```js + { + Profile: { + screen: Profile, + options: { + // highlight-start + sceneStyleInterpolator: ({ current }) => ({ + sceneStyle: { + opacity: current.progress.interpolate({ + inputRange: [-1, 0, 1], + outputRange: [0, 1, 0], + }), + }, + }), + // highlight-end + }, + }, + } + ``` + + + + + ```js + ({ + sceneStyle: { + opacity: current.progress.interpolate({ + inputRange: [-1, 0, 1], + outputRange: [0, 1, 0], + }), + }, + }), + // highlight-end + }} + /> + ``` + + + + +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 static2dynamic +import * as React from 'react'; +import { View, Text, Easing } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; + +function HomeScreen() { + return ( + + Home! + + ); +} + +function ProfileScreen() { + return ( + + Profile! + + ); +} + +// codeblock-focus-start +const RootTabs = createBottomTabNavigator({ + screenOptions: { + transitionSpec: { + animation: 'timing', + config: { + duration: 150, + easing: Easing.inOut(Easing.ease), + }, + }, + sceneStyleInterpolator: ({ current }) => ({ + sceneStyle: { + opacity: current.progress.interpolate({ + inputRange: [-1, 0, 1], + outputRange: [0, 1, 0], + }), + }, + }), + }, + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(RootTabs); + +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: + +#### `TransitionSpecs` + +- `FadeSpec` - Configuration for a cross-fade animation between screens. +- `ShiftSpec` - Configuration for a shifting animation between screens. + +Example: + + + + +```js +import { TransitionSpecs } from '@react-navigation/bottom-tabs'; + +// ... + +{ + Profile: { + screen: Profile, + options: { + // highlight-start + transitionSpec: TransitionSpecs.CrossFadeSpec, + // highlight-end + }, + }, +} +``` + + + + +```js +import { TransitionSpecs } from '@react-navigation/bottom-tabs'; + +// ... + +; +``` + + + + +#### `SceneStyleInterpolators` + +- `forFade` - Cross-fade animation for the screen transition where the new screen fades in and the old screen fades out. +- `forShift` - Shifting animation for the screen transition where the screens slightly shift to left/right. + +Example: + + + + +```js +import { SceneStyleInterpolators } from '@react-navigation/bottom-tabs'; + +// ... + +{ + Profile: { + screen: Profile, + options: { + // highlight-start + sceneStyleInterpolator: SceneStyleInterpolators.forFade, + // highlight-end + }, + }, +} +``` + + + + +```js +import { SceneStyleInterpolators } from '@react-navigation/bottom-tabs'; + +// ... + +; +``` + + + + +#### `TransitionPresets` + +We export transition presets that bundle various sets of these options together. A transition preset is an object containing a few animation-related screen options exported under `TransitionPresets`. Currently the following presets are available: + +- `FadeTransition` - Cross-fade animation for the screen transition where the new screen fades in and the old screen fades out. +- `ShiftTransition` - Shifting animation for the screen transition where the screens slightly shift to left/right. + +You can spread these presets in `options` to customize the animation for a screen: + +Example: + + + + +```js +import { TransitionPresets } from '@react-navigation/bottom-tabs'; + +// ... + +{ + Profile: { + screen: Profile, + options: { + // highlight-start + ...TransitionPresets.FadeTransition, + // highlight-end + }, + }, +} +``` + + + + +```js +import { TransitionPresets } from '@react-navigation/bottom-tabs'; + +// ... + +; +``` + + + diff --git a/versioned_docs/version-8.x/combine-static-with-dynamic.md b/versioned_docs/version-8.x/combine-static-with-dynamic.md new file mode 100644 index 0000000000..bfb8c80b61 --- /dev/null +++ b/versioned_docs/version-8.x/combine-static-with-dynamic.md @@ -0,0 +1,215 @@ +--- +id: combine-static-with-dynamic +title: Combining static and dynamic APIs +sidebar_label: Static and dynamic APIs +--- + +While the static API has many advantages, it doesn't fit use cases where the navigation configuration needs to be dynamic. So React Navigation supports interop between the static and dynamic APIs. + +Keep in mind that the features provided by the static API such as automatic linking configuration and automatic TypeScript types need the whole configuration to be static. If part of the configuration is dynamic, you'll need to handle those parts manually. + +There are 2 ways you may want to combine the static and dynamic APIs: + +## Static root navigator, dynamic nested navigator + +This is useful if you want to keep your configuration static, but need to use a dynamic configuration for a specific navigator. + +Let's consider the following example: + +- You have a root stack navigator that contains a tab navigator in a screen. +- The tab navigator is defined using the dynamic API. + +Our static configuration would look like this: + +```js +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +const RootStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + }, + Feed: { + screen: FeedScreen, + linking: { + path: 'feed', + }, + }, + }, +}); +``` + +Here, `FeedScreen` is a component that renders a tab navigator and is defined using the dynamic API: + +```js +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; + +const Tab = createBottomTabNavigator(); + +function FeedScreen() { + return ( + + + + + ); +} +``` + +This code will work, but we're missing 2 things: + +- Linking configuration for the screens in the top tab navigator. +- TypeScript types for the screens in the top tab navigator. + +Since the nested navigator is defined using the dynamic API, we need to handle these manually. For the linking configuration, we can define the screens in the `linking` property of the `Feed` screen: + +```js +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +const RootStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + }, + Feed: { + screen: FeedScreen, + linking: { + path: 'feed', + // highlight-start + screens: { + Latest: 'latest', + Popular: 'popular', + }, + // highlight-end + }, + }, + }, +}); +``` + +Here the `screens` property is the same as how you'd define it with `linking` config with the dynamic API. It can contain configuration for any nested navigators as well. See [configuring links](configuring-links.md) for more details on the API. + +For the TypeScript types, we can define the type of the `FeedScreen` component: + +```tsx +import { + StaticScreenProps, + NavigatorScreenParams, +} from '@react-navigation/native'; + +type FeedParamList = { + Latest: undefined; + Popular: undefined; +}; + +// highlight-next-line +type Props = StaticScreenProps>; + +// highlight-next-line +function FeedScreen(_: Props) { + // ... +} +``` + +In the above snippet: + +1. We first define the param list type for screens in the navigator that defines params for each screen +2. Then we use the `NavigatorScreenParams` type to get the type of route's `params` which will include types for the nested screens +3. Finally, we use the type of `params` with `StaticScreenProps` to define the type of the screen component + +This is based on how we'd define the type for a screen with a nested navigator with the dynamic API. See [Type checking screens and params in nested navigator](typescript.md#type-checking-screens-and-params-in-nested-navigator). + +## Dynamic root navigator, static nested navigator + +This is useful if you already have a dynamic configuration, but want to migrate to the static API. This way you can migrate one navigator at a time. + +Let's consider the following example: + +- You have a root stack navigator that contains a tab navigator in a screen. +- The root stack navigator is defined using the dynamic API. + +Our dynamic configuration would look like this: + +```js +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +const RootStack = createNativeStackNavigator(); + +function RootStackScreen() { + return ( + + + + + ); +} +``` + +Here, `FeedScreen` is a component that renders a tab navigator and is defined using the static API: + +```js +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; + +const FeedTabs = createBottomTabNavigator({ + screens: { + Latest: { + screen: LatestScreen, + }, + Popular: { + screen: PopularScreen, + }, + }, +}); +``` + +To use the `FeedTabs` navigator for the `Feed` screen, we need to use the `createComponentForStaticNavigation` function: + +```js +import { createComponentForStaticNavigation } from '@react-navigation/native'; + +// highlight-next-line +const FeedScreen = createComponentForStaticNavigation(FeedTabs, 'Feed'); +``` + +In addition, we can generate the TypeScript types for the `FeedTabs` navigator and use it in the types of `RootStack` without needing to write them manually: + +```tsx +import { + StaticParamList, + NavigatorScreenParams, +} from '@react-navigation/native'; + +// highlight-next-line +type FeedTabsParamList = StaticParamList; + +type RootStackParamList = { + Home: undefined; + // highlight-next-line + Feed: NavigatorScreenParams; +}; +``` + +Similarly, we can generate the linking configuration for the `FeedTabs` navigator and use it in the linking configuration passed to `NavigationContainer`: + +```js +import { createPathConfigForStaticNavigation } from '@react-navigation/native'; + +// highlight-next-line +const feedScreens = createPathConfigForStaticNavigation(FeedTabs); + +const linking = { + prefixes: ['https://example.com', 'example://'], + config: { + screens: { + Home: '', + Feed: { + path: 'feed', + // highlight-next-line + screens: feedScreens, + }, + }, + }, +}; +``` + +This will generate the linking configuration for the `Feed` screen based on the configuration of the `FeedTabs` navigator. diff --git a/versioned_docs/version-8.x/community-libraries.md b/versioned_docs/version-8.x/community-libraries.md new file mode 100755 index 0000000000..74b8fc2e3e --- /dev/null +++ b/versioned_docs/version-8.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-8.x/community-navigators.md b/versioned_docs/version-8.x/community-navigators.md new file mode 100755 index 0000000000..74c1a3278c --- /dev/null +++ b/versioned_docs/version-8.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-8.x/community-solutions.md b/versioned_docs/version-8.x/community-solutions.md new file mode 100755 index 0000000000..18b8f2c81c --- /dev/null +++ b/versioned_docs/version-8.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-8.x/configuring-links.md b/versioned_docs/version-8.x/configuring-links.md new file mode 100644 index 0000000000..7b98cbee23 --- /dev/null +++ b/versioned_docs/version-8.x/configuring-links.md @@ -0,0 +1,1391 @@ +--- +id: configuring-links +title: Configuring links +sidebar_label: Configuring links +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +In this guide, we will configure React Navigation to handle external links. This is necessary if you want to: + +1. Handle deep links in React Native apps on Android and iOS +2. Enable URL integration in browser when using on web +3. Use [``](link.md) or [`useLinkTo`](use-link-to.md) to navigate using paths. + +Make sure that you have [configured deep links](deep-linking.md) in your app before proceeding. If you have an Android or iOS app, remember to specify the [`prefixes`](navigation-container.md#linkingprefixes) option. + + + + +The [`Navigation`](static-configuration.md#createstaticnavigation) component accepts a [`linking`](static-configuration.md#differences-in-the-linking-prop) prop that makes it easier to handle incoming links: + +```js +import { createStaticNavigation } from '@react-navigation/native'; + +// highlight-start +const linking = { + enabled: 'auto' /* Automatically generate paths for all screens */, + prefixes: [ + /* your linking prefixes */ + ], +}; +// highlight-end + +function App() { + return ( + Loading...} + /> + ); +} + +const Navigation = createStaticNavigation(RootStack); +``` + + + + +The `NavigationContainer` accepts a [`linking`](navigation-container.md#linking) prop that makes it easier to handle incoming links. The 2 of the most important properties you can specify in the `linking` prop are `prefixes` and `config`: + +```js +import { NavigationContainer } from '@react-navigation/native'; + +// highlight-start +const linking = { + prefixes: [ + /* your linking prefixes */ + ], + config: { + /* configuration for matching screens with paths */ + }, +}; +// highlight-end + +function App() { + return ( + Loading...} + > + {/* content */} + + ); +} +``` + + + + +When you specify the `linking` prop, React Navigation will handle incoming links automatically. On Android and iOS, it'll use React Native's [`Linking` module](https://reactnative.dev/docs/linking) to handle incoming links, both when the app was opened with the link, and when new links are received when the app is open. On the Web, it'll use the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) to sync the URL with the browser. + +:::warning + +Currently there seems to be bug ([facebook/react-native#25675](https://github.com/facebook/react-native/issues/25675)) which results in it never resolving on Android. We add a timeout to avoid getting stuck forever, but it means that the link might not be handled in some cases. + +::: + +You can also pass a [`fallback`](navigation-container.md#fallback) prop that controls what's displayed when React Navigation is trying to resolve the initial deep link URL. + +## Prefixes + +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: ['example://', 'https://example.com'], +}; +``` + +Note that the `prefixes` option is not supported on Web. The host & domain names will be automatically determined from the Website URL in the browser. If your app runs only on Web, then you can omit this option from the config. + +### 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 `*.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: ['example://', 'https://example.com', 'https://*.example.com'], +}; +``` + +## Filtering certain paths + +Sometimes we may not want to handle all incoming links. For example, we may want to filter out links meant for authentication (e.g. `expo-auth-session`) or other purposes instead of navigating to a specific screen. + +To achieve this, you can use the `filter` option: + +```js +const linking = { + prefixes: ['example://', 'https://example.com'], + // highlight-next-line + filter: (url) => !url.includes('+expo-auth-session'), +}; +``` + +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://example.com/app`, you can specify the `path` as `app`: + +```js +const linking = { + prefixes: ['example://', 'https://example.com'], + config: { + // highlight-next-line + path: 'app', + + // ... + }, +}; +``` + +It's not possible to specify params here since this doesn't belong to a screen, e.g. `app/:id` won't work. + +## Mapping path to route names + + + + +If you specify `enabled: 'auto'` in the `linking` prop, React Navigation will automatically generate paths for all screens. For example, if you have a `Profile` screen in the navigator, it'll automatically generate a path for it as `profile`. + +If you wish to handle the configuration manually, or want to override the generated path for a specific screen, you can specify `linking` property next to the screen in the navigator to map a path to a screen. For example: + +```js +const RootStack = createStackNavigator({ + screens: { + Profile: { + screen: ProfileScreen, + // highlight-start + linking: { + path: 'user', + }, + // highlight-end + }, + Chat: { + screen: ChatScreen, + // highlight-start + linking: { + path: 'feed/:sort', + }, + // highlight-end + }, + }, +}); +``` + +In this example: + +- `Chat` screen that handles the URL `/feed` with the param `sort` (e.g. `/feed/latest` - the `Chat` screen will receive a param `sort` with the value `latest`). +- `Profile` screen that handles the URL `/user`. + +Similarly, when you have a nested navigator, you can specify the `linking` property for the screens in the navigator to handle the path for the nested screens: + +```js +const HomeTabs = createBottomTabNavigator({ + screens: { + Home: { + screen: HomeScreen, + // highlight-start + linking: { + path: 'home', + }, + // highlight-end + }, + Settings: { + screen: SettingsScreen, + // highlight-start + linking: { + path: 'settings', + }, + // highlight-end + }, + }, +}); + +const RootStack = createStackNavigator({ + screens: { + HomeTabs: { + screen: HomeTabs, + }, + Profile: { + screen: ProfileScreen, + // highlight-start + linking: { + path: 'user', + }, + // highlight-end + }, + Chat: { + screen: ChatScreen, + // highlight-start + linking: { + path: 'feed/:sort', + }, + // highlight-end + }, + }, +}); +``` + +In the above example, the following path formats are handled: + +- `/home` navigates to the `HomeTabs` -> `Home` screen +- `/settings` navigates to the `HomeTabs` -> `Settings` screen +- `/user` navigates to the `Profile` screen +- `/feed/:sort` navigates to the `Chat` screen with the param `sort` + +### How does automatic path generation work? + +When using automatic path generation with `enabled: 'auto'`, the following rules are applied: + +- Screens with an explicit `linking` property are not used for path generation and will be added as-is. +- Screen names will be converted from `PascalCase` to `kebab-case` to use as the path (e.g. `NewsFeed` -> `news-feed`). +- Unless a screen has explicit empty path (`path: ''`) to use for the homepage, the first leaf screen encountered will be used as the homepage. +- Path generation only handles leaf screens, i.e. no path is generated for screens containing nested navigators. It's still possible to specify a path for them with an explicit `linking` property. + +Let's say we have the following navigation structure: + +```js +const HomeTabs = createBottomTabNavigator({ + screens: { + Home: { + screen: HomeScreen, + }, + Settings: { + screen: SettingsScreen, + }, + }, +}); + +const RootStack = createStackNavigator({ + screens: { + HomeTabs: { + screen: HomeTabs, + }, + Profile: { + screen: ProfileScreen, + }, + Chat: { + screen: ChatScreen, + }, + }, +}); +``` + +With automatic path generation, the following paths will be generated: + +- `/` navigates to the `HomeTabs` -> `Home` screen +- `/settings` navigates to the `HomeTabs` -> `Settings` screen +- `/profile` navigates to the `Profile` screen +- `/chat` navigates to the `Chat` screen + +If the URL contains a query string, it'll be passed as params to the screen. For example, the URL `/profile?user=jane` will pass the `user` param to the `Profile` screen. + + + + +If you specify a `linking` option, by default React Navigation will use the path segments as the route name when parsing the URL. However, directly translating path segments to route names may not be the expected behavior. + +You can specify the [`config`](navigation-container.md#linkingconfig) option in `linking` to control how the deep link is parsed to suit your needs. The config should specify the mapping between route names and path patterns: + +```js +const config = { + screens: { + Chat: 'feed/:sort', + Profile: 'user', + }, +}; +``` + +In this example: + +- `Chat` screen that handles the URL `/feed` with the param `sort` (e.g. `/feed/latest` - the `Chat` screen will receive a param `sort` with the value `latest`). +- `Profile` screen that handles the URL `/user`. + +The config option can then be passed in the `linking` prop to the container: + +```js +import { NavigationContainer } from '@react-navigation/native'; + +const config = { + screens: { + Chat: 'feed/:sort', + Profile: 'user', + }, +}; + +const linking = { + prefixes: ['https://example.com', 'example://'], + config, +}; + +function App() { + return ( + Loading...}> + {/* content */} + + ); +} +``` + +The config object must match the navigation structure for your app. For example, the above configuration is if you have `Chat` and `Profile` screens in the navigator at the root: + +```js +function App() { + return ( + + + + + ); +} +``` + +If your `Chat` screen is inside a nested navigator, we'd need to account for that. For example, consider the following structure where your `Profile` screen is at the root, but the `Chat` screen is nested inside `Home`: + +```js +function App() { + return ( + + + + + ); +} + +function HomeScreen() { + return ( + + + + ); +} +``` + +For the above structure, our configuration will look like this: + +```js +const config = { + screens: { + Home: { + screens: { + Chat: 'feed/:sort', + }, + }, + Profile: 'user', + }, +}; +``` + +Similarly, any nesting needs to be reflected in the configuration. + + + +
+How it works + +The linking works by translating the URL to a valid [navigation state](navigation-state.md) and vice versa using the configuration provided. For example, the path `/rooms/chat?user=jane` may be translated to a state object like this: + +```js +const state = { + routes: [ + { + name: 'rooms', + state: { + routes: [ + { + name: 'chat', + params: { user: 'jane' }, + }, + ], + }, + }, + ], +}; +``` + +For example, you might want to parse the path `/feed/latest` to something like: + +```js +const state = { + routes: [ + { + name: 'Chat', + params: { + sort: 'latest', + }, + }, + ]; +} +``` + +See [Navigation State reference](navigation-state.md) for more details on how the state object is structured. + +
+ +## Passing params + +A common use case is to pass params to a screen to pass some data. For example, you may want the `Profile` screen to have an `id` param to know which user's profile it is. It's possible to pass params to a screen through a URL when handling deep links. + +By default, query params are parsed to get the params for a screen. For example, with the above example, the URL `/user?id=jane` will pass the `id` param to the `Profile` screen. + +You can also customize how the params are parsed from the URL. Let's say you want the URL to look like `/user/jane` where the `id` param is `jane` instead of having the `id` in query params. You can do this by specifying `user/:id` for the `path`. **When the path segment starts with `:`, it'll be treated as a param**. For example, the URL `/user/jane` would resolve to `Profile` screen with the string `jane` as a value of the `id` param and will be available in `route.params.id` in `Profile` screen. + +By default, all params are treated as strings. You can also customize how to parse them by specifying a function in the `parse` property to parse the param, and a function in the `stringify` property to convert it back to a string. + +If you wanted to resolve `/user/@jane/settings` to result in the params `{ id: 'jane' section: 'settings' }`, you could make `Profile`'s config to look like this: + + + + +```js +const RootStack = createStackNavigator({ + screens: { + Profile: { + screen: ProfileScreen, + // highlight-start + linking: { + path: 'user/:id/:section', + parse: { + id: (id) => id.replace(/^@/, ''), + }, + stringify: { + id: (id) => `@${id}`, + }, + }, + // highlight-end + }, + }, +}); +``` + + + + +```js +const config = { + screens: { + Profile: { + // highlight-start + path: 'user/:id/:section', + parse: { + id: (id) => id.replace(/^@/, ''), + }, + stringify: { + id: (id) => `@${id}`, + }, + // highlight-end + }, + }, +}; +``` + + + + +
+Result Navigation State + +With this configuration, the path `/user/@jane/settings` will resolve to the following state object: + +```js +const state = { + routes: [ + { + name: 'Profile', + params: { id: 'jane', section: 'settings' }, + }, + ], +}; +``` + +
+ +## Marking params as optional + +Sometimes a param may or may not be present in the URL depending on certain conditions. For example, in the above scenario, you may not always have the section parameter in the URL, i.e. both `/user/jane/settings` and `/user/jane` should go to the `Profile` screen, but the `section` param (with the value `settings` in this case) may or may not be present. + +In this case, you would need to mark the `section` param as optional. You can do it by adding the `?` suffix after the param name: + + + + +```js +const RootStack = createStackNavigator({ + screens: { + Profile: { + screen: ProfileScreen, + linking: { + // highlight-next-line + path: 'user/:id/:section?', + parse: { + id: (id) => `user-${id}`, + }, + stringify: { + id: (id) => id.replace(/^user-/, ''), + }, + }, + }, + }, +}); +``` + + + + +```js +const config = { + screens: { + Profile: { + // highlight-next-line + path: 'user/:id/:section?', + parse: { + id: (id) => `user-${id}`, + }, + stringify: { + id: (id) => id.replace(/^user-/, ''), + }, + }, + }, +}; +``` + + + + +
+Result Navigation State + +With this configuration, the path `/user/jane` will resolve to the following state object: + +```js +const state = { + routes: [ + { + name: 'Profile', + params: { id: 'user-jane' }, + }, + ], +}; +``` + +If the URL contains a `section` param (e.g. `/user/jane/settings`), this will result in the following with the same config: + +```js +const state = { + routes: [ + { + name: 'Profile', + params: { id: 'user-jane', section: 'settings' }, + }, + ], +}; +``` + +
+ +## Handling unmatched routes or 404 + +If your app is opened with an invalid URL, most of the times you'd want to show an error page with some information. On the web, this is commonly known as 404 - or page not found error. + +To handle this, you'll need to define a catch-all route that will be rendered if no other routes match the path. You can do it by specifying `*` for the path matching pattern: + + + + +```js +const HomeTabs = createBottomTabNavigator({ + screens: { + Feed: { + screen: FeedScreen, + }, + Profile: { + screen: HomeScreen, + linking: { + path: 'users/:id', + }, + }, + Settings: { + screen: SettingsScreen, + linking: { + path: 'settings', + }, + }, + }, +}); + +const RootStack = createStackNavigator({ + screens: { + Home: { + screen: HomeTabs, + }, + NotFound: { + screen: NotFoundScreen, + linking: { + // highlight-next-line + path: '*', + }, + }, + }, +}); +``` + + + + +```js +const config = { + screens: { + Home: { + initialRouteName: 'Feed', + screens: { + Profile: 'users/:id', + Settings: 'settings', + }, + }, + NotFound: { + // highlight-start + path: '*', + }, + }, +}; +``` + + + + +Here, we have defined a route named `NotFound` and set it to match `*` aka everything. If the path didn't match `user/:id` or `settings`, it'll be matched by this route. + +
+Result Navigation State + +With this configuration, a path like `/library` or `/settings/notification` will resolve to the following state object: + +```js +const state = { + routes: [{ name: 'NotFound' }], +}; +``` + +
+ +You can even go more specific, for example, say if you want to show a different screen for invalid paths under `/settings`, you can specify such a pattern under `Settings`: + + + + +```js +const SettingsStack = createStackNavigator({ + screens: { + UserSettings: { + screen: UserSettingsScreen, + linking: { + path: 'user-settings', + }, + }, + InvalidSettings: { + screen: InvalidSettingsScreen, + linking: { + // highlight-next-line + path: '*', + }, + }, + }, +}); + +const HomeTabs = createBottomTabNavigator({ + screens: { + Feed: { + screen: FeedScreen, + }, + Profile: { + screen: HomeScreen, + linking: { + path: 'users/:id', + }, + }, + Settings: { + screen: SettingsStack, + }, + }, +}); + +const RootStack = createStackNavigator({ + screens: { + Home: { + screen: HomeTabs, + }, + NotFound: { + screen: NotFoundScreen, + linking: { + path: '*', + }, + }, + }, +}); +``` + + + + +```js +const config = { + screens: { + Home: { + initialRouteName: 'Feed', + screens: { + Profile: 'users/:id', + Settings: { + path: 'settings', + screens: { + InvalidSettings: '*', + }, + }, + }, + }, + NotFound: '*', + }, +}; +``` + + + + +
+Result Navigation State + +With this configuration, the path `/settings/notification` will resolve to the following state object: + +```js +const state = { + routes: [ + { + name: 'Home', + state: { + index: 1, + routes: [ + { name: 'Feed' }, + { + name: 'Settings', + state: { + routes: [ + { name: 'InvalidSettings', path: '/settings/notification' }, + ], + }, + }, + ], + }, + }, + ], +}; +``` + +
+ +The `route` passed to the `NotFound` screen will contain a `path` property which corresponds to the path that opened the page. If you need, you can use this property to customize what's shown in this screen, e.g. load the page in a `WebView`: + +```js +function NotFoundScreen({ route }) { + if (route.path) { + return ; + } + + return This screen doesn't exist!; +} +``` + +When doing server rendering, you'd also want to return correct status code for 404 errors. See [server rendering docs](server-rendering.md#handling-404-or-other-status-codes) for a guide on how to handle it. + +## Rendering an initial route + +Sometimes you want to ensure that a certain screen will always be present as the first screen in the navigator's state. You can use the `initialRouteName` property to specify the screen to use for the initial screen. + +In the above example, if you want the `Feed` screen to be the initial route in the navigator under `Home`, your config will look like this: + + + + +```js +const HomeTabs = createBottomTabNavigator({ + screens: { + Feed: { + screen: FeedScreen, + }, + Profile: { + screen: HomeScreen, + linking: { + path: 'users/:id', + }, + }, + Settings: { + screen: SettingsScreen, + linking: { + path: 'settings', + }, + }, + }, +}); + +const RootStack = createStackNavigator({ + screens: { + Home: { + screen: HomeTabs, + linking: { + // highlight-next-line + initialRouteName: 'Feed', + }, + }, + NotFound: { + screen: NotFoundScreen, + linking: { + path: '*', + }, + }, + }, +}); +``` + + + + +```js +const config = { + screens: { + Home: { + // highlight-next-line + initialRouteName: 'Feed', + screens: { + Profile: 'users/:id', + Settings: 'settings', + }, + }, + }, +}; +``` + + + + +
+Result Navigation State + +With this configuration, the path `/users/42` will resolve to the following state object: + +```js +const state = { + routes: [ + { + name: 'Home', + state: { + index: 1, + routes: [ + { name: 'Feed' }, + { + name: 'Profile', + params: { id: '42' }, + }, + ], + }, + }, + ], +}; +``` + +
+ +:::warning + +The `initialRouteName` will add the screen to React Navigation's state only. If your app is running on the Web, the browser's history will not contain this screen as the user has never visited it. So, if the user presses the browser's back button, it'll not go back to this screen. + +::: + +Another thing to keep in mind is that it's not possible to pass params to the initial screen through the URL. So make sure that your initial route doesn't need any params or specify `initialParams` in the screen configuration to pass the required params. + +In this case, any params in the URL are only passed to the `Profile` screen which matches the path pattern `users/:id`, and the `Feed` screen doesn't receive any params. If you want to have the same params in the `Feed` screen, you can specify a [custom `getStateFromPath` function](navigation-container.md#linkinggetstatefrompath) and copy those params. + +Similarly, if you want to access params of a parent screen from a child screen, you can use [React Context](https://react.dev/reference/react/useContext) to expose them. + +## Matching exact paths + +By default, paths defined for each screen are matched against the URL relative to their parent screen's path. Consider the following config: + + + + +```js +const ProfileTabs = createBottomTabNavigator({ + screens: { + Profile: { + screen: HomeScreen, + linking: { + path: 'users/:id', + }, + }, + }, +}); + +const RootStack = createStackNavigator({ + screens: { + Home: { + screen: ProfileTabs, + linking: { + path: 'feed', + }, + }, + }, +}); +``` + + + + +```js +const config = { + screens: { + Home: { + path: 'feed', + screens: { + Profile: 'users/:id', + }, + }, + }, +}; +``` + + + + +Here, you have a `path` property defined for the `Home` screen, as well as the child `Profile` screen. The profile screen specifies the path `users/:id`, but since it's nested inside a screen with the path `feed`, it'll try to match the pattern `feed/users/:id`. + +This will result in the URL `/feed` navigating to `Home` screen, and `/feed/users/cal` navigating to the `Profile` screen. + +In this case, it makes more sense to navigate to the `Profile` screen using a URL like `/users/cal`, rather than `/feed/users/cal`. To achieve this, you can override the relative matching behavior to `exact` matching: + + + + +```js +const ProfileTabs = createBottomTabNavigator({ + screens: { + Profile: { + screen: HomeScreen, + linking: { + path: 'users/:id', + // highlight-next-line + exact: true, + }, + }, + }, +}); + +const RootStack = createStackNavigator({ + screens: { + Home: { + screen: ProfileTabs, + linking: { + path: 'feed', + }, + }, + }, +}); +``` + + + + +```js +const config = { + screens: { + Home: { + path: 'feed', + screens: { + Profile: { + path: 'users/:id', + // highlight-next-line + exact: true, + }, + }, + }, + }, +}; +``` + + + + +With `exact` property set to `true`, `Profile` will ignore the parent screen's `path` config and you'll be able to navigate to `Profile` using a URL like `users/cal`. + +## Omitting a screen from path + +Sometimes, you may not want to have the route name of a screen in the path. For example, let's say you have a `Home` screen and the following config. When the page is opened in the browser you'll get `/home` as the URL: + + + + +```js +const RootStack = createStackNavigator({ + screens: { + Home: { + screen: ProfileScreen, + linking: { + path: 'home', + }, + }, + Profile: { + screen: HomeScreen, + linking: { + path: 'users/:id', + }, + }, + }, +}); +``` + + + + +```js +const config = { + screens: { + Home: { + path: 'home', + }, + Profile: 'users/:id', + }, +}; +``` + + + + +But it'll be nicer if the URL was just `/` when visiting the home screen. + +You can specify an empty string as path or not specify a path at all, and React Navigation won't add the screen to the path (think of it like adding empty string to the path, which doesn't change anything): + + + + +```js +const RootStack = createStackNavigator({ + screens: { + Home: { + screen: ProfileScreen, + linking: { + path: '', + }, + }, + Profile: { + screen: HomeScreen, + linking: { + path: 'users/:id', + }, + }, + }, +}); +``` + + + + +```js +const config = { + screens: { + Home: { + path: '', + }, + Profile: 'users/:id', + }, +}; +``` + + + + +## Serializing and parsing params + +Since URLs are strings, any params you have for routes are also converted to strings when constructing the path. + +For example, say you have the URL `/chat/1589842744264` with the following config: + + + + +```js +const RootStack = createStackNavigator({ + screens: { + Chat: { + screen: ChatScreen, + linking: { + path: 'chat/:date', + }, + }, + }, +}); +``` + + + + +```js +const config = { + screens: { + Chat: 'chat/:date', + }, +}; +``` + + + + +When handling the URL, your params will look like this: + +```yml +{ date: '1589842744264' } +``` + +Here, the `date` param was parsed as a string because React Navigation doesn't know that it's supposed to be a timestamp, and hence number. You can customize it by providing a custom function to use for parsing: + + + + +```js +const RootStack = createStackNavigator({ + screens: { + Chat: { + screen: ChatScreen, + linking: { + path: 'chat/:date', + parse: { + date: Number, + }, + }, + }, + }, +}); +``` + + + + +```js +const config = { + screens: { + Chat: { + path: 'chat/:date', + parse: { + date: Number, + }, + }, + }, +}; +``` + + + + +You can also provide a your own function to serialize the params. For example, let's say that you want to use a DD-MM-YYYY format in the path instead of a timestamp: + + + + +```js +const RootStack = createStackNavigator({ + screens: { + Chat: { + screen: ChatScreen, + linking: { + path: 'chat/:date', + parse: { + date: (date) => new Date(date).getTime(), + }, + stringify: { + date: (date) => { + const d = new Date(date); + + return d.getFullYear() + '-' + d.getMonth() + '-' + d.getDate(); + }, + }, + }, + }, + }, +}); +``` + + + + +```js +const config = { + screens: { + Chat: { + path: 'chat/:date', + parse: { + date: (date) => new Date(date).getTime(), + }, + stringify: { + date: (date) => { + const d = new Date(date); + + return d.getFullYear() + '-' + d.getMonth() + '-' + d.getDate(); + }, + }, + }, + }, +}; +``` + + + + +Depending on your requirements, you can use this functionality to parse and stringify more complex data. + +## Matching regular expressions + +If you need more complex matching logic, you can use regular expressions to match the path. For example, if you want to use the pattern `@username` to match a user's profile, you can use a regular expression to match the path. This allows you to have the same path pattern for multiple screens, but fine-tune the matching logic to be more specific for a particular screen. + +Regular expressions can be specified between parentheses `(` and `)` in the after a param name. For example: + + + + +```js +const RootStack = createStackNavigator({ + screens: { + Feed: { + screen: FeedScreen, + linking: { + path: ':sort(latest|popular)', + }, + }, + Profile: { + screen: ProfileScreen, + linking: { + path: ':username(@[A-Za-z0-9_]+)', + }, + }, + }, +}); +``` + + + + +```js +const config = { + screens: { + Feed: ':sort(latest|popular)', + Profile: ':username(@[A-Za-z0-9_]+)', + }, +}; +``` + + + + +This will only match the path if it starts with `@` followed by alphanumeric characters or underscores. For example, the URL `/@jane` will match the `Profile` screen, but `/jane` won't. + +Regular expressions are intended to only match path segments, not the entire path. So avoid using `/`, `^`, `$`, etc. in the regular expressions. + +:::warning + +Regular expressions are an advanced feature. They cannot be validated to warn you about potential issues, so it's up to you to ensure that the regular expression is correct. + +::: + +## Alias for paths + +If you want to have multiple paths for the same screen, you can use the `alias` property to specify an array of paths. This can be useful to keep backward compatibility with old URLs while transitioning to a new URL structure. + +For example, if you want to match both `/users/:id` and `/:id` to the `Profile` screen, you can do this: + + + + +```js +const RootStack = createStackNavigator({ + screens: { + Profile: { + screen: ProfileScreen, + linking: { + path: ':id', + alias: ['users/:id'], + }, + }, + }, +}); +``` + + + + +```js +const config = { + screens: { + Profile: { + path: ':id', + alias: ['users/:id'], + }, + }, +}; +``` + + + + +In this case, when the URL is `/users/jane` or `/jane`, it'll match the `Profile` screen. The `path` is the primary pattern that will be used to generate the URL, e.g. when navigating to the `Profile` screen in the app on the Web. The patterns in `alias` will be ignored when generating URLs. The `alias` patterns are not used for matching any child screens in nested navigators. + +On the web, if a screen containing an alias contains a nested navigator, the URL matching the alias will only be used to match the screen, and will be updated to the URL of the focused child screen once the app renders. + +Each item in the `alias` array can be a string matching the syntax of the `path` property, or an object with the following properties: + +- `path` (required) - The path pattern to match. +- `exact` - Whether to match the path exactly. Defaults to `false`. See [Matching exact paths](#matching-exact-paths) for more details. +- `parse` - Function to parse path segments into param values. See [Passing params](#passing-params) for more details. + +## Advanced cases + +For some advanced cases, specifying the mapping may not be sufficient. To handle such cases, you can specify a custom function to parse the URL into a state object ([`getStateFromPath`](navigation-container.md#linkinggetstatefrompath)), and a custom function to serialize the state object into an URL ([`getPathFromState`](navigation-container.md#linkinggetpathfromstate)). + +Example: + +```js +const linking = { + 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` + }, + getPathFromState(state, config) { + // Return a path string here + // You can also reuse the default logic by importing `getPathFromState` from `@react-navigation/native` + }, + + // ... +}; +``` + +## Playground + +import LinkingTester from '@site/src/components/LinkingTester' + + + + +Playground is not available for static config. + + + + +You can play around with customizing the config and path below, and see how the path is parsed. + + + + + diff --git a/versioned_docs/version-8.x/contributing.md b/versioned_docs/version-8.x/contributing.md new file mode 100755 index 0000000000..ec05c128ca --- /dev/null +++ b/versioned_docs/version-8.x/contributing.md @@ -0,0 +1,159 @@ +--- +id: contributing +title: React Navigation contributor guide +sidebar_label: Contributing +--- + +Want to help improve React Navigation? Your help would be greatly appreciated! + +Here are some of the ways to contribute to the project: + +- [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) + +And here are a few helpful resources to aid in getting started: + +- [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) + +## Contributing + +### Reporting Bugs + +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/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! + +### Improving the Documentation + +Any successful projects needs quality documentation and React Navigation is no different. + +Read more about the documentation on the [react-navigation/react-navigation.github.io repository](https://github.com/react-navigation/react-navigation.github.io). + +### Responding to Issues + +Another great way to contribute to React Navigation is by responding to issues. Maybe it's answering someone's question, pointing out a small typo in their code, or helping them put together a reproduction. If you're interested in a more active role in React Navigation start with responding to issues - not only is it helpful but it demonstrates your commitment and knowledge of the code! + +### Bug Fixes + +Find a bug, fix it up, all day long you'll have good luck! Like it was mentioned earlier, bugs happen. If you find a bug do the following: + +1. Check if a pull request already exists addressing that bug. If it does give it a review and leave your comments +2. If there isn't already a pull request then figure out the fix! If it's relatively small go ahead and fix it and submit a pull request. If it's a decent number of changes file an issue first so we can discuss it (see the [Big Pull Requests](#big-pull-requests) section) +3. If there is an issue related to that bug leave a comment on it, linking to your pull request, so others know it's been addressed. + +Check out the [help wanted](https://github.com/react-navigation/react-navigation/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) and [good first issue](https://github.com/react-navigation/react-navigation/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) tags to see where you can start helping out! + +### Suggesting a Feature + +Is there something you want to see from React Navigation? Please [create a feature request on Canny](https://react-navigation.canny.io/feature-requests). + +### Big Pull Requests + +For any changes that will add/remove/modify multiple files in the project (new features or bug fixes) hold off on writing code right away. There's a few reasons for that + +1. Big pull requests take a lot of time to review and it's sometimes hard to pick up the context +2. Often you may not have to make as big of a change as you expect + +With that in mind, here's the suggestion + +1. Open an issue and clearly define what it is you want to accomplish and how you intend to accomplish it +2. Discuss that solution with the community and maintainers. Provide context, establish edge cases, and figure out the design +3. Decide on a plan of action +4. Write the code and submit the PR +5. Review the PR. This can take some time but, if you followed the steps above, hopefully it won't take too much time. + +The reason we want to do this is to save everyone time. Maybe that feature already exists but isn't documented? Or maybe it doesn't fit with the library. Regardless, by discussing a major change up front you're saving your time and others time as well. + +## Information + +### 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/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. + +Would you rather take 1 minute to create an incomplete issue report and wait months to get any sort of response? Or would you rather take 20 minutes to fill out a high quality issue report, with all the necessary elements, and get a response in days? It's also a respectful thing to do for anyone willing to take the time to review your issue. + +### Pull Request Template + +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 + +- Fork the [`repo`](https://github.com/react-navigation/react-navigation) on GitHub +- Run these commands in the terminal to download locally and install it: + +```bash +git clone https://github.com//navigation-ex.git +cd navigation-ex +git remote add upstream https://github.com/react-navigation/react-navigation.git +yarn +``` + +The project uses a monorepo structure for the packages managed by [yarn workspaces](https://yarnpkg.com/lang/en/docs/workspaces/) and [lerna](https://lerna.js.org). All of the packages are under the [packages/](https://github.com/react-navigation/react-navigation/tree/main/packages) directory. + +### Code Review Guidelines + +Look around. Match the style of the rest of the codebase. This project uses ESLint to ensure consistency throughout the project. You can check your project by running: + +```bash +yarn lint +``` + +If any errors occur you'll either have to manually fix them or you can attempt to automatically fix them by running: + +```bash +yarn lint --fix +``` + +The codebase is written in TypeScript, and must pass typecheck. To typecheck files, run: + +```bash +yarn typescript +``` + +It's useful to run typechecking in watch mode when working on the project. To do it, run: + +```bash +yarn typescript --watch +``` + +### Run the Example App + +The [example app](https://github.com/react-navigation/react-navigation/tree/main/packages/example) includes a variety of patterns and is used as a simple way for contributors to manually integration test changes. + +While developing, you can run the [example app](https://github.com/react-navigation/react-navigation/tree/main/example) with [Expo](https://expo.io/) to test your changes: + +```bash +yarn example start +``` + +### Run Tests + +React Navigation has tests implemented in [Jest](https://facebook.github.io/jest/). To run either of these, from the React Navigation directory, run either of the following commands (after installing the `node_modules`) to run tests or type-checking. + +```bash +yarn test +``` + +It's useful to run tests in watch mode when working on the project. To do it, run: + +```bash +yarn test --watch +``` + +These commands will be run by our CI and are required to pass before any contributions are merged. diff --git a/versioned_docs/version-8.x/custom-android-back-button-handling.md b/versioned_docs/version-8.x/custom-android-back-button-handling.md new file mode 100755 index 0000000000..ce741a6180 --- /dev/null +++ b/versioned_docs/version-8.x/custom-android-back-button-handling.md @@ -0,0 +1,254 @@ +--- +id: custom-android-back-button-handling +title: Custom Android back button behavior +sidebar_label: Android back button behavior +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +By default, when user presses the Android hardware back button, react-navigation will pop a screen or exit the app if there are no screens to pop. This is a sensible default behavior, but there are situations when you might want to implement custom handling. + +As an example, consider a screen where user is selecting items in a list, and a "selection mode" is active. On a back button press, you would first want the "selection mode" to be deactivated, and the screen should be popped only on the second back button press. The following code snippet demonstrates the situation. We make use of [`BackHandler`](https://reactnative.dev/docs/backhandler.html) which comes with react-native, along with the `useFocusEffect` hook to add our custom `hardwareBackPress` listener. + +Returning `true` from `onBackPress` denotes that we have handled the event, and react-navigation's listener will not get called, thus not popping the screen. Returning `false` will cause the event to bubble up and react-navigation's listener will pop the screen. + + + + +```js name="Custom android back button" snack +import * as React from 'react'; +import { Text, View, BackHandler, StyleSheet } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { useFocusEffect } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { PlatformPressable, Button } from '@react-navigation/elements'; + +const listData = [{ key: 'Apple' }, { key: 'Orange' }, { key: 'Carrot' }]; + +// codeblock-focus-start +function ScreenWithCustomBackBehavior() { + // codeblock-focus-end + const [selected, setSelected] = React.useState(listData[0].key); + const [isSelectionModeEnabled, setIsSelectionModeEnabled] = + React.useState(false); + + // codeblock-focus-start + // ... + + useFocusEffect( + React.useCallback(() => { + const onBackPress = () => { + if (isSelectionModeEnabled) { + setIsSelectionModeEnabled(false); + return true; + } else { + return false; + } + }; + + const subscription = BackHandler.addEventListener( + 'hardwareBackPress', + onBackPress + ); + + return () => subscription.remove(); + }, [isSelectionModeEnabled]) + ); + // codeblock-focus-end + + return ( + + {listData.map((item) => ( + <> + {isSelectionModeEnabled ? ( + { + setSelected(item.key); + }} + style={{ + textDecorationLine: item.key === selected ? 'underline' : '', + }} + > + + {item.key} + + + ) : ( + + {item.key === selected ? 'Selected: ' : ''} + {item.key} + + )} + + ))} + + Selection mode: {isSelectionModeEnabled ? 'ON' : 'OFF'} + + ); + // codeblock-focus-start + + // ... +} +// codeblock-focus-end + +const RootStack = createNativeStackNavigator({ + screens: { + CustomScreen: ScreenWithCustomBackBehavior, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + text: { + fontSize: 20, + marginBottom: 20, + }, +}); +``` + + + + +```js name="Custom android back button" snack +import * as React from 'react'; +import { Text, View, BackHandler, StyleSheet } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { useFocusEffect } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { PlatformPressable, Button } from '@react-navigation/elements'; + +const Stack = createNativeStackNavigator(); + +const listData = [{ key: 'Apple' }, { key: 'Orange' }, { key: 'Carrot' }]; + +// codeblock-focus-start +function ScreenWithCustomBackBehavior() { + // codeblock-focus-end + + const [selected, setSelected] = React.useState(listData[0].key); + const [isSelectionModeEnabled, setIsSelectionModeEnabled] = + React.useState(false); + // codeblock-focus-start + // ... + + useFocusEffect( + React.useCallback(() => { + const onBackPress = () => { + if (isSelectionModeEnabled) { + setIsSelectionModeEnabled(false); + return true; + } else { + return false; + } + }; + + const subscription = BackHandler.addEventListener( + 'hardwareBackPress', + onBackPress + ); + + return () => subscription.remove(); + }, [isSelectionModeEnabled]) + ); + // codeblock-focus-end + + return ( + + {listData.map((item) => ( + <> + {isSelectionModeEnabled ? ( + { + setSelected(item.key); + }} + style={{ + textDecorationLine: item.key === selected ? 'underline' : '', + }} + > + + {item.key} + + + ) : ( + + {item.key === selected ? 'Selected: ' : ''} + {item.key} + + )} + + ))} + + Selection mode: {isSelectionModeEnabled ? 'ON' : 'OFF'} + + ); + // codeblock-focus-start + + // ... +} +// codeblock-focus-end + +export default function App() { + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + text: { + fontSize: 20, + marginBottom: 20, + }, +}); +``` + + + + +The presented approach will work well for screens that are shown in a `StackNavigator`. Custom back button handling in other situations may not be supported at the moment (eg. A known case when this does not work is when you want to handle back button press in an open drawer. PRs for such use cases are welcome!). + +If instead of overriding system back button, you'd like to prevent going back from the screen, see docs for [preventing going back](preventing-going-back.md). + +### Why not use component lifecycle methods + +At first, you may be inclined to use `componentDidMount` to subscribe for the back press event and `componentWillUnmount` to unsubscribe, or use `useEffect` to add the listener. This approach will not work - learn more about this in [navigation lifecycle](navigation-lifecycle.md). diff --git a/versioned_docs/version-8.x/custom-navigators.md b/versioned_docs/version-8.x/custom-navigators.md new file mode 100755 index 0000000000..e50e11714d --- /dev/null +++ b/versioned_docs/version-8.x/custom-navigators.md @@ -0,0 +1,449 @@ +--- +id: custom-navigators +title: Custom navigators +sidebar_label: Custom navigators +--- + +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. + +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. + +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. + +## Overview + +Under the hood, navigators are plain React components that use the [`useNavigationBuilder`](#usenavigationbuilder) hook. + +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 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 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. + + If any other options are passed here, they'll be forwarded to the router. + +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. + +Example: + +```js +import * as React from 'react'; +import { Text, Pressable, View } from 'react-native'; +import { + NavigationHelpersContext, + useNavigationBuilder, + TabRouter, + TabActions, +} from '@react-navigation/native'; + +function TabNavigator({ tabBarStyle, contentStyle, ...rest }) { + const { state, navigation, descriptors, NavigationContent } = + useNavigationBuilder(TabRouter, rest); + + return ( + + + {state.routes.map((route, index) => ( + { + const isFocused = state.index === index; + const event = navigation.emit({ + type: 'tabPress', + target: route.key, + canPreventDefault: true, + }); + + if (!isFocused && !event.defaultPrevented) { + navigation.dispatch({ + ...TabActions.jumpTo(route.name, route.params), + target: state.key, + }); + } + }} + style={{ flex: 1 }} + > + {descriptors[route.key].options.title ?? route.name} + + ))} + + + {state.routes.map((route, i) => { + return ( + + {descriptors[route.key].render()} + + ); + })} + + + ); +} +``` + +The `navigation` object for navigators also has an `emit` method to emit custom events to the child screens. The usage looks like this: + +```js +navigation.emit({ + type: 'transitionStart', + data: { blurring: false }, + target: route.key, +}); +``` + +The `data` is available under the `data` property in the `event` object, i.e. `event.data`. + +The `target` property determines the screen that will receive the event. If the `target` property is omitted, the event is dispatched to all screens in the navigator. + +### `createNavigatorFactory` + +This `createNavigatorFactory` function is used to create a function that will `Navigator` and `Screen` pair. Custom navigators need to wrap the navigator component in `createNavigatorFactory` before exporting. + +Example: + +```js +import { + useNavigationBuilder, + createNavigatorFactory, +} from '@react-navigation/native'; + +// ... + +export function createMyNavigator(config) { + return createNavigatorFactory(TabNavigator)(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 +import { createMyNavigator } from './myNavigator'; + +const My = createMyNavigator(); + +function App() { + return ( + + + + + ); +} +``` + +## Type-checking navigators + +To type-check navigators, we need to provide 3 types: + +- Type of the props accepted by the view +- Type of supported screen options +- A map of event types emitted by the navigator + +For example, to type-check our custom tab navigator, we can do something like this: + +```tsx +import * as React from 'react'; +import { + View, + Text, + Pressable, + type StyleProp, + type ViewStyle, + StyleSheet, +} from 'react-native'; +import { + createNavigatorFactory, + CommonActions, + type DefaultNavigatorOptions, + type NavigatorTypeBagBase, + type ParamListBase, + type StaticConfig, + type TabActionHelpers, + type TabNavigationState, + TabRouter, + type TabRouterOptions, + type TypedNavigator, + useNavigationBuilder, +} from '@react-navigation/native'; + +// Additional props accepted by the view +type TabNavigationConfig = { + tabBarStyle: StyleProp; + contentStyle: StyleProp; +}; + +// Supported screen options +type TabNavigationOptions = { + title?: string; +}; + +// Map of event name and the type of data (in event.data) +// canPreventDefault: true adds the defaultPrevented property to the +// emitted events. +type TabNavigationEventMap = { + tabPress: { + data: { isAlreadyFocused: boolean }; + canPreventDefault: true; + }; +}; + +// 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, + TabNavigationProp +> & + TabRouterOptions & + TabNavigationConfig; + +function TabNavigator({ tabBarStyle, contentStyle, ...rest }: Props) { + const { state, navigation, descriptors, NavigationContent } = + useNavigationBuilder< + TabNavigationState, + TabRouterOptions, + TabActionHelpers, + TabNavigationOptions, + TabNavigationEventMap + >(TabRouter, rest); + + return ( + + + {state.routes.map((route, index) => ( + { + const isFocused = state.index === index; + const event = navigation.emit({ + type: 'tabPress', + target: route.key, + canPreventDefault: true, + data: { + isAlreadyFocused: isFocused, + }, + }); + + if (!isFocused && !event.defaultPrevented) { + navigation.dispatch({ + ...CommonActions.navigate(route), + target: state.key, + }); + } + }} + style={{ flex: 1 }} + > + {descriptors[route.key].options.title || route.name} + + ))} + + + {state.routes.map((route, i) => { + return ( + + {descriptors[route.key].render()} + + ); + })} + + + ); +} + +// The factory function with generic types for type-inference +export function createMyNavigator< + const ParamList extends ParamListBase, + const NavigatorID extends string | undefined = undefined, + const TypeBag extends NavigatorTypeBagBase = { + ParamList: ParamList; + NavigatorID: NavigatorID; + State: TabNavigationState; + ScreenOptions: TabNavigationOptions; + EventMap: TabNavigationEventMap; + NavigationList: { + [RouteName in keyof ParamList]: TabNavigationProp< + ParamList, + RouteName, + NavigatorID + >; + }; + Navigator: typeof TabNavigator; + }, + const Config extends StaticConfig = StaticConfig, +>(config?: Config): TypedNavigator { + return createNavigatorFactory(TabNavigator)(config); +} +``` + +## Extending Navigators + +All of the built-in navigators export their views, which we can reuse and build additional functionality on top of them. For example, if we want to re-build the bottom tab navigator, we need the following code: + +```js +import * as React from 'react'; +import { + useNavigationBuilder, + createNavigatorFactory, + TabRouter, +} from '@react-navigation/native'; +import { BottomTabView } from '@react-navigation/bottom-tabs'; + +function BottomTabNavigator({ + id, + initialRouteName, + children, + layout, + screenListeners, + screenOptions, + screenLayout, + backBehavior, + ...rest +}) { + const { state, descriptors, navigation, NavigationContent } = + useNavigationBuilder(TabRouter, { + id, + initialRouteName, + children, + layout, + screenListeners, + screenOptions, + screenLayout, + backBehavior, + }); + + return ( + + + + ); +} + +export function createMyNavigator(config) { + return createNavigatorFactory(TabNavigator)(config); +} +``` + +Now, we can customize it to add additional functionality or change the behavior. For example, use a [custom router](custom-routers.md) instead of the default `TabRouter`: + +```js +import MyRouter from './MyRouter'; + +// ... + +const { state, descriptors, navigation, NavigationContent } = + useNavigationBuilder(MyRouter, { + id, + initialRouteName, + children, + layout, + screenListeners, + screenOptions, + screenLayout, + backBehavior, + }); + +// ... +``` + +:::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-8.x/custom-routers.md b/versioned_docs/version-8.x/custom-routers.md new file mode 100755 index 0000000000..86b66d3a1f --- /dev/null +++ b/versioned_docs/version-8.x/custom-routers.md @@ -0,0 +1,224 @@ +--- +id: custom-routers +title: Custom routers +sidebar_label: Custom routers +--- + +The router object provides various helper methods to deal with the state and actions, a reducer to update the state as well as some action creators. + +The router is responsible for handling actions dispatched by calling methods on the navigation object. If the router cannot handle an action, it can return `null`, which would propagate the action to other routers until it's handled. + +You can make your own router by building an object with the following functions: + +- `type` - String representing the type of the router, e.g. `'stack'`, `'tab'`, `'drawer'` etc. +- `getInitialState` - Function that returns the initial state for the navigator. Receives an options object with `routeNames` and `routeParamList` properties. +- `getRehydratedState` - Function that rehydrates the full [navigation state](navigation-state.md) from a given partial state. Receives a partial state object and an options object with `routeNames` and `routeParamList` properties. +- `getStateForRouteNamesChange` - Function that takes the current state and updated list of route names, and returns a new state. Receives the state object and an options object with `routeNames` and `routeParamList` properties. +- `getStateForAction` - Reducer function that takes the current state and action along with an options object with `routeNames` and `routeParamList` properties, and returns a new state. If the action cannot be handled, it should return `null`. +- `getStateForRouteFocus` - Function that takes the current state and key of a route, and returns a new state with that route focused. +- `shouldActionChangeFocus` - Function that determines whether the action should also change focus in parent navigator. Some actions such as `NAVIGATE` can change focus in the parent. +- `actionCreators` - Optional object containing a list of action creators, such as `push`, `pop` etc. These will be used to add helper methods to the `navigation` object to dispatch those actions. + +:::info + +The functions in the router object should be pure functions, i.e. they should not have any side-effects, mutate parameters or external variables, and should return the same output for the same input. + +::: + +Example: + +```js +const router = { + type: 'tab', + + getInitialState({ routeNames, routeParamList }) { + const index = + options.initialRouteName === undefined + ? 0 + : routeNames.indexOf(options.initialRouteName); + + return { + stale: false, + type: 'tab', + key: shortid(), + index, + routeNames, + routes: routeNames.map(name => ({ + name, + key: name, + params: routeParamList[name], + })), + }; + }, + + getRehydratedState(partialState, { routeNames, routeParamList }) { + const state = partialState; + + if (state.stale === false) { + return state as NavigationState; + } + + const routes = state.routes + .filter(route => routeNames.includes(route.name)) + .map( + route => + ({ + ...route, + key: route.key || `${route.name}-${shortid()}`, + params: + routeParamList[route.name] !== undefined + ? { + ...routeParamList[route.name], + ...route.params, + } + : route.params, + } as Route) + ); + + return { + stale: false, + type: 'tab', + key: shortid(), + index: + typeof state.index === 'number' && state.index < routes.length + ? state.index + : 0, + routeNames, + routes, + }; + }, + + getStateForRouteNamesChange(state, { routeNames }) { + const routes = state.routes.filter(route => + routeNames.includes(route.name) + ); + + return { + ...state, + routeNames, + routes, + index: Math.min(state.index, routes.length - 1), + }; + }, + + getStateForRouteFocus(state, key) { + const index = state.routes.findIndex(r => r.key === key); + + if (index === -1 || index === state.index) { + return state; + } + + return { ...state, index }; + }, + + getStateForAction(state, action) { + switch (action.type) { + case 'NAVIGATE': { + const index = state.routes.findIndex( + route => route.name === action.payload.name + ); + + if (index === -1) { + return null; + } + + return { ...state, index }; + } + + default: + return BaseRouter.getStateForAction(state, action); + } + }, + + shouldActionChangeFocus() { + return false; + }, +}; + +const SimpleRouter = () => router; + +export default SimpleRouter; +``` + +## Built-In Routers + +The library ships with a few standard routers: + +- `StackRouter` +- `TabRouter` +- `DrawerRouter` + +## Customizing Routers + +There are two main ways to customize routers: + +- 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 + +Let's say you want to add a custom action to clear the history: + +```js +import { TabRouter } from '@react-navigation/native'; + +const MyTabRouter = (options) => { + const router = TabRouter(options); + + return { + ...router, + getStateForAction(state, action, options) { + switch (action.type) { + case 'CLEAR_HISTORY': + return { + ...state, + routeKeyHistory: [], + }; + default: + return router.getStateForAction(state, action, options); + } + }, + + actionCreators: { + ...router.actionCreators, + clearHistory() { + return { type: 'CLEAR_HISTORY' }; + }, + }, + }; +}; +``` + +Instead of writing a custom router to handle custom actions, you can [pass a function to `dispatch`](navigation-object.md#dispatch) instead. It's cleaner and recommended instead of overriding routers. + +### Blocking Navigation Actions + +Sometimes you may want to prevent some navigation activity, depending on your route. Let's say, you want to prevent pushing a new screen if `isEditing` is `true`: + +```js +import { StackRouter } from '@react-navigation/native'; + +const MyStackRouter = (options) => { + const router = StackRouter(options); + + return { + ...router, + getStateForAction(state, action, options) { + const result = router.getStateForAction(state, action, options); + + if ( + result != null && + result.index > state.index && + state.routes[state.index].params?.isEditing + ) { + // Returning the current state means that the action has been handled, but we don't have a new state + return state; + } + + return result; + }, + }; +}; +``` + +If you want to prevent going back, the recommended approach is to use the [`usePreventRemove` hook](preventing-going-back.md). diff --git a/versioned_docs/version-8.x/customizing-bottom-tabs.md b/versioned_docs/version-8.x/customizing-bottom-tabs.md new file mode 100755 index 0000000000..4f17f068c5 --- /dev/null +++ b/versioned_docs/version-8.x/customizing-bottom-tabs.md @@ -0,0 +1,290 @@ +--- +id: customizing-tabbar +title: Customizing bottom tab bar +sidebar_label: Customizing tab bar +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +This guide covers customizing the tab bar in [`createBottomTabNavigator`](bottom-tab-navigator.md). Make sure to install and configure the library according to the [installation instructions](bottom-tab-navigator.md#installation) first. + +## Add icons for each tab + +This is similar to how you would customize a stack navigator — there are some properties that are set when you initialize the tab navigator and others that can be customized per-screen in `options`. + + + + +```js name="Tab bar icons" snack dependencies=@expo/vector-icons,@expo/vector-icons/Ionicons +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +// codeblock-focus-start +// You can import Ionicons from @expo/vector-icons/Ionicons if you use Expo or +// react-native-vector-icons/Ionicons otherwise. +import Ionicons from 'react-native-vector-icons/Ionicons'; + +// codeblock-focus-end + +function HomeScreen() { + return ( + + Home! + + ); +} + +function SettingsScreen() { + return ( + + Settings! + + ); +} + +// codeblock-focus-start +const RootTabs = createBottomTabNavigator({ + screenOptions: ({ route }) => ({ + // highlight-start + tabBarIcon: ({ focused, color, size }) => { + let iconName; + + if (route.name === 'Home') { + iconName = focused + ? 'ios-information-circle' + : 'ios-information-circle-outline'; + } else if (route.name === 'Settings') { + iconName = focused ? 'ios-list' : 'ios-list-outline'; + } + + // You can return any component that you like here! + return ; + }, + // highlight-end + tabBarActiveTintColor: 'tomato', + tabBarInactiveTintColor: 'gray', + }), + screens: { + Home: HomeScreen, + Settings: SettingsScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(RootTabs); + +export default function App() { + return ; +} +``` + + + + +```js name="Tab based navigation" snack dependencies=@expo/vector-icons,@expo/vector-icons/Ionicons +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +// codeblock-focus-start +// You can import Ionicons from @expo/vector-icons/Ionicons if you use Expo or +// react-native-vector-icons/Ionicons otherwise. +import Ionicons from 'react-native-vector-icons/Ionicons'; + +// codeblock-focus-end + +function HomeScreen() { + return ( + + Home! + + ); +} + +function SettingsScreen() { + return ( + + Settings! + + ); +} + +const Tab = createBottomTabNavigator(); + +// codeblock-focus-start +function RootTabs() { + return ( + ({ + // highlight-start + tabBarIcon: ({ focused, color, size }) => { + let iconName; + + if (route.name === 'Home') { + iconName = focused + ? 'ios-information-circle' + : 'ios-information-circle-outline'; + } else if (route.name === 'Settings') { + iconName = focused ? 'ios-list' : 'ios-list-outline'; + } + + // You can return any component that you like here! + return ; + }, + // highlight-end + tabBarActiveTintColor: 'tomato', + tabBarInactiveTintColor: 'gray', + })} + > + + + + ); +} +// codeblock-focus-end + +export default function App() { + return ( + + + + ); +} +``` + + + + +Let's dissect this: + +- `tabBarIcon` is a supported option in bottom tab navigator. So we know we can use it on our screen components in the `options` prop, but in this case chose to put it in the `screenOptions` prop of `Tab.Navigator` in order to centralize the icon configuration for convenience. +- `tabBarIcon` is a function that is given the `focused` state, `color`, and `size` params. If you take a peek further down in the configuration you will see `tabBarActiveTintColor` and `tabBarInactiveTintColor`. These default to the iOS platform defaults, but you can change them here. The `color` that is passed through to the `tabBarIcon` is either the active or inactive one, depending on the `focused` state (focused is active). The `size` is the size of the icon expected by the tab bar. +- Read the [full API reference](bottom-tab-navigator.md) for further information on `createBottomTabNavigator` configuration options. + +## Add badges to icons + +Sometimes we want to add badges to some icons. You can use the [`tabBarBadge` option](bottom-tab-navigator.md#tabbarbadge) to do it: + + + + +```js name="Tab based navigation" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; + +function HomeScreen() { + return ( + + Home! + + ); +} + +function SettingsScreen() { + return ( + + Settings! + + ); +} + +// codeblock-focus-start +const RootTabs = createBottomTabNavigator({ + screens: { + Home: { + screen: HomeScreen, + options: { + // highlight-start + tabBarBadge: 3, + // highlight-end + }, + }, + Settings: SettingsScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(RootTabs); + +export default function App() { + return ; +} +``` + + + + +```js name="Tab based navigation" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; + +function HomeScreen() { + return ( + + Home! + + ); +} + +function SettingsScreen() { + return ( + + Settings! + + ); +} + +const Tab = createBottomTabNavigator(); + +// codeblock-focus-start +function RootTabs() { + return ( + + + + + ); +} +// codeblock-focus-end + +export default function App() { + return ( + + + + ); +} +``` + + + + +From UI perspective this component is ready to use, but you still need to find some way to pass down the badge count properly from somewhere else, like using [React Context](https://react.dev/reference/react/useContext), [Redux](https://redux.js.org/), [MobX](https://mobx.js.org/) or [event emitters](https://github.com/facebook/react-native/blob/master/Libraries/vendor/emitter/EventEmitter.js). + +You can also update the badge from within the screen component by using the `setOptions` method: + +```js +const navigation = useNavigation(); + +React.useEffect(() => { + navigation.setOptions({ + tabBarBadge: unreadMessagesCount, + }); +}, [navigation, unreadMessagesCount]); +``` + +![Tabs with badges](/assets/navigators/tabs/tabs-badges.png) diff --git a/versioned_docs/version-8.x/deep-linking.md b/versioned_docs/version-8.x/deep-linking.md new file mode 100755 index 0000000000..2dcac6d9f9 --- /dev/null +++ b/versioned_docs/version-8.x/deep-linking.md @@ -0,0 +1,517 @@ +--- +id: deep-linking +title: Deep linking +sidebar_label: Deep linking +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +This guide will describe how to configure your app to handle deep links on various platforms. To handle incoming links, you need to handle 2 scenarios: + +1. If the app wasn't previously open, the deep link needs to set the initial state +2. If the app was already open, the deep link needs to update the state to reflect the incoming link + +React Native provides a [`Linking`](https://reactnative.dev/docs/linking) to get notified of incoming links. React Navigation can integrate with the `Linking` module to automatically handle deep links. On Web, React Navigation can integrate with browser's `history` API to handle URLs on client side. See [configuring links](configuring-links.md) to see more details on how to configure links in React Navigation. + +While you don't need to use the `linking` prop from React Navigation, and can handle deep links yourself by using the `Linking` API and navigating from there, it'll be significantly more complicated than using the `linking` prop which handles many edge cases for you. So we don't recommend implementing it by yourself. + +Below, we'll go through required configurations so that the deep link integration works. + +## Setting up deep links + + + + +### 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": "example" + } +} +``` + +Next, install `expo-linking` which we'd need to get the deep link prefix: + +```bash +npx expo install expo-linking +``` + +Then you can use `Linking.createURL` to get the prefix for your app: + +```js +const linking = { + prefixes: [Linking.createURL('/'), +}; +``` + +See more details below at [Configuring React Navigation](#configuring-react-navigation). + +
+Why use `Linking.createURL`? + +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. + +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 +const linking = { + prefixes: [Linking.createURL('/'), 'https://app.example.com'], +}; +``` + +### Universal Links on iOS + +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: + +```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. + +See [Expo's documentation on iOS Universal Links](https://docs.expo.dev/linking/ios-universal-links/) for more details. + +### App Links on Android + +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`: + +```json +{ + "expo": { + "android": { + "intentFilters": [ + { + "action": "VIEW", + "autoVerify": true, + "data": [ + { + "scheme": "https", + "host": "app.example.com" + } + ], + "category": ["BROWSABLE", "DEFAULT"] + } + ] + } + } +} +``` + +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 `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: + + + + +```swift +func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + return RCTLinkingManager.application(app, open: url, options: options) +} +``` + + + + +```objc +#import + +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + options:(NSDictionary *)options +{ + return [RCTLinkingManager application:application openURL:url options:options]; +} +``` + + + + +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 +- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity + restorationHandler:(nonnull void (^)(NSArray> * _Nullable))restorationHandler +{ + return [RCTLinkingManager application:application + continueUserActivity:userActivity + restorationHandler:restorationHandler]; +} +``` + + + + +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 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 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. + +#### Hybrid React Native and native iOS Applications + +If you're using React Navigation within a hybrid app - an iOS app that has both Swift/ObjC and React Native parts - you may be missing the `RCTLinkingIOS` subspec in your `Podfile`, which is installed by default in new React Native projects. To add this, ensure your `Podfile` looks like the following: + +```pod + pod 'React', :path => '../node_modules/react-native', :subspecs => [ + . . . // other subspecs + 'RCTLinkingIOS', + . . . + ] +``` + +### Setup on Android + +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 example --android`. + +If you want to add it manually, open up `SimpleApp/android/app/src/main/AndroidManifest.xml`, and make the following adjustments: + +1. Set `launchMode` of `MainActivity` to `singleTask` in order to receive intent on existing `MainActivity` (this is the default, so you may not need to actually change anything). +2. Add the new [`intent-filter`](http://developer.android.com/training/app-indexing/deep-linking.html#adding-filters) inside the `MainActivity` entry with a `VIEW` type action: + +```xml + + + + + + + + + + + + +``` + +Similar to Universal Links on iOS, you can also use a domain to associate the app with your website on Android by [verifying Android App Links](https://developer.android.com/training/app-links/verify-android-applinks). First, you need to configure your `AndroidManifest.xml`: + +1. Add `android:autoVerify="true"` to your `` entry. +2. Add your domain's `scheme` and `host` in a new `` entry inside the ``. + +After adding them, it should look like this: + +```xml + + + + + + + + + + + + + + + + + + + + +``` + +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. + +If you're testing on iOS, run: + +```bash +npx react-native run-ios +``` + +If you're testing on Android, run: + +```bash +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`, 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. + +### Testing with `npx uri-scheme` + +The `uri-scheme` package is a command line tool that can be used to test deep links on both iOS & Android. It can be used as follows: + +```bash +npx uri-scheme open [your deep link] --[ios|android] +``` + +For example: + +```bash +npx uri-scheme open "example://chat/jane" --ios +``` + +Or if using Expo client: + +```bash +npx uri-scheme open "exp://127.0.0.1:19000/--/chat/jane" --ios +``` + +### Testing with `xcrun` on iOS + +The `xcrun` command can be used as follows to test deep links with the iOS simulator: + +```bash +xcrun simctl openurl booted [your deep link] +``` + +For example: + +```bash +xcrun simctl openurl booted "example://chat/jane" +``` + +### Testing with `adb` on Android + +The `adb` command can be used as follows to test deep links with the Android emulator or a connected device: + +```bash +adb shell am start -W -a android.intent.action.VIEW -d [your deep link] [your android package name] +``` + +For example: + +```bash +adb shell am start -W -a android.intent.action.VIEW -d "example://chat/jane" com.simpleapp +``` + +Or if using Expo client: + +```bash +adb shell am start -W -a android.intent.action.VIEW -d "exp://127.0.0.1:19000/--/chat/jane" host.exp.exponent +``` + +## 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. + +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="Expo Notifications" +const linking = { + prefixes: ['example://', 'https://app.example.com'], + + // Custom function to get the URL which was used to open the app + async getInitialURL() { + // First, handle deep links + const url = await Linking.getInitialURL(); + + if (url != null) { + return url; + } + + // Handle URL from expo push notifications + const response = await Notifications.getLastNotificationResponseAsync(); + + return response?.notification.request.content.data.url; + }, + + // Custom function to subscribe to incoming links + subscribe(listener) { + // 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 + linkingSubscription.remove(); + pushNotificationSubscription.remove(); + }; + }, +}; +``` + + + + +```js name="Expo Notifications" +const linking = { + prefixes: ['example://', 'https://app.example.com'], + + // Custom function to get the URL which was used to open the app + async getInitialURL() { + // First, handle deep links + const url = await Linking.getInitialURL(); + + if (url != null) { + return url; + } + + // Handle URL from expo push notifications + const response = await Notifications.getLastNotificationResponseAsync(); + + return response?.notification.request.content.data.url; + }, + + // Custom function to subscribe to incoming links + subscribe(listener) { + // 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 + linkingSubscription.remove(); + pushNotificationSubscription.remove(); + }; + }, + + config: { + // Deep link configuration + }, +}; +``` + + + + +Similar to the above example, you can integrate any API that provides a way to get the initial URL and to subscribe to new incoming URLs using the `getInitialURL` and `subscribe` options. diff --git a/versioned_docs/version-8.x/devtools.md b/versioned_docs/version-8.x/devtools.md new file mode 100644 index 0000000000..692ae42369 --- /dev/null +++ b/versioned_docs/version-8.x/devtools.md @@ -0,0 +1,141 @@ +--- +id: devtools +title: Developer tools +sidebar_label: Developer tools +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Developer tools to make debugging easier when using React Navigation. + +To use the developer tools, install [`@react-navigation/devtools`](https://github.com/react-navigation/react-navigation/tree/master/packages/devtools): + +```bash npm2yarn +npm install @react-navigation/devtools +``` + +Hooks from this package only work during development and are disabled in production. You don't need to do anything special to remove them from the production build. + +## API Definition + +The package exposes the following APIs: + +### `useLogger` + +This hook provides a logger for React Navigation. It logs the navigation state and actions to the console. + + + +**Usage:** + +To use the hook, import it and pass a `ref` to the `NavigationContainer` as its argument: + + + + +```js +import * as React from 'react'; +import { + createStaticNavigation, + useNavigationContainerRef, +} from '@react-navigation/native'; +import { useLogger } from '@react-navigation/devtools'; + +/* content */ + +export default function App() { + const navigationRef = useNavigationContainerRef(); + + useLogger(navigationRef); + + return ; +} +``` + + + + + +```js +import * as React from 'react'; +import { + NavigationContainer, + useNavigationContainerRef, +} from '@react-navigation/native'; +import { useLogger } from '@react-navigation/devtools'; + +export default function App() { + const navigationRef = useNavigationContainerRef(); + + useLogger(navigationRef); + + return ( + {/* ... */} + ); +} +``` + + + + +### `useReduxDevToolsExtension` + +This hook provides integration with [Redux DevTools Extension](https://github.com/reduxjs/redux-devtools). It also works with [`React Native Debugger app`](https://github.com/jhen0409/react-native-debugger) which includes this extension. + +**Usage:** + +To use the hook, import it and pass a `ref` to the `NavigationContainer` as its argument: + + + + +```js +import * as React from 'react'; +import { + createStaticNavigation, + useNavigationContainerRef, +} from '@react-navigation/native'; +import { useReduxDevToolsExtension } from '@react-navigation/devtools'; + +/* content */ + +export default function App() { + const navigationRef = useNavigationContainerRef(); + + useReduxDevToolsExtension(navigationRef); + + return ; +} +``` + + + + + +```js +import * as React from 'react'; +import { + NavigationContainer, + useNavigationContainerRef, +} from '@react-navigation/native'; +import { useReduxDevToolsExtension } from '@react-navigation/devtools'; + +export default function App() { + const navigationRef = useNavigationContainerRef(); + + useReduxDevToolsExtension(navigationRef); + + return ( + {/* ... */} + ); +} +``` + + + + +Now, you'll be able to see logs from React Navigation in Redux DevTools Extension, e.g. when you're debugging your app with React Native Debugger app. diff --git a/versioned_docs/version-8.x/drawer-actions.md b/versioned_docs/version-8.x/drawer-actions.md new file mode 100755 index 0000000000..ed8e3d62c5 --- /dev/null +++ b/versioned_docs/version-8.x/drawer-actions.md @@ -0,0 +1,250 @@ +--- +id: drawer-actions +title: DrawerActions reference +sidebar_label: DrawerActions +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +`DrawerActions` is an object containing methods for generating actions specific to drawer-based navigators. Its methods expand upon the actions available in [CommonActions](navigation-actions.md). + +The following actions are supported: + +### openDrawer + +The `openDrawer` action can be used to open the drawer pane. + +```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, +} from '@react-navigation/native'; +import { createDrawerNavigator } from '@react-navigation/drawer'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home! + + + ); +} + +const MyDrawer = createDrawerNavigator({ + screens: { + Home: HomeScreen, + }, +}); + +const Navigation = createStaticNavigation(MyDrawer); + +export default function App() { + return ; +} +``` + +### closeDrawer + +The `closeDrawer` action can be used to close the drawer pane. + +```js name="Drawer Actions - closeDrawer" 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'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home! + + + ); +} + +function CustomDrawerContent(props) { + const { navigation } = props; + + return ( + + + { + // codeblock-focus-start + navigation.dispatch(DrawerActions.closeDrawer()); + // codeblock-focus-end + }} + /> + + ); +} + +const MyDrawer = createDrawerNavigator({ + drawerContent: (props) => , + screens: { + Home: HomeScreen, + }, +}); + +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 - toggleDrawer" 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'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home! + + + ); +} + +function CustomDrawerContent(props) { + const { navigation } = props; + + return ( + + + { + // codeblock-focus-start + navigation.dispatch(DrawerActions.toggleDrawer()); + // codeblock-focus-end + }} + /> + + ); +} + +const MyDrawer = createDrawerNavigator({ + drawerContent: (props) => , + screens: { + Home: HomeScreen, + }, +}); + +const Navigation = createStaticNavigation(MyDrawer); + +export default function App() { + return ; +} +``` + +### jumpTo + +The `jumpTo` action can be used to jump to an existing route in the drawer navigator. + +- `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 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 } from '@react-navigation/drawer'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home! + + + ); +} + +function ProfileScreen({ route }) { + return ( + + Profile! + {route?.params?.user ? route.params.user : 'Noone'}'s profile + + ); +} + +const MyDrawer = createDrawerNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(MyDrawer); + +export default function App() { + return ; +} +``` diff --git a/versioned_docs/version-8.x/drawer-based-navigation.md b/versioned_docs/version-8.x/drawer-based-navigation.md new file mode 100755 index 0000000000..d760ff5d33 --- /dev/null +++ b/versioned_docs/version-8.x/drawer-based-navigation.md @@ -0,0 +1,627 @@ +--- +id: drawer-based-navigation +title: Drawer navigation +sidebar_label: Drawer navigation +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Common pattern in navigation is to use drawer from left (sometimes right) side for navigating between screens. + + + +Before continuing, first install and configure [`@react-navigation/drawer`](https://github.com/react-navigation/react-navigation/tree/main/packages/drawer) and its dependencies following the [installation instructions](drawer-navigator.md#installation). + +## Minimal example of drawer-based navigation + +To use this drawer navigator, import it from `@react-navigation/drawer`: +(swipe right to open) + + + + +```js name="Drawer navigation" snack +import * as React from 'react'; +import { View } from 'react-native'; +import { createDrawerNavigator } from '@react-navigation/drawer'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +function NotificationsScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +const Drawer = createDrawerNavigator({ + screens: { + Home: HomeScreen, + Notifications: NotificationsScreen, + }, +}); + +const Navigation = createStaticNavigation(Drawer); + +export default function App() { + return ; +} +``` + + + + +```js name="Drawer navigation" snack +import * as React from 'react'; +import { View } from 'react-native'; +import { createDrawerNavigator } from '@react-navigation/drawer'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +function NotificationsScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +const Drawer = createDrawerNavigator(); + +export default function App() { + return ( + + + + + + + ); +} +``` + + + + +## Opening and closing drawer + +To open and close drawer, use the following helpers: + + + +```js name="Drawer open and close" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { + useNavigation, + createStaticNavigation, +} from '@react-navigation/native'; +import { + createDrawerNavigator, + DrawerContentScrollView, + DrawerItemList, + DrawerItem, +} from '@react-navigation/drawer'; +import { Button } from '@react-navigation/elements'; + +function Feed() { + const navigation = useNavigation(); + + return ( + + Feed Screen + // codeblock-focus-start + + // codeblock-focus-end + + + ); +} + +function Notifications() { + return ( + + Notifications Screen + + ); +} + +// codeblock-focus-start + +/* content */ + +// codeblock-focus-end + +function CustomDrawerContent(props) { + return ( + + + // codeblock-focus-start + props.navigation.closeDrawer()} + /> + // codeblock-focus-end + props.navigation.toggleDrawer()} + /> + + ); +} + +const Drawer = createDrawerNavigator({ + drawerContent: (props) => , + screens: { + Feed: Feed, + Notifications: Notifications, + }, +}); + +const Navigation = createStaticNavigation(Drawer); + +export default function App() { + return ; +} +``` + + + + +```js name="Drawer open and close" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { + createDrawerNavigator, + DrawerContentScrollView, + DrawerItemList, + DrawerItem, +} from '@react-navigation/drawer'; +import { Button } from '@react-navigation/elements'; + +function Feed() { + const navigation = useNavigation(); + + return ( + + Feed Screen + // codeblock-focus-start + + // codeblock-focus-end + + + ); +} + +function Notifications() { + return ( + + Notifications Screen + + ); +} + +// codeblock-focus-start + +/* content */ + +// codeblock-focus-end + +function CustomDrawerContent(props) { + return ( + + + // codeblock-focus-start + props.navigation.closeDrawer()} + /> + // codeblock-focus-end + props.navigation.toggleDrawer()} + /> + + ); +} + +const Drawer = createDrawerNavigator(); + +function MyDrawer() { + return ( + } + > + + + + ); +} + +export default function App() { + return ( + + + + ); +} +``` + + + + +If you would like to toggle the drawer you call the following: + + + + +```js name="Drawer toggle" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { + useNavigation, + createStaticNavigation, +} from '@react-navigation/native'; +import { + createDrawerNavigator, + DrawerContentScrollView, + DrawerItemList, + DrawerItem, +} from '@react-navigation/drawer'; +import { Button } from '@react-navigation/elements'; + +function Feed() { + const navigation = useNavigation(); + + return ( + + Feed Screen + + // codeblock-focus-start + + // codeblock-focus-end + + ); +} + +function Notifications() { + return ( + + Notifications Screen + + ); +} + +function CustomDrawerContent(props) { + return ( + + + props.navigation.closeDrawer()} + /> + props.navigation.toggleDrawer()} + /> + + ); +} + +const Drawer = createDrawerNavigator({ + drawerContent: (props) => , + screens: { + Feed: Feed, + Notifications: Notifications, + }, +}); + +const Navigation = createStaticNavigation(Drawer); + +export default function App() { + return ; +} +``` + + + + +```js name="Drawer toggle" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { + createDrawerNavigator, + DrawerContentScrollView, + DrawerItemList, + DrawerItem, +} from '@react-navigation/drawer'; +import { Button } from '@react-navigation/elements'; + +function Feed() { + const navigation = useNavigation(); + + return ( + + Feed Screen + + // codeblock-focus-start + + // codeblock-focus-end + + ); +} + +function Notifications() { + return ( + + Notifications Screen + + ); +} + +function CustomDrawerContent(props) { + return ( + + + props.navigation.closeDrawer()} + /> + props.navigation.toggleDrawer()} + /> + + ); +} + +const Drawer = createDrawerNavigator(); + +function MyDrawer() { + return ( + } + > + + + + ); +} + +export default function App() { + return ( + + + + ); +} +``` + + + + +Each of these functions, behind the scenes, are simply dispatching actions: + + + + +```js name="Navigation dispatcher" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { + createStaticNavigation, + useNavigation, + DrawerActions, +} from '@react-navigation/native'; +import { + createDrawerNavigator, + DrawerContentScrollView, + DrawerItemList, + DrawerItem, +} from '@react-navigation/drawer'; +import { Button } from '@react-navigation/elements'; + +function Feed() { + const navigation = useNavigation(); + + return ( + + Feed Screen + // codeblock-focus-start + + // codeblock-focus-end + + + ); +} + +function Notifications() { + return ( + + Notifications Screen + + ); +} + +// codeblock-focus-start + +/* content */ + +// codeblock-focus-end + +function CustomDrawerContent(props) { + return ( + + + // codeblock-focus-start + props.navigation.dispatch(DrawerActions.closeDrawer())} + /> + props.navigation.dispatch(DrawerActions.toggleDrawer())} + /> + // codeblock-focus-end + + ); +} + +const Drawer = createDrawerNavigator({ + drawerContent: (props) => , + screens: { + Feed: Feed, + Notifications: Notifications, + }, +}); + +const Navigation = createStaticNavigation(Drawer); + +export default function App() { + return ; +} +``` + + + + +```js name="Navigation dispatcher" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { + NavigationContainer, + useNavigation, + DrawerActions, +} from '@react-navigation/native'; +import { + createDrawerNavigator, + DrawerContentScrollView, + DrawerItemList, + DrawerItem, +} from '@react-navigation/drawer'; +import { Button } from '@react-navigation/elements'; + +function Feed() { + const navigation = useNavigation(); + + return ( + + Feed Screen + // codeblock-focus-start + + // codeblock-focus-end + + + ); +} + +function Notifications() { + return ( + + Notifications Screen + + ); +} +// codeblock-focus-start + +/* content */ + +// codeblock-focus-end + +function CustomDrawerContent(props) { + return ( + + + // codeblock-focus-start + props.navigation.dispatch(DrawerActions.closeDrawer())} + /> + props.navigation.dispatch(DrawerActions.toggleDrawer())} + /> + // codeblock-focus-end + + ); +} + +const Drawer = createDrawerNavigator(); + +function MyDrawer() { + return ( + } + > + + + + ); +} + +export default function App() { + return ( + + + + ); +} +``` + + + + +If you would like to determine if drawer is open or closed, you can do the following: + +```js name="Drawer hook" +import { useDrawerStatus } from '@react-navigation/drawer'; + +// ... + +const isDrawerOpen = useDrawerStatus() === 'open'; +``` diff --git a/versioned_docs/version-8.x/drawer-layout.md b/versioned_docs/version-8.x/drawer-layout.md new file mode 100644 index 0000000000..0a5d8ddb7d --- /dev/null +++ b/versioned_docs/version-8.x/drawer-layout.md @@ -0,0 +1,248 @@ +--- +id: drawer-layout +title: React Native Drawer Layout +sidebar_label: Drawer Layout +--- + +A cross-platform Drawer component for React Native implemented using [`react-native-gesture-handler`](https://docs.swmansion.com/react-native-gesture-handler/) and [`react-native-reanimated`](https://docs.swmansion.com/react-native-reanimated/) on native platforms and CSS transitions on Web. + + + +This package doesn't integrate with React Navigation. If you want to integrate the drawer layout with React Navigation's navigation system, e.g. want to show screens in the drawer and be able to navigate between them using `navigation.navigate` etc, use [Drawer Navigator](drawer-navigator.md) instead. + +## Installation + +To use this package, open a Terminal in the project root and run: + +```bash npm2yarn +npm install react-native-drawer-layout +``` + +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. + + + + +If you have a Expo managed project, in your project directory, run: + +```bash +npx expo install react-native-gesture-handler react-native-reanimated react-native-worklets +``` + + + + +If you have a bare React Native project, in your project directory, run: + +```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). + + + + +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 + +```js +import * as React from 'react'; +import { Text } from 'react-native'; +import { Drawer } from 'react-native-drawer-layout'; +import { Button } from '@react-navigation/elements'; + +export default function DrawerExample() { + const [open, setOpen] = React.useState(false); + + return ( + setOpen(true)} + onClose={() => setOpen(false)} + renderDrawerContent={() => { + return Drawer content; + }} + > + + + ); +} + +function ProfileScreen() { + const navigation = useNavigation(); + + return ( + + Profile Screen + + + ); +} + +// codeblock-focus-start +const MyDrawer = createDrawerNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyDrawer); + +export default function App() { + return ; +} +``` + +## API Definition + +### Props + +In addition to the [common props](navigator.md#configuration) shared by all navigators, the drawer navigator component 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 + +#### `defaultStatus` + +The default status of the drawer - whether the drawer should stay `open` or `closed` by default. + +When this is set to `open`, the drawer will be open from the initial render. It can be closed normally using gestures or programmatically. However, when going back, the drawer will re-open if it was closed. This is essentially the opposite of the default behavior of the drawer where it starts `closed`, and the back button closes an open drawer. + +#### `detachInactiveScreens` + +Boolean used to indicate whether inactive screens should be detached from the view hierarchy to save memory. This enables integration with [react-native-screens](https://github.com/software-mansion/react-native-screens). Defaults to `true`. + +#### `drawerContent` + +Function that returns React element to render as the content of the drawer, for example, navigation items + +The content component receives the following props by default: + +- `state` - The [navigation state](navigation-state.md) of the navigator. +- `navigation` - The navigation object for the navigator. +- `descriptors` - An descriptor object containing options for the drawer screens. The options can be accessed at `descriptors[route.key].options`. + +##### Providing a custom `drawerContent` + +The default component for the drawer is scrollable and only contains links for the routes in the RouteConfig. You can easily override the default component to add a header, footer, or other content to the drawer. The default content component is exported as `DrawerContent`. It renders a `DrawerItemList` component inside a `ScrollView`. + +By default, the drawer is scrollable and supports devices with notches. If you customize the content, you can use `DrawerContentScrollView` to handle this automatically: + +```js +import { + DrawerContentScrollView, + DrawerItemList, +} from '@react-navigation/drawer'; + +function CustomDrawerContent(props) { + return ( + + + + ); +} +``` + +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 + + ); +} + +function SettingsScreen() { + return ( + + Settings Screen + + ); +} + +// codeblock-focus-start +function CustomDrawerContent(props) { + return ( + + + Linking.openURL('https://mywebsite.com/help')} + /> + + ); +} +// 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: + +- `label` (required): The label text of the item. Can be string, or a function returning a react element. e.g. `({ focused, color }) => {focused ? 'Focused text' : 'Unfocused text'}`. +- `icon`: Icon to display for the item. Accepts a function returning a react element. e.g. `({ focused, color, size }) => `. +- `focused`: Boolean indicating whether to highlight the drawer item as active. +- `onPress` (required): Function to execute on press. +- `activeTintColor`: Color for the icon and label when the item is active. +- `inactiveTintColor`: Color for the icon and label when the item is inactive. +- `activeBackgroundColor`: Background color for item when it's active. +- `inactiveBackgroundColor`: Background color for item when it's inactive. +- `labelStyle`: Style object for the label `Text`. +- `style`: Style object for the wrapper `View`. + +Note that you **cannot** use the `useNavigation` hook inside the `drawerContent` since `useNavigation` is only available inside screens. You get a `navigation` prop for your `drawerContent` which you can use instead: + +```js +function CustomDrawerContent({ navigation }) { + return ( + + ); +} +``` + +To use the custom component, we need to pass it in the `drawerContent` prop: + +```js + }> + {/* screens */} + +``` + +### 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`. + +#### `title` + +A generic title that can be used as a fallback for `headerTitle` and `drawerLabel`. + +#### `lazy` + +Whether this screen should render the first time it's accessed. Defaults to `true`. Set it to `false` if you want to render the screen on initial render. + +#### `drawerLabel` + +String or a function that given `{ focused: boolean, color: string }` returns a React.Node, to display in drawer sidebar. When undefined, scene `title` is used. + +#### `drawerIcon` + +Function, that given `{ focused: boolean, color: string, size: number }` returns a React.Node to display in drawer sidebar. + +#### `drawerActiveTintColor` + +Color for the icon and label in the active item in the drawer. + +Drawer active tint color + +```js + drawerActiveTintColor: 'green', +``` + +#### `drawerActiveBackgroundColor` + +Background color for the active item in the drawer. + +Drawer active background color + +```js + screenOptions={{ + drawerActiveTintColor: 'white', + drawerActiveBackgroundColor: '#003CB3', + drawerLabelStyle: { + color: 'white', + }, + }} +``` + +#### `drawerInactiveTintColor` + +Color for the icon and label in the inactive items in the drawer. + +#### `drawerInactiveBackgroundColor` + +Background color for the inactive items in the drawer. + +#### `drawerItemStyle` + +Style object for the single item, which can contain an icon and/or a label. + +Drawer item style + +Example: + +```js + drawerItemStyle: { + backgroundColor: '#9dd3c8', + borderColor: 'black', + orderWidth: 2, + opacity: 0.6, + }, +``` + +#### `drawerLabelStyle` + +Style object to apply to the `Text` style inside content section which renders a label. + +Drawer label style + +Example: + +```js + drawerLabelStyle: { + color: 'black', + fontSize: 20, + fontFamily: 'Georgia', + }, +``` + +#### `drawerContentContainerStyle` + +Style object for the content section inside the `ScrollView`. + +#### `drawerContentStyle` + +Style object for the wrapper view. + +#### `drawerStyle` + +Style object for the drawer component. You can pass a custom background color for a drawer or a custom width here. + +Drawer style + +```js + + {/* screens */} + +``` + +#### `drawerPosition` + +Options are `left` or `right`. Defaults to `left` for LTR languages and `right` for RTL languages. + +#### `drawerType` + +Type of the drawer. It determines how the drawer looks and animates. + +- `front`: Traditional drawer which covers the screen with an overlay behind it. + + +- `back`: The drawer is revealed behind the screen on swipe. + + +- `slide`: Both the screen and the drawer slide on swipe to reveal the drawer. + + +- `permanent`: A permanent drawer is shown as a sidebar. Useful for having always visible drawer on larger screens. + +Defaults to `slide` on iOS and `front` on other platforms. + +You can conditionally specify the `drawerType` to show a permanent drawer on bigger screens and a traditional drawer drawer on small screens: + +```js +import { useWindowDimensions } from 'react-native'; +import { createDrawerNavigator } from '@react-navigation/drawer'; + +const Drawer = createDrawerNavigator(); + +function MyDrawer() { + const dimensions = useWindowDimensions(); + + return ( + = 768 ? 'permanent' : 'front', + }} + > + {/* Screens */} + + ); +} +``` + +You can also specify other props such as `drawerStyle` based on screen size to customize the behavior. For example, you can combine it with `defaultStatus="open"` to achieve a master-detail layout: + + + +```js +import { useWindowDimensions } from 'react-native'; +import { createDrawerNavigator } from '@react-navigation/drawer'; + +const Drawer = createDrawerNavigator(); + +function MyDrawer() { + const dimensions = useWindowDimensions(); + + const isLargeScreen = dimensions.width >= 768; + + return ( + + {/* Screens */} + + ); +} +``` + +#### `drawerHideStatusBarOnOpen` + +When set to `true`, Drawer will hide the OS status bar whenever the drawer is pulled or when it's in an "open" state. + +#### `drawerStatusBarAnimation` + +Animation of the statusbar when hiding it. use in combination with `drawerHideStatusBarOnOpen`. + +This is only supported on iOS. Defaults to `slide`. + +Supported values: + +- `slide` + + +- `fade` + + +- `none` + +#### `overlayColor` + +Color overlay to be displayed on top of the content view when drawer gets open. The opacity is animated from `0` to `1` when the drawer opens. + + + +#### `sceneStyle` + +Style object for the component wrapping the screen content. + +#### `configureGestureHandler` + +Callback to configure the underlying [gesture from `react-native-gesture-handler`](https://docs.swmansion.com/react-native-gesture-handler/docs/gestures/gesture). It receives the `gesture` object as an argument: + +```js +configureGestureHandler: ({ gesture }) => { + return gesture.enableTrackpadTwoFingerGesture(false); +}, +``` + +This is not supported on Web. + +#### `swipeEnabled` + +Whether you can use swipe gestures to open or close the drawer. Defaults to `true`. + +Swipe gesture is not supported on Web. + +#### `swipeEdgeWidth` + +Allows for defining how far from the edge of the content view the swipe gesture should activate. + +This is not supported on Web. + +#### `swipeMinDistance` + +Minimum swipe distance threshold that should activate opening the drawer. + +#### `keyboardDismissMode` + +Whether the keyboard should be dismissed when the swipe gesture begins. Defaults to `'on-drag'`. Set to `'none'` to disable keyboard handling. + +#### `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. + +#### `popToTopOnBlur` + +Boolean indicating whether any nested stack should be popped to the top of the stack when navigating away from this drawer screen. 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 drawer navigator. + +### 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. + +In addition to those, the following options are also supported in drawer: + +#### `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 +- `layout` - Dimensions of the screen, contains `height` and `width` properties. + +Example: + +```js +import { getHeaderTitle } from '@react-navigation/elements'; + +// .. + +header: ({ navigation, route, options }) => { + const title = getHeaderTitle(options, route.name); + + return ; +}; +``` + +To set a custom header for all the screens in the navigator, you can specify this option in the `screenOptions` prop of the navigator. + +##### Specify a `height` in `headerStyle` + +If your custom header's height differs from the default header height, then you might notice glitches due to measurement being async. Explicitly specifying the height will avoid such glitches. + +Example: + +```js +headerStyle: { + height: 80, // Specify the height of your custom header +}; +``` + +Note that this style is not applied to the header by default since you control the styling of your custom header. If you also want to apply this style to your header, use `options.headerStyle` from the props. + +#### `headerShown` + +Whether to show or hide the header for the screen. The header is shown by default. Setting this to `false` hides the header. + +### Events + +The navigator can [emit events](navigation-events.md) on certain actions. Supported events are: + +#### `drawerItemPress` + +This event is fired when the user presses the button for the screen in the drawer. By default a drawer item press does several things: + +- If the screen is not focused, drawer item press will focus that screen +- If the screen is already focused, then it'll close the drawer + +To prevent the default behavior, you can call `event.preventDefault`: + +```js +React.useEffect(() => { + const unsubscribe = navigation.addListener('drawerItemPress', (e) => { + // Prevent default behavior + e.preventDefault(); + + // Do something manually + // ... + }); + + return unsubscribe; +}, [navigation]); +``` + +If you have custom drawer content, make sure to emit this event. + +### Helpers + +The drawer navigator adds the following methods to the navigation object: + +#### `openDrawer` + +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'; + +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(); +``` + +#### `toggleDrawer` + +Opens the drawer pane if closed, closes the drawer pane if opened. + +```js +navigation.toggleDrawer(); +``` + +#### `jumpTo` + +Navigates to an existing screen in the drawer navigator. The method accepts the following arguments: + +- `name` - _string_ - Name of the route to jump to. +- `params` - _object_ - Screen params to pass to the destination route. + +```js name="Drawer 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 { createDrawerNavigator } from '@react-navigation/drawer'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function ProfileScreen({ route }) { + return ( + + Profile Screen + {route.params?.owner && ( + Owner: {route.params.owner} + )} + + ); +} + +const MyDrawer = createDrawerNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(MyDrawer); + +export default function App() { + return ; +} +``` + +### 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 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'; + +function HomeScreen() { + // highlight-next-line + const progress = useDrawerProgress(); + + const animatedStyle = useAnimatedStyle(() => ({ + transform: [{ translateX: progress.value * -100 }], + })); + + return ( + + + + ); +} +// codeblock-focus-end + +const MyDrawer = createDrawerNavigator({ + screens: { + Home: HomeScreen, + }, +}); + +const Navigation = createStaticNavigation(MyDrawer); + +export default function App() { + return ; +} +``` + +If you are using class components, you can use the `DrawerProgressContext` to get the progress value. + +:::warning + +The `useDrawerProgress` hook (or `DrawerProgressContext`) will return a mock value on Web since Reanimated is not used on Web. The mock value can only represent the open state of the drawer (`0` when closed, `1` when open), and not the progress of the drawer. + +::: + +#### `useDrawerStatus` + +You can check if the drawer is open by using the `useDrawerStatus` hook. + +```js +import { useDrawerStatus } from '@react-navigation/drawer'; + +// ... + +const isDrawerOpen = useDrawerStatus() === 'open'; +``` + +If you can't use the hook, you can also use the `getDrawerStatusFromState` helper: + +```js +import { getDrawerStatusFromState } from '@react-navigation/drawer'; + +// ... + +const isDrawerOpen = getDrawerStatusFromState(navigation.getState()) === 'open'; +``` + +For class components, you can listen to the `state` event to check if the drawer was opened or closed: + +```js +class Profile extends React.Component { + componentDidMount() { + this._unsubscribe = navigation.addListener('state', () => { + const isDrawerOpen = + getDrawerStatusFromState(navigation.getState()) === 'open'; + + // do something + }); + } + + componentWillUnmount() { + this._unsubscribe(); + } + + render() { + // Content of the component + } +} +``` + +## Nesting drawer navigators inside others + +If a drawer navigator is nested inside of another navigator that provides some UI, for example, a tab navigator or stack navigator, then the drawer will be rendered below the UI from those navigators. The drawer will appear below the tab bar and below the header of the stack. You will need to make the drawer navigator the parent of any navigator where the drawer should be rendered on top of its UI. diff --git a/versioned_docs/version-8.x/elements.md b/versioned_docs/version-8.x/elements.md new file mode 100644 index 0000000000..688f45b88b --- /dev/null +++ b/versioned_docs/version-8.x/elements.md @@ -0,0 +1,847 @@ +--- +id: elements +title: Elements Library +sidebar_label: Elements +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +A component library containing the UI elements and helpers used in React Navigation. It can be useful if you're building your own navigator, or want to reuse a default functionality in your app. + +## Installation + +To use this package, ensure that you have [`@react-navigation/native` and its dependencies (follow this guide)](getting-started.md), then install [`@react-navigation/elements`](https://github.com/react-navigation/react-navigation/tree/main/packages/elements): + +```bash npm2yarn +npm install @react-navigation/elements +``` + +## Components + +### `Header` + +A component that can be used as a header. This is used by all the navigators by default. + +Usage: + +```js name="React Navigation Elements Header" snack +import * as React from 'react'; +import { SafeAreaProviderCompat } from '@react-navigation/elements'; +import { NavigationContainer } from '@react-navigation/native'; +// codeblock-focus-start +import { Header } from '@react-navigation/elements'; + +function MyHeader() { + return
; +} +// codeblock-focus-end + +export default function App() { + return ( + + + + + + ); +} +``` + +To use the header in a navigator, you can use the `header` option in the screen options: + + + + +```js name="Header with Native Stack" snack +import * as React from 'react'; +import { Text, View, Button } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +// codeblock-focus-start +import { Header, getHeaderTitle } from '@react-navigation/elements'; + +// codeblock-focus-end +function HomeScreen() { + return ( + + Home Screen + + ); +} + +// codeblock-focus-start +const MyStack = createNativeStackNavigator({ + screenOptions: { + header: ({ options, route, back }) => ( +
+ ), + }, + screens: { + Home: HomeScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Header with Native Stack" snack +import * as React from 'react'; +import { Text, View, Button } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +// codeblock-focus-start +import { Header, getHeaderTitle } from '@react-navigation/elements'; + +const Stack = createNativeStackNavigator(); + +function MyStack() { + return ( + ( +
+ ), + }} + > + + + ); +} +// codeblock-focus-end + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +export default function App() { + return ( + + + + ); +} +``` + + + + +:::note + +This doesn't replicate the behavior of the header in stack and native stack navigators as the stack navigator also includes animations, and the native stack navigator header is provided by the native platform. + +::: + +It accepts the following props: + +#### `headerTitle` + +String or a function that returns a React Element to be used by the header. Defaults to scene `title`. + +When a function is specified, it receives an object containing following properties: + +- `allowFontScaling`: Whether it scale to respect Text Size accessibility settings. +- `tintColor`: Text color of the header title. +- `style`: Style object for the `Text` component. +- `children`: The title string (from `title` in `options`). + + + + +```js +const RootStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + options: { + headerTitle: ({ allowFontScaling, tintColor, style, children }) => ( + + {children} + + ), + }, + }, + }, +}); +``` + + + + +```js + ( + + {children} + + ), + }} +/> +``` + + + + +#### `headerTitleAlign` + +How to align the header title. Possible values: + +- `left` +- `center` + +Defaults to `center` on iOS and `left` on Android. + +#### `headerTitleAllowFontScaling` + +Whether header title font should scale to respect Text Size accessibility settings. Defaults to `false`. + +#### `headerLeft` + +Function which returns a React Element to display on the left side of the header. + +It receives an object containing following properties: + +- `tintColor`: The color of the icon and label. +- `pressColor`: The color of the material ripple (Android >= 5.0 only). +- `pressOpacity`: The opacity of the button when it's pressed (Android < 5.0, and iOS). +- `displayMode`: How the element displays icon and title. Defaults to `default` on iOS and `minimal` on Android. Possible 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. +- `href`: The URL to open when the button is pressed on the Web. + +You can use it to implement your custom left button, for example: + + + + +```js +const RootStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + options: { + headerLeft: (props) => ( + { + // Do something + }}> + ) + } + } + } +}) +``` + + + + +```js + ( + { + // Do something + }} + /> + ), + }} +/> +``` + + + + +#### `headerRight` + +Function which returns a React Element to display on the right side of the header. + +It receives an object containing following properties: + +- `tintColor`: The color of the icon and label. +- `pressColor`: The color of the material ripple (Android >= 5.0 only). +- `pressOpacity`: The opacity of the button when it's pressed (Android < 5.0, and iOS). + + + + +```js +const RootStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + options: { + headerLeft: (props) => ( + { + // Do something + }}> + ) + } + } + } +}) +``` + + + + +```js + ( + { + // Do something + }} + /> + ), + }} +/> +``` + + + + +#### `headerSearchBarOptions` + +Options for the search bar in the header. When this is specified, the header will contain a button to show a search input. + +It can contain the following properties: + +- `ref`: Ref to manipulate the search input imperatively. It contains the following methods: + - `focus` - focuses the search bar + - `blur` - removes focus from the search bar + - `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 +- `autoCapitalize`: The auto-capitalization behavior. Possible values: `none`, `words`, `sentences`, `characters`. +- `autoFocus`: Automatically focus search input on mount. +- `cancelButtonText`: Text to be used instead of default `Cancel` button text (iOS only). +- `inputType`: Type of the input. Possible values: `text`, `phone`, `number`, `email`. +- `onBlur`: Callback that gets called when search input has lost focus. +- `onChangeText`: Callback that gets called when the text changes. +- `onClose`: Callback that gets called when search input is closed. +- `onFocus`: Callback that gets called when search input has received focus. +- `placeholder`: Text displayed when search input is empty. + +```js +React.useLayoutEffect(() => { + navigation.setOptions({ + headerSearchBarOptions: { + placeholder: 'Search', + onChangeText: (text) => { + // Do something + }, + }, + }); +}, [navigation]); +``` + +#### `headerShadowVisible` + +Whether to hide the elevation shadow (Android) or the bottom border (iOS) on the header. + +This is a short-hand for the following styles: + +```js +{ + elevation: 0, + shadowOpacity: 0, + borderBottomWidth: 0, +} +``` + +If any of the above styles are specified in `headerStyle` along with `headerShadowVisible: false`, then the styles in `headerStyle` will take precedence. + +#### `headerStyle` + +Style object for the header. You can specify a custom background color here, for example: + +```js +{ + backgroundColor: 'tomato', +} +``` + +Note that `headerStyle` won't take effect if you are also using [`headerBackground`](#headerbackground). In that case, you should style the element returned from `headerBackground` instead. + +#### `headerTitleStyle` + +Style object for the title component + +#### `headerLeftContainerStyle` + +Customize the style for the container of the `headerLeft` component, for example to add padding. + +#### `headerRightContainerStyle` + +Customize the style for the container of the `headerRight` component, for example to add padding. + +#### `headerTitleContainerStyle` + +Customize the style for the container of the `headerTitle` component, for example to add padding. + +By default, `headerTitleContainerStyle` is with an absolute position style and offsets both `left` and `right`. This may lead to white space or overlap between `headerLeft` and `headerTitle` if a customized `headerLeft` is used. It can be solved by adjusting `left` and `right` style in `headerTitleContainerStyle` and `marginHorizontal` in `headerTitleStyle`. + +#### `headerBackgroundContainerStyle` + +Style object for the container of the `headerBackground` element. + +#### `headerTintColor` + +Tint color for the header + +#### `headerPressColor` + +Color for material ripple (Android >= 5.0 only) + +#### `headerPressOpacity` + +Press opacity for the buttons in header (Android < 5.0, and iOS) + +#### `headerTransparent` + +Defaults to `false`. If `true`, the header will not have a background unless you explicitly provide it with `headerBackground`. The header will also float over the screen so that it overlaps the content underneath. + +This is useful if you want to render a semi-transparent header or a blurred background. + +Note that if you don't want your content to appear under the header, you need to manually add a top margin to your content. React Navigation won't do it automatically. + +To get the height of the header, you can use [`HeaderHeightContext`](#headerheightcontext) with [React's Context API](https://react.dev/reference/react/useContext#contextconsumer) or [`useHeaderHeight`](#useheaderheight). + +#### `headerBackground` + +Function which returns a React Element to render as the background of the header. This is useful for using backgrounds such as an image or a gradient. + +For example, you can use this with `headerTransparent` to render a blur view to create a translucent header. + + + + +```js name="Header blur" snack dependencies=expo-blur +import * as React from 'react'; +import { View, StyleSheet } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createStackNavigator } from '@react-navigation/stack'; +import { Button } from '@react-navigation/elements'; +import { BlurView } from 'expo-blur'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +function ProfileScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +// codeblock-focus-start +const Stack = createStackNavigator({ + initialRouteName: 'Home', + screens: { + Home: { + screen: HomeScreen, + options: { + headerTransparent: true, + headerBackground: () => ( + + ), + }, + }, + Profile: ProfileScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(Stack); + +function App() { + return ; +} + +export default App; +``` + + + + +```js name="Header blur" snack dependencies=expo-blur +import * as React from 'react'; +import { View, StyleSheet } 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 +import { BlurView } from 'expo-blur'; + +// codeblock-focus-end + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +function ProfileScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +const Stack = createStackNavigator(); + +function App() { + return ( + + + // codeblock-focus-start + ( + + ), + }} + /> + // codeblock-focus-end + + + + ); +} + +export default App; +``` + + + + +#### `headerStatusBarHeight` + +Extra padding to add at the top of header to account for translucent status bar. By default, it uses the top value from the safe area insets of the device. Pass 0 or a custom value to disable the default behavior, and customize the height. + +### `HeaderBackground` + +A component containing the styles used in the background of the header, such as the background color and shadow. It's the default for [`headerBackground`](#headerbackground). It accepts the same props as a [`View`](https://reactnative.dev/docs/view). + +Usage: + +```js + +``` + +### `HeaderTitle` + +A component used to show the title text in header. It's the default for [`headerTitle`](#headertitle). It accepts the same props as a [`Text`](https://reactnative.dev/docs/Text). + +The color of title defaults to the [theme text color](themes.md). You can override it by passing a `tintColor` prop. + +Usage: + +```js +Hello +``` + +### `HeaderButton` + +A component used to show a button in header. It can be used for both left and right buttons. It accepts the following props: + +- `onPress` - Callback to call when the button is pressed. +- `href` - The `href` to use for the anchor tag on web. +- `disabled` - Boolean which controls whether the button is disabled. +- `accessibilityLabel` - Accessibility label for the button for screen readers. +- `testID` - ID to locate this button in tests. +- `tintColor` - Tint color for the header button. +- `pressColor` - Color for material ripple (Android >= 5.0 only). +- `pressOpacity` - Opacity when the button is pressed if material ripple isn't supported by the platform. +- `style` - Style object for the button. +- `children` - Content to render for the button. Usually the icon. + +Usage: + +```js + console.log('button pressed')} +> + + +``` + +### `HeaderBackButton` + +A component used to show the back button header. It's the default for [`headerLeft`](#headerleft) in the [stack navigator](stack-navigator.md). It accepts the following props: + +- `disabled` - Boolean which controls Whether the button is disabled. +- `onPress` - Callback to call when the button is pressed. +- `pressColor` - Color for material ripple (Android >= 5.0 only). +- `backImage` - Function which returns a React Element to display custom image in header's back button. +- `tintColor` - Tint color for the header. +- `label` - Label text for the button. Usually the title of the previous screen. By default, this is only shown on iOS. +- `truncatedLabel` - Label text to show when there isn't enough space for the full label. +- `displayMode`: How the back button displays icon and title. Defaults to `default` on iOS and `minimal` on Android. Possible 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. +- `labelStyle` - Style object for the label. +- `allowFontScaling` - Whether label font should scale to respect Text Size accessibility settings. +- `onLabelLayout` - Callback to trigger when the size of the label changes. +- `screenLayout` - Layout of the screen. +- `titleLayout` - Layout of the title element in the header. +- `canGoBack` - Boolean to indicate whether it's possible to navigate back in stack. +- `accessibilityLabel` - Accessibility label for the button for screen readers. +- `testID` - ID to locate this button in tests. +- `style` - Style object for the button. + +Usage: + +```js + console.log('back pressed')} /> +``` + +### `MissingIcon` + +A component that renders a missing icon symbol. It can be used as a fallback for icons to show that there's a missing icon. It accepts the following props: + +- `color` - Color of the icon. +- `size` - Size of the icon. +- `style` - Additional styles for the icon. + +### `PlatformPressable` + +A component which provides an abstraction on top of [`Pressable`](https://reactnative.dev/docs/Pressable) to handle platform differences. In addition to `Pressable`'s props, it accepts following additional props: + +- `pressColor` - Color of material ripple on Android when it's pressed +- `pressOpacity` - Opacity when it's pressed if material ripple isn't supported by the platform + +### `Button` + +A component that renders a button. In addition to [`PlatformPressable`](#platformpressable)'s props, it accepts following additional props: + +- `variant` - Variant of the button. Possible values are: + - `tinted` (default) + - `plain` + - `filled` +- `color` - Color of the button. Defaults to the [theme](themes.md)'s primary color. +- `children` - Content to render inside the button. + +In addition, the button integrates with React Navigation and accepts the same props as [`useLinkProps`](use-link-props.md#options) hook. + +It can be used to navigate between screens by specifying a screen name and params: + +```js + +``` + +Or as a regular button: + +```js + +``` + +### `Label` + +The `Label` component is used to render small text. It is used in [Bottom Tab Navigator](bottom-tab-navigator.md) to render the label for each tab. + +In addition to the standard [`Text`](https://reactnative.dev/docs/text) props, it accepts the following props: + +- `tintColor` - Color of the label. Defaults to the [theme](themes.md)'s text color. + +Usage: + +```jsx + +``` + +### `ResourceSavingView` + +A component which aids in improving performance for inactive screens by utilizing [`removeClippedSubviews`](https://reactnative.dev/docs/view#removeclippedsubviews). It accepts a `visible` prop to indicate whether a screen should be clipped. + +Usage: + +```js +{/* Content */} +``` + +## Utilities + +### `SafeAreaProviderCompat` + +A wrapper over the `SafeAreaProvider` component from [`react-native-safe-area-context](https://github.com/th3rdwave/react-native-safe-area-context) which includes initial values. + +Usage: + +```js +{/* Your components */} +``` + +### `HeaderBackContext` + +React context that can be used to get the back title of the parent screen. + +```js +import { HeaderBackContext } from '@react-navigation/elements'; + +// ... + + + {(headerBack) => { + if (headerBack) { + const backTitle = headerBack.title; + + /* render something */ + } + + /* render something */ + }} +; +``` + +### `HeaderShownContext` + +React context that can be used to check if a header is visible in a parent screen. + +```js +import { HeaderShownContext } from '@react-navigation/elements'; + +// ... + + + {(headerShown) => { + /* render something */ + }} +; +``` + +### `HeaderHeightContext` + +React context that can be used to get the height of the nearest visible header in a parent screen. + +```js +import { HeaderHeightContext } from '@react-navigation/elements'; + +// ... + + + {(headerHeight) => { + /* render something */ + }} +; +``` + +### `useHeaderHeight` + +Hook that returns the height of the nearest visible header in the parent screen. + +```js +import { useHeaderHeight } from '@react-navigation/elements'; + +// ... + +const headerHeight = useHeaderHeight(); +``` + +### `getDefaultHeaderHeight` + +Helper that returns the default header height. It takes the following parameters: + +- `layout` - Layout of the screen, i.e. an object containing `height` and `width` properties. +- `statusBarHeight` - height of the statusbar + +### `getHeaderTitle` + +Helper that returns the title text to use in header. It takes the following parameters: + +- `options` - The options object of the screen. +- `fallback` - Fallback title string if no title was found in options. + +### `useFrameSize` + +Hook that returns the size of the frame of the parent navigator. It accepts a selector function which receives the frame dimensions and returns a value: + +```js +import { useFrameSize } from '@react-navigation/elements'; + +// ... + +const isLandscape = useFrameSize((frame) => frame.width > frame.height); +``` + +The selector ensures that the component only re-renders when we need to. diff --git a/versioned_docs/version-8.x/function-after-focusing-screen.md b/versioned_docs/version-8.x/function-after-focusing-screen.md new file mode 100755 index 0000000000..26528f8eeb --- /dev/null +++ b/versioned_docs/version-8.x/function-after-focusing-screen.md @@ -0,0 +1,332 @@ +--- +id: function-after-focusing-screen +title: Call a function when focused screen changes +sidebar_label: Call a function on focus +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +In this guide we will call a function or render something on screen focusing. This is useful for making additional API calls when a user revisits a particular screen in a Tab Navigator, or to track user events as they tap around our app. + +There are multiple approaches available to us: + +1. Listening to the `'focus'` event with an event listener. +2. Using the `useFocusEffect` hook provided by react-navigation. +3. Using the `useIsFocused` hook provided by react-navigation. + +## Triggering an action with a `'focus'` event listener + +We can also listen to the `'focus'` event with an event listener. After setting up an event listener, we must also stop listening to the event when the screen is unmounted. + +With this approach, we will only be able to call an action when the screen focuses. This is useful for performing an action such as logging the screen view for analytics. + +Example: + + + + +```js name="Focus event listener" snack +// codeblock-focus-start +import * as React from 'react'; +import { View } from 'react-native'; + +// codeblock-focus-end +import { + useNavigation, + createStaticNavigation, +} from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; + +// codeblock-focus-start +function ProfileScreen() { + const navigation = useNavigation(); + + React.useEffect(() => { + const unsubscribe = navigation.addListener('focus', () => { + alert('Screen is focused'); + // The screen is focused + // Call any action + }); + + // Return the function to unsubscribe from the event so it gets removed on unmount + return unsubscribe; + }, [navigation]); + + return ; +} +// codeblock-focus-end + +function HomeScreen() { + return ; +} + +const MyTabs = createBottomTabNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(MyTabs); + +export default function App() { + return ; +} +``` + + + + +```js name="Focus event listener" snack +// codeblock-focus-start +import * as React from 'react'; +import { View } from 'react-native'; + +// codeblock-focus-end +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; + +// codeblock-focus-start +function ProfileScreen() { + const navigation = useNavigation(); + + React.useEffect(() => { + const unsubscribe = navigation.addListener('focus', () => { + alert('Screen is focused'); + // The screen is focused + // Call any action + }); + + // Return the function to unsubscribe from the event so it gets removed on unmount + return unsubscribe; + }, [navigation]); + + return ; +} +// codeblock-focus-end + +function HomeScreen() { + return ; +} + +const Tab = createBottomTabNavigator(); + +export default function App() { + return ( + + + + + + + ); +} +``` + + + + +See the [navigation events guide](navigation-events.md) for more details on the event listener API. + +In most cases, it's recommended to use the `useFocusEffect` hook instead of adding the listener manually. See below for details. + +## Triggering an action with the `useFocusEffect` hook + +React Navigation provides a [hook](use-focus-effect.md) that runs an effect when the screen comes into focus and cleans it up when it goes out of focus. This is useful for cases such as adding event listeners, for fetching data with an API call when a screen becomes focused, or any other action that needs to happen once the screen comes into view. + +This is particularly handy when we are trying to stop something when the page is unfocused, like stopping a video or audio file from playing, or stopping the tracking of a user's location. + + + + +```js name="useFocusEffect hook" snack +import * as React from 'react'; +import { View } from 'react-native'; +import { + useFocusEffect, + createStaticNavigation, +} from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; + +// codeblock-focus-start +function ProfileScreen() { + useFocusEffect( + React.useCallback(() => { + alert('Screen was focused'); + // Do something when the screen is focused + return () => { + alert('Screen was unfocused'); + // Do something when the screen is unfocused + // Useful for cleanup functions + }; + }, []) + ); + + return ; +} +// codeblock-focus-end + +function HomeScreen() { + return ; +} + +const MyTabs = createBottomTabNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +const Navigation = createStaticNavigation(MyTabs); + +export default function App() { + return ; +} +``` + + + + +```js name="useFocusEffect hook" snack +import * as React from 'react'; +import { View } from 'react-native'; +import { NavigationContainer, useFocusEffect } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; + +// codeblock-focus-start +function ProfileScreen() { + useFocusEffect( + React.useCallback(() => { + alert('Screen was focused'); + // Do something when the screen is focused + return () => { + alert('Screen was unfocused'); + // Do something when the screen is unfocused + // Useful for cleanup functions + }; + }, []) + ); + + return ; +} +// codeblock-focus-end + +function HomeScreen() { + return ; +} + +const Tab = createBottomTabNavigator(); + +export default function App() { + return ( + + + + + + + ); +} +``` + + + + +See the [`useFocusEffect`](https://reactnavigation.org/docs/use-focus-effect/) documentation for more details. + +## Re-rendering screen with the `useIsFocused` hook + +React Navigation provides a [hook](use-is-focused.md) that returns a boolean indicating whether the screen is focused or not. + +The hook will return `true` when the screen is focused and `false` when our component is no longer focused. This enables us to render something conditionally based on whether the user is on the screen or not. + +The `useIsFocused` hook will cause our component to re-render when we focus and unfocus a screen. Using this hook component may introduce unnecessary component re-renders as a screen comes in and out of focus. This could cause issues depending on the type of action we're calling on focusing. Hence we recommend to use this hook only if you need to trigger a re-render. For side-effects such as subscribing to events or fetching data, use the methods described above. + + + + +```js name="useIsFocused hook" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { useIsFocused, createStaticNavigation } from '@react-navigation/native'; +import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; + +// codeblock-focus-start +function ProfileScreen() { + // codeblock-focus-end + // This hook returns `true` if the screen is focused, `false` otherwise + // codeblock-focus-start + const isFocused = useIsFocused(); + + return ( + + {isFocused ? 'focused' : 'unfocused'} + + ); +} +// codeblock-focus-end + +function HomeScreen() { + return ; +} + +const MyTabs = createMaterialTopTabNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(MyTabs); + +export default function App() { + return ; +} +``` + + + + +```js name="useIsFocused hook" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { NavigationContainer, useIsFocused } from '@react-navigation/native'; +import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; + +// codeblock-focus-start +function ProfileScreen() { + // codeblock-focus-end + // This hook returns `true` if the screen is focused, `false` otherwise + // codeblock-focus-start + const isFocused = useIsFocused(); + + return ( + + {isFocused ? 'focused' : 'unfocused'} + + ); +} +// codeblock-focus-end + +function HomeScreen() { + return ; +} + +const Tab = createMaterialTopTabNavigator(); + +export default function App() { + return ( + + + + + + + ); +} +``` + + + + +This example is also documented in the [`useIsFocused` API documentation](use-is-focused.md). diff --git a/versioned_docs/version-8.x/getting-started.md b/versioned_docs/version-8.x/getting-started.md new file mode 100755 index 0000000000..9aa3083423 --- /dev/null +++ b/versioned_docs/version-8.x/getting-started.md @@ -0,0 +1,189 @@ +--- +id: getting-started +title: Getting started +sidebar_label: Getting started +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +What follows within the _Fundamentals_ section of this documentation is a tour of the most important aspects of React Navigation. It should cover enough for you to know how to build your typical small mobile application, and give you the background that you need to dive deeper into the more advanced parts of React Navigation. + +## Pre-requisites + +If you're already familiar with JavaScript, React and React Native, then you'll be able to get moving with React Navigation quickly! If not, we highly recommend you to gain some basic knowledge first, then come back here when you're done. + +Here are some resources to help you out: + +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 + +- `react-native` >= 0.72.0 +- `expo` >= 52 (if you use [Expo Go](https://expo.dev/go)) +- `typescript` >= 5.0.0 (if you use TypeScript) + +## Starter template + +If you're starting a new project, you can use the [React Navigation template](https://github.com/react-navigation/template) to quickly set up a new project with [Static configuration](#static-configuration): + +```bash +npx create-expo-app@latest --template react-navigation/template +``` + +See the project's `README.md` for more information on how to get started. + +If you created a new project using the template, you can skip the installation steps below and move on to ["Hello React Navigation"](hello-react-navigation.md?config=static). + +Otherwise, you can follow the instructions below to install React Navigation into your existing project. + +## Installation + +The `@react-navigation/native` package contains the core functionality of React Navigation. + +In your project directory, run: + +```bash npm2yarn +npm install @react-navigation/native +``` + +### Installing dependencies + +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). + + + + +In your project directory, run: + +```bash +npx expo install react-native-screens react-native-safe-area-context +``` + +This will install versions of these libraries that are compatible with your Expo SDK version. + + + + +In your project directory, run: + +```bash npm2yarn +npm install react-native-screens react-native-safe-area-context +``` + +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 +``` + +#### Configuring `react-native-screens` on Android + +[`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?) { + supportFragmentManager.fragmentFactory = RNScreensFragmentFactory() + super.onCreate(savedInstanceState) + } + // 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) { + getSupportFragmentManager().setFragmentFactory(new RNScreensFragmentFactory()); + super.onCreate(savedInstanceState); + } + // highlight-end + + // ... +} +``` + + + + +This change is required to avoid crashes related to View state being not persisted consistently across Activity restarts. + +#### Opting-out of predictive back on Android + +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. + +To opt out, in `AndroidManifest.xml`, in the `` tag (or `` tag to opt-out at activity level), set the `android:enableOnBackInvokedCallback` flag to `false`: + +```xml + + + +``` + + + + +## Setting up React Navigation + +Once you've installed and configured the dependencies, you can move on to setting up your project to use React Navigation. + +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. + +:::info + +When you use a navigator (such as stack navigator), you'll need to follow the installation instructions of that navigator for any additional dependencies. + +::: + +There are 2 primary ways to configure the navigators: + +### Static configuration + +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. + +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. + +Continue to ["Hello React Navigation"](hello-react-navigation.md?config=static) to start writing some code with the static API. + +### 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-8.x/glossary-of-terms.md b/versioned_docs/version-8.x/glossary-of-terms.md new file mode 100755 index 0000000000..70edc36694 --- /dev/null +++ b/versioned_docs/version-8.x/glossary-of-terms.md @@ -0,0 +1,106 @@ +--- +id: glossary-of-terms +title: Glossary of terms +sidebar_label: Glossary of terms +--- + +:::note + +This is a new section of the documentation and it's missing a lot of terms! Please [submit a pull request or an issue](https://github.com/react-navigation/react-navigation.github.io) with a term that you think should be explained here. + +::: + +## Navigator + +A `Navigator` is React component that decides how to render the screens you have defined. It contains `Screen` elements as its children to define the configuration for screens. + +`NavigationContainer` is a component which manages our navigation tree and contains the [navigation state](navigation-state.md). This component must wrap all navigators structure. Usually, we'd render this component at the root of our app, which is usually the component exported from `App.js`. + +```js +function App() { + return ( + + // <---- This is a Navigator + + + + ); +} +``` + +## Router + +A router is a collection of functions that decide how to handle actions and state changes in the navigator (similar to reducers in Redux apps). Normally you'd never need to interact with a router directly, unless you're writing a [custom navigator](custom-navigators.md). + +## Screen component + +A screen component is a component that we use in our route configuration. + +```js +const Stack = createNativeStackNavigator(); + +const StackNavigator = ( + + + + +); +``` + +The suffix `Screen` in the component name is entirely optional, but a frequently used convention; we could call it `Michael` and this would work just the same. + +## Navigation object + +The navigation object contains methods used for navigation. It contains methods such as: + +- `dispatch` will send an action up to the router +- `navigate`, `goBack`, etc are available to dispatch actions in a convenient way + +This object can be accessed with the [`useNavigation`](use-navigation.md) hook. It's also passed as a prop to screens defined with the dynamic API. + +For more details, see the ["Navigation object docs"](navigation-object.md). + +The ["Route object reference"](route-object.md) section goes into more detail on this, describes workarounds, and provides more information on other properties available on `route` object. + +## Route object + +This prop will be passed to all screens. Contains information about the current route i.e. `params`, `key` and `name`. It can also contain arbitrary params: + +```js +{ + key: 'B', + name: 'Profile', + params: { id: '123' } +} +``` + +For more details, see the ["Route object reference"](route-object.md). + +## Navigation State + +The state of a navigator generally looks something like this: + +```js +{ + key: 'StackRouterRoot', + index: 1, + routes: [ + { key: 'A', name: 'Home' }, + { key: 'B', name: 'Profile' }, + ] +} +``` + +For this navigation state, there are two routes (which may be tabs, or cards in a stack). The index indicates the active route, which is "B". + +You can read more about the navigation state [here](navigation-state.md). + +## Header + +Also known as navigation header, navigation bar, app bar, and probably many other things. This is the rectangle at the top of your screen that contains the back button and the title for your screen. The entire rectangle is often referred to as the header in React Navigation. diff --git a/versioned_docs/version-8.x/group.md b/versioned_docs/version-8.x/group.md new file mode 100644 index 0000000000..4a7b6583ab --- /dev/null +++ b/versioned_docs/version-8.x/group.md @@ -0,0 +1,335 @@ +--- +id: group +title: Group +sidebar_label: Group +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +A group contains several [screens](screen.md) inside a navigator for organizational purposes. They can also be used to apply the same options such as header styles to a group of screens, or to define a common layout etc. + + + + +Groups can be defined using the `groups` property in the navigator configuration: + +```js name="Stack groups" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function EmptyScreen() { + return ; +} + +// codeblock-focus-start +const MyStack = createNativeStackNavigator({ + groups: { + App: { + screenOptions: { + headerStyle: { + backgroundColor: '#FFB6C1', + }, + }, + screens: { + Home: HomeScreen, + Profile: EmptyScreen, + }, + }, + Modal: { + screenOptions: { + presentation: 'modal', + }, + screens: { + Search: EmptyScreen, + Share: EmptyScreen, + }, + }, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(Stack); + +export default function App() { + return ; +} +``` + +The keys of the `groups` object (e.g. `Guest`, `User`) are used as the [`navigationKey`](#navigation-key) for the group. You can use any string as the key. + + + + +A `Group` component is returned from a `createXNavigator` function. After creating the navigator, it can be used as children of the `Navigator` component: + +```js name="Stack groups" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; + +const Stack = createNativeStackNavigator(); + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function EmptyScreen() { + return ; +} + +export default function App() { + return ( + + // codeblock-focus-start + + + + + + + + + + + // codeblock-focus-end + + ); +} +``` + +It's also possible to nest `Group` components inside other `Group` components. + + + + +## Configuration + +### Screen options + +Options to configure how the screens inside the group get presented in the navigator. It accepts either an object or a function returning an object: + + + + +```js +const MyStack = createNativeStackNavigator({ + groups: { + Modal: { + // highlight-start + screenOptions: { + presentation: 'modal', + }, + // highlight-end + screens: { + /* screens */ + }, + }, + }, +}); +``` + + + + +```jsx + + {/* screens */} + +``` + + + + +When you pass a function, it'll receive the [`route`](route-object.md) and [`navigation`](navigation-object.md): + + + + +```js +const MyStack = createNativeStackNavigator({ + groups: { + Modal: { + // highlight-start + screenOptions: ({ route, navigation }) => ({ + title: route.params.title, + }), + // highlight-end + screens: { + /* screens */ + }, + }, + }, +}); +``` + + + + +```jsx + ({ + title: route.params.title, + })} + // highlight-end +> + {/* screens */} + +``` + + + + +These options are merged with the `options` specified in the individual screens, and the screen's options will take precedence over the group's options. + +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 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: + + + + +```js +const MyStack = createNativeStackNavigator({ + groups: { + Modal: { + // highlight-start + screenLayout: ({ children }) => ( + + + Loading… + + } + > + {children} + + + ), + // highlight-end + screens: { + /* screens */ + }, + }, + }, +}); +``` + + + + +```jsx + ( + + + Loading… + + } + > + {children} + + + )} + // highlight-end +> + {/* screens */} + +``` + + + + +### Navigation key + +Optional key for a group of screens. If the key changes, all existing screens in this group will be removed (if used in a stack navigator) or reset (if used in a tab or drawer navigator): + + + + +The name of the group is used as the `navigationKey`: + +```js +const MyStack = createNativeStackNavigator({ + groups: { + // highlight-next-line + User: { + screens: { + /* screens */ + }, + }, + // highlight-next-line + Guest: { + screens: { + /* screens */ + }, + }, + }, +}); +``` + +This means if a screen is defined in 2 groups and the groups use the [`if`](static-configuration.md#if) property, the screen will remount if the condition changes resulting in one group being removed and the other group being used. + + + + +```jsx + + {/* screens */} + +``` + + + + +This is similar to the [`navigationKey`](screen.md#navigation-key) prop for screens, but applies to a group of screens. diff --git a/versioned_docs/version-8.x/handling-safe-area.md b/versioned_docs/version-8.x/handling-safe-area.md new file mode 100755 index 0000000000..225bfa87c1 --- /dev/null +++ b/versioned_docs/version-8.x/handling-safe-area.md @@ -0,0 +1,478 @@ +--- +id: handling-safe-area +title: Supporting safe areas +sidebar_label: Supporting safe areas +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +By default, React Navigation tries to ensure that the elements of the navigators display correctly on devices with notches (e.g. iPhone X) and UI elements which may overlap the app content. Such items include: + +- Physical notches +- Status bar overlay +- Home activity indicator on iOS +- Navigation bar on Android + +The area not overlapped by such items is referred to as "safe area". + +We try to apply proper insets on the UI elements of the navigators to avoid being overlapped by such items. The goal is to (a) maximize usage of the screen (b) without hiding content or making it difficult to interact with by having it obscured by a physical display cutout or some operating system UI. + +While React Navigation handles safe areas for the built-in UI elements by default, your own content may also need to handle it to ensure that content isn't hidden by these items. + +It's tempting to solve (a) by wrapping your entire app in a container with padding that ensures all content will not be occluded. But in doing so, we waste a bunch of space on the screen, as pictured in the image on the left below. What we ideally want is the image pictured on the right. + +![Notch on the iPhone X](/assets/iphoneX/00-intro.png) + +While React Native exports a `SafeAreaView` component, this component only supports iOS 10+ with no support for older iOS versions or Android. In addition, it also has some issues, i.e. if a screen containing safe area is animating, it causes jumpy behavior. So we recommend to use the `useSafeAreaInsets` hook from the [react-native-safe-area-context](https://github.com/th3rdwave/react-native-safe-area-context) library to handle safe areas in a more reliable way. + +:::warning + +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. + +::: + +The rest of this guide gives more information on how to support safe areas in React Navigation. + +## Hidden/Custom Header or Tab Bar + +![Default React Navigation Behavior](/assets/iphoneX/01-iphonex-default.png) + +React Navigation handles safe area in the default header. However, if you're using a custom header, it's important to ensure your UI is within the safe area. + +For example, if I render nothing for the `header` or `tabBar`, nothing renders + + + + +```js name="Hidden components" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function Demo() { + return ( + + This is top text. + This is bottom text. + + ); +} + +// codeblock-focus-start +const MyTabs = createBottomTabNavigator({ + initialRouteName: 'Analytics', + // highlight-start + tabBar: () => null, + screenOptions: { + headerShown: false, + }, + // highlight-end + screens: { + Analytics: Demo, + Profile: Demo, + }, +}); + +const RootStack = createNativeStackNavigator({ + initialRouteName: 'Home', + // highlight-start + screenOptions: { + headerShown: false, + }, + // highlight-end + screens: { + Home: MyTabs, + Settings: Demo, + }, +}); + +// codeblock-focus-end + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Hidden components" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function Demo() { + return ( + + This is top text. + This is bottom text. + + ); +} +const Stack = createNativeStackNavigator(); +const Tab = createBottomTabNavigator(); + +export default function App() { + return ( + + + + {() => ( + null} + screenOptions={{ headerShown: false }} + > + + + + )} + + + + + + ); +} +``` + + + + +![Text hidden by iPhoneX UI elements](/assets/iphoneX/02-iphonex-content-hidden.png) + +To fix this issue you can apply safe area insets on your content. This can be achieved using the `useSafeAreaInsets` hook from the `react-native-safe-area-context` library: + + + + +```js name="Safe area example" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { + SafeAreaProvider, + useSafeAreaInsets, +} from 'react-native-safe-area-context'; + +// codeblock-focus-start +function Demo() { + const insets = useSafeAreaInsets(); + + return ( + + This is top text. + This is bottom text. + + ); +} +// codeblock-focus-end + +const MyTabs = createBottomTabNavigator({ + initialRouteName: 'Analytics', + tabBar: () => null, + screenOptions: { + headerShown: false, + }, + screens: { + Analytics: Demo, + Profile: Demo, + }, +}); + +const RootStack = createNativeStackNavigator({ + initialRouteName: 'Home', + screenOptions: { + headerShown: false, + }, + screens: { + Home: MyTabs, + Settings: Demo, + }, +}); + +// codeblock-focus-start + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ( + + + + ); +} +// codeblock-focus-end +``` + + + + +```js name="Safe area example" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { + SafeAreaProvider, + useSafeAreaInsets, +} from 'react-native-safe-area-context'; + +// codeblock-focus-start +function Demo() { + const insets = useSafeAreaInsets(); + + return ( + + This is top text. + This is bottom text. + + ); +} +// codeblock-focus-end + +const Stack = createNativeStackNavigator(); +const Tab = createBottomTabNavigator(); + +// codeblock-focus-start +export default function App() { + return ( + + + {/*(...) */} + // codeblock-focus-end + + + {() => ( + null} + screenOptions={{ headerShown: false }} + > + + + + )} + + + + // codeblock-focus-start + + + ); +} +// codeblock-focus-end +``` + + + + +Make sure to wrap your app in `SafeAreaProvider` as per the instructions [here](https://github.com/th3rdwave/react-native-safe-area-context#usage). + +![Content spaced correctly with safe area insets](/assets/iphoneX/03-iphonex-content-fixed.png) + +This will detect if the app is running on a device with notches, if so, ensure the content isn't hidden behind any hardware elements. + +## Landscape Mode + +Even if you're using the default navigation bar and tab bar - if your application works in landscape mode it's important to ensure your content isn't hidden behind the sensor cluster. + +![App in landscape mode with text hidden](/assets/iphoneX/04-iphonex-landscape-hidden.png) + +To fix this you can, once again, apply safe area insets to your content. This will not conflict with the navigation bar nor the tab bar's default behavior in portrait mode. + +![App in landscape mode with text visible](/assets/iphoneX/05-iphonex-landscape-fixed.png) + +## Use the hook for more control + +In some cases you might need more control over which paddings are applied. For example, you can only apply the top and the bottom padding by changing the `style` object: + + + + +```js name="useSafeAreaInsets hook" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +// codeblock-focus-start +import { + SafeAreaProvider, + useSafeAreaInsets, +} from 'react-native-safe-area-context'; + +function Demo() { + const insets = useSafeAreaInsets(); + return ( + + This is top text. + This is bottom text. + + ); +} +// codeblock-focus-end + +const MyTabs = createBottomTabNavigator({ + initialRouteName: 'Analytics', + tabBar: () => null, + screenOptions: { + headerShown: false, + }, + screens: { + Analytics: Demo, + Profile: Demo, + }, +}); + +const RootStack = createNativeStackNavigator({ + initialRouteName: 'Home', + screenOptions: { + headerShown: false, + }, + screens: { + Home: MyTabs, + Settings: Demo, + }, +}); + +// codeblock-focus-start + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ( + + + + ); +} +// codeblock-focus-end +``` + + + + +```js name="useSafeAreaInsets hook" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createStackNavigator } from '@react-navigation/stack'; +// codeblock-focus-start +import { + SafeAreaProvider, + useSafeAreaInsets, +} from 'react-native-safe-area-context'; + +function Demo() { + const insets = useSafeAreaInsets(); + return ( + + This is top text. + This is bottom text. + + ); +} +// codeblock-focus-end + +const Stack = createStackNavigator(); +const Tab = createBottomTabNavigator(); + +export default function App() { + return ( + + + + + {() => ( + null} + screenOptions={{ headerShown: false }} + > + + + + )} + + + + + + + ); +} +``` + + + + +Similarly, you could apply these paddings in `contentContainerStyle` of `FlatList` to have the content avoid the safe areas, but still show them under the statusbar and navigation bar when scrolling. + +## Summary + +- 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-8.x/header-buttons.md b/versioned_docs/version-8.x/header-buttons.md new file mode 100755 index 0000000000..6de8a23371 --- /dev/null +++ b/versioned_docs/version-8.x/header-buttons.md @@ -0,0 +1,302 @@ +--- +id: header-buttons +title: Header buttons +sidebar_label: Header buttons +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Now that we know how to customize the look of our headers, let's make them sentient! Actually perhaps that's ambitious, let's just make them able to respond to our touches in very well-defined ways. + +## Adding a button to the header + +The most common way to interact with a header is by tapping on a button either to the left or the right of the title. Let's add a button to the right side of the header (one of the most difficult places to touch on your entire screen, depending on finger and phone size, but also a normal place to put buttons). + + + + +```js name="Header button" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +// codeblock-focus-start +const MyStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + options: { + // highlight-start + headerRight: () => ( + + ), + // highlight-end + }, + }, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Header button" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +const Stack = createNativeStackNavigator(); + +// codeblock-focus-start +function MyStack() { + return ( + + ( + + ), + // highlight-end + }} + /> + + ); +} +// codeblock-focus-end + +export default function App() { + return ( + + + + ); +} +``` + + + + +![Header button](/assets/headers/header-button.png) + +When we define our button this way, the `this` variable in `options` is _not_ the `HomeScreen` instance, so you can't call `setState` or any instance methods on it. This is pretty important because it's common to want the buttons in your header to interact with the screen that the header belongs to. So, we will look how to do this next. + +:::tip + +Note that a community-developed library for rendering buttons in the header with the correct styling is available: [react-navigation-header-buttons](https://github.com/vonovak/react-navigation-header-buttons). + +::: + +## Header interaction with its screen component + +In some cases, components in the header need to interact with the screen component. For this use case, we need to use `navigation.setOptions` to update our options. By using `navigation.setOptions` inside the screen component, we get access to screen's props, state, context etc. + + + + +```js name="Header button" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +// codeblock-focus-start +function HomeScreen() { + const navigation = useNavigation(); + const [count, setCount] = React.useState(0); + + React.useEffect(() => { + // Use `setOptions` to update the button that we previously specified + // Now the button includes an `onPress` handler to update the count + // highlight-start + navigation.setOptions({ + headerRight: () => ( + + ), + }); + // highlight-end + }, [navigation]); + + return Count: {count}; +} + +const MyStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + options: { + // Add a placeholder button without the `onPress` to avoid flicker + // highlight-next-line + headerRight: () => , + }, + }, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Header button" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +const Stack = createNativeStackNavigator(); + +// codeblock-focus-start +function HomeScreen() { + const navigation = useNavigation(); + const [count, setCount] = React.useState(0); + + React.useEffect(() => { + // Use `setOptions` to update the button that we previously specified + // Now the button includes an `onPress` handler to update the count + // highlight-start + navigation.setOptions({ + headerRight: () => ( + + ), + }); + // highlight-end + }, [navigation]); + + return Count: {count}; +} + +function MyStack() { + return ( + + , + }} + /> + + ); +} +// codeblock-focus-end + +export default function App() { + return ( + + + + ); +} +``` + + + + + + +Here we update the `headerRight` with a button with `onPress` handler that has access to the component's state and can update it. + +## Customizing the back button + +`createNativeStackNavigator` provides the platform-specific defaults for the back button. On iOS this includes a label next to the button, which shows the title of the previous screen when the title fits in the available space, otherwise it says "Back". + +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 icon, you can use [`headerBackIcon`](native-stack-navigator.md#headerbackicon). + + + + +```js +const MyStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + options: { + headerBackTitle: 'Custom Back', + headerBackTitleStyle: { fontSize: 30 }, + }, + }, + }, +}); +``` + + + + +```js + + + + +``` + + + + +![Header custom back](/assets/headers/header-back-custom.png) + +## Overriding the back button + +The back button will be rendered automatically in a stack navigator whenever it is possible for the user to go back from their current screen — in other words, the back button will be rendered whenever there is more than one screen in the stack. + +Generally, this is what you want. But it's possible that in some circumstances that you want to customize the back button more than you can through the options mentioned above, in which case you can set the `headerLeft` option to a React Element that will be rendered, just as we did with `headerRight`. Alternatively, the `headerLeft` option also accepts a React Component, which can be used, for example, for overriding the onPress behavior of the back button. Read more about this in the [api reference](native-stack-navigator.md#headerleft). + +## Summary + +- 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-8.x/headers.md b/versioned_docs/version-8.x/headers.md new file mode 100755 index 0000000000..04f1881160 --- /dev/null +++ b/versioned_docs/version-8.x/headers.md @@ -0,0 +1,694 @@ +--- +id: headers +title: Configuring the header bar +sidebar_label: Configuring the header bar +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +We've seen how to configure the header title already, but let's go over that again before moving on to some other options — repetition is key to learning! + +## Setting the header title + +Each screen has `options` which is either an object or a function that returns an object, that contains various configuration options. The one we use for the header title is `title`, as shown in the following example. + + + + +```js name="Setting header title" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +// codeblock-focus-start +const MyStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + // highlight-start + options: { + title: 'My home', + }, + // highlight-end + }, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Setting header title" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +const Stack = createNativeStackNavigator(); + +// codeblock-focus-start +function MyStack() { + return ( + + + + ); +} +// codeblock-focus-end + +export default function App() { + return ( + + + + ); +} +``` + + + + +![Header title](/assets/headers/header-title.png) + +## Using params in the title + +In order to use params in the title, we need to make `options` for the screen a function that returns a configuration object. If we make `options` a function then React Navigation will call it with an object containing `{ navigation, route }` - in this case, all we care about is `route`, which is the same object that is passed to your screen props as `route` prop. You may recall that we can get the params through `route.params`, and so we do this below to extract a param and use it as a title. + + + + +```js name="Using params in the title" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +function ProfileScreen() { + return ( + + Profile Screen + + ); +} + +// codeblock-focus-start +const MyStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + options: { + title: 'My home', + }, + }, + Profile: { + screen: ProfileScreen, + // highlight-start + options: ({ route }) => ({ + title: route.params.name, + }), + // highlight-end + }, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Using params in the title" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +function ProfileScreen() { + return ( + + Profile Screen + + ); +} + +const Stack = createNativeStackNavigator(); + +// codeblock-focus-start +function MyStack() { + return ( + + + ({ + title: route.params.name, + })} + // highlight-end + /> + + ); +} +// codeblock-focus-end + +export default function App() { + return ( + + + + ); +} +``` + + + + +The argument that is passed in to the `options` function is an object with the following properties: + +- `navigation` - The [navigation object](navigation-object.md) for the screen. +- `route` - The [route object](route-object.md) for the screen + +We only needed the `route` object in the above example but you may in some cases want to use `navigation` as well. + +## Updating `options` with `setOptions` + +It's often necessary to update the `options` configuration for the active screen from the mounted screen component itself. We can do this using `navigation.setOptions` + +```js name="Updating options" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + // codeblock-focus-start + + // codeblock-focus-end + + ); +} + +const MyStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + options: { + title: 'My home', + }, + }, + }, +}); + +const Navigation = createStaticNavigation(MyStack); + +export default function App() { + return ; +} +``` + +## Adjusting header styles + +There are three key properties to use when customizing the style of your header: `headerStyle`, `headerTintColor`, and `headerTitleStyle`. + +- `headerStyle`: a style object that will be applied to the view that wraps the header. If you set `backgroundColor` on it, that will be the color of your header. +- `headerTintColor`: the back button and title both use this property as their color. In the example below, we set the tint color to white (`#fff`) so the back button and the header title would be white. +- `headerTitleStyle`: if we want to customize the `fontFamily`, `fontWeight` and other `Text` style properties for the title, we can use this to do it. + + + + +```js name="Header styles" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +// codeblock-focus-start +const MyStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + options: { + title: 'My home', + // highlight-start + headerStyle: { + backgroundColor: '#f4511e', + }, + headerTintColor: '#fff', + headerTitleStyle: { + fontWeight: 'bold', + }, + // highlight-end + }, + }, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Header styles" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +const Stack = createNativeStackNavigator(); + +// codeblock-focus-start +function MyStack() { + return ( + + + + ); +} +// codeblock-focus-end + +export default function App() { + return ( + + + + ); +} +``` + + + + +![Custom header styles](/assets/headers/custom_headers.png) + +There are a couple of things to notice here: + +1. On iOS, the status bar text and icons are black, and this doesn't look great over a dark-colored background. We won't discuss it here, but you should be sure to configure the status bar to fit with your screen colors [as described in the status bar guide](status-bar.md). +2. The configuration we set only applies to the home screen; when we navigate to the details screen, the default styles are back. We'll look at how to share `options` between screens now. + +## Sharing common `options` across screens + +It is common to want to configure the header in a similar way across many screens. For example, your company brand color might be red and so you want the header background color to be red and the tint color to be white. Conveniently, these are the colors we're using in our running example, and you'll notice that when you navigate to the `DetailsScreen` the colors go back to the defaults. Wouldn't it be awful if we had to copy the `options` header style properties from `Home` to `Details`, and for every single screen we use in our app? Thankfully, we do not. We can instead move the configuration up to the native stack navigator under `screenOptions`: + + + + +```js name="Common screen options" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function DetailsScreen() { + return ( + + Details Screen + + ); +} + +// codeblock-focus-start +const MyStack = createNativeStackNavigator({ + // highlight-start + screenOptions: { + headerStyle: { + backgroundColor: '#f4511e', + }, + headerTintColor: '#fff', + headerTitleStyle: { + fontWeight: 'bold', + }, + }, + // highlight-end + screens: { + Home: { + screen: HomeScreen, + }, + Details: { + screen: DetailsScreen, + }, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Common screen options" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function DetailsScreen() { + return ( + + Details Screen + + ); +} + +const Stack = createNativeStackNavigator(); + +// codeblock-focus-start +function MyStack() { + return ( + + + + + ); +} +// codeblock-focus-end + +export default function App() { + return ( + + + + ); +} +``` + + + + +Now, any screen that belongs to this navigator will have our wonderful branded styles. Surely though, there must be a way to override these options if we need to? + +## Replacing the title with a custom component + +Sometimes you need more control than just changing the text and styles of your title -- for example, you may want to render an image in place of the title, or make the title into a button. In these cases you can completely override the component used for the title and provide your own. + + + + +```js name="Custom title" snack +import * as React from 'react'; +import { Text, View, Image } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +// codeblock-focus-start +function LogoTitle() { + return ( + + ); +} + +const MyStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + options: { + // highlight-next-line + headerTitle: (props) => , + }, + }, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Custom title" snack +import * as React from 'react'; +import { Text, View, Image } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +const Stack = createNativeStackNavigator(); + +// codeblock-focus-start +function LogoTitle() { + return ( + + ); +} + +function MyStack() { + return ( + + , + }} + /> + + ); +} +// codeblock-focus-end + +export default function App() { + return ( + + + + ); +} +``` + +![Header custom title](/assets/headers/header-custom-title.png) + +You might be wondering, why `headerTitle` when we provide a component and not `title`, like before? The reason is that `headerTitle` is a property that is specific to stack navigators, the `headerTitle` defaults to a `Text` component that displays the `title`. + + + +:::note + +You might be wondering, why `headerTitle` when we provide a component and not `title`, like before? The reason is that `headerTitle` is a property that is specific to headers, whereas `title` will be used for tab bars, drawers etc. as well. The `headerTitle` defaults to a `Text` component that displays the `title`. + +::: + +## Additional configuration + +You can read the full list of available `options` for screens inside of a native stack navigator in the [`createNativeStackNavigator` reference](native-stack-navigator.md#options). + +## Summary + +- 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-8.x/hello-react-navigation.md b/versioned_docs/version-8.x/hello-react-navigation.md new file mode 100755 index 0000000000..74e4e98f60 --- /dev/null +++ b/versioned_docs/version-8.x/hello-react-navigation.md @@ -0,0 +1,555 @@ +--- +id: hello-react-navigation +title: Hello React Navigation +sidebar_label: Hello React Navigation +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +In a web browser, you can link to different pages using an anchor (``) tag. When the user clicks on a link, the URL is pushed to the browser history stack. When the user presses the back button, the browser pops the item from the top of the history stack, so the active page is now the previously visited page. React Native doesn't have a built-in idea of a global history stack like a web browser does -- this is where React Navigation enters the story. + +React Navigation's native stack navigator provides a way for your app to transition between screens and manage navigation history. If your app uses only one stack navigator then it is conceptually similar to how a web browser handles navigation state - your app pushes and pops items from the navigation stack as users interact with it, and this results in the user seeing different screens. A key difference between how this works in a web browser and in React Navigation is that React Navigation's native stack navigator provides the gestures and animations that you would expect on Android and iOS when navigating between routes in the stack. + +Let's start by demonstrating the most common navigator, `createNativeStackNavigator`. + +## Installing the native stack navigator library + +The libraries we've installed so far are the building blocks and shared foundations for navigators, and each navigator in React Navigation lives in its own library. To use the native stack navigator, we need to install [`@react-navigation/native-stack`](https://github.com/react-navigation/react-navigation/tree/main/packages/native-stack) : + +```bash npm2yarn +npm install @react-navigation/native-stack +``` + +:::info + +`@react-navigation/native-stack` depends on `react-native-screens` and the other libraries that we installed in [Getting started](getting-started.md). If you haven't installed those yet, head over to that page and follow the installation instructions. + +::: + +## Installing the elements library + +The [`@react-navigation/elements`](elements.md) library provides a set of components that are designed to work well with React Navigation. We'll use a few of these components in this guide. So let's install it first: + +```bash npm2yarn +npm install @react-navigation/elements +``` + +## Creating a native stack navigator + + + + +`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. 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 + +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +const RootStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + +:::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`, `App.tsx` etc., or used with `AppRegistry.registerComponent`, `Expo.registerRootComponent` etc. + +```js name="Native Stack Example" snack +// In App.js in a new project + +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +const Stack = createNativeStackNavigator(); + +function RootStack() { + return ( + + + + ); +} + +export default function App() { + return ( + + + + ); +} +``` + +:::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. + +::: + + + + +![Basic app using stack navigator](/assets/navigators/stack/basic_stack_nav.png) + +If you run this code, you will see a screen with an empty navigation bar and a grey content area containing your `HomeScreen` component (shown above). The styles you see for the navigation bar and the content area are the default configuration for a stack navigator, we'll learn how to configure those later. + +:::tip + +The casing of the route name doesn't matter -- you can use lowercase `home` or capitalized `Home`, it's up to you. We prefer capitalizing our route names. + +::: + +## Configuring the navigator + +All of the route configuration is specified as props to our navigator. We haven't passed any props to our navigator, so it just uses the default configuration. + +Let's add a second screen to our native stack navigator and configure the `Home` screen to render first: + + + + +```js name="Native Stack Example" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +function DetailsScreen() { + return ( + + Details Screen + + ); +} + +// codeblock-focus-start +const RootStack = createNativeStackNavigator({ + // highlight-next-line + initialRouteName: 'Home', + screens: { + Home: HomeScreen, + Details: DetailsScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + +Now our stack has two _routes_, a `Home` route and a `Details` route. A route can be specified by under the `screens` property. The name of the property under `screens` corresponds to the name of the route we will use to navigate, and the value corresponds to the component it'll render. + + + + +```js name="Native Stack Example" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +function DetailsScreen() { + return ( + + Details Screen + + ); +} + +const Stack = createNativeStackNavigator(); + +// codeblock-focus-start +function RootStack() { + return ( + // highlight-next-line + + + + + ); +} +// codeblock-focus-end + +export default function App() { + return ( + + + + ); +} +``` + +Now our stack has two _routes_, a `Home` route and a `Details` route. A route can be specified by using the `Screen` component. The `Screen` component accepts a `name` prop which corresponds to the name of the route we will use to navigate, and a `component` prop which corresponds to the component it'll render. + +:::warning + +When using the dynamic API, the `component` prop accepts a component, not a render function. Don't pass an inline function (e.g. `component={() => }`), or your component will unmount and remount losing all state when the parent component re-renders. See [Passing additional props](#passing-additional-props) for alternatives. + +::: + + + + +Here, the `Home` route corresponds to the `HomeScreen` component, and the `Details` route corresponds to the `DetailsScreen` component. The initial route for the stack is the `Home` route. Try changing it to `Details` and reload the app (React Native's Fast Refresh won't update changes from `initialRouteName`, as you might expect), notice that you will now see the `Details` screen. Then change it back to `Home` and reload once more. + +## Specifying options + +Each screen in the navigator can specify some options for the navigator, such as the title to render in the header. + + + + +To specify the options, we'll change how we have specified the screen component. Instead of specifying the screen component as the value, we can also specify an object with a `screen` property: + +```js +const RootStack = createNativeStackNavigator({ + initialRouteName: 'Home', + screens: { + Home: { + // highlight-next-line + screen: HomeScreen, + }, + Details: DetailsScreen, + }, +}); +``` + +This will let us specify additional options for the screen. + +Now, we can add an `options` property: + +```js name="Options for Screen" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +function DetailsScreen() { + return ( + + Details Screen + + ); +} + +// codeblock-focus-start +const RootStack = createNativeStackNavigator({ + initialRouteName: 'Home', + screens: { + Home: { + screen: HomeScreen, + // highlight-start + options: { + title: 'Overview', + }, + // highlight-end + }, + Details: DetailsScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + +Sometimes we will want to specify the same options for all of the screens in the navigator. For that, we can add a `screenOptions` property to the configuration: + +```js name="Common options for Screens" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +function DetailsScreen() { + return ( + + Details Screen + + ); +} + +// codeblock-focus-start +const RootStack = createNativeStackNavigator({ + initialRouteName: 'Home', + // highlight-start + screenOptions: { + headerStyle: { backgroundColor: 'tomato' }, + }, + // highlight-end + screens: { + Home: { + screen: HomeScreen, + options: { + title: 'Overview', + }, + }, + Details: DetailsScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + + +Any customization options can be passed in the `options` prop for each screen component: + +```js name="Options for Screen" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +function DetailsScreen() { + return ( + + Details Screen + + ); +} + +const Stack = createNativeStackNavigator(); + +function RootStack() { + return ( + // codeblock-focus-start + + + + + // codeblock-focus-end + ); +} + +export default function App() { + return ( + + + + ); +} +``` + +Sometimes we will want to specify the same options for all of the screens in the navigator. For that, we can pass a `screenOptions` prop to the navigator: + +```js name="Common options for Screens" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +function DetailsScreen() { + return ( + + Details Screen + + ); +} + +const Stack = createNativeStackNavigator(); + +function RootStack() { + return ( + // codeblock-focus-start + + + + + // codeblock-focus-end + ); +} + +export default function App() { + return ( + + + + ); +} +``` + + + + +## Passing additional props + + + + +Passing additional props to a screen is not supported in the static API. + + + + +Sometimes we might want to pass additional props to a screen. We can do that with 2 approaches: + +1. Use [React context](https://react.dev/reference/react/useContext) and wrap the navigator with a context provider to pass data to the screens (recommended). +2. Use a render callback for the screen instead of specifying a `component` prop: + + ```js + + // highlight-next-line + {(props) => } + + ``` + +:::warning + +By default, React Navigation applies optimizations to screen components to prevent unnecessary renders. Using a render callback removes those optimizations. So if you use a render callback, you'll need to ensure that you use [`React.memo`](https://react.dev/reference/react/memo) or [`React.PureComponent`](https://react.dev/reference/react/PureComponent) for your screen components to avoid performance issues. + +::: + + + + +## What's next? + +The natural question at this point is: "how do I go from the `Home` route to the `Details` route?". That is covered in the [next section](navigating.md). + +
+Using with TypeScript + +If you are using TypeScript, you will need to specify the types accordingly. You can check [Type checking with TypeScript](typescript.md) after going through the fundamentals for more details. For now, we won't be covering TypeScript in the examples. + +
+ +## Summary + + + + +- 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`](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`](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`](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-8.x/hiding-tabbar-in-screens.md b/versioned_docs/version-8.x/hiding-tabbar-in-screens.md new file mode 100644 index 0000000000..345d36b62d --- /dev/null +++ b/versioned_docs/version-8.x/hiding-tabbar-in-screens.md @@ -0,0 +1,197 @@ +--- +id: hiding-tabbar-in-screens +title: Hiding tab bar in specific screens +sidebar_label: Hiding tab bar in screens +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Sometimes we may want to hide the tab bar in specific screens in a stack navigator nested in a tab navigator. Let's say we have 5 screens: `Home`, `Feed`, `Notifications`, `Profile` and `Settings`, and your navigation structure looks like this: + + + + +```js name="Hiding tab bar in screens" +const HomeStack = createNativeStackNavigator({ + screens: { + Home: Home, + Profile: Profile, + Settings: Settings, + }, +}); + +const MyTabs = createBottomTabNavigator({ + screens: { + Home: HomeStack, + Feed: Feed, + Notifications: Notifications, + }, +}); + +const Navigation = createStaticNavigation(MyTabs); + +export default function App() { + return ; +} +``` + + + + +```js +function HomeStack() { + return ( + + + + + + ); +} + +function App() { + return ( + + + + + + + + ); +} +``` + + + + +With this structure, when we navigate to the `Profile` or `Settings` screen, the tab bar will still stay visible over those screens. + +But if we want to show the tab bar only on the `Home`, `Feed` and `Notifications` screens, but not on the `Profile` and `Settings` screens, we'll need to change the navigation structure. The easiest way to achieve this is to nest the tab navigator inside the first screen of the stack instead of nesting stack inside tab navigator: + + + + +```js name="Hiding tabbar" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Button } from '@react-navigation/elements'; + +function EmptyScreen() { + return ; +} + +function Home() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + + ); +} + +// codeblock-focus-start +const HomeTabs = createBottomTabNavigator({ + screens: { + Home: Home, + Feed: EmptyScreen, + Notifications: EmptyScreen, + }, +}); + +const RootStack = createNativeStackNavigator({ + screens: { + Home: HomeTabs, + Profile: EmptyScreen, + Settings: EmptyScreen, + }, +}); + +// codeblock-focus-end + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Hiding tabbar" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Button } from '@react-navigation/elements'; + +const Tab = createBottomTabNavigator(); +const Stack = createNativeStackNavigator(); + +function EmptyScreen() { + return ; +} + +function Home() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + + ); +} + +// codeblock-focus-start +function HomeTabs() { + return ( + + + + + + ); +} + +function App() { + return ( + + + + + + + + ); +} +// codeblock-focus-end + +export default App; +``` + + + + +After re-organizing the navigation structure, now if we navigate to the `Profile` or `Settings` screens, the tab bar won't be visible over the screen anymore. diff --git a/versioned_docs/version-8.x/limitations.md b/versioned_docs/version-8.x/limitations.md new file mode 100755 index 0000000000..0958770e95 --- /dev/null +++ b/versioned_docs/version-8.x/limitations.md @@ -0,0 +1,17 @@ +--- +id: limitations +title: Limitations +sidebar_label: Limitations +--- + +As a potential user of the library, it's important to know what you can and cannot do with it. Armed with this knowledge, you may choose to adopt a different library such as [`react-native-navigation`](https://github.com/wix/react-native-navigation) instead. We discuss the high level design decisions in the [pitch & anti-pitch](pitch.md) section, and here we will cover some of the use cases that are either not supported or are so difficult to do that they may as well be impossible. If any of the following limitations are dealbreakers for your app, React Navigation might not be for you. + +## Limited right-to-left (RTL) layout support + +We try to handle RTL layouts properly in React Navigation, however the team working on React Navigation is fairly small and we do not have the bandwidth or processes at the moment to test all changes against RTL layouts. So you might encounter issues with RTL layouts. + +If you like what React Navigation has to offer but are turned off by this constraint, we encourage you to get involved and take ownership of RTL layout support. Please reach out to us on Twitter: [@reactnavigation](https://twitter.com/reactnavigation). + +## Some platform-specific behavior + +React Navigation does not include support for the peek & pop feature available on devices with 3D touch. diff --git a/versioned_docs/version-8.x/link.md b/versioned_docs/version-8.x/link.md new file mode 100644 index 0000000000..93dbaeb0df --- /dev/null +++ b/versioned_docs/version-8.x/link.md @@ -0,0 +1,29 @@ +--- +id: link +title: Link +sidebar_label: Link +--- + +The `Link` component renders a component that can navigate to a screen on press. This renders a `
` tag when used on the Web and uses a `Text` component on other platforms. It preserves the default behavior of anchor tags in the browser such as `Right click -> Open link in new tab"`, `Ctrl+Click`/`⌘+Click` etc. to provide a native experience. + +The path in the `href` for the `` tag is generated based on your [`linking` options](navigation-container.md#linking). + +Example: + +```js +import { Link } from '@react-navigation/native'; + +// ... + +function Home() { + return ( + + Go to Jane's profile + + ); +} +``` + +If you want to use your own custom link component, you can use [`useLinkProps`](use-link-props.md) instead. + +The `Link` component accepts the [same props as `useLinkProps`](use-link-props.md#options) diff --git a/versioned_docs/version-8.x/material-top-tab-navigator.md b/versioned_docs/version-8.x/material-top-tab-navigator.md new file mode 100755 index 0000000000..2942f68776 --- /dev/null +++ b/versioned_docs/version-8.x/material-top-tab-navigator.md @@ -0,0 +1,650 @@ +--- +id: material-top-tab-navigator +title: Material Top Tabs Navigator +sidebar_label: Material Top Tabs +--- + +A material-design themed tab bar on the top of the screen that lets you switch between different routes by tapping the tabs or swiping horizontally. Transitions are animated by default. Screen components for each route are mounted immediately. + + + +This wraps [`react-native-tab-view`](tab-view.md). If you want to use the tab view without React Navigation integration, use the library directly instead. + +## 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/material-top-tabs`](https://github.com/react-navigation/react-navigation/tree/main/packages/material-top-tabs): + +```bash npm2yarn +npm install @react-navigation/material-top-tabs +``` + +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: + +```bash +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 [pods](https://cocoapods.org/) to complete the linking. + +```bash +npx pod-install ios +``` + +## Usage + +To use this navigator, import it from `@react-navigation/material-top-tabs`: + +```js name="Material Top Tab Navigator" 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'; +// codeblock-focus-start +import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; + +// codeblock-focus-end +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function ProfileScreen() { + const navigation = useNavigation(); + + return ( + + Profile Screen + + + ); +} + +// codeblock-focus-start +const MyTabs = createMaterialTopTabNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyTabs); + +export default function App() { + return ; +} +``` + +## API Definition + +### Props + +In addition to the [common props](navigator.md#configuration) shared by all navigators, the material top tabs navigator component 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 +- `none` - do not handle back button + +#### `tabBarPosition` + +Position of the tab bar in the tab view. Possible values are `'top'` and `'bottom'`. Defaults to `'top'`. + +#### `keyboardDismissMode` + +String indicating whether the keyboard gets dismissed in response to a drag gesture. Possible values are: + +- `'auto'` (default): the keyboard is dismissed when the index changes. +- `'on-drag'`: the keyboard is dismissed when a drag begins. +- `'none'`: drags do not dismiss the keyboard. + +#### `initialLayout` + +Object containing the initial height and width of the screens. Passing this will improve the initial rendering performance. For most apps, this is a good default: + +```js +{ + width: Dimensions.get('window').width; +} +``` + +#### `style` + +Style to apply to the tab view container. + +#### `tabBar` + +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'; + +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(); + + return ( + + {state.routes.map((route, index) => { + const { options } = descriptors[route.key]; + const label = + options.tabBarLabel !== undefined + ? options.tabBarLabel + : options.title !== undefined + ? options.title + : route.name; + + const isFocused = state.index === index; + + const onPress = () => { + const event = navigation.emit({ + type: 'tabPress', + target: route.key, + canPreventDefault: true, + }); + + if (!isFocused && !event.defaultPrevented) { + navigation.navigate(route.name, route.params); + } + }; + + const onLongPress = () => { + navigation.emit({ + type: 'tabLongPress', + target: route.key, + }); + }; + + const inputRange = state.routes.map((_, i) => i); + const opacity = position.interpolate({ + inputRange, + 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. + +Note that you **cannot** use the `useNavigation` hook inside the `tabBar` since `useNavigation` is only available inside screens. You get a `navigation` prop for your `tabBar` which you can use instead: + +```js +function MyTabBar({ navigation }) { + return ( + + ); +} +``` + +### Options + +The following [options](screen-options.md) can be used to configure the screens in the navigator: + +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'; + +function HomeScreen() { + return ( + + 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` + +Generic title that can be used as a fallback for `headerTitle` and `tabBarLabel`. + +#### `tabBarLabel` + +Title string of a tab displayed in the tab bar or a function that given `{ focused: boolean, color: string }` returns a React.Node, to display in tab bar. When undefined, scene `title` is used. To hide, see [`tabBarShowLabel`](#tabbarshowlabel) option. + +#### `tabBarAccessibilityLabel` + +Accessibility label for the tab button. This is read by the screen reader when the user taps the tab. It's recommended to set this if you don't have a label for the tab. + +#### `tabBarAllowFontScaling` + +Whether label font should scale to respect Text Size accessibility settings. + +#### `tabBarShowLabel` + +Whether the tab label should be visible. Defaults to `true`. + +#### `tabBarIcon` + +Function that given `{ focused: boolean, color: string }` returns a React.Node, to display in the tab bar. + +#### `tabBarShowIcon` + +Whether the tab icon should be visible. Defaults to `false`. + +#### `tabBarBadge` + +Function that returns a React element to use as a badge for the tab. + +#### `tabBarIndicator` + +Function that returns a React element as the tab bar indicator. + +#### `tabBarIndicatorStyle` + +Style object for the tab bar indicator. + +#### `tabBarIndicatorContainerStyle` + +Style object for the view containing the tab bar indicator. + +#### `tabBarButtonTestID` + +ID to locate this tab button in tests. + +#### `tabBarActiveTintColor` + +Color for the icon and label in the active tab. + +#### `tabBarInactiveTintColor` + +Color for the icon and label in the inactive tabs. + +#### `tabBarPressColor` + +Color for material ripple (Android >= 5.0 only). + +#### `tabBarPressOpacity` + +Opacity for pressed tab (iOS and Android < 5.0 only). + +#### `tabBarBounces` + +Boolean indicating whether the tab bar bounces when overscrolling. + +#### `tabBarScrollEnabled` + +Boolean indicating whether to make the tab bar scrollable. + +If you set this to `true`, you should also specify a width in `tabBarItemStyle` to improve the performance of initial render. + +#### `tabBarLabelStyle` + +Style object for the tab label. + +#### `tabBarItemStyle` + +Style object for the individual tab items. + +#### `tabBarContentContainerStyle` + +Style object for the view containing the tab items. + +#### `tabBarStyle` + +Style object for the tab bar. + +#### `swipeEnabled` + +Boolean indicating whether to enable swipe gestures. Swipe gestures are enabled by default. Passing `false` will disable swipe gestures, but the user can still switch tabs by pressing the tab bar. + +#### `lazy` + +Whether this screen should be lazily rendered. When this is set to `true`, the screen will be rendered as it comes into the viewport. By default all screens are rendered to provide a smoother swipe experience. But you might want to defer the rendering of screens out of the viewport until the user sees them. To enable lazy rendering for this screen, set `lazy` to `true`. + +When you enable `lazy`, the lazy loaded screens will usually take some time to render when they come into the viewport. You can use the `lazyPlaceholder` prop to customize what the user sees during this short period. + +#### `lazyPreloadDistance` + +When `lazy` is enabled, you can specify how many adjacent screens should be preloaded in advance with this prop. This value defaults to `0` which means lazy pages are loaded as they come into the viewport. + +#### `lazyPlaceholder` + +Function that returns a React element to render if this screen hasn't been rendered yet. The `lazy` option also needs to be enabled for this to work. + +This view is usually only shown for a split second. Keep it lightweight. + +By default, this renders `null`. + +#### `sceneStyle` + +Style to apply to the view wrapping each screen. You can pass this to override some default styles such as overflow clipping. + +### 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 + +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(); + + // codeblock-focus-start + React.useEffect(() => { + const unsubscribe = navigation.addListener('tabPress', (e) => { + // Prevent default behavior + e.preventDefault(); + + // Do something manually + // ... + }); + + 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. + +Example: + +```js +React.useEffect(() => { + const unsubscribe = navigation.addListener('tabLongPress', (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 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'; + +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 + +The material top tab navigator exports the following hooks: + +#### `useTabAnimation` + +This hook returns an object containing an animated value that represents the current position of the tabs. This can be used to animate elements based on the swipe position of the tabs, such as the tab indicator: + +```js +import { Animated } from 'react-native'; +import { useTabAnimation } from '@react-navigation/material-top-tabs'; + +function MyView() { + const { position } = useTabAnimation(); + + return ( + + ); +} +``` diff --git a/versioned_docs/version-8.x/migration-guides.md b/versioned_docs/version-8.x/migration-guides.md new file mode 100644 index 0000000000..2ba276c440 --- /dev/null +++ b/versioned_docs/version-8.x/migration-guides.md @@ -0,0 +1,14 @@ +--- +id: migration-guides +title: Migration Guides +sidebar_label: Migration Guides +--- + +This page contains links to pages that will guide you through the process of upgrading React Navigation: + +- [Upgrading from 6.x to 7.x](../version-7.x/upgrading-from-6.x.md) +- [Upgrading from 5.x to 6.x](../version-6.x/upgrading-from-5.x.md) +- [Upgrading from 4.x to 5.x](../version-5.x/upgrading-from-4.x.md) +- [Upgrading from 3.x to 4.x](../version-4.x/upgrading-from-3.x.md) + +If you're upgrading from a version older by multiple major releases, please refer to the migration guides of all the versions in between when upgrading. We recommend configuring TypeScript for your React Navigation setup to make it easier to upgrade as you'll get type errors. diff --git a/versioned_docs/version-8.x/modal.md b/versioned_docs/version-8.x/modal.md new file mode 100755 index 0000000000..b97ab63ad4 --- /dev/null +++ b/versioned_docs/version-8.x/modal.md @@ -0,0 +1,127 @@ +--- +id: modal +title: Opening a modal +sidebar_label: Opening a modal +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +![Modal shown on screen](/assets/modal/modal-demo.gif) + +A modal displays content that temporarily blocks interactions with the main view. + +A modal is like a popup — it usually has a different transition animation, and is intended to focus on one particular interaction or piece of content. + +## Creating a stack with modal screens + +```js name="Modal" snack static2dynamic +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createStackNavigator } from '@react-navigation/stack'; +import { Button } from '@react-navigation/elements'; + +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 + + ); +} + +// codeblock-focus-start +const HomeStack = createStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + options: { + headerShown: false, + }, + }, + Details: { + screen: DetailsScreen, + options: { + headerShown: false, + }, + }, + }, +}); + +const RootStack = createStackNavigator({ + groups: { + Home: { + screens: { + App: { + screen: HomeStack, + options: { title: 'My App' }, + }, + }, + }, + // highlight-start + Modal: { + screenOptions: { + presentation: 'modal', + }, + screens: { + MyModal: ModalScreen, + }, + }, + // highlight-end + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +// codeblock-focus-end +``` + + + +Here, we are creating 2 groups of screens using the `RootStack.Group` component. The first group is for our regular screens, and the second group is for our modal screens. For the modal group, we have specified `presentation: 'modal'` in `screenOptions`. This will apply this option to all the screens inside the group. This option will change the animation for the screens to animate from bottom-to-top rather than right to left. The `presentation` option for stack navigator can be either `card` (default) or `modal`. The `modal` behavior slides the screen in from the bottom and allows the user to swipe down from the top to dismiss it on iOS. + +Instead of specifying this option for a group, it's also possible to specify it for a single screen using the `options` prop on `RootStack.Screen`. + +## Summary + +- 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. + +## Best practices + +Since modals are intended to be on top of other content, there are a couple of things to keep in mind when using modals: + +- Avoid nesting them inside other navigators like tab or drawer. Modal screens should be defined as part of the root stack. +- Modal screens should be the last in the stack - avoid pushing regular screens on top of modals. +- The first screen in a stack appears as a regular screen even if configured as a modal, since there is no screen before it to show behind. So always make sure that modal screens are pushed on top of a regular screen or another modal screen. diff --git a/versioned_docs/version-8.x/more-resources.md b/versioned_docs/version-8.x/more-resources.md new file mode 100755 index 0000000000..8e71db8cf3 --- /dev/null +++ b/versioned_docs/version-8.x/more-resources.md @@ -0,0 +1,15 @@ +--- +id: more-resources +title: More resources +sidebar_label: More resources +--- + +## Talks + +- [Mobile App Development with React Native at Harvard Extension School](https://cs50.harvard.edu/mobile/2018/): Lecture 6 covers React Navigation, includes exercises, slides, and video. + +- [Mobile Navigation at React Alicante](https://www.youtube.com/watch?v=GBhdooVxX6Q): An overview and comparison of the approaches taken by react-native-navigation and react-navigation. + +- [It all starts with navigation at React Native EU](https://www.youtube.com/watch?v=Z0Jl1KCWiag): Explains the evolution of React Native navigation libraries over time and the problems that required building native APIs to solve and what those solutions were. + +- [React Navigation at React Amsterdam](https://www.youtube.com/watch?v=wJJZ9Od8MjM): An introduction to React Navigation. diff --git a/versioned_docs/version-8.x/multiple-drawers.md b/versioned_docs/version-8.x/multiple-drawers.md new file mode 100644 index 0000000000..8b369a690a --- /dev/null +++ b/versioned_docs/version-8.x/multiple-drawers.md @@ -0,0 +1,560 @@ +--- +id: multiple-drawers +title: Multiple drawers +sidebar_label: Multiple drawers +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Sometimes we want to have multiple drawers on the same screen: one on the left and one on the right. This can be achieved in 2 ways: + +1. By using [`react-native-drawer-layout`](drawer-layout.md) directly (Recommended). +2. By [nesting](nesting-navigators.md) 2 [drawer navigators](drawer-navigator.md). + +## Using `react-native-drawer-layout` + +When we have multiple drawers, only one of them shows the list of screens. The second drawer may often be used to show some additional information such as the list of users etc. + +In such cases, we can use [`react-native-drawer-layout`](drawer-layout.md) directly to render the second drawer. The drawer navigator will be used to render the first drawer and can be nested inside the second drawer: + + + + +```js +import * as React from 'react'; +import { View } from 'react-native'; +import { Drawer } from 'react-native-drawer-layout'; +import { createDrawerNavigator } from '@react-navigation/drawer'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +const LeftDrawerScreen = createDrawerNavigator({ + screenOptions: { + drawerPosition: 'left', + }, + screens: { + Home: HomeScreen, + }, +}); + +function RightDrawerScreen() { + const [rightDrawerOpen, setRightDrawerOpen] = React.useState(false); + + return ( + setRightDrawerOpen(true)} + onClose={() => setRightDrawerOpen(false)} + drawerPosition="right" + renderDrawerContent={() => <>{/* Right drawer content */}} + > + + + ); +} + +const Navigation = createStaticNavigation(RightDrawerScreen); + +export default function App() { + return ; +} +``` + + + + +```js +import * as React from 'react'; +import { View } from 'react-native'; +import { Drawer } from 'react-native-drawer-layout'; +import { useNavigation } from '@react-navigation/native'; +import { createDrawerNavigator } from '@react-navigation/drawer'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +const LeftDrawer = createDrawerNavigator(); + +const LeftDrawerScreen = () => { + return ( + + + + ); +}; + +function RightDrawerScreen() { + const [rightDrawerOpen, setRightDrawerOpen] = React.useState(false); + + return ( + setRightDrawerOpen(true)} + onClose={() => setRightDrawerOpen(false)} + drawerPosition="right" + renderDrawerContent={() => <>{/* Right drawer content */}} + > + + + ); +} + +export default function App() { + return ( + + + + ); +} +``` + + + + +But there is one problem. When we call `navigation.openDrawer()` in our `HomeScreen`, it always opens the left drawer. We don't have access to the right drawer via the `navigation` object since it's not a navigator. + +To solve this, we need to use context API to pass down a function to control the right drawer: + + + + +```js +import * as React from 'react'; +import { View } from 'react-native'; +import { Drawer } from 'react-native-drawer-layout'; +import { createDrawerNavigator } from '@react-navigation/drawer'; +import { + useNavigation, + createStaticNavigation, +} from '@react-navigation/native'; +import { Button } from '@react-navigation/elements'; + +const RightDrawerContext = React.createContext(); + +function HomeScreen() { + const { openRightDrawer } = React.useContext(RightDrawerContext); + const navigation = useNavigation(); + + return ( + + + + + ); +} + +const LeftDrawerScreen = createDrawerNavigator({ + screenOptions: { + drawerPosition: 'left', + }, + screens: { + Home: HomeScreen, + }, +}); + +function RightDrawerScreen() { + const [rightDrawerOpen, setRightDrawerOpen] = React.useState(false); + + const value = React.useMemo( + () => ({ + openRightDrawer: () => setRightDrawerOpen(true), + closeRightDrawer: () => setRightDrawerOpen(false), + }), + [] + ); + + return ( + setRightDrawerOpen(true)} + onClose={() => setRightDrawerOpen(false)} + drawerPosition="right" + renderDrawerContent={() => <>{/* Right drawer content */}} + > + + + + + ); +} + +const Navigation = createStaticNavigation(RightDrawerScreen); + +export default function App() { + return ; +} +``` + + + + +```js +import * as React from 'react'; +import { View } from 'react-native'; +import { Drawer } from 'react-native-drawer-layout'; +import { useNavigation } from '@react-navigation/native'; +import { createDrawerNavigator } from '@react-navigation/drawer'; +import { Button } from '@react-navigation/elements'; + +const RightDrawerContext = React.createContext(); + +function HomeScreen() { + const navigation = useNavigation(); + const { openRightDrawer } = React.useContext(RightDrawerContext); + + return ( + + + + + ); +} + +const LeftDrawer = createDrawerNavigator(); + +const LeftDrawerScreen = () => { + return ( + + + + ); +}; + +function RightDrawerScreen() { + const [rightDrawerOpen, setRightDrawerOpen] = React.useState(false); + + const value = React.useMemo( + () => ({ + openRightDrawer: () => setRightDrawerOpen(true), + closeRightDrawer: () => setRightDrawerOpen(false), + }), + [] + ); + + return ( + setRightDrawerOpen(true)} + onClose={() => setRightDrawerOpen(false)} + drawerPosition="right" + renderDrawerContent={() => <>{/* Right drawer content */}} + > + + + + + ); +} + +export default function App() { + return ( + + + + ); +} +``` + + + + +Here, we are using the `RightDrawerContext` to pass down the `openRightDrawer` function to the `HomeScreen`. Then we use `openRightDrawer` to open the right drawer. + +## Nesting 2 drawer navigators + +An alternative approach is to nest 2 [drawer navigators](drawer-navigator.md) inside each other. This is not recommended since it requires creating an additional screen and more nesting - which can make navigating and type checking more verbose. But this can be useful if both navigators include multiple screens. + +Here we have 2 drawer navigators nested inside each other, one is positioned on left and the other on the right: + + + + +```js name="Multiple drawers" snack +import * as React from 'react'; +import { View } from 'react-native'; +import { createDrawerNavigator } from '@react-navigation/drawer'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +const LeftDrawerScreen = createDrawerNavigator({ + screenOptions: { + drawerPosition: 'left', + }, + screens: { + Home: HomeScreen, + }, +}); + +const RightDrawerScreen = createDrawerNavigator({ + screenOptions: { + drawerPosition: 'right', + headerShown: false, + }, + screens: { + HomeDrawer: LeftDrawerScreen, + }, +}); + +const Navigation = createStaticNavigation(RightDrawerScreen); + +export default function App() { + return ; +} +``` + + + + +```js name="Multiple drawers" snack +import * as React from 'react'; +import { View } from 'react-native'; +import { createDrawerNavigator } from '@react-navigation/drawer'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +const LeftDrawer = createDrawerNavigator(); + +const LeftDrawerScreen = () => { + return ( + + + + ); +}; + +const RightDrawer = createDrawerNavigator(); + +const RightDrawerScreen = () => { + return ( + + + + ); +}; + +export default function App() { + return ( + + + + ); +} +``` + + + + + + +But there is one problem. When we call `navigation.openDrawer()` in our `HomeScreen`, it always opens the left drawer since it's the immediate parent of the screen. + +To solve this, we need to use [`navigation.getParent`](navigation-object.md#getparent) to refer to the right drawer which is the parent of the left drawer. So our code would look like: + +```js + + +``` + +However, this means that our button needs to know about the parent navigators, which isn't ideal. If our button is further nested inside other navigators, it'd need multiple `getParent()` calls. To address this, we can use the [`id` prop](navigator.md#id) to identify the parent navigator. + +To customize the contents of the drawer, we can use the [`drawerContent` prop](drawer-navigator.md#drawercontent) to pass in a function that renders a custom component. + +The final code would look like this: + + + + +```js name="Multiple drawers navigators" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { createDrawerNavigator } from '@react-navigation/drawer'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + + + + ); +} + +function RightDrawerContent() { + return ( + + This is the right drawer + + ); +} + +const LeftDrawerScreen = createDrawerNavigator({ + id: 'LeftDrawer', + screenOptions: { + drawerPosition: 'left', + }, + screens: { + Home: HomeScreen, + }, +}); + +const RightDrawerScreen = createDrawerNavigator({ + id: 'RightDrawer', + drawerContent: (props) => , + screenOptions: { + drawerPosition: 'right', + headerShown: false, + }, + screens: { + HomeDrawer: LeftDrawerScreen, + }, +}); + +const Navigation = createStaticNavigation(RightDrawerScreen); + +export default function App() { + return ; +} +``` + + + + +```js name="Multiple drawers navigators" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { createDrawerNavigator } from '@react-navigation/drawer'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + + + + ); +} + +function RightDrawerContent() { + return ( + + This is the right drawer + + ); +} + +const LeftDrawer = createDrawerNavigator(); + +function LeftDrawerScreen() { + return ( + + + + ); +} + +const RightDrawer = createDrawerNavigator(); + +function RightDrawerScreen() { + return ( + } + screenOptions={{ + drawerPosition: 'right', + headerShown: false, + }} + > + + + ); +} + +export default function App() { + return ( + + + + ); +} +``` + + + + +Here, we are passing `"LeftDrawer"` and `"RightDrawer"` strings (you can use any string here) in the `id` prop of the drawer navigators. Then we use `navigation.getParent('LeftDrawer').openDrawer()` to open the left drawer and `navigation.getParent('RightDrawer').openDrawer()` to open the right drawer. + +## Summary + +- To have multiple drawers, you can use [`react-native-drawer-layout`](drawer-layout.md) directly in combination with a drawer navigator. +- The [`drawerPosition`](drawer-layout.md#drawerposition) prop can be used to position the drawer on the right. +- The methods to control the drawer can be passed down using context API when using [`react-native-drawer-layout`](drawer-layout.md). +- When nesting multiple navigators, you can use [`navigation.getParent`](navigation-object.md#getparent) in combination with the [`id` prop](navigator.md#id) to refer to the desired drawer. diff --git a/versioned_docs/version-8.x/native-bottom-tab-navigator.md b/versioned_docs/version-8.x/native-bottom-tab-navigator.md new file mode 100755 index 0000000000..9fddb22320 --- /dev/null +++ b/versioned_docs/version-8.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-8.x/native-stack-navigator.md b/versioned_docs/version-8.x/native-stack-navigator.md new file mode 100755 index 0000000000..9d67379480 --- /dev/null +++ b/versioned_docs/version-8.x/native-stack-navigator.md @@ -0,0 +1,1671 @@ +--- +id: native-stack-navigator +title: Native Stack Navigator +sidebar_label: Native Stack +--- + +Native Stack Navigator provides a way for your app to transition between screens where each new screen is placed on top of a stack. + + + + + +This navigator uses the native APIs `UINavigationController` on iOS and `Fragment` on Android so that navigation built with `createNativeStackNavigator` will behave exactly the same and have the same performance characteristics as apps built natively on top of those APIs. It also offers basic Web support using [`react-native-web`](https://github.com/necolas/react-native-web). + +One thing to keep in mind is that while `@react-navigation/native-stack` offers native performance and exposes native features such as large title on iOS etc., it may not be as customizable as [`@react-navigation/stack`](stack-navigator.md) depending on your needs. So if you need more customization than what's possible in this navigator, consider using `@react-navigation/stack` instead - which is a more customizable JavaScript based implementation. + +## 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/native-stack`](https://github.com/react-navigation/react-navigation/tree/main/packages/native-stack): + +```bash npm2yarn +npm install @react-navigation/native-stack +``` + +## Usage + +To use this navigator, import it from `@react-navigation/native-stack`: + +```js name="Native Stack Navigator" 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'; +// codeblock-focus-start +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +// codeblock-focus-end +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function ProfileScreen() { + return ( + + Profile Screen + + ); +} + +// codeblock-focus-start +const MyStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyStack); + +export default function App() { + return ; +} +``` + +:::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 + +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 { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { Button } from '@react-navigation/elements'; + +// codeblock-focus-start +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +// codeblock-focus-end + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function ProfileScreen() { + const navigation = useNavigation(); + + return ( + + 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 ; +} +``` + +:::warning + +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. + +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. + +Defaults to `automatic` for each edge. + +:::note + +Using both `blurEffect` and `scrollEdgeEffects` (>= iOS 26) simultaneously may cause overlapping effects. + +::: + +Only supported on iOS, starting from iOS 26. + +### Header related options + +The navigator supports following options to configure the header: + +#### `headerBackButtonMenuEnabled` + +Boolean indicating whether to show the menu on longPress of iOS >= 14 back button. Defaults to `true`. + +Only supported on iOS. + +Header back button menu enabled + +#### `headerBackVisible` + +Whether the back button is visible in the header. You can use it to show a back button alongside `headerLeft` if you have specified it. + +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, "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. + +Header back title + +#### `headerBackButtonDisplayMode` + +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). +- "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. + +#### `headerBackTitleStyle` + +Style object for header back title. Supported properties: + +- `fontFamily` +- `fontSize` + +Only supported on iOS. + +Header back title style + +Example: + +```js +headerBackTitleStyle: { + fontSize: 14, + fontFamily: 'Georgia', +}, +``` + +#### `headerBackIcon` + +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 `headerLargeTitleEnabled` is `true` and the edge of any scrollable content reaches the matching edge of the header. + +Supported properties: + +- backgroundColor + +Only supported on iOS. + + + +#### `headerLargeTitleEnabled` + +Whether to enable header with large title which collapses to regular header on scroll. +Defaults to `false`. + +For large title to collapse on scroll, the content of the screen should be wrapped in a scrollable view such as `ScrollView` or `FlatList`. If the scrollable area doesn't fill the screen, the large title won't collapse on scroll. You also need to specify `contentInsetAdjustmentBehavior="automatic"` in your `ScrollView`, `FlatList` etc. + +Only supported on iOS. + + + +#### `headerLargeTitleShadowVisible` + +Whether drop shadow of header is visible when a large title is shown. + +#### `headerLargeTitleStyle` + +Style object for large title in header. Supported properties: + +- `fontFamily` +- `fontSize` +- `fontWeight` +- `color` + +Only supported on iOS. + +Header large title style + +Example: + +```js + headerLargeTitleStyle: { + fontFamily: 'Georgia', + fontSize: 22, + fontWeight: '500', + color: 'blue', + }, +``` + +#### `headerStyle` + +Style object for header. Supported properties: + +- `backgroundColor` + + + +#### `headerShadowVisible` + +Whether to hide the elevation shadow (Android) or the bottom border (iOS) on the header. + +Android: +Header shadow visible Android + +iOS: +Header shadow visible iOS + +#### `headerTransparent` + +Boolean indicating whether the navigation bar is translucent. + +Defaults to `false`. Setting this to `true` makes the header absolutely positioned - so that the header floats over the screen so that it overlaps the content underneath, and changes the background color to `transparent` unless specified in `headerStyle`. + +This is useful if you want to render a semi-transparent header or a blurred background. + +Note that if you don't want your content to appear under the header, you need to manually add a top margin to your content. React Navigation won't do it automatically. + +To get the height of the header, you can use [`HeaderHeightContext`](elements.md#headerheightcontext) with [React's Context API](https://react.dev/reference/react/useContext#contextconsumer) or [`useHeaderHeight`](elements.md#useheaderheight). + +#### `headerBlurEffect` + +Blur effect for the translucent header. The `headerTransparent` option needs to be set to `true` for this to work. + +Supported values: + +- `extraLight` + +- `light` + Header blur effect light + +- `dark` + Header blur effect dark + +- `regular` + Header blur effect regular + +- `prominent` + Header blur effect systemUltraThinMaterial + +- `systemUltraThinMaterial` + Header blur effect systemUltraThinMaterial + +- `systemThinMaterial` + Header blur effect systemThinMaterial + +- `systemMaterial` + Header blur effect systemMaterial + +- `systemThickMaterial` + Header blur effect systemThickMaterial + +- `systemChromeMaterial` + Header blur effect systemChromeMaterial + +- `systemUltraThinMaterialLight` + Header blur effect systemUltraThinMaterialLight + +- `systemThinMaterialLight` + Header blur effect systemThinMaterialLight + +- `systemMaterialLight` + Header blur effect systemMaterialLight + +- `systemThickMaterialLight` + Header blur effect systemThickMaterialLight + +- `systemChromeMaterialLight` + Header blur effect systemChromeMaterialLight + +- `systemUltraThinMaterialDark` + Header blur effect systemUltraThinMaterialDark +- `systemThinMaterialDark` + Header blur effect systemThinMaterialDark + +- `systemMaterialDark` + Header blur effect systemMaterialDark +- `systemThickMaterialDark` + Header blur effect systemThickMaterialDark + +- `systemChromeMaterialDark` + Header blur effect systemChromeMaterialDark + +:::note + +Using both `blurEffect` and `scrollEdgeEffects` (>= iOS 26) simultaneously may cause overlapping effects. + +::: + +Only supported on iOS. + +#### `headerBackground` + +Function which returns a React Element to render as the background of the header. This is useful for using backgrounds such as an image or a gradient. + + Header background + +Example: + +```js + headerBackground: () => ( + + ), +``` + +#### `headerTintColor` + +Tint color for the header. Changes the color of back button and title. + + Header tint color + +#### `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. 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 + +Example: + +```js + headerLeft: () => ( + + ), + headerBackVisible: true, + 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. 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 + +Example: + +```js +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. + +When a function is passed, it receives `tintColor` and`children` in the options object as an argument. The title string is passed in `children`. + +Note that if you render a custom element by passing a function, animations for the title won't work. + +#### `headerTitleAlign` + +How to align the header title. Possible values: + +- `left` + Header title align left + +- `center` + Header title align center + +Defaults to `left` on platforms other than iOS. + +Not supported on iOS. It's always `center` on iOS and cannot be changed. + +#### `headerTitleStyle` + +Style object for header title. Supported properties: + +- `fontFamily` +- `fontSize` +- `fontWeight` +- `color` + + Header title style + +Example: + +```js + headerTitleStyle: { + color: 'blue', + fontSize: 22, + fontFamily: 'Georgia', + fontWeight: 300, + }, +``` + +#### `headerSearchBarOptions` + +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. + +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: + +```js +React.useLayoutEffect(() => { + navigation.setOptions({ + headerSearchBarOptions: { + // search bar options + }, + }); +}, [navigation]); +``` + +Supported properties are: + +##### `ref` + +Ref to manipulate the search input imperatively. It contains the following methods: + +- `focus` - focuses the search bar +- `blur` - removes focus from the search bar +- `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 `systemDefault` which is the same as `sentences` on iOS and `none` on Android. + +##### `autoFocus` + +Whether to automatically focus search bar when it's shown. Defaults to `false`. + +Only supported on Android. + +##### `barTintColor` + +The search field background color. By default bar tint color is translucent. + +Only supported on iOS. + +Header search bar options - Bar tint color + +##### `tintColor` + +The color for the cursor caret and cancel button text. + +Only supported on iOS. + +Header search bar options - Tint color + +##### `cancelButtonText` + +The text to be used instead of default `Cancel` button text. + +Only supported on iOS. **Deprecated** starting from iOS 26. + +##### `disableBackButtonOverride` + +Whether the back button should close search bar's text input or not. Defaults to `false`. + +Only supported on Android. + +##### `hideNavigationBar` + +Boolean indicating whether to hide the navigation bar during searching. + +If left unset, system default is used. + +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. + +If left unset, system default is used. + +Only supported on iOS. + +##### `placement` + +Controls preferred placement of the search bar. Defaults to `automatic`. + +Supported values: + +- `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`) + +Only supported on iOS. + +##### `allowToolbarIntegration` + +Boolean indicating whether the system can place the search bar among other toolbar items on iPhone. + +Set this prop to `false` to prevent the search bar from appearing in the toolbar when `placement` is `automatic`, `integrated`, `integratedButton` or `integratedCentered`. + +Defaults to `true`. If `placement` is set to `stacked`, the value of this prop will be overridden with `false`. + +Only supported on iOS, starting from iOS 26. + +##### `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]); +``` + +#### `headerShown` + +Whether to show the header. The header is shown by default. Setting this to `false` hides the header. + +#### `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. + +### Events + +The navigator can [emit events](navigation-events.md) on certain actions. Supported events are: + +#### `transitionStart` + +This event is fired when the transition animation starts for the current screen. + +Event data: + +- `e.data.closing` - Boolean indicating whether the screen is being opened or closed. + +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. + +Event data: + +- `e.data.closing` - Boolean indicating whether the screen was opened or closed. + +Example: + +```js +React.useEffect(() => { + const unsubscribe = navigation.addListener('transitionEnd', (e) => { + // Do something + }); + + return unsubscribe; +}, [navigation]); +``` + +### Helpers + +The native stack navigator adds the following methods to the navigation object: + +#### `replace` + +Replaces the current screen with a new screen in the stack. The method accepts the following arguments: + +- `name` - _string_ - Name of the route to push onto the stack. +- `params` - _object_ - Screen params to pass to the destination route. + +```js +navigation.replace('Profile', { owner: 'Michaś' }); +``` + +#### `push` + +Pushes a new screen to the top of the stack and navigate to it. The method accepts the following arguments: + +- `name` - _string_ - Name of the route to push onto the stack. +- `params` - _object_ - Screen params to pass to the destination route. + +```js +navigation.push('Profile', { owner: 'Michaś' }); +``` + +#### `pop` + +Pops the current screen from the stack and navigates back to the previous screen. It takes one optional argument (`count`), which allows you to specify how many screens to pop back by. + +```js +navigation.pop(); +``` + +#### `popTo` + +Navigates back to a previous screen in the stack by popping screens after it. The method accepts the following arguments: + +- `name` - _string_ - Name of the route to navigate to. +- `params` - _object_ - Screen params to pass to the destination route. +- `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. + +```js +navigation.popTo('Profile', { owner: 'Michaś' }); +``` + +#### `popToTop` + +Pops all of the screens in the stack except the first one and navigates to it. + +```js +navigation.popToTop(); +``` + +### Hooks + +The native stack navigator exports the following hooks: + +#### `useAnimatedHeaderHeight` + +The hook returns an animated value representing the height of the header. This is similar to [`useHeaderHeight`](elements.md#useheaderheight) but returns an animated value that changed as the header height changes, e.g. when expanding or collapsing large title or search bar on iOS. + +It can be used to animated content along with header height changes. + +```js +import { Animated } from 'react-native'; +import { useAnimatedHeaderHeight } from '@react-navigation/native-stack'; + +const MyView = () => { + const headerHeight = useAnimatedHeaderHeight(); + + return ( + + ); +}; +``` + +## 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-8.x/navigating-without-navigation-prop.md b/versioned_docs/version-8.x/navigating-without-navigation-prop.md new file mode 100755 index 0000000000..0200c83209 --- /dev/null +++ b/versioned_docs/version-8.x/navigating-without-navigation-prop.md @@ -0,0 +1,359 @@ +--- +id: navigating-without-navigation-prop +title: Navigating without the navigation prop +sidebar_label: Navigation Ref +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Sometimes you need to trigger a navigation action from places where you do not have access to the `navigation` object, such as a Redux middleware. For such cases, you can dispatch navigation actions use a [`ref` on the navigation container](navigation-container.md#ref). + +**Do not** use the `ref` if: + +- 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 [Integrating with other tools](deep-linking.md#integrating-with-other-tools) instead. + +**Do** use the `ref` if: + +- You use a state management library such as Redux, where you need to dispatch navigation actions from a middleware. + +Note that it's usually better to trigger navigation from user actions such as button presses, rather than from a Redux middleware. Navigating on user action makes the app feel more responsive and provides better UX. So consider this before using the `ref` for navigation. The `ref` is an escape hatch for scenarios that can't be handled with the existing APIs and should only be used in rare situations. + +## Usage + +You can get access to the root navigation object through a `ref` and pass it to the `RootNavigation` which we will later use to navigate. + + + + +```js +import { createStaticNavigation } from '@react-navigation/native'; +import { navigationRef } from './RootNavigation'; + +/* ... */ + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + + +```js +import { NavigationContainer } from '@react-navigation/native'; +import { navigationRef } from './RootNavigation'; + +export default function App() { + return ( + {/* ... */} + ); +} +``` + + + + +In the next step, we define `RootNavigation`, which is a simple module with functions that dispatch user-defined navigation actions. + +```js +// RootNavigation.js + +import { createNavigationContainerRef } from '@react-navigation/native'; + +export const navigationRef = createNavigationContainerRef(); + +export function navigate(name, params) { + if (navigationRef.isReady()) { + navigationRef.navigate(name, params); + } +} + +// add other navigation functions that you need and export them +``` + +Then, in any of your javascript modules, import the `RootNavigation` and call functions which you exported from it. You may use this approach outside of your React components and, in fact, it works as well when used from within them. + + + + +```js name="Using navigate in any js module" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { + createStaticNavigation, + createNavigationContainerRef, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +const navigationRef = createNavigationContainerRef(); + +// codeblock-focus-start +function navigate(name, params) { + if (navigationRef.isReady()) { + navigationRef.navigate(name, params); + } +} + +// Example of usage in any of js modules +//import * as RootNavigation from './path/to/RootNavigation.js'; + +// ... + +// RootNavigation.navigate('ChatScreen', { userName: 'Lucy' }); + +function Home() { + return ( + + + + ); +} +// codeblock-focus-end + +function Settings({ route }) { + return ( + + Hello {route.params.userName} + + + ); +} + +const RootStack = createNativeStackNavigator({ + screens: { + Home: Home, + Settings: Settings, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Using navigate in any js module" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { + NavigationContainer, + createNavigationContainerRef, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +const navigationRef = createNavigationContainerRef(); + +// codeblock-focus-start +function navigate(name, params) { + if (navigationRef.isReady()) { + navigationRef.navigate(name, params); + } +} + +// Example of usage in any of js modules +//import * as RootNavigation from './path/to/RootNavigation.js'; + +// ... + +// RootNavigation.navigate('ChatScreen', { userName: 'Lucy' }); + +function Home() { + return ( + + + + ); +} +// codeblock-focus-end + +function Settings({ route }) { + return ( + + Hello {route.params.userName} + + + ); +} + +const RootStack = createNativeStackNavigator(); + +export default function App() { + return ( + + + + + + + ); +} +``` + + + + +Apart from `navigate`, you can add other navigation actions: + +```js +import { StackActions } from '@react-navigation/native'; + +// ... + +export function push(...args) { + if (navigationRef.isReady()) { + navigationRef.dispatch(StackActions.push(...args)); + } +} +``` + +Note that a stack navigators needs to be rendered to handle this action. You may want to check the [docs for nesting](nesting-navigators.md#navigating-to-a-screen-in-a-nested-navigator) for more details. + +When writing tests, you may mock the navigation functions, and make assertions on whether the correct functions are called with the correct parameters. + +## Handling initialization + +When using this pattern, you need to keep few things in mind to avoid navigation from failing in your app. + +- The `ref` is set only after the navigation container renders, this can be async when handling deep links +- A navigator needs to be rendered to be able to handle actions, the `ref` won't be ready without a navigator + +If you try to navigate without rendering a navigator or before the navigator finishes mounting, it will print an error and do nothing. So you'll need to add an additional check to decide what to do until your app mounts. + +For an example, consider the following scenario, you have a screen somewhere in the app, and that screen dispatches a redux action on `useEffect`/`componentDidMount`. You are listening for this action in your middleware and try to perform navigation when you get it. This will throw an error, because by this time, the parent navigator hasn't finished mounting and isn't ready. Parent's `useEffect`/`componentDidMount` is always called **after** child's `useEffect`/`componentDidMount`. + +To avoid this, you can use the `isReady()` method available on the ref as shown in the above examples. + + + + +```js name="Handling navigation init" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + createNavigationContainerRef, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +// codeblock-focus-start +const navigationRef = createNavigationContainerRef(); + +function navigate(name, params) { + if (navigationRef.isReady()) { + // Perform navigation if the react navigation is ready to handle actions + navigationRef.navigate(name, params); + } else { + // You can decide what to do if react navigation is not ready + // You can ignore this, or add these actions to a queue you can call later + } +} +// codeblock-focus-end + +function Home() { + return ( + + Home + + + ); +} + +function Profile() { + return ( + + Profile + + ); +} + +const RootStack = createNativeStackNavigator({ + screens: { + Home: Home, + Profile: Profile, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Handling navigation init" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + NavigationContainer, + createNavigationContainerRef, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +const Stack = createNativeStackNavigator(); +// codeblock-focus-start +const navigationRef = createNavigationContainerRef(); + +function navigate(name, params) { + if (navigationRef.isReady()) { + // Perform navigation if the react navigation is ready to handle actions + navigationRef.navigate(name, params); + } else { + // You can decide what to do if react navigation is not ready + // You can ignore this, or add these actions to a queue you can call later + } +} +// codeblock-focus-end + +function Home() { + return ( + + Home + + + ); +} + +function Profile() { + return ( + + Profile + + ); +} + +export default function App() { + return ( + + + + + + + ); +} +``` + + + + +If you're unsure if a navigator is rendered, you can call `navigationRef.current.getRootState()`, and it'll return a valid state object if any navigators are rendered, otherwise it will return `undefined`. diff --git a/versioned_docs/version-8.x/navigating.md b/versioned_docs/version-8.x/navigating.md new file mode 100755 index 0000000000..73057ef519 --- /dev/null +++ b/versioned_docs/version-8.x/navigating.md @@ -0,0 +1,365 @@ +--- +id: navigating +title: Moving between screens +sidebar_label: Moving between screens +--- + +In the previous section, we defined a stack navigator with two routes (`Home` and `Details`), but we didn't learn how to let a user navigate from `Home` to `Details` (although we did learn how to change the _initial_ route in our code, but forcing our users to clone our repository and change the route in our code in order to see another screen is arguably among the worst user experiences one could imagine). + +If this was a web browser, we'd be able to write something like this: + +```js +Go to Details +``` + +Another way to write this would be: + +```js + { + window.location.href = 'details.html'; + }} +> + Go to Details + +``` + +We'll do something similar to the latter, but rather than using a `window.location` global, we'll use the `navigation` object that's accessible in our screen components. + +## Navigating to a new screen + +```js name="Navigating to a new screen" snack +// codeblock-focus-start +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + // highlight-next-line + const navigation = useNavigation(); + + return ( + + Home Screen + // highlight-start + + // highlight-end + + ); +} + +// ... other code from the previous section +// codeblock-focus-end + +function DetailsScreen() { + return ( + + Details Screen + + ); +} + +const RootStack = createNativeStackNavigator({ + initialRouteName: 'Home', + screens: { + Home: HomeScreen, + Details: DetailsScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + +Let's break this down: + +- `navigation` - the `navigation` object is returned from the [`useNavigation`](use-navigation.md) hook (more about this later in ["The navigation object in depth"](navigation-object.md)). +- `navigate('Details')` - we call the `navigate` function (on the `navigation` object — naming is hard!) with the name of the route that we'd like to move the user to. + +:::note + +If we call `navigation.navigate` with a route name that we haven't defined in a navigator, it'll print an error in development builds and nothing will happen in production builds. Said another way, we can only navigate to routes that have been defined on our navigator — we cannot navigate to an arbitrary component. + +::: + +So we now have a stack with two routes: 1) the `Home` route 2) the `Details` route. What would happen if we navigated to the `Details` route again, from the `Details` screen? + +## Navigate to a screen multiple times + +```js name="Navigate to a screen multiple times" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +// codeblock-focus-start +function DetailsScreen() { + const navigation = useNavigation(); + + return ( + + Details Screen + // highlight-start + + // highlight-end + + ); +} +// codeblock-focus-end + +const RootStack = createNativeStackNavigator({ + initialRouteName: 'Home', + screens: { + Home: HomeScreen, + Details: DetailsScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + +If you run this code, you'll notice that when you tap "Go to Details... again", it doesn't do anything! This is because we are already on the Details route. The `navigate` function roughly means "go to this screen", and if you are already on that screen then it makes sense that it would do nothing. + +Let's suppose that we actually _want_ to add another details screen. This is pretty common in cases where you pass in some unique data to each route (more on that later when we talk about `params`!). To do this, we can change `navigate` to `push`. This allows us to express the intent to add another route regardless of the existing navigation history. + +```js name="Navigate to a screen multiple times" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function DetailsScreen() { + const navigation = useNavigation(); + + return ( + + Details Screen + // codeblock-focus-start + + // codeblock-focus-end + + ); +} + +const RootStack = createNativeStackNavigator({ + initialRouteName: 'Home', + screens: { + Home: HomeScreen, + Details: DetailsScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + +Each time you call `push` we add a new route to the navigation stack. When you call `navigate` it only pushes a new route if you're not already on that route. + +## Going back + +The header provided by the native stack navigator will automatically include a back button when it is possible to go back from the active screen (if there is only one screen in the navigation stack, there is nothing that you can go back to, and so there is no back button). + +Sometimes you'll want to be able to programmatically trigger this behavior, and for that, you can use `navigation.goBack()`. + +```js name="Going back" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +// codeblock-focus-start +function DetailsScreen() { + const navigation = useNavigation(); + + return ( + + Details Screen + + // highlight-start + + // highlight-end + + ); +} +// codeblock-focus-end + +const RootStack = createNativeStackNavigator({ + initialRouteName: 'Home', + screens: { + Home: HomeScreen, + Details: DetailsScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + +:::note + +On Android, React Navigation hooks in to the hardware back button and fires the `goBack()` function for you when the user presses it, so it behaves as the user would expect. + +::: + +Another common requirement is to be able to go back _multiple_ screens -- for example, if you are several screens deep in a stack and want to dismiss all of them to go back to the first screen. In this case, we know that we want to go back to `Home` so we can use `popTo('Home')`. Another alternative would be `navigation.popToTop()`, which goes back to the first screen in the stack. + +```js name="Going back to specific screen" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +// codeblock-focus-start +function DetailsScreen() { + const navigation = useNavigation(); + + return ( + + Details Screen + + + // highlight-start + + + // highlight-end + + ); +} +// codeblock-focus-end + +const RootStack = createNativeStackNavigator({ + initialRouteName: 'Home', + screens: { + Home: HomeScreen, + Details: DetailsScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + +## Summary + +- [`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-8.x/navigation-actions.md b/versioned_docs/version-8.x/navigation-actions.md new file mode 100755 index 0000000000..a4480e69e7 --- /dev/null +++ b/versioned_docs/version-8.x/navigation-actions.md @@ -0,0 +1,891 @@ +--- +id: navigation-actions +title: CommonActions reference +sidebar_label: CommonActions +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +A navigation action is an object containing at least a `type` property. Internally, the action can be handled by [routers](custom-routers.md) with the `getStateForAction` method to return a new state from an existing [navigation state](navigation-state.md). + +Each navigation actions can contain at least the following properties: + +- `type` (required) - A string that represents the name of the action. +- `payload` (options) - An object containing additional information about the action. For example, it will contain `name` and `params` for `navigate`. +- `source` (optional) - The key of the route which should be considered as the source of the action. This is used for some actions to determine which route to apply the action on. By default, `navigation.dispatch` adds the key of the route that dispatched the action. +- `target` (optional) - The key of the [navigation state](navigation-state.md) the action should be applied on. + +It's important to highlight that dispatching a navigation action doesn't throw any error when the action is unhandled (similar to when you dispatch an action that isn't handled by a reducer in redux and nothing happens). + +## Common actions + +The library exports several action creators under the `CommonActions` namespace. You should use these action creators instead of writing action objects manually. + +### navigate + +The `navigate` action allows to navigate to a specific route. 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. +- `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 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'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home! + + + ); +} + +function ProfileScreen({ route }) { + const navigation = useNavigation(); + return ( + + Profile! + {route.params.user}'s profile + + ); +} + +const RootStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +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 bring that screen to focus and update its params instead. + +
+Advanced usage + +The `navigate` action can also accepts an object as the argument with the following properties: + +- `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. + +
+ +### reset + +The `reset` action allows to reset the [navigation state](navigation-state.md) to the given state. It takes the following arguments: + +- `state` - _object_ - The new [navigation state](navigation-state.md) object to use. + +```js name="Common actions reset" 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'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home! + + + ); +} + +function ProfileScreen({ route }) { + const navigation = useNavigation(); + return ( + + Profile! + {route.params.user}'s profile + + + ); +} + +const RootStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +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. + +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 { + 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 RootStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +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 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'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home! + + + ); +} + +function ProfileScreen({ route }) { + const navigation = useNavigation(); + return ( + + Profile! + {route.params.user}'s profile + + + ); +} + +const RootStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +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 static2dynamic +import * as React from 'react'; +import { View, Text } from 'react-native'; +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(); + + return ( + + Home! + + + + ); +} + +function ProfileScreen({ route }) { + const navigation = useNavigation(); + const [startTime] = React.useState(Date.now()); + const [endTime, setEndTime] = React.useState(null); + + React.useEffect(() => { + const unsubscribe = navigation.addListener('focus', () => { + setEndTime(Date.now()); + }); + + return () => { + unsubscribe(); + }; + }, [navigation]); + + return ( + + Profile! + {route.params.user}'s profile + Preloaded for: {endTime ? endTime - startTime : 'N/A'}ms + + ); +} + +const RootStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + 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: + +- In a stack navigator ([stack](stack-navigator.md), [native stack](native-stack-navigator.md)), the screen will be rendered off-screen and animated in when you navigate to it. If [`getId`](screen.md#id) is specified, it'll be used for the navigation to identify the preloaded screen. +- In a tab or drawer navigator ([bottom tabs](bottom-tab-navigator.md), [material top tabs](material-top-tab-navigator.md), [drawer](drawer-navigator.md), etc.), the existing screen will be rendered as if `lazy` was set to `false`. Calling `preload` on a screen that is already rendered will not have any effect. + +When a screen is preloaded in a stack navigator, it will have a few limitations: + +- It can't dispatch navigation actions (e.g. `navigate`, `goBack`, etc.). +- It can't update options with `navigation.setOptions`. +- It can't listen to events from the navigator (e.g. `focus`, `tabPress`, etc.). + +The `navigation` object will be updated once you navigate to the screen. So if you have an event listener in a `useEffect` hook, and have a dependency on `navigation`, it will add any listeners when the screen is navigated to: + +```js +React.useEffect(() => { + const unsubscribe = navigation.addListener('tabPress', () => { + // do something + }); + + return () => { + unsubscribe(); + }; +}, [navigation]); +``` + +Similarly, for dispatching actions or updating options, you can check if the screen is focused before doing so: + +```js +if (navigation.isFocused()) { + navigation.setOptions({ title: 'Updated title' }); +} +``` + +### setParams + +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 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'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home! + + + ); +} + +function ProfileScreen({ route }) { + const navigation = useNavigation(); + return ( + + Profile! + {route.params.user}'s profile + + + ); +} + +const RootStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +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 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'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home! + + + ); +} + +function ProfileScreen({ route }) { + const navigation = useNavigation(); + return ( + + Profile! + {route.params.user}'s profile + + + ); +} + +const RootStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + +If the `source` property is explicitly set to `undefined`, it'll replace the params for the focused route. + +### replaceParams + +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 replaceParams" 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'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home! + + + ); +} + +function ProfileScreen({ route }) { + const navigation = useNavigation(); + return ( + + Profile! + {route.params.user}'s profile + + + ); +} + +const RootStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +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 replaceParams" 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'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home! + + + + ); +} + +function ProfileScreen({ route }) { + const navigation = useNavigation(); + return ( + + Profile! + {route.params.user}'s profile + + + ); +} + +const RootStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + +If the `source` property is explicitly set to `undefined`, it'll replace the params for the focused route. diff --git a/versioned_docs/version-8.x/navigation-container.md b/versioned_docs/version-8.x/navigation-container.md new file mode 100644 index 0000000000..536dd908ae --- /dev/null +++ b/versioned_docs/version-8.x/navigation-container.md @@ -0,0 +1,1122 @@ +--- +id: navigation-container +title: NavigationContainer +sidebar_label: NavigationContainer +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The `NavigationContainer` is responsible for managing your app's navigation state and linking your top-level navigator to the app environment. + +The container takes care of platform specific integration and provides various useful functionality: + +1. Deep link integration with the [`linking`](#linking) prop. +2. Notify state changes for [screen tracking](screen-tracking.md), [state persistence](state-persistence.md) etc. +3. Handle system back button on Android by using the [`BackHandler`](https://reactnative.dev/docs/backhandler) API from React Native. + +Usage: + + + + +When using the static API, the component returned by [`createStaticNavigation`](static-configuration.md#createstaticnavigation) is equivalent to the `NavigationContainer` component. + +```js +import { createStaticNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +const Stack = createNativeStackNavigator({ + screens: { + /* ... */ + }, +}); + +const Navigation = createStaticNavigation(Stack); + +export default function App() { + return ; +} +``` + + + + +```js +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +const Stack = createNativeStackNavigator(); + +export default function App() { + return ( + + {/* ... */} + + ); +} +``` + + + + +## Ref + +It's possible to pass a [`ref`](https://react.dev/learn/referencing-values-with-refs) to the container to get access to various helper methods, for example, dispatch navigation actions. This should be used in rare cases when you don't have access to the [`navigation` object](navigation-object.md), such as a Redux middleware. + +Example: + + + + +```js name="Using refs" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +// codeblock-focus-start +import { + createStaticNavigation, + useNavigationContainerRef, +} from '@react-navigation/native'; +// codeblock-focus-end +import { createStackNavigator } from '@react-navigation/stack'; + +const Stack = createStackNavigator({ + initialRouteName: 'Empty', + screens: { + Empty: () => , + Home: HomeScreen, + }, +}); + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +const Navigation = createStaticNavigation(Stack); + +// codeblock-focus-start + +export default function App() { + // highlight-next-line + const navigationRef = useNavigationContainerRef(); // You can also use a regular ref with `React.useRef()` + + return ( + + + + + ); +} +// codeblock-focus-end +``` + + + + +```js name="Using refs" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +// codeblock-focus-start +import { + NavigationContainer, + useNavigationContainerRef, +} from '@react-navigation/native'; +// codeblock-focus-end +import { createStackNavigator } from '@react-navigation/stack'; + +const Stack = createStackNavigator(); + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +// codeblock-focus-start + +export default function App() { + // highlight-next-line + const navigationRef = useNavigationContainerRef(); // You can also use a regular ref with `React.useRef()` + + return ( + + + + + } /> + + + + + ); +} +// codeblock-focus-end +``` + + + + +If you're using a regular ref object, keep in mind that the ref may be initially `null` in some situations (such as when linking is enabled). To make sure that the ref is initialized, you can use the [`onReady`](#onready) callback to get notified when the navigation container finishes mounting. + +Check how to setup `ref` with TypeScript [here](typescript.md#annotating-ref-on-navigationcontainer). + +See the [Navigating without the navigation prop](navigating-without-navigation-prop.md) guide for more details. + +### Methods on the ref + +The ref object includes all of the common navigation methods such as `navigate`, `goBack` etc. See [docs for `CommonActions`](navigation-actions.md) for more details. + +Example: + +```js +navigationRef.navigate(name, params); +``` + +All of these methods will act as if they were called inside the currently focused screen. It's important note that there must be a navigator rendered to handle these actions. + +In addition to these methods, the ref object also includes the following special methods: + +#### `isReady` + +The `isReady` method returns a `boolean` indicating whether the navigation tree is ready. The navigation tree is ready when the `NavigationContainer` contains at least one navigator and all of the navigators have finished mounting. + +This can be used to determine whether it's safe to dispatch navigation actions without getting an error. See [handling initialization](navigating-without-navigation-prop.md#handling-initialization) for more details. + +#### `resetRoot` + +The `resetRoot` method lets you reset the state of the navigation tree to the specified state object: + +```js +navigationRef.resetRoot({ + index: 0, + routes: [{ name: 'Profile' }], +}); +``` + +Unlike the `reset` method, this acts on the root navigator instead of navigator of the currently focused screen. + +#### `getRootState` + +The `getRootState` method returns a [navigation state](navigation-state.md) object containing the navigation states for all navigators in the navigation tree: + +```js +const state = navigationRef.getRootState(); +``` + +Note that the returned `state` object will be `undefined` if there are no navigators currently rendered. + +#### `getCurrentRoute` + +The `getCurrentRoute` method returns the route object for the currently focused screen in the whole navigation tree: + +```js +const route = navigationRef.getCurrentRoute(); +``` + +Note that the returned `route` object will be `undefined` if there are no navigators currently rendered. + +#### `getCurrentOptions` + +The `getCurrentOptions` method returns the options for the currently focused screen in the whole navigation tree: + +```js +const options = navigationRef.getCurrentOptions(); +``` + +Note that the returned `options` object will be `undefined` if there are no navigators currently rendered. + +#### `addListener` + +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: + +```js +const unsubscribe = navigationRef.addListener('state', (e) => { + // You can get the raw navigation state (partial state object of the root navigator) + console.log(e.data.state); + + // Or get the full state object with `getRootState()` + console.log(navigationRef.getRootState()); +}); +``` + +This is analogous to the [`onStateChange`](#onstatechange) method. The only difference is that the `e.data.state` object might contain partial state object unlike the `state` argument in `onStateChange` which will always contain the full state object. + +##### `options` + +The event is triggered whenever the options change for the currently focused screen in the navigation tree: + +```js +const unsubscribe = navigationRef.addListener('options', (e) => { + // You can get the new options for the currently focused screen + console.log(e.data.options); +}); +``` + +## Props + +### `initialState` + +Prop that accepts initial state for the navigator. This can be useful for cases such as deep linking, state persistence etc. + +Example: + + + + +```js + +``` + + + + +```js + + {/* ... */} + +``` + + + + +See [Navigation state reference](navigation-state.md) for more details on the structure of the state object. + +Providing a custom initial state object will override the initial state object obtained via linking configuration or from browser's URL. If you're providing an initial state object, make sure that you don't pass it on web and that there's no deep link to handle. + +Example: + +```js +const initialUrl = await Linking.getInitialURL(); + +if (Platform.OS !== 'web' && initialUrl == null) { + // Only restore state if there's no deep link and we're not on web +} +``` + +See [state persistence guide](state-persistence.md) for more details on how to persist and restore state. + +### `onStateChange` + +:::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. + +::: + +Function that gets called every time [navigation state](navigation-state.md) changes. It receives the new navigation state as the argument. + +You can use it to track the focused screen, persist the navigation state etc. + +Example: + + + +```js + console.log('New state is', state)} +/> +``` + + + + +```js + console.log('New state is', state)} +> + {/* ... */} + +``` + + + + +### `onReady` + +Function which is called after the navigation container and all its children finish mounting for the first time. You can use it for: + +- Making sure that the `ref` is usable. See [docs regarding initialization of the ref](navigating-without-navigation-prop.md#handling-initialization) for more details. +- Hiding your native splash screen + +Example: + + + + +```js + console.log('Navigation container is ready')} +/> +``` + + + + +```js + console.log('Navigation container is ready')} +> + {/* ... */} + +``` + + + + +This callback won't fire if there are no navigators rendered inside the container. + +The current status can be obtained with the [`isReady`](#isready) method on the ref. + +### `onUnhandledAction` + +Function which is called when a navigation action is not handled by any of the navigators. + +By default, React Navigation will show a development-only error message when an action is not handled. You can override the default behavior by providing a custom function. + +Example: + + + + +```js + console.error('Unhandled action', action)} +/> +``` + + + + +```js + console.error('Unhandled action', action)} +> + {/* ... */} + +``` + + + + +### `linking` + +Configuration for linking integration used for deep linking, URL support in browsers etc. + +Example: + + + + +```js +import { createStaticNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +const RootStack = createNativeStackNavigator({ + screens: { + Home: { + screen: Home, + linking: { + path: 'feed/:sort', + }, + }, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +function App() { + const linking = { + prefixes: ['https://example.com', 'example://'], + }; + + return ( + Loading...} + /> + ); +} +``` + + + + +```js +import { NavigationContainer } from '@react-navigation/native'; + +function App() { + const linking = { + prefixes: ['https://example.com', 'example://'], + config: { + screens: { + Home: 'feed/:sort', + }, + }, + }; + + return ( + Loading...} + > + {/* content */} + + ); +} +``` + + + +See [configuring links guide](configuring-links.md) for more details on how to configure deep links and URL integration. + +#### Options + +##### `linking.prefixes` + +URL prefixes to handle. You can provide multiple prefixes to support custom schemes as well as [universal links](https://developer.apple.com/ios/universal-links/). + +Only URLs matching these prefixes will be handled. The prefix will be stripped from the URL before parsing. + +Example: + + + + +```js +Loading...} +/> +``` + + + + +```js + + {/* content */} + +``` + + + + +This is only supported on iOS and Android. + +##### `linking.config` + +Config to fine-tune how to parse the path. + +When using dynamic API, the config object should represent the structure of the navigators in the app. + +See the [configuring links guide](configuring-links.md) for more details on how to configure deep links and URL integration. + +##### `linking.enabled` + +Optional boolean to enable or disable the linking integration. Defaults to `true` if the `linking` prop is specified. + +When using the static API, it's possible to pass `'auto'` to automatically generate the config based on the navigator's structure. See the [configuring links guide](configuring-links.md) for more details. + +##### `linking.getInitialURL` + +By default, linking integrates with React Native's `Linking` API and uses `Linking.getInitialURL()` to provide built-in support for deep linking. However, you might also want to handle links from other sources, such as [Branch](https://help.branch.io/developers-hub/docs/react-native), or push notifications using [Firebase](https://rnfirebase.io/messaging/notifications) etc. + +You can provide a custom `getInitialURL` function where you can return the link which we should use as the initial URL. The `getInitialURL` function should return a `string` if there's a URL to handle, otherwise `undefined`. + +For example, you could do something like following to handle both deep linking and [Firebase notifications](https://rnfirebase.io/messaging/notifications): + + + + +```js +import messaging from '@react-native-firebase/messaging'; + +; +``` + + + + +```js +import messaging from '@react-native-firebase/messaging'; + + + {/* content */} +; +``` + + + + +This option is not available on Web. + +##### `linking.subscribe` + +Similar to [`getInitialURL`](#linkinggetinitialurl), you can provide a custom `subscribe` function to handle any incoming links instead of the default deep link handling. The `subscribe` function will receive a listener as the argument and you can call it with a URL string whenever there's a new URL to handle. It should return a cleanup function where you can unsubscribe from any event listeners that you have setup. + +For example, you could do something like following to handle both deep linking and [Firebase notifications](https://rnfirebase.io/messaging/notifications): + + + + +```js +import messaging from '@react-native-firebase/messaging'; + + listener(url); + + // Listen to incoming links from deep linking + const subscription = Linking.addEventListener('url', onReceiveURL); + + // Listen to firebase push notifications + const unsubscribeNotification = messaging().onNotificationOpenedApp( + (message) => { + const url = message.data?.url; + + if (url) { + // Any custom logic to check whether the URL needs to be handled + //... + + // Call the listener to let React Navigation handle the URL + listener(url); + } + } + ); + + return () => { + // Clean up the event listeners + subscription.remove(); + unsubscribeNotification(); + }; + }, + // highlight-end + }} +/> +``` + + + + +```js +import messaging from '@react-native-firebase/messaging'; + + listener(url); + + // Listen to incoming links from deep linking + const subscription = Linking.addEventListener('url', onReceiveURL); + + // Listen to firebase push notifications + const unsubscribeNotification = messaging().onNotificationOpenedApp( + (message) => { + const url = message.data?.url; + + if (url) { + // Any custom logic to check whether the URL needs to be handled + //... + + // Call the listener to let React Navigation handle the URL + listener(url); + } + } + ); + + return () => { + // Clean up the event listeners + subscription.remove(); + unsubscribeNotification(); + }; + }, + // highlight-end + }} +> + {/* content */} +; +``` + + + + +This option is not available on Web. + +##### `linking.getStateFromPath` + +React Navigation handles deep links and [URLs on Web](web-support.md) by parsing the path to a [navigation state](navigation-state.md) object based on the [linking config](#linkingconfig). You can optionally override the way the parsing happens by providing your own `getStateFromPath` function. + +Example: + + + + +```js + +``` + + + + +```js + + {/* content */} + +``` + + + + +##### `linking.getPathFromState` + +On Web, React Navigation automatically updates the [URL in the browser's address bar](web-support.md) to match the current navigation state by serializing the state to a path based on the [linking config](#linkingconfig). You can optionally override the way the serialization happens by providing your own `getPathFromState` function. + +If you provide a custom [`getStateFromPath`](#linkinggetstatefrompath), you should also provide a custom `getPathFromState` to ensure that the parsing and serialization are consistent with each other for Web support to work correctly. + +Example: + + + + +```js + +``` + + + + +```js + + {/* content */} + +``` + + + + +##### `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`. + + + + +```js +Loading...} +/> +``` + + + + +```js +Loading...} +> + {/* content */} + +``` + + + + +If you have a native splash screen, please use [`onReady`](#onready) instead of `fallback` prop. + +### `documentTitle` + +By default, React Navigation automatically updates the document title on Web to match the `title` option of the focused screen. You can disable it or customize it using this prop. It accepts a configuration object with the following options: + +#### `documentTitle.enabled` + +Whether document title handling should be enabled. Defaults to `true`. + +#### `documentTitle.formatter` + +Custom formatter to use if you want to customize the title text. Defaults to: + +```js +(options, route) => options?.title ?? route?.name; +``` + +Example: + + + + +```js + + `${options?.title ?? route?.name} - My Cool App`, + }} + // highlight-end +/> +``` + + + + +```js + + `${options?.title ?? route?.name} - My Cool App`, + }} + // highlight-end +> + {/* content */} + +``` + + + + +### `theme` + +Custom theme to use for the navigation components such as the header, tab bar etc. See [theming guide](themes.md) for more details and usage guide. + +### `direction` + +The direction of the text configured in the app. Defaults to `'rtl'` when `I18nManager.getConstants().isRTL` returns `true`, otherwise `'ltr'`. + +Supported values: + +- `'ltr'`: Left-to-right text direction for languages like English, French etc. +- `'rtl'`: Right-to-left text direction for languages like Arabic, Hebrew etc. + +Example: + + + + +```js + +``` + + + + +```js + + {/* content */} + +``` + + + + +This is used in various navigators to adjust the content according to the text direction, for example, the drawer in the [drawer navigator](drawer-navigator.md) is positioned on the right side in RTL languages. + +This prop informs React Navigation about the text direction in the app, it doesn't change the text direction by itself. If you intend to support RTL languages, it's important to set this prop to the correct value that's configured in the app. If it doesn't match the actual text direction, the layout might be incorrect. + +On the Web, it may also be necessary to set the `dir` attribute on the root element of the app to ensure that the text direction is correct: + +```html + + + +``` + +The `direction` will be available to use in your own components via the `useLocale` hook: + +```js +import { useLocale } from '@react-navigation/native'; + +function MyComponent() { + const { direction } = useLocale(); + + // Use the direction +} +``` + +### `navigationInChildEnabled` + +:::warning + +This prop exists for backward compatibility reasons. It's not recommended to use it in new projects. It will be removed in a future release. + +::: + +In previous versions of React Navigation, it was possible to navigate to a screen in a nested navigator without specifying the name of the parent screen, i.e. `navigation.navigate(ScreenName)` instead of `navigation.navigate(ParentScreenName, { screen: ScreenName })`. + +However, it has a few issues: + +- It only works if the navigator is already mounted - making navigation coupled to other logic. +- It doesn't work with the TypeScript types. + +The `navigationInChildEnabled` prop allows you to opt-in to this behavior to make it easier to migrate legacy code. It's disabled by default. + +For new code, see [navigating to a screen in a nested navigator](nesting-navigators.md#navigating-to-a-screen-in-a-nested-navigator) instead. + +## Independent navigation containers + +:::warning + +This is an advanced use case. Don't use this unless you are 100% sure that you need it. + +::: + +In most apps, there will be only a single `NavigationContainer`. Nesting multiple `NavigationContainer`s will throw an error. However, in rare cases, it may be useful to have multiple independent navigation trees, e.g. including a mini-app inside a larger app. + +You can wrap the nested `NavigationContainer` with the `NavigationIndependentTree` component to make it independent from the parent navigation tree: + + + + +```js +import { + createStaticNavigation, + NavigationIndependentTree, +} from '@react-navigation/native'; + +/* content */ + +const Navigation = createStaticNavigation(RootStack); + +function NestedApp() { + return ( + // highlight-start + + + + // highlight-end + ); +} +``` + + + + +```js +import { + NavigationContainer, + NavigationIndependentTree, +} from '@react-navigation/native'; + +function NestedApp() { + return ( + // highlight-start + + {/* content */} + + // highlight-end + ); +} +``` + + + + +Doing this disconnects any children navigators from the parent container and doesn't allow navigation between them. + +Avoid using this if you need to integrate with third-party components such as modals or bottom sheets. Consider using a [custom navigator](custom-navigators.md) instead. diff --git a/versioned_docs/version-8.x/navigation-context.md b/versioned_docs/version-8.x/navigation-context.md new file mode 100755 index 0000000000..afb0b0153f --- /dev/null +++ b/versioned_docs/version-8.x/navigation-context.md @@ -0,0 +1,77 @@ +--- +id: navigation-context +title: NavigationContext +sidebar_label: NavigationContext +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +`NavigationContext` provides the `navigation` object (same object as the [navigation](navigation-object.md) prop). In fact, [useNavigation](use-navigation.md) uses this context to get the `navigation` prop. + +Most of the time, you won't use `NavigationContext` directly, as the provided `useNavigation` covers most use cases. But just in case you have something else in mind, `NavigationContext` is available for you to use. + +Example: + +```js name="Navigation context" snack static2dynamic +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 { + useNavigation, + createStaticNavigation, +} 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 + + + ); + // codeblock-focus-start +} +// codeblock-focus-end + +function ProfileScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +const RootStack = createNativeStackNavigator({ + initialRouteName: 'Home', + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +function App() { + return ; +} + +export default App; +``` diff --git a/versioned_docs/version-8.x/navigation-events.md b/versioned_docs/version-8.x/navigation-events.md new file mode 100644 index 0000000000..1d3c6430a1 --- /dev/null +++ b/versioned_docs/version-8.x/navigation-events.md @@ -0,0 +1,473 @@ +--- +id: navigation-events +title: Navigation events +sidebar_label: Navigation events +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +You can listen to various events emitted by React Navigation to get notified of certain events, and in some cases, override the default action. There are few core events such as `focus`, `blur` etc. (documented below) that work for every navigator, as well as navigator specific events that work only for certain navigators. + +Apart from the core events, each navigator can emit their own custom events. For example, stack navigator emits `transitionStart` and `transitionEnd` events, tab navigator emits `tabPress` event etc. You can find details about the events emitted on the individual navigator's documentation. + +## Core events + +Following are the events available in every navigator: + +### `focus` + +This event is emitted when the screen comes into focus. + +For most cases, the [`useFocusEffect`](use-focus-effect.md) hook might be appropriate than adding the listener manually. See [this guide](function-after-focusing-screen.md) for more details to decide which API you should use. + +### `blur` + +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`). + +### `beforeRemove` + +This event is emitted when the user is leaving the screen due to a navigation action. It is possible to prevent the user from leaving the screen by calling `e.preventDefault()` in the event listener. + +```js +React.useEffect( + () => + navigation.addListener('beforeRemove', (e) => { + if (!hasUnsavedChanges) { + return; + } + + // Prevent default behavior of leaving the screen + e.preventDefault(); + + // Prompt the user before leaving the screen + Alert.alert( + 'Discard changes?', + 'You have unsaved changes. Are you sure to discard them and leave the screen?', + [ + { + text: "Don't leave", + style: 'cancel', + onPress: () => { + // Do nothing + }, + }, + { + text: 'Discard', + style: 'destructive', + // If the user confirmed, then we dispatch the action we blocked earlier + // This will continue the action that had triggered the removal of the screen + onPress: () => navigation.dispatch(e.data.action), + }, + ] + ); + }), + [navigation, hasUnsavedChanges] +); +``` + +:::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. + +::: + +## Listening to events + +There are multiple ways to listen to events from the navigators. Each callback registered as an event listener receives an event object as its argument. The event object contains few properties: + +- `data` - Additional data regarding the event passed by the navigator. This can be `undefined` if no data was passed. +- `target` - The route key for the screen that should receive the event. For some events, this maybe `undefined` if the event wasn't related to a specific screen. +- `preventDefault` - For some events, there may be a `preventDefault` method on the event object. Calling this method will prevent the default action performed by the event (such as switching tabs on `tabPress`). Support for preventing actions are only available for certain events like `tabPress` and won't work for all events. + +You can listen to events with the following APIs: + +### `navigation.addListener` + +Inside a screen, you can add listeners on the `navigation` object with the `addListener` method. The `addListener` method takes 2 arguments: type of the event, and a callback to be called on the event. It returns a function that can be called to unsubscribe from the event. + +Example: + +```js +const unsubscribe = navigation.addListener('tabPress', (e) => { + // Prevent default action + e.preventDefault(); +}); +``` + +Normally, you'd add an event listener in `React.useEffect` for function components. For example: + + + + +```js name="navigation.addListener with focus" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function SettingsScreen() { + const navigation = useNavigation(); + + return ( + + Settings Screen + + + ); +} + +// codeblock-focus-start +function ProfileScreen() { + const navigation = useNavigation(); + + React.useEffect(() => { + const unsubscribe = navigation.addListener('focus', () => { + // Screen was focused + }); + return unsubscribe; + }, [navigation]); + + React.useEffect(() => { + const unsubscribe = navigation.addListener('blur', () => { + // Screen was unfocused + }); + return unsubscribe; + }, [navigation]); + + // Rest of the component + // codeblock-focus-end + return ( + + Profile Screen + + + ); + // codeblock-focus-start +} +// codeblock-focus-end + +const SettingsStack = createNativeStackNavigator({ + screens: { + Settings: SettingsScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(SettingsStack); + +export default function App() { + return ; +} +``` + + + + +```js name="navigation.addListener with focus" 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'; + +function SettingsScreen({ navigation }) { + return ( + + Settings Screen + + + ); +} + +// codeblock-focus-start +function ProfileScreen({ navigation }) { + React.useEffect(() => { + const unsubscribe = navigation.addListener('focus', () => { + // Screen was focused + }); + return unsubscribe; + }, [navigation]); + + React.useEffect(() => { + const unsubscribe = navigation.addListener('blur', () => { + // Screen was unfocused + }); + return unsubscribe; + }, [navigation]); + + // Rest of the component + // codeblock-focus-end + return ( + + Profile Screen + + + ); + // codeblock-focus-start +} +// codeblock-focus-end + +const SettingsStack = createNativeStackNavigator(); + +export default function App() { + return ( + + + + + + + ); +} +``` + + + + +The `unsubscribe` function can be returned as the cleanup function in the effect. + +For class components, you can add the event in the `componentDidMount` lifecycle method and unsubscribe in `componentWillUnmount`: + +```js +class Profile extends React.Component { + componentDidMount() { + this._unsubscribe = navigation.addListener('focus', () => { + // do something + }); + } + + componentWillUnmount() { + this._unsubscribe(); + } + + render() { + // Content of the component + } +} +``` + +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 + .getParent('MyTabs') + .addListener('tabPress', (e) => { + // Do something + }); +``` + +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. + +Example: + + + + +```js +const Tab = createBottomTabNavigatior({ + screens: { + Chat: { + screen: Chat, + listeners: { + tabPress: (e) => { + // Prevent default action + e.preventDefault; + }, + }, + }, + }, +}); +``` + + + + +```js + { + // Prevent default action + e.preventDefault(); + }, + }} +/> +``` + + + + +You can also pass a callback which returns the object with listeners. It'll receive `navigation` and `route` as the arguments. + +Example: + + + +```js +const Tab = createBottomTabNavigatior({ + screens: { + Chat: { + screen: Chat, + listeners: ({ navigation, route }) => ({ + tabPress: (e) => { + // Prevent default action + e.preventDefault; + + // Do something with the `navigation` object + navigation.navigate('AnotherPlace'); + }, + }), + }, + }, +}); +``` + + + + +```js + ({ + tabPress: (e) => { + // Prevent default action + e.preventDefault(); + + // Do something with the `navigation` object + navigation.navigate('AnotherPlace'); + }, + })} +/> +``` + + + + +### `screenListeners` prop on the navigator + +You can pass a prop named `screenListeners` to the navigator component, where you can specify listeners for events from all screens for this navigator. This can be useful if you want to listen to specific events regardless of the screen, or want to listen to common events such as `state` which is emitted to all screens. + +Example: + + + + +```js +const Stack = createNativeStackNavigator({ + screenListeners: { + state: (e) => { + // Do something with the state + console.log('state changed', e.data); + }, + }, + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +``` + + + + +```js + { + // Do something with the state + console.log('state changed', e.data); + }, + }} +> + + + +``` + + + + +Similar to `listeners`, you can also pass a function to `screenListeners`. 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 need access to the `navigation` object. + + + + +```js +const Tab = createBottomTabNavigatior({ + screenListeners: ({ navigation }) => ({ + state: (e) => { + // Do something with the state + console.log('state changed', e.data); + + // Do something with the `navigation` object + if (!navigation.canGoBack()) { + console.log("we're on the initial screen"); + } + }, + }), + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +``` + + + + +```js + ({ + state: (e) => { + // Do something with the state + console.log('state changed', e.data); + + // Do something with the `navigation` object + if (!navigation.canGoBack()) { + console.log("we're on the initial screen"); + } + }, + })} +> + + + +``` + + + diff --git a/versioned_docs/version-8.x/navigation-lifecycle.md b/versioned_docs/version-8.x/navigation-lifecycle.md new file mode 100755 index 0000000000..d2a2317652 --- /dev/null +++ b/versioned_docs/version-8.x/navigation-lifecycle.md @@ -0,0 +1,632 @@ +--- +id: navigation-lifecycle +title: Navigation lifecycle +sidebar_label: Navigation lifecycle +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +In a previous section, we worked with a stack navigator that has two screens (`Home` and `Profile`) and learned how to use `navigation.navigate('RouteName')` to navigate between the screens. + +An important question in this context is: what happens with `Home` when we navigate away from it, or when we come back to it? How does a screen find out that a user is leaving it or coming back to it? + +If you are coming to react-navigation from a web background, you may assume that when the user navigates from route `A` to route `B`, `A` will unmount (its `componentWillUnmount` is called) and `A` will mount again when the user comes back to it. While these React lifecycle methods are still valid and are used in React Navigation, their usage differs from the web. This is driven by the more complex needs of mobile navigation. + +## Example scenario + +Consider a stack navigator with 2 screens: `Home` and `Profile`. When we first render the navigator, the `Home` screen is mounted, i.e. its `useEffect` or `componentDidMount` is called. When we navigate to `Profile`, now `Profile` is mounted and its `useEffect` or `componentDidMount` is called. But nothing happens to `Home` - it remains mounted in the stack. The cleanup function returned by `useEffect` or `componentWillUnmount` is not called. + +When we go back from `Profile` to `Home`, `Profile` is unmounted and its `useEffect` cleanup or `componentWillUnmount` is called. But `Home` is not mounted again - it remained mounted the whole time - and its `useEffect` or `componentDidMount` is not called. + +Similar results can be observed (in combination) with other navigators as well. Consider a tab navigator with two tabs, where each tab is a stack navigator: + + + + +```js name="Navigation lifecycle" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Button } from '@react-navigation/elements'; + +function SettingsScreen() { + const navigation = useNavigation(); + + React.useEffect(() => { + console.log('SettingsScreen mounted'); + + return () => console.log('SettingsScreen unmounted'); + }, []); + + return ( + + Settings Screen + + + ); +} + +function ProfileScreen() { + const navigation = useNavigation(); + + React.useEffect(() => { + console.log('ProfileScreen mounted'); + + return () => console.log('ProfileScreen unmounted'); + }, []); + + return ( + + Profile Screen + + + ); +} + +function HomeScreen() { + const navigation = useNavigation(); + + React.useEffect(() => { + console.log('HomeScreen mounted'); + + return () => console.log('HomeScreen unmounted'); + }, []); + + return ( + + Home Screen + + + ); +} + +function DetailsScreen() { + const navigation = useNavigation(); + + React.useEffect(() => { + console.log('DetailsScreen mounted'); + + return () => console.log('DetailsScreen unmounted'); + }, []); + + return ( + + Details Screen + + + ); +} + +// codeblock-focus-start +const SettingsStack = createNativeStackNavigator({ + screens: { + Settings: SettingsScreen, + Profile: ProfileScreen, + }, +}); + +const HomeStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + Details: DetailsScreen, + }, +}); + +const MyTabs = createBottomTabNavigator({ + screenOptions: { + headerShown: false, + }, + screens: { + First: SettingsStack, + Second: HomeStack, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyTabs); + +export default function App() { + return ; +} +``` + + + + +```js name="Navigation lifecycle" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Button } from '@react-navigation/elements'; + +function SettingsScreen() { + const navigation = useNavigation(); + + React.useEffect(() => { + console.log('SettingsScreen mounted'); + + return () => console.log('SettingsScreen unmounted'); + }, []); + + return ( + + Settings Screen + + + ); +} + +function ProfileScreen() { + const navigation = useNavigation(); + + React.useEffect(() => { + console.log('ProfileScreen mounted'); + + return () => console.log('ProfileScreen unmounted'); + }, []); + + return ( + + Profile Screen + + + ); +} + +function HomeScreen() { + const navigation = useNavigation(); + + React.useEffect(() => { + console.log('HomeScreen mounted'); + + return () => console.log('HomeScreen unmounted'); + }, []); + + return ( + + Home Screen + + + ); +} + +function DetailsScreen() { + const navigation = useNavigation(); + + React.useEffect(() => { + console.log('DetailsScreen mounted'); + + return () => console.log('DetailsScreen unmounted'); + }, []); + + return ( + + Details Screen + + + ); +} + +const SettingsStack = createNativeStackNavigator(); +const HomeStack = createNativeStackNavigator(); +const MyTabs = createBottomTabNavigator(); + +// codeblock-focus-start +function FirstScreen() { + return ( + + + + + ); +} + +function SecondScreen() { + return ( + + + + + ); +} + +function Root() { + return ( + + + + + ); +} +// codeblock-focus-end + +export default function App() { + return ( + + + + ); +} +``` + + + + + + +We start on the `HomeScreen` and navigate to `DetailsScreen`. Then we use the tab bar to switch to the `SettingsScreen` and navigate to `ProfileScreen`. After this sequence of operations is done, all 4 of the screens are mounted! If you use the tab bar to switch back to the `HomeStack`, you'll notice you'll be presented with the `DetailsScreen` - the navigation state of the `HomeStack` has been preserved! + +## React Navigation lifecycle events + +Now that we understand how React lifecycle methods work in React Navigation, let's answer the question we asked at the beginning: "How do we find out that a user is leaving (blur) it or coming back to it (focus)?" + +React Navigation emits events to screen components that subscribe to them. We can listen to `focus` and `blur` events to know when a screen comes into focus or goes out of focus respectively. + +Example: + + + + +```js name="Focus and blur" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +// codeblock-focus-start +function ProfileScreen() { + const navigation = useNavigation(); + + React.useEffect(() => { + // highlight-start + const unsubscribe = navigation.addListener('focus', () => { + console.log('ProfileScreen focused'); + }); + // highlight-end + + return unsubscribe; + }, [navigation]); + + React.useEffect(() => { + // highlight-start + const unsubscribe = navigation.addListener('blur', () => { + console.log('ProfileScreen blurred'); + }); + // highlight-end + + return unsubscribe; + }, [navigation]); + + return ( + + Profile Screen + + ); +} +// codeblock-focus-end + +function HomeScreen() { + const navigation = useNavigation(); + + React.useEffect(() => { + const unsubscribe = navigation.addListener('focus', () => { + console.log('HomeScreen focused'); + }); + + return unsubscribe; + }, [navigation]); + + React.useEffect(() => { + const unsubscribe = navigation.addListener('blur', () => { + console.log('HomeScreen blurred'); + }); + + return unsubscribe; + }, [navigation]); + + return ( + + Home Screen + + + ); +} + +const RootStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Focus and blur" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +// codeblock-focus-start +function ProfileScreen() { + const navigation = useNavigation(); + + React.useEffect(() => { + // highlight-start + const unsubscribe = navigation.addListener('focus', () => { + console.log('ProfileScreen focused'); + }); + // highlight-end + + return unsubscribe; + }, [navigation]); + + React.useEffect(() => { + // highlight-start + const unsubscribe = navigation.addListener('blur', () => { + console.log('ProfileScreen blurred'); + }); + // highlight-end + + return unsubscribe; + }, [navigation]); + + return ( + + Profile Screen + + ); +} +// codeblock-focus-end + +function HomeScreen() { + const navigation = useNavigation(); + + React.useEffect(() => { + const unsubscribe = navigation.addListener('focus', () => { + console.log('HomeScreen focused'); + }); + + return unsubscribe; + }, [navigation]); + + React.useEffect(() => { + const unsubscribe = navigation.addListener('blur', () => { + console.log('HomeScreen blurred'); + }); + + return unsubscribe; + }, [navigation]); + + return ( + + Home Screen + + + ); +} + +const Stack = createNativeStackNavigator(); + +function RootStack() { + return ( + + + + + ); +} + +export default function App() { + return ( + + + + ); +} +``` + + + + +See [Navigation events](navigation-events.md) for more details on the available events and the API usage. + +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: + + + + +```js name="Focus effect" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; +// codeblock-focus-start +import { useFocusEffect } from '@react-navigation/native'; + +function ProfileScreen() { + // highlight-start + useFocusEffect( + React.useCallback(() => { + // Do something when the screen is focused + console.log('ProfileScreen focus effect'); + + return () => { + // Do something when the screen is unfocused + // Useful for cleanup functions + console.log('ProfileScreen focus effect cleanup'); + }; + }, []) + ); + // highlight-end + + return ( + + Profile Screen + + ); +} +// codeblock-focus-end + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +const RootStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Focus effect" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; +// codeblock-focus-start +import { useFocusEffect } from '@react-navigation/native'; + +function ProfileScreen() { + // highlight-start + useFocusEffect( + React.useCallback(() => { + // Do something when the screen is focused + console.log('ProfileScreen focus effect'); + + return () => { + // Do something when the screen is unfocused + // Useful for cleanup functions + console.log('ProfileScreen focus effect cleanup'); + }; + }, []) + ); + // highlight-end + + return ( + + Profile Screen + + ); +} +// codeblock-focus-end + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +const Stack = createNativeStackNavigator(); + +function RootStack() { + return ( + + + + + ); +} + +export default function App() { + return ( + + + + ); +} +``` + + + + + + +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 + +- 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-8.x/navigation-object.md b/versioned_docs/version-8.x/navigation-object.md new file mode 100755 index 0000000000..42af46e61b --- /dev/null +++ b/versioned_docs/version-8.x/navigation-object.md @@ -0,0 +1,1913 @@ +--- +id: navigation-object +title: Navigation object reference +sidebar_label: Navigation object +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The `navigation` object contains various convenience functions that dispatch navigation actions. It looks like this: + +- `navigation` + - `navigate` - go to the given screen, this will behave differently based on the navigator + - `goBack` - go back to the previous screen, this will pop the current screen when used in a stack + - `reset` - replace the navigation state of the navigator with the given state + - `preload` - preload a screen in the background before navigating to it + - `setParams` - merge new params onto the route's params + - `dispatch` - send an action object to update the [navigation state](navigation-state.md) + - `setOptions` - update the screen's options + - `isFocused` - check whether the screen is focused + - `canGoBack` - check whether it's possible to go back from the current screen + - `getState` - get the navigation state of the navigator + - `getParent` - get the navigation object of the parent screen, if any + - `addListener` - subscribe to events for the screen + - `removeListener` - unsubscribe from events for the screen + +The `navigation` object can be accessed inside any screen component with the [`useNavigation`](use-navigation.md) hook. It's also passed as a prop only to screens components defined with the dynamic API. + +:::warning + +`setParams`/`setOptions` etc. should only be called in event listeners or `useEffect`/`useLayoutEffect`/`componentDidMount`/`componentDidUpdate` etc. Not during render or in constructor. + +::: + +### Navigator-dependent functions + +There are several additional functions present on `navigation` object based on the kind of the current navigator. + +If the navigator is a stack navigator, several alternatives to `navigate` and `goBack` are provided and you can use whichever you prefer. The functions are: + +- `navigation` + - `replace` - replace the current screen with a new one + - `push` - push a new screen onto the stack + - `pop` - go back in the stack + - `popTo` - go back to a specific screen in the stack + - `popToTop` - go to the top of the stack + +See [Stack navigator helpers](stack-navigator.md#helpers) and [Native Stack navigator helpers](native-stack-navigator.md#helpers) for more details on these methods. + +If the navigator is a tab navigator, the following are also available: + +- `navigation` + - `jumpTo` - go to a specific screen in the tab navigator + +See [Bottom Tab navigator helpers](bottom-tab-navigator.md#helpers) and [Material Top Tab navigator helpers](material-top-tab-navigator.md#helpers) for more details on these methods. + +If the navigator is a drawer navigator, the following are also available: + +- `navigation` + - `jumpTo` - go to a specific screen in the drawer navigator + - `openDrawer` - open the drawer + - `closeDrawer` - close the drawer + - `toggleDrawer` - toggle the state, ie. switch from closed to open and vice versa + +See [Drawer navigator helpers](drawer-navigator.md#helpers) for more details on these methods. + +## Common API reference + +The vast majority of your interactions with the `navigation` object will involve `navigate`, `goBack`, and `setParams`. + +### `navigate` + +The `navigate` method lets us navigate to another screen in your app. It takes the following arguments: + +`navigation.navigate(name, params)` + +- `name` - _string_ - A destination name of the screen in the current or a parent navigator. +- `params` - _object_ - Params to use for the destination route. +- `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="Navigate method" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +import { + useNavigation, + createStaticNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +// codeblock-focus-start +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + This is the home screen of the app + + + ); +} +// codeblock-focus-end + +function ProfileScreen({ route }) { + const navigation = useNavigation(); + + return ( + + Profile Screen + Friends: + {route.params.names[0]} + {route.params.names[1]} + {route.params.names[2]} + + + ); +} + +const Stack = createNativeStackNavigator({ + initialRouteName: 'Home', + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(Stack); + +function App() { + return ; +} + +export default App; +``` + + + + +```js name="Navigate method" 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 +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + This is the home screen of the app + + + ); +} +// codeblock-focus-end + +function ProfileScreen({ route }) { + const navigation = useNavigation(); + + return ( + + Profile Screen + Friends: + {route.params.names[0]} + {route.params.names[1]} + {route.params.names[2]} + + + ); +} + +const Stack = createNativeStackNavigator(); + +function App() { + return ( + + + + + + + ); +} + +export default App; +``` + + + + +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 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. + +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` + +:::warning + +This method is deprecated and will be removed in a future release. It only exists for compatibility purposes. Use `navigate` instead. + +::: + +The `navigateDeprecated` action implements the old behavior of `navigate` from previous versions. + +It takes the following arguments: + +`navigation.navigateDeprecated(name, params)` + +- `name` - _string_ - A destination name of the screen in the current or a parent navigator. +- `params` - _object_ - Params to use for the destination route. + +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 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. + +### `goBack` + +The `goBack` method lets us go back to the previous screen in the navigator. + +By default, `goBack` will go back from the screen that it is called from: + + + + +```js name="Navigate method" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +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.names[0]} + {route.params.names[1]} + {route.params.names[2]} + // highlight-next-line + + + ); +} +// codeblock-focus-end + +const Stack = createNativeStackNavigator({ + initialRouteName: 'Home', + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(Stack); + +function App() { + return ; +} + +export default App; +``` + + + + +```js name="Navigate method" 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'; + +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.names[0]} + {route.params.names[1]} + {route.params.names[2]} + // highlight-next-line + + + ); +} +// codeblock-focus-end + +const Stack = createNativeStackNavigator(); + +function App() { + return ( + + + + + + + ); +} + +export default App; +``` + + + + +### `reset` + +The `reset` method lets us replace the navigator state with a new state: + + + + +```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'; +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 + + + ); +} + +function ProfileScreen({ route }) { + const navigation = useNavigation(); + + return ( + + Profile Screen + Friends: + {route.params.names[0]} + {route.params.names[1]} + {route.params.names[2]} + + + + + + + ); +} + +function SettingsScreen({ route }) { + const navigation = useNavigation(); + + return ( + + Settings screen + {route.params.someParam} + + + + ); +} + +const Stack = createNativeStackNavigator({ + initialRouteName: 'Home', + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + Settings: SettingsScreen, + }, +}); + +const Navigation = createStaticNavigation(Stack); + +function App() { + return ; +} + +export default App; +``` + + + + +```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'; +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 + + + ); +} + +function ProfileScreen({ route }) { + const navigation = useNavigation(); + + return ( + + Profile Screen + Friends: + {route.params.names[0]} + {route.params.names[1]} + {route.params.names[2]} + + + + + + + ); +} + +function SettingsScreen({ route }) { + const navigation = useNavigation(); + + return ( + + Settings screen + {route.params.someParam} + + + + ); +} + +const Stack = createNativeStackNavigator(); + +function App() { + return ( + + + + + + + + ); +} + +export default App; +``` + + + + +The state object specified in `reset` replaces the existing [navigation state](navigation-state.md) with the new one, i.e. removes existing screens and add new ones. If you want to preserve the existing screens when changing the state, you can use [`CommonActions.reset`](navigation-actions.md#reset) with [`dispatch`](#dispatch) instead. + +:::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. + +::: + +### `preload` + +The `preload` method 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 +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { + createStaticNavigation, + useNavigation, + CommonActions, +} 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 ( + + Home! + + + + ); +} +// codeblock-focus-end + +function ProfileScreen({ route }) { + const navigation = useNavigation(); + const [startTime] = React.useState(Date.now()); + const [endTime, setEndTime] = React.useState(null); + + React.useEffect(() => { + const unsubscribe = navigation.addListener('focus', () => { + setEndTime(Date.now()); + }); + + return () => { + unsubscribe(); + }; + }, [navigation]); + + return ( + + Profile! + {route.params.user}'s profile + Preloaded for: {endTime ? endTime - startTime : 'N/A'}ms + + ); +} + +const Stack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(Stack); + +export default function App() { + return ; +} +``` + + + + +```js name="Common actions preload" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { + NavigationContainer, + CommonActions, + 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 ( + + Home! + + + + ); +} +// codeblock-focus-end + +function ProfileScreen({ route }) { + const navigation = useNavigation(); + const [startTime] = React.useState(Date.now()); + const [endTime, setEndTime] = React.useState(null); + + React.useEffect(() => { + const unsubscribe = navigation.addListener('focus', () => { + setEndTime(Date.now()); + }); + + return () => { + unsubscribe(); + }; + }, [navigation]); + + return ( + + Profile! + {route.params.user}'s profile + Preloaded for: {endTime ? endTime - startTime : 'N/A'}ms + + ); +} + +const Stack = createStackNavigator(); + +export default function App() { + 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 slightly differently: + +- In a stack navigator ([stack](stack-navigator.md), [native stack](native-stack-navigator.md)), the screen will be rendered off-screen and animated in when you navigate to it. If [`getId`](screen.md#id) is specified, it'll be used for the navigation to identify the preloaded screen. +- In a tab or drawer navigator ([bottom tabs](bottom-tab-navigator.md), [material top tabs](material-top-tab-navigator.md), [drawer](drawer-navigator.md), etc.), the existing screen will be rendered as if `lazy` was set to `false`. Calling `preload` on a screen that is already rendered will not have any effect. + +When a screen is preloaded in a stack navigator, it will have a few limitations: + +- It can't dispatch navigation actions (e.g. `navigate`, `goBack`, etc.). +- It can't update options with `navigation.setOptions`. +- It can't listen to events from the navigator (e.g. `focus`, `tabPress`, etc.). + +The `navigation` object will be updated once you navigate to the screen. So if you have an event listener in a `useEffect` hook, and have a dependency on `navigation`, it will add any listeners when the screen is navigated to: + +```js +React.useEffect(() => { + const unsubscribe = navigation.addListener('tabPress', () => { + // do something + }); + + return () => { + unsubscribe(); + }; +}, [navigation]); +``` + +Similarly, for dispatching actions or updating options, you can check if the screen is focused before doing so: + +```js +if (navigation.isFocused()) { + navigation.setOptions({ title: 'Updated title' }); +} +``` + +### `setParams` + +The `setParams` method lets us update the params (`route.params`) of the current screen. `setParams` works like React's `setState` - it shallow merges the provided params object with the current params. + + + + +```js name="Navigation object setParams" 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 setParams" 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; +``` + + + + +### `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. + + + + +```js name="Navigation object setOptions" snack +import * as React from 'react'; +import { View, Text, TextInput } from 'react-native'; +import { Button } from '@react-navigation/elements'; +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(); + const [value, onChangeText] = React.useState(route.params.title); + + React.useEffect(() => { + // highlight-start + navigation.setOptions({ + title: value === '' ? 'No title' : value, + }); + // highlight-end + }, [navigation, value]); + + return ( + + + + + ); +} +// 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 setOptions" snack +import * as React from 'react'; +import { View, Text, TextInput } from 'react-native'; +import { Button } from '@react-navigation/elements'; +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(); + const [value, onChangeText] = React.useState(route.params.title); + + React.useEffect(() => { + // highlight-start + navigation.setOptions({ + title: value === '' ? 'No title' : value, + }); + // highlight-end + }, [navigation, value]); + + return ( + + + + + ); +} +// codeblock-focus-end + +const Stack = createNativeStackNavigator(); + +function App() { + return ( + + + + ({ title: route.params.title })} + /> + + + ); +} + +export default App; +``` + + + + +Any options specified here are shallow merged with the options specified when defining the screen. + +When using `navigation.setOptions`, we recommend specifying a placeholder in the screen's `options` prop and update it using `navigation.setOptions`. This makes sure that the delay for updating the options isn't noticeable to the user. It also makes it work with lazy-loaded screens. + +You can also use `React.useLayoutEffect` to reduce the delay in updating the options. But we recommend against doing it if you support web and do server side rendering. + +:::note + +`navigation.setOptions` is intended to provide the ability to update existing options when necessary. It's not a replacement for the `options` prop on the screen. Make sure to use `navigation.setOptions` sparingly only when absolutely necessary. + +::: + +## Navigation events + +Screens can add listeners on the `navigation` object with the `addListener` method. For example, to listen to the `focus` event: + + + + +```js name="Navigation events" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +import { + useNavigation, + createStaticNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function SettingsScreen() { + const navigation = useNavigation(); + + return ( + + Settings Screen + + + ); +} + +// codeblock-focus-start +function ProfileScreen() { + const navigation = useNavigation(); + + React.useEffect( + () => navigation.addListener('focus', () => alert('Screen was focused')), + [navigation] + ); + + React.useEffect( + () => navigation.addListener('blur', () => alert('Screen was unfocused')), + [navigation] + ); + + return ( + + Profile Screen + + + ); +} +// codeblock-focus-end + +const SettingsStack = createNativeStackNavigator({ + screens: { + Settings: SettingsScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(SettingsStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Navigation events" 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'; + +function SettingsScreen() { + const navigation = useNavigation(); + + return ( + + Settings Screen + + + ); +} + +// codeblock-focus-start +function ProfileScreen() { + const navigation = useNavigation(); + + React.useEffect( + () => navigation.addListener('focus', () => alert('Screen was focused')), + [navigation] + ); + + React.useEffect( + () => navigation.addListener('blur', () => alert('Screen was unfocused')), + [navigation] + ); + + return ( + + Profile Screen + + + ); +} +// codeblock-focus-end + +const SettingsStack = createNativeStackNavigator(); + +export default function App() { + return ( + + + + + + + ); +} +``` + + + + +See [Navigation events](navigation-events.md) for more details on the available events and the API usage. + +### `isFocused` + +This method lets us check whether the screen is currently focused. Returns `true` if the screen is focused and `false` otherwise. + +```js +const isFocused = navigation.isFocused(); +``` + +This method doesn't re-render the screen when the value changes and mainly useful in callbacks. You probably want to use [useIsFocused](use-is-focused.md) instead of using this directly, it will return a boolean a prop to indicating if the screen is focused. + +## Advanced API Reference + +The `dispatch` function is much less commonly used, but a good escape hatch if you can't do what you need with the available methods such as `navigate`, `goBack` etc. We recommend to avoid using the `dispatch` method often unless absolutely necessary. + +### `dispatch` + +The `dispatch` method lets us send a navigation action object which determines how the [navigation state](navigation-state.md) will be updated. All of the navigation functions like `navigate` use `dispatch` behind the scenes. + +Note that if you want to dispatch actions you should use the action creators provided in this library instead of writing the action object directly. + +See [Navigation Actions Docs](navigation-actions.md) for a full list of available actions. + +```js +import { CommonActions } from '@react-navigation/native'; + +navigation.dispatch( + CommonActions.navigate({ + name: 'Profile', + params: {}, + }) +); +``` + +When dispatching action objects, you can also specify few additional properties: + +- `source` - The key of the route which should be considered as the source of the action. For example, the `replace` action will replace the route with the given key. By default, it'll use the key of the route that dispatched the action. You can explicitly pass `undefined` to override this behavior. +- `target` - The key of the [navigation state](navigation-state.md) the action should be applied on. By default, actions bubble to other navigators if not handled by a navigator. If `target` is specified, the action won't bubble if the navigator with the same key didn't handle it. + +Example: + +```js +import { CommonActions } from '@react-navigation/native'; + +navigation.dispatch({ + ...CommonActions.navigate('Profile'), + source: 'someRoutekey', + target: 'someStatekey', +}); +``` + +#### Custom action creators + +It's also possible to pass a action creator function to `dispatch`. The function will receive the current state and needs to return a navigation action object to use: + +```js +import { CommonActions } from '@react-navigation/native'; + +navigation.dispatch((state) => { + // Add the home route to the start of the stack + const routes = [{ name: 'Home' }, ...state.routes]; + + return CommonActions.reset({ + ...state, + routes, + index: routes.length - 1, + }); +}); +``` + +You can use this functionality to build your own helpers that you can utilize in your app. Here is an example which implements inserting a screen just before the last one: + +```js +import { CommonActions } from '@react-navigation/native'; + +const insertBeforeLast = (routeName, params) => (state) => { + const routes = [ + ...state.routes.slice(0, -1), + { name: routeName, params }, + state.routes[state.routes.length - 1], + ]; + + return CommonActions.reset({ + ...state, + routes, + index: routes.length - 1, + }); +}; +``` + +Then use it like: + +```js +navigation.dispatch(insertBeforeLast('Home')); +``` + +### `canGoBack` + +This method returns a boolean indicating whether there's any navigation history available in the current navigator, or in any parent navigators. You can use this to check if you can call `navigation.goBack()`: + +```js +if (navigation.canGoBack()) { + navigation.goBack(); +} +``` + +Don't use this method for rendering content as this will not trigger a re-render. This is only intended for use inside callbacks, event listeners etc. + +### `getParent` + +This method returns the navigation object from the parent navigator that the current navigator is nested in. For example, if you have a stack navigator and a tab navigator nested inside the stack, then you can use `getParent` inside a screen of the tab navigator to get the navigation object passed from the stack navigator. + +It accepts an optional ID parameter to refer to a specific parent navigator. For example, if your screen is nested with multiple levels of nesting somewhere under a drawer navigator with the `id` prop as `"LeftDrawer"`, you can directly refer to it without calling `getParent` multiple times. + +To use an ID for a navigator, first pass a unique `id` prop: + + + + +```js +const Drawer = createDrawerNavigator({ + id: 'LeftDrawer', + screens: { + /* content */ + }, +}); +``` + + + + +```js +{/* .. */} +``` + + + + +Then when using `getParent`, instead of: + +```js +// Avoid this +const drawerNavigation = navigation.getParent().getParent(); + +// ... + +drawerNavigation?.openDrawer(); +``` + +You can do: + +```js +// Do this +const drawerNavigation = navigation.getParent('LeftDrawer'); + +// ... + +drawerNavigation?.openDrawer(); +``` + +This approach allows components to not have to know the nesting structure of the navigators. So it's highly recommended that use an `id` when using `getParent`. + +This method will return `undefined` if there is no matching parent navigator. Be sure to always check for `undefined` when using this method. + +### `getState` + +:::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. + +::: + +This method returns the state object of the navigator which contains the screen. Getting the navigator state could be useful in very rare situations. You most likely don't need to use this method. If you do, make sure you have a good reason. + +If you need the state for rendering content, you should use [`useNavigationState`](use-navigation-state.md) instead of this method. diff --git a/versioned_docs/version-8.x/navigation-state.md b/versioned_docs/version-8.x/navigation-state.md new file mode 100644 index 0000000000..43d2a59a7b --- /dev/null +++ b/versioned_docs/version-8.x/navigation-state.md @@ -0,0 +1,127 @@ +--- +id: navigation-state +title: Navigation state reference +sidebar_label: Navigation state +--- + +The navigation state is the state where React Navigation stores the navigation structure and history of the app. It's useful to know about the structure of the navigation state if you need to do advanced operations such as [resetting the state](navigation-actions.md#reset), [providing a custom initial state](navigation-container.md#initialstate) etc. + +It's a JavaScript object which looks like this: + +```js +const state = { + type: 'stack', + key: 'stack-1', + routeNames: ['Home', 'Profile', 'Settings'], + routes: [ + { key: 'home-1', name: 'Home', params: { sortBy: 'latest' } }, + { key: 'settings-1', name: 'Settings' }, + ], + index: 1, + stale: false, +}; +``` + +There are few properties present in every navigation state object: + +- `type` - Type of the navigator that the state belongs to, e.g. `stack`, `tab`, `drawer`. +- `key` - Unique key to identify the navigator. +- `routeNames` - Name of the screens defined in the navigator. This is an unique array containing strings for each screen. +- `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"](#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 [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: + +```js +const state = { + type: 'stack', + key: 'stack-1', + routeNames: ['Home', 'Profile', 'Settings'], + routes: [ + { + key: 'home-1', + name: 'Home', + state: { + key: 'tab-1', + routeNames: ['Feed', 'Library', 'Favorites'], + routes: [ + { key: 'feed-1', name: 'Feed', params: { sortBy: 'latest' } }, + { key: 'library-1', name: 'Library' }, + { key: 'favorites-1', name: 'Favorites' }, + ], + index: 0, + }, + }, + { key: 'settings-1', name: 'Settings' }, + ], + index: 1, +}; +``` + +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. + +## Stale 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. + +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({ + index: 0, + routes: [{ name: 'Home' }, { name: 'Profile' }], +}); +``` + +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: + +```js +const state = { + routes: [{ name: 'Home' }, { name: 'Profile' }], +}; +``` + +After rehydration, it'll look something like this: + +```js +const state = { + type: 'stack', + key: 'stack-1', + routeNames: ['Home', 'Profile', 'Settings'], + routes: [ + { key: 'home-1', name: 'Home' }, + { key: 'settings-1', name: 'Settings' }, + ], + index: 1, + stale: false, +}; +``` + +Here, React Navigation filled in the missing bits such as keys, route names, index etc. + +It's also possible to provide invalid data such as non-existent screens and it'll be fixed automatically. While it's not recommended to write code with invalid state objects, it can be super useful if you do things like [state persistence](state-persistence.md), where the configured screens might have changed after an update, which could cause problems if React Navigation didn't fix the state object automatically. + +:::tip + +If you want React Navigation to fix invalid state, you need to make sure that you don't have `stale: false` in the state object. State objects with `stale: false` are assumed to be valid state objects and React Navigation won't attempt to fix them. + +::: + +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-8.x/navigator.md b/versioned_docs/version-8.x/navigator.md new file mode 100644 index 0000000000..f34638f4be --- /dev/null +++ b/versioned_docs/version-8.x/navigator.md @@ -0,0 +1,463 @@ +--- +id: navigator +title: Navigator +sidebar_label: Navigator +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +A navigator is responsible for managing and rendering a set of screens. It can be created using the `createXNavigator` functions, e.g. [`createStackNavigator`](stack-navigator.md), [`createNativeStackNavigator`](native-stack-navigator.md), [`createBottomTabNavigator`](bottom-tab-navigator.md), [`createMaterialTopTabNavigator`](material-top-tab-navigator.md), [`createDrawerNavigator`](drawer-navigator.md) etc.: + + + + +```js +const MyStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +``` + + + + +```js +const Stack = createNativeStackNavigator(); + +function MyStack() { + return ( + + + + + ); +} +``` + + + + +In addition to the built-in navigators, it's also possible to build your custom navigators or use third-party navigators. See [custom navigators](custom-navigators.md) for more details. + +## Configuration + +Different navigators accept different configuration options. You can find the list of options for each navigator in their respective documentation. + +There is a set of common configurations that are shared across all navigators: + +### ID + +Optional unique ID for the navigator. This can be used with [`navigation.getParent`](navigation-object.md#getparent) to refer to this navigator in a child navigator. + + + + +```js +const MyStack = createNativeStackNavigator({ + // highlight-next-line + id: 'RootStack', + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +``` + + + + +```js +const Stack = createNativeStackNavigator(); + +function MyStack() { + return ( + + + + + ); +} +``` + + + + +### Initial route name + +The name of the route to render on the first load of the navigator. + + + + +```js +const MyStack = createNativeStackNavigator({ + // highlight-next-line + initialRouteName: 'Home', + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +``` + + + + +```js +const Stack = createNativeStackNavigator(); + +function MyStack() { + return ( + + + + + ); +} +``` + + + + +### Layout + +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. + +It takes a function that returns a React element: + + + + +```js +const MyStack = createNativeStackNavigator({ + // highlight-start + layout: ({ children, state, descriptors, navigation }) => ( + + + {children} + + ), + // highlight-end + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +``` + + + + +```js +const Stack = createNativeStackNavigator(); + +function MyStack() { + return ( + ( + + + {children} + + )} + // highlight-end + > + + + + ); +} +``` + + + + +### Screen options + +Default options to use for all the screens in the navigator. It accepts either an object or a function returning an object: + + + + +```js +const MyStack = createNativeStackNavigator({ + // highlight-start + screenOptions: { + headerShown: false, + }, + // highlight-end + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +``` + + + + +```js +const Stack = createNativeStackNavigator(); + +function MyStack() { + return ( + + + + + ); +} +``` + + + + +See [Options for screens](screen-options.md) for more details and examples. + +### Screen listeners + +Event listeners can be used to subscribe to various events emitted for the screen. See [`screenListeners` prop on the navigator](navigation-events.md#screenlisteners-prop-on-the-navigator) for more details. + +### Screen layout + +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: + + + + +```js +const MyStack = createNativeStackNavigator({ + // highlight-start + screenLayout: ({ children }) => ( + + + Loading… + + } + > + {children} + + + ), + // highlight-end + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +``` + + + + +```js +const Stack = createNativeStackNavigator(); + +function MyStack() { + return ( + ( + + + Loading… + + } + > + {children} + + + )} + // highlight-end + > + + + + ); +} +``` + + + + +### 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-8.x/nesting-navigators.md b/versioned_docs/version-8.x/nesting-navigators.md new file mode 100755 index 0000000000..4264cc57dc --- /dev/null +++ b/versioned_docs/version-8.x/nesting-navigators.md @@ -0,0 +1,1124 @@ +--- +id: nesting-navigators +title: Nesting navigators +sidebar_label: Nesting navigators +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Nesting navigators means rendering a navigator inside a screen of another navigator, for example: + + + + +```js name="Nested navigators" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Button } from '@react-navigation/elements'; + +function ProfileScreen() { + return ( + + Profile Screen + + ); +} + +function FeedScreen() { + const navigation = useNavigation(); + + return ( + + Feed Screen + + + ); +} + +function MessagesScreen() { + return ( + + Messages Screen + + ); +} + +// codeblock-focus-start +const HomeTabs = createBottomTabNavigator({ + screens: { + Feed: FeedScreen, + Messages: MessagesScreen, + }, +}); + +const RootStack = createNativeStackNavigator({ + screens: { + Home: { + // highlight-next-line + screen: HomeTabs, + options: { + headerShown: false, + }, + }, + Profile: ProfileScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Nested navigators" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Button } from '@react-navigation/elements'; + +function ProfileScreen() { + return ( + + Profile Screen + + ); +} + +function FeedScreen() { + const navigation = useNavigation(); + + return ( + + Feed Screen + + + ); +} + +function MessagesScreen() { + return ( + + Messages Screen + + ); +} + +const Tab = createBottomTabNavigator(); +const Stack = createNativeStackNavigator(); + +// codeblock-focus-start +function HomeTabs() { + return ( + + + + + ); +} + +function RootStack() { + return ( + + + + + ); +} +// codeblock-focus-end + +export default function App() { + return ( + + + + ); +} +``` + + + + +In the above example, `HomeTabs` contains a tab navigator. It is also used for the `Home` screen in your stack navigator in `RootStack`. So here, a tab navigator is nested inside a stack navigator: + +- `RootStack` (Stack navigator) + - `HomeTabs` (Tab navigator) + - `Feed` (screen) + - `Messages` (screen) + - `Profile` (screen) + +Nesting navigators work very much like nesting regular components. To achieve the behavior you want, it's often necessary to nest multiple navigators. + +## How nesting navigators affects the behaviour + +When nesting navigators, there are some things to keep in mind: + +### Each navigator keeps its own navigation history + +For example, when you press the back button when inside a screen in a nested stack navigator, it'll go back to the previous screen inside the nested stack even if there's another navigator as the parent. + +### Each navigator has its own options + +For example, specifying a `title` option in a screen nested in a child navigator won't affect the title shown in a parent navigator. + +If you want to achieve this behavior, see the guide for [screen options with nested navigators](screen-options-resolution.md#setting-parent-screen-options-based-on-child-navigators-state). this could be useful if you are rendering a tab navigator inside a stack navigator and want to show the title of the active screen inside the tab navigator in the header of the stack navigator. + +### Each screen in a navigator has its own params + +For example, any `params` passed to a screen in a nested navigator are in the `route` object of that screen and aren't accessible from a screen in a parent or child navigator. + +If you need to access params of the parent screen from a child screen, you can use [React Context](https://react.dev/reference/react/useContext) to expose params to children. + +### Navigation actions are handled by current navigator and bubble up if couldn't be handled + +For example, if you're calling `navigation.goBack()` in a nested screen, it'll only go back in the parent navigator if you're already on the first screen of the navigator. Other actions such as `navigate` work similarly, i.e. navigation will happen in the nested navigator and if the nested navigator couldn't handle it, then the parent navigator will try to handle it. In the above example, when calling `navigate('Messages')`, inside `Feed` screen, the nested tab navigator will handle it, but if you call `navigate('Settings')`, the parent stack navigator will handle it. + +### Navigator specific methods are available in the navigators nested inside + +For example, if you have a stack inside a drawer navigator, the drawer's `openDrawer`, `closeDrawer`, `toggleDrawer` methods etc. will also be available on the `navigation` object in the screens inside the stack navigator. But say you have a stack navigator as the parent of the drawer, then the screens inside the stack navigator won't have access to these methods, because they aren't nested inside the drawer. + +Similarly, if you have a tab navigator inside stack navigator, the screens in the tab navigator will get the `push` and `replace` methods for stack in their `navigation` object. + +If you need to dispatch actions to the nested child navigators from a parent, you can use [`navigation.dispatch`](navigation-object.md#dispatch): + +```js +navigation.dispatch(DrawerActions.toggleDrawer()); +``` + +### Nested navigators don't receive parent's events + +For example, if you have a stack navigator nested inside a tab navigator, the screens in the stack navigator won't receive the events emitted by the parent tab navigator such as (`tabPress`) when using `navigation.addListener`. + +To receive events from the parent navigator, you can explicitly listen to parent's events with [`navigation.getParent`](navigation-object.md#getparent): + + + + +```js name="Events from parent" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Button } from '@react-navigation/elements'; + +function ProfileScreen() { + return ( + + Profile Screen + + ); +} + +function FeedScreen() { + const navigation = useNavigation(); + + return ( + + Feed Screen + + + ); +} + +function MessagesScreen() { + const navigation = useNavigation(); + + React.useEffect(() => { + // codeblock-focus-start + const unsubscribe = navigation + .getParent('MyTabs') + .addListener('tabPress', (e) => { + // Do something + alert('Tab pressed!'); + }); + // codeblock-focus-end + + return unsubscribe; + }, [navigation]); + + return ( + + Messages Screen + + ); +} + +const HomeStack = createNativeStackNavigator({ + screens: { + Feed: FeedScreen, + Messages: MessagesScreen, + }, +}); + +const RootTabs = createBottomTabNavigator({ + id: 'MyTabs', + screens: { + Home: { + screen: HomeStack, + options: { + headerShown: false, + }, + }, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(RootTabs); + +export default function App() { + return ; +} +``` + + + + +```js name="Events from parent" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Button } from '@react-navigation/elements'; + +function ProfileScreen() { + return ( + + Profile Screen + + ); +} + +function FeedScreen() { + const navigation = useNavigation(); + + return ( + + Feed Screen + + + ); +} + +function MessagesScreen() { + const navigation = useNavigation(); + + React.useEffect(() => { + // codeblock-focus-start + const unsubscribe = navigation + .getParent('MyTabs') + .addListener('tabPress', (e) => { + // Do something + alert('Tab pressed!'); + }); + // codeblock-focus-end + + return unsubscribe; + }, [navigation]); + + return ( + + Messages Screen + + ); +} + +const Tab = createBottomTabNavigator(); +const Stack = createNativeStackNavigator(); + +function HomeStack() { + return ( + + + + + ); +} + +function RootTabs() { + return ( + + + + + ); +} + +export default function App() { + return ( + + + + ); +} +``` + + + + +Here `'MyTabs'` refers to the value you pass in the `id` of the parent tab navigator whose event you want to listen to. + +### Parent navigator's UI is rendered on top of child navigator + +For example, when you nest a stack navigator inside a drawer navigator, you'll see that the drawer appears above the stack navigator's header. However, if you nest the drawer navigator inside a stack, the drawer will appear below the header of the stack. This is an important point to consider when deciding how to nest your navigators. + +In your app, you will probably use these patterns depending on the behavior you want: + +- Tab navigator nested inside the initial screen of stack navigator - New screens cover the tab bar when you push them. +- Drawer navigator nested inside the initial screen of stack navigator with the initial screen's stack header hidden - The drawer can only be opened from the first screen of the stack. +- Stack navigators nested inside each screen of drawer navigator - The drawer appears over the header from the stack. +- Stack navigators nested inside each screen of tab navigator - The tab bar is always visible. Usually pressing the tab again also pops the stack to top. + +## Navigating to a screen in a nested navigator + +Consider the following example: + + + + +```js name="Navigating to nested screen" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + + ); +} + +function FeedScreen() { + const navigation = useNavigation(); + + return ( + + Feed Screen + + + ); +} + +function MessagesScreen() { + const navigation = useNavigation(); + + return ( + + Messages Screen + + + ); +} + +// codeblock-focus-start +const MoreTabs = createBottomTabNavigator({ + screens: { + Feed: FeedScreen, + Messages: MessagesScreen, + }, +}); + +const RootStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + More: { + screen: MoreTabs, + options: { + headerShown: false, + }, + }, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Navigating to nested screen" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + + ); +} + +function FeedScreen() { + const navigation = useNavigation(); + + return ( + + Feed Screen + + + ); +} + +function MessagesScreen() { + const navigation = useNavigation(); + + return ( + + Messages Screen + + + ); +} + +const Tab = createBottomTabNavigator(); +const Stack = createNativeStackNavigator(); + +// codeblock-focus-start +function MoreTabs() { + return ( + + + + + ); +} + +function RootStack() { + return ( + + + + + ); +} +// codeblock-focus-end + +export default function App() { + return ( + + + + ); +} +``` + + + + +Here, you might want to navigate to the `More` screen (which contains `MoreTabs`) from your `HomeScreen` component: + +```js +navigation.navigate('More'); +``` + +It works, and the initial screen inside the `MoreTabs` component is shown, which is `Feed`. But sometimes you may want to control the screen that should be shown upon navigation. To achieve it, you can pass the name of the screen in params: + +```js +navigation.navigate('More', { screen: 'Messages' }); +``` + +Now, the `Messages` screen will be rendered instead of `Feed` upon navigation. + +### Passing params to a screen in a nested navigator + +You can also pass params by specifying a `params` key: + + + + +```js name="Navigating to nested screen" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function FeedScreen() { + const navigation = useNavigation(); + + return ( + + Feed Screen + + + ); +} + +function MessagesScreen({ route }) { + const navigation = useNavigation(); + + return ( + + Messages Screen + User: {route.params.user} + + + ); +} + +const MoreTabs = createBottomTabNavigator({ + screens: { + Feed: FeedScreen, + Messages: MessagesScreen, + }, +}); + +const RootStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + More: { + screen: MoreTabs, + options: { + headerShown: false, + }, + }, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Navigating to nested screen" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function FeedScreen() { + const navigation = useNavigation(); + + return ( + + Feed Screen + + + ); +} + +function MessagesScreen({ route }) { + const navigation = useNavigation(); + + return ( + + Messages Screen + User: {route.params.user} + + + ); +} + +const Tab = createBottomTabNavigator(); +const Stack = createNativeStackNavigator(); + +function MoreTabs() { + return ( + + + + + ); +} + +function RootStack() { + return ( + + + + + ); +} + +export default function App() { + return ( + + + + ); +} +``` + + + + +If the navigator was already rendered, navigating to another screen will push a new screen in case of stack navigator. + +You can follow a similar approach for deeply nested screens. Note that the second argument to `navigate` here is just `params`, so you can do something like: + +```js +navigation.navigate('Home', { + screen: 'Settings', + params: { + screen: 'Sound', + params: { + screen: 'Media', + }, + }, +}); +``` + +In the above case, you're navigating to the `Media` screen, which is in a navigator nested inside the `Sound` screen, which is in a navigator nested inside the `Settings` screen. + +:::warning + +The `screen` and related params are reserved for internal use and are managed by React Navigation. While you can access `route.params.screen` etc. in the parent screens, relying on them may lead to unexpected behavior. + +::: + +### Rendering initial route defined in the navigator + +By default, when you navigate a screen in the nested navigator, the specified screen is used as the initial screen and the `initialRouteName` prop on the navigator is ignored. + +If you need to render the initial route specified in the navigator, you can disable the behaviour of using the specified screen as the initial screen by setting `initial: false`: + +```js +navigation.navigate('Root', { + screen: 'Settings', + initial: false, +}); +``` + +This affects what happens when pressing the back button. When there's an initial screen, the back button will take the user there. + +## Avoiding multiple headers when nesting + +When nesting multiple stack, drawer or bottom tab navigators, headers from both child and parent navigators would be shown. However, usually it's more desirable to show the header in the child navigator and hide the header in the screen of the parent navigator. + +To achieve this, you can hide the header in the screen containing the navigator using the `headerShown: false` option. + +For example: + + + + +```js name="Nested navigators" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Button } from '@react-navigation/elements'; + +function ProfileScreen() { + return ( + + Profile Screen + + ); +} + +function FeedScreen() { + const navigation = useNavigation(); + + return ( + + Feed Screen + + + ); +} + +function MessagesScreen() { + return ( + + Messages Screen + + ); +} + +// codeblock-focus-start +const HomeTabs = createBottomTabNavigator({ + screens: { + Feed: FeedScreen, + Messages: MessagesScreen, + }, +}); + +const RootStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeTabs, + options: { + // highlight-next-line + headerShown: false, + }, + }, + Profile: ProfileScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Nested navigators" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Button } from '@react-navigation/elements'; + +function ProfileScreen() { + return ( + + Profile Screen + + ); +} + +function FeedScreen() { + const navigation = useNavigation(); + + return ( + + Feed Screen + + + ); +} + +function MessagesScreen() { + return ( + + Messages Screen + + ); +} + +const Tab = createBottomTabNavigator(); +const Stack = createNativeStackNavigator(); + +// codeblock-focus-start +function HomeTabs() { + return ( + + + + + ); +} + +function RootStack() { + return ( + + + + + ); +} +// codeblock-focus-end + +export default function App() { + return ( + + + + ); +} +``` + + + + +In these examples, we have used a bottom tab navigator directly nested inside another stack navigator, but the same principle applies when there are other navigators in the middle, for example: stack navigator inside a tab navigator which is inside another stack navigator, stack navigator inside drawer navigator etc. + +If you don't want headers in any of the navigators, you can specify `headerShown: false` in all of the navigators: + + + + +```js +const HomeTabs = createBottomTabNavigator({ + // highlight-start + screenOptions: { + headerShown: false, + }, + // highlight-end + screens: { + Feed: FeedScreen, + Messages: MessagesScreen, + }, +}); + +const RootStack = createStackNavigator({ + // highlight-start + screenOptions: { + headerShown: false, + }, + // highlight-end + screens: { + Home: HomeTabs, + Profile: ProfileScreen, + }, +}); +``` + + + + +```js +function HomeTabs() { + return ( + + + + + ); +} + +function RootStack() { + return ( + + + + + ); +} +``` + + + + +## Best practices when nesting + +We recommend to reduce nesting navigators to minimal. Try to achieve the behavior you want with as little nesting as possible. Nesting has many downsides: + +- It results in deeply nested view hierarchy which can cause memory and performance issues in lower end devices +- Nesting same type of navigators (e.g. tabs inside tabs, drawer inside drawer etc.) might lead to a confusing UX +- With excessive nesting, code becomes difficult to follow when navigating to nested screens, configuring deep link etc. + +Think of nesting navigators as a way to achieve the UI you want rather than a way to organize your code. If you want to create separate group of screens for organization, instead of using separate navigators, you can use the [`Group`](group.md) component for dynamic configuration or [`groups` property](static-configuration.md#groups) for static configuration. + + + + +```js +const MyStack = createStackNavigator({ + screens: { + // Common screens + }, + groups: { + // Common modal screens + Modal: { + screenOptions: { + presentation: 'modal', + }, + screens: { + Help, + Invite, + }, + }, + // Screens for logged in users + User: { + if: useIsLoggedIn, + screens: { + Home, + Profile, + }, + }, + // Auth screens + Guest: { + if: useIsGuest, + screens: { + SignIn, + SignUp, + }, + }, + }, +}); +``` + + + + +```js + + {isLoggedIn ? ( + // Screens for logged in users + + + + + ) : ( + // Auth screens + + + + + )} + {/* Common modal screens */} + + + + + +``` + + + diff --git a/versioned_docs/version-8.x/next-steps.md b/versioned_docs/version-8.x/next-steps.md new file mode 100755 index 0000000000..6efc2fb171 --- /dev/null +++ b/versioned_docs/version-8.x/next-steps.md @@ -0,0 +1,24 @@ +--- +id: next-steps +title: Next steps +sidebar_label: Next steps +--- + +You are now familiar with how to create a stack navigator, configure it on your screen components, navigate between routes, and display [modals](modal.md). Stack navigators and their related APIs will be the most frequently used tools in your React Navigation toolbelt, but there are problems that they don't solve. For example, you can't build tab-based navigation using a stack navigator — for that, you need to use a [Bottom Tabs Navigator](bottom-tab-navigator.md). + +You can check out the **"Navigators"** section in the sidebar to learn more about the different navigators available in React Navigation. + +The rest of the documentation is organized around specific use cases, so you can jump between the sections under **"Guides"** as the need arises (but it also wouldn't hurt you to familiarize yourself with them pre-emptively!). + +Some of the guides you may want to check out are: + +- [Authentication flows](auth-flow.md): How to handle authentication flows in your app. +- [Supporting safe areas](handling-safe-area.md): How to handle safe areas such as statusbar in your app. +- [Deep linking](deep-linking.md): How to handle deep linking and universal links in your app. +- [Themes](themes.md): How to customize the look and feel of various UI elements. +- [Testing with Jest](testing.md): How to test your navigation components. +- [Configuring TypeScript](typescript.md): How to configure TypeScript for React Navigation. + +While most users won't need to do this, if you are curious and want to learn more about how React Navigation works, it's recommended to work through the [Navigation State reference](navigation-state.md) and [Build your own Navigator](custom-navigators.md) sections. + +Good luck! diff --git a/versioned_docs/version-8.x/params.md b/versioned_docs/version-8.x/params.md new file mode 100755 index 0000000000..9bd5d6fb88 --- /dev/null +++ b/versioned_docs/version-8.x/params.md @@ -0,0 +1,448 @@ +--- +id: params +title: Passing parameters to routes +sidebar_label: Passing parameters to routes +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Remember when I said "more on that later when we talk about `params`!"? Well, the time has come. + +Now that we know how to create a stack navigator with some routes and [navigate between those routes](navigating.md), let's look at how we can pass data to routes when we navigate to them. + +There are two pieces to this: + +1. Pass params to a route by putting them in an object as a second parameter to the `navigation.navigate` function: `navigation.navigate('RouteName', { /* params go here */ })` +2. Read the params in your screen component: `route.params`. + +:::note + +We recommend that the params you pass are JSON-serializable. That way, you'll be able to use [state persistence](state-persistence.md) and your screen components will have the right contract for implementing [deep linking](deep-linking.md). + +::: + +```js name="Passing params" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +// codeblock-focus-start +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function DetailsScreen({ route }) { + const navigation = useNavigation(); + + /* 2. Get the param */ + // highlight-next-line + const { itemId, otherParam } = route.params; + + return ( + + Details Screen + itemId: {JSON.stringify(itemId)} + otherParam: {JSON.stringify(otherParam)} + + + + + ); +} +// codeblock-focus-end + +const RootStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + Details: DetailsScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + +## Initial params + +You can also pass some initial params to a screen. If you didn't specify any params when navigating to this screen, the initial params will be used. They are also shallow merged with any params that you pass. Initial params can be specified in `initialParams`: + + + + +```js +{ + Details: { + screen: DetailsScreen, + // highlight-next-line + initialParams: { itemId: 42 }, + }, +} +``` + + + + +```js + +``` + + + + +## Updating params + +Screens can also update their params, like they can update their state. The `navigation.setParams` method lets you update the params of a screen. Refer to the [API reference for `setParams`](navigation-object.md#setparams) for more details. + +Basic usage: + +```js name="Updating params" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +function HomeScreen({ route }) { + const navigation = useNavigation(); + const { itemId } = route.params; + + return ( + + Home Screen + itemId: {JSON.stringify(itemId)} + + + ); +} + +const RootStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + initialParams: { itemId: 42 }, + }, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + +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` or `replaceParams` to update screen options such as `title` etc. If you need to update options, use [`setOptions`](navigation-object.md#setoptions) instead. + +::: + +## Passing params to a previous screen + +Params aren't only useful for passing some data to a new screen, but they can also be useful to pass data to a previous screen as well. For example, let's say you have a screen with a "Create post" button, and the button opens a new screen to create a post. After creating the post, you want to pass the data for the post back to the previous screen. + +To achieve this, you can use the `popTo` method to go back to the previous screen as well as pass params to it: + +```js name="Passing params back" snack +import * as React from 'react'; +import { Text, View, TextInput } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +// codeblock-focus-start +function HomeScreen({ route }) { + const navigation = useNavigation(); + + // Use an effect to monitor the update to params + // highlight-start + React.useEffect(() => { + if (route.params?.post) { + // Post updated, do something with `route.params.post` + // For example, send the post to the server + alert('New post: ' + route.params?.post); + } + }, [route.params?.post]); + // highlight-end + + return ( + + + Post: {route.params?.post} + + ); +} + +function CreatePostScreen({ route }) { + const navigation = useNavigation(); + const [postText, setPostText] = React.useState(''); + + return ( + <> + + + + ); +} +// codeblock-focus-end + +const RootStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + CreatePost: CreatePostScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + +Here, after you press "Done", the home screen's `route.params` will be updated to reflect the post text that you passed in `navigate`. + +## Passing params to a nested screen + +If you have nested navigators, you need to pass params a bit differently. For example, say you have a navigator inside the `More` screen and want to pass params to the `Settings` screen inside that navigator. Then you can pass params as the following: + +```js name="Passing params to nested screen" snack +import * as React from 'react'; +import { Text, View, TextInput } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Button } from '@react-navigation/elements'; + +function SettingsScreen({ route }) { + const navigation = useNavigation(); + const { userId } = route.params; + + return ( + + Settings Screen + User ID: {JSON.stringify(userId)} + + + ); +} + +function ProfileScreen() { + return ( + + Profile Screen + + ); +} + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +const MoreStack = createNativeStackNavigator({ + screens: { + Settings: SettingsScreen, + Profile: ProfileScreen, + }, +}); + +const RootTabs = createBottomTabNavigator({ + screens: { + Home: HomeScreen, + More: MoreStack, + }, +}); + +const Navigation = createStaticNavigation(RootTabs); + +export default function App() { + return ; +} +``` + +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 serve two main purposes: + +- 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: + +```js +// Don't do this +navigation.navigate('Profile', { + user: { + id: 'jane', + firstName: 'Jane', + lastName: 'Done', + age: 25, + }, +}); +``` + +This looks convenient and lets you access the user objects with `route.params.user` without any extra work. + +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. + +A better way is to pass only the ID of the user in params: + +```js +navigation.navigate('Profile', { userId: 'jane' }); +``` + +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: + +- 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`](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-8.x/pitch.md b/versioned_docs/version-8.x/pitch.md new file mode 100755 index 0000000000..c126beb56f --- /dev/null +++ b/versioned_docs/version-8.x/pitch.md @@ -0,0 +1,23 @@ +--- +id: pitch +title: Pitch & anti-pitch +sidebar_label: Pitch & anti-pitch +--- + +It's useful when considering whether or not to use a project to understand the tradeoffs that the developers of the project made when building it. What problems does it explicitly try to solve for you, and which ones does it ignore? What are the current limitations of the project and common problems that people encounter? These are the kinds of questions that we believe you should have answers to when making an important technology decision for your project, and so we have documented answers to these questions as best we can here, in the form of a "pitch" (why you should use it) and "anti-pitch" (why you should not use it). Please [submit a pull request](https://github.com/react-navigation/react-navigation.github.io) if you believe we have omitted important information! + +## Pitch + +- React Navigation doesn't include any native code in the library itself, but we use many native libraries such as [Screens](https://github.com/software-mansion/react-native-screens), [Reanimated](https://software-mansion.github.io/react-native-reanimated/), [Gesture Handler](https://software-mansion.github.io/react-native-gesture-handler/) etc. to implement performant animations and gestures. Depending on the navigator, many UI components are written in JavaScript on top of React Native primitives. This has a lot of benefits: + - Easy OTA updates + - Debuggable + - Customizable +- Most apps heavily customize navigation, to do this with an API that wraps native navigation you will need to write a lot of native code. In React Navigation, we provide navigators written fully with JavaScript (e.g. [Stack Navigator](stack-navigator.md)) and navigators implemented on top of platform navigation primitives (e.g. [Native Stack Navigator](native-stack-navigator.md)). This lets you pick the navigators suitable for your use case, depending on whether you want native platform behavior or full customizability. +- It's possible to write your own navigators that integrate cleanly with standard navigators, or to fork the standard navigators and create your own version of them with the exact look and feel you want in your app. + +## Anti-pitch + +- Improvements may require breaking changes. We are working to make ["easy things easy and hard things possible"](https://www.quora.com/What-is-the-origin-of-the-phrase-make-the-easy-things-easy-and-the-hard-things-possible) and this may require us to change the API at times. +- Some navigators don't directly use the native navigation APIs on iOS and Android; rather, they use the lowest level pieces and then re-creates some subset of the APIs on top. This is a conscious choice in order to make it possible for users to customize any part of the navigation experience (because it's implemented in JavaScript) and to be able to debug issues that they encounter without needing to learn Objective C / Swift / Java / Kotlin. + - If you need the exact platform behavior, you can choose to use the navigators that use native platform primitives (e.g. [Native Stack Navigator](native-stack-navigator.md)), or use a different navigation library which provides fully native navigation APIs (e.g. [React Native Navigation](https://github.com/wix/react-native-navigation)). +- There are other limitations which you may want to consider, see [Limitations](limitations.md) for more details. diff --git a/versioned_docs/version-8.x/preventing-going-back.md b/versioned_docs/version-8.x/preventing-going-back.md new file mode 100644 index 0000000000..0d727fdc03 --- /dev/null +++ b/versioned_docs/version-8.x/preventing-going-back.md @@ -0,0 +1,96 @@ +--- +id: preventing-going-back +title: Preventing going back +sidebar_label: Preventing going back +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Sometimes you may want to prevent the user from leaving a screen to avoid losing unsaved changes. There are a couple of things you may want to do in this case: + +## Prevent the user from leaving the screen + +The `usePreventRemove` hook allows you to prevent the user from leaving a screen. See the [`usePreventRemove`](use-prevent-remove.md) docs for more details. + +
+Previous approach + +Previously, the way to do this was to: + +- Override the back button in the header +- Disable back swipe gesture +- Override system back button/gesture on Android + +However, using the hook has many important differences in addition to being less code: + +- It's not coupled to any specific buttons, going back from custom buttons will trigger it as well +- It's not coupled to any specific actions, any action that removes the route from the state will trigger it +- It works across nested navigators, e.g. if the screen is being removed due to an action in the parent navigator +- The user can still swipe back in the stack navigator, however, the swipe will be canceled if the event is prevented +- It's possible to continue the same action that triggered the event + +
+ +## Prevent the user from leaving the app + +To be able to prompt the user before they leave the app on Android, you can use the `BackHandler` API from React Native: + +```js +import { Alert, BackHandler } from 'react-native'; + +// ... + +React.useEffect(() => { + const onBackPress = () => { + Alert.alert( + 'Exit App', + 'Do you want to exit?', + [ + { + text: 'Cancel', + onPress: () => { + // Do nothing + }, + style: 'cancel', + }, + { text: 'YES', onPress: () => BackHandler.exitApp() }, + ], + { cancelable: false } + ); + + return true; + }; + + const backHandler = BackHandler.addEventListener( + 'hardwareBackPress', + onBackPress + ); + + return () => backHandler.remove(); +}, []); +``` + +On the Web, you can use the `beforeunload` event to prompt the user before they leave the browser tab: + +```js +React.useEffect(() => { + const onBeforeUnload = (event) => { + // Prevent the user from leaving the page + event.preventDefault(); + event.returnValue = true; + }; + + window.addEventListener('beforeunload', onBeforeUnload); + + return () => { + window.removeEventListener('beforeunload', onBeforeUnload); + }; +}, []); +``` + +:::warning + +The user can still close the app by swiping it away from the app switcher or closing the browser tab. Or the app can be closed by the system due to low memory or other reasons. It's also not possible to prevent leaving the app on iOS. We recommend persisting the data and restoring it when the app is opened again instead of prompting the user before they leave the app. + +::: diff --git a/versioned_docs/version-8.x/route-object.md b/versioned_docs/version-8.x/route-object.md new file mode 100755 index 0000000000..346af1b4ed --- /dev/null +++ b/versioned_docs/version-8.x/route-object.md @@ -0,0 +1,85 @@ +--- +id: route-object +title: Route object reference +sidebar_label: Route object +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Each `screen` component in your app is provided with the `route` object as a prop automatically. The prop contains various information regarding current route (place in navigation hierarchy component lives). + +- `route` + - `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. + - `path` - An optional string containing the path that opened the screen, exists when the screen was opened via a deep link. + - `params` - An optional object containing params which is defined while navigating e.g. `navigate('Twitter', { user: 'Dan Abramov' })`. + + + + +```js name="Route prop" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +const Stack = createNativeStackNavigator({ + screens: { + Profile: ProfileScreen, + }, +}); + +// codeblock-focus-start +function ProfileScreen({ route }) { + return ( + + This is the profile screen of the app + {route.name} + + ); +} +// codeblock-focus-end + +const Navigation = createStaticNavigation(Stack); + +export default function App() { + return ; +} +``` + + + + +```js name="Route prop" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +const Stack = createNativeStackNavigator(); + +// codeblock-focus-start +function ProfileScreen({ route }) { + return ( + + This is the profile screen of the app + {route.name} + + ); +} +// codeblock-focus-end + +export default function App() { + return ( + + + + + + ); +} +``` + + + diff --git a/versioned_docs/version-8.x/screen-options-resolution.md b/versioned_docs/version-8.x/screen-options-resolution.md new file mode 100755 index 0000000000..1a97af4543 --- /dev/null +++ b/versioned_docs/version-8.x/screen-options-resolution.md @@ -0,0 +1,763 @@ +--- +id: screen-options-resolution +title: Screen options with nested navigators +sidebar_label: Options with nested navigators +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +In this document we'll explain how [screen options](screen-options.md) work when there are multiple navigators. It's important to understand this so that you put your `options` in the correct place and can properly configure your navigators. If you put them in the wrong place, at best nothing will happen and at worst something confusing and unexpected will happen. + +**You can only modify navigation options for a navigator from one of its screen components. This applies equally to navigators that are nested as screens.** + +Let's take for example a tab navigator that contains a native stack in each tab. What happens if we set the `options` on a screen inside of the stack? + + + + +```js name="Tabs with native stack" snack +import * as React from 'react'; +import { View } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function A() { + return ; +} + +function B() { + return ; +} + +// codeblock-focus-start +const HomeStackScreen = createNativeStackNavigator({ + screens: { + A: { + screen: A, + options: { + tabBarLabel: 'Home', + }, + }, + }, +}); + +const SettingsStackScreen = createNativeStackNavigator({ + screens: { + B: { + screen: B, + options: { + tabBarLabel: 'Settings!', + }, + }, + }, +}); + +const Tab = createBottomTabNavigator({ + screens: { + Home: HomeStackScreen, + Settings: SettingsStackScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(Tab); + +export default function App() { + return ; +} +``` + + + + +```js name="Tabs with native stack" snack +import * as React from 'react'; +import { View } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +const Tab = createBottomTabNavigator(); +const HomeStack = createNativeStackNavigator(); +const SettingsStack = createNativeStackNavigator(); + +function A() { + return ; +} + +function B() { + return ; +} +// codeblock-focus-start +function HomeStackScreen() { + return ( + + + + ); +} + +function SettingsStackScreen() { + return ( + + + + ); +} + +export default function App() { + return ( + + + + + + + ); +} +// codeblock-focus-end +``` + + + + +As we mentioned earlier, you can only modify navigation options for a navigator from one of its screen components. `A` and `B` above are screen components in `HomeStack` and `SettingsStack` respectively, not in the tab navigator. So the result will be that the `tabBarLabel` property is not applied to the tab navigator. We can fix this though! + + + + +```js name="Tabs with native stack" snack +import * as React from 'react'; +import { View } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function A() { + return ; +} + +function B() { + return ; +} + +const HomeStackScreen = createNativeStackNavigator({ + screens: { + A: A, + }, +}); + +const SettingsStackScreen = createNativeStackNavigator({ + screens: { + B: B, + }, +}); + +// codeblock-focus-start +const Tab = createBottomTabNavigator({ + screens: { + Home: { + screen: HomeStackScreen, + options: { + tabBarLabel: 'Home!', + }, + }, + Settings: { + screen: SettingsStackScreen, + options: { + tabBarLabel: 'Settings!', + }, + }, + }, +}); +// codeblock-focus-start + +const Navigation = createStaticNavigation(Tab); + +export default function App() { + return ; +} +``` + + + + +```js name="Tabs with native stack" snack +import * as React from 'react'; +import { View } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +const Tab = createBottomTabNavigator(); +const HomeStack = createNativeStackNavigator(); +const SettingsStack = createNativeStackNavigator(); + +function A() { + return ; +} + +function B() { + return ; +} + +function HomeStackScreen() { + return ( + + + + ); +} + +function SettingsStackScreen() { + return ( + + + + ); +} + +// codeblock-focus-start +export default function App() { + return ( + + + + + + + ); +} +// codeblock-focus-end +``` + + + + +When we set the `options` directly on `Screen` components containing the `HomeStack` and `SettingsStack` component, it allows us to control the options for its parent navigator when its used as a screen component. In this case, the options on our stack components configure the label in the tab navigator that renders the stacks. + +## Setting parent screen options based on child navigator's state + +Imagine the following configuration: + + + + +```js name="Parent options from a child" snack +import * as React from 'react'; +import { View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +function FeedScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +function ProfileScreen() { + return ; +} + +function AccountScreen() { + return ; +} + +function SettingsScreen() { + return ; +} + +// codeblock-focus-start +const HomeTabs = createBottomTabNavigator({ + screens: { + Feed: FeedScreen, + Profile: ProfileScreen, + Account: AccountScreen, + }, +}); + +const RootStack = createNativeStackNavigator({ + screens: { + Home: HomeTabs, + Settings: SettingsScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +// codeblock-focus-end +``` + + + + +```js name="Parent options from a child" snack +import * as React from 'react'; +import { View } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +function FeedScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +function ProfileScreen() { + return ; +} + +function AccountScreen() { + return ; +} + +function SettingsScreen() { + return ; +} + +// codeblock-focus-start +const Tab = createBottomTabNavigator(); + +function HomeTabs() { + return ( + + + + + + ); +} + +const Stack = createNativeStackNavigator(); + +export default function App() { + return ( + + + + + + + ); +} + +// codeblock-focus-end +``` + + + + +If we were to set the `headerTitle` with `options` for the `FeedScreen`, this would not work. This is because `App` stack will only look at its immediate children for configuration: `HomeTabs` and `SettingsScreen`. + +But we can determine the `headerTitle` option based on the [navigation state](navigation-state.md) of our tab navigator using the `getFocusedRouteNameFromRoute` helper. Let's create a function to get the title first: + +```js +import { getFocusedRouteNameFromRoute } from '@react-navigation/native'; + +function getHeaderTitle(route) { + // If the focused route is not found, we need to assume it's the initial screen + // This can happen during if there hasn't been any navigation inside the screen + // In our case, it's "Feed" as that's the first screen inside the navigator + const routeName = getFocusedRouteNameFromRoute(route) ?? 'Feed'; + + switch (routeName) { + case 'Feed': + return 'News feed'; + case 'Profile': + return 'My profile'; + case 'Account': + return 'My account'; + } +} +``` + +Then we can use this function with the `options` prop on `Screen`: + + + + +```js name="Parent options from a child" snack +import * as React from 'react'; +import { View } from 'react-native'; +import { getFocusedRouteNameFromRoute } from '@react-navigation/native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +function getHeaderTitle(route) { + // If the focused route is not found, we need to assume it's the initial screen + // This can happen during if there hasn't been any navigation inside the screen + // In our case, it's "Feed" as that's the first screen inside the navigator + const routeName = getFocusedRouteNameFromRoute(route) ?? 'Feed'; + + switch (routeName) { + case 'Feed': + return 'News feed'; + case 'Profile': + return 'My profile'; + case 'Account': + return 'My account'; + } +} + +function FeedScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +function ProfileScreen() { + return ; +} + +function AccountScreen() { + return ; +} + +function SettingsScreen() { + return ; +} +const HomeTabs = createBottomTabNavigator({ + screenOptions: { + headerShown: false, + }, + screens: { + Feed: FeedScreen, + Profile: ProfileScreen, + Account: AccountScreen, + }, +}); + +// codeblock-focus-start +const RootStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeTabs, + options: ({ route }) => ({ + headerTitle: getHeaderTitle(route), + }), + }, + Settings: SettingsScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + + + +```js name="Parent options from a child" snack +import * as React from 'react'; +import { View } from 'react-native'; +import { + NavigationContainer, + useNavigation, + getFocusedRouteNameFromRoute, +} from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +function getHeaderTitle(route) { + // If the focused route is not found, we need to assume it's the initial screen + // This can happen during if there hasn't been any navigation inside the screen + // In our case, it's "Feed" as that's the first screen inside the navigator + const routeName = getFocusedRouteNameFromRoute(route) ?? 'Feed'; + + switch (routeName) { + case 'Feed': + return 'News feed'; + case 'Profile': + return 'My profile'; + case 'Account': + return 'My account'; + } +} + +function FeedScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +function ProfileScreen() { + return ; +} + +function AccountScreen() { + return ; +} + +function SettingsScreen() { + return ; +} + +const Tab = createBottomTabNavigator(); + +function HomeTabs() { + return ( + + + + + + ); +} + +const Stack = createNativeStackNavigator(); + +export default function App() { + return ( + + + // codeblock-focus-start + ({ + headerTitle: getHeaderTitle(route), + })} + /> + // codeblock-focus-end + + + + ); +} +``` + + + + +So what's happening here? With the `getFocusedRouteNameFromRoute` helper, we can get the currently active route name from this child navigator (in this case it's the tab navigator since that's what we're rendering) and setting an appropriate title for the header. + +This approach can be used anytime you want to set options for a parent navigator based on a child navigator's state. Common use cases are: + +1. Show tab title in stack header: a stack contains a tab navigator and you want to set the title on the stack header (above example) +2. Show screens without tab bar: a tab navigator contains a stack and you want to hide the tab bar on specific screens (not recommended, see [Hiding tab bar in specific screens](hiding-tabbar-in-screens.md) instead) +3. Lock drawer on certain screens: a drawer has a stack inside of it and you want to lock the drawer on certain screens + +In many cases, similar behavior can be achieved by reorganizing our navigators. We usually recommend this option if it fits your use case. + +For example, for the above use case, instead of adding a tab navigator inside a stack navigator, we can add a stack navigator inside each of the tabs. + + + + +```js name="Reorganized navigators" snack +import * as React from 'react'; +import { View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +function FeedScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +function ProfileScreen() { + return ; +} + +function SettingsScreen() { + return ; +} + +// codeblock-focus-start +const FeedStackScreen = createNativeStackNavigator({ + screens: { + Feed: FeedScreen, + /* other screens */ + }, +}); + +const ProfileStackScreen = createNativeStackNavigator({ + screens: { + Profile: ProfileScreen, + /* other screens */ + }, +}); + +const HomeTabs = createBottomTabNavigator({ + screens: { + Feed: FeedStackScreen, + Profile: ProfileStackScreen, + }, +}); + +const RootStack = createNativeStackNavigator({ + screens: { + Home: HomeTabs, + Settings: SettingsScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +// codeblock-focus-end +``` + + + + +```js name="Reorganized navigators" snack +import * as React from 'react'; +import { View } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +function FeedScreen() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +function ProfileScreen() { + return ; +} + +function SettingsScreen() { + return ; +} + +const FeedStack = createNativeStackNavigator(); + +// codeblock-focus-start +function FeedStackScreen() { + return ( + + + {/* other screens */} + + ); +} + +const ProfileStack = createNativeStackNavigator(); + +function ProfileStackScreen() { + return ( + + + {/* other screens */} + + ); +} + +const Tab = createBottomTabNavigator(); + +function HomeTabs() { + return ( + + + + + ); +} + +const RootStack = createNativeStackNavigator(); + +export default function App() { + return ( + + + + + + + ); +} +// codeblock-focus-end +``` + + + + +Additionally, this lets you push new screens to the feed and profile stacks without hiding the tab bar by adding more routes to those stacks. + +If you want to push screens on top of the tab bar (i.e. that don't show the tab bar), then you can add them to the `App` stack instead of adding them into the screens inside the tab navigator. diff --git a/versioned_docs/version-8.x/screen-options.md b/versioned_docs/version-8.x/screen-options.md new file mode 100644 index 0000000000..9463726e45 --- /dev/null +++ b/versioned_docs/version-8.x/screen-options.md @@ -0,0 +1,349 @@ +--- +id: screen-options +title: Options for screens +sidebar_label: Options for screens +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Each screen can configure various aspects about how it gets presented in the navigator that renders it by specifying certain options, for example, the header title in stack navigator, tab bar icon in bottom tab navigator etc. Different navigators support different set of options. + +In the [configuring the header bar](headers.md) section of the fundamentals documentation we explain the basics of how this works. Also see the [screen options resolution guide](screen-options-resolution.md) to get an idea of how they work when there are multiple navigators. + +See [our docs](typescript.md#annotating-options-and-screenoptions) to learn more about how to use TypeScript with `screenOptions` and `options`. + +There are 3 ways of specifying options for screens: + +### `options` prop on `Screen` + +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 static2dynamic +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function ProfileScreen() { + const navigation = useNavigation(); + + return ( + + Profile Screen + + + ); +} + +// codeblock-focus-start +const RootStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + options: { + title: 'Awesome app', + }, + }, + Profile: { + screen: ProfileScreen, + options: { + title: 'My profile', + }, + }, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +// 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 static2dynamic +const RootStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + options: ({ navigation }) => ({ + 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. + +Example: + + + + +```js name="Screen options for group" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +import { + useNavigation, + createStaticNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +// codeblock-focus-start +const RootStack = createNativeStackNavigator({ + groups: { + App: { + screenOptions: { + headerStyle: { + backgroundColor: '#FFB6C1', + }, + }, + screens: { + Home: ScreenWithButton('Home', 'Profile'), + Profile: ScreenWithButton('Profile', 'Settings'), + }, + }, + Modal: { + screenOptions: { + presentation: 'modal', + }, + screens: { + Settings: ScreenWithButton('Settings', 'Share'), + Share: ScreenWithButton('Share'), + }, + }, + }, +}); +// codeblock-focus-end + +function ScreenWithButton(screenName, navigateTo) { + return function () { + const navigation = useNavigation(); + return ( + + {screenName} Screen + {navigateTo && ( + + )} + + ); + }; +} + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Screen options for group" 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 ScreenWithButton(screenName, navigateTo) { + return function () { + const navigation = useNavigation(); + + return ( + + {screenName} Screen + {navigateTo && ( + + )} + + ); + }; +} + +const HomeScreen = ScreenWithButton('Home', 'Profile'); +const ProfileScreen = ScreenWithButton('Profile', 'Settings'); +const SettingsScreen = ScreenWithButton('Settings', 'Share'); +const ShareScreen = ScreenWithButton('Share'); + +export default function App() { + return ( + + // codeblock-focus-start + + + + + + + + + + + // codeblock-focus-end + + ); +} +``` + + + + +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 +const Stack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, + groups: { + Modal: { + screenOptions: { + presentation: 'modal', + headerLeft: () => , + }, + screens: { + Settings: Settings, + Share: Share, + }, + }, + }, +}); +``` + + + + +```js + + + + ({ + presentation: 'modal', + headerLeft: () => , + })} + > + + + + +``` + + + + +### `screenOptions` prop on the navigator + +You can pass a prop named `screenOptions` to the navigator component, where you can specify an object with different options. The options specified in `screenOptions` apply to all of the screens in the navigator. So this is a good place to specify options that you want to configure for the whole navigator. + +Example: + +```js static2dynamic +const RootStack = createNativeStackNavigator({ + screenOptions: { + headerStyle: { + backgroundColor: 'papayawhip', + }, + }, + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +``` + +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 static2dynamic +import * as React from 'react'; +import { View } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { MaterialCommunityIcons } from '@expo/vector-icons'; + +// codeblock-focus-start +const MyTabs = createBottomTabNavigator({ + screenOptions: ({ route }) => ({ + tabBarIcon: ({ color, size }) => { + const icons = { + Home: 'home', + Profile: 'account', + }; + + return ( + + ); + }, + }), + screens: { + Home: EmptyScreen, + Profile: EmptyScreen, + }, +}); +// codeblock-focus-end + +function EmptyScreen() { + return ; +} + +const Navigation = createStaticNavigation(MyTabs); + +export default function App() { + return ; +} +``` + +### `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. + +```js + +``` diff --git a/versioned_docs/version-8.x/screen-tracking.md b/versioned_docs/version-8.x/screen-tracking.md new file mode 100644 index 0000000000..87786b0bfe --- /dev/null +++ b/versioned_docs/version-8.x/screen-tracking.md @@ -0,0 +1,189 @@ +--- +id: screen-tracking +title: Screen tracking for analytics +sidebar_label: Screen tracking +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +To track the currently active screen, we need to: + +1. Add a callback to get notified of state changes +2. Get the root navigator state and find the active route name + +To get notified of state changes, we can use the `onStateChange` prop on `NavigationContainer`. To get the root navigator state, we can use the `getRootState` method on the container's ref. Please note that `onStateChange` is not called on initial render so you have to set your initial screen separately. + +## Example + +This example shows how the approach can be adapted to any mobile analytics SDK. + + + + +```js name="Screen tracking for analytics" snack +import * as React from 'react'; +import { View } from 'react-native'; +// codeblock-focus-start +import { + createStaticNavigation, + useNavigationContainerRef, + useNavigation, +} from '@react-navigation/native'; +// codeblock-focus-end +import { Button } from '@react-navigation/elements'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function Home() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +function Settings() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +const RootStack = createNativeStackNavigator({ + screens: { + Home: Home, + Settings: Settings, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +// codeblock-focus-start + +export default function App() { + const navigationRef = useNavigationContainerRef(); + const routeNameRef = React.useRef(); + + return ( + { + routeNameRef.current = navigationRef.current.getCurrentRoute().name; + }} + onStateChange={async () => { + const previousRouteName = routeNameRef.current; + const currentRouteName = navigationRef.current.getCurrentRoute().name; + const trackScreenView = () => { + // Your implementation of analytics goes here! + }; + + if (previousRouteName !== currentRouteName) { + // Replace the line below to add the tracker from a mobile analytics SDK + await trackScreenView(currentRouteName); + } + + // Save the current route name for later comparison + routeNameRef.current = currentRouteName; + }} + /> + ); +} +// codeblock-focus-end +``` + + + + +```js name="Screen tracking for anylytics" snack +import * as React from 'react'; +import { View } from 'react-native'; +// codeblock-focus-start +import { + NavigationContainer, + useNavigation, + useNavigationContainerRef, +} from '@react-navigation/native'; +// codeblock-focus-end +import { Button } from '@react-navigation/elements'; +import { createStackNavigator } from '@react-navigation/stack'; + +function Home() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +function Settings() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +const Stack = createStackNavigator(); + +// codeblock-focus-start + +export default function App() { + const navigationRef = useNavigationContainerRef(); + const routeNameRef = React.useRef(); + + return ( + { + routeNameRef.current = navigationRef.current.getCurrentRoute().name; + }} + onStateChange={async () => { + const previousRouteName = routeNameRef.current; + const currentRouteName = navigationRef.current.getCurrentRoute().name; + const trackScreenView = () => { + // Your implementation of analytics goes here! + }; + + if (previousRouteName !== currentRouteName) { + // Replace the line below to add the tracker from a mobile analytics SDK + await trackScreenView(currentRouteName); + } + + // Save the current route name for later comparison + routeNameRef.current = currentRouteName; + }} + > + {/* ... */} + // codeblock-focus-end + + + + + // codeblock-focus-start + + ); +} +// codeblock-focus-end +``` + + + + +:::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-8.x/screen.md b/versioned_docs/version-8.x/screen.md new file mode 100644 index 0000000000..354b1a00ba --- /dev/null +++ b/versioned_docs/version-8.x/screen.md @@ -0,0 +1,439 @@ +--- +id: screen +title: Screen +sidebar_label: Screen +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +A screen represents routes in a navigator. A screen's configuration contains the component for the route, options, event listeners, etc. + + + + +Screens can be defined under the `screens` key in the navigator configuration: + +```js +const MyStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +``` + + + + +A `Screen` component is returned from a `createXNavigator` function. After creating the navigator, it can be used as children of the `Navigator` component: + +```js +const Stack = createNativeStackNavigator(); + +function MyStack() { + return ( + + + + + ); +} +``` + +You need to provide at least a name and a component to render for each screen. + + + + +## Configuration + +### Name + +The name to use for the screen. + + + + +The key in the `screens` object is used as the name: + +```js +const Stack = createNativeStackNavigator({ + screens: { + // highlight-next-line + Profile: { + screen: ProfileScreen, + }, + }, +}); +``` + + + + +It can be passed in the `name` prop to the `Screen` component: + +```jsx + +``` + + + + +This name is used to navigate to the screen: + +```js +navigation.navigate('Profile'); +``` + +It is also used for the `name` property in the [`route`](route-object.md). + +While it is supported, we recommend avoiding spaces or special characters in screen names and keeping them simple. + +### Options + +Options are used to configure how the screen gets presented in the navigator. It accepts either an object or a function returning an object: + + + + +```js +const Stack = createNativeStackNavigator({ + screens: { + Profile: { + screen: ProfileScreen, + // highlight-start + options: { + title: 'Awesome app', + }, + // highlight-end + }, + }, +}); +``` + + + + +```jsx + +``` + + + + +When you pass a function, it'll receive the [`route`](route-object.md), [`navigation`](navigation-object.md) and [`theme`](themes.md) as arguments: + + + + +```js +const Stack = createNativeStackNavigator({ + screens: { + Profile: { + screen: ProfileScreen, + // highlight-start + options: ({ route, navigation, theme }) => ({ + title: route.params.userId, + }), + // highlight-end + }, + }, +}); +``` + + + + +```jsx + ({ + title: route.params.userId, + })} + // highlight-end +/> +``` + + + + +See [Options for screens](screen-options.md) for more details and examples. + +### Initial params + +Initial params are used as the default params for the screen. If a screen is used as `initialRouteName`, it'll contain the params from `initialParams`. If you navigate to a new screen, the params passed are shallow merged with the initial params. + + + + +```js +const Stack = createNativeStackNavigator({ + screens: { + Details: { + screen: DetailsScreen, + // highlight-next-line + initialParams: { itemId: 42 }, + }, + }, +}); +``` + + + + +```jsx + +``` + + + + +### ID + +A screen can have an ID to identify it uniquely. This is useful when you want to ensure that the screen with the same ID doesn't appear multiple times in the stack. + +This can be done by specifying the `getId` callback. It receives an object with the route params: + + + + +```js +const Stack = createStackNavigator({ + screens: { + Profile: { + screen: ProfileScreen, + // highlight-next-line + getId: ({ params }) => params.userId, + }, + }, +}); +``` + + + + +```jsx + params.userId} +/> +``` + + + + +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. + +Let's say you have a stack with the history `Home > Profile (userId: bob) > Settings`, consider following scenarios: + +- 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. + +If `getId` is specified in a tab or drawer navigator, the screen will remount if the ID changes. + +:::warning + +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. + +::: + +### Component + +Each screen must specify a component to render for that route. + + + + +It can be passed under the `screen` property in the screen configuration: + +```js +const Stack = createNativeStackNavigator({ + screens: { + Profile: { + // highlight-next-line + screen: ProfileScreen, + }, + }, +}); +``` + + + + +#### `component` + +It can be passed in the `component` prop to the `Screen` component: + +```jsx + +``` + +#### `getComponent` + +It's also possible to pass a function in the `getComponent` prop to lazily evaluate the component: + +```jsx + require('./ProfileScreen').default} +/> +``` + +You can use this approach instead of the `component` prop if you want the `ProfileScreen` module to be lazily evaluated when needed. This is especially useful when using [ram bundles](https://reactnative.dev/docs/ram-bundles-inline-requires) to improve initial load. + +#### `children` + +Another way is to pass a render callback to return React Element to use for the screen: + +```jsx + + // highlight-next-line + {(props) => } + +``` + +You can use this approach instead of the `component` prop if you need to pass additional props. Though we recommend using [React context](https://react.dev/reference/react/useContext) for passing data instead. + +:::warning + +By default, React Navigation applies optimizations to screen components to prevent unnecessary renders. Using a render callback removes those optimizations. So if you use a render callback, you'll need to ensure that you use [`React.memo`](https://react.dev/reference/react/memo) or [`React.PureComponent`](https://react.dev/reference/react/PureComponent) for your screen components to avoid performance issues. + +::: + + + + +### 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, or wrap the screen with additional UI. + +It takes a function that returns a React element: + + + + +```js +const Stack = createNativeStackNavigator({ + screens: { + Profile: { + screen: ProfileScreen, + // highlight-start + layout: ({ children }) => ( + + + Loading… + + } + > + {children} + + + ), + // highlight-end + }, + }, +}); +``` + + + + +```jsx + ( + + + Loading… + + } + > + {children} + + + )} + // highlight-end +/> +``` + +To specify a layout for all multiple screens, you can use `screenLayout` in a [group](group.md#screen-layout) or [navigator](navigator.md#screen-layout). + + + + +### Navigation key + +A navigation key is an optional key for this screen. This doesn't need to be unique. If the key changes, existing screens with this name will be removed (if used in a stack navigator) or reset (if used in a tab or drawer navigator). + +This can be useful when we have some screens that we want to be removed or reset when the condition changes: + + + + +```js +const Stack = createNativeStackNavigator({ + screens: { + Profile: { + screen: ProfileScreen, + // highlight-next-line + navigationKey: 'user', + }, + }, +}); +``` + +For the static API, we recommend using the [`groups`](group.md#navigation-key) instead of the `navigationKey` for each screen as you can dynamically add or remove groups with the [`if`](static-configuration.md#if) property. + + + + +```jsx + +``` + + + + +### Event listeners + +Event listeners can be used to subscribe to various events emitted for the screen. See [`listeners` prop on `Screen`](navigation-events.md#listeners-prop-on-screen) for more details. diff --git a/versioned_docs/version-8.x/server-container.md b/versioned_docs/version-8.x/server-container.md new file mode 100644 index 0000000000..655b158fea --- /dev/null +++ b/versioned_docs/version-8.x/server-container.md @@ -0,0 +1,76 @@ +--- +id: server-container +title: ServerContainer +sidebar_label: ServerContainer +--- + +The `ServerContainer` component provides utilities to render your app on server with the correct [navigation state](navigation-state.md). + +Example: + +```js +// Ref which will be populated with the screen options +const ref = React.createRef(); + +// Location object containing the `pathname` and `search` fields of the current URL +const location = { pathname: '/profile', search: '?user=jane' }; + +// Get rendered HTML +const html = ReactDOMServer.renderToString( + + + +); + +// Then you can access the options for the current screen in the ref +const options = ref.current.getCurrentOptions(); // { title: 'My Profile' } +``` + +The `ServerContainer` component should wrap your entire app during server rendering. Note that you still need a `NavigationContainer` in your app, `ServerContainer` doesn't replace it.' + +See the [`server rendering guide`](server-rendering.md) for a detailed guide and examples. + +## Ref + +If you attach a `ref` to the container, you can get the options for the current screen after rendering the app. The `ref` will contain a method called `getCurrentOptions` which will return an object with options for the focused screen in the navigation tree: + +```js +const options = ref.current.getCurrentOptions(); +``` + +Then you can access the options for the screen from this object and put it in the HTML: + +```jsx +{options.title} + +``` + +Note that the `options` object can be undefined if you are not rendering a navigator on the initial render. + +## Props + +### `location` + +Location object containing the location to use for server rendered output. You can pass the `pathname` and `search` properties matching the `location` object in the browsers: + +```js + + + +``` + +Normally, you'd construct this object based on the incoming request. + +Basic example with Koa (don't use as is in production): + +```js +app.use(async (ctx) => { + const html = ReactDOMServer.renderToString( + + + + ); + + ctx.body = html; +}); +``` diff --git a/versioned_docs/version-8.x/server-rendering.md b/versioned_docs/version-8.x/server-rendering.md new file mode 100644 index 0000000000..4dddb2019b --- /dev/null +++ b/versioned_docs/version-8.x/server-rendering.md @@ -0,0 +1,256 @@ +--- +id: server-rendering +title: Server rendering +sidebar_label: Server rendering +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +This guide will cover how to server render your React Native app using React Native for Web and React Navigation. We'll cover the following cases: + +1. Rendering the correct layout depending on the request URL +2. Setting appropriate page metadata based on the focused screen + +:::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. + +::: + +## Pre-requisites + +Before you follow the guide, make sure that your app already renders fine on server. To do that, you will need to ensure the following: + +- All of the dependencies that you use are [compiled before publishing](https://github.com/react-native-community/bob) to npm, so that you don't get syntax errors on Node. +- Node is configured to be able to `require` asset files such as images and fonts. You can try [webpack-isomorphic-tools](https://github.com/catamphetamine/webpack-isomorphic-tools) to do that. +- `react-native` is aliased to `react-native-web`. You can do it with [babel-plugin-module-resolver](https://github.com/tleunen/babel-plugin-module-resolver). + +## Rendering the app + +First, let's take a look at an example of how you'd do [server rendering with React Native Web](http://necolas.github.io/react-native-web/docs/?path=/docs/guides-server-side--page) without involving React Navigation: + +```js +import { AppRegistry } from 'react-native-web'; +import ReactDOMServer from 'react-dom/server'; +import App from './src/App'; + +const { element, getStyleElement } = AppRegistry.getApplication('App'); + +const html = ReactDOMServer.renderToString(element); +const css = ReactDOMServer.renderToStaticMarkup(getStyleElement()); + +const document = ` + + + + + + ${css} + +
+ ${html} +
+`; +``` + +Here, `./src/App` is the file where you have `AppRegistry.registerComponent('App', () => App)`. + +If you're using React Navigation in your app, this will render the screens rendered by your home page. However, if you have [configured links](configuring-links.md) in your app, you'd want to render the correct screens for the request URL on server so that it matches what'll be rendered on the client. + +We can use the [`ServerContainer`](server-container.md) to do that by passing this info in the `location` prop. For example, with Koa, you can use the `path` and `search` properties from the context argument: + +```js +app.use(async (ctx) => { + const location = new URL(ctx.url, 'https://example.org/'); + + const { element, getStyleElement } = AppRegistry.getApplication('App'); + + const html = ReactDOMServer.renderToString( + {element} + ); + + const css = ReactDOMServer.renderToStaticMarkup(getStyleElement()); + + const document = ` + + + + + + ${css} + +
+ ${html} +
+`; + + ctx.body = document; +}); +``` + +You may also want to set the correct document title and descriptions for search engines, open graph etc. To do that, you can pass a `ref` to the container which will give you the current screen's options. + +```js +app.use(async (ctx) => { + const location = new URL(ctx.url, 'https://example.org/'); + + const { element, getStyleElement } = AppRegistry.getApplication('App'); + + const ref = React.createRef(); + + const html = ReactDOMServer.renderToString( + + {element} + + ); + + const css = ReactDOMServer.renderToStaticMarkup(getStyleElement()); + + const options = ref.current?.getCurrentOptions(); + + const document = ` + + + + + + ${css} + ${options.title} + +
+ ${html} +
+`; + + ctx.body = document; +}); +``` + +Make sure that you have specified a `title` option for your screens: + + + + +```js +const Stack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + options: { + // highlight-next-line + title: 'My App', + }, + }, + Profile: { + screen: ProfileScreen, + options: ({ route }) => ({ + // highlight-next-line + title: `${route.params.name}'s Profile`, + }), + }, + }, +}); +``` + + + + +```js + + + ({ + // highlight-next-line + title: `${route.params.name}'s Profile`, + })} + /> + +``` + + + + +## Handling 404 or other status codes + +When [rendering a screen for an invalid URL](configuring-links.md#handling-unmatched-routes-or-404), we should also return a `404` status code from the server. + +First, we need to create a context where we'll attach the status code. To do this, place the following code in a separate file that we will be importing on both the server and client: + +```js +import * as React from 'react'; + +const StatusCodeContext = React.createContext(); + +export default StatusCodeContext; +``` + +Then, we need to use the context in our `NotFound` screen. Here, we add a `code` property with the value of `404` to signal that the screen was not found: + +```js +function NotFound() { + const status = React.useContext(StatusCodeContext); + + if (status) { + staus.code = 404; + } + + return ( + + Oops! This URL doesn't exist. + + ); +} +``` + +You could also attach additional information in this object if you need to. + +Next, we need to create a status object to pass in the context on our server. By default, we'll set the `code` to `200`. Then pass the object in `StatusCodeContext.Provider` which should wrap the element with `ServerContainer`: + +```js +// Create a status object +const status = { code: 200 }; + +const html = ReactDOMServer.renderToString( + // Pass the status object via context + + + {element} + + +); + +// After rendering, get the status code and use it for server's response +ctx.status = status.code; +``` + +After we render the app with `ReactDOMServer.renderToString`, the `code` property of the `status` object will be updated to be `404` if the `NotFound` screen was rendered. + +You can follow a similar approach for other status codes too, for example, `401` for unauthorized etc. + +## Summary + +- Use the `location` prop on `ServerContainer` to render correct screens based on the incoming request. +- Attach a `ref` to the `ServerContainer` get options for the current screen. +- Use context to attach more information such as status code. diff --git a/versioned_docs/version-8.x/shared-element-transitions.md b/versioned_docs/version-8.x/shared-element-transitions.md new file mode 100644 index 0000000000..18d5fd917f --- /dev/null +++ b/versioned_docs/version-8.x/shared-element-transitions.md @@ -0,0 +1,229 @@ +--- +id: shared-element-transitions +title: Animating elements between screens +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/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). + +::: + + + +## Pre-requisites + +Before continuing this guide make sure your app meets these criteria: + +- You are using [`@react-navigation/native-stack`](native-stack-navigator.md). The Shared Element Transitions feature isn't supported in JS-based [`@react-navigation/stack`](stack-navigator.md). +- You have [`react-native-reanimated`](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started) **v3.0.0 or higher** installed and configured. + +## Minimal example + +To create a shared transition: + +1. Use `Animated` components imported from `react-native-reanimated`. +2. Assign the same `sharedTransitionTag` to elements on different screens. +3. Navigate between screens. The transition will start automatically. + + + + +```js name="Shared transition" +import * as React from 'react'; +import { View, StyleSheet } from 'react-native'; +import { + useNavigation, + createStaticNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +import Animated from 'react-native-reanimated'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + + + + ); +} + +function DetailsScreen() { + const navigation = useNavigation(); + + return ( + + + + + ); +} + +// highlight-start +const RootStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + Details: DetailsScreen, + }, +}); +// highlight-end + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + }, +}); +``` + + + + +```js name="Shared transition" +import * as React from 'react'; +import { View, StyleSheet } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; + +import Animated from 'react-native-reanimated'; + +// highlight-next-line +const Stack = createNativeStackNavigator(); + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + + + + ); +} + +function DetailsScreen() { + const navigation = useNavigation(); + + return ( + + + + + ); +} + +export default function App() { + return ( + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + }, +}); +``` + + + + +`sharedTransitionTag` is a string that has to be unique in the context of a single screen, but has to match elements between screens. This prop allows Reanimated to identify and animate the elements, similarly to the [`key`](https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key) property, which tells React which element in the list is which. + +## Customizing the transition + +By default, the transition animates the `width`, `height`, `originX`, `originY` and `transform` properties using `withTiming` with a 500 ms duration. You can easily customize `width`, `height`, `originX`, and `originY` props. Customizing `transform` is also possible but it's far beyond the scope of this guide. + +:::warning + +Custom SharedTransition API is not finalized and might change in a future release. + +::: + +To customize the transition you need to pass all the properties besides `transform`. + +```jsx +import { SharedTransition } from 'react-native-reanimated'; + +const customTransition = SharedTransition.custom((values) => { + 'worklet'; + return { + height: withSpring(values.targetHeight), + width: withSpring(values.targetWidth), + originX: withSpring(values.targetOriginX), + originY: withSpring(values.targetOriginY), + }; +}); + +function HomeScreen() { + return ( + + ); +} +``` + +## Reference + +You can find a full Shared Element Transitions reference in the [React Native Reanimated documentation](https://docs.swmansion.com/react-native-reanimated/docs/shared-element-transitions/overview/). + +## Alternatives + +Alternatively, you can use [`react-native-shared-element`](https://github.com/IjzerenHein/react-native-shared-element) library with a [React Navigation binding](https://github.com/IjzerenHein/react-navigation-shared-element) which implements Shared Element Transitions in a JS-based `@react-navigation/stack` navigator. This solution, however, isn't actively maintained. + +The [`react-native-navigation`](https://github.com/wix/react-native-navigation) also comes with support for Shared Element Transitions. You can read more about it [here](https://wix.github.io/react-native-navigation/docs/style-animations#shared-element-transitions). diff --git a/versioned_docs/version-8.x/stack-actions.md b/versioned_docs/version-8.x/stack-actions.md new file mode 100755 index 0000000000..6e1d34e500 --- /dev/null +++ b/versioned_docs/version-8.x/stack-actions.md @@ -0,0 +1,469 @@ +--- +id: stack-actions +title: StackActions reference +sidebar_label: StackActions +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +`StackActions` is an object containing methods for generating actions specific to stack-based navigators. Its methods expand upon the actions available in [`CommonActions`](navigation-actions.md). + +The following actions are supported: + +### replace + +The `replace` action allows to replace a route in the [navigation state](navigation-state.md). It takes the following arguments: + +- `name` - _string_ - A destination name of the route that has been registered somewhere. +- `params` - _object_ - Params to pass to the destination route. + +```js name="Stack actions replace" snack static2dynamic +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +import { + createStaticNavigation, + useNavigation, + StackActions, +} 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 RootStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + +If you want to replace a particular route, you can add a `source` property referring to the route key and `target` property referring to the navigation state key: + +```js +import { StackActions } from '@react-navigation/native'; + +navigation.dispatch({ + ...StackActions.replace('Profile', { + user: 'jane', + }), + source: route.key, + target: navigation.getState().key, +}); +``` + +If the `source` property is explicitly set to `undefined`, it'll replace the focused route. + +### push + +The `push` action adds a route on top of the stack and navigates forward to it. This differs from `navigate` in that `navigate` will pop back to earlier in the stack if a route of the given name is already present there. `push` will always add on top, so a route can be present multiple times. + +- `name` - _string_ - Name of the route to push onto the stack. +- `params` - _object_ - Screen params to pass to the destination route. + +```js name="Stack actions push" snack static2dynamic +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +import { + createStaticNavigation, + useNavigation, + StackActions, +} 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 RootStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + +### pop + +The `pop` action takes you back to a previous screen in the stack. It takes one optional argument (`count`), which allows you to specify how many screens to pop back by. + +```js name="Stack actions pop" snack static2dynamic +import * as React from 'react'; +import { Button } from '@react-navigation/elements'; +import { View, Text } from 'react-native'; +import { + createStaticNavigation, + useNavigation, + StackActions, +} 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 RootStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + +### popTo + +The `popTo` action takes you back to a previous screen in the stack by the name. It also allows you to pass params to the route. + +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 - essentially behaving like a [`replace`](#replace). This ensures that the app doesn't break if a previous screen with the name did not exist - which can happen when the screen was opened from a deep link or push notification, or when used on the web etc. + +The method accepts the following arguments: + +- `name` - _string_ - Name of the route to navigate to. +- `params` - _object_ - Screen params to pass to the destination route. +- `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`. + +```js name="Stack actions popTo" snack static2dynamic +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +import { + createStaticNavigation, + useNavigation, + StackActions, +} 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 || 'Guest'}'s profile + + + + ); +} + +function SettingsScreen() { + const navigation = useNavigation(); + return ( + + Settings! + + + + ); +} + +const RootStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + Settings: SettingsScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` + +### popToTop + +The `popToTop` action takes you back to the first screen in the stack, dismissing all the others. It's functionally identical to `StackActions.pop({n: currentIndex})`. + +```js name="Stack actions popToTop" snack static2dynamic +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +import { + createStaticNavigation, + useNavigation, + StackActions, +} 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 RootStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} +``` diff --git a/versioned_docs/version-8.x/stack-navigator.md b/versioned_docs/version-8.x/stack-navigator.md new file mode 100755 index 0000000000..6a1f3fed9e --- /dev/null +++ b/versioned_docs/version-8.x/stack-navigator.md @@ -0,0 +1,1350 @@ +--- +id: stack-navigator +title: Stack Navigator +sidebar_label: Stack +--- + +Stack Navigator provides a way for your app to transition between screens where each new screen is placed on top of a stack. + +By default the stack navigator is configured to have the familiar iOS and Android look & feel: new screens slide in from the right on iOS, use OS default animation on Android. But the [animations can be customized](#animation-related-options) to match your needs. + + + + + +One thing to keep in mind is that while `@react-navigation/stack` is extremely customizable, it's implemented in JavaScript. While it runs animations and gestures using natively, the performance may not be as fast as a native implementation. This may not be an issue for a lot of apps, but if you're experiencing performance issues during navigation, consider using [`@react-navigation/native-stack`](native-stack-navigator.md) instead - which uses native navigation primitives. + +## 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/stack`](https://github.com/react-navigation/react-navigation/tree/main/packages/stack): + +```bash npm2yarn +npm install @react-navigation/stack +``` + +The navigator depends on [`react-native-gesture-handler`](https://docs.swmansion.com/react-native-gesture-handler/) for gestures and optionally [`@react-native-masked-view/masked-view`](https://github.com/react-native-masked-view/masked-view) for [UIKit style animations for the header](#headerstyleinterpolator). + + + + +If you have a Expo managed project, in your project directory, run: + +```bash +npx expo install react-native-gesture-handler @react-native-masked-view/masked-view +``` + + + + +If you have a bare React Native project, in your project directory, run: + +```bash npm2yarn +npm install react-native-gesture-handler @react-native-masked-view/masked-view +``` + + + + +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 +``` + +## Usage + +To use this navigator, import it from `@react-navigation/stack`: + +```js name="Stack Navigator" 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'; +// codeblock-focus-start +import { createStackNavigator } from '@react-navigation/stack'; + +// codeblock-focus-end +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function ProfileScreen() { + return ( + + Profile Screen + + ); +} + +// codeblock-focus-start +const MyStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyStack); + +export default function App() { + return ; +} +``` + +## API Definition + +### Props + +In addition to the [common props](navigator.md#configuration) shared by all navigators, the stack navigator accepts the following additional props: + +#### `detachInactiveScreens` + +Boolean used to indicate whether inactive screens should be detached from the view hierarchy to save memory. This enables integration with [react-native-screens](https://github.com/software-mansion/react-native-screens). Defaults to `true`. + +If you need to disable this optimization for specific screens (e.g. you want to screen to stay in view even when unfocused) [`detachPreviousScreen`](#detachpreviousscreen) option. + +### 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 `Stack.Navigator` or `options` prop of `Stack.Screen`. + +#### `title` + +String that can be used as a fallback for `headerTitle`. + +#### `cardShadowEnabled` + +Use this prop to have visible shadows during transitions. Defaults to `true`. + +#### `cardOverlayEnabled` + +Use this prop to have a semi-transparent dark overlay visible under the card during transitions. Defaults to `true` on Android and `false` on iOS. + +#### `cardOverlay` + +Function which returns a React Element to display as the overlay for the card. Make sure to set `cardOverlayEnabled` to `true` when using this. + +#### `cardStyle` + +Style object for the card in stack. You can provide a custom background color to use instead of the default background here. + +You can also specify `{ backgroundColor: 'transparent' }` to make the previous screen visible underneath (for transparent modals). This is useful to implement things like modal dialogs. You should also specify `presentation: 'modal'` in the options when using a transparent background so previous screens aren't detached and stay visible underneath. + +On Web, the height of the screen isn't limited to the height of the viewport. This is by design to allow the browser's address bar to hide when scrolling. If this isn't desirable behavior, you can set `cardStyle` to `{ flex: 1 }` to force the screen to fill the viewport. + +#### `presentation` + +This is shortcut option which configures several options to configure the style for rendering and transitions: + +- `card`: Use the default OS animations for iOS and Android screen transitions. +- `modal`: Use Modal animations. This changes a few things: + - Sets `headerMode` to `screen` for the screen unless specified otherwise. + - Changes the screen animation to match the platform behavior for modals. +- `transparentModal`: Similar to `modal`. This changes following things: + - Sets `headerMode` to `screen` for the screen unless specified otherwise. + - Sets background color of the screen to transparent, so previous screen is visible + - Adjusts the `detachPreviousScreen` option so that the previous screen stays rendered. + - Prevents the previous screen from animating from its last position. + - Changes the screen animation to a vertical slide animation. + +See [Transparent modals](#transparent-modals) for more details on how to customize `transparentModal`. + +#### `animationTypeForReplace` + +The type of animation to use when this screen replaces another screen. It takes the following values: + +- `push` - The animation of a new screen being pushed will be used +- `pop` - The animation of a screen being popped will be used + +Defaults to `push`. + +When `pop` is used, the `pop` animation is applied to the screen being replaced. + +#### `gestureEnabled` + +Whether you can use gestures to dismiss this screen. Defaults to `true` on iOS, `false` on Android. + +Gestures are not supported on Web. + +#### `gestureResponseDistance` + +Number to override the distance of touch start from the edge of the screen to recognize gestures. + +It'll configure either the horizontal or vertical distance based on the [`gestureDirection`](#gesturedirection) value. + +The default values are: + +- `50` - when `gestureDirection` is `horizontal` or `horizontal-inverted` +- `135` - when `gestureDirection` is `vertical` or `vertical-inverted` + +This is not supported on Web. + +#### `gestureVelocityImpact` + +Number which determines the relevance of velocity for the gesture. Defaults to 0.3. + +This is not supported on Web. + +#### `gestureDirection` + +Direction of the gestures. Refer the [Animations section](#animations) for details. + +This is not supported on Web. + +#### `transitionSpec` + +Configuration object for the screen transition. Refer the [Animations section](#animations) for details. + +#### `cardStyleInterpolator` + +Interpolated styles for various parts of the card. Refer the [Animations section](#animations) for details. + +#### `headerStyleInterpolator` + +Interpolated styles for various parts of the header. Refer the [Animations section](#animations) for details. + +#### `keyboardHandlingEnabled` + +If `false`, the keyboard will NOT automatically dismiss when navigating to a new screen from this screen. Defaults to `true`. + +#### `detachPreviousScreen` + +Boolean used to indicate whether to detach the previous screen from the view hierarchy to save memory. Set it to `false` if you need the previous screen to be seen through the active screen. Only applicable if `detachInactiveScreens` isn't set to `false`. + +This is automatically adjusted when using [`presentation`](#presentation) as `transparentModal` or `modal` to keep the required screens visible. Defaults to `true` in other cases. + +#### `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. + +### 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 `Stack.Navigator` or `options` prop of `Stack.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 stack: + +#### `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 +- `layout` - Dimensions of the screen, contains `height` and `width` properties. +- `progress` Animated nodes representing the progress of the animation. +- `back` - Options for the back button, contains an object with a `title` property to use for back button label. +- `styleInterpolator` - Function which returns interpolated styles for various elements in the header. + +Make sure to set `headerMode` to `screen` as well when using a custom header (see below for more details). + +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. + +When using a custom header, there are 2 things to keep in mind: + +##### Specify a `height` in `headerStyle` to avoid glitches + +If your header's height differs from the default header height, then you might notice glitches due to measurement being async. Explicitly specifying the height will avoid such glitches. + +Example: + +```js +headerStyle: { + height: 80, // Specify the height of your custom header +}; +``` + +Note that this style is not applied to the header by default since you control the styling of your custom header. If you also want to apply this style to your header, use `headerStyle` from the props. + +##### Set `headerMode` to `float` for custom header animations + +By default, there is one floating header which renders headers for multiple screens on iOS for non-modals. These headers include animations to smoothly switch to one another. + +If you specify a custom header, React Navigation will change it to `screen` automatically so that the header animated along with the screen instead. This means that you don't have to implement animations to animate it separately. + +But you might want to keep the floating header to have a different transition animation between headers. To do that, you'll need to specify `headerMode: 'float'` in the options, and then interpolate on the `progress.current` and `progress.next` props in your custom header. For example, following will cross-fade the header: + +```js +const opacity = Animated.add(progress.current, progress.next || 0).interpolate({ + inputRange: [0, 1, 2], + outputRange: [0, 1, 0], +}); + +return ( + {/* Header content */} +); +``` + +#### `headerMode` + +Specifies how the header should be rendered: + +- `float` - The header is rendered above the screen and animates independently of the screen. This is default on iOS for non-modals. +- `screen` - The header is rendered as part of the screen and animates together with the screen. This is default on other platforms. + +#### `headerShown` + +Whether to show or hide the header for the screen. The header is shown by default. Setting this to `false` hides the header. + +#### `headerBackAllowFontScaling` + +Whether back button title font should scale to respect Text Size accessibility settings. Defaults to false. + +#### `headerBackAccessibilityLabel` + +Accessibility label for the header back button. + +#### `headerBackImage` + +Function which returns a React Element to display custom image in header's back button. When a function is used, it receives the `tintColor` in it's argument object. Defaults to Image component with back image source, which is the default back icon image for the platform (a chevron on iOS and an arrow on Android). + +#### `headerBackTitle` + +Title string used by the back button on iOS. Defaults to the previous scene's title. Use `headerBackButtonDisplayMode` to customize the behavior. + +#### `headerTruncatedBackTitle` + +Title string used by the back button when `headerBackTitle` doesn't fit on the screen. `"Back"` by default. + +#### `headerBackButtonDisplayMode` + +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). +- `minimal`: Always displays only the icon without a title. + +Defaults to `default` on iOS, and `minimal` on Android. + +#### `headerBackTitleStyle` + +Style object for the back title. + +### Events + +The navigator can [emit events](navigation-events.md) on certain actions. Supported events are: + +#### `transitionStart` + +This event is fired when the transition animation starts for the current screen. + +Event data: + +- `e.data.closing` - Boolean indicating whether the screen is being opened or closed. + +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. + +Event data: + +- `e.data.closing` - Boolean indicating whether the screen was opened or closed. + +Example: + +```js +React.useEffect(() => { + const unsubscribe = navigation.addListener('transitionEnd', (e) => { + // Do something + }); + + return unsubscribe; +}, [navigation]); +``` + +#### `gestureStart` + +This event is fired when the swipe gesture starts for the current screen. + +Example: + +```js +React.useEffect(() => { + const unsubscribe = navigation.addListener('gestureStart', (e) => { + // Do something + }); + + return unsubscribe; +}, [navigation]); +``` + +#### `gestureEnd` + +This event is fired when the swipe gesture ends for the current screen. e.g. a screen was successfully dismissed. + +Example: + +```js +React.useEffect(() => { + const unsubscribe = navigation.addListener('gestureEnd', (e) => { + // Do something + }); + + return unsubscribe; +}, [navigation]); +``` + +#### `gestureCancel` + +This event is fired when the swipe gesture is cancelled for the current screen. e.g. a screen wasn't dismissed by the gesture. + +Example: + +```js +React.useEffect(() => { + const unsubscribe = navigation.addListener('gestureCancel', (e) => { + // Do something + }); + + return unsubscribe; +}, [navigation]); +``` + +### Helpers + +The stack navigator adds the following methods to the navigation object: + +#### `replace` + +Replaces the current screen with a new screen in the stack. The method accepts the following arguments: + +- `name` - _string_ - Name of the route to push onto the stack. +- `params` - _object_ - Screen params to pass to the destination route. + +```js +navigation.replace('Profile', { owner: 'Michaś' }); +``` + +#### `push` + +Pushes a new screen to the top of the stack and navigate to it. The method accepts the following arguments: + +- `name` - _string_ - Name of the route to push onto the stack. +- `params` - _object_ - Screen params to pass to the destination route. + +```js +navigation.push('Profile', { owner: 'Michaś' }); +``` + +#### `pop` + +Pops the current screen from the stack and navigates back to the previous screen. It takes one optional argument (`count`), which allows you to specify how many screens to pop back by. + +```js +navigation.pop(); +``` + +#### `popTo` + +Navigates back to a previous screen in the stack by popping screens after it. The method accepts the following arguments: + +- `name` - _string_ - Name of the route to navigate to. +- `params` - _object_ - Screen params to pass to the destination route. +- `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. + +```js +navigation.popTo('Profile', { owner: 'Michaś' }); +``` + +#### `popToTop` + +Pops all of the screens in the stack except the first one and navigates to it. + +```js +navigation.popToTop(); +``` + +### Hooks + +The stack navigator exports the following hooks: + +#### `useCardAnimation` + +This hook returns values related to the screen's animation. It contains the following properties: + +- `current` - Values for the current screen: + - `progress` - Animated node representing the progress value of the current screen. +- `next` - Values for the screen after this one in the stack. This can be `undefined` in case the screen animating is the last one. + - `progress` - Animated node representing the progress value of the next screen. +- `closing` - Animated node representing whether the card is closing. `1` when closing, `0` if not. +- `swiping` - Animated node representing whether the card is being swiped. `1` when swiping, `0` if not. +- `inverted` - Animated node representing whether the card is inverted. `-1` when inverted, `1` if not. +- `index` - The index of the card in the stack. +- `layouts` - Layout measurements for various items we use for animation. + - `screen` - Layout of the whole screen. Contains `height` and `width` properties. +- `insets` - Layout of the safe area insets. Contains `top`, `right`, `bottom` and `left` properties. + +See [Transparent modals](#transparent-modals) for an example of how to use this hook. + +## Animations + +You can specify the `animation` option to customize the transition animation for screens being pushed or popped. + +Supported values for `animation` are: + +- `default` - Default animation based on the platform and OS version. +- `fade` - Simple fade animation for dialogs. +- `fade_from_bottom` - Standard Android-style fade-in from the bottom for Android Oreo. +- `fade_from_right` - Standard Android-style fade-in from the right for Android 14. +- `reveal_from_bottom` - Standard Android-style reveal from the bottom for Android Pie. +- `scale_from_center` - Scale animation from the center. +- `slide_from_right` - Standard iOS-style slide in from the right. +- `slide_from_left` - Similar to `slide_from_right`, but the screen will slide in from the left. +- `slide_from_bottom` - Slide animation from the bottom for modals and bottom sheets. +- `none` - The screens are pushed or popped immediately without any animation. + +By default, Android and iOS use the `default` animation and other platforms use `none`. + +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 + +Stack Navigator exposes various options to configure the transition animation when a screen is added or removed. These transition animations can be customized on a per-screen basis by specifying the options in the `options` prop for each screen. + +- `gestureDirection` - The direction of swipe gestures: + - `horizontal` - The gesture to close the screen will start from the left, and from the right in RTL. For animations, screen will slide from the right with `SlideFromRightIOS`, and from the left in RTL. + - `horizontal-inverted` - The gesture to close the screen will start from the right, and from the left in RTL. For animations, screen will slide from the left with `SlideFromRightIOS`, and from the right in RTL as the direction is inverted. + - `vertical` - The gesture to close the screen will start from the top. For animations, screen will slide from the bottom. + - `vertical-inverted` - The gesture to close the screen will start from the bottom. For animations, screen will slide from the top. + + You may want to specify a matching horizontal/vertical animation along with `gestureDirection` as well. For the animations included in the library, if you set `gestureDirection` to one of the inverted ones, it'll also flip the animation direction. + +- `transitionSpec` - An object which specifies the animation type (`timing` or `spring`) and their options (such as `duration` for `timing`). It takes 2 properties: + - `open` - Configuration for the transition when adding a screen + - `close` - Configuration for the transition when removing a screen. + + Each of the object should specify 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`. + + A config which uses spring animation looks like this: + + ```js + const config = { + animation: 'spring', + config: { + stiffness: 1000, + damping: 500, + mass: 3, + overshootClamping: true, + restDisplacementThreshold: 0.01, + restSpeedThreshold: 0.01, + }, + }; + ``` + + We can pass this config in the `transitionSpec` option: + + ```js name="Custom Transition Config" 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 { createStackNavigator } from '@react-navigation/stack'; + + function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); + } + + function ProfileScreen() { + const navigation = useNavigation(); + + return ( + + Profile Screen + + + ); + } + + // codeblock-focus-start + const config = { + animation: 'spring', + config: { + stiffness: 1000, + damping: 500, + mass: 3, + overshootClamping: true, + restDisplacementThreshold: 0.01, + restSpeedThreshold: 0.01, + }, + }; + + const MyStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: { + screen: ProfileScreen, + options: { + transitionSpec: { + open: config, + close: config, + }, + }, + }, + }, + }); + // codeblock-focus-end + + const Navigation = createStaticNavigation(MyStack); + + export default function App() { + return ; + } + ``` + +- `cardStyleInterpolator` - This is a function which specifies interpolated styles for various parts of the card. This allows you to customize the transitions when navigating from screen to screen. It is expected to return at least empty object, possibly containing interpolated styles for container, the card itself, overlay and shadow. Supported properties are: + - `containerStyle` - Style for the container view wrapping the card. + - `cardStyle` - Style for the view representing the card. + - `overlayStyle` - Style for the view representing the semi-transparent overlay below + - `shadowStyle` - Style for the view representing the card shadow. + + The function receives the following properties in its argument: + - `current` - Values for the current screen: + - `progress` - Animated node representing the progress value of the current screen. + - `next` - Values for the screen after this one in the stack. This can be `undefined` in case the screen animating is the last one. + - `progress` - Animated node representing the progress value of the next screen. + - `index` - The index of the card in the stack. + - `closing` - Animated node representing whether the card is closing. `1` when closing, `0` if not. + - `layouts` - Layout measurements for various items we use for animation. + - `screen` - Layout of the whole screen. Contains `height` and `width` properties. + + > **Note that when a screen is not the last, it will use the next screen's transition config.** This is because many transitions involve an animation of the previous screen, and so these two transitions need to be kept together to prevent running two different kinds of transitions on the two screens (for example a slide and a modal). You can check the `next` parameter to find out if you want to animate out the previous screen. For more information about this parameter, see [Animation](stack-navigator.md#animations) section. + + A config which just fades the screen looks like this: + + ```js + const forFade = ({ current }) => ({ + cardStyle: { + opacity: current.progress, + }, + }); + ``` + + We can pass this function in `cardStyleInterpolator` option: + + ```js name="Custom Card Style Interpolator" 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 { createStackNavigator } from '@react-navigation/stack'; + + function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); + } + + function ProfileScreen() { + const navigation = useNavigation(); + + return ( + + Profile Screen + + + ); + } + + // codeblock-focus-start + const forFade = ({ current }) => ({ + cardStyle: { + opacity: current.progress, + }, + }); + + const MyStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: { + screen: ProfileScreen, + options: { + cardStyleInterpolator: forFade, + }, + }, + }, + }); + // codeblock-focus-end + + const Navigation = createStaticNavigation(MyStack); + + export default function App() { + return ; + } + ``` + +The interpolator will be called for each screen. For example, say you have a 2 screens in the stack, A & B. B is the new screen coming into focus and A is the previous screen. The interpolator will be called for each screen: + +- The interpolator is called for `B`: Here, the `current.progress` value represents the progress of the transition, which will start at `0` and end at `1`. There won't be a `next.progress` since `B` is the last screen. +- The interpolator is called for `A`: Here, the `current.progress` will stay at the value of `1` and won't change, since the current transition is running for `B`, not `A`. The `next.progress` value represents the progress of `B` and will start at `0` and end at `1`. + +Say we want to animate both screens during the transition. The easiest way to do it would be to combine the progress value of current and next screens: + +```js +const progress = Animated.add( + current.progress.interpolate({ + inputRange: [0, 1], + outputRange: [0, 1], + extrapolate: 'clamp', + }), + next + ? next.progress.interpolate({ + inputRange: [0, 1], + outputRange: [0, 1], + extrapolate: 'clamp', + }) + : 0 +); +``` + +Here, the screen `A` will have both `current.progress` and `next.progress`, and since `current.progress` stays at `1` and `next.progress` is changing, combined, the progress will change from `1` to `2`. The screen `B` will only have `current.progress` which will change from `0` to `1`. So, we can apply different interpolations for `0-1` and `1-2` to animate focused screen and unfocused screen respectively. + +A config which translates the previous screen slightly to the left, and translates the current screen from the right edge would look like this: + +```js +const forSlide = ({ current, next, inverted, layouts: { screen } }) => { + const progress = Animated.add( + current.progress.interpolate({ + inputRange: [0, 1], + outputRange: [0, 1], + extrapolate: 'clamp', + }), + next + ? next.progress.interpolate({ + inputRange: [0, 1], + outputRange: [0, 1], + extrapolate: 'clamp', + }) + : 0 + ); + + return { + cardStyle: { + transform: [ + { + translateX: Animated.multiply( + progress.interpolate({ + inputRange: [0, 1, 2], + outputRange: [ + screen.width, // Focused, but offscreen in the beginning + 0, // Fully focused + screen.width * -0.3, // Fully unfocused + ], + extrapolate: 'clamp', + }), + inverted + ), + }, + ], + }, + }; +}; +``` + +- `headerStyleInterpolator` - This is a function which specifies interpolated styles for various parts of the header. It is expected to return at least empty object, possibly containing interpolated styles for left label and button, right button, title and background. Supported properties are: + - `leftLabelStyle` - Style for the label of the left button (back button label). + - `leftButtonStyle` - Style for the left button (usually the back button). + - `rightButtonStyle` - Style for the right button. + - `titleStyle` - Style for the header title text. + - `backgroundStyle` - Style for the header background. + + The function receives the following properties in it's argument: + - `current` - Values for the current screen (the screen which owns this header). + - `progress` - Animated node representing the progress value of the current screen. `0` when screen should start coming into view, `0.5` when it's mid-way, `1` when it should be fully in view. + - `next` - Values for the screen after this one in the stack. This can be `undefined` in case the screen animating is the last one. + - `progress` - Animated node representing the progress value of the next screen. + - `layouts` - Layout measurements for various items we use for animation. Each layout object contain `height` and `width` properties. + - `screen` - Layout of the whole screen. + - `title` - Layout of the title element. Might be `undefined` when not rendering a title. + - `leftLabel` - Layout of the back button label. Might be `undefined` when not rendering a back button label. + + A config that just fades the elements looks like this: + + ```js + const forFade = ({ current, next }) => { + const opacity = Animated.add( + current.progress, + next ? next.progress : 0 + ).interpolate({ + inputRange: [0, 1, 2], + outputRange: [0, 1, 0], + }); + + return { + leftButtonStyle: { opacity }, + rightButtonStyle: { opacity }, + titleStyle: { opacity }, + backgroundStyle: { opacity }, + }; + }; + ``` + + We can pass this function in `headerStyleInterpolator` option: + + ```js name="Custom Header Style Interpolator" 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 { createStackNavigator } from '@react-navigation/stack'; + + function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); + } + + function ProfileScreen() { + const navigation = useNavigation(); + + return ( + + Profile Screen + + + ); + } + + // codeblock-focus-start + const forFade = ({ current, next }) => { + const opacity = Animated.add( + current.progress, + next ? next.progress : 0 + ).interpolate({ + inputRange: [0, 1, 2], + outputRange: [0, 1, 0], + }); + + return { + leftButtonStyle: { opacity }, + rightButtonStyle: { opacity }, + titleStyle: { opacity }, + backgroundStyle: { opacity }, + }; + }; + + const MyStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: { + screen: ProfileScreen, + options: { + headerStyleInterpolator: forFade, + }, + }, + }, + }); + // codeblock-focus-end + + const Navigation = createStaticNavigation(MyStack); + + export default function App() { + return ; + } + ``` + +### Pre-made configs + +With these options, it's possible to build custom transition animations for screens. We also export various configs from the library with ready-made animations which you can use: + +#### `TransitionSpecs` + +- `TransitionIOSSpec` - Exact values from UINavigationController's animation configuration. +- `FadeInFromBottomAndroidSpec` - Configuration for activity open animation from Android Nougat. +- `FadeOutToBottomAndroidSpec` - Configuration for activity close animation from Android Nougat. +- `RevealFromBottomAndroidSpec` - Approximate configuration for activity open animation from Android Pie. + +Example: + +```js +import { TransitionSpecs } from '@react-navigation/stack'; + +// ... + +; +``` + +#### `CardStyleInterpolators` + +- `forHorizontalIOS` - Standard iOS-style slide in from the right. +- `forVerticalIOS` - Standard iOS-style slide in from the bottom (used for modals). +- `forModalPresentationIOS` - Standard iOS-style modal animation in iOS 13. +- `forFadeFromBottomAndroid` - Standard Android-style fade in from the bottom for Android Oreo. +- `forRevealFromBottomAndroid` - Standard Android-style reveal from the bottom for Android Pie. + +Example configuration for Android Oreo style vertical screen fade animation: + +```js name="Card Style Interpolators" 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'; +// codeblock-focus-start +import { + createStackNavigator, + CardStyleInterpolators, +} from '@react-navigation/stack'; +// codeblock-focus-end + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function ProfileScreen() { + const navigation = useNavigation(); + + return ( + + Profile Screen + + + ); +} + +// codeblock-focus-start +const MyStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: { + screen: ProfileScreen, + options: { + title: 'Profile', + cardStyleInterpolator: CardStyleInterpolators.forFadeFromBottomAndroid, + }, + }, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyStack); + +export default function App() { + return ; +} +``` + +#### `HeaderStyleInterpolators` + +- `forUIKit` - Standard UIKit style animation for the header where the title fades into the back button label. +- `forFade` - Simple fade animation for the header elements. +- `forStatic` - Simple translate animation to translate the header along with the sliding screen. + +Example configuration for default iOS animation for header elements where the title fades into the back button: + +```js name="Header Style Interpolators" 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'; +// codeblock-focus-start +import { + createStackNavigator, + HeaderStyleInterpolators, +} from '@react-navigation/stack'; +// codeblock-focus-end + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function ProfileScreen() { + const navigation = useNavigation(); + + return ( + + Profile Screen + + + ); +} + +// codeblock-focus-start +const MyStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: { + screen: ProfileScreen, + options: { + title: 'Profile', + headerStyleInterpolator: HeaderStyleInterpolators.forUIKit, + }, + }, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyStack); + +export default function App() { + return ; +} +``` + +:::warning + +Always define your animation configuration at the top-level of the file to ensure that the references don't change across re-renders. This is important for smooth and reliable transition animations. + +::: + +#### `TransitionPresets` + +We export various transition presets which bundle various set of these options together to match certain native animations. A transition preset is an object containing few animation related screen options exported under `TransitionPresets`. Currently the following presets are available: + +- `SlideFromRightIOS` - Standard iOS navigation transition. +- `ModalSlideFromBottomIOS` - Standard iOS navigation transition for modals. +- `ModalPresentationIOS` - Standard iOS modal presentation style (introduced in iOS 13). +- `FadeFromBottomAndroid` - Standard Android navigation transition when opening or closing an Activity on Android < 9 (Oreo). +- `RevealFromBottomAndroid` - Standard Android navigation transition when opening or closing an Activity on Android 9 (Pie). +- `ScaleFromCenterAndroid` - Standard Android navigation transition when opening or closing an Activity on Android >= 10. +- `DefaultTransition` - Default navigation transition for the current platform. +- `ModalTransition` - Default modal transition for the current platform. + +You can spread these presets in `options` to customize the animation for a screen: + +```js name="Transition Presets - Modal Slide" 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'; +// codeblock-focus-start +import { + createStackNavigator, + TransitionPresets, +} from '@react-navigation/stack'; +// codeblock-focus-end + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function ProfileScreen() { + const navigation = useNavigation(); + + return ( + + Profile Screen + + + ); +} + +// codeblock-focus-start +const MyStack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: { + screen: ProfileScreen, + options: { + title: 'Profile', + ...TransitionPresets.ModalSlideFromBottomIOS, + }, + }, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyStack); + +export default function App() { + return ; +} +``` + +If you want to customize the transition animations for all of the screens in the navigator, you can specify it in `screenOptions` prop for the navigator. + +Example configuration for iOS modal presentation style: + +```js name="Transition Presets - Modal Presentation" 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'; +// codeblock-focus-start +import { + createStackNavigator, + TransitionPresets, +} from '@react-navigation/stack'; +// codeblock-focus-end + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function ProfileScreen() { + const navigation = useNavigation(); + + return ( + + Profile Screen + + + ); +} + +// codeblock-focus-start +const MyStack = createStackNavigator({ + initialRouteName: 'Home', + screenOptions: { + headerShown: false, + ...TransitionPresets.ModalPresentationIOS, + }, + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyStack); + +export default function App() { + return ; +} +``` + +### Transparent modals + +A transparent modal is like a modal dialog which overlays the screen. The previous screen still stays visible underneath. To get a transparent modal screen, you can specify `presentation: 'transparentModal'` in the screen's options. + +Example: + +```js + + + + +``` + +Now, when you navigate to the `Modal` screen, it'll have a transparent background and the `Home` screen will be visible underneath. + +In addition to `presentation`, you might want to optionally specify few more things to get a modal dialog like behavior: + +- Disable the header with `headerShown: false` +- Enable the overlay with `cardOverlayEnabled: true` (you can't tap the overlay to close the screen this way, see below for alternatives) + +If you want to further customize how the dialog animates, or want to close the screen when tapping the overlay etc., you can use the `useCardAnimation` hook to customize elements inside your screen. + +Example: + +```js +import { Animated, View, Text, Pressable, StyleSheet } from 'react-native'; +import { useTheme, useNavigation } from '@react-navigation/native'; +import { useCardAnimation } from '@react-navigation/stack'; +import { Button } from '@react-navigation/elements'; + +function ModalScreen() { + const navigation = useNavigation(); + const { colors } = useTheme(); + const { current } = useCardAnimation(); + + return ( + + + + + Mise en place is a French term that literally means “put in place.” It + also refers to a way cooks in professional kitchens and restaurants + set up their work stations—first by gathering all ingredients for a + recipes, partially preparing them (like measuring out and chopping), + and setting them all near each other. Setting up mise en place before + cooking is another top tip for home cooks, as it seriously helps with + organization. It’ll pretty much guarantee you never forget to add an + ingredient and save you time from running back and forth from the + pantry ten times. + + + + + ); +} +``` + +Here we animate the scale of the dialog, and also add an overlay to close the dialog. diff --git a/versioned_docs/version-8.x/state-persistence.md b/versioned_docs/version-8.x/state-persistence.md new file mode 100755 index 0000000000..1e9cc94e24 --- /dev/null +++ b/versioned_docs/version-8.x/state-persistence.md @@ -0,0 +1,314 @@ +--- +id: state-persistence +title: State persistence +sidebar_label: State persistence +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +You might want to save the user's location in the app, so that they are immediately returned to the same location after the app is restarted. + +This is especially valuable during development because it allows the developer to stay on the same screen when they refresh the app. + +## Usage + +To be able to persist the [navigation state](navigation-state.md), we can use the `onStateChange` and `initialState` props of the container. + +- `onStateChange` - This prop notifies us of any state changes. We can persist the state in this callback. +- `initialState` - This prop allows us to pass an initial state to use for [navigation state](navigation-state.md). We can pass the restored state in this prop. + + + + +```js name="Persisting the navigation state" snack dependencies=@react-native-async-storage/async-storage +import * as React from 'react'; +// codeblock-focus-start +import { Platform, View, Linking } from 'react-native'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { + useNavigation, + createStaticNavigation, +} from '@react-navigation/native'; +// codeblock-focus-end +import { Button } from '@react-navigation/elements'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function A() { + return ; +} + +function B() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +function C() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +function D() { + return ; +} + +const HomeStackScreen = createNativeStackNavigator({ + screens: { + A: A, + }, +}); + +const SettingsStackScreen = createNativeStackNavigator({ + screens: { + B: B, + C: C, + D: D, + }, +}); + +const Tab = createBottomTabNavigator({ + screens: { + Home: { + screen: HomeStackScreen, + options: { + headerShown: false, + tabBarLabel: 'Home!', + }, + }, + Settings: { + screen: SettingsStackScreen, + options: { + headerShown: false, + tabBarLabel: 'Settings!', + }, + }, + }, +}); + +const Navigation = createStaticNavigation(Tab); + +// codeblock-focus-start + +const PERSISTENCE_KEY = 'NAVIGATION_STATE_V1'; + +export default function App() { + const [isReady, setIsReady] = React.useState(Platform.OS === 'web'); // Don't persist state on web since it's based on URL + const [initialState, setInitialState] = React.useState(); + + React.useEffect(() => { + const restoreState = async () => { + try { + const initialUrl = await Linking.getInitialURL(); + + if (Platform.OS !== 'web' && initialUrl == null) { + const savedState = await AsyncStorage.getItem(PERSISTENCE_KEY); + const state = savedState ? JSON.parse(savedState) : undefined; + + if (state !== undefined) { + setInitialState(state); + } + } + } finally { + setIsReady(true); + } + }; + + if (!isReady) { + restoreState(); + } + }, [isReady]); + + if (!isReady) { + return null; + } + + return ( + + AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state)) + } + /> + ); +} +// codeblock-focus-end +``` + + + + +```js name="Persisting the navigation state" snack dependencies=@react-native-async-storage/async-storage +import * as React from 'react'; +// codeblock-focus-start +import { Platform, View, Linking } from 'react-native'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +// codeblock-focus-end +import { Button } from '@react-navigation/elements'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +const Tab = createBottomTabNavigator(); +const HomeStack = createNativeStackNavigator(); +const SettingsStack = createNativeStackNavigator(); + +function A() { + return ; +} + +function B() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +function C() { + const navigation = useNavigation(); + + return ( + + + + ); +} + +function D() { + return ; +} + +function HomeStackScreen() { + return ( + + + + ); +} + +function SettingsStackScreen() { + return ( + + + + + + ); +} + +function RootTabs() { + return ( + + + + + ); +} + +// codeblock-focus-start + +const PERSISTENCE_KEY = 'NAVIGATION_STATE_V1'; + +export default function App() { + const [isReady, setIsReady] = React.useState(Platform.OS === 'web'); // Don't persist state on web since it's based on URL + const [initialState, setInitialState] = React.useState(); + + React.useEffect(() => { + const restoreState = async () => { + try { + const initialUrl = await Linking.getInitialURL(); + + if (initialUrl == null) { + // Only restore state if there's no deep link + const savedStateString = await AsyncStorage.getItem(PERSISTENCE_KEY); + const state = savedStateString + ? JSON.parse(savedStateString) + : undefined; + + if (state !== undefined) { + setInitialState(state); + } + } + } finally { + setIsReady(true); + } + }; + + if (!isReady) { + restoreState(); + } + }, [isReady]); + + if (!isReady) { + return null; + } + + return ( + + AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state)) + } + > + + + ); +} +// codeblock-focus-end +``` + + + + +:::warning + +It is recommended to use an [error boundary](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) in your app and clear the persisted state if an error occurs. This will ensure that the app doesn't get stuck in an error state if a screen crashes. + +::: + +### Development Mode + +This feature is particularly useful in development mode. You can enable it selectively using the following approach: + +```js +const [isReady, setIsReady] = React.useState(__DEV__ ? false : true); +``` + +While it can be used for production as well, use it with caution as it can make the app unusable if the app is crashing on a particular screen - as the user will still be on the same screen after restarting. So if you are using it in production, make sure to clear the persisted state if an error occurs. + +### Loading View + +Because the state is restored asynchronously, the app must render an empty/loading view for a moment before we have the initial state. To handle this, we can return a loading view when `isReady` is `false`: + +```js +if (!isReady) { + return ; +} +``` + +## Warning: Serializable State + +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#stale-state-objects), React Navigation may not be able to handle the situation gracefully in some scenarios. diff --git a/versioned_docs/version-8.x/static-configuration.md b/versioned_docs/version-8.x/static-configuration.md new file mode 100644 index 0000000000..a272bef5a7 --- /dev/null +++ b/versioned_docs/version-8.x/static-configuration.md @@ -0,0 +1,260 @@ +--- +id: static-configuration +title: Static configuration +sidebar_label: Static configuration +--- + +The bulk of the static configuration is done using the `createXNavigator` functions, e.g. [`createNativeStackNavigator`](native-stack-navigator.md), [`createBottomTabNavigator`](bottom-tab-navigator.md), [`createDrawerNavigator`](drawer-navigator.md) etc. We'll refer to these functions as `createXNavigator` in the rest of this guide. + +## `createXNavigator` + +The `createXNavigator` functions take one argument, which is an object with the following properties: + +- Same props as the navigator component, e.g. `id`, `initialRouteName`, `screenOptions` etc. See [Navigator](navigator.md) as well as the docs for each navigator for more details on the props they accept. +- `screens` - an object containing configuration for each screen in the navigator. +- `groups` - an optional object containing groups of screens (equivalent to [`Group`](group.md) in the dynamic API). + +For example: + +```js +const RootStack = createNativeStackNavigator({ + initialRouteName: 'Home', + screenOptions: { + headerTintColor: 'white', + headerStyle: { + backgroundColor: 'tomato', + }, + }, + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +``` + +### `screens` + +The `screens` object can contain key value pairs where the key is the name of the screen and the value can be several things: + +- A component to render: + + ```js + const RootStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + }, + }); + ``` + +- A navigator configured using `createXNavigator` for nested navigators: + + ```js + const HomeTabs = createBottomTabNavigator({ + screens: { + Groups: GroupsScreen, + Chats: ChatsScreen, + }, + }); + + const RootStack = createNativeStackNavigator({ + screens: { + Home: HomeTabs, + }, + }); + ``` + +- An object containing configuration for the screen. This configuration contains the various properties: + + ```js + const RootStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + linking: { + path: 'home', + }, + }, + }, + }); + ``` + + See [Screen configuration](#screen-configuration) for more details. + +### `groups` + +The `groups` object can contain key-value pairs where the key is the name of the group and the value is the group configuration. + +The configuration object for a screen accepts the [properties described in the Group page](group.md). In addition, the following properties are available when using static configuration: + +- `if` - this can be used to conditionally render the group and works the same as the [`if` property in the screen configuration](#if). +- `screens` - an object containing configuration for each screen in the group. The configuration is the same as the [`screens` object in the navigator configuration](#screens). + +Example: + +```js +const RootStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, + groups: { + Guest: { + if: useIsGuest, + screenOptions: { + headerShown: false, + }, + screens: { + // ... + }, + }, + User: { + if: useIsUser, + screens: { + // ... + }, + }, + }, +}); +``` + +### Screen configuration + +The configuration object for a screen accepts the [properties described in the Screen page](screen.md). In addition, the following properties are available when using static configuration: + +#### `linking` + +[Linking configuration](configuring-links.md) for the screen. It can be either a string for a path or an object with the linking configuration: + +```js +const RootStack = createNativeStackNavigator({ + screens: { + Profile: { + screen: ProfileScreen, + linking: { + path: 'u/:userId', + parse: { + userId: (id) => id.replace(/^@/, ''), + }, + stringify: { + userId: (id) => `@${id}`, + }, + }, + }, + Chat: { + screen: ChatScreen, + linking: 'chat/:chatId', + }, + }, +}); +``` + +The `linking` object supports the same configuration options described in [Configuring links](configuring-links.md) such as `parse`, `stringify` and `exact`. + +To make deep links work on native apps, you also need to [configure your app](deep-linking.md) and pass `prefixes` to the navigation component returned by [`createStaticNavigation`](static-configuration.md#createstaticnavigation): + +```js +const Navigation = createStaticNavigation(RootStack); + +const linking = { + prefixes: ['https://example.com', 'example://'], +}; + +function App() { + return ; +} +``` + +#### `if` + +Callback to determine whether the screen should be rendered or not. It doesn't receive any arguments. This can be useful for conditional rendering of screens, e.g. - if you want to render a different screen for logged in users. + +You can use a custom hook to use custom logic to determine the return value: + +```js +const useIsLoggedIn = () => { + const { isLoggedIn } = React.useContext(AuthContext); + + return isLoggedIn; +}; + +const RootStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + if: useIsLoggedIn, + }, + }, +}); +``` + +The above example will only render the `HomeScreen` if the user is logged in. + +For more details, see [Authentication flow](auth-flow.md?config=static). + +## `createStaticNavigation` + +The `createStaticNavigation` function takes the static config returned by `createXNavigator` functions and returns a React component to render: + +```js +const Navigation = createStaticNavigation(RootStack); + +function App() { + return ; +} +``` + +This component is a wrapper around the `NavigationContainer` component and accepts the [same props and ref as the `NavigationContainer`](navigation-container.md) component. It is intended to be rendered once at the root of your app similar to how you'd use `NavigationContainer` component. + +### Differences in the `linking` prop + +Similar to `NavigationContainer`, the component returned by `createStaticNavigation` also accepts a [`linking`](navigation-container.md#linking) prop. However, there are some key differences: + +1. It's not possible to pass a full `config` object to the `linking` prop. It can only accept [`path`](configuring-links.md#apps-under-subpaths) and an [`initialRouteName` for the root navigator](configuring-links.md#rendering-an-initial-route). +2. The linking config is collected from the [`linking`](#linking) properties specified in the screen configuration. +3. It's possible to pass `enabled: 'auto'` to automatically generate paths for all leaf screens: + + ```js + const Navigation = createStaticNavigation(RootStack); + + const linking = { + enabled: 'auto', + prefixes: ['https://example.com', 'example://'], + }; + + function App() { + return ; + } + ``` + + See [How does automatic path generation work](configuring-links.md#how-does-automatic-path-generation-work) for more details. + +## `createComponentForStaticNavigation` + +The `createComponentForStaticNavigation` function takes the static config returned by `createXNavigator` functions and returns a React component to render. The second argument is a name for the component that'd be used in React DevTools: + +```js +const RootStackNavigator = createComponentForStaticNavigation( + RootStack, + 'RootNavigator' +); +``` + +The returned component doesn't take any props. All of the configuration is inferred from the static config. It's essentially the same as defining a component using the dynamic API. + +This looks similar to `createStaticNavigation` however they are very different. When using static configuration, you'd never use this function directly. The only time you'd use this is if you're migrating away from static configuration and want to reuse existing code you wrote instead of rewriting it to the dynamic API. See [Combining static and dynamic APIs](combine-static-with-dynamic.md) for more details. + +## `createPathConfigForStaticNavigation` + +The `createPathConfigForStaticNavigation` function takes the static config returned by `createXNavigator` functions and returns a path config object that can be used within the linking config. + +```js +const config = { + screens: { + Home: { + screens: createPathConfigForStaticNavigation(HomeTabs), + }, + }, +}; +``` + +Similar to `createComponentForStaticNavigation`, this is intended to be used when migrating away from static configuration. See [Combining static and dynamic APIs](combine-static-with-dynamic.md) for more details. diff --git a/versioned_docs/version-8.x/status-bar.md b/versioned_docs/version-8.x/status-bar.md new file mode 100755 index 0000000000..9b97b4820d --- /dev/null +++ b/versioned_docs/version-8.x/status-bar.md @@ -0,0 +1,453 @@ +--- +id: status-bar +title: Different status bar configuration based on route +sidebar_label: Status bar configuration +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +If you don't have a navigation header, or your navigation header changes color based on the route, you'll want to ensure that the correct color is used for the content. + +## Stack + +This is a simple task when using a stack. You can render the `StatusBar` component, which is exposed by React Native, and set your config. + + + + +```js name="Different status bar" snack +import * as React from 'react'; +import { View, Text, StatusBar, StyleSheet } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +function Screen1() { + const navigation = useNavigation(); + const insets = useSafeAreaInsets(); + + return ( + + // highlight-start + + // highlight-end + Light Screen + + + ); +} + +function Screen2() { + const navigation = useNavigation(); + const insets = useSafeAreaInsets(); + + return ( + + // highlight-start + + // highlight-end + Dark Screen + + + ); +} + +const RootStack = createNativeStackNavigator({ + screenOptions: { + headerShown: false, + }, + screens: { + Screen1: Screen1, + Screen2: Screen2, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} + +const styles = StyleSheet.create({ + container: { flex: 1, justifyContent: 'center', alignItems: 'center' }, +}); +``` + + + + + +```js name="Different status bar" snack +import * as React from 'react'; +import { View, Text, StatusBar, StyleSheet } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; +import { + SafeAreaProvider, + useSafeAreaInsets, +} from 'react-native-safe-area-context'; + +function Screen1() { + const navigation = useNavigation(); + const insets = useSafeAreaInsets(); + + return ( + + // highlight-start + + // highlight-end + Light Screen + + + ); +} + +function Screen2() { + const navigation = useNavigation(); + const insets = useSafeAreaInsets(); + + return ( + + // highlight-start + + // highlight-end + Dark Screen + + + ); +} + +const Stack = createNativeStackNavigator(); + +export default function App() { + return ( + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, +}); +``` + + + + + + + + +## Tabs and Drawer + +If you're using a tab or drawer navigator, it's a bit more complex because all of the screens in the navigator might be rendered at once and kept rendered - that means that the last `StatusBar` config you set will be used (likely on the final tab of your tab navigator, not what the user is seeing). + +To fix this, we'll have to do make the status bar component aware of screen focus and render it only when the screen is focused. We can achieve this by using the [`useIsFocused` hook](use-is-focused.md) and creating a wrapper component: + +```js +import * as React from 'react'; +import { StatusBar } from 'react-native'; +import { useIsFocused } from '@react-navigation/native'; + +function FocusAwareStatusBar(props) { + const isFocused = useIsFocused(); + + return isFocused ? : null; +} +``` + +Now, our screens (both `Screen1.js` and `Screen2.js`) will use the `FocusAwareStatusBar` component instead of the `StatusBar` component from React Native: + + + + +```js name="Different status bar based on tabs" snack +import * as React from 'react'; +import { View, Text, StatusBar, StyleSheet } from 'react-native'; +import { useIsFocused } from '@react-navigation/native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +function FocusAwareStatusBar(props) { + const isFocused = useIsFocused(); + + return isFocused ? : null; +} + +// codeblock-focus-start +function Screen1() { + const navigation = useNavigation(); + const insets = useSafeAreaInsets(); + + return ( + + + Light Screen + + + ); +} + +function Screen2() { + const navigation = useNavigation(); + const insets = useSafeAreaInsets(); + + return ( + + + Dark Screen + + + ); +} +// codeblock-focus-end + +const RootStack = createNativeStackNavigator({ + screenOptions: { + headerShown: false, + }, + screens: { + Screen1: Screen1, + Screen2: Screen2, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, +}); +``` + + + + +```js name="Different status bar based on tabs" snack +import * as React from 'react'; +import { View, Text, StatusBar, StyleSheet } from 'react-native'; +import { useIsFocused } from '@react-navigation/native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { Button } from '@react-navigation/elements'; +import { + SafeAreaProvider, + useSafeAreaInsets, +} from 'react-native-safe-area-context'; + +function FocusAwareStatusBar(props) { + const isFocused = useIsFocused(); + + return isFocused ? : null; +} + +// codeblock-focus-start +function Screen1() { + const navigation = useNavigation(); + const insets = useSafeAreaInsets(); + + return ( + + + Light Screen + + + ); +} + +function Screen2() { + const navigation = useNavigation(); + const insets = useSafeAreaInsets(); + + return ( + + + Dark Screen + + + ); +} +// codeblock-focus-end + +const Stack = createNativeStackNavigator(); + +export default function App() { + return ( + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, +}); +``` + + + + +Although not necessary, you can use the `FocusAwareStatusBar` component in the screens of the native stack navigator as well. + +
+ + + + + + + +
diff --git a/versioned_docs/version-8.x/tab-actions.md b/versioned_docs/version-8.x/tab-actions.md new file mode 100755 index 0000000000..0b3f0a0780 --- /dev/null +++ b/versioned_docs/version-8.x/tab-actions.md @@ -0,0 +1,75 @@ +--- +id: tab-actions +title: TabActions reference +sidebar_label: TabActions +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +`TabActions` is an object containing methods for generating actions specific to tab-based navigators. Its methods expand upon the actions available in [`CommonActions`](navigation-actions.md). + +The following actions are supported: + +### jumpTo + +The `jumpTo` action can be used to jump to an existing route in the tab navigator. + +- `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 static2dynamic +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +import { + createStaticNavigation, + useNavigation, + TabActions, +} 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 MyTabs = createBottomTabNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(MyTabs); + +export default function App() { + return ; +} +``` diff --git a/versioned_docs/version-8.x/tab-view.md b/versioned_docs/version-8.x/tab-view.md new file mode 100644 index 0000000000..b1e13b3d2c --- /dev/null +++ b/versioned_docs/version-8.x/tab-view.md @@ -0,0 +1,668 @@ +--- +id: tab-view +title: React Native Tab View +sidebar_label: Tab View +--- + +React Native Tab View is a cross-platform Tab View component for React Native implemented using [`react-native-pager-view`](https://github.com/callstack/react-native-viewpager) on Android & iOS, and [PanResponder](https://reactnative.dev/docs/panresponder) on Web, macOS, and Windows. + +It follows material design guidelines by default, but you can also use your own custom tab bar or position the tab bar at the bottom. + + + +This package doesn't integrate with React Navigation. If you want to integrate the tab view with React Navigation's navigation system, e.g. want to show screens in the tab bar and be able to navigate between them using `navigation.navigate` etc, use [Material Top Tab Navigator](material-top-tab-navigator.md) instead. + +## Installation + +To use this package, open a Terminal in the project root and run: + +```bash npm2yarn +npm install react-native-tab-view +``` + +The library 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: + +```bash +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 [pods](https://cocoapods.org/) to complete the linking. + +```bash +npx pod-install ios +``` + +## Quick start + +```js name="React Native Tab View" snack +// codeblock-focus-start +import * as React from 'react'; +import { View, useWindowDimensions } from 'react-native'; +import { TabView, SceneMap } from 'react-native-tab-view'; + +// codeblock-focus-end +const FirstRoute = () => ( + +); + +const SecondRoute = () => ( + +); + +// codeblock-focus-start +const renderScene = SceneMap({ + first: FirstRoute, + second: SecondRoute, +}); + +const routes = [ + { key: 'first', title: 'First' }, + { key: 'second', title: 'Second' }, +]; + +export default function TabViewExample() { + const layout = useWindowDimensions(); + const [index, setIndex] = React.useState(0); + + return ( + + ); +} +// codeblock-focus-end +``` + +## More examples on Snack + +- [Custom Tab Bar](https://snack.expo.io/@satya164/react-native-tab-view-custom-tabbar) +- [Lazy Load](https://snack.expo.io/@satya164/react-native-tab-view-lazy-load) + +## API reference + +The package exports a `TabView` component which is the one you'd use to render the tab view, and a `TabBar` component which is the default tab bar implementation. + +### `TabView` + +Container component responsible for rendering and managing tabs. Follows material design styles by default. + +Basic usage look like this: + +```js + +``` + +#### TabView Props + +##### `navigationState` (`required`) + +State for the tab view. The state should contain the following properties: + +- `index`: a number representing the index of the active route in the `routes` array +- `routes`: an array containing a list of route objects used for rendering the tabs + +Each route object should contain the following properties: + +- `key`: a unique key to identify the route (required) +- `title`: title for the route to display in the tab bar +- `icon`: icon for the route to display in the tab bar +- `accessibilityLabel`: accessibility label for the tab button +- `testID`: test id for the tab button + +Example: + +```js +{ + index: 1, + routes: [ + { key: 'music', title: 'Music' }, + { key: 'albums', title: 'Albums' }, + { key: 'recents', title: 'Recents' }, + { key: 'purchased', title: 'Purchased' }, + ] +} +``` + +`TabView` is a controlled component, which means the `index` needs to be updated via the `onIndexChange` callback. + +##### `onIndexChange` (`required`) + +Callback which is called on tab change, receives the index of the new tab as argument. +The navigation state needs to be updated when it's called, otherwise the change is dropped. + +##### `renderScene` (`required`) + +Callback which returns a react element to render as the page for the tab. Receives an object containing the route as the argument: + +```js +const renderScene = ({ route, jumpTo }) => { + switch (route.key) { + case 'music': + return ; + case 'albums': + return ; + } +}; +``` + +You need to make sure that your individual routes implement a `shouldComponentUpdate` to improve the performance. To make it easier to specify the components, you can use the `SceneMap` helper. + +`SceneMap` takes an object with the mapping of `route.key` to React components and returns a function to use with `renderScene` prop. + +```js +import { SceneMap } from 'react-native-tab-view'; + +... + +const renderScene = SceneMap({ + music: MusicRoute, + albums: AlbumsRoute, +}); +``` + +Specifying the components this way is easier and takes care of implementing a `shouldComponentUpdate` method. + +Each scene receives the following props: + +- `route`: the current route rendered by the component +- `jumpTo`: method to jump to other tabs, takes a `route.key` as it's argument +- `position`: animated node which represents the current position + +The `jumpTo` method can be used to navigate to other tabs programmatically: + +```js +props.jumpTo('albums'); +``` + +All the scenes rendered with `SceneMap` are optimized using `React.PureComponent` and don't re-render when parent's props or states change. If you need more control over how your scenes update (e.g. - triggering a re-render even if the `navigationState` didn't change), use `renderScene` directly instead of using `SceneMap`. + +**IMPORTANT:** **Do not** pass inline functions to `SceneMap`, for example, don't do the following: + +```js +SceneMap({ + first: () => , + second: SecondRoute, +}); +``` + +Always define your components elsewhere in the top level of the file. If you pass inline functions, it'll re-create the component every render, which will cause the entire route to unmount and remount every change. It's very bad for performance and will also cause any local state to be lost. + +If you need to pass additional props, use a custom `renderScene` function: + +```js +const renderScene = ({ route }) => { + switch (route.key) { + case 'first': + return ; + case 'second': + return ; + default: + return null; + } +}; +``` + +##### `renderTabBar` + +Callback which returns a custom React Element to use as the tab bar: + +```js +import { TabBar } from 'react-native-tab-view'; + +... + + } + ... +/> +``` + +If this is not specified, the default tab bar is rendered. You pass this props to customize the default tab bar, provide your own tab bar, or disable the tab bar completely. + +```js + null} + ... +/> +``` + +##### `tabBarPosition` + +Position of the tab bar in the tab view. Possible values are `'top'` and `'bottom'`. Defaults to `'top'`. + +##### `lazy` + +Function which takes an object with the current route and returns a boolean to indicate whether to lazily render the scenes. + +By default all scenes are rendered to provide a smoother swipe experience. But you might want to defer the rendering of unfocused scenes until the user sees them. To enable lazy rendering for a particular scene, return `true` from `lazy` for that `route`: + +```js + route.name === 'Albums'} + ... +/> +``` + +When you enable lazy rendering for a screen, it will usually take some time to render when it comes into focus. You can use the `renderLazyPlaceholder` prop to customize what the user sees during this short period. + +You can also pass a boolean to enable lazy for all of the scenes: + +```js + +``` + +##### `lazyPreloadDistance` + +When `lazy` is enabled, you can specify how many adjacent routes should be preloaded with this prop. This value defaults to `0` which means lazy pages are loaded as they come into the viewport. + +##### `renderLazyPlaceholder` + +Callback which returns a custom React Element to render for routes that haven't been rendered yet. Receives an object containing the route as the argument. The `lazy` prop also needs to be enabled. + +This view is usually only shown for a split second. Keep it lightweight. + +By default, this renders `null`. + +##### `keyboardDismissMode` + +String indicating whether the keyboard gets dismissed in response to a drag gesture. Possible values are: + +- `'auto'` (default): the keyboard is dismissed when the index changes. +- `'on-drag'`: the keyboard is dismissed when a drag begins. +- `'none'`: drags do not dismiss the keyboard. + +##### `swipeEnabled` + +Boolean indicating whether to enable swipe gestures. Swipe gestures are enabled by default. Passing `false` will disable swipe gestures, but the user can still switch tabs by pressing the tab bar. + +#### `animationEnabled` + +Enables animation when changing tab. By default it's true. + +##### `onSwipeStart` + +Callback which is called when the swipe gesture starts, i.e. the user touches the screen and moves it. + +##### `onSwipeEnd` + +Callback which is called when the swipe gesture ends, i.e. the user lifts their finger from the screen after the swipe gesture. + +##### `initialLayout` + +Object containing the initial height and width of the screens. Passing this will improve the initial rendering performance. For most apps, this is a good default: + +```js + +``` + +##### `overScrollMode` + +Used to override default value of pager's overScroll mode. Can be `auto`, `always` or `never` (Android only). + +##### `pagerStyle` + +Style to apply to the pager view wrapping all the scenes. + +##### `style` + +Style to apply to the tab view container. + +### `TabBar` + +Material design themed tab bar. To customize the tab bar, you'd need to use the `renderTabBar` prop of `TabView` to render the `TabBar` and pass additional props. + +For example, to customize the indicator color and the tab bar background color, you can pass `indicatorStyle` and `style` props to the `TabBar` respectively: + +```js +const renderTabBar = props => ( + +); + +//... + + +return ( + +); +``` + +#### TabBar Props + +##### `renderTabBarItem` + +Function which takes a `TabBarItemProps` object and returns a custom React Element to be used as a tab button. + +##### `renderIndicator` + +Function which takes an object with the current route and returns a custom React Element to be used as a tab indicator. + +##### `onTabPress` + +Function to execute on tab press. It receives the scene for the pressed tab, useful for things like scroll to top. + +By default, tab press also switches the tab. To prevent this behavior, you can call `preventDefault`: + +```js + { + if (route.key === 'home') { + preventDefault(); + + // Do something else + } + }} + ... +/> +``` + +##### `onTabLongPress` + +Function to execute on tab long press, use for things like showing a menu with more options + +##### `activeColor` + +Custom color for icon and label in the active tab. + +##### `inactiveColor` + +Custom color for icon and label in the inactive tab. + +##### `pressColor` + +Color for material ripple (Android >= 5.0 only). + +##### `pressOpacity` + +Opacity for pressed tab (iOS and Android < 5.0 only). + +##### `scrollEnabled` + +Boolean indicating whether to make the tab bar scrollable. + +If you set `scrollEnabled` to `true`, you should also specify a `width` in `tabStyle` to improve the initial render. + +##### `bounces` + +Boolean indicating whether the tab bar bounces when scrolling. + +##### `tabStyle` + +Style to apply to the individual tab items in the tab bar. + +By default, all tab items take up the same pre-calculated width based on the width of the container. If you want them to take their original width, you can specify `width: 'auto'` in `tabStyle`. + +##### `indicatorStyle` + +Style to apply to the active indicator. + +##### `indicatorContainerStyle` + +Style to apply to the container view for the indicator. + +##### `contentContainerStyle` + +Style to apply to the inner container for tabs. + +##### `style` (`TabBar`) + +Style to apply to the tab bar container. + +##### `gap` + +Spacing between the tab items. + +##### `testID` (`TabBar`) + +Test ID for the tab bar. Can be used for scrolling the tab bar in tests + +#### Options + +Options describe how each tab should be configured. There are 2 ways to specify options: + +- `commonOptions`: Options that apply to all tabs. +- `options`: Options that apply to specific tabs. It has the route key as the key and the object with options. + +Example: + +```js + ( + + ), + }} + options={{ + albums: { + labelText: 'Albums', + }, + profile: { + labelText: 'Profile', + }, + }} +/> +``` + +The following options are available: + +##### `accessibilityLabel` + +Accessibility label for the tab button. Uses `route.accessibilityLabel` by default if specified, otherwise uses the route title. + +##### `accessible` + +Whether to mark the tab as `accessible`. Defaults to `true`. + +##### `testID` + +Test ID for the tab button. Uses `route.testID` by default. + +##### `labelText` + +Label text for the tab button. Uses `route.title` by default. + +##### `labelAllowFontScaling` + +Whether label font should scale to respect Text Size accessibility settings. Defaults to `true`. + +##### `href` + +URL to use for the anchor tag for the tab button on the Web. + +##### `label` + +A function that returns a custom React Element to be used as a label. The function receives an object with the following properties: + +- `route` - The route object for the tab. +- `labelText` - The label text for the tab specified in the `labelText` option or the `route title`. +- `focused` - Whether the label is for the focused state. +- `color` - The color of the label. +- `allowFontScaling` - Whether label font should scale to respect Text Size accessibility settings. +- `style` - The style object for the label. + +```js +label: ({ route, labelText, focused, color }) => ( + {labelText ?? route.name} +); +``` + +##### `labelStyle` + +Style to apply to the tab item label. + +##### `icon` + +A function that returns a custom React Element to be used as an icon. The function receives an object with the following properties: + +- `route` - The route object for the tab. +- `focused` - Whether the icon is for the focused state. +- `color` - The color of the icon. +- `size` - The size of the icon. + +```js +icon: ({ route, focused, color }) => ( + +); +``` + +##### `badge` + +A function that returns a custom React Element to be used as a badge. The function receives an object with the following properties: + +- `route` - The route object for the tab. + +```js +badge: ({ route }) => ( + +); +``` + +##### `sceneStyle` + +Style to apply to the view wrapping each screen. You can pass this to override some default styles such as overflow clipping. + +## Optimization Tips + +### Avoid unnecessary re-renders + +The `renderScene` function is called every time the index changes. If your `renderScene` function is expensive, it's good idea move each route to a separate component if they don't depend on the index, and use `shouldComponentUpdate` or `React.memo` in your route components to prevent unnecessary re-renders. + +For example, instead of: + +```js +const renderScene = ({ route }) => { + switch (route.key) { + case 'home': + return ( + + + + + ); + default: + return null; + } +}; +``` + +Do the following: + +```js +const renderScene = ({ route }) => { + switch (route.key) { + case 'home': + return ; + default: + return null; + } +}; +``` + +Where `` is a `PureComponent` if you're using class components: + +```js +export default class HomeComponent extends React.PureComponent { + render() { + return ( + + + + + ); + } +} +``` + +Or, wrapped in `React.memo` if you're using function components: + +```js +function HomeComponent() { + return ( + + + + + ); +} + +export default React.memo(HomeComponent); +``` + +### Avoid one frame delay + +We need to measure the width of the container and hence need to wait before rendering some elements on the screen. If you know the initial width upfront, you can pass it in and we won't need to wait for measuring it. Most of the time, it's just the window width. + +For example, pass the following `initialLayout` to `TabView`: + +```js +const initialLayout = { + height: 0, + width: Dimensions.get('window').width, +}; +``` + +The tab view will still react to changes in the dimension and adjust accordingly to accommodate things like orientation change. + +### Optimize large number of routes + +If you've a large number of routes, especially images, it can slow the animation down a lot. You can instead render a limited number of routes. + +For example, do the following to render only 2 routes on each side: + +```js +const renderScene = ({ route }) => { + if (Math.abs(index - routes.indexOf(route)) > 2) { + return ; + } + + return ; +}; +``` + +### Avoid rendering TabView inside ScrollView + +Nesting the `TabView` inside a vertical `ScrollView` will disable the optimizations in the `FlatList` components rendered inside the `TabView`. So avoid doing it if possible. + +### Use `lazy` and `renderLazyPlaceholder` props to render routes as needed + +The `lazy` option is disabled by default to provide a smoother tab switching experience, but you can enable it and provide a placeholder component for a better lazy loading experience. Enabling `lazy` can improve initial load performance by rendering routes only when they come into view. Refer the [prop reference](#lazy) for more details. diff --git a/versioned_docs/version-8.x/testing.md b/versioned_docs/version-8.x/testing.md new file mode 100644 index 0000000000..a39c33b1fd --- /dev/null +++ b/versioned_docs/version-8.x/testing.md @@ -0,0 +1,957 @@ +--- +id: testing +title: Writing tests +sidebar_label: Writing tests +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +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). + +## 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/stack`, you will need to mock: + +- `react-native-gesture-handler` + +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 +import 'react-native-gesture-handler/jestSetup'; + +// Include this section for mocking react-native-reanimated +import { setUpTests } from 'react-native-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 [`setupFilesAfterEnv`](https://jestjs.io/docs/configuration#setupfilesafterenv-array) option 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)/)" + ], ++ "setupFilesAfterEnv": ["/jest/setup.js"] +} +``` + +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. + +## 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'; + +jest.useFakeTimers(); + +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() { + return ( + + Profile Screen + + ); +} + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +const PanelStack = createNativeStackNavigator({ + screens: { + Profile: ProfileScreen, + Settings: SettingsScreen, + }, +}); + +const Drawer = createDrawerNavigator({ + initialRouteName: 'Panel', + screens: { + Home: HomeScreen, + Panel: PanelStack, + }, +}); + +// codeblock-focus-start + +const Navigation = createStaticNavigation(Drawer); + +export default function App() { + // highlight-next-line + return ; +} +// codeblock-focus-end +``` + + + + +```js name="Simple theme" snack +// codeblock-focus-start +import * as React from 'react'; +import { + NavigationContainer, + DefaultTheme, + useNavigation, +} from '@react-navigation/native'; +// codeblock-focus-end +import { View, Text } from 'react-native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createDrawerNavigator } from '@react-navigation/drawer'; +import { Button } from '@react-navigation/elements'; + +// codeblock-focus-start + +const MyTheme = { + ...DefaultTheme, + colors: { + ...DefaultTheme.colors, + background: 'rgb(140, 201, 125)', + primary: 'rgb(255, 45, 85)', + }, +}; +// codeblock-focus-end + +function SettingsScreen({ route }) { + const navigation = useNavigation(); + const { user } = route.params; + + return ( + + Settings Screen + userParam: {JSON.stringify(user)} + + + ); +} + +function ProfileScreen() { + const navigation = useNavigation(); + + return ( + + Profile Screen + + ); +} + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +const Drawer = createDrawerNavigator(); +const Stack = createNativeStackNavigator(); + +function Root() { + return ( + + + + + ); +} + +// codeblock-focus-start + +export default function App() { + return ( + // highlight-next-line + + + + + + + ); +} +// codeblock-focus-start +``` + + + + +You can change the theme prop dynamically and all the components will automatically update to reflect the new theme. If you haven't provided a `theme` prop, the default theme will be used. + +## Properties + +A theme is a JS object containing a list of colors to use. It contains the following properties: + +- `dark` (`boolean`): Whether this is a dark theme or a light theme +- `colors` (`object`): Various colors used by react navigation components: + - `primary` (`string`): The primary color of the app used to tint various elements. Usually you'll want to use your brand color for this. + - `background` (`string`): The color of various backgrounds, such as the background color for the screens. + - `card` (`string`): The background color of card-like elements, such as headers, tab bars etc. + - `text` (`string`): The text color of various elements. + - `border` (`string`): The color of borders, e.g. header border, tab bar border etc. + - `notification` (`string`): The color of notifications and badge (e.g. badge in bottom tabs). +- `fonts` (`object`): Various fonts used by react navigation components: + - `regular` (`object`): Style object for the primary font used in the app. + - `medium` (`object`): Style object for the semi-bold variant of the primary font. + - `bold` (`object`): Style object for the bold variant of the primary font. + - `heavy` (`object`): Style object for the extra-bold variant of the primary font. + +The style objects for fonts contain the following properties: + +- `fontFamily` (`string`): The name of the font family (or font stack on Web) to use, e.g. `Roboto` or `Helvetica Neue`. The system fonts are used by default. +- `fontWeight` (`string`): The font weight to use. Valid values are `normal`, `bold`, `100`, `200`, `300`, `400`, `500`, `600`, `700`, `800`, `900`. + +When creating a custom theme, you will need to provide all of these properties. + +Example theme: + +```js +const WEB_FONT_STACK = + 'system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"'; + +const MyTheme = { + dark: false, + colors: { + primary: 'rgb(255, 45, 85)', + background: 'rgb(242, 242, 242)', + card: 'rgb(255, 255, 255)', + text: 'rgb(28, 28, 30)', + border: 'rgb(199, 199, 204)', + notification: 'rgb(255, 69, 58)', + }, + fonts: Platform.select({ + web: { + regular: { + fontFamily: WEB_FONT_STACK, + fontWeight: '400', + }, + medium: { + fontFamily: WEB_FONT_STACK, + fontWeight: '500', + }, + bold: { + fontFamily: WEB_FONT_STACK, + fontWeight: '600', + }, + heavy: { + fontFamily: WEB_FONT_STACK, + fontWeight: '700', + }, + }, + ios: { + regular: { + fontFamily: 'System', + fontWeight: '400', + }, + medium: { + fontFamily: 'System', + fontWeight: '500', + }, + bold: { + fontFamily: 'System', + fontWeight: '600', + }, + heavy: { + fontFamily: 'System', + fontWeight: '700', + }, + }, + default: { + regular: { + fontFamily: 'sans-serif', + fontWeight: 'normal', + }, + medium: { + fontFamily: 'sans-serif-medium', + fontWeight: 'normal', + }, + bold: { + fontFamily: 'sans-serif', + fontWeight: '600', + }, + heavy: { + fontFamily: 'sans-serif', + fontWeight: '700', + }, + }, + }), +}; +``` + +Providing a theme will take care of styling of all the official navigators. React Navigation also provides several tools to help you make your customizations of those navigators and the screens within the navigators can use the theme too. + +## Built-in themes + +As operating systems add built-in support for light and dark modes, supporting dark mode is less about keeping hip to trends and more about conforming to the average user expectations for how apps should work. In order to provide support for light and dark mode in a way that is reasonably consistent with the OS defaults, these themes are built in to React Navigation. + +You can import the default and dark themes like so: + +```js +import { DefaultTheme, DarkTheme } from '@react-navigation/native'; +``` + +## Keeping the native theme in sync + +If you're changing the theme in the app, native UI elements such as Alert, ActionSheet etc. won't reflect the new theme. You can do the following to keep the native theme in sync: + +```js +React.useEffect(() => { + const colorScheme = theme.dark ? 'dark' : 'light'; + + if (Platform.OS === 'web') { + document.documentElement.style.colorScheme = colorScheme; + } else { + Appearance.setColorScheme(colorScheme); + } +}, [theme.dark]); +``` + +Alternatively, you can use the [`useColorScheme`](#using-the-operating-system-preferences) hook to get the current native color scheme and update the theme accordingly. + +## Using the operating system preferences + +On iOS 13+ and Android 10+, you can get user's preferred color scheme (`'dark'` or `'light'`) with the ([`useColorScheme` hook](https://reactnative.dev/docs/usecolorscheme)). + + + + +```js name="Operating system color theme" snack +import * as React from 'react'; +// codeblock-focus-start +import { + useNavigation, + createStaticNavigation, + DefaultTheme, + DarkTheme, + useTheme, +} from '@react-navigation/native'; +import { View, Text, TouchableOpacity, useColorScheme } from 'react-native'; +// codeblock-focus-end +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createDrawerNavigator } from '@react-navigation/drawer'; +import { Button } from '@react-navigation/elements'; + +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 + + ); +} + +function MyButton() { + const { colors } = useTheme(); + + return ( + + Button! + + ); +} + +function HomeScreen() { + const navigation = useNavigation(); + const { colors } = useTheme(); + + return ( + + Home Screen + + + + ); +} + +const PanelStack = createNativeStackNavigator({ + screens: { + Profile: ProfileScreen, + Settings: SettingsScreen, + }, +}); + +const Drawer = createDrawerNavigator({ + initialRouteName: 'Panel', + screens: { + Home: HomeScreen, + Panel: PanelStack, + }, +}); + +// codeblock-focus-start + +const Navigation = createStaticNavigation(Drawer); + +export default function App() { + // highlight-next-line + const scheme = useColorScheme(); + + // highlight-next-line + return ; +} + +// codeblock-focus-end +``` + + + + +```js name="Operating system color theme" snack +import * as React from 'react'; +// codeblock-focus-start +import { View, Text, TouchableOpacity, useColorScheme } from 'react-native'; +import { + NavigationContainer, + DefaultTheme, + DarkTheme, + useTheme, +} from '@react-navigation/native'; +// codeblock-focus-end +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createDrawerNavigator } from '@react-navigation/drawer'; +import { Button } from '@react-navigation/elements'; + +function SettingsScreen({ route, navigation }) { + const { user } = route.params; + const { colors } = useTheme(); + + return ( + + Settings Screen + + userParam: {JSON.stringify(user)} + + + + ); +} + +function ProfileScreen() { + const { colors } = useTheme(); + + return ( + + Profile Screen + + ); +} + +function MyButton() { + const { colors } = useTheme(); + + return ( + + Button! + + ); +} + +function HomeScreen() { + const navigation = useNavigation(); + const { colors } = useTheme(); + + return ( + + Home Screen + + + + ); +} + +const Drawer = createDrawerNavigator(); +const Stack = createNativeStackNavigator(); + +function Root() { + return ( + + + + + ); +} + +// codeblock-focus-start + +export default function App() { + // highlight-next-line + const scheme = useColorScheme(); + + return ( + // highlight-next-line + + + + + + + ); +} +// codeblock-focus-end +``` + + + + +## Using the current theme in your own components + +To gain access to the theme in any component that is rendered inside the navigation container:, you can use the `useTheme` hook. It returns the theme object: + + + + +```js name="System themes" snack +import * as React from 'react'; +// codeblock-focus-start +import { + useNavigation, + createStaticNavigation, + DefaultTheme, + DarkTheme, + useTheme, +} from '@react-navigation/native'; +import { View, Text, TouchableOpacity, useColorScheme } from 'react-native'; +// codeblock-focus-end +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createDrawerNavigator } from '@react-navigation/drawer'; +import { Button } from '@react-navigation/elements'; + +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 PanelStack = createNativeStackNavigator({ + screens: { + Profile: ProfileScreen, + Settings: SettingsScreen, + }, +}); + +const Drawer = createDrawerNavigator({ + initialRouteName: 'Panel', + screens: { + Home: HomeScreen, + Panel: PanelStack, + }, +}); + +const Navigation = createStaticNavigation(Drawer); + +export default function App() { + const scheme = useColorScheme(); + + return ; +} +``` + + + + +```js name="System themes" snack +import * as React from 'react'; +// codeblock-focus-start +import { View, Text, TouchableOpacity, useColorScheme } from 'react-native'; +import { + NavigationContainer, + DefaultTheme, + DarkTheme, + useTheme, + useNavigation, +} from '@react-navigation/native'; +// codeblock-focus-end +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { createDrawerNavigator } from '@react-navigation/drawer'; + +function SettingsScreen({ route, navigation }) { + const { colors } = useTheme(); + const { user } = route.params; + + 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 ( + + + + + + + ); +} +``` + + + diff --git a/versioned_docs/version-8.x/troubleshooting.md b/versioned_docs/version-8.x/troubleshooting.md new file mode 100755 index 0000000000..1186ea690e --- /dev/null +++ b/versioned_docs/version-8.x/troubleshooting.md @@ -0,0 +1,491 @@ +--- +id: troubleshooting +title: Troubleshooting +sidebar_label: Troubleshooting +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +This section attempts to outline issues that users frequently encounter when first getting accustomed to using React Navigation. These issues may or may not be related to React Navigation itself. + +Before troubleshooting an issue, make sure that you have upgraded to **the latest available versions** of the packages. You can install the latest versions by installing the packages again (e.g. `npm install package-name`). + +## I'm getting an error "Unable to resolve module" after updating to the latest version + +This might happen for 3 reasons: + +### Stale cache of Metro bundler + +If the module points to a local file (i.e. the name of the module starts with `./`), then it's probably due to stale cache. To fix this, try the following solutions. + +If you're using Expo, run: + +```bash +expo start -c +``` + +If you're not using Expo, run: + +```bash +npx react-native start --reset-cache +``` + +If that doesn't work, you can also try the following: + +```bash +rm -rf $TMPDIR/metro-bundler-cache-* +``` + +### Missing peer dependency + +If the module points to an npm package (i.e. the name of the module doesn't with `./`), then it's probably due to a missing dependency. To fix this, install the dependency in your project: + +```bash npm2yarn +npm install name-of-the-module +``` + +Sometimes it might even be due to a corrupt installation. If clearing cache didn't work, try deleting your `node_modules` folder and run `npm install` again. + +### Missing extensions in metro configuration + +Sometimes the error may look like this: + +```bash +Error: While trying to resolve module "@react-navigation/native" from file "/path/to/src/App.js", the package "/path/to/node_modules/@react-navigation/native/package.json" was successfully found. However, this package itself specifies a "main" module field that could not be resolved ("/path/to/node_modules/@react-navigation/native/src/index.tsx" +``` + +This can happen if you have a custom configuration for metro and haven't specified `ts` and `tsx` as valid extensions. These extensions are present in the default configuration. To check if this is the issue, look for a `metro.config.js` file in your project and check if you have specified the [`sourceExts`](https://facebook.github.io/metro/docs/en/configuration#sourceexts) option. It should at least have the following configuration: + +```js +sourceExts: ['js', 'json', 'ts', 'tsx']; +``` + +If it's missing these extensions, add them and then clear metro cache as shown in the section above. + +## I'm getting "SyntaxError in @react-navigation/xxx/xxx.tsx" or "SyntaxError: /xxx/@react-navigation/xxx/xxx.tsx: Unexpected token" + +This might happen if you have an old version of the `@react-native/babel-preset` package. Try upgrading it to the latest version. + +```bash npm2yarn +npm install --save-dev @react-native/babel-preset +``` + +If you have `@babel/core` installed, also upgrade it to latest version. + +```bash npm2yarn +npm install --save-dev @babel/core +``` + +If upgrading the packages don't help, you can also try deleting your `node_modules` and then the lock the file and reinstall your dependencies. + +If you use `npm`: + +```bash +rm -rf node_modules +rm package-lock.json +npm install +``` + +If you use `yarn`: + +```bash +rm -rf node_modules +rm yarn.lock +yarn +``` + +:::warning + +Deleting the lockfile is generally not recommended as it may upgrade your dependencies to versions that haven't been tested with your project. So only use this as a last resort. + +::: + +After upgrading or reinstalling the packages, you should also clear Metro bundler's cache following the instructions earlier in the page. + +## I'm getting "Module '[...]' has no exported member 'xxx' when using TypeScript + +This might happen if you have an old version of TypeScript in your project. You can try upgrading it: + +```bash npm2yarn +npm install --save-dev typescript +``` + +## I'm getting an error "null is not an object (evaluating 'RNGestureHandlerModule.default.Direction')" + +This and some similar errors might occur if you have a bare React Native project and the library [`react-native-gesture-handler`](https://github.com/software-mansion/react-native-gesture-handler) library isn't linked. + +Linking is automatic from React Native 0.60, so if you have linked the library manually, first unlink it: + +```bash +react-native unlink react-native-gesture-handler +``` + +If you're testing on iOS and use Mac, make sure you have run `pod install` in the `ios/` folder: + +```bash +cd ios +pod install +cd .. +``` + +Now rebuild the app and test on your device or simulator. + +## I'm getting an error "requireNativeComponent: "RNCSafeAreaProvider" was not found in the UIManager" + +This and some similar errors might occur if you have a bare React Native project and the library [`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context) library isn't linked. + +Linking is automatic from React Native 0.60, so if you have linked the library manually, first unlink it: + +```bash +react-native unlink react-native-safe-area-context +``` + +If you're testing on iOS and use Mac, make sure you have run `pod install` in the `ios/` folder: + +```bash +cd ios +pod install +cd .. +``` + +Now rebuild the app and test on your device or simulator. + +## I'm getting an error "Tried to register two views with the same name RNCSafeAreaProvider" + +This might occur if you have multiple versions of [`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context) installed. + +If you're using Expo managed workflow, it's likely that you have installed an incompatible version. To install the correct version, run: + +```bash +npx expo install react-native-safe-area-context +``` + +If it didn't fix the error or you're not using Expo managed workflow, you'll need to check which package depends on a different version of `react-native-safe-area-context`. + +If you use `yarn`, run: + +```bash +yarn why react-native-safe-area-context +``` + +If you use `npm`, run: + +```bash +npm ls react-native-safe-area-context +``` + +This will tell you if a package you use has a dependency on `react-native-safe-area-context`. If it's a third-party package, you should open an issue on the relevant repo's issue tracker explaining the problem. Generally for libraries, dependencies containing native code should be defined in `peerDependencies` instead of `dependencies` to avoid such issues. + +If it's already in `peerDependencies` and not in `dependencies`, and you use `npm`, it might be because of incompatible version range defined for the package. The author of the library will need to relax the version range in such cases to allow a wider range of versions to be installed. + +If you use `yarn`, you can also temporarily override the version being installed using `resolutions`. Add the following in your `package.json`: + +```json +"resolutions": { + "react-native-safe-area-context": "" +} +``` + +And then run: + +```bash +yarn +``` + +If you're on iOS and not using Expo managed workflow, also run: + +```bash +cd ios +pod install +cd .. +``` + +Now rebuild the app and test on your device or simulator. + +## Nothing is visible on the screen after adding a `View` + +If you wrap the container in a `View`, make sure the `View` stretches to fill the container using `flex: 1`: + + + + +```js +import * as React from 'react'; +import { View } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; + +/* ... */ + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ( + // highlight-next-line + + + + ); +} +``` + + + + +```js +import * as React from 'react'; +import { View } from 'react-native'; +import { NavigationContainer } from '@react-navigation/native'; + +export default function App() { + return ( + // highlight-next-line + + {/* ... */} + + ); +} +``` + + + + +## I get the warning "Non-serializable values were found in the navigation state" + +This can happen if you are passing non-serializable values such as class instances, functions etc. in params. React Navigation warns you in this case because this can break other functionality such [state persistence](state-persistence.md), [deep linking](deep-linking.md), [web support](web-support.md) etc. + +Example of some use cases for passing functions in params are the following: + +- To pass a callback to use in a header button. This can be achieved using `navigation.setOptions` instead. See the [guide for header buttons](header-buttons.md#header-interaction-with-its-screen-component) for examples. +- To pass a callback to the next screen which it can call to pass some data back. You can usually achieve it using `popTo` instead. See [passing params to a previous screen](params.md#passing-params-to-a-previous-screen) for examples. +- To pass complex data to another screen. Instead of passing the data `params`, you can store that complex data somewhere else (like a global store), and pass an id instead. Then the screen can get the data from the global store using the id. See [what should be in params](params.md#what-should-be-in-params). +- Pass data, callbacks etc. from a parent to child screens. You can either use React Context, or pass a children callback to pass these down instead of using params. See [passing additional props](hello-react-navigation.md#passing-additional-props). + +We don't generally recommend passing functions in params. But if you don't use state persistence, deep links, or use React Navigation on Web, then you can choose to ignore it. To ignore the warning, you can use `LogBox.ignoreLogs`. + +Example: + +```js +import { LogBox } from 'react-native'; + +LogBox.ignoreLogs([ + 'Non-serializable values were found in the navigation state', +]); +``` + +## I'm getting "Invalid hook call. Hooks can only be called inside of the body of a function component" + +This can happen when you pass a React component to an option that accepts a function returning a react element. For example, the [`headerTitle` option in native stack navigator](native-stack-navigator.md#headertitle) expects a function returning a react element: + + + + +```js +const Stack = createNativeStackNavigator({ + screens: { + Home: { + screen: Home, + options: { + // highlight-next-line + headerTitle: (props) => , + }, + }, + }, +}); +``` + + + + +```js + , + }} +/> +``` + + + + +If you directly pass a function here, you'll get this error when using hooks: + + + + +```js +const Stack = createNativeStackNavigator({ + screens: { + Home: { + screen: Home, + options: { + // This is not correct + // highlight-next-line + headerTitle: MyTitle, + }, + }, + }, +}); +``` + + + + +```js + +``` + + + + +The same applies to other options like `headerLeft`, `headerRight`, `tabBarIcon` etc. as well as props such as `tabBar`, `drawerContent` etc. + +## Screens are unmounting/remounting during navigation + +Sometimes you might have noticed that your screens unmount/remount, or your local component state or the navigation state resets when you navigate. This might happen if you are creating React components during render. + +The simplest example is something like following: + +```js +function App() { + return ( + + { + return ; + }} + /> + + ); +} +``` + +The `component` prop expects a React Component, but in the example, it's getting a function returning an React Element. While superficially a component and a function returning a React Element look the exact same, they don't behave the same way when used. + +Here, every time the component re-renders, a new function will be created and passed to the `component` prop. React will see a new component and unmount the previous component before rendering the new one. This will cause any local state in the old component to be lost. React Navigation will detect and warn for this specific case but there can be other ways you might be creating components during render which it can't detect. + +Another easy to identify example of this is when you create a component inside another component: + + + + +```js +function App() { + const Home = () => { + return ; + }; + + const RootStack = createNativeStackNavigator({ + screens: { + Home: Home, + }, + }); + + const Navigation = createStaticNavigation(RootStack); + + return ; +} +``` + + + + +```js +function App() { + const Home = () => { + return ; + }; + + return ( + + + + ); +} +``` + + + + +Or when you use a higher order component (such as `connect` from Redux, or `withX` functions that accept a component) inside another component: + +```js +function App() { + return ( + + + + ); +} +``` + +If you're unsure, it's always best to make sure that the components you are using as screens are defined outside of a React component. They could be defined in another file and imported, or defined at the top level scope in the same file: + + + + +```js +const Home = () => { + // ... + + return ; +}; + +const RootStack = createNativeStackNavigator({ + screens: { + Home: Home, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +function App() { + return ; +} +``` + + + + +```js +const Home = () => { + // ... + + return ; +}; + +function App() { + return ( + + + + ); +} +``` + + + + +This is not React Navigation specific, but related to React in general. You should always avoid creating components during render, whether you are using React Navigation or not. + +## App is not working properly when connected to Chrome Debugger + +When the app is connected to Chrome Debugger (or other tools that use Chrome Debugger such as [React Native Debugger](https://github.com/jhen0409/react-native-debugger)) you might encounter various issues related to timing. + +This can result in issues such as button presses taking a long time to register or not working at all, [gestures and animations being slow and buggy](https://github.com/facebook/react-native/issues/2367) etc. There can be other functional issues such as promises not resolving, [timeouts and intervals not working correctly](https://github.com/facebook/react-native/issues/4470) etc. as well. + +The issues are not related to React Navigation, but due to the nature of how the Chrome Debugger works. When connected to Chrome Debugger, your whole app runs on Chrome and communicates with the native app via sockets over the network, which can introduce latency and timing related issues. + +So, unless you are trying to debug something, it's better to test the app without being connected to the Chrome Debugger. If you are using iOS, you can alternatively use [Safari to debug your app](https://reactnative.dev/docs/debugging#safari-developer-tools) which debugs the app on the device directly and does not have these issues, though it has other downsides. diff --git a/versioned_docs/version-8.x/typescript.md b/versioned_docs/version-8.x/typescript.md new file mode 100755 index 0000000000..72fd891716 --- /dev/null +++ b/versioned_docs/version-8.x/typescript.md @@ -0,0 +1,560 @@ +--- +id: typescript +title: Type checking with TypeScript +sidebar_label: Configuring TypeScript +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +React Navigation can be configured to type-check screens and their params, as well as various other APIs using TypeScript. This provides better intelliSense and type safety when working with React Navigation. + +First, make sure you have the following configuration in your `tsconfig.json` under `compilerOptions`: + +- `strict: true` or `strictNullChecks: true` - Necessary for intelliSense and type inference to work correctly. +- `moduleResolution: "bundler"` - Necessary to resolve the types correctly and match the behavior of [Metro](https://metrobundler.dev/) and other bundlers. + + + + +There are 2 steps to configure TypeScript with the static API: + +1. Each screen component needs to specify the type of the [`route.params`](params.md) prop that it accepts. The `StaticScreenProps` type makes it simpler: + + ```ts + import type { StaticScreenProps } from '@react-navigation/native'; + + // highlight-start + type Props = StaticScreenProps<{ + username: string; + }>; + // highlight-end + + function ProfileScreen({ route }: Props) { + // ... + } + ``` + +2. Generate the `ParamList` type for the root navigator and specify it as the default type for the `RootParamList` type: + + ```ts + import type { StaticParamList } from '@react-navigation/native'; + + const HomeTabs = createBottomTabNavigator({ + screens: { + Feed: FeedScreen, + Profile: ProfileScreen, + }, + }); + + const RootStack = createNativeStackNavigator({ + screens: { + Home: HomeTabs, + }, + }); + + // highlight-next-line + type RootStackParamList = StaticParamList; + + // highlight-start + declare global { + namespace ReactNavigation { + interface RootParamList extends RootStackParamList {} + } + } + // highlight-end + ``` + + This is needed to type-check the [`useNavigation`](use-navigation.md) hook. + +## Navigator specific types + +Generally, we recommend using the default types for the [`useNavigation`](use-navigation.md) prop to access the navigation object in a navigator-agnostic manner. However, if you need to use navigator-specific APIs, e.g. `setOptions` to update navigator options, `push`, `pop`, `popTo` etc. with stacks, `openDrawer`, `closeDrawer` etc. with drawer and so on, you need to manually annotate [`useNavigation`](use-navigation.md): + +```ts +import type { BottomTabNavigationProp } from '@react-navigation/bottom-tabs'; + +type BottomTabParamList = StaticParamList; +type ProfileScreenNavigationProp = BottomTabNavigationProp< + BottomTabParamList, + 'Profile' +>; + +// ... + +const navigation = useNavigation(); +``` + +Similarly, you can import `NativeStackNavigationProp` from [`@react-navigation/native-stack`](native-stack-navigator.md), `StackNavigationProp` from [`@react-navigation/stack`](stack-navigator.md), `DrawerNavigationProp` from [`@react-navigation/drawer`](drawer-navigator.md) etc. + +:::danger + +Annotating [`useNavigation`](use-navigation.md) this way is not type-safe since we can't guarantee that the type you provided matches the type of the navigator. So try to keep manual annotations to a minimum and use the default types instead. + +::: + +## Nesting navigator using dynamic API + +Consider the following example: + +```js +const Tab = createBottomTabNavigator(); + +function HomeTabs() { + return ( + + + + + ); +} + +const RootStack = createStackNavigator({ + Home: HomeTabs, +}); +``` + +Here, the `HomeTabs` component is defined using the dynamic API. This means that when we create the param list for the root navigator with `StaticParamList`, it won't know about the screens defined in the nested navigator. To fix this, we'd need to specify the param list for the nested navigator explicitly. + +This can be done by using the type of the [`route`](route-object.md) prop that the screen component receives: + +```ts +type HomeTabsParamList = { + Feed: undefined; + Profile: undefined; +}; + +// highlight-start +type HomeTabsProps = StaticScreenProps< + NavigatorScreenParams +>; +// highlight-end + +// highlight-next-line +function HomeTabs(_: HomeTabsProps) { + return ( + + + + + ); +} +``` + +Now, when using `StaticParamList`, it will include the screens defined in the nested navigator. + + + + +When using the dynamic API, it is necessary to specify the types for each screen as well as the nesting structure as it cannot be inferred from the code. + +## Typechecking the navigator + +To typecheck our route name and params, the first thing we need to do is to create an object type with mappings for route names to the params of the route. For example, say we have a route called `Profile` in our root navigator which should have a param `userId`: + +```tsx +type RootStackParamList = { + Profile: { userId: string }; +}; +``` + +Similarly, we need to do the same for each route: + +```tsx +type RootStackParamList = { + Home: undefined; + Profile: { userId: string }; + Feed: { sort: 'latest' | 'top' } | undefined; +}; +``` + +Specifying `undefined` means that the route doesn't have params. A union type with `undefined` (e.g. `SomeType | undefined`) means that params are optional. + +After we have defined the mapping, we need to tell our navigator to use it. To do that, we can pass it as a generic to the [`createXNavigator`](static-configuration.md) functions: + +```tsx +import { createStackNavigator } from '@react-navigation/stack'; + +const RootStack = createStackNavigator(); +``` + +And then we can use it: + +```tsx + + + + + +``` + +This will provide type checking and intelliSense for props of the [`Navigator`](navigator.md) and [`Screen`](screen.md) components. + +:::note + +The type containing the mapping must be a type alias (e.g. `type RootStackParamList = { ... }`). It cannot be an interface (e.g. `interface RootStackParamList { ... }`). It also shouldn't extend `ParamListBase` (e.g. `interface RootStackParamList extends ParamListBase { ... }`). Doing so will result in incorrect type checking which allows you to pass incorrect route names. + +::: + +If you have an [`id`](./navigator.md#id) prop for your navigator, you will also need to pass it as a generic: + +```tsx +const RootStack = createStackNavigator(); +``` + +## Type checking screens + +To typecheck our screens, we need to annotate the `navigation` and the `route` props received by a screen. The navigator packages in React Navigation export generic types to define types for both the `navigation` and `route` props from the corresponding navigator. + +For example, you can use `NativeStackScreenProps` for the Native Stack Navigator. + +```tsx +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; + +type RootStackParamList = { + Home: undefined; + Profile: { userId: string }; + Feed: { sort: 'latest' | 'top' } | undefined; +}; + +type Props = NativeStackScreenProps; +``` + +The type takes 3 generics: + +- The param list object we defined earlier +- The name of the route the screen belongs to +- The ID of the navigator (optional) + +If you have an [`id`](./navigator.md#id) prop for your navigator, you can do: + +```ts +type Props = NativeStackScreenProps; +``` + +This allows us to type check route names and params which you're navigating using [`navigate`](navigation-object.md#navigate), [`push`](stack-actions.md#push) etc. The name of the current route is necessary to type check the params in `route.params` and when you call [`setParams`](navigation-actions#setparams) or [`replaceParams`](navigation-actions#replaceparams). + +Similarly, you can import `StackScreenProps` from [`@react-navigation/stack`](stack-navigator.md), `DrawerScreenProps` from [`@react-navigation/drawer`](drawer-navigator.md), `BottomTabScreenProps` from [`@react-navigation/bottom-tabs`](bottom-tab-navigator.md) and so on. + +Then you can use the `Props` type you defined above to annotate your component. + +For function components: + +```tsx +function ProfileScreen({ route, navigation }: Props) { + // ... +} +``` + +For class components: + +```ts +class ProfileScreen extends React.Component { + render() { + // ... + } +} +``` + +You can get the types for `navigation` and `route` from the `Props` type as follows: + +```ts +type ProfileScreenNavigationProp = Props['navigation']; + +type ProfileScreenRouteProp = Props['route']; +``` + +Alternatively, you can also annotate the `navigation` and `route` objects separately. + +To get the type for the `navigation` prop, we need to import the corresponding type from the navigator. For example, `NativeStackNavigationProp` for `@react-navigation/native-stack`: + +```tsx +import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; + +type ProfileScreenNavigationProp = NativeStackNavigationProp< + RootStackParamList, + 'Profile' +>; +``` + +Similarly, you can import `StackNavigationProp` from [`@react-navigation/stack`](stack-navigator.md), `DrawerNavigationProp` from [`@react-navigation/drawer`](drawer-navigator.md), `BottomTabNavigationProp` from [`@react-navigation/bottom-tabs`](bottom-tab-navigator.md) etc. + +To get the type for the `route` object, we need to use the `RouteProp` type from `@react-navigation/native`: + +```tsx +import type { RouteProp } from '@react-navigation/native'; + +type ProfileScreenRouteProp = RouteProp; +``` + +We recommend creating a separate file: `types.tsx` - where you keep the types and import from there in your component files instead of repeating them in each file. + +## Nesting navigators + +### Type checking screens and params in nested navigator + +You can [navigate to a screen in a nested navigator](nesting-navigators.md#navigating-to-a-screen-in-a-nested-navigator) by passing `screen` and `params` properties for the nested screen: + +```ts +navigation.navigate('Home', { + screen: 'Feed', + params: { sort: 'latest' }, +}); +``` + +To be able to type check this, we need to extract the params from the screen containing the nested navigator. This can be done using the `NavigatorScreenParams` utility: + +```ts +import { NavigatorScreenParams } from '@react-navigation/native'; + +type TabParamList = { + Home: NavigatorScreenParams; + Profile: { userId: string }; +}; +``` + +### Combining navigation props + +When you nest navigators, the navigation prop of the screen is a combination of multiple navigation props. For example, if we have a tab inside a stack, the `navigation` prop will have both [`jumpTo`](tab-actions.md#jumpto) (from the tab navigator) and [`push`](stack-actions.md#push) (from the stack navigator). To make it easier to combine types from multiple navigators, you can use the `CompositeScreenProps` type. + +For example, if we have a `Profile` in a navigator, nested inside `Account` screen of a stack navigator, we can combine the types as follows: + +```ts +import type { CompositeScreenProps } from '@react-navigation/native'; +import type { BottomTabScreenProps } from '@react-navigation/bottom-tabs'; +import type { StackScreenProps } from '@react-navigation/stack'; + +type ProfileScreenProps = CompositeScreenProps< + BottomTabScreenProps, + StackScreenProps +>; +``` + +The `CompositeScreenProps` type takes 2 parameters: + +- The first parameter is the type for the navigator that owns this screen, in our case the tab navigator which contains the `Profile` screen +- The second parameter is the type of props for a parent navigator, in our case the stack navigator which contains the `Account` screen + +For multiple parent navigators, this second parameter can nest another `CompositeScreenProps`: + +```ts +type ProfileScreenProps = CompositeScreenProps< + BottomTabScreenProps, + CompositeScreenProps< + StackScreenProps, + DrawerScreenProps + > +>; +``` + +If annotating the `navigation` prop separately, you can use `CompositeNavigationProp` instead. The usage is similar to `CompositeScreenProps`: + +```ts +import type { CompositeNavigationProp } from '@react-navigation/native'; +import type { BottomTabNavigationProp } from '@react-navigation/bottom-tabs'; +import type { StackNavigationProp } from '@react-navigation/stack'; + +type ProfileScreenNavigationProp = CompositeNavigationProp< + BottomTabNavigationProp, + StackNavigationProp +>; +``` + +## Annotating `useNavigation` + +:::danger + +Annotating `useNavigation` isn't type-safe because the type parameter cannot be statically verified. +Prefer [specifying a default type](#specifying-default-types-for-usenavigation-link-ref-etc) instead. + +::: + +To annotate the `navigation` object that we get from [`useNavigation`](use-navigation.md), we can use a type parameter: + +```ts +const navigation = useNavigation(); +``` + +## Annotating `useRoute` + +:::danger + +Annotating `useRoute` isn't type-safe because the type parameter cannot be statically verified. +Prefer using the [`route` object](route-object.md) from the screen component's props instead when possible. Use `useRoute` for generic code that doesn't need specific route type. + +::: + +To annotate the `route` object that we get from [`useRoute`](use-route.md), we can use a type parameter: + +```ts +const route = useRoute(); +``` + +## Annotating `options` and `screenOptions` + +When you pass the `options` to a `Screen` or `screenOptions` prop to a `Navigator` component, they are already type-checked and you don't need to do anything special. However, sometimes you might want to extract the options to a separate object, and you might want to annotate it. + +To annotate the options, we need to import the corresponding type from the navigator. For example, `StackNavigationOptions` for `@react-navigation/stack`: + +```ts +import type { StackNavigationOptions } from '@react-navigation/stack'; + +const options: StackNavigationOptions = { + headerShown: false, +}; +``` + +Similarly, you can import `DrawerNavigationOptions` from `@react-navigation/drawer`, `BottomTabNavigationOptions` from `@react-navigation/bottom-tabs` etc. + +When using the function form of `options` and `screenOptions`, you can annotate the arguments with a type exported from the navigator, e.g. `StackOptionsArgs` for `@react-navigation/stack`, `DrawerOptionsArgs` for `@react-navigation/drawer`, `BottomTabOptionsArgs` for `@react-navigation/bottom-tabs` etc.: + +```ts +import type { + StackNavigationOptions, + StackOptionsArgs, +} from '@react-navigation/stack'; + +const options = ({ route }: StackOptionsArgs): StackNavigationOptions => { + return { + headerTitle: route.name, + }; +}; +``` + +## Annotating `ref` on `NavigationContainer` + +If you use the `createNavigationContainerRef()` method to create the ref, you can annotate it to type-check navigation actions: + +```ts +import { createNavigationContainerRef } from '@react-navigation/native'; + +// ... + +const navigationRef = createNavigationContainerRef(); +``` + +Similarly, for `useNavigationContainerRef()`: + +```ts +import { useNavigationContainerRef } from '@react-navigation/native'; + +// ... + +const navigationRef = useNavigationContainerRef(); +``` + +If you're using a regular `ref` object, you can pass a generic to the `NavigationContainerRef` type.. + +Example when using `React.useRef` hook: + +```ts +import type { NavigationContainerRef } from '@react-navigation/native'; + +// ... + +const navigationRef = + React.useRef>(null); +``` + +Example when using `React.createRef`: + +```ts +import type { NavigationContainerRef } from '@react-navigation/native'; + +// ... + +const navigationRef = + React.createRef>(); +``` + +## Specifying default types for `useNavigation`, `Link`, `ref` etc + +Instead of manually annotating these APIs, you can specify a global type for your root navigator which will be used as the default type. + +To do this, you can add this snippet somewhere in your codebase: + +```js +declare global { + namespace ReactNavigation { + interface RootParamList extends RootStackParamList {} + } +} +``` + +The `RootParamList` interface lets React Navigation know about the params accepted by your root navigator. Here we extend the type `RootStackParamList` because that's the type of params for our stack navigator at the root. The name of this type isn't important. + +Specifying this type is important if you heavily use [`useNavigation`](use-navigation.md), [`Link`](link.md) etc. in your app since it'll ensure type-safety. It will also make sure that you have correct nesting on the [`linking`](navigation-container.md#linking) prop. + +## Organizing types + +When writing types for React Navigation, there are a couple of things we recommend to keep things organized. + +1. It's good to create a separate file (e.g. `navigation/types.tsx`) that contains the types related to React Navigation. +2. Instead of using `CompositeNavigationProp` directly in your components, it's better to create a helper type that you can reuse. +3. Specifying a global type for your root navigator would avoid manual annotations in many places. + +Considering these recommendations, the file containing the types may look something like this: + +```ts +import type { + CompositeScreenProps, + NavigatorScreenParams, +} from '@react-navigation/native'; +import type { StackScreenProps } from '@react-navigation/stack'; +import type { BottomTabScreenProps } from '@react-navigation/bottom-tabs'; + +export type RootStackParamList = { + Home: NavigatorScreenParams; + PostDetails: { id: string }; + NotFound: undefined; +}; + +export type RootStackScreenProps = + StackScreenProps; + +export type HomeTabParamList = { + Popular: undefined; + Latest: undefined; +}; + +export type HomeTabScreenProps = + CompositeScreenProps< + BottomTabScreenProps, + RootStackScreenProps + >; + +declare global { + namespace ReactNavigation { + interface RootParamList extends RootStackParamList {} + } +} +``` + +Now, when annotating your components, you can write: + +```ts +import type { HomeTabScreenProps } from './navigation/types'; + +function PopularScreen({ navigation, route }: HomeTabScreenProps<'Popular'>) { + // ... +} +``` + +If you're using hooks such as [`useRoute`](use-route.md), you can write: + +```ts +import type { HomeTabScreenProps } from './navigation/types'; + +function PopularScreen() { + const route = useRoute['route']>(); + + // ... +} +``` + + + diff --git a/versioned_docs/version-8.x/upgrading-from-6.x.md b/versioned_docs/version-8.x/upgrading-from-6.x.md new file mode 100755 index 0000000000..fd5c971ced --- /dev/null +++ b/versioned_docs/version-8.x/upgrading-from-6.x.md @@ -0,0 +1,1023 @@ +--- +id: upgrading-from-6.x +title: Upgrading from 6.x +sidebar_label: Upgrading from 6.x +--- + +React Navigation 7 focuses on streamlining the API to avoid patterns that can cause bugs. This means deprecating some of the legacy behavior kept for backward compatibility reasons. + +This guides lists all the breaking changes and new features in React Navigation 7 that you need to be aware of when upgrading from React Navigation 6. + +## Minimum Requirements + +- `react-native` >= 0.72.0 +- `expo` >= 52 (if you use [Expo Go](https://expo.dev/go)) +- `typescript` >= 5.0.0 (if you use TypeScript) + +## Breaking changes + +### Changes to the `navigate` action + +#### The `navigate` method no longer navigates to screen in a nested child navigator + +Due to backward compatibility reasons, React Navigation 5 and 6 support navigating to a screen in a nested child navigator with `navigation.navigate(ScreenName)` syntax. But this is problematic: + +- It only works if the navigator is already mounted - making navigation coupled to other logic. +- It doesn't work with the TypeScript types. + +Due to these issues, we have a special API to navigate to a nested screen (`navigation.navigate(ParentScreenName, { screen: ScreenName })`). + +From these release, this is no longer the default behavior. If you're relying on this behavior in your app, you can pass the [`navigationInChildEnabled`](navigation-container.md#navigationinchildenabled) prop to `NavigationContainer` to keep the behavior until you are able to migrate: + +```jsx +{/* ... */} +``` + +The `navigationInChildEnabled` prop will be removed in the next major. + +See [`navigate`](navigation-object.md#navigate) for updated usage. + +#### The `navigate` method no longer goes back, use `popTo` instead + +Previously, `navigate` method navigated back if the screen already exists in the stack. We have seen many people get confused by this behavior. + +To avoid this confusion, we have removed the going back behavior from `navigate` and added a [new method `popTo`](stack-actions.md#popto) to explicitly go back to a specific screen in the stack: + +```diff lang=js +- navigation.navigate('PreviousScreen', { foo: 42 }); ++ navigation.popTo('PreviousScreen', { foo: 42 }); +``` + +The methods now behave as follows: + +- `navigate(screenName)` will stay on the current screen if the screen is already focused, otherwise push a new screen to the stack. +- `popTo(screenName)` will go back to the screen if it exists in the stack, otherwise pop the current screen and add this screen to the stack. + +See [`popTo`](stack-actions.md#popto) for more details. + +To achieve a behavior similar to before with `navigate`, you can use specify `pop: true` in the options: + +```diff lang=js +- navigation.navigate('PreviousScreen', { foo: 42 }); ++ navigation.navigate('PreviousScreen', { foo: 42 }, { pop: true }); +``` + +To help with the migration, we have added a new method called `navigateDeprecated` which will behave like the old `navigate` method. You can replace your current `navigate` calls with [`navigateDeprecated`](navigation-object.md#navigatedeprecated) to gradually migrate to the new behavior: + +```diff lang=js +- navigation.navigate('SomeScreen'); ++ navigation.navigateDeprecated('SomeScreen'); +``` + +The `navigateDeprecated` method will be removed in the next major. + +#### The `navigate` method no longer accepts a `key` option + +Previously, you could specify a route `key` to navigate to, e.g.: + +```js +navigation.navigate({ key: 'someuniquekey' })` +``` + +It's problematic since: + +- `key` is an internal implementation detail and created by the library internally - which makes it weird to use. +- None of the other actions support such usage. +- Specifying a `key` is not type-safe, making it easy to cause bugs. + +In React Navigation 5, we added the [`getId`](screen.md#id) prop which can be used for similar use cases - and gives users full control since they provide the ID and it's not autogenerated by the library. + +So the `key` option is now being removed from the `navigate` action. + +See [`navigate`](navigation-object.md#navigate) for updated usage. + +### Changes to `NavigationContainer` + +#### The `onReady` callback on `NavigationContainer` now fires only when there are navigators rendered + +Previously, the `onReady` prop and `navigationRef.isReady()` worked slightly differently: + +- The `onReady` callback fired when `NavigationContainer` finishes mounting and deep links is resolved. +- The `navigationRef.isReady()` method additionally checks if there are any navigators rendered - which may not be true if the user is rendering their navigators conditionally inside a `NavigationContainer`. + +This is important to know since if no navigator is rendered, we can't dispatch any navigation actions as there's no navigator to handle them. But the inconsistency between `onReady` and `navigationRef.isReady()` made it easy to cause issues and confusion. + +This changes `onReady` to work similar to `navigationRef.isReady()`. The `onReady` callback will now fire only when there are navigators rendered - reflecting the value of `navigationRef.isReady()`. + +This change is not breaking for most users, so you may not need to do anything. + +See [`onReady`](navigation-container.md#onready) for usage. + +#### The `independent` prop on `NavigationContainer` is removed in favor of `NavigationIndependentTree` component + +The `independent` prop on `NavigationContainer` was added to support rendering navigators in a separate tree from the rest of the app. This is useful for use cases such as miniapps. + +However, there are issues with this approach: + +- When building a miniapp, the responsibility of adding this prop was on the miniapp developer, which isn't ideal since forgetting it can cause problems. +- A lot of beginners mistakenly added this prop and were confused why navigation wasn't working. + +So we've removed this prop instead of a `NavigationIndependentTree` component which you can use to wrap the navigation container: + +```diff lang=jsx +- +- {/* ... */} +- ++ ++ ++ {/* ... */} ++ ++ +``` + +This way, the responsibility no longer lies on the miniapp developer, but on the parent app. It's also harder for beginners to accidentally add this. + +See [Independent navigation containers](navigation-container.md#independent-navigation-containers) for usage. + +#### The `theme` prop now accepts a `fonts` property + +Previously, the `theme` prop on `NavigationContainer` accepted a `colors` property to customize the colors used by various UI elements from React Navigation. We have now added a `fonts` property to customize the fonts as well. If you are passing a custom theme in the `theme` prop, you'll need to update it to include the `fonts` property. + +```diff lang=js +import { DefaultTheme } from '@react-navigation/native'; + +const theme = { + colors: { + // ... + }, ++ fonts: DefaultTheme.fonts, +}; +``` + +If you want to customize the fonts, see [the themes guide](themes.md) for more details. + +#### The navigation state is frozen in development mode + +The navigation state is now frozen in development mode to prevent accidental mutations. This includes the state object and all the nested objects such as `route` object etc. If you're mutating the navigation state directly, you may get an error like `Cannot assign to read only property 'key' of object`. + +Note that React Navigation relies on the immutability of the navigation state to detect changes and update the UI. Mutating the navigation state directly can cause issues and was never supported. So if you're mutating the navigation state directly, you'll need to use a different approach. + +### Changes to linking + +#### Encoding of params in path position is now more relaxed + +Previously, params were always URL encoded with `encodeURIComponent` regardless of their position (e.g. query position such as `?user=jane` or path position such as `/users/jane`) when generating a link for a screen (e.g. URL on the Web). This made it hard to use special characters in the params. + +Now, only the params in the query position are URL encoded. For the params in the path position, we only encode the characters that are not allowed in the path position. + +With this change, it's easier to use special characters such as `@` in the path. For example, to have a URL such as `profile/@username`, you can use the following in the linking config: + +```js +const config = { + prefixes: ['https://mysite.com'], + config: { + screens: { + Profile: { + path: 'profile/:username', + parse: { + username: (username) => username.replace(/^@/, ''), + }, + stringify: { + username: (username) => `@${username}`, + }, + }, + }, + }, +}; +``` + +See [Configuring links](configuring-links.md) for usage of the linking config. + +#### The `Link` component and `useLinkProps` hook now use screen names instead of paths + +Previously, the `Link` component and `useLinkProps` hook were designed to work with path strings via the `to` prop. But it had few issues: + +- The path strings are not type-safe, making it easy to cause typos and bugs after refactor +- The API made navigating via screen name more inconvenient, even if that's the preferred approach + +Now, instead of the `to` prop that took a path string, they now accept `screen` and `params` props, as well as an optional `href` prop to use instead of the generated path: + +```diff lang=jsx +- Go to Details ++ Go to Details +``` + +or + +```diff lang=js +- const props = useLinkProps({ to: '/details?foo=42' }); ++ const props = useLinkProps({ screen: 'Details', params: { foo: 42 } }); +``` + +With this change, you'd now have full type-safety when using the `Link` component given that you have [configured the global type](typescript.md#specifying-default-types-for-usenavigation-link-ref-etc). + +See [`Link`](link.md) and [`useLinkProps`](use-link-props.md) for usage. + +#### The `useLinkBuilder` hooks now returns an object instead of a function + +Previously, the `useLinkBuilder` hooks returned a function to build a `href` for a screen - which is primarily useful for building custom navigators. Now, it returns an object with `buildHref` and `buildAction` methods: + +```js +const { buildHref, buildAction } = useLinkBuilder(); + +const href = buildHref('Details', { foo: 42 }); // '/details?foo=42' +const action = buildAction('/details?foo=42'); // { type: 'NAVIGATE', payload: { name: 'Details', params: { foo: 42 } } } +``` + +The `buildHref` method acts the same as the previously returned function. The new `buildAction` method can be used to build a navigation action from a `href` string. + +Note that this hook is intended to be primarily used by custom navigators and not by end users. For end users, the `Link` component and `useLinkProps` are the recommended way to navigate. + +See [`useLinkBuilder`](use-link-builder.md) for usage. + +### Changes to navigators + +#### Screens pushed on top of modals are now shown as modals in the Stack and Native Stack navigators + +Previously, screens pushed on top of modals were shown as regular screens in the Stack and Native Stack navigators. This often caused glitchy animation on Stack Navigator and appeared behind the modal on Native Stack Navigator. This can be especially confusing if the user came to the screen from a deep link. + +Now, screens pushed on top of modals are automatically shown as modals to avoid these issues. This behavior can be disabled by explicitly setting the `presentation` option to `card`: + +```jsx + +``` + +See [Stack Navigator](stack-navigator.md#presentation) and [Native Stack Navigator](native-stack-navigator.md#presentation) docs for usage. + +#### `headerBackTitleVisible` is removed in favor of `headerBackButtonDisplayMode` in Stack and Native Stack navigators + +Previously, `headerBackTitleVisible` could be used to control whether the back button title is shown in the header. It's now removed in favor of `headerBackButtonDisplayMode` which provides more flexibility. + +The previous behavior can be achieved by setting `headerBackButtonDisplayMode` to `default` and `minimal` for showing and hiding the back button title respectively: + +```diff lang=js + +``` + +#### `animationEnabled` option is removed in favor of `animation` option in Stack Navigator + +Previously, `animationEnabled: false` was used to disable the animation for the screen transition in Stack Navigator. + +There's now a new `animation` prop to configure animations similar to the Native Stack. So you can now use `animation: 'none'` to disable the animation instead: + +```diff lang=js + +``` + +See [Stack Navigator animation](stack-navigator.md#animations) for usage. + +#### `customAnimationOnGesture` is renamed to `animationMatchesGesture` in Native Stack Navigator + +The `customAnimationOnGesture` option in Native Stack Navigator is renamed to `animationMatchesGesture` to better reflect its purpose. If you are using `customAnimationOnGesture` in your project, you can rename it to `animationMatchesGesture`: + +```diff lang=js +- ++ +``` + +See [Native Stack Navigator](native-stack-navigator.md#animationmatchesgesture) for usage. + +#### `statusBarColor` is renamed to `statusBarBackgroundColor` in Native Stack Navigator + +The `statusBarColor` option in Native Stack Navigator is renamed to `statusBarBackgroundColor` to better reflect its purpose. If you are using `statusBarColor` in your project, you can rename it to `statusBarBackgroundColor`: + +```diff lang=js +- ++ +``` + +See [Native Stack Navigator](native-stack-navigator.md#statusbarbackgroundcolor) for usage. + +#### Native Stack now requires `react-native-screens` 4 + +`@react-navigation/native-stack` now requires `react-native-screens` 4 and will break when using an earlier version. If you are using Native Stack Navigator in your project, make sure to upgrade `react-native-screens` to version 4. + +See [Native Stack Navigator](native-stack-navigator.md) for usage. + +#### Material Top Tab Navigator no longer requires installing `react-native-tab-view` + +Previously, `@react-navigation/material-top-tabs` required installing `react-native-tab-view` as a dependency in the project. We have now moved this package to the React Navigation monorepo and able to coordinate the releases together, so it's no longer necessary to install it separately. + +If you use `@react-navigation/material-top-tabs` and don't use `react-native-tab-view` anywhere else in your project, you can remove it from your dependencies after upgrading. + +If you need to enforce a specific version of `react-native-tab-view` for some reason, we recommend using [Yarn resolutions](https://classic.yarnpkg.com/lang/en/docs/selective-version-resolutions/) or [npm overrides](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#overrides) to do so. + +See [Material Top Tab Navigator](material-top-tab-navigator.md) for usage. + +#### The `unmountOnBlur` option is removed in favor of `popToTopOnBlur` in Bottom Tab Navigator and Drawer Navigator + +In many cases, the desired behavior is to return to the first screen of the stack nested in a tab or drawer navigator after it's unfocused. Previously, the `unmountOnBlur` option was used to achieve this behavior. However, it had some issues: + +- It destroyed the local state of the screen in the stack. +- It was slow to remount the nested navigator on tab navigation. + +The `popToTopOnBlur` option provides an alternative approach - it pops the screens on a nested stack to go back to the first screen in the stack and doesn't have the above issues. + +See [Bottom Tab Navigator](bottom-tab-navigator.md#poptotoponblur) and [Drawer Navigator](drawer-navigator.md#poptotoponblur) docs for usage. + +It's still possible to achieve the old behavior of `unmountOnBlur` by using the [`useIsFocused`](use-is-focused.md) hook in the screen: + +```js +const isFocused = useIsFocused(); + +if (!isFocused) { + return null; +} +``` + +This could also be combined with the new [layout props](#new-layout-props) to specify it at the screen configuration level to make the migration easier. + +To achieve this, define a component that uses the `useIsFocused` hook to conditionally render its children: + +```js +function UnmountOnBlur({ children }) { + const isFocused = useIsFocused(); + + if (!isFocused) { + return null; + } + + return children; +} +``` + +Then use the component in `layout` prop of the screen: + +```diff lang=js + {children}} + options={{ +- unmountOnBlur: true, + }} +/> +``` + +Or `screenLayout` prop of the navigator: + +```diff lang=js + {children}} + screenOptions={{ +- unmountOnBlur: true, + }} +> +``` + +#### The `tabBarTestID` option is renamed to `tabBarButtonTestID` in Bottom Tab Navigator and Material Top Tab Navigator + +The `tabBarTestID` option in `@react-navigation/bottom-tabs` and `@react-navigation/material-top-tabs` is renamed to `tabBarButtonTestID` to better reflect its purpose. If you are using `tabBarTestID` in your project, you can rename it to `tabBarButtonTestID`: + +```diff lang=js +- ++ +``` + +See [Bottom Tab Navigator](bottom-tab-navigator.md#tabbarbuttontestid) and [Material Top Tab Navigator](material-top-tab-navigator.md#tabbarbuttontestid) docs for usage. + +#### The `sceneContainerStyle` prop and option are removed from Bottom Tab Navigator, Material Top Tab Navigator and Drawer Navigator in favor of `sceneStyle` + +Previously, the Bottom Tab Navigator and Material Top Tab Navigator accepted a `sceneContainerStyle` prop to style the container of the scene. This was inflexible as it didn't allow different styles for different screens. Now, the `sceneStyle` option is added to these navigators to style individual screens. + +Similarly, the `sceneContainerStyle` option in Drawer Navigator is renamed to `sceneStyle` for consistency. + +If you are using `sceneContainerStyle` prop, you can pass `sceneStyle` in `screenOptions` instead to keep the same behavior: + +```diff lang=js +- ++ +``` + +#### Drawer Navigator now requires Reanimated 2 or 3 on native platforms + +Previously, `@react-navigation/drawer` supported both Reanimated 1 and Reanimated 2 APIs with the `useLegacyImplementation` option. This is now no longer supported and the `useLegacyImplementation` option is removed. + +If you are using Reanimated 1 in your project, you'll need to upgrade to Reanimated 2 or 3 to use `@react-navigation/drawer`. + +If you're using Drawer Navigator on the Web, it'll now use CSS transitions instead of Reanimated for a smaller bundle size. + +See [Drawer Navigator](drawer-navigator.md) for usage. + +### Changes to elements + +#### `labelVisible` is removed in favor of `displayMode` in `headerLeft` and `HeaderBackButton` elements + +Previously, `labelVisible` could be used to control whether the back button title is shown in the header. It's now removed in favor of `displayMode` which provides more flexibility. + +The new possible values are: + +- `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. + +The previous behavior can be achieved by setting `displayMode` to `default` or `generic` for showing and `minimal` for hiding the back button title respectively: + +```diff lang=js + +``` + +### Deprecations and removals + +#### Material Bottom Tab Navigator now lives in `react-native-paper` package + +The `@react-navigation/material-bottom-tabs` package provided React Navigation integration for `react-native-paper`'s `BottomNavigation` component. To make it easier to keep it updated with the changes in `react-native-paper`, we have now moved it to the `react-native-paper` package. + +If you are using `@react-navigation/material-bottom-tabs` in your project, you can remove it from your dependencies and change the imports to `react-native-paper/react-navigation` instead: + +```diff lang=js +- import { createMaterialBottomTabNavigator } from '@react-navigation/material-bottom-tabs'; ++ import { createMaterialBottomTabNavigator } from 'react-native-paper/react-navigation'; +``` + +See [Material Bottom Tab Navigator](https://callstack.github.io/react-native-paper/docs/guides/bottom-navigation/) for usage. + +Alternatively, you can use the [`BottomNavigation.Bar`](https://callstack.github.io/react-native-paper/docs/components/BottomNavigation/BottomNavigationBar) component as a custom tab bar with `@react-navigation/bottom-tabs`. + +For any issues related to the Material Bottom Tab Navigator or `BottomNavigation.Bar`, please open them in the [react-native-paper repository](https://github.com/callstack/react-native-paper) instead of the React Navigation repository. + +#### The flipper devtools plugin is now removed + +Previously, we added a Flipper plugin for React Navigation to make debugging navigation easier. However, it has added significant maintenance overhead for us. The Flipper team hasn't been focused on React Native recently, so the overall experience of using Flipper with React Native has been poor. + +> Currently, the Flipper team has been focused on native developer experience, so we are going back to the drawing board. We have created a new pillar within our team focused on Developer Experience. We are currently investigating improved Chrome Debugger protocol support from the Hermes team as well as migrating the debugging experience from Flipper to Chrome DevTools so we can deliver a debugging experience that meets our standard. +> +> [react-native-community/discussions-and-proposals#546 (comment)](https://github.com/react-native-community/discussions-and-proposals/discussions/546#discussioncomment-4178951) + +Since the React Native team migrating away from Flipper, it doesn't make much sense for us to spend additional resources to keep supporting it. So we've removed the Flipper plugin from `@react-navigation/devtools`. + +Alternatively, you can use the following developer tools: + +- [Logger](devtools.md#uselogger) +- [Integration for Redux DevTools Extension](devtools.md#usereduxdevtoolsextension) +- [Devtools plugin for Expo](https://docs.expo.dev/debugging/devtools-plugins/#react-navigation) if you are using [Expo](https://expo.dev). + +#### Various deprecated APIs are removed + +We have removed all of the previously deprecated APIs. These APIs were deprecated in React Navigation 6 and showed a warning when used. So make sure that you have addressed all the warnings before upgrading. + +
+Full list of removed APIs + +- `@react-navigation/stack` + - Removed `mode` prop - use `presentation` option instead + - Removed `headerMode` prop - use `headerMode` and `headerShown` options instead + - Removed `keyboardHandlingEnabled` prop - use `keyboardHandlingEnabled` option instead +- `@react-navigation/drawer` + - Removed `openByDefault` prop - use `defaultStatus` prop instead + - Removed `lazy` prop - use `lazy` option instead + - Removed `drawerContentOptions` prop which contained following options: + - `drawerPosition` - use `drawerPosition` option instead + - `drawerType` - use `drawerType` option instead + - `edgeWidth` - use `swipeEdgeWidth` option instead + - `hideStatusBar` - use `drawerHideStatusBarOnOpen` option instead + - `keyboardDismissMode` - use `keyboardDismissMode` option instead + - `minSwipeDistance` - use `swipeMinDistance` option instead + - `overlayColor` - use `overlayColor` option instead + - `statusBarAnimation` - use `drawerStatusBarAnimation` option instead + - `gestureHandlerProps` - use `configureGestureHandler` option instead +- `@react-navigation/bottom-tabs` + - Removed `lazy` prop - use `lazy` option instead + - Removed `tabBarOptions` prop which contained following options: + - `keyboardHidesTabBar` - use `tabBarHideOnKeyboard` option instead + - `activeTintColor` - use `tabBarActiveTintColor` option instead + - `inactiveTintColor` - use `tabBarInactiveTintColor` option instead + - `activeBackgroundColor` - use `tabBarActiveBackgroundColor` option instead + - `inactiveBackgroundColor` - use `tabBarInactiveBackgroundColor` option instead + - `allowFontScaling` - use `tabBarAllowFontScaling` option instead + - `showLabel` - use `tabBarShowLabel` option instead + - `labelStyle` - use `tabBarLabelStyle` option instead + - `iconStyle` - use `tabBarIconStyle` option instead + - `tabStyle` - use `tabBarItemStyle` option instead + - `labelPosition` and `adapative` - use `tabBarLabelPosition` option instead + - `tabBarVisible` - use `display: 'none'` `tabBarStyle` option instead +- `@react-navigation/material-top-tabs` + - Removed `swipeEnabled` prop - use `swipeEnabled` option instead + - Removed `lazy` prop - use `lazy` option instead + - Removed `lazyPlaceholder` prop - use `lazyPlaceholder` option instead + - Removed `lazyPreloadDistance` prop - use `lazyPreloadDistance` option instead + - Removed `tabBarOptions` prop which contained following options: - `renderBadge` - use `tabBarBadge` option instead - `renderIndicator` - use `tabBarIndicator` option instead - `activeTintColor` - use `tabBarActiveTintColor` option instead - `inactiveTintColor` - use `tabBarInactiveTintColor` option instead - `pressColor` - use `tabBarPressColor` option instead - `pressOpacity` - use `tabBarPressOpacity` option instead - `showLabel` - use `tabBarShowLabel` option instead - `showIcon` - use `tabBarShowIcon` option instead - `allowFontScaling` - use `tabBarAllowFontScaling` option instead - `bounces` - use `tabBarBounces` option instead - `scrollEnabled` - use `tabBarScrollEnabled` option instead - `iconStyle` - use `tabBarIconStyle` option instead - `labelStyle` - use `tabBarLabelStyle` option instead - `tabStyle` - use `tabBarItemStyle` option instead - `indicatorStyle` - use `tabBarIndicatorStyle` option instead - `indicatorContainerStyle` - use `tabBarIndicatorContainerStyle` option instead - `contentContainerStyle` - use `tabBarContentContainerStyle` option instead - `style` - use `tabBarStyle` option instead + +
+ +### Miscellaneous + +#### Various UI elements now follow Material Design 3 guidelines + +Previously, the UI elements in React Navigation such as the header on platforms other than iOS, drawer, material top tabs etc. were following the Material Design 2 guidelines. We have now updated them to follow the Material Design 3 guidelines. + +#### React Native Tab View now has a new API to specify various options + +The API for the `TabView` and `TabBar` component in `react-native-tab-view` has been revamped. + +Some of props accepted by the `TabBar` have now been replaced with `commonOptions` and `options` props on `TabView`: + +- `getLabelText` -> `labelText` +- `getAccessible` -> `accessible` +- `getAccessibilityLabel` -> `accessibilityLabel` +- `getTestID` -> `testID` +- `renderIcon` -> `icon` +- `renderLabel` -> `label` +- `renderBadge` -> `badge` +- `labelStyle` +- `sceneContainerStyle` -> `sceneStyle` + +To keep the same behavior when updating your existing code, move these props to `commonOptions` prop on `TabView`: + +```diff lang=js + ( +- ++ + )} ++ commonOptions={{ ++ label: renderLabel, ++ labelStyle, ++ }} +/> +``` + +The options can also be customized individually for each tab by passing an object to the `options` prop with the `route.key` as the key and the options as the value: + +```js + ( + + ), + }} + options={{ + albums: { + labelText: 'Albums', + }, + profile: { + labelText: 'Profile', + }, + }} +/> +``` + +When using a custom tab bar, it will receive the `options` in the arguments. + +The new API will make it easier for us to improve re-rendering performance of the tab bar items in the library. + +See [React Native Tab View](tab-view.md#options) for usage. + +#### Custom navigators now require more type information + +Custom navigators now require more type information to work correctly so that we can provide better type-checking and autocompletion in TypeScript when using the navigator. + +```diff lang=js +- export const createMyNavigator = createNavigatorFactory< +- MyNavigationState, +- MyNavigationOptions, +- MyNavigationEventMap, +- typeof MyNavigator +- >(MyNavigator); ++ export function createMyNavigator< ++ const ParamList extends ParamListBase, ++ const NavigatorID extends string | undefined = undefined, ++ const TypeBag extends NavigatorTypeBagBase = { ++ ParamList: ParamList; ++ NavigatorID: NavigatorID; ++ State: TabNavigationState; ++ ScreenOptions: TabNavigationOptions; ++ EventMap: TabNavigationEventMap; ++ NavigationList: { ++ [RouteName in keyof ParamList]: TabNavigationProp< ++ ParamList, ++ RouteName, ++ NavigatorID ++ >; ++ }; ++ Navigator: typeof TabNavigator; ++ }, ++ const Config extends StaticConfig = StaticConfig, ++ >(config?: Config): TypedNavigator { ++ return createNavigatorFactory(MyNavigator)(config); ++ } +``` + +See [Custom navigators](custom-navigators.md) for usage. + +#### Packages now use ESM and package exports + +All the packages in React Navigation now use ESM exports. While it shouldn't affect most users, there are some changes to be aware of: + +- If you are importing internal files from the packages, they might now be restricted by your bundler and it won't be possible to import them directly. You should use the public API instead. +- If you're patching the packages using `patch-package`, `yarn patch` etc., you'll need to patch the built files under `lib/` folders instead of the source files under `src/` as the source files are no longer exported. +- If you're using TypeScript with the `module` or `moduleResolution` option, it maybe necessary to set `moduleResolution` to `bundler` to match [Metro's resolution behavior](https://reactnative.dev/blog/2023/06/21/package-exports-support#enabling-package-exports-beta). +- If you're using Webpack for bundling code using React Navigation, it maybe necessary to set [`resolve.fullySpecified`](https://webpack.js.org/configuration/module/#resolvefullyspecified) to `false` for bundling to work. + +## New features + +### Static configuration API + +React Navigation 5 introduced a dynamic API to support more flexible use cases. With React Navigation 7, we are re-introducing a static configuration API: + +```js +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +const MyStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + options: { + title: 'My App', + }, + }, + Details: { + screen: DetailsScreen, + linking: 'details/:id', + }, + }, +}); +``` + +The static configuration API provides the following benefits: + +- **Simpler type-checking with TypeScript**: It's not necessary to specify screens and their params separately. See [Type checking with TypeScript](typescript.md?config=static) for more details. +- **Easier deep linking setup**: Paths can be generated automatically. Linking configuration can be defined next to the screen for explicit configuration. See [Configuring links](configuring-links.md?config=static) for more details. + +It's also possible to mix the static and dynamic configuration APIs. For example, you can use the static configuration API for the top-level navigators and the dynamic configuration API for the nested navigators where you need more flexibility. + +:::note + +The static configuration API doesn't replace the dynamic configuration API. Both APIs are equally supported and you can choose the one that fits your use case better. + +::: + +You can see examples for both the static and dynamic configuration APIs in the documentation by selecting the appropriate tab in the examples. + +Go to ["Hello React Navigation"](hello-react-navigation.md?config=static) to start writing some code with the static API. + +### Improved TypeScript support + +Previously, the `navigation` object received in `options` and `listeners` callbacks were typed as `any` and required manual type annotation. Now, the `navigation` object has a more accurate type based on the navigator it's used in, and the type annotation is no longer required. + +We also export a new `XOptionsArgs` (where `X` is the navigator name, e.g. `StackOptionsArgs`, `BottomTabOptionsArgs` etc.) type which can be used to type the arguments of the `options` callback. This can be useful if you want to define the options callback separately. + +```ts +const options = ({ + route, +}: StackOptionsArgs) => { + return { + title: route.params.title, + }; +}; +``` + +### Improved RTL support + +Previously, various UI elements used the `I18nManager` API to determine the writing direction. However, this API doesn't work well on the Web as the writing direction can be different for a specific subtree and hence can't be determined globally. + +The `NavigationContainer` now accepts a `direction` prop to specify the direction of the layout instead of relying on the `I18nManager` API. It also exposes this value via `useLocale` hook for use in your own components. + +See the [navigation container docs](navigation-container.md#direction) for usage. + +### The `options` callback gets `theme` + +The `options` callback now receives the `theme` object to allow customizing the UI elements specified in the options: + +```jsx + ({ + headerRight: () => ( + {}} + color={theme.colors.primary} + /> + ), + })} +/> +``` + +See [Screen options](screen-options.md) for usage. + +### Top-level `path` in linking config + +The linking configuration now supports a top-level `path` configuration to define the base path for all the screens in the navigator: + +```js +const linking = { + prefixes: ['https://mysite.com'], + config: { + // highlight-next-line + path: 'app', + screens: { + Home: 'home', + Details: 'details/:id', + }, + }, +}; +``` + +This can be useful if your app lives under a subpath on the web. For example, if your app lives under `https://mysite.com/app`, you can define the `path` as `app` and the `Details` screen will be accessible at `https://mysite.com/app/details/42`. + +See [Configuring links](configuring-links.md#apps-under-subpaths) for usage. + +### Improved Web integration + +More built-in UI elements that trigger navigation now render `a` tags on the Web for better accessibility and SEO. This includes: + +- Back button in the header +- The tab buttons in material top tab navigator + +UI elements such as the bottom tab bar and drawer items already rendered `a` tags on the Web. + +### New `usePreventRemove` hook + +Previously, the only way to prevent a screen from being removed from the stack was to use the `beforeRemove` event. This didn't work well with the Native Stack Navigator. + +The new `usePreventRemove` hook is an alternative to `beforeRemove` that works with the Native Stack Navigator. + +See [`usePreventRemove`](use-prevent-remove.md) for usage. + +### New `layout` props + +#### For navigators + +Navigators now support a `layout` prop. It can be useful for augmenting the navigators with additional UI with a wrapper. The difference from adding a regular wrapper is that the code in `layout` callback has access to the navigator's state, options etc.: + +```jsx + ( + + + {children} + + )} + // highlight-end +> + {/* ... */} + +``` + +See [Navigator layout](navigator.md#layout) for usage. + +#### For screens and groups + +The `layout` prop makes it easier to provide things such as a global error boundary and suspense fallback for a group of screens without having to manually add HOCs for every screen separately. + +It can be used for a single screen with [`layout`](screen.md#layout): + +```jsx + ( + + + Loading… +
+ } + > + {children} + + + )} + // highlight-end +/> +``` + +Or with a [group](group.md#screen-layout) or [navigator](navigator.md#screen-layout) with `screenLayout`: + +```jsx + ( + + + Loading… +
+ } + > + {children} + + + )} +> + // highlight-end + {/* screens */} + +``` + +### Preloading screens + +All built-in navigators now support preloading screens prior to navigating to them. This can be useful to improve the perceived performance of the app by preloading the screens that the user is likely to navigate to next. Preloading a screen will render it off-screen and execute its side-effects such as data fetching. + +To preload a screen, you can use the `preload` method on the navigation object: + +```js +navigation.preload('Details', { id: 42 }); +``` + +See [`preload`](navigation-object.md#preload) for usage. + +### Improvements to navigators + +#### Bottom Tab Navigator can now show tabs on the side and top + +The `@react-navigation/bottom-tabs` package now supports showing tabs on the side. This will make it easier to build responsive UIs for where you want to show tabs on the bottom on smaller screens and switch to a sidebar on larger screens. + +Similarly, showing tabs on the top is also supported which can be useful for Android TV or Apple TV apps. + +You can use the `tabBarPosition` option to customize the position of the tabs: + +```jsx + + {/* ... */} + +``` + +See [Bottom Tab Navigator options](bottom-tab-navigator.md#tabbarposition) for usage. + +#### Bottom Tab Navigator now supports animations + +The `@react-navigation/bottom-tabs` package now supports animations. This was one of the most requested features on our Canny board: [TabNavigator Custom Transition](https://react-navigation.canny.io/feature-requests/p/tabnavigator-custom-transition). + +You can use the `animation` option to customize the animations for the tab transitions: + +```jsx + + {/* ... */} + +``` + +See [Bottom Tab Navigator animation](bottom-tab-navigator.md#animations) for usage. + +#### Stack Navigator now supports an `animation` option + +The `@react-navigation/stack` package now supports an `animation` option to customize the animations for the screen transitions: + +```jsx + + {/* ... */} + +``` + +The `animation` option is an alternative to the `TransitionPresets` API, and is intended to make migrating between JS stack and native stack navigators easier. + +See [Stack Navigator animation](stack-navigator.md#animations) for usage. + +#### Native Stack Navigator now exports a `useAnimatedHeaderHeight` hook + +The `@react-navigation/native-stack` package now exports a `useAnimatedHeaderHeight` hook. It can be used to animate content based on the header height changes - such as when the large title shrinks to a small title on iOS: + +```jsx +const headerHeight = useAnimatedHeaderHeight(); + +return ( + + {/* ... */} + +); +``` + +#### All navigators with headers now support `headerSearchBarOptions` + +The `Header` component from `@react-navigation/elements` now supports a `headerSearchBarOptions` prop. This means all navigators that use the `Header` component now support a search bar in the header as well on all platforms. Previously, this was only available in the Native Stack Navigator on iOS and Android. + +```js +React.useLayoutEffect(() => { + navigation.setOptions({ + headerSearchBarOptions: { + placeholder: 'Search', + onChangeText: (text) => { + // Do something + }, + }, + }); +}, [navigation]); +``` + +See [`headerSearchBarOptions`](elements.md#headersearchbaroptions) for usage. + +### New components in elements library + +The `@react-navigation/elements` package now includes new components that can be used in your app: + +#### `Button` + +The `Button` component has built-in support for navigating to screens, and renders an anchor tag on the Web when used for navigation: + +```jsx + +``` + +The button follows the [Material Design 3 guidelines](https://m3.material.io/components/buttons/overview). + +See [`Button`](elements.md#button) for usage. + +#### `HeaderButton` + +The `HeaderButton` component can be used to render buttons in the header with appropriate styling: + +```js +headerRight: ({ tintColor }) => ( + { + /* do something */ + }} + > + + +), +``` + +See [`HeaderButton`](elements.md#headerbutton) for usage. + +#### `Label` + +The `Label` component can be used to render label text, such as the label in a tab bar button: + +```jsx + +``` + +See [`Label`](elements.md#label) for usage. + +### `react-native-drawer-layout` package + +The drawer implementation used in `@react-navigation/drawer` is now available as a standalone package called `react-native-drawer-layout`. This makes it easier to use the drawer implementation even if you're not using React Navigation, or if you want to use it without a navigator. + +You can install it with: + +```bash npm2yarn +npm install react-native-drawer-layout +``` + +See [`react-native-drawer-layout`](drawer-layout.md) for usage. + +### `useLogger` devtool + +The `@react-navigation/devtools` package now exports a `useLogger` hook. It can be used to log navigation actions to the console: + +See [`useLogger`](devtools.md#uselogger) for usage. diff --git a/versioned_docs/version-8.x/use-focus-effect.md b/versioned_docs/version-8.x/use-focus-effect.md new file mode 100755 index 0000000000..771a42aeca --- /dev/null +++ b/versioned_docs/version-8.x/use-focus-effect.md @@ -0,0 +1,189 @@ +--- +id: use-focus-effect +title: useFocusEffect +sidebar_label: useFocusEffect +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Sometimes we want to run side-effects when a screen is focused. A side effect may involve things like adding an event listener, fetching data, updating document title, etc. While this can be achieved using `focus` and `blur` events, it's not very ergonomic. + +To make this easier, the library exports a `useFocusEffect` hook: + +```js name="useFocusEffect hook" snack static2dynamic +import * as React from 'react'; +import { View } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +// codeblock-focus-start +import { useFocusEffect } from '@react-navigation/native'; + +function ProfileScreen() { + useFocusEffect( + React.useCallback(() => { + // Do something when the screen is focused + return () => { + // Do something when the screen is unfocused + // Useful for cleanup functions + }; + }, []) + ); + + return ; +} +// codeblock-focus-end + +function HomeScreen() { + return ; +} + +const MyTabs = createBottomTabNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(MyTabs); + +export default function App() { + return ; +} +``` + +:::warning + +To avoid the running the effect too often, it's important to wrap the callback in `useCallback` before passing it to `useFocusEffect` as shown in the example. + +::: + +The `useFocusEffect` is analogous to React's `useEffect` hook. The only difference is that it only runs if the screen is currently focused. + +The effect will run whenever the dependencies passed to `React.useCallback` change, i.e. it'll run on initial render (if the screen is focused) as well as on subsequent renders if the dependencies have changed. If you don't wrap your effect in `React.useCallback`, the effect will run every render if the screen is focused. + +The cleanup function runs when the previous effect needs to be cleaned up, i.e. when dependencies change and a new effect is scheduled and when the screen unmounts or blurs. + +## Running asynchronous effects + +When running asynchronous effects such as fetching data from server, it's important to make sure that you cancel the request in the cleanup function (similar to `React.useEffect`). If you're using an API that doesn't provide a cancellation mechanism, make sure to ignore the state updates: + +```js +useFocusEffect( + React.useCallback(() => { + let isActive = true; + + const fetchUser = async () => { + try { + const user = await API.fetch({ userId }); + + if (isActive) { + setUser(user); + } + } catch (e) { + // Handle error + } + }; + + fetchUser(); + + return () => { + isActive = false; + }; + }, [userId]) +); +``` + +If you don't ignore the result, then you might end up with inconsistent data due to race conditions in your API calls. + +## Delaying effect until transition finishes + +The `useFocusEffect` hook runs the effect as soon as the screen comes into focus. This often means that if there is an animation for the screen change, it might not have finished yet. + +React Navigation runs its animations in native thread, so it's not a problem in many cases. But if the effect updates the UI or renders something expensive, then it can affect the animation performance. In such cases, we can use [`InteractionManager`](https://reactnative.dev/docs/interactionmanager) to defer our work until the animations or gestures have finished: + +```js +useFocusEffect( + React.useCallback(() => { + const task = InteractionManager.runAfterInteractions(() => { + // Expensive task + }); + + return () => task.cancel(); + }, []) +); +``` + +## How is `useFocusEffect` different from adding a listener for `focus` event + +The `focus` event fires when a screen comes into focus. Since it's an event, your listener won't be called if the screen was already focused when you subscribed to the event. This also doesn't provide a way to perform a cleanup function when the screen becomes unfocused. You can subscribe to the `blur` event and handle it manually, but it can get messy. You will usually need to handle `componentDidMount` and `componentWillUnmount` as well in addition to these events, which complicates it even more. + +The `useFocusEffect` allows you to run an effect on focus and clean it up when the screen becomes unfocused. It also handles cleanup on unmount. It re-runs the effect when dependencies change, so you don't need to worry about stale values in your listener. + +## When to use `focus` and `blur` events instead + +Like `useEffect`, a cleanup function can be returned from the effect in `useFocusEffect`. The cleanup function is intended to cleanup the effect - e.g. abort an asynchronous task, unsubscribe from an event listener, etc. It's not intended to be used to do something on `blur`. + +For example, **don't do the following**: + +```js +useFocusEffect( + React.useCallback(() => { + return () => { + // Do something that should run on blur + }; + }, []) +); +``` + +The cleanup function runs whenever the effect needs to cleanup, i.e. on `blur`, unmount, dependency change etc. It's not a good place to update the state or do something that should happen on `blur`. You should use listen to the `blur` event instead: + +```js +React.useEffect(() => { + const unsubscribe = navigation.addListener('blur', () => { + // Do something when the screen blurs + }); + + return unsubscribe; +}, [navigation]); +``` + +Similarly, if you want to do something when the screen receives focus (e.g. track screen focus) and it doesn't need cleanup or need to be re-run on dependency changes, then you should use the `focus` event instead: + +## Using with class component + +You can make a component for your effect and use it in your class component: + +```js +function FetchUserData({ userId, onUpdate }) { + useFocusEffect( + React.useCallback(() => { + const unsubscribe = API.subscribe(userId, onUpdate); + + return () => unsubscribe(); + }, [userId, onUpdate]) + ); + + return null; +} + +// ... + +class Profile extends React.Component { + _handleUpdate = (user) => { + // Do something with user object + }; + + render() { + return ( + <> + + {/* rest of your code */} + + ); + } +} +``` diff --git a/versioned_docs/version-8.x/use-is-focused.md b/versioned_docs/version-8.x/use-is-focused.md new file mode 100755 index 0000000000..9b2aa38514 --- /dev/null +++ b/versioned_docs/version-8.x/use-is-focused.md @@ -0,0 +1,71 @@ +--- +id: use-is-focused +title: useIsFocused +sidebar_label: useIsFocused +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +We might want to render different content based on the current focus state of the screen. The library exports a `useIsFocused` hook to make this easier: + +```js name="useIsFocused hook" snack static2dynamic +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; +// codeblock-focus-start +import { useIsFocused } from '@react-navigation/native'; + +function ProfileScreen() { + // This hook returns `true` if the screen is focused, `false` otherwise + // highlight-next-line + const isFocused = useIsFocused(); + + return ( + + {isFocused ? 'focused' : 'unfocused'} + + ); +} +// codeblock-focus-end + +function HomeScreen() { + return ; +} + +const MyTabs = createMaterialTopTabNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(MyTabs); + +export default function App() { + return ; +} +``` + +Note that using this hook triggers a re-render for the component when the screen it's in changes focus. This might cause lags during the animation if your component is heavy. You might want to extract the expensive parts to separate components and use [`React.memo`](https://react.dev/reference/react/memo) or [`React.PureComponent`](https://react.dev/reference/react/PureComponent) to minimize re-renders for them. + +## Using with class component + +You can wrap your class component in a function component to use the hook: + +```js +class Profile extends React.Component { + render() { + // Get it from props + const { isFocused } = this.props; + } +} + +// Wrap and export +export default function (props) { + const isFocused = useIsFocused(); + + return ; +} +``` diff --git a/versioned_docs/version-8.x/use-link-builder.md b/versioned_docs/version-8.x/use-link-builder.md new file mode 100644 index 0000000000..dcb3da963f --- /dev/null +++ b/versioned_docs/version-8.x/use-link-builder.md @@ -0,0 +1,64 @@ +--- +id: use-link-builder +title: useLinkBuilder +sidebar_label: useLinkBuilder +--- + +The `useLinkBuilder` hook returns helpers to build `href` or action based on the [`linking` configuration](configuring-links.md). It returns an object with the following properties: + +- [`buildHref`](#buildhref) +- [`buildAction`](#buildaction) + +## `buildHref` + +The `buildHref` method lets us build a path to use for links for a screen in the current navigator's state. It returns a function that takes `name` and `params` for the screen to focus and returns path based on the [`linking` configuration](configuring-links.md). + +```js +import { useLinkBuilder } from '@react-navigation/native'; +import { PlatformPressable } from '@react-navigation/elements'; + +// ... + +function DrawerContent({ state, descriptors, navigation }) { + const { buildHref } = useLinkBuilder(); + + return state.routes((route) => ( + navigation.navigate(route.name, route.params)} + > + {descriptors[route.key].options.title} + + )); +} +``` + +This hook is intended to be used in navigators to show links to various pages in the navigator, such as drawer and tab navigators. If you're building a custom navigator, custom drawer content, custom tab bar etc. then you might want to use this hook. + +There are couple of important things to note: + +- The destination screen must be present in the current navigator. It cannot be in a parent navigator or a navigator nested in a child. +- It's intended to be only used in custom navigators to keep them reusable in multiple apps. For your regular app code, use screen names directly instead of building paths for screens. + +## `buildAction` + +The `buildAction` method lets us parse a `href` string into an action object that can be used with [`navigation.dispatch`](navigation-object.md#dispatch) to navigate to the relevant screen. + +```js +import { Link, CommonActions, useLinkBuilder } from '@react-navigation/native'; +import { Button } from '@react-navigation/elements'; + +// ... + +function MyComponent() { + const { buildAction } = useLinkBuilder(); + + return ( + + ); +} +``` + +The [`useLinkTo`](use-link-to.md) hook is a convenient wrapper around this hook to navigate to a screen using a `href` string. diff --git a/versioned_docs/version-8.x/use-link-props.md b/versioned_docs/version-8.x/use-link-props.md new file mode 100644 index 0000000000..9a1834fa9c --- /dev/null +++ b/versioned_docs/version-8.x/use-link-props.md @@ -0,0 +1,110 @@ +--- +id: use-link-props +title: useLinkProps +sidebar_label: useLinkProps +--- + +The `useLinkProps` hook lets us build our custom link component. The link component can be used as a button to navigate to a screen. On the web, it will be rendered as an anchor tag (``) with the `href` attribute so that all the accessibility features of a link are preserved, e.g. - such as `Right click -> Open link in new tab"`, `Ctrl+Click`/`⌘+Click` etc. + +It returns an object with some props that you can pass to a component. + +Example: + +```js +import { useLinkProps } from '@react-navigation/native'; + +// ... + +const LinkButton = ({ screen, params, action, href, children, ...rest }) => { + const props = useLinkProps({ screen, params, action, href }); + + const [isHovered, setIsHovered] = React.useState(false); + + return ( + + {children} + + ); +}; +``` + +Then you can use the `LinkButton` component elsewhere in your app: + +```js +function Home() { + return ( + + Go to Jane's profile + + ); +} +``` + +## Options + +### `screen` and `params` + +You can pass `screen` and `params` to navigate to a screen on press: + +```js +function Home() { + return ( + + Go to Jane's profile + + ); +} +``` + +If you want to navigate to a nested screen, you can pass the name of the `screen` in `params` similar to [navigating to a screen in a nested navigator](nesting-navigators.md#navigating-to-a-screen-in-a-nested-navigator): + +```js + + Go to post 123 + +``` + +### `action` + +Sometimes we want a different behavior for in-page navigation, such as `replace` instead of `navigate`. We can use the `action` prop to customize it: + +Example: + +```js +import { StackActions } from '@react-navigation/native'; + +// ... + +function Home() { + return ( + + Go to Jane's profile + + ); +} +``` + +The `screen` and `params` props can be omitted if the `action` prop is specified. In that case, we recommend specifying the `href` prop as well to ensure that the link is accessible. + +### `href` + +The `href` is used for the `href` attribute of the anchor tag on the Web to make the links accessible. By default, this is automatically determined based on the [`linking` options](navigation-container.md#linking) using the `screen` and `params` props. + +If you want to use a custom `href`, you can pass it as the `href` prop: + +```js +function Home() { + return ( + + Getting Started + + ); +} +``` diff --git a/versioned_docs/version-8.x/use-link-to.md b/versioned_docs/version-8.x/use-link-to.md new file mode 100644 index 0000000000..e75da45d47 --- /dev/null +++ b/versioned_docs/version-8.x/use-link-to.md @@ -0,0 +1,51 @@ +--- +id: use-link-to +title: useLinkTo +sidebar_label: useLinkTo +--- + +The `useLinkTo` hook lets us navigate to a screen using a path instead of a screen name based on the [`linking` options](navigation-container.md#linking). It returns a function that receives the path to navigate to. + +```js +import { useLinkTo } from '@react-navigation/native'; + +// ... + +function Home() { + const linkTo = useLinkTo(); + + return ( + + ); +} +``` + +This is a low-level hook used to build more complex behavior on top. We recommended using the [`useLinkProps` hook](use-link-props.md) to build your custom link components instead of using this hook directly. It will ensure that your component is properly accessible on the web. + +:::warning + +Navigating via `href` strings is not type-safe. If you want to navigate to a screen with type-safety, it's recommended to use screen names directly. + +::: + +## Using with class component + +You can wrap your class component in a function component to use the hook: + +```js +class Home extends React.Component { + render() { + // Get it from props + const { linkTo } = this.props; + } +} + +// Wrap and export +export default function (props) { + const linkTo = useLinkTo(); + + return ; +} +``` diff --git a/versioned_docs/version-8.x/use-navigation-state.md b/versioned_docs/version-8.x/use-navigation-state.md new file mode 100755 index 0000000000..c620118dc9 --- /dev/null +++ b/versioned_docs/version-8.x/use-navigation-state.md @@ -0,0 +1,161 @@ +--- +id: use-navigation-state +title: useNavigationState +sidebar_label: useNavigationState +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +`useNavigationState` is a hook which gives access to the [navigation state](navigation-state.md) of the navigator which contains the screen. It's useful in rare cases where you want to render something based on the navigation state. + +:::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. + +::: + +It takes a selector function as an argument. The selector will receive the full [navigation state](navigation-state.md) and can return a specific value from the state: + +```js +const index = useNavigationState((state) => state.index); +``` + +The selector function helps to reduce unnecessary re-renders, so your screen will re-render only when that's something you care about. If you actually need the whole state object, you can do this explicitly: + +```js +const state = useNavigationState((state) => state); +``` + +:::warning + +This hook is useful for advanced cases and it's easy to introduce performance issues if you're not careful. For most of the cases, you don't need the navigator's state. + +::: + +## How is `useNavigationState` different from `navigation.getState()`? + +The `navigation.getState()` function also returns the current [navigation state](navigation-state.md). The main difference is that the `useNavigationState` hook will trigger a re-render when values change, while `navigation.getState()` won't. For example, the following code will be incorrect: + +```js +function Profile() { + const routesLength = navigation.getState().routes.length; // Don't do this + + return Number of routes: {routesLength}; +} +``` + +In this example, even if you push a new screen, this text won't update. If you use the hook, it'll work as expected: + +```js name="useNavigation hook" snack static2dynamic +import * as React from 'react'; +import { Button } from '@react-navigation/elements'; +import { View, Text } from 'react-native'; +import { + createStaticNavigation, + useNavigation, + useRoute, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +// codeblock-focus-start +import { useNavigationState } from '@react-navigation/native'; + +function useIsFirstRouteInParent() { + const route = useRoute(); + const isFirstRouteInParent = useNavigationState( + (state) => state.routes[0].key === route.key + ); + + return isFirstRouteInParent; +} + +function usePreviousRouteName() { + return useNavigationState((state) => + state.routes[state.index - 1]?.name + ? state.routes[state.index - 1].name + : 'None' + ); +} +// codeblock-focus-end + +function HomeScreen() { + const navigation = useNavigation(); + const isFirstRoute = useIsFirstRouteInParent(); + const previousRouteName = usePreviousRouteName(); + return ( + + It is {isFirstRoute ? '' : 'not '}first route in navigator + Previous route name: {previousRouteName} + + + + ); +} + +function ProfileScreen() { + const navigation = useNavigation(); + const isFirstRoute = useIsFirstRouteInParent(); + const previousRouteName = usePreviousRouteName(); + return ( + + It is {isFirstRoute ? '' : 'not '}first route in navigator + Previous route name: {previousRouteName} + + + + ); +} + +function SettingsScreen() { + const navigation = useNavigation(); + const isFirstRoute = useIsFirstRouteInParent(); + const previousRouteName = usePreviousRouteName(); + return ( + + It is {isFirstRoute ? '' : 'not '}first route in navigator + Previous route name: {previousRouteName} + + + ); +} + +const RootStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + Settings: SettingsScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +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 + +You can wrap your class component in a function component to use the hook: + +```js +class Profile extends React.Component { + render() { + // Get it from props + const { routesLength } = this.props; + } +} + +// Wrap and export +export default function (props) { + const routesLength = useNavigationState((state) => state.routes.length); + + return ; +} +``` diff --git a/versioned_docs/version-8.x/use-navigation.md b/versioned_docs/version-8.x/use-navigation.md new file mode 100755 index 0000000000..03c019158d --- /dev/null +++ b/versioned_docs/version-8.x/use-navigation.md @@ -0,0 +1,100 @@ +--- +id: use-navigation +title: useNavigation +sidebar_label: useNavigation +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +`useNavigation` is a hook that gives access to `navigation` object. It's useful when you cannot pass the `navigation` object as a prop to the component directly, or don't want to pass it in case of a deeply nested child. + +The `useNavigation` hook returns the `navigation` object of the screen where it's used: + +```js name="useNavigation hook" snack static2dynamic +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +import { createStaticNavigation } 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() { + const navigation = useNavigation(); + + return ( + + This is the home screen of the app + + + ); +} + +function ProfileScreen() { + return ( + + Profile Screen + + + ); +} + +const RootStack = createNativeStackNavigator({ + initialRouteName: 'Home', + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +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. + +## Using with class component + +You can wrap your class component in a function component to use the hook: + +```js +class MyBackButton extends React.Component { + render() { + // Get it from props + const { navigation } = this.props; + } +} + +// Wrap and export +export default function (props) { + const navigation = useNavigation(); + + return ; +} +``` diff --git a/versioned_docs/version-8.x/use-prevent-remove.md b/versioned_docs/version-8.x/use-prevent-remove.md new file mode 100644 index 0000000000..a37168eb9c --- /dev/null +++ b/versioned_docs/version-8.x/use-prevent-remove.md @@ -0,0 +1,158 @@ +--- +id: use-prevent-remove +title: usePreventRemove +sidebar_label: usePreventRemove +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The `usePreventRemove` hook allows you to prevent the user from leaving a screen. For example, if there are unsaved changes, you might want to show a confirmation dialog before the user can navigate away. + +The hook takes 2 parameters: + +- `preventRemove`: A boolean value indicating whether to prevent the screen from being removed. +- `callback`: A function that will be called when the removal is prevented. This can be used to show a confirmation dialog. + +The callback receives a `data` object with the `action` that triggered the removal of the screen. You can dispatch this action again after confirmation, or check the action object to determine what to do. + +Example: + +```js name="usePreventRemove hook" snack static2dynamic +import * as React from 'react'; +import { Alert, View, TextInput, Platform, StyleSheet } from 'react-native'; +import { + useNavigation, + usePreventRemove, + createStaticNavigation, +} from '@react-navigation/native'; +import { createStackNavigator } from '@react-navigation/stack'; +import { Button } from '@react-navigation/elements'; + +// codeblock-focus-start +const EditTextScreen = () => { + const [text, setText] = React.useState(''); + const navigation = useNavigation(); + + const hasUnsavedChanges = Boolean(text); + + usePreventRemove(hasUnsavedChanges, ({ data }) => { + if (Platform.OS === 'web') { + const discard = confirm( + 'You have unsaved changes. Discard them and leave the screen?' + ); + + if (discard) { + navigation.dispatch(data.action); + } + } else { + Alert.alert( + 'Discard changes?', + 'You have unsaved changes. Discard them and leave the screen?', + [ + { text: "Don't leave", style: 'cancel', onPress: () => {} }, + { + text: 'Discard', + style: 'destructive', + onPress: () => navigation.dispatch(data.action), + }, + ] + ); + } + }); + + return ( + + + + ); +}; +// codeblock-focus-end + +const HomeScreen = () => { + const navigation = useNavigation(); + + return ( + + + + ); +}; + +const RootStack = createStackNavigator({ + screens: { + Home: HomeScreen, + EditText: EditTextScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +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, + }, +}); +``` + + + +Internally, the hook uses the [`beforeRemove`](navigation-events.md#beforeremove) event to prevent the screen from being removed. This event is triggered whenever a screen is being removed due to a navigation action. + +## Limitations + +There are a couple of limitations to be aware of when using the `usePreventRemove` hook. It is **only** triggered whenever a screen is being removed due to a navigation state change. For example: + +- The user pressed the back button on a screen in a stack. +- The user performed a swipe-back gesture. +- Some action such as `pop` or `reset` was dispatched which removes the screen from the state. + +It **does not prevent** a screen from being unfocused if it's not being removed. For example: + +- The user pushed a new screen on top of the screen with the listener in a stack. +- The user navigated from one tab/drawer screen to another tab/drawer screen. + +It also **does not prevent** a screen from being removed when the user is exiting the screen due to actions not controlled by the navigation state: + +- The user closes the app (e.g. by pressing the back button on the home screen, closing the tab in the browser, closing it from the app switcher etc.). You can additionally use [`hardwareBackPress`](https://reactnative.dev/docs/backhandler) event on Android, [`beforeunload`](https://developer.mozilla.org/en-US/docs/web/api/window/beforeunload_event) event on the Web etc. to handle some of these cases. See [Prevent the user from leaving the app](preventing-going-back.md#prevent-the-user-from-leaving-the-app) for more details. +- A screen gets unmounted due to conditional rendering, or due to a parent component being unmounted. + +## UX considerations + +Generally, we recommend using this hook sparingly. A better approach is to persist the unsaved data into [`AsyncStorage`](https://github.com/react-native-async-storage/async-storage) or similar persistent storage and prompt to restore it when the user returns to the screen. + +Doing so has several benefits: + +- This approach still works if the app is closed or crashes unexpectedly. +- It's less intrusive to the user as they can still navigate away from the screen to check something and return without losing the data. diff --git a/versioned_docs/version-8.x/use-route-path.md b/versioned_docs/version-8.x/use-route-path.md new file mode 100644 index 0000000000..530bc324cb --- /dev/null +++ b/versioned_docs/version-8.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-8.x/use-route.md b/versioned_docs/version-8.x/use-route.md new file mode 100755 index 0000000000..0a94305c5e --- /dev/null +++ b/versioned_docs/version-8.x/use-route.md @@ -0,0 +1,101 @@ +--- +id: use-route +title: useRoute +sidebar_label: useRoute +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +`useRoute` is a hook which gives access to `route` object. It's useful when you cannot pass down the `route` object from props to the component, or don't want to pass it in case of a deeply nested child. + +`useRoute()` returns the `route` object of the screen it's inside. + +## Example + +```js name="useRoute hook" snack static2dynamic +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +import { + createStaticNavigation, + 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() { + const navigation = useNavigation(); + + return ( + + This is the home screen of the app + + + ); +} + +function ProfileScreen() { + return ( + + Profile Screen + + + ); +} + +const RootStack = createNativeStackNavigator({ + initialRouteName: 'Home', + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +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. + +## Using with class component + +You can wrap your class component in a function component to use the hook: + +```js +class MyText extends React.Component { + render() { + // Get it from props + const { route } = this.props; + } +} + +// Wrap and export +export default function (props) { + const route = useRoute(); + + return ; +} +``` diff --git a/versioned_docs/version-8.x/use-scroll-to-top.md b/versioned_docs/version-8.x/use-scroll-to-top.md new file mode 100755 index 0000000000..9c714c15cb --- /dev/null +++ b/versioned_docs/version-8.x/use-scroll-to-top.md @@ -0,0 +1,169 @@ +--- +id: use-scroll-to-top +title: useScrollToTop +sidebar_label: useScrollToTop +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The expected native behavior of scrollable components is to respond to events from navigation that will scroll to top when tapping on the active tab as you would expect from native tab bars. + +In order to achieve it we export `useScrollToTop` which accept ref to scrollable component (e,g. `ScrollView` or `FlatList`). + +Example: + +```js name="useScrollToTop hook" snack static2dynamic +import * as React from 'react'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { createStaticNavigation } from '@react-navigation/native'; +import { View, Image } from 'react-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 MyTabs = createBottomTabNavigator({ + screens: { + Home: HomeScreen, + Albums: Albums, + }, +}); + +const Navigation = createStaticNavigation(MyTabs); + +export default function App() { + return ; +} +``` + +## Using with class component + +You can wrap your class component in a function component to use the hook: + +```js +class Albums extends React.Component { + render() { + return {/* content */}; + } +} + +// Wrap and export +export default function (props) { + const ref = React.useRef(null); + + useScrollToTop(ref); + + return ; +} +``` + +## Providing scroll offset + +If you require offset to scroll position you can wrap and decorate passed reference: + +```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'; +import { createStaticNavigation } 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 MyTab = createBottomTabNavigator({ + screens: { + Home: HomeScreen, + Albums: Albums, + }, +}); + +const Navigation = createStaticNavigation(MyTab); + +export default function App() { + return ; +} +``` diff --git a/versioned_docs/version-8.x/use-theme.md b/versioned_docs/version-8.x/use-theme.md new file mode 100644 index 0000000000..d4b4b374f2 --- /dev/null +++ b/versioned_docs/version-8.x/use-theme.md @@ -0,0 +1,140 @@ +--- +id: use-theme +title: useTheme +sidebar_label: useTheme +--- + +import Tabs from '@theme/Tabs'; +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 static2dynamic +import * as React from 'react'; +import { + useNavigation, + createStaticNavigation, + DefaultTheme, + DarkTheme, +} from '@react-navigation/native'; +import { View, Text, TouchableOpacity, useColorScheme } from 'react-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 PanelStack = createNativeStackNavigator({ + screens: { + Profile: ProfileScreen, + Settings: SettingsScreen, + }, +}); + +const MyDrawer = createDrawerNavigator({ + initialRouteName: 'Panel', + screens: { + Home: HomeScreen, + Panel: { + screen: PanelStack, + options: { + headerShown: false, + }, + }, + }, +}); + +const Navigation = createStaticNavigation(MyDrawer); + +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 + +You can wrap your class component in a function component to use the hook: + +```js +class MyButton extends React.Component { + render() { + // Get it from props + const { theme } = this.props; + } +} + +// Wrap and export +export default function (props) { + const theme = useTheme(); + + return ; +} +``` diff --git a/versioned_docs/version-8.x/used-by.md b/versioned_docs/version-8.x/used-by.md new file mode 100644 index 0000000000..e2eb210758 --- /dev/null +++ b/versioned_docs/version-8.x/used-by.md @@ -0,0 +1,55 @@ +--- +id: used-by +title: Apps using React Navigation +sidebar_label: Apps using React Navigation +--- + +It's impossible to list every single app that uses React Navigation, but below are some of the great apps that we have found that make us feel humbled and proud! + +## Selected highlights + +- [Bloomberg](https://www.bloombergapps.com/app/bloomberg/) +- [Brex](https://brex.com/mobile/) +- [COVID Symptom Study](https://covid.joinzoe.com/) +- [Call of Duty companion app](https://www.callofduty.com/app) +- [Codecademy Go](https://www.codecademy.com/mobile-app-download) +- [Coinbase Pro](https://pro.coinbase.com/) +- [DataCamp](https://www.datacamp.com/mobile/) +- [Expo](https://expo.io/client) +- [How We Feel](https://howwefeel.org/) +- [National Football League (NFL)](https://itunes.apple.com/app/nfl/id389781154) and [NFL Fantasy Football](https://apps.apple.com/us/app/nfl-fantasy-football/id876054082) +- [Playstation App](https://www.playstation.com/en-ca/playstation-app/) ([iOS](https://apps.apple.com/us/app/playstation-app/id410896080)) ([Android](https://play.google.com/store/apps/details?id=com.scee.psxandroid&hl=en_CA&gl=US)) +- [Readwise](https://readwise.io/) +- [Shop from Shopify](https://www.shopify.com/shop) +- [Steady](https://steadyapp.com/) ([iOS](https://apps.apple.com/us/app/id1339259265)) ([Android](https://play.google.com/store/apps/details?id=com.steady.steadyapp.com)) +- [TaskRabbit](https://apps.apple.com/ca/app/taskrabbit-handyman-more/id374165361) +- [Th3rdwave](https://www.th3rdwave.coffee/) + +## Other great apps + +- [1000Kitap](https://1000kitap.com/) ([iOS](https://apps.apple.com/tr/app/1000kitap/id1319837589?l=tr)) ([Android](https://play.google.com/store/apps/details?id=com.binkitap.android&hl=en)) +- [ActiveCollab](https://activecollab.com/) ([iOS](https://apps.apple.com/us/app/activecollab-work-management/id1509421965)) ([Android](https://play.google.com/store/apps/details?id=com.activecollab.mobile)) +- [Cameo](https://apps.apple.com/us/app/cameo-personal-celeb-videos/id1258311581) +- [COVID Shield](https://www.covidshield.app/) ([Source Code](https://github.com/CovidShield/mobile)) +- [CuppaZee](https://www.cuppazee.app/) ([Source Code](https://github.com/CuppaZee/CuppaZee)) ([iOS](https://apps.apple.com/us/app/cuppazee/id1514563308)) ([Android](https://play.google.com/store/apps/details?id=uk.cuppazee.paper)) +- [Driversnote](https://www.driversnote.com/) +- [Disprz](https://www.disprz.com/) ([iOS](https://apps.apple.com/us/app/disprz/id1458716803#?platform=iphone)) ([Android](https://play.google.com/store/apps/details?id=com.disprz&hl=en_IN&gl=US)) +- [Fin](https://tryfin.app/) +- [JustCash](https://justcash.app/) ([Android](https://play.google.com/store/apps/details?id=com.justcash&hl=en&gl=US)) +- [NMF.earth](https://nmf.earth/) ([Source Code](https://github.com/NMF-earth/nmf-app)) ([iOS](https://apps.apple.com/us/app/nmf-earth/id1494561829)) ([Android](https://play.google.com/store/apps/details?id=nmf.earth)) +- [Pickyourtrail](https://apps.apple.com/us/app/pickyourtrail/id1400253672) +- [Prep: University Companion](https://prep.surf) ([iOS](http://tiny.cc/q4lliz)) ([Android](http://tiny.cc/14lliz)) ([Web](https://app.prep.surf/)) +- [Rocket.Chat](https://rocket.chat/) ([Source Code](https://github.com/RocketChat/Rocket.Chat.ReactNative)) ([iOS](https://apps.apple.com/us/app/rocket-chat/id1148741252)) ([Android](https://play.google.com/store/apps/details?id=chat.rocket.android)) +- [Saffron](https://www.mysaffronapp.com/) ([iOS](https://apps.apple.com/us/app/saffron-your-digital-cookbook/id1438683531)) ([Android](https://play.google.com/store/apps/details?id=com.awad.saffron)) +- [Single Origin 2](https://singleoriginapp.com/) +- [Skeel](https://www.skeelapp.com/) ([iOS](https://apps.apple.com/fr/app/skeel-qui-est-le-meilleur/id1292404366)) ([Android](https://play.google.com/store/apps/details?id=com.skeelofficial.reactnativeclient)) +- [Stillwhite: Wedding Dresses](https://www.stillwhite.com/) ([iOS](https://apps.apple.com/us/app/stillwhite-wedding-dresses/id1483180828)) ([Android](https://play.google.com/store/apps/details?id=com.stillwhite.app)) +- [Summer](https://www.summerapp.com/) ([iOS](https://apps.apple.com/app/apple-store/id1512328590?pt=118010433)) +- [Surely Todos](https://www.surelytodo.com/) ([iOS](https://apps.apple.com/us/app/surely/id1586633713)) ([Web](https://www.surelytodo.com/)) +- [Sweepy](https://sweepy.app/) +- [Tracker Network for Fortnite](https://apps.apple.com/us/app/tracker-network-for-fortnite/id1287696482) +- [Vrbo](https://www.vrbo.com/mobile/) + +## Your app? + +If you would like to suggest to add your app to this list, [please open a pull request](https://github.com/react-navigation/website)! diff --git a/versioned_docs/version-8.x/web-support.md b/versioned_docs/version-8.x/web-support.md new file mode 100755 index 0000000000..2bb89461c8 --- /dev/null +++ b/versioned_docs/version-8.x/web-support.md @@ -0,0 +1,159 @@ +--- +id: web-support +title: React Navigation on Web +sidebar_label: Web support +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +React Navigation has built-in support for the Web platform. This allows you to use the same navigation logic in your React Native app as well as on the web. The navigators require using [React Native for Web](https://github.com/necolas/react-native-web) to work on the web. + +## Pre-requisites + +While Web support works out of the box, there are some things to configure to ensure a good experience on the web: + +1. [**Configure linking**](configuring-links.md) + + Configuring linking allows React Navigation to integrate with the browser's URL bar. This is crucial for web apps to have proper URLs for each screen. + +2. [**Use Button or Link components**](link.md) + + You may be familiar with using `navigation.navigate` to navigate between screens. But it's important to avoid using it when supporting the web. Instead, use the `Link` or [`Button`](elements.md#button) components to navigate between screens. This ensures that an anchor tag is rendered which provides the expected behavior on the web. + +3. [**Server rendering**](server-rendering.md) + + 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. + +::: + +## Lazy loading screens + +By default, screen components are bundled in the main bundle. This can lead to a large bundle size if you have many screens. It's important to keep the bundle size small on the web for faster loading times. + +To reduce the bundle size, you can use [dynamic `import()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) with [`React.lazy`](https://react.dev/reference/react/lazy) to lazy load screens: + + + + +```js name="Lazy loading screens" snack +import { Suspense, lazy } from 'react'; + +const MyStack = createNativeStackNavigator({ + screenLayout: ({ children }) => ( + }>{children} + ), + screens: { + Home: { + component: lazy(() => import('./HomeScreen')), + }, + Profile: { + component: lazy(() => import('./ProfileScreen')), + }, + }, +}); +``` + + + + +```js name="Lazy loading screens" snack +import { Suspense, lazy } from 'react'; + +const HomeScreen = lazy(() => import('./HomeScreen')); +const ProfileScreen = lazy(() => import('./ProfileScreen')); + +function MyStack() { + return ( + ( + }>{children} + )} + > + + + + ); +} +``` + +:::warning + +Make sure to use `React.lazy` **outside** the component containing the navigator configuration. Otherwise, it will return a new component on each render, causing the [screen to be unmounted and remounted](troubleshooting.md#screens-are-unmountingremounting-during-navigation) every time the component rerenders. + +::: + + + + +This will split the screen components into separate chunks (depending on your bundler) which are loaded on-demand when the screen is rendered. This can significantly reduce the initial bundle size. + +In addition, you can use the [`screenLayout`](navigator.md#screen-layout) to wrap your screens in a [``](https://react.dev/reference/react/Suspense) boundary. The suspense fallback can be used to show a loading indicator and will be shown while the screen component is being loaded. + +## Web-specific behavior + +Some of the navigators have different behavior on the web compared to native platforms: + +1. [**Native Stack Navigator**](stack-navigator.md) + + Native Stack Navigator uses the platform's primitives to handle animations and gestures on native platforms. However, animations and gestures are not supported on the web. + +2. [**Stack Navigator**](stack-navigator.md) + + Stack Navigator uses [`react-native-gesture-handler`](https://docs.swmansion.com/react-native-gesture-handler/) to handle swipe gestures on native platforms. However, gestures are not supported on the web. + + In addition, screen transitions are disabled by default on the web. You can enable them by setting `animationEnabled: true` in the navigator's options. + +3. [**Drawer Navigator**](drawer-navigator.md) + + Drawer Navigator uses [`react-native-gesture-handler`](https://docs.swmansion.com/react-native-gesture-handler/) to handle swipe gestures and [`react-native-reanimated`](https://docs.swmansion.com/react-native-reanimated/) for animations on native platforms. However, gestures are not supported on the web, and animations are handled using CSS transitions. + +In addition, navigators render hyperlinks on the web when possible, such as in the drawer sidebar, tab bar, stack navigator's back button, etc. + +Since `react-native-gesture-handler` and `react-native-reanimated` are not used on the web, avoid importing them in your own code to reduce the bundle size unless you need them for your components. You can use `.native.js` or `.native.ts` extensions for code specific to native platforms. + +## Configuring hosting providers + +React Navigation is designed for Single Page Applications (SPAs). This usually means that the `index.html` file needs to be served for all routes. + +During development, the bundler such as Webpack or Metro automatically handles this. However, when deploying the site, you may need to configure redirects to ensure that the `index.html` file is served for all routes to avoid 404 errors. + +Here are instructions for some of the popular hosting providers: + +### Netlify + +To handle redirects on Netlify, add the following in the `netlify.toml` file at the root of your project: + +```toml +[[redirects]] + from = "/*" + to = "/index.html" + status = 200 +``` + +### Vercel + +To handle redirects on Vercel, add the following in the `vercel.json` file at the root of your project: + +```json +{ + "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] +} +``` + +### GitHub Pages + +GitHub Pages doesn't support such redirection configuration for SPAs. There are a couple of ways to work around this: + +- Rename your `index.html` to `404.html`. This will serve the `404.html` file for all routes. However, this will cause a 404 status code to be returned for all routes. So it's not ideal for SEO. +- Write a script that copies the `index.html` file to all routes in the build output. For example, if your app has routes `/`, `/about`, and `/contact`, you can copy the `index.html` file to `about.html` and `contact.html`. diff --git a/versioned_sidebars/version-8.x-sidebars.json b/versioned_sidebars/version-8.x-sidebars.json new file mode 100644 index 0000000000..2467400dbf --- /dev/null +++ b/versioned_sidebars/version-8.x-sidebars.json @@ -0,0 +1,111 @@ +{ + "docs": { + "Fundamentals": [ + "getting-started", + "hello-react-navigation", + "navigating", + "params", + "headers", + "header-buttons", + "nesting-navigators", + "navigation-lifecycle", + "next-steps" + ], + "Guides": [ + "auth-flow", + "handling-safe-area", + "customizing-tabbar", + "hiding-tabbar-in-screens", + "status-bar", + "modal", + "multiple-drawers", + "screen-options-resolution", + "custom-android-back-button-handling", + "shared-element-transitions", + "preventing-going-back", + "function-after-focusing-screen", + "navigating-without-navigation-prop", + "deep-linking", + "configuring-links", + "web-support", + "server-rendering", + "screen-tracking", + "themes", + "state-persistence", + "combine-static-with-dynamic", + "testing", + "typescript", + "troubleshooting", + "upgrading-from-6.x" + ], + "Navigators": [ + "stack-navigator", + "native-stack-navigator", + "bottom-tab-navigator", + "native-bottom-tab-navigator", + "drawer-navigator", + "material-top-tab-navigator" + ], + "Libraries": ["devtools", "elements", "tab-view", "drawer-layout"], + "API reference": [ + "static-configuration", + "navigation-container", + "server-container", + "navigator", + "group", + "screen", + "screen-options", + "route-object", + "navigation-object", + "navigation-context", + "navigation-events", + "navigation-state", + "link", + { + "type": "category", + "collapsible": true, + "label": "Hooks", + "items": [ + "use-navigation", + "use-route", + "use-navigation-state", + "use-focus-effect", + "use-is-focused", + "use-prevent-remove", + "use-route-path", + "use-link-to", + "use-link-props", + "use-link-builder", + "use-scroll-to-top", + "use-theme" + ] + }, + { + "type": "category", + "collapsible": true, + "label": "Actions", + "items": [ + "navigation-actions", + "stack-actions", + "drawer-actions", + "tab-actions" + ] + } + ], + "Build your own Navigator": ["custom-routers", "custom-navigators"], + "Ecosystem": [ + "community-solutions", + "community-navigators", + "community-libraries", + "more-resources" + ], + "Meta": [ + "migration-guides", + "glossary-of-terms", + "pitch", + "limitations", + "used-by", + "contributing" + ] + } +} diff --git a/versions.json b/versions.json index 9aca05ea13..efdafab2ca 100644 --- a/versions.json +++ b/versions.json @@ -1 +1 @@ -["7.x", "6.x", "5.x", "4.x", "3.x", "2.x", "1.x"] +["8.x", "7.x", "6.x", "5.x", "4.x", "3.x", "2.x", "1.x"] From 32d28c3f07b6230bfb6687556eefcd158999f91e Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Thu, 4 Dec 2025 14:27:47 +0100 Subject: [PATCH 7/7] Update docs for React Navigation 8 --- .github/workflows/ci.yml | 3 + __tests__/rehype-static-to-dynamic.test.mjs | 1002 +++++++++++++++ blog/2025-12-03-react-navigation-8.0-alpha.md | 224 ++++ package.json | 3 + src/components/Pre.js | 10 +- src/plugins/rehype-static-to-dynamic.mjs | 1129 +++++++++++------ versioned_docs/version-7.x/headers.md | 335 +---- .../version-7.x/hello-react-navigation.md | 4 - versioned_docs/version-7.x/navigating.md | 10 +- versioned_docs/version-7.x/params.md | 8 +- .../version-8.x/bottom-tab-navigator.md | 400 +++++- .../version-8.x/configuring-links.md | 35 +- .../version-8.x/custom-navigators.md | 99 +- .../version-8.x/drawer-navigator.md | 12 +- versioned_docs/version-8.x/elements.md | 87 +- .../function-after-focusing-screen.md | 39 +- versioned_docs/version-8.x/getting-started.md | 7 +- versioned_docs/version-8.x/header-buttons.md | 141 +- versioned_docs/version-8.x/headers.md | 364 +----- .../version-8.x/hello-react-navigation.md | 316 +---- .../version-8.x/migration-guides.md | 1 + .../version-8.x/multiple-drawers.md | 164 +-- .../native-bottom-tab-navigator.md | 445 ------- .../version-8.x/native-stack-navigator.md | 49 +- versioned_docs/version-8.x/navigating.md | 78 +- .../version-8.x/navigation-actions.md | 113 ++ .../version-8.x/navigation-events.md | 2 +- .../version-8.x/navigation-lifecycle.md | 347 +---- .../version-8.x/navigation-object.md | 71 +- versioned_docs/version-8.x/navigator.md | 54 +- .../version-8.x/nesting-navigators.md | 110 +- versioned_docs/version-8.x/params.md | 74 +- versioned_docs/version-8.x/screen-options.md | 54 +- .../version-8.x/static-configuration.md | 36 + versioned_docs/version-8.x/tab-view.md | 72 +- versioned_docs/version-8.x/themes.md | 44 + versioned_docs/version-8.x/typescript.md | 340 +++-- .../version-8.x/upgrading-from-6.x.md | 1023 --------------- .../version-8.x/upgrading-from-7.x.md | 660 ++++++++++ .../version-8.x/use-focus-effect.md | 12 +- .../version-8.x/use-navigation-state.md | 84 +- versioned_docs/version-8.x/use-navigation.md | 24 +- versioned_docs/version-8.x/use-route.md | 26 +- versioned_sidebars/version-8.x-sidebars.json | 3 +- yarn.lock | 405 +++++- 45 files changed, 4693 insertions(+), 3826 deletions(-) create mode 100644 __tests__/rehype-static-to-dynamic.test.mjs create mode 100644 blog/2025-12-03-react-navigation-8.0-alpha.md delete mode 100755 versioned_docs/version-8.x/native-bottom-tab-navigator.md delete mode 100755 versioned_docs/version-8.x/upgrading-from-6.x.md create mode 100755 versioned_docs/version-8.x/upgrading-from-7.x.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f81b36a2e2..b39b448ff4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,5 +17,8 @@ jobs: - name: Lint markdown run: yarn markdownlint-cli2 + - name: Run tests + run: yarn test + - name: Build pages run: yarn build diff --git a/__tests__/rehype-static-to-dynamic.test.mjs b/__tests__/rehype-static-to-dynamic.test.mjs new file mode 100644 index 0000000000..5f9a30afaa --- /dev/null +++ b/__tests__/rehype-static-to-dynamic.test.mjs @@ -0,0 +1,1002 @@ +import dedent from 'dedent'; +import assert from 'node:assert'; +import { describe, test } from 'node:test'; +import rehypeStaticToDynamic from '../src/plugins/rehype-static-to-dynamic.mjs'; + +/** + * Helper function to create a test tree structure + */ +function createTestTree(code) { + return { + type: 'root', + children: [ + { + type: 'element', + tagName: 'pre', + children: [ + { + type: 'element', + tagName: 'code', + data: { meta: 'static2dynamic' }, + children: [ + { + type: 'text', + value: code, + }, + ], + }, + ], + }, + ], + }; +} + +/** + * Helper function to extract the transformed code from the tree + */ +function extractTransformedCode(tree) { + // After transformation, the tree should have TabItem elements + const tabsElement = tree.children[0]; + + if (!tabsElement || tabsElement.tagName !== 'Tabs') { + throw new Error('Expected Tabs element not found'); + } + + // Find the "Dynamic" tab + const dynamicTab = tabsElement.children.find( + (child) => + child.type === 'element' && + child.tagName === 'TabItem' && + child.properties?.value === 'dynamic' + ); + + if (!dynamicTab) { + throw new Error('Dynamic tab not found'); + } + + // Extract the code from the dynamic tab + const preElement = dynamicTab.children.find( + (child) => child.type === 'element' && child.tagName === 'pre' + ); + + if (!preElement) { + throw new Error('Pre element not found in dynamic tab'); + } + + const codeElement = preElement.children.find( + (child) => child.type === 'element' && child.tagName === 'code' + ); + + if (!codeElement) { + throw new Error('Code element not found'); + } + + const textNode = codeElement.children.find((child) => child.type === 'text'); + + return textNode?.value || ''; +} + +describe('rehype-static-to-dynamic', () => { + test('basic screen transformation', async () => { + const input = dedent /* javascript */ ` + import { createNativeStackNavigator } from '@react-navigation/native-stack'; + import { createStaticNavigation } from '@react-navigation/native'; + + const RootStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + Details: DetailsScreen, + }, + }); + + const Navigation = createStaticNavigation(RootStack); + + export default function App() { + return ; + } + `; + + const tree = createTestTree(input); + const plugin = rehypeStaticToDynamic(); + await plugin(tree); + + const output = extractTransformedCode(tree); + + const expected = dedent /* javascript */ ` + import { createNativeStackNavigator } from '@react-navigation/native-stack'; + import { NavigationContainer } from '@react-navigation/native'; + + const Stack = createNativeStackNavigator(); + + function RootStack() { + return ( + + + + + ); + } + + export default function App() { + return ( + + + + ); + } + `; + + assert.strictEqual(output, expected); + }); + + test('screen with options', async () => { + const input = dedent /* javascript */ ` + import { createNativeStackNavigator, createNativeStackScreen } from '@react-navigation/native-stack'; + import { createStaticNavigation } from '@react-navigation/native'; + + const RootStack = createNativeStackNavigator({ + screens: { + Home: createNativeStackScreen({ + screen: HomeScreen, + options: { title: 'My Home' }, + }), + }, + }); + + const Navigation = createStaticNavigation(RootStack); + + export default function App() { + return ; + } + `; + + const tree = createTestTree(input); + const plugin = rehypeStaticToDynamic(); + await plugin(tree); + + const output = extractTransformedCode(tree); + + const expected = dedent /* javascript */ ` + import { createNativeStackNavigator } from '@react-navigation/native-stack'; + import { NavigationContainer } from '@react-navigation/native'; + + const Stack = createNativeStackNavigator(); + + function RootStack() { + return ( + + + + ); + } + + export default function App() { + return ( + + + + ); + } + `; + + assert.strictEqual(output, expected); + }); + + test('highlight comment on screen property', async () => { + const input = dedent /* javascript */ ` + import { createNativeStackNavigator, createNativeStackScreen } from '@react-navigation/native-stack'; + import { createStaticNavigation } from '@react-navigation/native'; + + const MyStack = createNativeStackNavigator({ + screens: { + Home: createNativeStackScreen({ + // highlight-next-line + screen: HomeScreen, + options: { title: 'My Home' }, + }), + Details: createNativeStackScreen({ + screen: DetailsScreen, + }), + }, + }); + + const Navigation = createStaticNavigation(MyStack); + + export default function App() { + return ; + } + `; + + const tree = createTestTree(input); + const plugin = rehypeStaticToDynamic(); + await plugin(tree); + + const output = extractTransformedCode(tree); + + const expected = dedent /* javascript */ ` + import { createNativeStackNavigator } from '@react-navigation/native-stack'; + import { NavigationContainer } from '@react-navigation/native'; + + const Stack = createNativeStackNavigator(); + + function MyStack() { + return ( + + + + + ); + } + + export default function App() { + return ( + + + + ); + } + `; + + assert.strictEqual(output, expected); + }); + + test('highlight comment on screen options', async () => { + const input = dedent /* javascript */ ` + import { createNativeStackNavigator, createNativeStackScreen } from '@react-navigation/native-stack'; + import { createStaticNavigation } from '@react-navigation/native'; + + const MyStack = createNativeStackNavigator({ + screens: { + Home: createNativeStackScreen({ + screen: HomeScreen, + // highlight-start + options: { + title: 'My Home' + }, + // highlight-end + }), + }, + }); + + const Navigation = createStaticNavigation(MyStack); + + export default function App() { + return ; + } + `; + + const tree = createTestTree(input); + const plugin = rehypeStaticToDynamic(); + await plugin(tree); + + const output = extractTransformedCode(tree); + + const expected = dedent /* javascript */ ` + import { createNativeStackNavigator } from '@react-navigation/native-stack'; + import { NavigationContainer } from '@react-navigation/native'; + + const Stack = createNativeStackNavigator(); + + function MyStack() { + return ( + + + + ); + } + + export default function App() { + return ( + + + + ); + } + `; + + assert.strictEqual(output, expected); + }); + + test('highlight-start and highlight-end comments', async () => { + const input = dedent /* javascript */ ` + import { createNativeStackNavigator, createNativeStackScreen } from '@react-navigation/native-stack'; + import { createStaticNavigation } from '@react-navigation/native'; + + const MyStack = createNativeStackNavigator({ + screens: { + // highlight-start + Home: createNativeStackScreen({ + screen: HomeScreen, + options: { title: 'My Home' }, + }), + // highlight-end + Details: createNativeStackScreen({ + screen: DetailsScreen, + }), + }, + }); + + const Navigation = createStaticNavigation(MyStack); + + export default function App() { + return ; + } + `; + + const tree = createTestTree(input); + const plugin = rehypeStaticToDynamic(); + await plugin(tree); + + const output = extractTransformedCode(tree); + + const expected = dedent /* javascript */ ` + import { createNativeStackNavigator } from '@react-navigation/native-stack'; + import { NavigationContainer } from '@react-navigation/native'; + + const Stack = createNativeStackNavigator(); + + function MyStack() { + return ( + + // highlight-start + + // highlight-end + + + ); + } + + export default function App() { + return ( + + + + ); + } + `; + + assert.strictEqual(output, expected); + }); + + test('navigator props with highlight', async () => { + const input = dedent /* javascript */ ` + import { createNativeStackNavigator, createNativeStackScreen } from '@react-navigation/native-stack'; + import { createStaticNavigation } from '@react-navigation/native'; + + const RootStack = createNativeStackNavigator({ + // highlight-next-line + initialRouteName: 'Home', + screens: { + Home: createNativeStackScreen({ + screen: HomeScreen, + }), + Details: createNativeStackScreen({ + screen: DetailsScreen, + }), + }, + }); + + const Navigation = createStaticNavigation(RootStack); + + export default function App() { + return ; + } + `; + + const tree = createTestTree(input); + const plugin = rehypeStaticToDynamic(); + await plugin(tree); + + const output = extractTransformedCode(tree); + + const expected = dedent /* javascript */ ` + import { createNativeStackNavigator } from '@react-navigation/native-stack'; + import { NavigationContainer } from '@react-navigation/native'; + + const Stack = createNativeStackNavigator(); + + function RootStack() { + return ( + // highlight-next-line + + + + + ); + } + + export default function App() { + return ( + + + + ); + } + `; + + assert.strictEqual(output, expected); + }); + + test('multiple screens with mixed comments', async () => { + const input = dedent /* javascript */ ` + import { createNativeStackNavigator, createNativeStackScreen } from '@react-navigation/native-stack'; + import { createStaticNavigation } from '@react-navigation/native'; + + const MyStack = createNativeStackNavigator({ + screens: { + // highlight-start + Home: createNativeStackScreen({ + screen: HomeScreen, + }), + Profile: createNativeStackScreen({ + screen: ProfileScreen, + options: { title: 'User Profile' }, + }), + // highlight-end + Details: createNativeStackScreen({ + screen: DetailsScreen, + }), + }, + }); + + const Navigation = createStaticNavigation(MyStack); + + export default function App() { + return ; + } + `; + + const tree = createTestTree(input); + const plugin = rehypeStaticToDynamic(); + await plugin(tree); + + const output = extractTransformedCode(tree); + + const expected = dedent /* javascript */ ` + import { createNativeStackNavigator } from '@react-navigation/native-stack'; + import { NavigationContainer } from '@react-navigation/native'; + + const Stack = createNativeStackNavigator(); + + function MyStack() { + return ( + + // highlight-start + + + // highlight-end + + + ); + } + + export default function App() { + return ( + + + + ); + } + `; + + assert.strictEqual(output, expected); + }); + + test('groups with screens', async () => { + const input = dedent /* javascript */ ` + import { createNativeStackNavigator, createNativeStackScreen } from '@react-navigation/native-stack'; + import { createStaticNavigation } from '@react-navigation/native'; + + const RootStack = createNativeStackNavigator({ + groups: { + App: { + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, + }, + Modal: { + screenOptions: { presentation: 'modal' }, + screens: { + Settings: SettingsScreen, + }, + }, + }, + }); + + const Navigation = createStaticNavigation(RootStack); + + export default function App() { + return ; + } + `; + + const tree = createTestTree(input); + const plugin = rehypeStaticToDynamic(); + await plugin(tree); + + const output = extractTransformedCode(tree); + + const expected = dedent /* javascript */ ` + import { createNativeStackNavigator } from '@react-navigation/native-stack'; + import { NavigationContainer } from '@react-navigation/native'; + + const Stack = createNativeStackNavigator(); + + function RootStack() { + return ( + + + + + + + + + + ); + } + + export default function App() { + return ( + + + + ); + } + `; + + assert.strictEqual(output, expected); + }); + + test('listeners property with highlight', async () => { + const input = dedent /* javascript */ ` + import { createNativeStackNavigator, createNativeStackScreen } from '@react-navigation/native-stack'; + import { createStaticNavigation } from '@react-navigation/native'; + + const MyStack = createNativeStackNavigator({ + screens: { + Home: createNativeStackScreen({ + screen: HomeScreen, + // highlight-start + listeners: { + focus: () => console.log('focused'), + }, + // highlight-end + }), + }, + }); + + const Navigation = createStaticNavigation(MyStack); + + export default function App() { + return ; + } + `; + + const tree = createTestTree(input); + const plugin = rehypeStaticToDynamic(); + await plugin(tree); + + const output = extractTransformedCode(tree); + + const expected = dedent /* javascript */ ` + import { createNativeStackNavigator } from '@react-navigation/native-stack'; + import { NavigationContainer } from '@react-navigation/native'; + + const Stack = createNativeStackNavigator(); + + function MyStack() { + return ( + + console.log('focused'), + }} + // highlight-end + /> + + ); + } + + export default function App() { + return ( + + + + ); + } + `; + + assert.strictEqual(output, expected); + }); + + test('block comments', async () => { + const input = dedent /* javascript */ ` + import { createNativeStackNavigator, createNativeStackScreen } from '@react-navigation/native-stack'; + import { createStaticNavigation } from '@react-navigation/native'; + + const MyStack = createNativeStackNavigator({ + screens: { + /* some comment */ + Home: createNativeStackScreen({ + screen: HomeScreen, + }), + }, + }); + + const Navigation = createStaticNavigation(MyStack); + + export default function App() { + return ; + } + `; + + const tree = createTestTree(input); + const plugin = rehypeStaticToDynamic(); + await plugin(tree); + + const output = extractTransformedCode(tree); + + const expected = dedent /* javascript */ ` + import { createNativeStackNavigator } from '@react-navigation/native-stack'; + import { NavigationContainer } from '@react-navigation/native'; + + const Stack = createNativeStackNavigator(); + + function MyStack() { + return ( + + {/* some comment */} + + + ); + } + + export default function App() { + return ( + + + + ); + } + `; + + assert.strictEqual(output, expected); + }); + + test('screenOptions on navigator', async () => { + const input = dedent /* javascript */ ` + import { createNativeStackNavigator } from '@react-navigation/native-stack'; + import { createStaticNavigation } from '@react-navigation/native'; + + const RootStack = createNativeStackNavigator({ + // highlight-start + screenOptions: { + headerShown: false, + }, + // highlight-end + screens: { + Home: HomeScreen, + }, + }); + + const Navigation = createStaticNavigation(RootStack); + + export default function App() { + return ; + } + `; + + const tree = createTestTree(input); + const plugin = rehypeStaticToDynamic(); + await plugin(tree); + + const output = extractTransformedCode(tree); + + const expected = dedent /* javascript */ ` + import { createNativeStackNavigator } from '@react-navigation/native-stack'; + import { NavigationContainer } from '@react-navigation/native'; + + const Stack = createNativeStackNavigator(); + + function RootStack() { + return ( + + + + ); + } + + export default function App() { + return ( + + + + ); + } + `; + + assert.strictEqual(output, expected); + }); + + test('options callback', async () => { + const input = dedent /* javascript */ ` + import { createNativeStackNavigator, createNativeStackScreen } from '@react-navigation/native-stack'; + import { createStaticNavigation } from '@react-navigation/native'; + + const MyStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + // highlight-start + options: ({ route }) => ({ + title: route.params?.name, + }), + // highlight-end + }, + }, + }); + + const Navigation = createStaticNavigation(MyStack); + + export default function App() { + return ; + } + `; + + const tree = createTestTree(input); + const plugin = rehypeStaticToDynamic(); + await plugin(tree); + + const output = extractTransformedCode(tree); + + const expected = dedent /* javascript */ ` + import { createNativeStackNavigator } from '@react-navigation/native-stack'; + import { NavigationContainer } from '@react-navigation/native'; + + const Stack = createNativeStackNavigator(); + + function MyStack() { + return ( + + ({ + title: route.params?.name, + })} + // highlight-end + /> + + ); + } + + export default function App() { + return ( + + + + ); + } + `; + + assert.strictEqual(output, expected); + }); + + test('options callback with createNativeStackScreen', async () => { + const input = dedent /* javascript */ ` + import { createNativeStackNavigator, createNativeStackScreen } from '@react-navigation/native-stack'; + import { createStaticNavigation } from '@react-navigation/native'; + + const MyStack = createNativeStackNavigator({ + screens: { + Home: createNativeStackScreen({ + screen: HomeScreen, + // highlight-start + options: ({ route }) => ({ + title: route.params?.name, + }), + // highlight-end + }), + }, + }); + + const Navigation = createStaticNavigation(MyStack); + + export default function App() { + return ; + } + `; + + const tree = createTestTree(input); + const plugin = rehypeStaticToDynamic(); + await plugin(tree); + + const output = extractTransformedCode(tree); + + const expected = dedent /* javascript */ ` + import { createNativeStackNavigator } from '@react-navigation/native-stack'; + import { NavigationContainer } from '@react-navigation/native'; + + const Stack = createNativeStackNavigator(); + + function MyStack() { + return ( + + ({ + title: route.params?.name, + })} + // highlight-end + /> + + ); + } + + export default function App() { + return ( + + + + ); + } + `; + + assert.strictEqual(output, expected); + }); + + test('drawer navigator', async () => { + const input = dedent /* javascript */ ` + import { createDrawerNavigator } from '@react-navigation/drawer'; + import { createStaticNavigation } from '@react-navigation/native'; + + const Drawer = createDrawerNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, + }); + + const Navigation = createStaticNavigation(Drawer); + + export default function App() { + return ; + } + `; + + const tree = createTestTree(input); + const plugin = rehypeStaticToDynamic(); + await plugin(tree); + + const output = extractTransformedCode(tree); + + const expected = dedent /* javascript */ ` + import { createDrawerNavigator } from '@react-navigation/drawer'; + import { NavigationContainer } from '@react-navigation/native'; + + const Drawer = createDrawerNavigator(); + + function Drawer() { + return ( + + + + + ); + } + + export default function App() { + return ( + + + + ); + } + `; + + assert.strictEqual(output, expected); + }); + + test('mixed screen definitions', async () => { + const input = dedent /* javascript */ ` + import { createNativeStackNavigator, createNativeStackScreen } from '@react-navigation/native-stack'; + import { createStaticNavigation } from '@react-navigation/native'; + + const MyStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + Profile: createNativeStackScreen({ + screen: ProfileScreen, + options: { title: 'User Profile' }, + }), + Settings: SettingsScreen, + }, + }); + + const Navigation = createStaticNavigation(MyStack); + + export default function App() { + return ; + } + `; + + const tree = createTestTree(input); + const plugin = rehypeStaticToDynamic(); + await plugin(tree); + + const output = extractTransformedCode(tree); + + const expected = dedent /* javascript */ ` + import { createNativeStackNavigator } from '@react-navigation/native-stack'; + import { NavigationContainer } from '@react-navigation/native'; + + const Stack = createNativeStackNavigator(); + + function MyStack() { + return ( + + + + + + ); + } + + export default function App() { + return ( + + + + ); + } + `; + + assert.strictEqual(output, expected); + }); +}); diff --git a/blog/2025-12-03-react-navigation-8.0-alpha.md b/blog/2025-12-03-react-navigation-8.0-alpha.md new file mode 100644 index 0000000000..afeee5c166 --- /dev/null +++ b/blog/2025-12-03-react-navigation-8.0-alpha.md @@ -0,0 +1,224 @@ +--- +title: React Navigation 8.0 Alpha +authors: satya +tags: [announcement] +--- + +We're excited to announce the first alpha release of React Navigation 8.0. + +This release focuses on improved TypeScript types for static configuration, native bottom tabs as the default, and various other improvements and new features. And there are many more improvements planned for the final release. + + + +You can read the full list of changes in the [upgrade guide](/docs/8.x/upgrading-from-7.x). Here are some highlights: + +## Highlights + +### Native Bottom Tabs by default + +The Bottom Tab Navigator now uses native implementations by default on iOS and Android based on [`react-native-screens`](https://github.com/software-mansion/react-native-screens). This lets us to support the new liquid glass effect on iOS 26 and provide a more native feel by default. Making it default also helps with adoption. + + + + + +We also include a custom JS based implementation in order to support Web and more customization options. You can switch to the JS implementation by passing the `implementation` prop to the navigator. + +See [Bottom Tab Navigator docs](/docs/8.x/bottom-tab-navigator) for more details. + +### Ability to get `route`, `navigation`, and state for any parent screen + +Hooks such as `useRoute`, `useNavigation`, and `useNavigationState` now accept a screen to get the corresponding `route`, `navigation`, and state respectively for any parent screen The `navigation` object already had a `getParent` method, so this is not a new capability for `useNavigation`, but it was not possible for `useRoute` and `useNavigationState` before. + +One of the commonly requested features has been for screens to be able to access the params for parent screens, but this had a few problems: + +- Passing down params to child screens may lead to unnecessary re-renders when the parent params change, even when they are not needed by the child screen. +- Since the param types are defined by the screen itself, having additional parent params would not be compatible with the existing type system. + +It was necessary to manually setup React Context to pass down parent params, which was cumbersome. + +The screen name parameter in `useRoute` solves these problems. Now, you can access the parent route and its params directly by specifying the screen name: + +```js +const route = useRoute('Profile'); + +console.log(route.params); // Params for the 'Profile' screen +``` + +Similarly, you can get the `navigation` object for any parent screen: + +```js +const navigation = useNavigation('Profile'); + +console.log(navigation); // Navigation object for the 'Profile' screen +``` + +And you can get the navigation state for any parent screen: + +```js +const focusedRoute = useNavigationState( + 'Profile', + (state) => state.routes[state.index] +); + +console.log(focusedRoute); // Focused route for the navigator which contains the 'Profile' screen +``` + +See [`useRoute`](/docs/8.x/use-route), [`useNavigation`](/docs/8.x/use-navigation), and [`useNavigationState`](/docs/8.x/use-navigation-state) for more details. + +### Better TypeScript types for static configuration + +In React Navigation 7, we introduced a static API for configuring navigators in order to improve deep linking and add automatic type inference. However, the type inference was limited and did not express the full capabilities of React Navigation without manual type annotations in some cases. We've reworked the types to solve many of these issues. + +Hooks like `useNavigation`, `useRoute`, and `useNavigationState` now automatically infer types based on the provided screen name: + +```js +const navigation = useNavigation('Profile'); + +// navigation is correctly typed as StackNavigationProp +``` + +The `navigation` object will now have proper types based on navigator nesting, and will include navigator specific methods such as `openDrawer` for drawer navigators or `push` for stack navigators without requiring manual type annotations. + +Similarly, `useRoute` will return the correct route type with properly typed params: + +```js +const route = useRoute('Profile'); + +// route is correctly typed as RouteProp +``` + +And `useNavigationState` will infer the correct state type for the specified screen: + +```js +const focusedRoute = useNavigationState( + 'Profile', + (state) => state.routes[state.index] +); + +// state is correctly typed as StackNavigationState +``` + +In addition, previously, the type of `route` object can't be inferred in screen callback, listeners callback etc. This made it difficult to use route params in these callbacks. + +The new `createXScreen` helper functions addresses this: + +```js +const Stack = createStackNavigator({ + screens: { + Profile: createStackScreen({ + screen: ProfileScreen, + options: ({ route }) => { + const userId = route.params.userId; + + return { + title: `${userId}'s profile` + }; + }, + }); + } +}); +``` + +Here, the type of `route.params` is correctly inferred based on the type annotation of `ProfileScreen`. + +Not only that, but it also infers types based on the path pattern in the `linking` configuration specified for the screen: + +```js +const Stack = createStackNavigator({ + screens: { + Profile: createStackScreen({ + screen: ProfileScreen, + linking: { + path: 'profile/:userId', + parse: { + userId: (userId) => Number(userId), + }, + }, + }); + } +}); +``` + +In this case, React Navigation can automatically infer that `userId` is a param of type `number` based on `:userId` in the path pattern and the return type of `userId` in the `parse` config. This is inspired by how [TanStack Router infers types based on the URL pattern](https://tanstack.com/router/latest/docs/framework/solid/decisions-on-dx#declaring-the-router-instance-for-type-inference). + +Each navigator exports its own helper function, e.g. `createNativeStackScreen` for Native Stack Navigator, `createBottomTabScreen` for Bottom Tab Navigator, `createDrawerScreen` for Drawer Navigator etc. + +See [TypeScript docs](/docs/8.x/typescript) and [Static configuration docs](/docs/8.x/static-configuration) for more details. + +### Pushing history entries without pushing new screens + +One of the things React Navigation lacked was to add a new history entry without pushing a new screen. But this is not always desirable, as pushing a new screen adds an entirely new instance of the screen component, and shows transition animations. + +This is useful for many scenarios: + +- A product listing page with filters, where changing filters should create a new history entry so that users can go back to previous filter states. +- A screen with a custom modal component, where the modal is not a separate screen in the navigator, but its state should be reflected in the URL and history. + +Especially on the web, users expect that changing certain UI states should create a new history entry, so that they can use the browser back and forward buttons to navigate through these states. + +The new `pushParams` API makes this possible. You can now push an entry to the history stack by adding new params without needing to push a new screen. + +See [`pushParams` docs](/docs/8.x/navigation-object#pushparams) for more details. + +### Replacing params + +Previously, the only way to update screen params was via the `setParams` action, it took an object containing the params and merged them with the existing params. But removing params required setting them to `undefined`, which was not very intuitive. + +The new `replaceParams` action replaces params entirely instead of merging them. This makes it easier to use a new set of params without needing to worry about removing old params. + +See [`replaceParams` docs](/docs/8.x/navigation-object#replaceparams) for more details. + +### Support for `PlatformColor`, `DynamicColorIOS` and CSS custom properties in theme colors + +React Navigation has its own theming system to change styling for the built-in components. Previously, it only supported string color values. In this release, we've added support for platform-specific dynamic colors such as `PlatformColor` and `DynamicColorIOS` on native, as well as CSS custom properties on the web. + +This makes it easier to use system colors as well as share colors across native components and React Navigation components. + +```js +const MyTheme = { + ...DefaultTheme, + colors: Platform.select({ + ios: () => ({ + primary: PlatformColor('systemRed'), + background: PlatformColor('systemGroupedBackground'), + // ... + }), + android: () => ({ + primary: PlatformColor('@android:color/system_primary_light'), + // ... + }), + default: () => DefaultTheme.colors, + })(), +}; +``` + +This comes with one limitation: with string colors, React Navigation can automatically adjust colors in some scenarios (e.g. adjust the text color based on background color), which is not possible with dynamic colors. So it will fallback to pre-defined colors according to the theme in these cases. + +See [Themes docs](/docs/8.x/themes) for more details. + +## Try it out + +If you'd like to try it out, add `@alpha` to the package you're installing. For example: + +```sh npm2yarn +npm install @react-navigation/native@alpha @react-navigation/bottom-tabs@alpha +``` + +Your feedback is very important to us to ensure a smooth final release. If you encounter any issues or have any feedback or suggestions, please let us know on [GitHub issues](https://github.com/react-navigation/react-navigation/issues) or our [GitHub Discussions forum](https://github.com/react-navigation/react-navigation/discussions). + +## Special thanks + +React Navigation 8 would not have been possible without our amazing contributors. + +Thanks a lot to [Michał Osadnik](https://x.com/mosdnk), [Kacper Kafara](https://x.com/kafara_kacper), [Krzystof Ligarski](https://github.com/kligarski), [Tomasz Boroń](https://github.com/t0maboro), [Konrad Michalik](https://github.com/kmichalikk), [Oskar Kwaśniewski](https://github.com/okwasniewski) and many others for their contributions to this release. + +## Sponsor us + +If React Navigation helps you to deliver value to your customers, it'd mean a lot if you could sponsor us. Sponsorships will help us to move more quickly towards our goal of building the best cross-platform navigation library and continue to provide timely support for bug reports in our GitHub issues. + +👉 [Visit our GitHub Sponsors page](https://github.com/sponsors/react-navigation) 👈 diff --git a/package.json b/package.json index 833fb6dfc0..9a66d24a6c 100755 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "serve": "docusaurus serve", "swizzle": "docusaurus swizzle", "deploy": "DEPLOYMENT_BRANCH=gh-pages docusaurus deploy", + "test": "node --test --test-reporter=@voxpelli/node-test-pretty-reporter", "crowdin-upload": "crowdin upload sources --auto-update -b main", "crowdin-download": "crowdin download -b main", "fetch-sponsors": "node scripts/fetch-sponsors.js && prettier --write src/data/sponsors.js" @@ -22,6 +23,7 @@ "@docusaurus/remark-plugin-npm2yarn": "3.9.2", "@octokit/graphql": "^9.0.3", "@react-navigation/core": "^7.13.5", + "dedent": "^1.7.0", "escape-html": "^1.0.3", "mkdirp": "^3.0.1", "netlify-plugin-cache": "^1.0.3", @@ -33,6 +35,7 @@ "devDependencies": { "@babel/types": "^7.28.5", "@ffprobe-installer/ffprobe": "^2.1.2", + "@voxpelli/node-test-pretty-reporter": "^1.1.2", "markdownlint": "^0.40.0", "markdownlint-cli2": "^0.19.1", "prettier": "^3.7.4", diff --git a/src/components/Pre.js b/src/components/Pre.js index 227c15c070..aaeb07c9c5 100644 --- a/src/components/Pre.js +++ b/src/components/Pre.js @@ -2,9 +2,9 @@ import { useActiveVersion } from '@docusaurus/plugin-content-docs/client'; import { useColorMode } from '@docusaurus/theme-common'; import { usePluginData } from '@docusaurus/useGlobalData'; import MDXPre from '@theme-original/MDXComponents/Pre'; -import React from 'react'; -import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; +import Tabs from '@theme/Tabs'; +import React from 'react'; const SUPPORTED_TABS = { config: [ @@ -152,7 +152,11 @@ export default function Pre({ const version = activeVersion?.name; if (version == null || versions[version] == null) { - throw new Error(`Invalid version: ${version}`); + console.warn( + `No version information found for version "${version}", cannot resolve Snack dependencies automatically.` + ); + + return {children}; } Object.assign( diff --git a/src/plugins/rehype-static-to-dynamic.mjs b/src/plugins/rehype-static-to-dynamic.mjs index 46ca91ca70..ffb855a3d9 100644 --- a/src/plugins/rehype-static-to-dynamic.mjs +++ b/src/plugins/rehype-static-to-dynamic.mjs @@ -1,6 +1,17 @@ import * as t from '@babel/types'; +import { readFileSync } from 'fs'; +import { dirname, join } from 'path'; +import * as prettier from 'prettier'; import * as recast from 'recast'; +import * as babelParser from 'recast/parsers/babel-ts.js'; import { visit } from 'unist-util-visit'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const prettierConfig = JSON.parse( + readFileSync(join(__dirname, '..', '..', '.prettierrc.json'), 'utf-8') +); /** * Plugin to automatically convert static config examples to dynamic config @@ -9,7 +20,10 @@ import { visit } from 'unist-util-visit'; * corresponding dynamic configuration examples wrapped in tabs. */ export default function rehypeStaticToDynamic() { - return (tree) => { + return async (tree) => { + const promises = []; + const replacements = []; + visit(tree, 'element', (node, index, parent) => { // Look for code blocks with static2dynamic in meta if ( @@ -34,29 +48,69 @@ export default function rehypeStaticToDynamic() { ); } - const dynamicCode = convertStaticToDynamic(code); - const tabsElement = createTabsWithBothConfigs(code, dynamicCode, node); - - // Replace the current pre element with the tabs - parent.children[index] = tabsElement; + // Queue async conversion + const promise = convertStaticToDynamic(code).then((dynamicCode) => { + const tabsElement = createTabsWithBothConfigs( + code, + dynamicCode, + node + ); + replacements.push({ parent, index, tabsElement }); + }); + promises.push(promise); } }); + + // Wait for all conversions to complete + await Promise.all(promises); + + // Apply all replacements + replacements.forEach(({ parent, index, tabsElement }) => { + parent.children[index] = tabsElement; + }); }; } /** - * Convert static config code to dynamic config code + * Convert static config code to dynamic config code. + * + * Strategy: + * 1. Parse code to AST with comment attachment + * 2. First pass: Transform imports and collect navigator information + * - Remove createStaticNavigation and createXScreen imports + * - Add NavigationContainer import if needed + * - Collect navigator declarations and their comments + * 3. Second pass: Transform navigator declarations + * - Create const Stack = createStackNavigator() declarations + * - Create function components with JSX (Stack.Navigator, Stack.Screen) + * - Track comments for later injection + * 4. Format with Prettier + * 5. Post-process: Inject comments into formatted code + * - Comments are injected as strings since Prettier may move/remove AST comments */ -function convertStaticToDynamic(code) { - // Parse the code into AST using recast +async function convertStaticToDynamic(code) { + // Parse the code into AST using recast with comment attachment enabled const ast = recast.parse(code, { - parser: require('recast/parsers/babel-ts'), + parser: { + parse(source, options) { + return babelParser.parse(source, { + ...options, + tokens: true, + attachComment: true, + }); + }, + }, }); let navigatorInfos = []; let staticNavigationIndices = []; - // First pass: collect information and transform imports + // Track comments throughout the transformation + // We collect comments from the AST during transformation, then inject them + // after Prettier formatting (since Prettier may reformat/move AST comments) + const commentTracking = new Set(); + + // First pass: Collect navigator info and transform imports recast.visit(ast, { visitImportDeclaration(path) { const source = path.node.source.value; @@ -104,6 +158,22 @@ function convertStaticToDynamic(code) { } } + // Remove createXScreen imports from navigator packages + // e.g., createNativeStackScreen from @react-navigation/native-stack + if (source.startsWith('@react-navigation/')) { + path.node.specifiers = path.node.specifiers.filter((spec) => { + if (t.isImportSpecifier(spec)) { + const importedName = spec.imported.name; + // Remove imports that match createXScreen pattern + return !( + importedName.startsWith('create') && + importedName.endsWith('Screen') + ); + } + return true; + }); + } + this.traverse(path); }, @@ -160,7 +230,7 @@ function convertStaticToDynamic(code) { navigatorInfos.length > 0 ) { // Preserve any props passed to Navigation - const navigationProps = path.node.openingElement.attributes || []; + const navigationProps = path.node.openingElement.attributes; // Use the last navigator (which is passed to createStaticNavigation) const mainNavigator = navigatorInfos[navigatorInfos.length - 1]; @@ -197,8 +267,10 @@ function convertStaticToDynamic(code) { }, }); - // Second pass: manually transform the AST body - // Process all navigators + // Second pass: Transform navigator declarations into const + function components + // Example: const MyStack = createStackNavigator({ screens: {...} }) + // becomes: const Stack = createStackNavigator(); + // function MyStack() { return ... } if (navigatorInfos.length > 0) { const replacements = []; const navigatorConstNames = new Map(); // Track usage of navigator constant names @@ -214,32 +286,14 @@ function convertStaticToDynamic(code) { 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); + const baseNavigatorConstName = deriveNavigatorConstName(type); + const navigatorConstName = getUniqueNavigatorConstName( + baseNavigatorConstName, + navigatorConstNames + ); // Parse the config object - const parsedConfig = parseNavigatorConfig(config); + const parsedConfig = parseNavigatorConfig(config, commentTracking); // Create: const Stack = createStackNavigator(); const navigatorConstDeclaration = t.variableDeclaration('const', [ @@ -258,12 +312,11 @@ function convertStaticToDynamic(code) { // Preserve all comments from the original node if (originalNode.comments && originalNode.comments.length > 0) { - // Separate leading and trailing comments + // Separate leading and trailing comments based on recast markers const leadingComments = []; const trailingCommentsFromNode = []; originalNode.comments.forEach((comment) => { - // Recast marks comments with leading/trailing properties if (comment.trailing) { trailingCommentsFromNode.push(comment); } else { @@ -271,37 +324,17 @@ function convertStaticToDynamic(code) { } }); - // 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; - } + attachCommentsToNode(navigatorConstDeclaration, leadingComments, false); + attachCommentsToNode( + navigatorComponent, + trailingCommentsFromNode, + true + ); } - // Also check for trailingComments property + // Attach any additional trailing comments if (trailingComments && trailingComments.length > 0) { - trailingComments.forEach((c) => { - c.leading = false; - c.trailing = true; - }); - navigatorComponent.comments = [ - ...(navigatorComponent.comments || []), - ...trailingComments, - ]; + attachCommentsToNode(navigatorComponent, trailingComments, true); } // Store the replacement info @@ -316,7 +349,6 @@ function convertStaticToDynamic(code) { replacements.sort((a, b) => b.index - a.index); const programBody = ast.program.body; - let indexShift = 0; replacements.forEach( ({ index, navigatorConstDeclaration, navigatorComponent }) => { @@ -327,14 +359,12 @@ function convertStaticToDynamic(code) { 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 + // So indices after each replacement need to be shifted by 1 staticNavigationIndices = staticNavigationIndices.map((idx) => { let shift = 0; replacements.forEach(({ index }) => { @@ -344,7 +374,7 @@ function convertStaticToDynamic(code) { }); } - // Remove createStaticNavigation declarations (in reverse order to maintain indices) + // Remove createStaticNavigation declarations (in reverse to maintain correct indices) staticNavigationIndices.sort((a, b) => b - a); staticNavigationIndices.forEach((index) => { ast.program.body.splice(index, 1); @@ -357,35 +387,593 @@ function convertStaticToDynamic(code) { trailingComma: true, }); - // Parse with Babel to verify syntax - recast.parse(output.code, { - parser: require('recast/parsers/babel-ts'), + // Format with prettier first + let formattedCode = await prettier.format(output.code, { + ...prettierConfig, + parser: 'babel', + singleQuote: true, + }); + + // Remove trailing newline that prettier adds + formattedCode = formattedCode.trimEnd(); + + // Post-process: Inject tracked comments into the formatted code + // We do this after Prettier to ensure comments aren't moved/removed during formatting + // Comments are injected by searching for patterns in the string output + commentTracking.forEach((commentObj) => { + const { + screenName, + leadingComments, + trailingComments, + navigatorProp, + screenConfigProperty, + } = commentObj; + + // Handle navigator property comments (e.g., screenOptions on Navigator) + if (navigatorProp) { + const lines = formattedCode.split('\n'); + + // Find the property line within a Navigator element context + const propLineIndex = findPropertyLine( + lines, + (line) => + line.includes(navigatorProp) && + (line.includes('=') || line.includes(':')), + (line) => line.includes('.Navigator'), + 5 // Search 5 lines before/after for context + ); + + if (propLineIndex !== -1) { + const indent = getIndentation(lines[propLineIndex]); + + // Inject leading comments before the property + const updatedLineIndex = injectComments( + lines, + leadingComments, + propLineIndex, + indent, + true // JSX context + ); + + // Find where the property value ends (}} for objects) + const closingIndex = findPropertyClosingLine(lines, updatedLineIndex); + + // Inject trailing comments after the closing + injectComments(lines, trailingComments, closingIndex + 1, indent, true); + + formattedCode = lines.join('\n'); + } + return; + } + + // Handle screen config property comments (options, listeners, etc.) + if (screenConfigProperty) { + const lines = formattedCode.split('\n'); + + // Map 'screen' property to 'component' in JSX + const jsxPropName = + screenConfigProperty === 'screen' ? 'component' : screenConfigProperty; + + // Find the property line within the correct Screen element + const propLineIndex = findPropertyLine( + lines, + (line) => + line.includes(`${jsxPropName}=`) || line.includes(`${jsxPropName}:{`), + (line) => lineMatchesScreenName(line, screenName), + 10 // Search 10 lines before for screen context + ); + + if (propLineIndex !== -1) { + const indent = getIndentation(lines[propLineIndex], ' '); + + // Inject leading comments before the property + const updatedLineIndex = injectComments( + lines, + leadingComments, + propLineIndex, + indent + ); + + // Find where the property value ends (}} or })}) + const closingIndex = findPropertyClosingLine(lines, updatedLineIndex); + + // Inject trailing comments after the closing + injectComments(lines, trailingComments, closingIndex + 1, indent); + + formattedCode = lines.join('\n'); + } + return; + } + + // Process leading comments for screen elements (highlight-next-line, highlight-start) + if (leadingComments.length > 0) { + const lines = formattedCode.split('\n'); + const screenLineIndex = findScreenElementLine(lines, screenName); + + if (screenLineIndex !== -1) { + const indent = getIndentation(lines[screenLineIndex]); + injectComments(lines, leadingComments, screenLineIndex, indent, true); + formattedCode = lines.join('\n'); + } + } + + // Process trailing comments for screen elements (highlight-end) + if (trailingComments.length > 0) { + const lines = formattedCode.split('\n'); + const screenLineIndex = findScreenClosingLine(lines, screenName); + + if (screenLineIndex !== -1) { + const indent = getIndentation(lines[screenLineIndex]); + // Inject after the closing tag + injectComments( + lines, + trailingComments, + screenLineIndex + 1, + indent, + true + ); + formattedCode = lines.join('\n'); + } + } + }); + + return formattedCode; +} + +/** + * Extract screen config from a screen value node. + * Handles both direct object expressions and createXScreen function calls. + */ +function extractScreenConfig(screenValue) { + // Handle createXScreen function calls + // e.g., createNativeStackScreen({ screen: ProfileScreen, ... }) + if ( + t.isCallExpression(screenValue) && + t.isIdentifier(screenValue.callee) && + screenValue.callee.name.startsWith('create') && + screenValue.callee.name.endsWith('Screen') && + screenValue.arguments.length > 0 && + t.isObjectExpression(screenValue.arguments[0]) + ) { + // Extract the object argument from the createXScreen call + return screenValue.arguments[0]; + } + + // Return the value as-is for other cases + return screenValue; +} + +/** + * Track comments on screen config properties (options, screen, listeners, etc.) + */ +function trackScreenConfigComments(screenValue, screenName, commentTracking) { + const configObject = extractScreenConfig(screenValue); + + if (t.isObjectExpression(configObject)) { + configObject.properties.forEach((configProp) => { + if ( + t.isObjectProperty(configProp) && + (configProp.leadingComments || configProp.trailingComments) + ) { + const propName = configProp.key.name || configProp.key.value; + commentTracking.add({ + originalNode: configProp, + screenName, + screenConfigProperty: propName, + leadingComments: + configProp.leadingComments?.map((c) => ({ + value: c.value, + type: c.type, + })) || [], + trailingComments: + configProp.trailingComments?.map((c) => ({ + value: c.value, + type: c.type, + })) || [], + }); + } + }); + } +} + +/** + * Track comments on screen elements themselves + */ +function trackScreenComments(screenProp, screenName, commentTracking) { + if (screenProp.leadingComments || screenProp.trailingComments) { + commentTracking.add({ + originalNode: screenProp, + screenName, + leadingComments: + screenProp.leadingComments?.map((c) => ({ + value: c.value, + type: c.type, + })) || [], + trailingComments: + screenProp.trailingComments?.map((c) => ({ + value: c.value, + type: c.type, + })) || [], + targetNode: null, + }); + } +} + +/** + * Find the line index of a Screen element's opening tag. + * Handles both single-line and multi-line Screen elements. + */ +function findScreenElementLine(lines, screenName) { + for (let i = 0; i < lines.length; i++) { + // Check if line contains Screen opening tag with the name attribute + if ( + lines[i].includes('<') && + lines[i].includes('.Screen') && + lineMatchesScreenName(lines[i], screenName) + ) { + return i; + } + + // For multiline Screen elements, check if opening tag is on this line + // and name attribute is on a subsequent line + if (lines[i].includes('<') && lines[i].includes('.Screen')) { + for (let j = i; j < Math.min(i + 5, lines.length); j++) { + if (lineMatchesScreenName(lines[j], screenName)) { + return i; // Return opening tag line, not name line + } + } + } + } + return -1; +} + +/** + * Find the line index of a Screen element's closing tag. + * Looks for either self-closing /> or closing tag. + */ +function findScreenClosingLine(lines, screenName) { + for (let i = 0; i < lines.length; i++) { + if ( + (lines[i].includes('.Screen') && lines[i].includes('/>')) || + (lines[i].includes('.Screen') && lines[i].includes(' { + const commentText = formatComment(comment, indent, isJSXContext); + lines.splice(currentIndex, 0, commentText); + currentIndex++; + }); + return currentIndex; +} + +/** + * Check if a line contains a screen name attribute (with either quote style) + */ +function lineMatchesScreenName(line, screenName) { + return ( + line.includes(`name='${screenName}'`) || + line.includes(`name="${screenName}"`) + ); +} + +/** + * Extract indentation from a line + */ +function getIndentation(line, defaultIndent = ' ') { + return line.match(/^(\s*)/)?.[1] || defaultIndent; +} + +/** + * Derive navigator constant name from navigator type string. + * Examples: + * - "createStackNavigator" -> "Stack" + * - "createNativeStackNavigator" -> "Stack" + * - "createBottomTabNavigator" -> "Tab" + * - "createMaterialTopTabNavigator" -> "Tab" + */ +function deriveNavigatorConstName(navigatorType) { + // Remove "create" prefix and "Navigator" suffix + const withoutCreate = navigatorType.replace(/^create/, ''); + const withoutNavigator = withoutCreate.replace(/Navigator$/, ''); + // Extract the last capitalized word (e.g., "NativeStack" -> "Stack", "MaterialTopTab" -> "Tab") + const match = withoutNavigator.match(/([A-Z][a-z]+)$/); + return match ? match[1] : withoutNavigator; +} + +/** + * Generate unique navigator constant name by adding suffix if needed. + * Second occurrence gets 'A', third gets 'B', etc. + */ +function getUniqueNavigatorConstName( + baseNavigatorConstName, + navigatorConstNames +) { + const currentCount = navigatorConstNames.get(baseNavigatorConstName) || 0; + navigatorConstNames.set(baseNavigatorConstName, currentCount + 1); + + if (currentCount === 0) { + return baseNavigatorConstName; + } + // Add suffix: A for second occurrence, B for third, etc. + const suffix = String.fromCharCode(65 + currentCount - 1); // 65 is 'A' + return baseNavigatorConstName + suffix; +} + +/** + * Attach comments to AST nodes with proper leading/trailing markers + */ +function attachCommentsToNode(node, comments, isTrailing = false) { + if (comments.length === 0) return; + + comments.forEach((c) => { + c.leading = !isTrailing; + c.trailing = isTrailing; }); + node.comments = [...(node.comments || []), ...comments]; +} - return output.code; +/** + * Find a property line within a context (searches nearby lines for context marker) + */ +function findPropertyLine(lines, propMatcher, contextMatcher, searchRange) { + for (let i = 0; i < lines.length; i++) { + if (propMatcher(lines[i])) { + // Check if this is within the right context by searching nearby lines + const startIdx = Math.max(0, i - searchRange); + const endIdx = Math.min(i + searchRange, lines.length); + + for (let j = startIdx; j < endIdx; j++) { + if (contextMatcher(lines[j])) { + return i; + } + } + } + } + return -1; } /** - * Parse navigator configuration object + * Find the closing line for a JSX property value (looks for }} or })}) */ -function parseNavigatorConfig(configNode) { +function findPropertyClosingLine(lines, startLine, maxSearchLines = 10) { + for ( + let i = startLine; + i < Math.min(startLine + maxSearchLines, lines.length); + i++ + ) { + const line = lines[i]; + // Look for closing patterns but not the Screen element closing + if (line.includes('}}') && !line.includes('/>')) { + return i; + } + // Also check for arrow function returning object: })} + if (line.includes('})}')) { + return i; + } + } + return startLine; +} + +/** + * Create a JSX member expression (e.g., Stack.Navigator, Stack.Screen) + */ +function createJsxMemberExpression(componentName, memberName) { + return t.jsxMemberExpression( + t.jsxIdentifier(componentName), + t.jsxIdentifier(memberName) + ); +} + +/** + * Create JSX attributes from propInfo objects + */ +function createJsxAttributesFromProps(propsObject) { + return Object.values(propsObject).map((propInfo) => { + if (propInfo.isStringLiteral) { + return t.jsxAttribute( + t.jsxIdentifier(propInfo.key), + t.stringLiteral(propInfo.value.value) + ); + } + return t.jsxAttribute( + t.jsxIdentifier(propInfo.key), + t.jsxExpressionContainer(propInfo.value) + ); + }); +} + +/** + * Create a Screen JSX element + */ +function createScreenElement(componentName, 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)) + ); + }); + + return t.jsxElement( + t.jsxOpeningElement( + createJsxMemberExpression(componentName, 'Screen'), + screenProps, + true + ), + null, + [], + true + ); +} + +/** + * Parse a screen value and return component and screenProps. + * Handles identifiers, object expressions, and createXScreen calls. + */ +function parseScreenValue(screenValue) { + if (t.isIdentifier(screenValue)) { + // Simple screen: Home: HomeScreen + return { + component: screenValue.name, + screenProps: {}, + }; + } + + // Extract config from createXScreen calls if present + const configNode = extractScreenConfig(screenValue); + + if (t.isObjectExpression(configNode)) { + // Screen with config: Home: { screen: HomeScreen, options: {...}, listeners: {...} } + let component = null; + const screenProps = {}; + + configNode.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, linking, etc.) + // But skip 'linking' as it's only for static config + if (configKey !== 'linking') { + screenProps[configKey] = screenConfigProp.value; + } + } + }); + + return { component, screenProps }; + } + + return null; +} + +/** + * Parse navigator configuration object. + * Extracts screens, groups, and navigator-level properties. + * Also tracks comments for later injection into the dynamic code. + */ +function parseNavigatorConfig(configNode, commentTracking) { const result = { - screens: {}, - groups: {}, // Store groups - navigatorProps: {}, // Store all navigator-level props + screens: {}, // Standalone screens (not in groups) + groups: {}, // Screen groups with their own screens and props + navigatorProps: {}, // Navigator-level props (screenOptions, initialRouteName, etc.) }; if (!t.isObjectExpression(configNode)) { return result; } - configNode.properties.forEach((prop) => { - if (!t.isObjectProperty(prop) && !t.isObjectMethod(prop)) { - return; - } + // Get all properties from the navigator config object + const props = configNode.properties.filter( + (prop) => t.isObjectProperty(prop) || t.isObjectMethod(prop) + ); + props.forEach((prop, index) => { const keyName = prop.key.name || prop.key.value; + // Track comments on navigator-level properties (but not on screens/groups) + if (keyName !== 'screens' && keyName !== 'groups') { + const leadingComments = + prop.leadingComments?.map((c) => ({ + value: c.value, + type: c.type, + })) || []; + + // Collect trailing comments from both the property and its value + let trailingComments = []; + if (prop.trailingComments) { + trailingComments.push( + ...prop.trailingComments.map((c) => ({ + value: c.value, + type: c.type, + })) + ); + } + if (prop.value?.trailingComments) { + trailingComments.push( + ...prop.value.trailingComments.map((c) => ({ + value: c.value, + type: c.type, + })) + ); + } + + // Heuristic: Check if the next property has leading comments that are actually + // trailing comments for this property (detected by -end or end suffix) + // This handles cases where Babel attaches multiline trailing comments as leading + const nextProp = props[index + 1]; + + if (nextProp?.leadingComments) { + nextProp.leadingComments.forEach((c) => { + // Only treat as trailing if the comment ends with -end or similar markers + if ( + c.value.trim().endsWith('-end') || + c.value.trim().endsWith('end') + ) { + trailingComments.push({ + value: c.value, + type: c.type, + }); + } + }); + } + + if (leadingComments.length > 0 || trailingComments.length > 0) { + const commentObj = { + originalNode: prop, + navigatorProp: keyName, + leadingComments, + trailingComments, + }; + commentTracking.add(commentObj); + } + } + + // Parse groups object (e.g., groups: { modal: { screens: {...}, screenOptions: {...} } }) if (keyName === 'groups' && t.isObjectExpression(prop.value)) { // Parse groups object prop.value.properties.forEach((groupProp) => { @@ -417,32 +1005,20 @@ function parseNavigatorConfig(configNode) { 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 }; + // Track comments on any property inside the screen config + trackScreenConfigComments( + screenValue, + screenName, + commentTracking + ); + + // Track comments on the screen element itself + trackScreenComments(screenProp, screenName, commentTracking); + + const parsed = parseScreenValue(screenValue); + + if (parsed) { + groupConfig.screens[screenName] = parsed; } }); } else { @@ -458,6 +1034,7 @@ function parseNavigatorConfig(configNode) { result.groups[groupKey] = groupConfig; } }); + // Parse top-level screens object (e.g., screens: { Home: HomeScreen, Profile: {...} }) } else if (keyName === 'screens' && t.isObjectExpression(prop.value)) { // Parse screens object prop.value.properties.forEach((screenProp) => { @@ -466,40 +1043,21 @@ function parseNavigatorConfig(configNode) { 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 = {}; + // Track comments on any property inside the screen config + trackScreenConfigComments(screenValue, screenName, commentTracking); - screenValue.properties.forEach((screenConfigProp) => { - if (!t.isObjectProperty(screenConfigProp)) return; + // Track comments on the screen element itself + trackScreenComments(screenProp, screenName, commentTracking); - const configKey = - screenConfigProp.key.name || screenConfigProp.key.value; + const parsed = parseScreenValue(screenValue); - 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 }; + if (parsed) { + result.screens[screenName] = parsed; } }); } else { - // All other props are navigator-level props - // Store both the key name and the AST node value + // Store all other navigator-level props (screenOptions, initialRouteName, etc.) + // Keep track of whether the value is a string literal to determine JSX attribute format result.navigatorProps[keyName] = { key: keyName, value: prop.value, @@ -515,28 +1073,8 @@ function parseNavigatorConfig(configNode) { * 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) - ) - ); - } - }); + const navigatorProps = createJsxAttributesFromProps(config.navigatorProps); // Create screen elements const screenElements = []; @@ -549,69 +1087,18 @@ function createNavigatorComponent(functionName, componentName, config) { t.jsxIdentifier('navigationKey'), t.stringLiteral(groupKey) ), + ...createJsxAttributesFromProps(groupConfig.groupProps), ]; - // 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( + createScreenElement(componentName, screenName, screenConfig) + ); } ); @@ -620,18 +1107,10 @@ function createNavigatorComponent(functionName, componentName, config) { // Create the Group element const groupElement = t.jsxElement( t.jsxOpeningElement( - t.jsxMemberExpression( - t.jsxIdentifier(componentName), - t.jsxIdentifier('Group') - ), + createJsxMemberExpression(componentName, 'Group'), groupProps ), - t.jsxClosingElement( - t.jsxMemberExpression( - t.jsxIdentifier(componentName), - t.jsxIdentifier('Group') - ) - ), + t.jsxClosingElement(createJsxMemberExpression(componentName, 'Group')), groupScreenElements, false ); @@ -643,37 +1122,10 @@ function createNavigatorComponent(functionName, componentName, config) { // 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( + createScreenElement(componentName, screenName, screenConfig) + ); }); screenElements.push(t.jsxText('\n')); @@ -681,18 +1133,10 @@ function createNavigatorComponent(functionName, componentName, config) { // Create Navigator element const navigatorElement = t.jsxElement( t.jsxOpeningElement( - t.jsxMemberExpression( - t.jsxIdentifier(componentName), - t.jsxIdentifier('Navigator') - ), + createJsxMemberExpression(componentName, 'Navigator'), navigatorProps ), - t.jsxClosingElement( - t.jsxMemberExpression( - t.jsxIdentifier(componentName), - t.jsxIdentifier('Navigator') - ) - ), + t.jsxClosingElement(createJsxMemberExpression(componentName, 'Navigator')), screenElements, false ); @@ -710,6 +1154,47 @@ function createNavigatorComponent(functionName, componentName, config) { return functionDeclaration; } +/** + * Create a TabItem element with code block + */ +function createTabItem( + value, + label, + code, + codeNode, + originalCodeBlock, + cleanData, + isDefault = false +) { + return { + type: 'element', + tagName: 'TabItem', + properties: { + value, + label, + ...(isDefault && { 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: code }], + }, + ], + }, + { type: 'text', value: '\n\n' }, + ], + }; +} + /** * Create a Tabs element with both static and dynamic TabItems */ @@ -721,6 +1206,26 @@ function createTabsWithBothConfigs(staticCode, dynamicCode, originalCodeBlock) { codeNode.data?.meta?.replace(/\bstatic2dynamic\b\s*/g, '').trim() || ''; const cleanData = { ...codeNode.data, meta: cleanMeta }; + const tabItems = [ + { value: 'static', label: 'Static', code: staticCode, isDefault: true }, + { value: 'dynamic', label: 'Dynamic', code: dynamicCode, isDefault: false }, + ]; + + const children = tabItems.flatMap((item) => [ + { type: 'text', value: '\n' }, + createTabItem( + item.value, + item.label, + item.code, + codeNode, + originalCodeBlock, + cleanData, + item.isDefault + ), + ]); + + children.push({ type: 'text', value: '\n' }); + return { type: 'element', tagName: 'Tabs', @@ -728,96 +1233,6 @@ function createTabsWithBothConfigs(staticCode, dynamicCode, originalCodeBlock) { 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', - }, - ], + children, }; } diff --git a/versioned_docs/version-7.x/headers.md b/versioned_docs/version-7.x/headers.md index 04f1881160..2372bf9d68 100755 --- a/versioned_docs/version-7.x/headers.md +++ b/versioned_docs/version-7.x/headers.md @@ -13,10 +13,7 @@ We've seen how to configure the header title already, but let's go over that aga Each screen has `options` which is either an object or a function that returns an object, that contains various configuration options. The one we use for the header title is `title`, as shown in the following example. - - - -```js name="Setting header title" snack +```js name="Setting header title" snack static2dynamic import * as React from 'react'; import { Text, View } from 'react-native'; import { createStaticNavigation } from '@react-navigation/native'; @@ -52,65 +49,13 @@ export default function App() { } ``` - - - -```js name="Setting header title" snack -import * as React from 'react'; -import { Text, View } from 'react-native'; -import { NavigationContainer } from '@react-navigation/native'; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; - -function HomeScreen() { - return ( - - Home Screen - - ); -} - -const Stack = createNativeStackNavigator(); - -// codeblock-focus-start -function MyStack() { - return ( - - - - ); -} -// codeblock-focus-end - -export default function App() { - return ( - - - - ); -} -``` - - - - ![Header title](/assets/headers/header-title.png) ## Using params in the title In order to use params in the title, we need to make `options` for the screen a function that returns a configuration object. If we make `options` a function then React Navigation will call it with an object containing `{ navigation, route }` - in this case, all we care about is `route`, which is the same object that is passed to your screen props as `route` prop. You may recall that we can get the params through `route.params`, and so we do this below to extract a param and use it as a title. - - - -```js name="Using params in the title" snack +```js name="Using params in the title" snack static2dynamic import * as React from 'react'; import { Text, View } from 'react-native'; import { @@ -174,79 +119,6 @@ export default function App() { } ``` - - - -```js name="Using params in the title" snack -import * as React from 'react'; -import { Text, View } from 'react-native'; -import { NavigationContainer, useNavigation } from '@react-navigation/native'; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import { Button } from '@react-navigation/elements'; - -function HomeScreen() { - const navigation = useNavigation(); - - return ( - - - - ); -} - -function ProfileScreen() { - return ( - - Profile Screen - - ); -} - -const Stack = createNativeStackNavigator(); - -// codeblock-focus-start -function MyStack() { - return ( - - - ({ - title: route.params.name, - })} - // highlight-end - /> - - ); -} -// codeblock-focus-end - -export default function App() { - return ( - - - - ); -} -``` - - - - The argument that is passed in to the `options` function is an object with the following properties: - `navigation` - The [navigation object](navigation-object.md) for the screen. @@ -258,7 +130,7 @@ We only needed the `route` object in the above example but you may in some cases It's often necessary to update the `options` configuration for the active screen from the mounted screen component itself. We can do this using `navigation.setOptions` -```js name="Updating options" snack +```js name="Updating options" snack static2dynamic import * as React from 'react'; import { Text, View } from 'react-native'; import { @@ -314,10 +186,7 @@ There are three key properties to use when customizing the style of your header: - `headerTintColor`: the back button and title both use this property as their color. In the example below, we set the tint color to white (`#fff`) so the back button and the header title would be white. - `headerTitleStyle`: if we want to customize the `fontFamily`, `fontWeight` and other `Text` style properties for the title, we can use this to do it. - - - -```js name="Header styles" snack +```js name="Header styles" snack static2dynamic import * as React from 'react'; import { Text, View } from 'react-native'; import { createStaticNavigation } from '@react-navigation/native'; @@ -360,62 +229,6 @@ export default function App() { } ``` - - - -```js name="Header styles" snack -import * as React from 'react'; -import { Text, View } from 'react-native'; -import { NavigationContainer } from '@react-navigation/native'; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; - -function HomeScreen() { - return ( - - Home Screen - - ); -} - -const Stack = createNativeStackNavigator(); - -// codeblock-focus-start -function MyStack() { - return ( - - - - ); -} -// codeblock-focus-end - -export default function App() { - return ( - - - - ); -} -``` - - - - ![Custom header styles](/assets/headers/custom_headers.png) There are a couple of things to notice here: @@ -427,10 +240,7 @@ There are a couple of things to notice here: It is common to want to configure the header in a similar way across many screens. For example, your company brand color might be red and so you want the header background color to be red and the tint color to be white. Conveniently, these are the colors we're using in our running example, and you'll notice that when you navigate to the `DetailsScreen` the colors go back to the defaults. Wouldn't it be awful if we had to copy the `options` header style properties from `Home` to `Details`, and for every single screen we use in our app? Thankfully, we do not. We can instead move the configuration up to the native stack navigator under `screenOptions`: - - - -```js name="Common screen options" snack +```js name="Common screen options" snack static2dynamic import * as React from 'react'; import { Text, View } from 'react-native'; import { @@ -492,88 +302,13 @@ export default function App() { } ``` - - - -```js name="Common screen options" snack -import * as React from 'react'; -import { Text, View } from 'react-native'; -import { NavigationContainer, useNavigation } from '@react-navigation/native'; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import { Button } from '@react-navigation/elements'; - -function HomeScreen() { - const navigation = useNavigation(); - - return ( - - Home Screen - - - ); -} - -function DetailsScreen() { - return ( - - Details Screen - - ); -} - -const Stack = createNativeStackNavigator(); - -// codeblock-focus-start -function MyStack() { - return ( - - - - - ); -} -// codeblock-focus-end - -export default function App() { - return ( - - - - ); -} -``` - - - - Now, any screen that belongs to this navigator will have our wonderful branded styles. Surely though, there must be a way to override these options if we need to? ## Replacing the title with a custom component Sometimes you need more control than just changing the text and styles of your title -- for example, you may want to render an image in place of the title, or make the title into a button. In these cases you can completely override the component used for the title and provide your own. - - - -```js name="Custom title" snack +```js name="Custom title" snack static2dynamic import * as React from 'react'; import { Text, View, Image } from 'react-native'; import { createStaticNavigation } from '@react-navigation/native'; @@ -617,66 +352,8 @@ export default function App() { } ``` - - - -```js name="Custom title" snack -import * as React from 'react'; -import { Text, View, Image } from 'react-native'; -import { NavigationContainer } from '@react-navigation/native'; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; - -function HomeScreen() { - return ( - - Home Screen - - ); -} - -const Stack = createNativeStackNavigator(); - -// codeblock-focus-start -function LogoTitle() { - return ( - - ); -} - -function MyStack() { - return ( - - , - }} - /> - - ); -} -// codeblock-focus-end - -export default function App() { - return ( - - - - ); -} -``` - ![Header custom title](/assets/headers/header-custom-title.png) -You might be wondering, why `headerTitle` when we provide a component and not `title`, like before? The reason is that `headerTitle` is a property that is specific to stack navigators, the `headerTitle` defaults to a `Text` component that displays the `title`. - - - :::note You might be wondering, why `headerTitle` when we provide a component and not `title`, like before? The reason is that `headerTitle` is a property that is specific to headers, whereas `title` will be used for tab bars, drawers etc. as well. The `headerTitle` defaults to a `Text` component that displays the `title`. diff --git a/versioned_docs/version-7.x/hello-react-navigation.md b/versioned_docs/version-7.x/hello-react-navigation.md index 74e4e98f60..1d885f73a6 100755 --- a/versioned_docs/version-7.x/hello-react-navigation.md +++ b/versioned_docs/version-7.x/hello-react-navigation.md @@ -45,8 +45,6 @@ npm install @react-navigation/elements `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 - import * as React from 'react'; import { View, Text } from 'react-native'; import { createStaticNavigation } from '@react-navigation/native'; @@ -87,8 +85,6 @@ In a typical React Native app, the `createStaticNavigation` function should be o `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 - import * as React from 'react'; import { View, Text } from 'react-native'; import { NavigationContainer } from '@react-navigation/native'; diff --git a/versioned_docs/version-7.x/navigating.md b/versioned_docs/version-7.x/navigating.md index 73057ef519..e4b09581d7 100755 --- a/versioned_docs/version-7.x/navigating.md +++ b/versioned_docs/version-7.x/navigating.md @@ -28,7 +28,7 @@ We'll do something similar to the latter, but rather than using a `window.locati ## Navigating to a new screen -```js name="Navigating to a new screen" snack +```js name="Navigating to a new screen" snack static2dynamic // codeblock-focus-start import * as React from 'react'; import { View, Text } from 'react-native'; @@ -100,7 +100,7 @@ So we now have a stack with two routes: 1) the `Home` route 2) the `Details` rou ## Navigate to a screen multiple times -```js name="Navigate to a screen multiple times" snack +```js name="Navigate to a screen multiple times" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { @@ -159,7 +159,7 @@ If you run this code, you'll notice that when you tap "Go to Details... again", Let's suppose that we actually _want_ to add another details screen. This is pretty common in cases where you pass in some unique data to each route (more on that later when we talk about `params`!). To do this, we can change `navigate` to `push`. This allows us to express the intent to add another route regardless of the existing navigation history. -```js name="Navigate to a screen multiple times" snack +```js name="Navigate to a screen multiple times" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { @@ -224,7 +224,7 @@ The header provided by the native stack navigator will automatically include a b Sometimes you'll want to be able to programmatically trigger this behavior, and for that, you can use `navigation.goBack()`. -```js name="Going back" snack +```js name="Going back" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { @@ -292,7 +292,7 @@ On Android, React Navigation hooks in to the hardware back button and fires the Another common requirement is to be able to go back _multiple_ screens -- for example, if you are several screens deep in a stack and want to dismiss all of them to go back to the first screen. In this case, we know that we want to go back to `Home` so we can use `popTo('Home')`. Another alternative would be `navigation.popToTop()`, which goes back to the first screen in the stack. -```js name="Going back to specific screen" snack +```js name="Going back to specific screen" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { diff --git a/versioned_docs/version-7.x/params.md b/versioned_docs/version-7.x/params.md index 9bd5d6fb88..4bd6aa840f 100755 --- a/versioned_docs/version-7.x/params.md +++ b/versioned_docs/version-7.x/params.md @@ -22,7 +22,7 @@ We recommend that the params you pass are JSON-serializable. That way, you'll be ::: -```js name="Passing params" snack +```js name="Passing params" snack static2dynamic import * as React from 'react'; import { View, Text } from 'react-native'; import { @@ -144,7 +144,7 @@ Screens can also update their params, like they can update their state. The `nav Basic usage: -```js name="Updating params" snack +```js name="Updating params" snack static2dynamic import * as React from 'react'; import { Text, View } from 'react-native'; import { @@ -208,7 +208,7 @@ Params aren't only useful for passing some data to a new screen, but they can al To achieve this, you can use the `popTo` method to go back to the previous screen as well as pass params to it: -```js name="Passing params back" snack +```js name="Passing params back" snack static2dynamic import * as React from 'react'; import { Text, View, TextInput } from 'react-native'; import { @@ -294,7 +294,7 @@ Here, after you press "Done", the home screen's `route.params` will be updated t If you have nested navigators, you need to pass params a bit differently. For example, say you have a navigator inside the `More` screen and want to pass params to the `Settings` screen inside that navigator. Then you can pass params as the following: -```js name="Passing params to nested screen" snack +```js name="Passing params to nested screen" snack static2dynamic import * as React from 'react'; import { Text, View, TextInput } from 'react-native'; import { diff --git a/versioned_docs/version-8.x/bottom-tab-navigator.md b/versioned_docs/version-8.x/bottom-tab-navigator.md index cfaf478245..aee5db4c77 100755 --- a/versioned_docs/version-8.x/bottom-tab-navigator.md +++ b/versioned_docs/version-8.x/bottom-tab-navigator.md @@ -4,10 +4,14 @@ title: Bottom Tabs Navigator sidebar_label: Bottom Tabs --- -A simple tab bar on the bottom of the screen that lets you switch between different routes. Routes are lazily initialized -- their screen components are not mounted until they are first focused. +Bottom Tab Navigator displays a set of screens with a tab bar to switch between them. + + ## Installation @@ -80,6 +84,52 @@ export default function App() { In addition to the [common props](navigator.md#configuration) shared by all navigators, the bottom tab navigator accepts the following additional props: +#### `implementation` + +The implementation to use for rendering the tab bar. Possible values: + +- `'native'` (default) - Uses native bottom tabs on iOS and Android. This allows matching the native design such as liquid glass effect on iOS 26. +- `'custom'` - Uses a JavaScript-based implementation for the tab bar. Use this if you need more customization than what's possible with native tabs. + + + + +```js +createBottomTabNavigator({ + implementation: 'custom', + // ... +}); +``` + + + + +```js +{/* ... */} +``` + + + + +When using native tabs, some options behave differently: + +- `tabBarShowLabel` is replaced with `tabBarLabelVisibilityMode` which accepts: + - `"auto"` (default) + - `"selected"` + - `"labeled"` - same as `tabBarShowLabel: true` + - `"unlabeled"` - same as `tabBarShowLabel: false` +- `tabBarLabel` only accepts a `string` +- `tabBarIcon` accepts a function that returns an icon object + +:::note + +- The `native` implementation uses `UITabBarController` on iOS and `BottomNavigationView` on Android. +- 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 with `native` implementation. This is a limitation of the underlying native component. +- The `native` implementation requires React Native 0.79 or above. If you're using [Expo](https://expo.dev/), it requires SDK 53 or above. + +::: + #### `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. @@ -97,10 +147,14 @@ It supports the following values: Boolean used to indicate whether inactive screens should be detached from the view hierarchy to save memory. This enables integration with [react-native-screens](https://github.com/software-mansion/react-native-screens). Defaults to `true`. +Only supported with `custom` implementation. + #### `tabBar` Function that returns a React element to display as the tab bar. +Only supported with `custom` implementation. + The function receives an object containing the following properties as the argument: - `state` - The state object for the tab navigator. @@ -272,11 +326,61 @@ Generic title that can be used as a fallback for `headerTitle` and `tabBarLabel` #### `tabBarLabel` -Title string of a tab displayed in the tab bar or a function that given `{ focused: boolean, color: string }` returns a React.Node, to display in tab bar. When undefined, scene `title` is used. To hide, see `tabBarShowLabel`. +Title string of a tab displayed in the tab bar. When undefined, scene `title` is used. To hide, see [`tabBarLabelVisibilityMode`](#tabbarlabelvisibilitymode). + +Overrides the label provided by [`tabBarSystemItem`](#tabbarsystemitem) on iOS. + +#### `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` + +Only supported with `native` implementation on iOS. + +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', +}, +``` + + -#### `tabBarShowLabel` +#### `tabBarLabelVisibilityMode` -Whether the tab label should be visible. Defaults to `true`. +The label visibility mode for the tab bar items. Supported values: + +- `auto` - decided based on platform and implementation (default) +- `labeled` - labels are always shown +- `unlabeled` - labels are never shown +- `selected` - labels shown only for selected tab (only supported on Android with `native` implementation) + +Supported on all platforms with `custom` implementation. Only supported on Android with `native` implementation. #### `tabBarLabelPosition` @@ -284,15 +388,29 @@ Whether the label is shown below the icon or beside the icon. By default, the position is chosen automatically based on device width. +Only supported with `custom` implementation. + - `below-icon`: the label is shown below the icon (typical for iPhones) Tab bar label position - below - `beside-icon` the label is shown next to the icon (typical for iPad) Tab bar label position - beside +#### `tabBarAllowFontScaling` + +Whether label font should scale to respect Text Size accessibility settings. Defaults to `true`. + +Only supported with `custom` implementation. + #### `tabBarLabelStyle` -Style object for the tab label. +Style object for the tab label. Supported properties: + +- `fontFamily` +- `fontSize` +- `fontWeight` +- `fontStyle` + Tab bar label style Example: @@ -309,10 +427,85 @@ Example: Function that given `{ focused: boolean, color: string, size: number }` returns a React.Node, to display in the tab bar. +With `native` implementation, you can pass an icon object directly instead of a function. A React element is only supported with `custom` implementation. + +It overrides the icon provided by [`tabBarSystemItem`](#tabbarsystemitem) on iOS. + +The icon can be of following types with `native` implementation: + +- 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 with `native` implementation, you can use a function: + +```js +tabBarIcon: ({ focused }) => { + return { + type: 'sfSymbol', + name: focused ? 'heart.fill' : 'heart', + }; +}, +``` + +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', + }, +}); +``` + #### `tabBarIconStyle` Style object for the tab icon. +Only supported with `custom` implementation. + #### `tabBarBadge` Text to show in a badge on the tab icon. Accepts a `string` or a `number`. @@ -321,7 +514,12 @@ 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. You can specify a background color or text color here. +Style for the badge on the tab icon. Supported properties: + +- `backgroundColor` +- `color` + +Only supported with `native` implementation on Android. Tab bar badge style @@ -338,10 +536,14 @@ Example: Accessibility label for the tab button. This is read by the screen reader when the user taps the tab. It's recommended to set this if you don't have a label for the tab. +Only supported with `custom` implementation. + #### `tabBarButton` Function which returns a React element to render as the tab bar button. It wraps the icon and label. Renders `Pressable` by default. +Only supported with `custom` implementation. + You can specify a custom implementation here: ```js @@ -352,6 +554,8 @@ tabBarButton: (props) => ; ID to locate this tab button in tests. +Only supported with `custom` implementation. + #### `tabBarActiveTintColor` Color for the icon and label in the active tab. @@ -362,27 +566,80 @@ Color for the icon and label in the active tab. Color for the icon and label in the inactive tabs. Tab bar inactive tint color +#### `tabBarActiveIndicatorColor` + +Background color of the active indicator. + +Only supported with `native` implementation on Android. + +#### `tabBarActiveIndicatorEnabled` + +Whether the active indicator should be used. Defaults to `true`. + +Only supported with `native` implementation on Android. + +#### `tabBarRippleColor` + +Color of the ripple effect when pressing a tab. + +Only supported with `native` implementation on Android. + #### `tabBarActiveBackgroundColor` Background color for the active tab. +Only supported with `custom` implementation. + #### `tabBarInactiveBackgroundColor` Background color for the inactive tabs. +Only supported with `custom` implementation. + #### `tabBarHideOnKeyboard` Whether the tab bar is hidden when the keyboard opens. Defaults to `false`. +Only supported with `custom` implementation. + +#### `tabBarVisibilityAnimationConfig` + +Animation config for showing and hiding the tab bar when the keyboard is shown/hidden. + +Only supported with `custom` implementation. + +Example: + +```js +tabBarVisibilityAnimationConfig: { + show: { + animation: 'timing', + config: { + duration: 200, + }, + }, + hide: { + animation: 'timing', + config: { + duration: 100, + }, + }, +}, +``` + #### `tabBarItemStyle` Style object for the tab item container. +Only supported with `custom` implementation. + #### `tabBarStyle` Style object for the tab bar. You can configure styles such as background color here. -To show your screen under the tab bar, you can set the `position` style to absolute: +With `custom` implementation, this accepts any style properties. With `native` implementation, only `backgroundColor` and `shadowColor` (iOS 18 and below) are supported. + +To show your screen under the tab bar, you can set the `position` style to absolute (only with `custom` implementation): ```js @@ -498,10 +761,77 @@ Variant of the tab bar. Available values are: - `uikit` (Default) - The tab bar will be styled according to the iOS UIKit guidelines. - `material` - The tab bar will be styled according to the Material Design guidelines. +Only supported with `custom` implementation. + The `material` variant is currently only supported when the [`tabBarPosition`](#tabbarposition) is set to `left` or `right`. ![Material sidebar](/assets/7.x/bottom-tabs-side-material.png) +#### `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 with `native` implementation 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 + +Supported on all platforms with `custom` implementation. By default: + +- `tabBar` is positioned at the bottom +- `tabSidebar` is positioned on the left (LTR) or right (RTL) + +The [`tabBarPosition`](#tabbarposition) option can be used to override this in `custom` implementation. + +Supported on iOS 18 and above with `native` implementation. 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 with `native` implementation 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. @@ -525,7 +855,9 @@ 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. +With `custom` implementation, you can find the list of header related options in the [Elements library documentation](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 documented in that page. + +With `native` implementation, 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. 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`). In addition to those, the following options are also supported in bottom tabs: @@ -587,7 +919,13 @@ This event is fired when the user presses the tab button for the current screen - 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 -To prevent the default behavior, you can call `event.preventDefault`: +To prevent the default behavior, you can call `event.preventDefault`. + +:::note + +Calling `event.preventDefault` is only supported with the `custom` implementation. With the `native` implementation, the default behavior cannot be prevented. + +::: ```js name="Tab Press Event" snack static2dynamic import * as React from 'react'; @@ -659,6 +997,8 @@ By default, tabs are rendered lazily. So if you add a listener inside a screen c 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. +Only supported with the `custom` implementation. + Example: ```js @@ -671,6 +1011,38 @@ React.useEffect(() => { }, [navigation]); ``` +#### `transitionStart` + +This event is fired when a transition animation starts when switching tabs. + +Example: + +```js +React.useEffect(() => { + const unsubscribe = navigation.addListener('transitionStart', (e) => { + // Do something + }); + + return unsubscribe; +}, [navigation]); +``` + +#### `transitionEnd` + +This event is fired when a transition animation ends when switching tabs. + +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: @@ -779,6 +1151,12 @@ import { BottomTabBarHeightContext } from '@react-navigation/bottom-tabs'; By default, switching between tabs doesn't have any animation. You can specify the `animation` option to customize the transition animation. +:::note + +Animations are only supported with the `custom` implementation. + +::: + Supported values for `animation` are: - `fade` - Cross-fade animation for the screen transition where the new screen fades in and the old screen fades out. diff --git a/versioned_docs/version-8.x/configuring-links.md b/versioned_docs/version-8.x/configuring-links.md index 7b98cbee23..835cf623fb 100644 --- a/versioned_docs/version-8.x/configuring-links.md +++ b/versioned_docs/version-8.x/configuring-links.md @@ -13,7 +13,7 @@ In this guide, we will configure React Navigation to handle external links. This 2. Enable URL integration in browser when using on web 3. Use [``](link.md) or [`useLinkTo`](use-link-to.md) to navigate using paths. -Make sure that you have [configured deep links](deep-linking.md) in your app before proceeding. If you have an Android or iOS app, remember to specify the [`prefixes`](navigation-container.md#linkingprefixes) option. +Make sure that you have [configured deep links](deep-linking.md) in your app before proceeding. @@ -23,20 +23,14 @@ The [`Navigation`](static-configuration.md#createstaticnavigation) component acc ```js import { createStaticNavigation } from '@react-navigation/native'; -// highlight-start -const linking = { - enabled: 'auto' /* Automatically generate paths for all screens */, - prefixes: [ - /* your linking prefixes */ - ], -}; -// highlight-end - function App() { return ( Loading...} /> ); @@ -55,9 +49,6 @@ import { NavigationContainer } from '@react-navigation/native'; // highlight-start const linking = { - prefixes: [ - /* your linking prefixes */ - ], config: { /* configuration for matching screens with paths */ }, @@ -80,19 +71,15 @@ function App() { -When you specify the `linking` prop, React Navigation will handle incoming links automatically. On Android and iOS, it'll use React Native's [`Linking` module](https://reactnative.dev/docs/linking) to handle incoming links, both when the app was opened with the link, and when new links are received when the app is open. On the Web, it'll use the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) to sync the URL with the browser. - -:::warning - -Currently there seems to be bug ([facebook/react-native#25675](https://github.com/facebook/react-native/issues/25675)) which results in it never resolving on Android. We add a timeout to avoid getting stuck forever, but it means that the link might not be handled in some cases. +When you specify the `linking` prop, React Navigation will handle incoming links automatically. -::: +On Android and iOS, it'll use React Native's [`Linking` module](https://reactnative.dev/docs/linking) to handle incoming deep links and universal links, both when the app was opened with the link, and when new links are received when the app is open. On the Web, it'll use the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) to sync the URL with the browser. You can also pass a [`fallback`](navigation-container.md#fallback) prop that controls what's displayed when React Navigation is trying to resolve the initial deep link URL. ## Prefixes -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). +The `prefixes` option can be optionally 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: @@ -102,7 +89,9 @@ const linking = { }; ``` -Note that the `prefixes` option is not supported on Web. The host & domain names will be automatically determined from the Website URL in the browser. If your app runs only on Web, then you can omit this option from the config. +If not specified, it defaults to `['*']`, which will match any host starting with `http`, `https`, and custom schemes such as `myapp://`. You only need to specify `prefixes` if you're using **Expo Go** or want to restrict the URLs your app handles. + +Note that the `prefixes` option has no effect on Web. The host & domain names will be automatically determined from the Website URL in the browser. ### Multiple subdomains​ diff --git a/versioned_docs/version-8.x/custom-navigators.md b/versioned_docs/version-8.x/custom-navigators.md index e50e11714d..55d758c3bd 100755 --- a/versioned_docs/version-8.x/custom-navigators.md +++ b/versioned_docs/version-8.x/custom-navigators.md @@ -4,6 +4,9 @@ title: Custom navigators sidebar_label: Custom navigators --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + 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. 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. @@ -19,7 +22,7 @@ The navigator component then uses this state to layout the screens appropriately A very basic example looks like this: ```js -function MyStackNavigator(props) { +function MyNavigator(props) { const { state, descriptors, NavigationContent } = useNavigationBuilder( StackRouter, props @@ -31,14 +34,14 @@ function MyStackNavigator(props) { return {descriptor.render()}; } -export const createMyStackNavigator = createNavigatorFactory(MyStackNavigator); +export const createMyNavigator = createNavigatorFactory(MyNavigator); ``` 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 define a `MyNavigator` component that contains our navigator logic. This is the component that's rendered when you render the navigator in your app with the `createMyNavigator` 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. @@ -180,24 +183,27 @@ We can also do `export const createMyNavigator = createNavigatorFactory(MyNaviga Then it can be used like this: -```js +```js static2dynamic +import { createStaticNavigation } from '@react-navigation/native'; import { createMyNavigator } from './myNavigator'; -const My = createMyNavigator(); +const MyTabs = createMyNavigator({ + screens: { + Home: HomeScreen, + Feed: FeedScreen, + }, +}); + +const Navigation = createStaticNavigation(MyTabs); function App() { - return ( - - - - - ); + return ; } ``` ## Type-checking navigators -To type-check navigators, we need to provide 3 types: +To type-check navigators, we need to provide few types: - Type of the props accepted by the view - Type of supported screen options @@ -222,6 +228,7 @@ import { type NavigatorTypeBagBase, type ParamListBase, type StaticConfig, + type StaticScreenConfig, type TabActionHelpers, type TabNavigationState, TabRouter, @@ -337,29 +344,55 @@ function TabNavigator({ tabBarStyle, contentStyle, ...rest }: Props) { ); } -// The factory function with generic types for type-inference +// Types required for type-checking the navigator +type MyTabTypeBag = { + ParamList: ParamList; + State: TabNavigationState; + ScreenOptions: TabNavigationOptions; + EventMap: TabNavigationEventMap; + NavigationList: { + [RouteName in keyof ParamList]: TabNavigationProp; + }; + Navigator: typeof TabNavigator; +}; + +// The factory function with overloads for static and dynamic configuration export function createMyNavigator< const ParamList extends ParamListBase, - const NavigatorID extends string | undefined = undefined, - const TypeBag extends NavigatorTypeBagBase = { - ParamList: ParamList; - NavigatorID: NavigatorID; - State: TabNavigationState; - ScreenOptions: TabNavigationOptions; - EventMap: TabNavigationEventMap; - NavigationList: { - [RouteName in keyof ParamList]: TabNavigationProp< - ParamList, - RouteName, - NavigatorID - >; - }; - Navigator: typeof TabNavigator; - }, - const Config extends StaticConfig = StaticConfig, ->(config?: Config): TypedNavigator { +>(): TypedNavigator, undefined>; +export function createMyNavigator< + const Config extends StaticConfig>, +>( + config: Config +): TypedNavigator>, Config>; +export function createMyNavigator(config?: unknown) { return createNavigatorFactory(TabNavigator)(config); } + +// Helper function for creating screen config with proper types for static configuration +export function createMyScreen< + const Linking extends StaticScreenConfigLinking, + const Screen extends StaticScreenConfigScreen, +>( + config: StaticScreenConfigInput< + Linking, + Screen, + TabNavigationState, + MyNavigationOptions, + MyNavigationEventMap, + MyNavigationProp + > +): StaticScreenConfigResult< + Linking, + Screen, + TabNavigationState, + MyNavigationOptions, + MyNavigationEventMap, + MyNavigationProp +> { + // @ts-expect-error: there is some issue with the generic inference here + return config; +} ``` ## Extending Navigators @@ -376,7 +409,6 @@ import { import { BottomTabView } from '@react-navigation/bottom-tabs'; function BottomTabNavigator({ - id, initialRouteName, children, layout, @@ -388,7 +420,6 @@ function BottomTabNavigator({ }) { const { state, descriptors, navigation, NavigationContent } = useNavigationBuilder(TabRouter, { - id, initialRouteName, children, layout, @@ -442,7 +473,7 @@ const { state, descriptors, navigation, NavigationContent } = 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 +- [`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-8.x/drawer-navigator.md b/versioned_docs/version-8.x/drawer-navigator.md index 594f49b0c1..0feb5e44db 100644 --- a/versioned_docs/version-8.x/drawer-navigator.md +++ b/versioned_docs/version-8.x/drawer-navigator.md @@ -450,7 +450,7 @@ function MyDrawer() { screenOptions={{ drawerType: isLargeScreen ? 'permanent' : 'back', drawerStyle: isLargeScreen ? null : { width: '100%' }, - overlayColor: 'transparent', + overlayStyle: { backgroundColor: 'transparent' }, }} > {/* Screens */} @@ -483,9 +483,15 @@ Supported values: - `none` -#### `overlayColor` +#### `overlayStyle` -Color overlay to be displayed on top of the content view when drawer gets open. The opacity is animated from `0` to `1` when the drawer opens. +Style for the overlay on top of the content view when drawer gets open. You can use this to customize the overlay color, opacity, and other properties: + +```js +overlayStyle: { + backgroundColor: 'rgba(0, 0, 0, 0.5)', +} +``` - } - > - {children} - - - )} - // highlight-end -/> -``` - -Or with a [group](group.md#screen-layout) or [navigator](navigator.md#screen-layout) with `screenLayout`: - -```jsx - ( - - - Loading… - - } - > - {children} - - - )} -> - // highlight-end - {/* screens */} - -``` - -### Preloading screens - -All built-in navigators now support preloading screens prior to navigating to them. This can be useful to improve the perceived performance of the app by preloading the screens that the user is likely to navigate to next. Preloading a screen will render it off-screen and execute its side-effects such as data fetching. - -To preload a screen, you can use the `preload` method on the navigation object: - -```js -navigation.preload('Details', { id: 42 }); -``` - -See [`preload`](navigation-object.md#preload) for usage. - -### Improvements to navigators - -#### Bottom Tab Navigator can now show tabs on the side and top - -The `@react-navigation/bottom-tabs` package now supports showing tabs on the side. This will make it easier to build responsive UIs for where you want to show tabs on the bottom on smaller screens and switch to a sidebar on larger screens. - -Similarly, showing tabs on the top is also supported which can be useful for Android TV or Apple TV apps. - -You can use the `tabBarPosition` option to customize the position of the tabs: - -```jsx - - {/* ... */} - -``` - -See [Bottom Tab Navigator options](bottom-tab-navigator.md#tabbarposition) for usage. - -#### Bottom Tab Navigator now supports animations - -The `@react-navigation/bottom-tabs` package now supports animations. This was one of the most requested features on our Canny board: [TabNavigator Custom Transition](https://react-navigation.canny.io/feature-requests/p/tabnavigator-custom-transition). - -You can use the `animation` option to customize the animations for the tab transitions: - -```jsx - - {/* ... */} - -``` - -See [Bottom Tab Navigator animation](bottom-tab-navigator.md#animations) for usage. - -#### Stack Navigator now supports an `animation` option - -The `@react-navigation/stack` package now supports an `animation` option to customize the animations for the screen transitions: - -```jsx - - {/* ... */} - -``` - -The `animation` option is an alternative to the `TransitionPresets` API, and is intended to make migrating between JS stack and native stack navigators easier. - -See [Stack Navigator animation](stack-navigator.md#animations) for usage. - -#### Native Stack Navigator now exports a `useAnimatedHeaderHeight` hook - -The `@react-navigation/native-stack` package now exports a `useAnimatedHeaderHeight` hook. It can be used to animate content based on the header height changes - such as when the large title shrinks to a small title on iOS: - -```jsx -const headerHeight = useAnimatedHeaderHeight(); - -return ( - - {/* ... */} - -); -``` - -#### All navigators with headers now support `headerSearchBarOptions` - -The `Header` component from `@react-navigation/elements` now supports a `headerSearchBarOptions` prop. This means all navigators that use the `Header` component now support a search bar in the header as well on all platforms. Previously, this was only available in the Native Stack Navigator on iOS and Android. - -```js -React.useLayoutEffect(() => { - navigation.setOptions({ - headerSearchBarOptions: { - placeholder: 'Search', - onChangeText: (text) => { - // Do something - }, - }, - }); -}, [navigation]); -``` - -See [`headerSearchBarOptions`](elements.md#headersearchbaroptions) for usage. - -### New components in elements library - -The `@react-navigation/elements` package now includes new components that can be used in your app: - -#### `Button` - -The `Button` component has built-in support for navigating to screens, and renders an anchor tag on the Web when used for navigation: - -```jsx - -``` - -The button follows the [Material Design 3 guidelines](https://m3.material.io/components/buttons/overview). - -See [`Button`](elements.md#button) for usage. - -#### `HeaderButton` - -The `HeaderButton` component can be used to render buttons in the header with appropriate styling: - -```js -headerRight: ({ tintColor }) => ( - { - /* do something */ - }} - > - - -), -``` - -See [`HeaderButton`](elements.md#headerbutton) for usage. - -#### `Label` - -The `Label` component can be used to render label text, such as the label in a tab bar button: - -```jsx - -``` - -See [`Label`](elements.md#label) for usage. - -### `react-native-drawer-layout` package - -The drawer implementation used in `@react-navigation/drawer` is now available as a standalone package called `react-native-drawer-layout`. This makes it easier to use the drawer implementation even if you're not using React Navigation, or if you want to use it without a navigator. - -You can install it with: - -```bash npm2yarn -npm install react-native-drawer-layout -``` - -See [`react-native-drawer-layout`](drawer-layout.md) for usage. - -### `useLogger` devtool - -The `@react-navigation/devtools` package now exports a `useLogger` hook. It can be used to log navigation actions to the console: - -See [`useLogger`](devtools.md#uselogger) for usage. diff --git a/versioned_docs/version-8.x/upgrading-from-7.x.md b/versioned_docs/version-8.x/upgrading-from-7.x.md new file mode 100755 index 0000000000..f46881e750 --- /dev/null +++ b/versioned_docs/version-8.x/upgrading-from-7.x.md @@ -0,0 +1,660 @@ +--- +id: upgrading-from-7.x +title: Upgrading from 7.x +sidebar_label: Upgrading from 7.x +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +:::warning + +React Navigation 8 is still in pre-release stage. The API may still change before the stable release. The upgrade guide will be updated accordingly when we release the stable version. + +::: + +This guides lists all the breaking changes and new features in React Navigation 8 that you need to be aware of when upgrading from React Navigation 7. + +## Dependency changes + +The minimum required version of React Native, Expo, and TypeScript have been bumped: + +- `react-native` >= 0.81 +- `expo` >= 54 (if you use [Expo Go](https://expo.dev/go)) +- `typescript` >= 5.9.2 (if you use TypeScript) + +The minimum required version of various peer dependencies have also been bumped: + +- `react-native-screens` >= 3.22.0 +- `react-native-safe-area-context` >= 5.5.0 +- `react-native-reanimated` >= 4.0.0 +- `react-native-web` >= 0.21.0 + +Previously, many navigators worked without `react-native-screens`, but now it's required for all navigators. + +Additionally, React Navigation now uses [`@callstack/liquid-glass`](https://github.com/callstack/liquid-glass) to implement liquid glass effect on iOS 26. + +## Breaking changes + +### Dropping support for old architecture + +React Navigation 8 no longer supports the old architecture of React Native. The old architecture has been frozen since React Native 0.80 and will be removed in React Native 0.82. + +So if you're still on the old architecture, you'll need to upgrade to the new architecture in order to use React Navigation 8. + +### Changes to TypeScript setup + +We introduced a static API in React Navigation 7. However, some of the TypeScript types were not inferred and required manual annotations. In React Navigation 8, we reworked the TypeScript types to solve many of these issues. + +#### The root type now uses navigator type instead of param list + +Previously the types for the root navigator were specified using `declare global` and `RootParamList`. Now, they can be specified with module augmentation of `@react-navigation/core` and use the navigator's type instead a param list: + +```diff lang=ts +- type RootStackParamList = StaticParamList; +- +- declare global { +- namespace ReactNavigation { +- interface RootParamList extends RootStackParamList {} +- } +- } ++ type RootStackType = typeof RootStack; ++ ++ declare module '@react-navigation/core' { ++ interface RootNavigator extends RootStackType {} ++ } +``` + +Using module augmentation is shorter, and avoids namespace usage - which ESLint may complain about in some configurations. + +Using the navigator's type instead of a param list allows to infer the type of navigators - primarily in case of static configuration. + +#### Common hooks no longer accept generics + +Previously hooks such as `useNavigation`, `useRoute` and `useNavigationState` accepted a generic to override the default types. This is not type-safe as we cannot verify that the provided type matches the actual navigators, and we recommended minimizing such usage. + +In React Navigation 8, we reworked the types to automatically determine the correct type [based on the name of the screen](#common-hooks-now-accept-name-of-the-screen) when using static config: + +```diff lang=ts +- const navigation = useNavigation>(); ++ const navigation = useNavigation('Profile'); +``` + +If you're using dynamic configuration, unfortunately we cannot currently infer the types automatically. So it still requires manual annotation. However, now you need to use `as` instead of generics to make it clearer that this is unsafe: + +```diff lang=ts +- const navigation = useNavigation>(); ++ const navigation = useNavigation() as StackNavigationProp; +``` + +The `useRoute` type has been updated in the same way: + +```diff lang=ts +- const route = useRoute>(); ++ const route = useRoute('Profile'); +``` + +And if you're using dynamic configuration: + +```diff lang=ts +- const route = useRoute>(); ++ const route = useRoute() as RouteProp; +``` + +Similarly, the `useNavigationState` type has been updated to accept the name of the screen in addition to the selector: + +```diff lang=ts +- const focusedRouteName = useNavigationState((state) => state.routes[state.index].name); ++ const focusedRouteName = useNavigationState('Settings', (state) => state.routes[state.index].name); +``` + +If you're using dynamic configuration, you can use `as`: + +```diff lang=ts +- const focusedRouteName = useNavigationState((state) => state.routes[state.index].name); ++ const focusedRouteName = useNavigationState((state) => state.routes[state.index].name as keyof RootStackParamList); +``` + +#### New `createXScreen` API for creating screen config + +One of the limitations of the static config API is that the type of `route` object can't be inferred in screen callback, listeners callback etc. This made it difficult to use route params in these callbacks. + +To address this, we added a new `createXScreen` API for each navigator to create screen config with proper types: + +```diff lang=js +const Stack = createStackNavigator({ + screens: { +- Profile: { +- screen: ProfileScreen, +- options: ({ route }) => { +- const userId = route.params.userId; // Don't know the type of route params +- +- return { title: `User ${userId}` }; +- }, +- }, ++ Profile: createStackScreen({ ++ screen: ProfileScreen, ++ options: ({ route }) => { ++ const userId = route.params.userId; // Now correctly inferred ++ ++ return { title: `User ${userId}` }; ++ }, ++ }); + } +}); +``` + +When using the `createXScreen` API, the type of params are automatically inferred based on the type annotation for the component specified in `screen` (e.g. `(props: StaticScreenProps)`) and the path pattern specified in the linking config (e.g. `linking: 'profile/:userId'`). + +Each navigator exports its own helper function, e.g. `createNativeStackScreen` for Native Stack Navigator, `createBottomTabScreen` for Bottom Tab Navigator, `createDrawerScreen` for Drawer Navigator etc. + +See [Static configuration docs](static-configuration.md#createxscreen) for more details. + +#### Custom navigators now require overloads for types + +To work with the reworked TypeScript types, custom navigators now need to provide overloads for static and dynamic configuration APIs, and an additional API to create screen config. + +```diff lang=ts +- export function createMyNavigator< +- const ParamList extends ParamListBase, +- const NavigatorID extends string | undefined = string | undefined, +- const TypeBag extends NavigatorTypeBagBase = { +- ParamList: ParamList; +- NavigatorID: NavigatorID; +- State: TabNavigationState; +- ScreenOptions: MyNavigationOptions; +- EventMap: MyNavigationEventMap; +- NavigationList: { +- [RouteName in keyof ParamList]: MyNavigationProp< +- ParamList, +- RouteName, +- NavigatorID +- >; +- }; +- Navigator: typeof MyNavigator; +- }, +- const Config extends StaticConfig = StaticConfig, +- >(config?: Config): TypedNavigator { +- return createNavigatorFactory(MyNavigator)(config); +- } ++ type MyTypeBag = { ++ ParamList: ParamList; ++ State: TabNavigationState; ++ ScreenOptions: MyNavigationOptions; ++ EventMap: MyNavigationEventMap; ++ NavigationList: { ++ [RouteName in keyof ParamList]: MyNavigationProp< ++ ParamList, ++ RouteName ++ >; ++ }; ++ Navigator: typeof MyNavigator; ++ }; ++ ++ export function createMyNavigator< ++ const ParamList extends ParamListBase, ++ >(): TypedNavigator, undefined>; ++ export function createMyNavigator< ++ const Config extends StaticConfig>, ++ >( ++ config: Config ++ ): TypedNavigator< ++ MyTypeBag>, ++ Config ++ >; ++ export function createMyNavigator(config?: unknown) { ++ return createNavigatorFactory(MyNavigator)(config); ++ } + ++ export function createMyScreen< ++ const Linking extends StaticScreenConfigLinking, ++ const Screen extends StaticScreenConfigScreen, ++ >( ++ config: StaticScreenConfigInput< ++ Linking, ++ Screen, ++ TabNavigationState, ++ MyNavigationOptions, ++ MyNavigationEventMap, ++ MyNavigationProp ++ > ++ ): StaticScreenConfigResult< ++ Linking, ++ Screen, ++ TabNavigationState, ++ MyNavigationOptions, ++ MyNavigationEventMap, ++ MyNavigationProp ++ > { ++ // @ts-expect-error: there is some issue with the generic inference here ++ return config; ++ } +``` + +See [Custom navigators](custom-navigators.md) for more details. + +### Changes to navigators + +#### Native Bottom Tabs are now default + +Previously, the Bottom Tab Navigator used a JavaScript-based implementation and a native implementation was available under `@react-navigation/bottom-tabs/unstable`. Native bottom tabs are not used by default on iOS and Android. This allows us to match the new native design such as liquid glass effect on iOS 26. + +The `@react-navigation/bottom-tabs/unstable` entry point has been removed. + +To keep the previous behavior with JavaScript-based tabs, you can pass `implementation: 'custom'` to the navigator: + + + + +```diff lang=js +createBottomTabNavigator({ ++ implementation: 'custom', + // ... +}); +``` + + + + +```diff lang=js + +``` + + + + +As part of this change, some of the options have changed to work with native tabs: + +- `tabBarShowLabel` is replaced with `tabBarLabelVisibilityMode` which accepts: + - `"auto"` (default) + - `"selected"` + - `"labeled"` - same as `tabBarShowLabel: true` + - `"unlabeled"` - same as `tabBarShowLabel: false` +- `tabBarLabel` now only accepts a `string` +- `tabBarIcon` now accepts an function that returns an icon object + +The following props have been removed: + +- `safeAreaInsets` from the navigator props +- `insets` from the bottom tab bar props +- `layout` from the bottom tab bar props + +See the [Bottom Tab Navigator docs](bottom-tab-navigator.md) for all the available options. + +#### Navigators no longer accept an `id` prop + +Previously, navigators accepted an `id` prop to identify them - which was used with `navigation.getParent(id)` to get a parent navigator by id. However, there were a couple of issues with this approach: + +- It wasn't well integrated with TypeScript types, and required manual annotations. +- The navigation object is specific to a screen, so using the navigator's id was inconsistent. +- It was used for a very specific use case, so it added unnecessary complexity. + +In React Navigation 8, we removed the `id` prop from navigators. Instead, you can use the screen's name to get a parent navigator: + +```diff lang=js +- const parent = navigation.getParent('some-id'); ++ const parent = navigation.getParent('SomeScreenName'); +``` + +In this case, 'SomeScreenName' refers to the name of a parent screen that's used in the navigator. + +See [navigation object docs](navigation-object.md#getparent) for more details. + +#### `setParams` no longer pushes to history in tab and drawer navigators when `backBehavior` is set to `fullHistory` + +Previously, when using `setParams` in tab and drawer navigators with `backBehavior` set to `fullHistory`, it would push a new entry to the history stack. + +In React Navigation 8, we [added a new `pushParams` action](#params-can-now-be-updated-with-pushparams-and-replaceparams-actions) that achieves this behavior. So `setParams` now only updates the params without affecting the history stack. + +```diff lang=js +- navigation.setParams({ filter: 'new' }); ++ navigation.pushParams({ filter: 'new' }); +``` + +This way you have more control over how params are updated in tab and drawer navigators. + +See [`setParams` action docs](navigation-actions.md#setparams) for more details. + +#### Navigators no longer use `InteractionManager` + +Previously, various navigators used `InteractionManager` to mark when animations and gestures were in progress. This was primarily used to defer code that should run after transitions, such as loading data or rendering heavy components. + +However, `InteractionManager` has been deprecated in latest React Native versions, so we are removing support for this API in React Navigation 8. As an alternative, consumers can listen to events such as `transitionStart`, `transitionEnd` etc. when applicable: + +```diff lang=js +- InteractionManager.runAfterInteractions(() => { +- // code to run after transition +- }); ++ navigation.addListener('transitionEnd', () => { ++ // code to run after transition ++ }); +``` + +Keep in mind that unlike `InteractionManager` which is global, the transition events are specific to a navigator. + +If you have a use case that cannot be solved with transition events, please open a [discussion on GitHub](https://github.com/react-navigation/react-navigation/discussions). + +#### Drawer Navigator now accepts `overlayStyle` instead of `overlayColor` + +Previously, the Drawer Navigator accepted an `overlayColor` prop to customize the color of the overlay that appears when the drawer is open. It now accepts `overlayStyle` prop instead to provide more flexibility for styling the overlay: + +```diff lang=js +- overlayColor="rgba(0, 0, 0, 0.5)" ++ overlayStyle={{ backgroundColor: 'rgba(0, 0, 0, 0.5)' }} +``` + +See [Drawer Navigator](drawer-navigator.md) for more details. + +#### APIs for customizing Navigation bar and status bar colors are removed from Native Stack Navigator + +Previously, Native Stack Navigator provided options to customize the appearance of the navigation bar and status bar on Android: + +- `navigationBarColor` +- `navigationBarTranslucent` +- `statusBarBackgroundColor` +- `statusBarTranslucent` + +In Android 15 and onwards, edge-to-edge is now the default behavior, and will likely be enforced in future versions. Therefore, these options have been removed in React Navigation 8. + +You can use [`react-native-edge-to-edge`](https://github.com/zoontek/react-native-edge-to-edge) instead to configure status bar and navigation bar related settings. + +See [Native Stack Navigator](native-stack-navigator.md) for all available options. + +#### The color arguments in various navigators now accept `ColorValue` + +Previously, color options in various navigators only accepted string values. In React Navigation 8, these options now accept `ColorValue` to match the [changes to theming](#themes-now-support-colorvalue-and-css-custom-properties). + +Unless you are using a custom theme with `PlatformColor` or `DynamicColorIOS` etc, this change only breaks TypeScript types: + +```diff lang=js +- const tabBarIcon = ({ color, size }: { color: string, size: number }) => { ++ const tabBarIcon = ({ color, size }: { color: ColorValue, size: number }) => { + // ... +}; +``` + +See [Themes](themes.md#using-platform-colors) for more information about dynamic colors. + +### Miscellaneous + +#### Various deprecated APIs have been removed + +The following API that were marked as deprecated in React Navigation 7 have been removed: + +- `navigateDeprecated` from the navigation object has been removed. Use `navigate` instead. To preserve the previous behavior, you can pass `pop: true` as the third argument to `navigate`: + + ```diff lang=js + - navigation.navigateDeprecated('Profile', { userId: 123 }); + + navigation.navigate('Profile', { userId: 123 }, { pop: true }); + ``` + +- `getId` from the navigation object has been removed since the [`id` prop has been removed](#navigators-no-longer-accept-an-id-prop). + +- `navigationInChildEnabled` prop from `NavigationContainer` has been removed. This behavior is no longer supported. + +#### The linking config no longer requires a `prefixes` option + +Previously, the linking configuration required a `prefixes` option to specify the URL prefixes that the app should handle. This historical reason for this is to support Expo Go which uses a custom URL scheme. + +Since then, the recommended way to develop with Expo has been to use [Development Builds](https://docs.expo.dev/develop/development-builds/introduction/), which use the app's own URL scheme. So the `prefixes` option is not needed for most use cases. + +You can now omit the `prefixes` option in the linking configuration unless you're using Expo Go: + + + + +```diff lang=js + +``` + + + + +```diff lang=js + +``` + + + + +The `prefixes` default to `['*']`, which will match any host starting with `http`, `https`, and custom schemes such as `myapp://`. + +See [Configuring links](configuring-links.md) for more details. + +#### Some exports are removed from `@react-navigation/elements` + +The `@react-navigation/elements` package has exported some components that were primarily intended for internal usage. These components have been removed from the public API: + +- `Background` + + Background color can instead be applied by using it from `useTheme`. + + ```diff lang=js + - import { Background } from '@react-navigation/elements'; + + import { useTheme } from '@react-navigation/native'; + // ... + - {children} + + const { colors } = useTheme(); + + + + {children} + ``` + +- `Screen` + + You can render the `Header` component directly instead. + +- `SafeAreaProviderCompat` + + You can use `SafeAreaProvider` from [`react-native-safe-area-context`](https://github.com/AppAndFlow/react-native-safe-area-context) directly instead. + +- `MissingIcon` + + You can copy the implementation from the [source code](https://github.com/react-navigation/react-navigation/blob/main/packages/elements/src/MissingIcon.tsx) if you need a placeholder icon. + +Some of these components are still available and exported at `@react-navigation/elements/internal`, so you can continue using them if you really need. However, since they are not part of the public API, they don't follow semver and may change without warning in future releases. + +#### The `getDefaultHeaderHeight` utility now accepts an object instead of positional arguments + +The `getDefaultHeaderHeight` utility from `@react-navigation/elements` now accepts an object with named properties instead of positional arguments to improve readability" + +```diff lang=js +- getDefaultHeaderHeight(layout, false, statusBarHeight); ++ getDefaultHeaderHeight({ ++ landscape: false, ++ modalPresentation: false, ++ topInset: statusBarHeight ++ }); +``` + +See [Elements docs](elements.md#getdefaultheaderheight) for more details. + +## New features + +### Common hooks now accept name of the screen + +The `useNavigation`, `useRoute`, and `useNavigationState` hooks can now optionally accept the name of the screen: + +```js +const route = useRoute('Profile'); +``` + +The name of the screen can be for the current screen or any of its parent screens. This makes it possible to get params and navigation state for a parent screen without needing to setup context to pass them down. + +If the provided screen name does not exist in any of the parent screens, it will throw an error, so any mistakes are caught early. + +When using static configuration, the types are automatically inferred based on the name of the screen. + +It's still possible to use these hooks without passing the screen name, same as before, and it will return the navigation or route for the current screen. + +See [`useNavigation`](use-navigation.md), [`useRoute`](use-route.md), and [`useNavigationState`](use-navigation-state.md) for more details. + +### Params can now be updated with `pushParams` and `replaceParams` actions + +In addition to the existing `setParams` action, two new actions have been added to update params: + +#### `pushParams` + +The `pushParams` action updates the params and pushes a new entry to the history stack: + +```js +navigation.pushParams({ filter: 'new' }); +``` + +Unlike `setParams`, this does not merge the new params with the existing ones. Instead, it uses the new params object as-is. + +The action works in all navigators, such as stack, tab, and drawer. This allows to add a new entry to the history stack without needing to push a new screen instance. + +This can be useful in various scenario: + +- A product listing page with filters, where changing filters should create a new history entry so that users can go back to previous filter states. +- A screen with a custom modal component, where the modal is not a separate screen in the navigator, but its state should be reflected in the URL and history. + +See [`pushParams` docs](navigation-actions.md#pushparams) for more details. + +#### `replaceParams` + +The `replaceParams` action updates the params similarly to `setParams`, but replaces the params entirely instead of merging them: + +```js +navigation.replaceParams({ filter: 'popular' }); +``` + +See [`replaceParams` docs](navigation-actions#replaceparams) for more details. + +### Themes now support `ColorValue` and CSS custom properties + +Previously, theme colors only supported string values. In React Navigation 8, theme colors now support `PlatformColor`, `DynamicColorIOS` on native, and CSS custom properties on Web for more flexibility. + +Example theme using `PlatformColor`: + +```js +const MyTheme = { + ...DefaultTheme, + colors: Platform.select({ + ios: () => ({ + primary: PlatformColor('systemRed'), + background: PlatformColor('systemGroupedBackground'), + card: PlatformColor('tertiarySystemBackground'), + text: PlatformColor('label'), + border: PlatformColor('separator'), + notification: PlatformColor('systemRed'), + }), + android: () => ({ + primary: PlatformColor('@android:color/system_primary_light'), + background: PlatformColor( + '@android:color/system_surface_container_light' + ), + card: PlatformColor('@android:color/system_background_light'), + text: PlatformColor('@android:color/system_on_surface_light'), + border: PlatformColor('@android:color/system_outline_variant_light'), + notification: PlatformColor('@android:color/holo_red_light'), + }), + default: () => DefaultTheme.colors, + })(), +}; +``` + +See [Themes](themes.md#using-platform-colors) for more details. + +### Groups now support `linking` option in static configuration + +The `linking` option can now be specified for groups in static configuration to define nested paths: + +```js +const Stack = createStackNavigator({ + groups: { + Settings: { + linking: { path: 'settings' }, + screens: { + UserSettings: 'user', + AppSettings: 'app', + }, + }, + }, +}); +``` + +This lets you prefix the paths of the screens in the group with a common prefix, e.g. `settings/` for `settings/user` and `settings/app`. + +See [Group](group.md) for more details. + +### Navigators now accept a `router` prop + +A router defines how the navigator updates its state based on navigation actions. Previously, custom routers could only be used by [creating a custom navigator](custom-navigators.md#extending-navigators). + +We later added an experimental `UNSTABLE_router` prop to various navigators to customize the router without needing to create a custom navigator. In React Navigation 8, we have dropped the `UNSTABLE_` prefix and made it a stable API. + +```js static2dynamic +const MyStack = createNativeStackNavigator({ + // highlight-start + router: (original) => ({ + getStateForAction(state, action) { + if (action.type === 'NAVIGATE') { + // Custom logic for NAVIGATE action + } + + // Fallback to original behavior + return original.getStateForAction(state, action); + }, + }), + // highlight-end + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +``` + +See [`Navigator` docs](navigator.md#router) for more details. + +### `Header` from `@react-navigation/elements` has been reworked + +The `Header` component from `@react-navigation/elements` has been reworked with various improvements: + +- It uses the new liquid glass effect on iOS 26 +- It supports `ColorValue` and CSS custom properties for colors +- It supports `headerBlurEffect` on Web (previously only supported on iOS in Native Stack Navigator) +- It no longer needs the layout of the screen to render correctly + +To match the iOS 26 design, the back button title is no longer shown by default on iOS 26. + +See [Elements](elements.md) for more details. + +### `react-native-tab-view` now supports a `renderAdapter` prop for custom adapters + +By default, `react-native-tab-view` uses [`react-native-pager-view`](https://github.com/callstack/react-native-pager-view) for rendering pages on Android and iOS. However, it may not be suitable for all use cases. + +So it now supports a `renderAdapter` prop to provide a custom adapter for rendering pages. For example, you can use `ScrollViewAdapter` to use a `ScrollView` for rendering pages: + +```js +import React from 'react'; +import { TabView, ScrollViewAdapter } from 'react-native-tab-view'; + +export default function TabViewExample() { + const [index, setIndex] = React.useState(0); + + return ( + + ); +} +``` + +You can also create your own custom adapter by implementing the required interface. See the [`react-native-tab-view` docs](tab-view.md) for more information. diff --git a/versioned_docs/version-8.x/use-focus-effect.md b/versioned_docs/version-8.x/use-focus-effect.md index 771a42aeca..ec6d7aae01 100755 --- a/versioned_docs/version-8.x/use-focus-effect.md +++ b/versioned_docs/version-8.x/use-focus-effect.md @@ -100,20 +100,22 @@ If you don't ignore the result, then you might end up with inconsistent data due The `useFocusEffect` hook runs the effect as soon as the screen comes into focus. This often means that if there is an animation for the screen change, it might not have finished yet. -React Navigation runs its animations in native thread, so it's not a problem in many cases. But if the effect updates the UI or renders something expensive, then it can affect the animation performance. In such cases, we can use [`InteractionManager`](https://reactnative.dev/docs/interactionmanager) to defer our work until the animations or gestures have finished: +The navigators in React Navigation run animations in native thread when possible, so it's not a problem in most cases. But if the effect updates the UI or renders something expensive, then it can affect the animation performance. In such cases, you can listen to the `transitionEnd` event to defer your work until the transition has finished: ```js useFocusEffect( React.useCallback(() => { - const task = InteractionManager.runAfterInteractions(() => { - // Expensive task + const unsubscribe = navigation.addListener('transitionEnd', () => { + // Expensive task after transition finishes }); - return () => task.cancel(); - }, []) + return unsubscribe; + }, [navigation]) ); ``` +Keep in mind that the `transitionEnd` event is specific to stack navigators. For other navigators, you may need to use different approaches or events. + ## How is `useFocusEffect` different from adding a listener for `focus` event The `focus` event fires when a screen comes into focus. Since it's an event, your listener won't be called if the screen was already focused when you subscribed to the event. This also doesn't provide a way to perform a cleanup function when the screen becomes unfocused. You can subscribe to the `blur` event and handle it manually, but it can get messy. You will usually need to handle `componentDidMount` and `componentWillUnmount` as well in addition to these events, which complicates it even more. diff --git a/versioned_docs/version-8.x/use-navigation-state.md b/versioned_docs/version-8.x/use-navigation-state.md index c620118dc9..e3d9f9a7ed 100755 --- a/versioned_docs/version-8.x/use-navigation-state.md +++ b/versioned_docs/version-8.x/use-navigation-state.md @@ -15,18 +15,94 @@ Consider the navigator's state object to be internal and subject to change in a ::: -It takes a selector function as an argument. The selector will receive the full [navigation state](navigation-state.md) and can return a specific value from the state: +It can be used in two ways. -```js -const index = useNavigationState((state) => state.index); +## Getting the navigation state by screen name + +The hook accepts the name of the current screen or any of its parent screens as the first argument to get the navigation state for the navigator containing that screen. The second argument is a selector function that receives the full [navigation state](navigation-state.md) and can return a specific value from the state: + +```js name="useNavigationState hook" snack static2dynamic +import * as React from 'react'; +import { Button } from '@react-navigation/elements'; +import { View, Text } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +// codeblock-focus-start +import { useNavigationState } from '@react-navigation/native'; + +function CurrentRouteDisplay() { + // highlight-start + const focusedRouteName = useNavigationState( + 'Home', + (state) => state.routes[state.index] + ); + // highlight-end + + return Current route: {focusedRouteName}; +} +// codeblock-focus-end + +function HomeScreen() { + const navigation = useNavigation('Home'); + + return ( + + Home Screen + + + + ); +} + +function ProfileScreen() { + const navigation = useNavigation('Profile'); + + return ( + + Profile Screen + + + + ); +} + +const RootStack = createNativeStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(RootStack); + +export default function App() { + return ; +} ``` The selector function helps to reduce unnecessary re-renders, so your screen will re-render only when that's something you care about. If you actually need the whole state object, you can do this explicitly: ```js -const state = useNavigationState((state) => state); +const state = useNavigationState('Home', (state) => state); ``` +## Getting the current navigation state + +You can also use `useNavigationState` without a screen name to get the navigation state for the current screen's navigator. In this case, it takes a selector function as the only argument: + +```js +const focusedRouteName = useNavigationState( + (state) => state.routes[state.index].name +); +``` + +This is often useful for re-usable components that are used across multiple screens. + :::warning This hook is useful for advanced cases and it's easy to introduce performance issues if you're not careful. For most of the cases, you don't need the navigator's state. diff --git a/versioned_docs/version-8.x/use-navigation.md b/versioned_docs/version-8.x/use-navigation.md index 03c019158d..100f307027 100755 --- a/versioned_docs/version-8.x/use-navigation.md +++ b/versioned_docs/version-8.x/use-navigation.md @@ -9,7 +9,11 @@ import TabItem from '@theme/TabItem'; `useNavigation` is a hook that gives access to `navigation` object. It's useful when you cannot pass the `navigation` object as a prop to the component directly, or don't want to pass it in case of a deeply nested child. -The `useNavigation` hook returns the `navigation` object of the screen where it's used: +It can be used in two ways. + +## Getting the navigation object by screen name + +The hook accepts the name of the current screen or any of its parent screens to get the corresponding navigation object: ```js name="useNavigation hook" snack static2dynamic import * as React from 'react'; @@ -22,7 +26,7 @@ import { useNavigation } from '@react-navigation/native'; function MyBackButton() { // highlight-next-line - const navigation = useNavigation(); + const navigation = useNavigation('Profile'); return ( ; +} +``` + +This is often useful for re-usable components that are used across multiple screens. See the documentation for the [`navigation` object](navigation-object.md) for more info. diff --git a/versioned_docs/version-8.x/use-route.md b/versioned_docs/version-8.x/use-route.md index 0a94305c5e..1f2cf2fc68 100755 --- a/versioned_docs/version-8.x/use-route.md +++ b/versioned_docs/version-8.x/use-route.md @@ -9,9 +9,11 @@ import TabItem from '@theme/TabItem'; `useRoute` is a hook which gives access to `route` object. It's useful when you cannot pass down the `route` object from props to the component, or don't want to pass it in case of a deeply nested child. -`useRoute()` returns the `route` object of the screen it's inside. +It can be used in two ways. -## Example +## Getting the route object by screen name + +The hook accepts the name of the current screen or any of its parent screens to get the corresponding route object: ```js name="useRoute hook" snack static2dynamic import * as React from 'react'; @@ -27,14 +29,14 @@ import { useRoute } from '@react-navigation/native'; function MyText() { // highlight-next-line - const route = useRoute(); + const route = useRoute('Profile'); return {route.params.caption}; } // codeblock-focus-end function HomeScreen() { - const navigation = useNavigation(); + const navigation = useNavigation('Home'); return ( @@ -76,7 +78,19 @@ function App() { export default App; ``` -Check how to setup `useRoute` with TypeScript [here](typescript.md#annotating-useroute). +## Getting the current route object + +You can also use `useRoute` without any arguments to get the route object for the current screen: + +```js +function MyComponent() { + const route = useRoute(); + + return {route.name}; +} +``` + +This is often useful for re-usable components that are used across multiple screens. See the documentation for the [`route` object](route-object.md) for more info. @@ -94,7 +108,7 @@ class MyText extends React.Component { // Wrap and export export default function (props) { - const route = useRoute(); + const route = useRoute('Profile'); return ; } diff --git a/versioned_sidebars/version-8.x-sidebars.json b/versioned_sidebars/version-8.x-sidebars.json index 2467400dbf..b0bed7c7f5 100644 --- a/versioned_sidebars/version-8.x-sidebars.json +++ b/versioned_sidebars/version-8.x-sidebars.json @@ -36,13 +36,12 @@ "testing", "typescript", "troubleshooting", - "upgrading-from-6.x" + "upgrading-from-7.x" ], "Navigators": [ "stack-navigator", "native-stack-navigator", "bottom-tab-navigator", - "native-bottom-tab-navigator", "drawer-navigator", "material-top-tab-navigator" ], diff --git a/yarn.lock b/yarn.lock index 58a58cd967..e4fdf243a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4282,6 +4282,15 @@ __metadata: languageName: node linkType: hard +"@types/mdast@npm:^4.0.3": + version: 4.0.4 + resolution: "@types/mdast@npm:4.0.4" + dependencies: + "@types/unist": "npm:*" + checksum: 84f403dbe582ee508fd9c7643ac781ad8597fcbfc9ccb8d4715a2c92e4545e5772cbd0dbdf18eda65789386d81b009967fdef01b24faf6640f817287f54d9c82 + languageName: node + linkType: hard + "@types/mdx@npm:^2.0.0": version: 2.0.10 resolution: "@types/mdx@npm:2.0.10" @@ -4542,6 +4551,17 @@ __metadata: languageName: node linkType: hard +"@voxpelli/node-test-pretty-reporter@npm:^1.1.2": + version: 1.1.2 + resolution: "@voxpelli/node-test-pretty-reporter@npm:1.1.2" + dependencies: + clean-stack: "npm:^5.2.0" + jest-diff: "npm:^29.7.0" + markdown-or-chalk: "npm:^0.2.0" + checksum: ae08c9aba06b751f6d8a2d7ef0f77dd5bf6e9d86cd8724ad9622fd7b85dac3f56082b68a5b0586bec8adb5ec01e14a2c665bfe1b054479e6a515d90f0e2559aa + languageName: node + linkType: hard + "@webassemblyjs/ast@npm:1.14.1, @webassemblyjs/ast@npm:^1.12.1": version: 1.14.1 resolution: "@webassemblyjs/ast@npm:1.14.1" @@ -4907,6 +4927,15 @@ __metadata: languageName: node linkType: hard +"ansi-escapes@npm:^5.0.0": + version: 5.0.0 + resolution: "ansi-escapes@npm:5.0.0" + dependencies: + type-fest: "npm:^1.0.2" + checksum: f705cc7fbabb981ddf51562cd950792807bccd7260cc3d9478a619dda62bff6634c87ca100f2545ac7aade9b72652c4edad8c7f0d31a0b949b5fa58f33eaf0d0 + languageName: node + linkType: hard + "ansi-html-community@npm:^0.0.8": version: 0.0.8 resolution: "ansi-html-community@npm:0.0.8" @@ -4948,6 +4977,13 @@ __metadata: languageName: node linkType: hard +"ansi-styles@npm:^5.0.0": + version: 5.2.0 + resolution: "ansi-styles@npm:5.2.0" + checksum: 9c4ca80eb3c2fb7b33841c210d2f20807f40865d27008d7c3f707b7f95cab7d67462a565e2388ac3285b71cb3d9bb2173de8da37c57692a362885ec34d6e27df + languageName: node + linkType: hard + "ansi-styles@npm:^6.1.0": version: 6.2.1 resolution: "ansi-styles@npm:6.2.1" @@ -4955,6 +4991,20 @@ __metadata: languageName: node linkType: hard +"ansi-styles@npm:^6.2.1": + version: 6.2.3 + resolution: "ansi-styles@npm:6.2.3" + checksum: 23b8a4ce14e18fb854693b95351e286b771d23d8844057ed2e7d083cd3e708376c3323707ec6a24365f7d7eda3ca00327fe04092e29e551499ec4c8b7bfac868 + languageName: node + linkType: hard + +"any-promise@npm:^1.0.0": + version: 1.3.0 + resolution: "any-promise@npm:1.3.0" + checksum: 60f0298ed34c74fef50daab88e8dab786036ed5a7fad02e012ab57e376e0a0b4b29e83b95ea9b5e7d89df762f5f25119b83e00706ecaccb22cfbacee98d74889 + languageName: node + linkType: hard + "anymatch@npm:~3.1.2": version: 3.1.3 resolution: "anymatch@npm:3.1.3" @@ -5241,6 +5291,22 @@ __metadata: languageName: node linkType: hard +"boxen@npm:^8.0.1": + version: 8.0.1 + resolution: "boxen@npm:8.0.1" + dependencies: + ansi-align: "npm:^3.0.1" + camelcase: "npm:^8.0.0" + chalk: "npm:^5.3.0" + cli-boxes: "npm:^3.0.0" + string-width: "npm:^7.2.0" + type-fest: "npm:^4.21.0" + widest-line: "npm:^5.0.0" + wrap-ansi: "npm:^9.0.0" + checksum: 8c54f9797bf59eec0b44c9043d9cb5d5b2783dc673e4650235e43a5155c43334e78ec189fd410cf92056c1054aee3758279809deed115b49e68f1a1c6b3faa32 + languageName: node + linkType: hard + "brace-expansion@npm:^1.1.7": version: 1.1.11 resolution: "brace-expansion@npm:1.1.11" @@ -5432,6 +5498,13 @@ __metadata: languageName: node linkType: hard +"camelcase@npm:^8.0.0": + version: 8.0.0 + resolution: "camelcase@npm:8.0.0" + checksum: 56c5fe072f0523c9908cdaac21d4a3b3fb0f608fb2e9ba90a60e792b95dd3bb3d1f3523873ab17d86d146e94171305f73ef619e2f538bd759675bc4a14b4bff3 + languageName: node + linkType: hard + "caniuse-api@npm:^3.0.0": version: 3.0.0 resolution: "caniuse-api@npm:3.0.0" @@ -5486,6 +5559,13 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^5.3.0": + version: 5.6.2 + resolution: "chalk@npm:5.6.2" + checksum: 99a4b0f0e7991796b1e7e3f52dceb9137cae2a9dfc8fc0784a550dc4c558e15ab32ed70b14b21b52beb2679b4892b41a0aa44249bcb996f01e125d58477c6976 + languageName: node + linkType: hard + "char-regex@npm:^1.0.2": version: 1.0.2 resolution: "char-regex@npm:1.0.2" @@ -5634,6 +5714,15 @@ __metadata: languageName: node linkType: hard +"clean-stack@npm:^5.2.0": + version: 5.3.0 + resolution: "clean-stack@npm:5.3.0" + dependencies: + escape-string-regexp: "npm:5.0.0" + checksum: 1aa8b6772eed1f678a9dcf6e02c74c59f26b6fdad26eaaca1dc6a367ff19c924315836b6143484c2686366758e05396f1ac0f32aaa70481b11d8e23790947ca0 + languageName: node + linkType: hard + "cli-boxes@npm:^3.0.0": version: 3.0.0 resolution: "cli-boxes@npm:3.0.0" @@ -5641,6 +5730,22 @@ __metadata: languageName: node linkType: hard +"cli-highlight@npm:^2.1.11": + version: 2.1.11 + resolution: "cli-highlight@npm:2.1.11" + dependencies: + chalk: "npm:^4.0.0" + highlight.js: "npm:^10.7.1" + mz: "npm:^2.4.0" + parse5: "npm:^5.1.1" + parse5-htmlparser2-tree-adapter: "npm:^6.0.0" + yargs: "npm:^16.0.0" + bin: + highlight: bin/highlight + checksum: b5b4af3b968aa9df77eee449a400fbb659cf47c4b03a395370bd98d5554a00afaa5819b41a9a8a1ca0d37b0b896a94e57c65289b37359a25b700b1f56eb04852 + languageName: node + linkType: hard + "cli-table3@npm:^0.6.3": version: 0.6.3 resolution: "cli-table3@npm:0.6.3" @@ -5654,6 +5759,17 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^7.0.2": + version: 7.0.4 + resolution: "cliui@npm:7.0.4" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.0" + wrap-ansi: "npm:^7.0.0" + checksum: 6035f5daf7383470cef82b3d3db00bec70afb3423538c50394386ffbbab135e26c3689c41791f911fa71b62d13d3863c712fdd70f0fbdffd938a1e6fd09aac00 + languageName: node + linkType: hard + "clone-deep@npm:^4.0.1": version: 4.0.1 resolution: "clone-deep@npm:4.0.1" @@ -6299,6 +6415,18 @@ __metadata: languageName: node linkType: hard +"dedent@npm:^1.7.0": + version: 1.7.0 + resolution: "dedent@npm:1.7.0" + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + checksum: c5e8a8beb5072bd5e520cb64b27a82d7ec3c2a63ee5ce47dbc2a05d5b7700cefd77a992a752cd0a8b1d979c1db06b14fb9486e805f3ad6088eda6e07cd9bf2d5 + languageName: node + linkType: hard + "deep-extend@npm:^0.6.0": version: 0.6.0 resolution: "deep-extend@npm:0.6.0" @@ -6439,6 +6567,13 @@ __metadata: languageName: node linkType: hard +"diff-sequences@npm:^29.6.3": + version: 29.6.3 + resolution: "diff-sequences@npm:29.6.3" + checksum: 32e27ac7dbffdf2fb0eb5a84efd98a9ad084fbabd5ac9abb8757c6770d5320d2acd172830b28c4add29bb873d59420601dfc805ac4064330ce59b1adfd0593b2 + languageName: node + linkType: hard + "dir-glob@npm:^3.0.1": version: 3.0.1 resolution: "dir-glob@npm:3.0.1" @@ -6600,6 +6735,13 @@ __metadata: languageName: node linkType: hard +"emoji-regex@npm:^10.3.0": + version: 10.6.0 + resolution: "emoji-regex@npm:10.6.0" + checksum: 1e4aa097bb007301c3b4b1913879ae27327fdc48e93eeefefe3b87e495eb33c5af155300be951b4349ff6ac084f4403dc9eff970acba7c1c572d89396a9a32d7 + languageName: node + linkType: hard + "emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" @@ -6756,6 +6898,13 @@ __metadata: languageName: node linkType: hard +"escape-string-regexp@npm:5.0.0, escape-string-regexp@npm:^5.0.0": + version: 5.0.0 + resolution: "escape-string-regexp@npm:5.0.0" + checksum: 6366f474c6f37a802800a435232395e04e9885919873e382b157ab7e8f0feb8fed71497f84a6f6a81a49aab41815522f5839112bd38026d203aea0c91622df95 + languageName: node + linkType: hard + "escape-string-regexp@npm:^1.0.5": version: 1.0.5 resolution: "escape-string-regexp@npm:1.0.5" @@ -6770,13 +6919,6 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:^5.0.0": - version: 5.0.0 - resolution: "escape-string-regexp@npm:5.0.0" - checksum: 6366f474c6f37a802800a435232395e04e9885919873e382b157ab7e8f0feb8fed71497f84a6f6a81a49aab41815522f5839112bd38026d203aea0c91622df95 - languageName: node - linkType: hard - "eslint-scope@npm:5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" @@ -7314,7 +7456,14 @@ __metadata: languageName: node linkType: hard -"get-east-asian-width@npm:^1.3.0": +"get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde + languageName: node + linkType: hard + +"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.3.0": version: 1.4.0 resolution: "get-east-asian-width@npm:1.4.0" checksum: 4e481d418e5a32061c36fbb90d1b225a254cc5b2df5f0b25da215dcd335a3c111f0c2023ffda43140727a9cafb62dac41d022da82c08f31083ee89f714ee3b83 @@ -7770,6 +7919,13 @@ __metadata: languageName: node linkType: hard +"highlight.js@npm:^10.7.1": + version: 10.7.3 + resolution: "highlight.js@npm:10.7.3" + checksum: 073837eaf816922427a9005c56c42ad8786473dc042332dfe7901aa065e92bc3d94ebf704975257526482066abb2c8677cc0326559bb8621e046c21c5991c434 + languageName: node + linkType: hard + "history@npm:^4.9.0": version: 4.10.1 resolution: "history@npm:4.10.1" @@ -8428,6 +8584,13 @@ __metadata: languageName: node linkType: hard +"is-unicode-supported@npm:^2.0.0": + version: 2.1.0 + resolution: "is-unicode-supported@npm:2.1.0" + checksum: a0f53e9a7c1fdbcf2d2ef6e40d4736fdffff1c9f8944c75e15425118ff3610172c87bf7bc6c34d3903b04be59790bb2212ddbe21ee65b5a97030fc50370545a5 + languageName: node + linkType: hard + "is-wsl@npm:^2.2.0": version: 2.2.0 resolution: "is-wsl@npm:2.2.0" @@ -8501,6 +8664,25 @@ __metadata: languageName: node linkType: hard +"jest-diff@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-diff@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + diff-sequences: "npm:^29.6.3" + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 89a4a7f182590f56f526443dde69acefb1f2f0c9e59253c61d319569856c4931eae66b8a3790c443f529267a0ddba5ba80431c585deed81827032b2b2a1fc999 + languageName: node + linkType: hard + +"jest-get-type@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-get-type@npm:29.6.3" + checksum: 552e7a97a983d3c2d4e412a44eb7de0430ff773dd99f7500962c268d6dfbfa431d7d08f919c9d960530e5f7f78eb47f267ad9b318265e5092b3ff9ede0db7c2b + languageName: node + linkType: hard + "jest-util@npm:^29.7.0": version: 29.7.0 resolution: "jest-util@npm:29.7.0" @@ -9023,6 +9205,24 @@ __metadata: languageName: node linkType: hard +"markdown-or-chalk@npm:^0.2.0": + version: 0.2.3 + resolution: "markdown-or-chalk@npm:0.2.3" + dependencies: + "@types/mdast": "npm:^4.0.3" + boxen: "npm:^8.0.1" + chalk: "npm:^5.3.0" + cli-highlight: "npm:^2.1.11" + is-unicode-supported: "npm:^2.0.0" + mdast-util-gfm-table: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.1.0" + mdast-util-to-string: "npm:^4.0.0" + strip-ansi: "npm:^7.1.0" + terminal-link: "npm:^3.0.0" + checksum: 003580a34ec6fc465f552ccb4d50da50162a10121f3f8147da9a7ff126a4a4fffd216e7e79e5ea842fe829fd327598026caa0f72b203c8fe576793326b0efd0d + languageName: node + linkType: hard + "markdown-table@npm:^2.0.0": version: 2.0.0 resolution: "markdown-table@npm:2.0.0" @@ -9357,6 +9557,23 @@ __metadata: languageName: node linkType: hard +"mdast-util-to-markdown@npm:^2.1.0": + version: 2.1.2 + resolution: "mdast-util-to-markdown@npm:2.1.2" + dependencies: + "@types/mdast": "npm:^4.0.0" + "@types/unist": "npm:^3.0.0" + longest-streak: "npm:^3.0.0" + mdast-util-phrasing: "npm:^4.0.0" + mdast-util-to-string: "npm:^4.0.0" + micromark-util-classify-character: "npm:^2.0.0" + micromark-util-decode-string: "npm:^2.0.0" + unist-util-visit: "npm:^5.0.0" + zwitch: "npm:^2.0.0" + checksum: 4649722a6099f12e797bd8d6469b2b43b44e526b5182862d9c7766a3431caad2c0112929c538a972f214e63c015395e5d3f54bd81d9ac1b16e6d8baaf582f749 + languageName: node + linkType: hard + "mdast-util-to-string@npm:^4.0.0": version: 4.0.0 resolution: "mdast-util-to-string@npm:4.0.0" @@ -10335,6 +10552,17 @@ __metadata: languageName: node linkType: hard +"mz@npm:^2.4.0": + version: 2.7.0 + resolution: "mz@npm:2.7.0" + dependencies: + any-promise: "npm:^1.0.0" + object-assign: "npm:^4.0.1" + thenify-all: "npm:^1.0.0" + checksum: 103114e93f87362f0b56ab5b2e7245051ad0276b646e3902c98397d18bb8f4a77f2ea4a2c9d3ad516034ea3a56553b60d3f5f78220001ca4c404bd711bd0af39 + languageName: node + linkType: hard + "nanoid@npm:^3.3.11": version: 3.3.11 resolution: "nanoid@npm:3.3.11" @@ -10513,7 +10741,7 @@ __metadata: languageName: node linkType: hard -"object-assign@npm:^4.1.1": +"object-assign@npm:^4.0.1, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" checksum: 1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 @@ -10747,6 +10975,15 @@ __metadata: languageName: node linkType: hard +"parse5-htmlparser2-tree-adapter@npm:^6.0.0": + version: 6.0.1 + resolution: "parse5-htmlparser2-tree-adapter@npm:6.0.1" + dependencies: + parse5: "npm:^6.0.1" + checksum: dfa5960e2aaf125707e19a4b1bc333de49232eba5a6ffffb95d313a7d6087c3b7a274b58bee8d3bd41bdf150638815d1d601a42bbf2a0345208c3c35b1279556 + languageName: node + linkType: hard + "parse5-htmlparser2-tree-adapter@npm:^7.0.0": version: 7.0.0 resolution: "parse5-htmlparser2-tree-adapter@npm:7.0.0" @@ -10757,6 +10994,20 @@ __metadata: languageName: node linkType: hard +"parse5@npm:^5.1.1": + version: 5.1.1 + resolution: "parse5@npm:5.1.1" + checksum: b0f87a77a7fea5f242e3d76917c983bbea47703b9371801d51536b78942db6441cbda174bf84eb30e47315ddc6f8a0b57d68e562c790154430270acd76c1fa03 + languageName: node + linkType: hard + +"parse5@npm:^6.0.1": + version: 6.0.1 + resolution: "parse5@npm:6.0.1" + checksum: 595821edc094ecbcfb9ddcb46a3e1fe3a718540f8320eff08b8cf6742a5114cce2d46d45f95c26191c11b184dcaf4e2960abcd9c5ed9eb9393ac9a37efcfdecb + languageName: node + linkType: hard + "parse5@npm:^7.0.0": version: 7.1.2 resolution: "parse5@npm:7.1.2" @@ -11756,6 +12007,17 @@ __metadata: languageName: node linkType: hard +"pretty-format@npm:^29.7.0": + version: 29.7.0 + resolution: "pretty-format@npm:29.7.0" + dependencies: + "@jest/schemas": "npm:^29.6.3" + ansi-styles: "npm:^5.0.0" + react-is: "npm:^18.0.0" + checksum: edc5ff89f51916f036c62ed433506b55446ff739358de77207e63e88a28ca2894caac6e73dcb68166a606e51c8087d32d400473e6a9fdd2dbe743f46c9c0276f + languageName: node + linkType: hard + "pretty-time@npm:^1.1.0": version: 1.1.0 resolution: "pretty-time@npm:1.1.0" @@ -12011,6 +12273,13 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^18.0.0": + version: 18.3.1 + resolution: "react-is@npm:18.3.1" + checksum: f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 + languageName: node + linkType: hard + "react-is@npm:^19.1.0": version: 19.2.1 resolution: "react-is@npm:19.2.1" @@ -12064,6 +12333,8 @@ __metadata: "@ffprobe-installer/ffprobe": "npm:^2.1.2" "@octokit/graphql": "npm:^9.0.3" "@react-navigation/core": "npm:^7.13.5" + "@voxpelli/node-test-pretty-reporter": "npm:^1.1.2" + dedent: "npm:^1.7.0" escape-html: "npm:^1.0.3" markdownlint: "npm:^0.40.0" markdownlint-cli2: "npm:^0.19.1" @@ -12408,6 +12679,13 @@ __metadata: languageName: node linkType: hard +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 + languageName: node + linkType: hard + "require-from-string@npm:^2.0.2": version: 2.0.2 resolution: "require-from-string@npm:2.0.2" @@ -13176,6 +13454,17 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^7.0.0, string-width@npm:^7.2.0": + version: 7.2.0 + resolution: "string-width@npm:7.2.0" + dependencies: + emoji-regex: "npm:^10.3.0" + get-east-asian-width: "npm:^1.0.0" + strip-ansi: "npm:^7.1.0" + checksum: eb0430dd43f3199c7a46dcbf7a0b34539c76fe3aa62763d0b0655acdcbdf360b3f66f3d58ca25ba0205f42ea3491fa00f09426d3b7d3040e506878fc7664c9b9 + languageName: node + linkType: hard + "string_decoder@npm:^1.1.1": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" @@ -13300,7 +13589,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^7.1.0": +"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" dependencies: @@ -13318,6 +13607,16 @@ __metadata: languageName: node linkType: hard +"supports-hyperlinks@npm:^2.2.0": + version: 2.3.0 + resolution: "supports-hyperlinks@npm:2.3.0" + dependencies: + has-flag: "npm:^4.0.0" + supports-color: "npm:^7.0.0" + checksum: 4057f0d86afb056cd799602f72d575b8fdd79001c5894bcb691176f14e870a687e7981e50bc1484980e8b688c6d5bcd4931e1609816abb5a7dc1486b7babf6a1 + languageName: node + linkType: hard + "supports-preserve-symlinks-flag@npm:^1.0.0": version: 1.0.0 resolution: "supports-preserve-symlinks-flag@npm:1.0.0" @@ -13394,6 +13693,16 @@ __metadata: languageName: node linkType: hard +"terminal-link@npm:^3.0.0": + version: 3.0.0 + resolution: "terminal-link@npm:3.0.0" + dependencies: + ansi-escapes: "npm:^5.0.0" + supports-hyperlinks: "npm:^2.2.0" + checksum: 2ccf93f474d9c4fe1ac75764a48836e61c281def08f4aff154696bc83dd764078ee2f5a6a6148382fb928943d53f44313ae513c5f457649d2961a95e5cd343b3 + languageName: node + linkType: hard + "terser-webpack-plugin@npm:^5.3.10": version: 5.3.10 resolution: "terser-webpack-plugin@npm:5.3.10" @@ -13466,6 +13775,24 @@ __metadata: languageName: node linkType: hard +"thenify-all@npm:^1.0.0": + version: 1.6.0 + resolution: "thenify-all@npm:1.6.0" + dependencies: + thenify: "npm:>= 3.1.0 < 4" + checksum: 9b896a22735e8122754fe70f1d65f7ee691c1d70b1f116fda04fea103d0f9b356e3676cb789506e3909ae0486a79a476e4914b0f92472c2e093d206aed4b7d6b + languageName: node + linkType: hard + +"thenify@npm:>= 3.1.0 < 4": + version: 3.3.1 + resolution: "thenify@npm:3.3.1" + dependencies: + any-promise: "npm:^1.0.0" + checksum: f375aeb2b05c100a456a30bc3ed07ef03a39cbdefe02e0403fb714b8c7e57eeaad1a2f5c4ecfb9ce554ce3db9c2b024eba144843cd9e344566d9fcee73b04767 + languageName: node + linkType: hard + "thingies@npm:^2.5.0": version: 2.5.0 resolution: "thingies@npm:2.5.0" @@ -13591,7 +13918,7 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^1.0.1": +"type-fest@npm:^1.0.1, type-fest@npm:^1.0.2": version: 1.4.0 resolution: "type-fest@npm:1.4.0" checksum: a3c0f4ee28ff6ddf800d769eafafcdeab32efa38763c1a1b8daeae681920f6e345d7920bf277245235561d8117dab765cb5f829c76b713b4c9de0998a5397141 @@ -13605,6 +13932,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^4.21.0": + version: 4.41.0 + resolution: "type-fest@npm:4.41.0" + checksum: f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4 + languageName: node + linkType: hard + "type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" @@ -14233,6 +14567,15 @@ __metadata: languageName: node linkType: hard +"widest-line@npm:^5.0.0": + version: 5.0.0 + resolution: "widest-line@npm:5.0.0" + dependencies: + string-width: "npm:^7.0.0" + checksum: 6bd6cca8cda502ef50e05353fd25de0df8c704ffc43ada7e0a9cf9a5d4f4e12520485d80e0b77cec8a21f6c3909042fcf732aa9281e5dbb98cc9384a138b2578 + languageName: node + linkType: hard + "wildcard@npm:^2.0.0, wildcard@npm:^2.0.1": version: 2.0.1 resolution: "wildcard@npm:2.0.1" @@ -14262,6 +14605,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^9.0.0": + version: 9.0.2 + resolution: "wrap-ansi@npm:9.0.2" + dependencies: + ansi-styles: "npm:^6.2.1" + string-width: "npm:^7.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 3305839b9a0d6fb930cb63a52f34d3936013d8b0682ff3ec133c9826512620f213800ffa19ea22904876d5b7e9a3c1f40682f03597d986a4ca881fa7b033688c + languageName: node + linkType: hard + "write-file-atomic@npm:^3.0.3": version: 3.0.3 resolution: "write-file-atomic@npm:3.0.3" @@ -14331,6 +14685,13 @@ __metadata: languageName: node linkType: hard +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 + languageName: node + linkType: hard + "yallist@npm:^3.0.2": version: 3.1.1 resolution: "yallist@npm:3.1.1" @@ -14345,6 +14706,28 @@ __metadata: languageName: node linkType: hard +"yargs-parser@npm:^20.2.2": + version: 20.2.9 + resolution: "yargs-parser@npm:20.2.9" + checksum: 0685a8e58bbfb57fab6aefe03c6da904a59769bd803a722bb098bd5b0f29d274a1357762c7258fb487512811b8063fb5d2824a3415a0a4540598335b3b086c72 + languageName: node + linkType: hard + +"yargs@npm:^16.0.0": + version: 16.2.0 + resolution: "yargs@npm:16.2.0" + dependencies: + cliui: "npm:^7.0.2" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.0" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^20.2.2" + checksum: b1dbfefa679848442454b60053a6c95d62f2d2e21dd28def92b647587f415969173c6e99a0f3bab4f1b67ee8283bf735ebe3544013f09491186ba9e8a9a2b651 + languageName: node + linkType: hard + "yocto-queue@npm:^1.0.0": version: 1.0.0 resolution: "yocto-queue@npm:1.0.0"