Compare commits

...

132 Commits

Author SHA1 Message Date
satyajit.happy
3aaf6eb648 chore: publish
- @react-navigation/core@5.0.0-alpha.16
 - @react-navigation/native-stack@5.0.0-alpha.2
 - @react-navigation/stack@5.0.0-alpha.27
2019-10-18 16:44:00 +02:00
Janic Duplessis
477c08858d fix: fix header font size config in native stack (#128)
Oups copy paste mistake :o
2019-10-18 16:43:04 +02:00
satyajit.happy
300791ab49 feat: add an option to override safe area insets 2019-10-18 16:20:53 +02:00
satyajit.happy
3e92e22941 fix: rehydrate state before using it 2019-10-18 16:06:21 +02:00
Satyajit Sahoo
a543f1bfc3 feat: make it easier to navigate to a specific route in navigator (#114)
We now use the `params` of the route to determine `initialRouteName` and `initialParams` of a navigator.

So you can do something like this:

```js
navigation.push('Auth', { name: 'Login', params: { token 'xxx' } })
```

This will navigate to the `Login` screen inside the `Auth` navigator.

Closes #90
2019-10-18 15:16:19 +02:00
satyajit.happy
cd5f355bd0 chore: publish
- @react-navigation/drawer@5.0.0-alpha.15
 - @react-navigation/example@5.0.0-alpha.14
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.14
 - @react-navigation/stack@5.0.0-alpha.26
2019-10-18 01:57:14 +02:00
satyajit.happy
cab616069f fix: fix passing content options in drawer 2019-10-18 01:56:04 +02:00
satyajit.happy
f90e00cc93 chore: update paper so types work 2019-10-17 22:52:48 +02:00
satyajit.happy
731cf7d5b1 fix: fix incorrect type 2019-10-16 22:59:45 +02:00
Sirui Li
c6d0c19b49 fix: don't fade incoming background when fading header (#127) 2019-10-16 22:58:31 +02:00
satyajit.happy
442b95d9e4 fix: use header height from style if specified 2019-10-16 15:02:39 +02:00
osdnk
01277575f2 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.14
 - @react-navigation/compat@5.0.0-alpha.8
 - @react-navigation/core@5.0.0-alpha.15
 - @react-navigation/drawer@5.0.0-alpha.14
 - @react-navigation/example@5.0.0-alpha.13
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.13
 - @react-navigation/material-top-tabs@5.0.0-alpha.12
 - @react-navigation/native-stack@5.0.0-alpha.1
 - @react-navigation/native@5.0.0-alpha.12
 - @react-navigation/stack@5.0.0-alpha.25
2019-10-15 16:08:48 +02:00
Michał Osadnik
386d1c0888 fix: block GH interactions in Native Stack example (#126) 2019-10-15 14:28:39 +02:00
satyajit.happy
9d9fe31f02 fix: don't ignore descriptors change 2019-10-15 14:17:49 +02:00
satyajit.happy
0a5fb3e268 fix: fix content component not rendering in drawer 2019-10-11 18:20:56 +02:00
satyajit.happy
1d7a37cd4a chore: fix minor typo 2019-10-11 18:05:32 +02:00
satyajit.happy
2b57702a62 fix: add flex: 1 to drawer content 2019-10-11 17:49:15 +02:00
satyajit.happy
c7da1e4145 fix: increase hitSlop of back button on Android 2019-10-11 13:39:29 +02:00
satyajit.happy
6a0ca90873 feat: add a headerTitleAlign option to center or left align title 2019-10-11 13:38:53 +02:00
osdnk
e9a1cfcec3 chore: bump screens 2019-10-11 13:26:52 +02:00
satyajit.happy
b71f4e52a3 fix: update supported options for native stack 2019-10-11 13:12:39 +02:00
satyajit.happy
9ce8ec59fb refactor: refresh drawer according to latest material design guidelines 2019-10-11 00:54:22 +02:00
satyajit.happy
d685e78fa9 refactor: change tintColor argument to just color 2019-10-11 00:40:39 +02:00
satyajit.happy
7a901af5b5 fix: make it possible to run the example on web 2019-10-11 00:28:38 +02:00
Michał Osadnik
ba3f718ab3 feat: initial version of native stack (#102) 2019-10-10 23:39:04 +02:00
satyajit.happy
42beb660ca fix: improve keyboard handling with bottom tab bar 2019-10-10 14:50:42 +02:00
Satyajit Sahoo
e789846692 fix: make modal presentation mode fullscreen on landscape (#124) 2019-10-08 15:50:52 +02:00
osdnk
b32cda2446 fix: interpolation in iOS modal presentation 2019-10-08 13:01:15 +02:00
satyajit.happy
708dde0e47 feat: export TransitionSpecs 2019-10-07 17:43:37 +02:00
satyajit.happy
5303e8ffb5 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.13
 - @react-navigation/compat@5.0.0-alpha.7
 - @react-navigation/core@5.0.0-alpha.14
 - @react-navigation/drawer@5.0.0-alpha.13
 - @react-navigation/example@5.0.0-alpha.12
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.12
 - @react-navigation/material-top-tabs@5.0.0-alpha.11
 - @react-navigation/native@5.0.0-alpha.11
 - @react-navigation/stack@5.0.0-alpha.24
2019-10-06 16:44:17 +02:00
satyajit.happy
ba6b6ae025 feat: drop header: null in favor of more explitit headerShown option 2019-10-06 15:56:30 +02:00
satyajit.happy
16079d1050 fix: actually expose gestureVelocityImpact in the public API 2019-10-06 04:17:49 +02:00
satyajit.happy
b4a76814c6 fix: use next screen's animation when not focused. fixes #87 2019-10-06 04:13:13 +02:00
Michał Osadnik
8294efc8f4 feat: add gestureVelocityImpact as a prop for stack (#123) 2019-10-06 00:05:42 +02:00
Michał Osadnik
a27ade8881 fix: handling vertical gesture in RTL (#122) 2019-10-06 00:00:43 +02:00
satyajit.happy
615b523d26 fix: don't recompute if routes didn't change 2019-10-05 22:50:09 +02:00
satyajit.happy
070c46ba64 chore: fix react and react-native versions 2019-10-04 14:36:49 +02:00
satyajit.happy
d8394cf919 chore: publish
- @react-navigation/example@5.0.0-alpha.11
 - @react-navigation/stack@5.0.0-alpha.23
2019-10-04 00:54:40 +02:00
satyajit.happy
a7c4a4d7cd fix: fix vertical gesture 2019-10-04 00:53:26 +02:00
satyajit.happy
282b465ae1 chore: add example for modal presentation 2019-10-04 00:12:56 +02:00
satyajit.happy
6f5f4b7d35 fix: fix passing insets to interpolator 2019-10-04 00:01:45 +02:00
satyajit.happy
d75915d1f3 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.12
 - @react-navigation/compat@5.0.0-alpha.6
 - @react-navigation/core@5.0.0-alpha.13
 - @react-navigation/drawer@5.0.0-alpha.12
 - @react-navigation/example@5.0.0-alpha.10
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.11
 - @react-navigation/material-top-tabs@5.0.0-alpha.10
 - @react-navigation/native@5.0.0-alpha.10
 - @react-navigation/stack@5.0.0-alpha.22
2019-10-03 21:33:06 +02:00
satyajit.happy
832ed882bc refactor: use react-native-safe-area-context 2019-10-03 21:31:09 +02:00
satyajit.happy
8318c49331 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.11
 - @react-navigation/compat@5.0.0-alpha.5
 - @react-navigation/core@5.0.0-alpha.12
 - @react-navigation/drawer@5.0.0-alpha.11
 - @react-navigation/example@5.0.0-alpha.9
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.10
 - @react-navigation/material-top-tabs@5.0.0-alpha.9
 - @react-navigation/native@5.0.0-alpha.9
 - @react-navigation/routers@5.0.0-alpha.9
 - @react-navigation/stack@5.0.0-alpha.21
2019-10-03 19:47:41 +02:00
satyajit.happy
ece6e3899e fix: add missing React import 2019-10-03 19:07:56 +02:00
satyajit.happy
da944ccef9 fix: fix header buttons not clickable on Android. fixes #108 2019-10-03 19:07:21 +02:00
satyajit.happy
bc3586ae3e fix: keep the routes we are closing or replacing 2019-10-03 18:44:18 +02:00
satyajit.happy
19be2b4bbc fix: don't throw when switching navigator. fixes #91 2019-10-03 17:51:13 +02:00
satyajit.happy
a8851b730d chore: upgrade deps 2019-10-03 17:35:24 +02:00
Michał Osadnik
7a5bcb446b feat: add a getRootState method (#119) 2019-10-01 23:17:28 +02:00
satyajit.happy
1345a8fec6 chore: upgrade eslint config 2019-09-28 11:58:21 +02:00
satyajit.happy
7393464515 fix: don't merge state with existing state during reset. fixes #111 2019-09-28 11:49:45 +02:00
osdnk
9810a06061 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.10
 - @react-navigation/compat@5.0.0-alpha.4
 - @react-navigation/core@5.0.0-alpha.11
 - @react-navigation/drawer@5.0.0-alpha.10
 - @react-navigation/example@5.0.0-alpha.8
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.9
 - @react-navigation/material-top-tabs@5.0.0-alpha.8
 - @react-navigation/routers@5.0.0-alpha.8
 - @react-navigation/stack@5.0.0-alpha.20
2019-09-27 17:30:35 +02:00
osdnk
655a220137 fix: close drawer on navigate 2019-09-27 17:29:59 +02:00
satyajit.happy
e61f5941d7 feat: add a method to reset root navigator state 2019-09-26 16:34:34 +02:00
satyajit.happy
8b78d617c0 feat: export some more type aliases 2019-09-25 23:09:58 +02:00
Dulmandakh
1fd5a868a3 feat: export BottomTabBar props type (#109) 2019-09-25 16:41:40 +02:00
satyajit.happy
1153d5575b fix: fire blur event when a route is removed with a delay
closes #110
2019-09-24 20:27:54 +02:00
osdnk
76de32f441 chore: publish
- @react-navigation/stack@5.0.0-alpha.19
2019-09-23 18:26:26 +02:00
osdnk
4ee19bcdb9 fix: vertical gesture in stack 2019-09-23 18:25:06 +02:00
satyajit.happy
06cc8b9142 chore: publish
- @react-navigation/compat@5.0.0-alpha.3
 - @react-navigation/stack@5.0.0-alpha.18
2019-09-23 17:26:27 +02:00
satyajit.happy
2f66556b10 fix: fix header rendered behind card. closes #108 2019-09-23 17:24:43 +02:00
Dulmandakh
f39813626a feat(stack): use Animated.Text for header title (#105) 2019-09-21 17:55:11 +02:00
Dulmandakh
089390ce87 feat(stack): use Animated.View for header background (#106) 2019-09-21 17:54:21 +02:00
Dulmandakh
d09c9ae643 refactor(stack): convert Header component to function (#104) 2019-09-21 17:53:44 +02:00
satyajit.happy
8920da68a7 fix: throw when wrapping a compat navigatofor compat 2019-09-19 14:57:31 +02:00
satyajit.happy
98a9487cda chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.9
 - @react-navigation/compat@5.0.0-alpha.2
 - @react-navigation/core@5.0.0-alpha.10
 - @react-navigation/stack@5.0.0-alpha.17
2019-09-17 18:44:55 +02:00
satyajit.happy
ff502820c7 feat: add createSwitchNavigator 2019-09-17 18:32:07 +02:00
satyajit.happy
30e510d123 fix: provide navigation prop in header 2019-09-17 18:13:42 +02:00
satyajit.happy
114a5dc888 feat: add withNavigation and withNavigationFocus 2019-09-17 17:57:01 +02:00
Ryan Stelly
b4bbf9b0c3 fix: add fallbacks for non-web modules
closes #95, #96
2019-09-17 16:58:17 +02:00
satyajit.happy
98f0de088f refactor: make dispatch accept thunks 2019-09-17 13:05:45 +02:00
satyajit.happy
2537145d81 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.8
 - @react-navigation/compat@5.0.0-alpha.1
 - @react-navigation/core@5.0.0-alpha.9
 - @react-navigation/drawer@5.0.0-alpha.9
 - @react-navigation/example@5.0.0-alpha.7
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.8
 - @react-navigation/native@5.0.0-alpha.8
 - @react-navigation/stack@5.0.0-alpha.16
2019-09-16 23:45:20 +02:00
Michał Osadnik
6af8400421 fix: don't remove route if animation isn't finished when dragging (#100) 2019-09-16 21:02:49 +02:00
satyajit.happy
d83d3de697 fix: support legacy goBack method 2019-09-16 16:04:31 +02:00
satyajit.happy
88a560aacb fix: fix dispatching compat actions 2019-09-16 15:21:07 +02:00
satyajit.happy
e0f28a432d feat: compatibility layer 2019-09-16 15:16:28 +02:00
satyajit.happy
438692d661 chore: fix running the example app 2019-09-16 14:59:54 +02:00
satyajit.happy
849d952703 feat: make deep link handling more flexible
This adds ability to specify a custom config to control how to convert between state and path.

Example:

```js
{
  Chat: {
    path: 'chat/:author/:id',
    parse: { id: Number }
  }
}
```

The above config can parse a path matching the provided pattern: `chat/jane/42` to a valid state:

```js
{
  routes: [
    {
      name: 'Chat',
      params: { author: 'jane', id: 42 },
    },
  ],
}
```

This makes it much easier to control the parsing without having to specify a custom function.
2019-09-16 13:20:19 +02:00
satyajit.happy
17045f5b6d refactor: share fallback descriptor 2019-09-14 16:05:19 +02:00
Brent Vatne
d16c20cd39 feat: make example run as bare react-native project as well (#85) 2019-09-08 18:10:44 +02:00
Janic Duplessis
9563a27284 feat: integrate InterationManager in stack 2019-09-08 17:01:06 +02:00
satyajit.happy
f57a91c417 fix: tweak android q animations 2019-09-08 12:40:38 +02:00
satyajit.happy
8e8b7eef36 chore: add issue and pull request templates 2019-09-07 15:42:35 +02:00
satyajit.happy
44020452fb chore: publish
- @react-navigation/core@5.0.0-alpha.8
 - @react-navigation/stack@5.0.0-alpha.15
2019-09-04 11:56:16 +02:00
satyajit.happy
ceaf18edd6 chore: remove commented out code 2019-09-04 11:55:37 +02:00
satyajit.happy
faaf855717 refactor: separate navigation and route context 2019-09-04 11:54:20 +02:00
satyajit.happy
196cce0803 feat: add approximate android Q transition 2019-09-04 11:36:00 +02:00
satyajit.happy
a022860317 chore: publish
- @react-navigation/stack@5.0.0-alpha.14
2019-09-03 16:43:26 +02:00
satyajit.happy
167d58ce27 fix: change order of attaching nodes in card exec 2019-09-03 16:41:11 +02:00
Michal Osadnik
798a905d0a chore: publish
- @react-navigation/stack@5.0.0-alpha.13
2019-09-01 16:56:21 +01:00
Michal Osadnik
aa6313c0e9 feat: useForeground if possible in stack header backButton 2019-09-01 16:56:00 +01:00
Michal Osadnik
2563924f53 chore: publish
- @react-navigation/stack@5.0.0-alpha.12
2019-09-01 15:43:59 +01:00
Michal Osadnik
55ec815247 fix: stack with gesture enabled 2019-09-01 12:46:55 +01:00
satyajit.happy
c7a79a62fa fix: defer running the animation to next frame 2019-09-01 12:43:03 +02:00
Michal Osadnik
5c6e85cec8 chore: publish
- @react-navigation/stack@5.0.0-alpha.11
2019-09-01 02:17:06 +01:00
Michal Osadnik
3f853d458f feat: optimizations in stack 2019-09-01 02:09:48 +01:00
satyajit.happy
06b3e6bda7 chore: publish
- @react-navigation/core@5.0.0-alpha.7
 - @react-navigation/example@5.0.0-alpha.6
 - @react-navigation/native@5.0.0-alpha.7
2019-08-31 23:23:10 +02:00
satyajit.happy
3c840bbae3 fix: fix navigation object changing too often 2019-08-31 23:21:27 +02:00
satyajit.happy
d4ad9d48f9 refactor: make descriptor.options a getter 2019-08-31 23:10:30 +02:00
satyajit.happy
a0e9784d98 chore: publish
- @react-navigation/core@5.0.0-alpha.6
 - @react-navigation/example@5.0.0-alpha.5
 - @react-navigation/native@5.0.0-alpha.6
2019-08-31 23:01:28 +02:00
satyajit.happy
eff0c0464f feat: support function in screenOptions 2019-08-31 23:00:27 +02:00
satyajit.happy
fe9ba2bf71 refactor: rename NativeContainer -> NavigationNativeContainer 2019-08-31 21:02:48 +02:00
Michał Osadnik
b0a3756d18 feat: add useRoute (#89) 2019-08-31 15:56:04 +01:00
satyajit.happy
4e0ebb05f9 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.7
 - @react-navigation/drawer@5.0.0-alpha.8
 - @react-navigation/example@5.0.0-alpha.4
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.7
 - @react-navigation/material-top-tabs@5.0.0-alpha.7
 - @react-navigation/routers@5.0.0-alpha.7
 - @react-navigation/stack@5.0.0-alpha.10
2019-08-31 10:22:57 +02:00
satyajit.happy
bf0b408238 refactor: change signature of interpolation props 2019-08-31 10:21:36 +02:00
Satyajit Sahoo
1b2983eaa9 fix: handle route names change when all routes are removed (#86) 2019-08-30 19:15:54 +01:00
satyajit.happy
b38ee1c162 chore: publish
- @react-navigation/core@5.0.0-alpha.5
 - @react-navigation/drawer@5.0.0-alpha.7
 - @react-navigation/stack@5.0.0-alpha.9
2019-08-30 15:34:34 +02:00
satyajit.happy
fdc24d2523 fix: rename contentContainerStyle to sceneContainerStyle for drawer 2019-08-30 15:30:18 +02:00
satyajit.happy
6a8242c31a fix: properly set animated node on gestureEnabled change 2019-08-29 19:55:42 +02:00
satyajit.happy
3ad2e6ebcf fix: change interpolated style when idle to avoid messing up reanimated 2019-08-29 16:12:22 +02:00
satyajit.happy
5c8d183d68 chore: rename BaseActions -> CommonActions 2019-08-29 13:10:36 +02:00
Michal Osadnik
f1b976b68c chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.6
 - @react-navigation/core@5.0.0-alpha.4
 - @react-navigation/drawer@5.0.0-alpha.6
 - @react-navigation/example@5.0.0-alpha.3
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.6
 - @react-navigation/material-top-tabs@5.0.0-alpha.6
 - @react-navigation/native@5.0.0-alpha.5
 - @react-navigation/routers@5.0.0-alpha.6
 - @react-navigation/stack@5.0.0-alpha.8
2019-08-29 10:40:13 +01:00
Michał Osadnik
6b75cbaaa6 feat: handle navigating with both with both key and name (#83) 2019-08-29 09:46:51 +02:00
satyajit.happy
c951027ebb fix: handle both null and undefined in useScrollToTop 2019-08-29 08:33:42 +02:00
Janic Duplessis
cdbf1e97f9 feat: handle animated component wrappers in useScrollToTop (#81) 2019-08-28 23:13:00 +01:00
Janic Duplessis
56a2ee99f9 chore: add prettier config to package.json so it is picked up by editors (#82) 2019-08-28 23:12:29 +01:00
satyajit.happy
a9d4813b47 fix: allow making params optional. fixes #80 2019-08-28 22:05:48 +02:00
satyajit.happy
74c3377b63 chore: fix typo in example 2019-08-28 21:48:58 +02:00
satyajit.happy
8c1acc33c6 fix: fix gestures not working in stack 2019-08-28 21:41:15 +02:00
satyajit.happy
d72a96d1ef chore: add example for pop to top on tab press 2019-08-28 21:28:18 +02:00
satyajit.happy
f9e8c7e80f feat: handle more methods in useScrollToTop 2019-08-28 21:17:48 +02:00
satyajit.happy
9245c79990 feat: export NavigationContext 2019-08-28 15:52:48 +02:00
satyajit.happy
ea4c753d0a refactor: rename 'timing' option to 'animation' in transition spec 2019-08-28 15:48:17 +02:00
satyajit.happy
01196d7b48 docs: add JSDoc for transition configurations 2019-08-28 15:41:11 +02:00
Janic Duplessis
b4a5c3c35e fix: types path (#75) 2019-08-28 11:36:37 +01:00
Michal Osadnik
f302416631 chore: publish
- @react-navigation/native@5.0.0-alpha.4
 - @react-navigation/stack@5.0.0-alpha.7
2019-08-28 11:24:44 +01:00
satyajit.happy
dead4e826a fix: fix stack nested in tab always getting reset 2019-08-28 12:23:15 +02:00
Michal Osadnik
d5b4210eb2 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.5
 - @react-navigation/drawer@5.0.0-alpha.5
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.5
 - @react-navigation/material-top-tabs@5.0.0-alpha.5
 - @react-navigation/routers@5.0.0-alpha.5
 - @react-navigation/stack@5.0.0-alpha.6
2019-08-28 11:22:18 +01:00
Michal Osadnik
38336b0290 feat: disable gesture logic when no gesture stack 2019-08-28 11:22:04 +01:00
satyajit.happy
fc37e93b5b chore: add some badges 2019-08-27 17:09:59 +02:00
satyajit.happy
093858b68b test: improve coverage for router tests 2019-08-27 16:58:43 +02:00
209 changed files with 12668 additions and 4395 deletions

View File

@@ -6,7 +6,9 @@
"@react-navigation/core",
"@react-navigation/native",
"@react-navigation/routers",
"@react-navigation/compat",
"@react-navigation/stack",
"@react-navigation/native-stack",
"@react-navigation/drawer",
"@react-navigation/bottom-tabs",
"@react-navigation/material-top-tabs",

40
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,40 @@
## The issue tracker is reserved for bug reports only.
If you have a question, feature request, or an idea for improving the library or its related tools, please try one of the following resources:
- [Read the documentation](https://reactnavigation.org/)
- [Post an issue to the website repository if you'd like to see a documentation change](http://github.com/react-navigation/website)
- [Post a feature request to Canny](https://react-navigation.canny.io/feature-requests)
- [Write a RFC if you have ideas for how to implement a feature request](https://github.com/react-navigation/rfcs)
- [Get help on Discord chat (#react-navigation on Reactiflux)](https://discord.gg/4xEK3nD) or [on StackOverflow](https://stackoverflow.com/questions/tagged/react-navigation)
- Search for your issue - it may have already been answered or even fixed in the development branch. However, if you find that an old, closed issue still persists in the latest version, you should open a new issue.
Bugs with react-navigation must be reproducible *without any external libraries that operate on it*. This means that if you are attempting to use Redux or MobX with it and you think you have found a bug, you must be able to reproduce it without Redux or MobX in this report.
---
### Current Behavior
- What code are you running and what is happening?
- Include a screenshot if it makes sense.
### Expected Behavior
- What do you expect should be happening?
- Include a screenshot if it makes sense.
### How to reproduce
- You must provide a way to reproduce the problem. If you are having an issue with your machine or build tools, the issue belongs on another repository as that is outside of the scope of React Navigation.
- Either re-create the bug on [Snack](https://snack.expo.io) or link to a GitHub repository with code that reproduces the bug.
- Explain how to run the example app and any steps that we need to take to reproduce the issue from the example app.
### Your Environment
| software | version
| ---------------- | -------
| react-navigation |
| react-native |
| expo |
| node |
| npm or yarn |

17
.github/PULL_REQUEST.md vendored Normal file
View File

@@ -0,0 +1,17 @@
Please provide enough information so that others can review your pull request:
## Motivation
Explain the **motivation** for making this change. What existing problem does the pull request solve?
## Test plan
Demonstrate the code is solid. Example: the exact commands you ran and their output, screenshots / videos if the pull request changes UI.
Make sure you test on both platforms if your change affects both platforms.
The code must pass tests.
## Code formatting
Look around. Match the style of the rest of the codebase. Run `yarn lint --fix` before committing.

9
.gitignore vendored
View File

@@ -3,17 +3,24 @@
.vscode
.idea
.expo
.gradle
local.properties
/coverage/
/types/
/dist/
/lib/
/packages/**/lib/
node_modules/
web-build/
web-report/
xcuserdata
generated
lib
build
npm-debug.*
*.log

View File

@@ -1,14 +0,0 @@
{
"git": {
"commitMessage": "chore: release %s",
"tagName": "v%s"
},
"github": {
"release": true
},
"plugins": {
"@release-it/conventional-changelog": {
"preset": "angular"
}
}
}

View File

@@ -1,5 +1,9 @@
# Rethinking Navigation
[![Build Status][build-badge]][build]
[![Code Coverage][coverage-badge]][coverage]
[![MIT License][license-badge]][license]
An exploration of a component-first API for React Navigation for building more dynamic navigation solutions.
## Considerations
@@ -530,3 +534,12 @@ yarn lerna publish
```
This will automatically bump the version and publish the packages. It'll also publish the changelogs on GitHub for each package.
<!-- badges -->
[build-badge]: https://img.shields.io/circleci/project/github/react-navigation/navigation-ex/master.svg?style=flat-square
[build]: https://circleci.com/gh/react-navigation/navigation-ex
[coverage-badge]: https://img.shields.io/codecov/c/github/react-navigation/navigation-ex.svg?style=flat-square
[coverage]: https://codecov.io/github/react-navigation/navigation-ex
[license-badge]: https://img.shields.io/npm/l/@react-navigation/core.svg?style=flat-square
[license]: https://opensource.org/licenses/MIT

View File

@@ -1,5 +1,3 @@
/* eslint-disable import/no-commonjs */
module.exports = {
presets: [
[

View File

@@ -1,5 +1,3 @@
/* eslint-disable import/no-commonjs */
module.exports = {
extends: ['@commitlint/config-conventional'],
};

View File

@@ -23,27 +23,26 @@
},
"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@babel/preset-env": "^7.6.2",
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.3.3",
"@babel/runtime": "^7.5.5",
"@commitlint/config-conventional": "^8.0.0",
"@babel/preset-typescript": "^7.6.0",
"@babel/runtime": "^7.6.2",
"@commitlint/config-conventional": "^8.2.0",
"@types/jest": "^24.0.13",
"codecov": "^3.5.0",
"commitlint": "^8.0.0",
"core-js": "^3.1.4",
"eslint": "^5.16.0",
"eslint-config-satya164": "^2.4.1",
"husky": "^2.4.0",
"codecov": "^3.6.1",
"commitlint": "^8.2.0",
"core-js": "^3.2.1",
"eslint": "^6.5.1",
"eslint-config-satya164": "^3.1.2",
"husky": "^3.0.8",
"jest": "^24.8.0",
"lerna": "^3.16.4",
"prettier": "^1.18.2",
"typescript": "^3.5.3"
"typescript": "^3.6.3"
},
"resolutions": {
"react": "16.8.3",
"react-native": "https://github.com/expo/react-native/archive/sdk-34.0.0.tar.gz",
"react-native-safe-area-view": "0.14.6"
"react": "~16.8.3",
"react-native": "~0.59.10"
},
"husky": {
"hooks": {
@@ -64,5 +63,10 @@
"@react-navigation/([^/]+)": "<rootDir>/packages/$1/src"
}
},
"name": "react-navigation"
"prettier": {
"tabWidth": 2,
"useTabs": false,
"singleQuote": true,
"trailingComma": "es5"
}
}

View File

@@ -3,6 +3,109 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.14](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.13...@react-navigation/bottom-tabs@5.0.0-alpha.14) (2019-10-15)
### Bug Fixes
* improve keyboard handling with bottom tab bar ([42beb66](https://github.com/react-navigation/navigation-ex/commit/42beb66))
* make it possible to run the example on web ([7a901af](https://github.com/react-navigation/navigation-ex/commit/7a901af))
### Features
* initial version of native stack ([#102](https://github.com/react-navigation/navigation-ex/issues/102)) ([ba3f718](https://github.com/react-navigation/navigation-ex/commit/ba3f718))
# [5.0.0-alpha.13](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.12...@react-navigation/bottom-tabs@5.0.0-alpha.13) (2019-10-06)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.12](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.11...@react-navigation/bottom-tabs@5.0.0-alpha.12) (2019-10-03)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.10...@react-navigation/bottom-tabs@5.0.0-alpha.11) (2019-10-03)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.9...@react-navigation/bottom-tabs@5.0.0-alpha.10) (2019-09-27)
### Features
* export BottomTabBar props type ([#109](https://github.com/react-navigation/navigation-ex/issues/109)) ([1fd5a86](https://github.com/react-navigation/navigation-ex/commit/1fd5a86))
* export some more type aliases ([8b78d61](https://github.com/react-navigation/navigation-ex/commit/8b78d61))
# [5.0.0-alpha.9](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.8...@react-navigation/bottom-tabs@5.0.0-alpha.9) (2019-09-17)
### Bug Fixes
* provide navigation prop in header ([30e510d](https://github.com/react-navigation/navigation-ex/commit/30e510d))
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.7...@react-navigation/bottom-tabs@5.0.0-alpha.8) (2019-09-16)
### Features
* make example run as bare react-native project as well ([#85](https://github.com/react-navigation/navigation-ex/issues/85)) ([d16c20c](https://github.com/react-navigation/navigation-ex/commit/d16c20c))
# [5.0.0-alpha.7](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.6...@react-navigation/bottom-tabs@5.0.0-alpha.7) (2019-08-31)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.5...@react-navigation/bottom-tabs@5.0.0-alpha.6) (2019-08-29)
### Bug Fixes
* allow making params optional. fixes [#80](https://github.com/react-navigation/navigation-ex/issues/80) ([a9d4813](https://github.com/react-navigation/navigation-ex/commit/a9d4813))
* types path ([#75](https://github.com/react-navigation/navigation-ex/issues/75)) ([b4a5c3c](https://github.com/react-navigation/navigation-ex/commit/b4a5c3c))
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.4...@react-navigation/bottom-tabs@5.0.0-alpha.5) (2019-08-28)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.3...@react-navigation/bottom-tabs@5.0.0-alpha.4) (2019-08-27)

View File

@@ -10,11 +10,40 @@ Open a Terminal in your project's folder and run,
yarn add @react-navigation/core @react-navigation/bottom-tabs
```
Now we need to install [`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context).
If you are using Expo, to ensure that you get the compatible versions of the libraries, run:
```sh
expo install react-native-safe-area-context
```
If you are not using Expo, run the following:
```sh
yarn add react-native-safe-area-context
```
If you are using Expo, you are done. Otherwise, continue to the next steps.
To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run:
```sh
cd ios
pod install
cd ..
```
## Usage
```js
import { MaterialCommunityIcons } from 'react-native-vector-icons';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
const getTabBarIcon = name => ({ color, size }) => (
<MaterialCommunityIcons name={name} color={color} size={size} />
);
const BottomTabs = createBottomTabNavigator();
export default function App() {
@@ -25,7 +54,7 @@ export default function App() {
component={Article}
options={{
tabBarLabel: 'Article',
tabBarIcon: 'chrome-reader-mode',
tabBarIcon: getTabBarIcon('file-document-box'),
}}
/>
<BottomTabs.Screen
@@ -33,7 +62,7 @@ export default function App() {
component={Chat}
options={{
tabBarLabel: 'Chat',
tabBarIcon: 'chat-bubble',
tabBarIcon: getTabBarIcon('message-reply'),
}}
/>
<BottomTabs.Screen
@@ -41,7 +70,7 @@ export default function App() {
component={Contacts}
options={{
tabBarLabel: 'Contacts',
tabBarIcon: 'contacts',
tabBarIcon: getTabBarIcon('contacts'),
}}
/>
</BottomTabs.Navigator>

View File

@@ -10,7 +10,7 @@
"android",
"tab"
],
"version": "5.0.0-alpha.4",
"version": "5.0.0-alpha.14",
"license": "MIT",
"repository": {
"type": "git",
@@ -20,7 +20,7 @@
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/bottom-tabssrc/index.d.ts",
"types": "lib/typescript/bottom-tabs/src/index.d.ts",
"files": [
"src",
"lib"
@@ -33,22 +33,23 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.4",
"react-native-safe-area-view": "^0.14.6"
"@react-navigation/routers": "^5.0.0-alpha.9"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",
"@types/react": "^16.8.24",
"@types/react-native": "^0.60.2",
"del-cli": "^2.0.0",
"react": "16.8.3",
"react-native": "^0.59.8",
"typescript": "^3.5.3"
"@types/react": "^16.9.4",
"@types/react-native": "^0.60.17",
"del-cli": "^3.0.0",
"react": "~16.8.3",
"react-native": "~0.59.10",
"react-native-safe-area-context": "^0.3.6",
"typescript": "^3.6.3"
},
"peerDependencies": {
"@react-navigation/core": "^5.0.0-alpha.0",
"react": "*",
"react-native": "*"
"react-native": "*",
"react-native-safe-area-context": "^0.3.6"
},
"@react-native-community/bob": {
"source": "src",

View File

@@ -13,4 +13,9 @@ export { default as BottomTabBar } from './views/BottomTabBar';
/**
* Types
*/
export { BottomTabNavigationOptions, BottomTabNavigationProp } from './types';
export {
BottomTabNavigationOptions,
BottomTabNavigationProp,
BottomTabBarProps,
BottomTabBarOptions,
} from './types';

View File

@@ -6,7 +6,6 @@ import {
TextStyle,
ViewStyle,
} from 'react-native';
import SafeAreaView from 'react-native-safe-area-view';
import {
NavigationHelpers,
NavigationProp,
@@ -53,8 +52,8 @@ export type BottomTabNavigationProp<
* @param [params] Params object for the route.
*/
jumpTo<RouteName extends Extract<keyof ParamList, string>>(
...args: ParamList[RouteName] extends void
? [RouteName]
...args: ParamList[RouteName] extends (undefined | any)
? [RouteName] | [RouteName, ParamList[RouteName]]
: [RouteName, ParamList[RouteName]]
): void;
};
@@ -67,26 +66,26 @@ export type BottomTabNavigationOptions = {
/**
* Title string of a tab displayed in the tab bar or React Element
* or a function that given { focused: boolean, tintColor: string } returns a React.Node, to display in 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 tabBarOptions.showLabel in the previous section.
*/
tabBarLabel?:
| React.ReactNode
| ((props: {
focused: boolean;
tintColor: string;
horizontal: boolean;
color: string;
size: number;
}) => React.ReactNode);
/**
* React Element or a function that given { focused: boolean, tintColor: string } returns a React.Node, to display in the tab bar.
* React Element or a function that given { focused: boolean, color: string } returns a React.Node, to display in the tab bar.
*/
tabBarIcon?:
| React.ReactNode
| ((props: {
focused: boolean;
tintColor: string;
horizontal: boolean;
color: string;
size: number;
}) => React.ReactNode);
/**
@@ -140,7 +139,7 @@ export type BottomTabNavigationConfig = {
export type BottomTabBarOptions = {
/**
* Whether the tab bar gets hidden when the keyboard is shown.
* Whether the tab bar gets hidden when the keyboard is shown. Defaults to `false`.
*/
keyboardHidesTabBar?: boolean;
/**
@@ -199,6 +198,7 @@ export type BottomTabBarOptions = {
export type BottomTabBarProps = BottomTabBarOptions & {
state: TabNavigationState;
descriptors: BottomTabDescriptorMap;
navigation: NavigationHelpers<ParamListBase>;
onTabPress: (props: { route: Route<string> }) => void;
onTabLongPress: (props: { route: Route<string> }) => void;
@@ -220,7 +220,7 @@ export type BottomTabBarProps = BottomTabBarOptions & {
}) =>
| ((scene: {
focused: boolean;
tintColor: string;
color: string;
orientation: 'horizontal' | 'vertical';
}) => React.ReactNode | undefined)
| React.ReactNode;
@@ -228,8 +228,9 @@ export type BottomTabBarProps = BottomTabBarOptions & {
renderIcon: (props: {
route: Route<string>;
focused: boolean;
tintColor: string;
horizontal: boolean;
color: string;
size: number;
}) => React.ReactNode;
safeAreaInset?: React.ComponentProps<typeof SafeAreaView>['forceInset'];
activeTintColor: string;
inactiveTintColor: string;
};

View File

@@ -1,5 +1,6 @@
import React from 'react';
import {
View,
Animated,
StyleSheet,
Keyboard,
@@ -8,8 +9,8 @@ import {
ScaledSize,
Dimensions,
} from 'react-native';
import SafeAreaView from 'react-native-safe-area-view';
import { Route } from '@react-navigation/core';
import { Route, NavigationContext } from '@react-navigation/core';
import { SafeAreaConsumer } from 'react-native-safe-area-context';
import TabBarIcon from './TabBarIcon';
import TouchableWithoutFeedbackWrapper from './TouchableWithoutFeedbackWrapper';
@@ -22,11 +23,7 @@ type State = {
visible: Animated.Value;
};
type Props = BottomTabBarProps & {
activeTintColor: string;
inactiveTintColor: string;
safeAreaInset: React.ComponentProps<typeof SafeAreaView>['forceInset'];
};
type Props = BottomTabBarProps;
const majorVersion = parseInt(Platform.Version as string, 10);
const isIos = Platform.OS === 'ios';
@@ -36,7 +33,7 @@ const DEFAULT_MAX_TAB_ITEM_WIDTH = 125;
export default class TabBarBottom extends React.Component<Props, State> {
static defaultProps = {
keyboardHidesTabBar: true,
keyboardHidesTabBar: false,
activeTintColor: '#007AFF',
activeBackgroundColor: 'transparent',
inactiveTintColor: '#8E8E93',
@@ -45,9 +42,6 @@ export default class TabBarBottom extends React.Component<Props, State> {
showIcon: true,
allowFontScaling: true,
adaptive: isIOS11,
safeAreaInset: { bottom: 'always', top: 'never' } as React.ComponentProps<
typeof SafeAreaView
>['forceInset'],
};
state = {
@@ -89,7 +83,7 @@ export default class TabBarBottom extends React.Component<Props, State> {
this.setState({ keyboard: true }, () =>
Animated.timing(this.state.visible, {
toValue: 0,
duration: 150,
duration: 200,
useNativeDriver: true,
}).start()
);
@@ -97,10 +91,12 @@ export default class TabBarBottom extends React.Component<Props, State> {
private handleKeyboardHide = () =>
Animated.timing(this.state.visible, {
toValue: 1,
duration: 100,
duration: 250,
useNativeDriver: true,
}).start(() => {
this.setState({ keyboard: false });
}).start(({ finished }) => {
if (finished) {
this.setState({ keyboard: false });
}
});
private handleLayout = (e: LayoutChangeEvent) => {
@@ -141,7 +137,7 @@ export default class TabBarBottom extends React.Component<Props, State> {
const label = this.props.getLabelText({ route });
const horizontal = this.shouldUseHorizontalLabels();
const tintColor = focused ? activeTintColor : inactiveTintColor;
const color = focused ? activeTintColor : inactiveTintColor;
if (typeof label === 'string') {
return (
@@ -149,7 +145,7 @@ export default class TabBarBottom extends React.Component<Props, State> {
numberOfLines={1}
style={[
styles.label,
{ color: tintColor },
{ color },
showIcon && horizontal ? styles.labelBeside : styles.labelBeneath,
labelStyle,
]}
@@ -163,7 +159,7 @@ export default class TabBarBottom extends React.Component<Props, State> {
if (typeof label === 'function') {
return label({
focused,
tintColor,
color,
orientation: horizontal ? 'horizontal' : 'vertical',
});
}
@@ -198,7 +194,7 @@ export default class TabBarBottom extends React.Component<Props, State> {
return (
<TabBarIcon
route={route}
horizontal={horizontal}
size={horizontal ? 17 : 24}
activeOpacity={activeOpacity}
inactiveOpacity={inactiveOpacity}
activeTintColor={activeTintColor}
@@ -261,6 +257,7 @@ export default class TabBarBottom extends React.Component<Props, State> {
render() {
const {
state,
descriptors,
keyboardHidesTabBar,
activeBackgroundColor,
inactiveBackgroundColor,
@@ -271,96 +268,102 @@ export default class TabBarBottom extends React.Component<Props, State> {
getAccessibilityStates,
getButtonComponent,
getTestID,
safeAreaInset,
style,
tabStyle,
} = this.props;
const { routes } = state;
const tabBarStyle = [
styles.tabBar,
// @ts-ignore
this.shouldUseHorizontalLabels() && !Platform.isPad
? styles.tabBarCompact
: styles.tabBarRegular,
style,
];
return (
<Animated.View
style={[
styles.container,
keyboardHidesTabBar
? // eslint-disable-next-line react-native/no-inline-styles
<SafeAreaConsumer>
{insets => (
<Animated.View
style={[
styles.tabBar,
keyboardHidesTabBar
? {
// When the keyboard is shown, slide down the tab bar
transform: [
{
translateY: this.state.visible.interpolate({
inputRange: [0, 1],
outputRange: [this.state.layout.height, 0],
}),
},
],
// Absolutely position the tab bar so that the content is below it
// This is needed to avoid gap at bottom when the tab bar is hidden
position: this.state.keyboard ? 'absolute' : null,
}
: null,
{
// When the keyboard is shown, slide down the tab bar
transform: [
{
translateY: this.state.visible.interpolate({
inputRange: [0, 1],
outputRange: [this.state.layout.height, 0],
}),
},
],
// Absolutely position the tab bar so that the content is below it
// This is needed to avoid gap at bottom when the tab bar is hidden
position: this.state.keyboard ? 'absolute' : null,
}
: null,
]}
pointerEvents={
keyboardHidesTabBar && this.state.keyboard ? 'none' : 'auto'
}
onLayout={this.handleLayout}
>
<SafeAreaView style={tabBarStyle} forceInset={safeAreaInset}>
{routes.map((route, index) => {
const focused = index === state.index;
const scene = { route, focused };
const accessibilityLabel = getAccessibilityLabel({
route,
});
height:
// @ts-ignore
(this.shouldUseHorizontalLabels() && !Platform.isPad
? COMPACT_HEIGHT
: DEFAULT_HEIGHT) + (insets ? insets.bottom : 0),
paddingBottom: insets ? insets.bottom : 0,
},
style,
]}
pointerEvents={
keyboardHidesTabBar && this.state.keyboard ? 'none' : 'auto'
}
>
<View style={styles.content} onLayout={this.handleLayout}>
{routes.map((route, index) => {
const focused = index === state.index;
const scene = { route, focused };
const accessibilityLabel = getAccessibilityLabel({
route,
});
const accessibilityRole = getAccessibilityRole({
route,
});
const accessibilityRole = getAccessibilityRole({
route,
});
const accessibilityStates = getAccessibilityStates(scene);
const accessibilityStates = getAccessibilityStates(scene);
const testID = getTestID({ route });
const testID = getTestID({ route });
const backgroundColor = focused
? activeBackgroundColor
: inactiveBackgroundColor;
const backgroundColor = focused
? activeBackgroundColor
: inactiveBackgroundColor;
const ButtonComponent =
getButtonComponent({ route }) || TouchableWithoutFeedbackWrapper;
const ButtonComponent =
getButtonComponent({ route }) ||
TouchableWithoutFeedbackWrapper;
return (
<ButtonComponent
key={route.key}
onPress={() => onTabPress({ route })}
onLongPress={() => onTabLongPress({ route })}
testID={testID}
accessibilityLabel={accessibilityLabel}
accessibilityRole={accessibilityRole}
accessibilityStates={accessibilityStates}
style={[
styles.tab,
{ backgroundColor },
this.shouldUseHorizontalLabels()
? styles.tabLandscape
: styles.tabPortrait,
tabStyle,
]}
>
{this.renderIcon(scene)}
{this.renderLabel(scene)}
</ButtonComponent>
);
})}
</SafeAreaView>
</Animated.View>
return (
<NavigationContext.Provider
key={route.key}
value={descriptors[route.key].navigation}
>
<ButtonComponent
onPress={() => onTabPress({ route })}
onLongPress={() => onTabLongPress({ route })}
testID={testID}
accessibilityLabel={accessibilityLabel}
accessibilityRole={accessibilityRole}
accessibilityStates={accessibilityStates}
style={[
styles.tab,
{ backgroundColor },
this.shouldUseHorizontalLabels()
? styles.tabLandscape
: styles.tabPortrait,
tabStyle,
]}
>
{this.renderIcon(scene)}
{this.renderLabel(scene)}
</ButtonComponent>
</NavigationContext.Provider>
);
})}
</View>
</Animated.View>
)}
</SafeAreaConsumer>
);
}
}
@@ -370,22 +373,17 @@ const COMPACT_HEIGHT = 29;
const styles = StyleSheet.create({
tabBar: {
backgroundColor: '#fff',
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: 'rgba(0, 0, 0, .3)',
flexDirection: 'row',
},
container: {
left: 0,
right: 0,
bottom: 0,
backgroundColor: '#fff',
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: 'rgba(0, 0, 0, .3)',
elevation: 8,
},
tabBarCompact: {
height: COMPACT_HEIGHT,
},
tabBarRegular: {
height: DEFAULT_HEIGHT,
content: {
flex: 1,
flexDirection: 'row',
},
tab: {
flex: 1,

View File

@@ -9,6 +9,7 @@ import { Route, CommonActions } from '@react-navigation/core';
import { TabNavigationState } from '@react-navigation/routers';
// eslint-disable-next-line import/no-unresolved
import { ScreenContainer } from 'react-native-screens';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import BottomTabBar from './BottomTabBar';
import {
@@ -63,13 +64,13 @@ export default class BottomTabView extends React.Component<Props, State> {
private renderIcon = ({
route,
focused,
tintColor,
horizontal,
color,
size,
}: {
route: Route<string>;
focused: boolean;
tintColor: string;
horizontal: boolean;
color: string;
size: number;
}) => {
const { descriptors } = this.props;
const descriptor = descriptors[route.key];
@@ -77,7 +78,7 @@ export default class BottomTabView extends React.Component<Props, State> {
if (options.tabBarIcon) {
return typeof options.tabBarIcon === 'function'
? options.tabBarIcon({ focused, tintColor, horizontal })
? options.tabBarIcon({ focused, color, size })
: options.tabBarIcon;
}
@@ -177,6 +178,7 @@ export default class BottomTabView extends React.Component<Props, State> {
<TabBarComponent
{...tabBarOptions}
state={state}
descriptors={descriptors}
navigation={navigation}
onTabPress={this.handleTabPress}
onTabLongPress={this.handleTabLongPress}
@@ -197,29 +199,31 @@ export default class BottomTabView extends React.Component<Props, State> {
const { loaded } = this.state;
return (
<View style={styles.container}>
<ScreenContainer style={styles.pages}>
{routes.map((route, index) => {
if (lazy && !loaded.includes(index)) {
// Don't render a screen if we've never navigated to it
return null;
}
<SafeAreaProvider>
<View style={styles.container}>
<ScreenContainer style={styles.pages}>
{routes.map((route, index) => {
if (lazy && !loaded.includes(index)) {
// Don't render a screen if we've never navigated to it
return null;
}
const isFocused = state.index === index;
const isFocused = state.index === index;
return (
<ResourceSavingScene
key={route.key}
style={StyleSheet.absoluteFill}
isVisible={isFocused}
>
{descriptors[route.key].render()}
</ResourceSavingScene>
);
})}
</ScreenContainer>
{this.renderTabBar()}
</View>
return (
<ResourceSavingScene
key={route.key}
style={StyleSheet.absoluteFill}
isVisible={isFocused}
>
{descriptors[route.key].render()}
</ResourceSavingScene>
);
})}
</ScreenContainer>
{this.renderTabBar()}
</View>
</SafeAreaProvider>
);
}
}

View File

@@ -24,12 +24,7 @@ export default class ResourceSavingScene extends React.Component<Props> {
return (
<View
style={[
styles.container,
style,
// eslint-disable-next-line react-native/no-inline-styles
{ opacity: isVisible ? 1 : 0 },
]}
style={[styles.container, style, { opacity: isVisible ? 1 : 0 }]}
collapsable={false}
removeClippedSubviews={
// On iOS, set removeClippedSubviews to true only when not focused

View File

@@ -4,7 +4,7 @@ import { Route } from '@react-navigation/core';
type Props = {
route: Route<string>;
horizontal: boolean;
size: number;
activeOpacity: number;
inactiveOpacity: number;
activeTintColor: string;
@@ -12,8 +12,8 @@ type Props = {
renderIcon: (props: {
route: Route<string>;
focused: boolean;
tintColor: string;
horizontal: boolean;
color: string;
size: number;
}) => React.ReactNode;
style: StyleProp<ViewStyle>;
};
@@ -25,7 +25,7 @@ export default function TabBarIcon({
activeTintColor,
inactiveTintColor,
renderIcon,
horizontal,
size,
style,
}: Props) {
// We render the icon twice at the same position on top of each other:
@@ -36,16 +36,16 @@ export default function TabBarIcon({
{renderIcon({
route,
focused: true,
horizontal,
tintColor: activeTintColor,
size,
color: activeTintColor,
})}
</View>
<View style={[styles.icon, { opacity: inactiveOpacity }]}>
{renderIcon({
route,
focused: false,
horizontal,
tintColor: inactiveTintColor,
size,
color: inactiveTintColor,
})}
</View>
</View>

View File

@@ -0,0 +1,88 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.7...@react-navigation/compat@5.0.0-alpha.8) (2019-10-15)
### Features
* initial version of native stack ([#102](https://github.com/react-navigation/navigation-ex/issues/102)) ([ba3f718](https://github.com/react-navigation/navigation-ex/commit/ba3f718))
# [5.0.0-alpha.7](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.6...@react-navigation/compat@5.0.0-alpha.7) (2019-10-06)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.5...@react-navigation/compat@5.0.0-alpha.6) (2019-10-03)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.4...@react-navigation/compat@5.0.0-alpha.5) (2019-10-03)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.3...@react-navigation/compat@5.0.0-alpha.4) (2019-09-27)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.2...@react-navigation/compat@5.0.0-alpha.3) (2019-09-23)
### Bug Fixes
* throw when wrapping a compat navigatofor compat ([8920da6](https://github.com/react-navigation/navigation-ex/commit/8920da6))
# [5.0.0-alpha.2](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.1...@react-navigation/compat@5.0.0-alpha.2) (2019-09-17)
### Bug Fixes
* provide navigation prop in header ([30e510d](https://github.com/react-navigation/navigation-ex/commit/30e510d))
### Features
* add createSwitchNavigator ([ff50282](https://github.com/react-navigation/navigation-ex/commit/ff50282))
* add withNavigation and withNavigationFocus ([114a5dc](https://github.com/react-navigation/navigation-ex/commit/114a5dc))
# 5.0.0-alpha.1 (2019-09-16)
### Bug Fixes
* fix dispatching compat actions ([88a560a](https://github.com/react-navigation/navigation-ex/commit/88a560a))
* support legacy goBack method ([d83d3de](https://github.com/react-navigation/navigation-ex/commit/d83d3de))
### Features
* compatibility layer ([e0f28a4](https://github.com/react-navigation/navigation-ex/commit/e0f28a4))

28
packages/compat/README.md Normal file
View File

@@ -0,0 +1,28 @@
# `@react-navigation/compat`
Compatibility layer to write navigator definitions in static configuration format.
## Installation
Open a Terminal in your project's folder and run,
```sh
yarn add @react-navigation/core @react-navigation/compat
```
## Usage
```js
import { createCompatNavigatorFactory } from '@react-navigation/compat';
import { createStackNavigator } from '@react-navigation/stack';
const RootStack = createCompatNavigatorFactory(createStackNavigator)(
{
Home: { screen: HomeScreen },
Profile: { screen: ProfileScreen },
},
{
initialRouteName: 'Profile',
}
);
```

View File

@@ -0,0 +1,47 @@
{
"name": "@react-navigation/compat",
"description": "Compatibility layer to write navigator definitions in static configuration format",
"version": "5.0.0-alpha.8",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/react-navigation/navigation-ex.git",
"directory": "packages/compat"
},
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/compat/src/index.d.ts",
"files": [
"src",
"lib"
],
"publishConfig": {
"access": "public"
},
"scripts": {
"prepare": "bob build",
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.9"
},
"devDependencies": {
"@types/react": "^16.9.4",
"react": "~16.8.3",
"typescript": "^3.6.3"
},
"peerDependencies": {
"@react-navigation/core": "^5.0.0-alpha.0",
"react": "~16.8.3"
},
"@react-native-community/bob": {
"source": "src",
"output": "lib",
"targets": [
"commonjs",
"module",
"typescript"
]
}
}

View File

@@ -0,0 +1,30 @@
import * as React from 'react';
import {
NavigationProp,
ParamListBase,
RouteProp,
} from '@react-navigation/core';
import ScreenPropsContext from './ScreenPropsContext';
import createCompatNavigationProp from './createCompatNavigationProp';
type Props<ParamList extends ParamListBase> = {
navigation: NavigationProp<ParamList>;
route: RouteProp<ParamList, string>;
component: React.ComponentType<any>;
};
function ScreenComponent<ParamList extends ParamListBase>(
props: Props<ParamList>
) {
const navigation = React.useMemo(
() =>
createCompatNavigationProp(props.navigation as any, props.route as any),
[props.navigation, props.route]
);
const screenProps = React.useContext(ScreenPropsContext);
return <props.component navigation={navigation} screenProps={screenProps} />;
}
export default React.memo(ScreenComponent);

View File

@@ -0,0 +1,13 @@
import { DrawerActions, DrawerActionType } from '@react-navigation/routers';
export function openDrawer(): DrawerActionType {
return DrawerActions.openDrawer();
}
export function closeDrawer(): DrawerActionType {
return DrawerActions.closeDrawer();
}
export function toggleDrawer(): DrawerActionType {
return DrawerActions.toggleDrawer();
}

View File

@@ -0,0 +1,48 @@
import { CommonActions, NavigationState } from '@react-navigation/core';
export function navigate({
routeName,
params,
key,
action,
}: {
routeName: string;
params?: object;
key?: string;
action?: never;
}): CommonActions.Action {
if (action !== undefined) {
throw new Error(
'Sub-actions are not supported for `navigate`. Remove the `action` key from the options.'
);
}
return CommonActions.navigate({
name: routeName,
key: key,
params: params,
});
}
export function back(options?: { key?: null | string }) {
return options && options.key != null
? (state: NavigationState) => ({
...CommonActions.goBack(),
source: options.key,
target: state.key,
})
: CommonActions.goBack();
}
export function setParams({
params,
key,
}: {
params: object;
key?: string;
}): CommonActions.Action {
return {
...CommonActions.setParams(params),
...(key !== undefined ? { source: key } : null),
};
}

View File

@@ -0,0 +1,3 @@
import * as React from 'react';
export default React.createContext<unknown>(undefined);

View File

@@ -0,0 +1,68 @@
import { CommonActions } from '@react-navigation/core';
import { StackActions, StackActionType } from '@react-navigation/routers';
export function reset(): CommonActions.Action {
throw new Error(
'The legacy `reset` action is not supported. Use the new reset API by accessing the original navigation object at `navigation.original`.'
);
}
export function replace({
routeName,
params,
key,
newKey,
action,
}: {
routeName: string;
params?: object;
key?: string;
newKey?: string;
action?: never;
}): CommonActions.Action {
if (action !== undefined) {
throw new Error(
'Sub-actions are not supported for `replace`. Remove the `action` key from the options.'
);
}
return {
type: 'REPLACE',
payload: {
name: routeName,
key: newKey,
params,
},
...(key !== undefined ? { source: key } : null),
};
}
export function push({
routeName,
params,
action,
}: {
routeName: string;
params?: object;
action?: never;
}): StackActionType {
if (action !== undefined) {
throw new Error(
'Sub-actions are not supported for `push`. Remove the `action` key from the options.'
);
}
return StackActions.push(routeName, params);
}
export function pop({ n = 1 }: { n: number }): StackActionType {
return StackActions.pop(n);
}
export function popToTop(): StackActionType {
return StackActions.popToTop();
}
export function dismiss(): CommonActions.Action {
throw new Error('The legacy `dismiss` action is not supported.');
}

View File

@@ -0,0 +1,18 @@
import { TabActions, TabActionType } from '@react-navigation/routers';
export function jumpTo({
routeName,
key,
}: {
routeName: string;
key?: string;
}): TabActionType {
if (key === undefined) {
return TabActions.jumpTo(routeName);
} else {
return {
...TabActions.jumpTo(routeName),
target: key,
};
}
}

View File

@@ -0,0 +1,189 @@
import {
NavigationState,
PartialState,
ParamListBase,
NavigationProp,
RouteProp,
} from '@react-navigation/core';
import * as helpers from './helpers';
import { CompatNavigationProp } from './types';
type EventName = 'willFocus' | 'willBlur' | 'didFocus' | 'didBlur' | 'refocus';
const focusSubscriptions = new WeakMap<() => void, () => void>();
const blurSubscriptions = new WeakMap<() => void, () => void>();
const refocusSubscriptions = new WeakMap<() => void, () => void>();
export default function createCompatNavigationProp<
NavigationPropType extends NavigationProp<ParamListBase>,
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
infer P
>
? P
: ParamListBase
>(
navigation: NavigationPropType,
state:
| (RouteProp<ParamList, keyof ParamList> & {
state?: NavigationState | PartialState<NavigationState>;
})
| NavigationState
| PartialState<NavigationState>
): CompatNavigationProp<NavigationPropType> {
return {
...navigation,
...Object.entries(helpers).reduce<{
[key: string]: (...args: any[]) => void;
}>((acc, [name, method]: [string, Function]) => {
if (name in navigation) {
acc[name] = (...args: any[]) => navigation.dispatch(method(...args));
}
return acc;
}, {}),
original: navigation,
addListener(type: EventName, callback: () => void) {
let unsubscribe: () => void;
switch (type) {
case 'willFocus':
unsubscribe = navigation.addListener('focus', callback);
break;
case 'willBlur':
unsubscribe = navigation.addListener('blur', callback);
break;
case 'didFocus': {
const listener = () => {
if (navigation.isFocused()) {
callback();
}
};
// @ts-ignore
unsubscribe = navigation.addListener('transitionEnd', listener);
focusSubscriptions.set(callback, unsubscribe);
break;
}
case 'didBlur': {
const listener = () => {
if (!navigation.isFocused()) {
callback();
}
};
// @ts-ignore
unsubscribe = navigation.addListener('transitionEnd', listener);
blurSubscriptions.set(callback, unsubscribe);
break;
}
case 'refocus': {
const listener = () => {
if (navigation.isFocused()) {
callback();
}
};
// @ts-ignore
unsubscribe = navigation.addListener('tabPress', listener);
refocusSubscriptions.set(callback, unsubscribe);
break;
}
default:
// @ts-ignore
unsubscribe = navigation.addListener(type, callback);
}
const subscription = () => unsubscribe();
subscription.remove = unsubscribe;
return subscription;
},
removeListener(type: EventName, callback: () => void) {
switch (type) {
case 'willFocus':
navigation.removeListener('focus', callback);
break;
case 'willBlur':
navigation.removeListener('blur', callback);
break;
case 'didFocus': {
const unsubscribe = focusSubscriptions.get(callback);
unsubscribe && unsubscribe();
break;
}
case 'didBlur': {
const unsubscribe = blurSubscriptions.get(callback);
unsubscribe && unsubscribe();
break;
}
case 'refocus': {
const unsubscribe = refocusSubscriptions.get(callback);
unsubscribe && unsubscribe();
break;
}
default:
// @ts-ignore
navigation.removeListener(type, callback);
}
},
state: {
...state,
// @ts-ignore
routeName: state.name,
get index() {
// @ts-ignore
if (state.index !== undefined) {
// @ts-ignore
return state.index;
}
console.warn(
"Accessing child navigation state for a route is not safe and won't work correctly."
);
// @ts-ignore
return state.state ? state.state.index : undefined;
},
get routes() {
// @ts-ignore
if (state.routes !== undefined) {
// @ts-ignore
return state.routes;
}
console.warn(
"Accessing child navigation state for a route is not safe and won't work correctly."
);
// @ts-ignore
return state.state ? state.state.routes : undefined;
},
},
getParam<T extends keyof ParamList>(
paramName: T,
defaultValue: ParamList[T]
): ParamList[T] {
// @ts-ignore
const params = state.params;
if (params && paramName in params) {
return params[paramName];
}
return defaultValue;
},
dangerouslyGetParent() {
const parent = navigation.dangerouslyGetParent();
if (parent) {
return createCompatNavigationProp(
parent,
navigation.dangerouslyGetState()
);
}
return undefined;
},
} as any;
}

View File

@@ -0,0 +1,171 @@
import * as React from 'react';
import {
NavigationState,
PartialState,
ParamListBase,
TypedNavigator,
NavigationProp,
RouteProp,
} from '@react-navigation/core';
import CompatScreen from './CompatScreen';
import ScreenPropsContext from './ScreenPropsContext';
import createCompatNavigationProp from './createCompatNavigationProp';
import { CompatScreenType, CompatRouteConfig } from './types';
export default function createCompatNavigatorFactory<
CreateNavigator extends () => TypedNavigator<
ParamListBase,
{},
React.ComponentType<any>
>
>(createNavigator: CreateNavigator) {
// @ts-ignore
if (createNavigator.isCompat) {
throw new Error(
`The navigator is already in compat mode. You don't need to wrap it in 'createCompatNavigatorFactory'.`
);
}
const createCompatNavigator = <
NavigationPropType extends NavigationProp<any, any, any, any, any>,
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
infer P
>
? P
: ParamListBase,
ScreenOptions extends {} = NavigationPropType extends NavigationProp<
any,
any,
any,
infer O
>
? O
: {},
NavigationConfig extends {} = React.ComponentProps<
ReturnType<CreateNavigator>['Navigator']
>
>(
routeConfig: CompatRouteConfig<NavigationPropType>,
navigationConfig: Partial<Omit<NavigationConfig, 'screenOptions'>> & {
order?: Array<Extract<keyof ParamList, string>>;
defaultNavigationOptions?: ScreenOptions;
navigationOptions?: { [key: string]: any };
} = {}
) => {
const Pair = createNavigator();
const {
order,
defaultNavigationOptions,
navigationOptions: parentNavigationOptions,
...restConfig
} = navigationConfig;
const routeNames = order !== undefined ? order : Object.keys(routeConfig);
function Navigator({ screenProps }: { screenProps?: unknown }) {
const screens = React.useMemo(
() =>
routeNames.map(name => {
let getScreenComponent: () => CompatScreenType<NavigationPropType>;
let initialParams;
const routeConfigItem = routeConfig[name];
if ('getScreen' in routeConfigItem) {
getScreenComponent = routeConfigItem.getScreen;
initialParams = routeConfigItem.params;
} else if ('screen' in routeConfigItem) {
getScreenComponent = () => routeConfigItem.screen;
initialParams = routeConfigItem.params;
} else {
getScreenComponent = () => routeConfigItem;
}
const screenOptions = ({
navigation,
route,
}: {
navigation: NavigationPropType;
route: RouteProp<ParamList, keyof ParamList> & {
state?: NavigationState | PartialState<NavigationState>;
};
}) => {
// @ts-ignore
const routeNavigationOptions = routeConfigItem.navigationOptions;
const screenNavigationOptions = getScreenComponent()
.navigationOptions;
if (
routeNavigationOptions == null &&
screenNavigationOptions == null
) {
return undefined;
}
const options =
typeof routeNavigationOptions === 'function' ||
typeof screenNavigationOptions === 'function'
? {
navigation: createCompatNavigationProp<
NavigationPropType
>(navigation, route),
navigationOptions: defaultNavigationOptions || {},
screenProps,
}
: {};
return {
...(typeof routeNavigationOptions === 'function'
? routeNavigationOptions(options)
: routeNavigationOptions),
...(typeof screenNavigationOptions === 'function'
? (screenNavigationOptions as (o: any) => ScreenOptions)(
options
)
: screenNavigationOptions),
} as ScreenOptions;
};
return (
<Pair.Screen
key={name}
name={name}
initialParams={initialParams}
options={screenOptions}
>
{({ navigation, route }) => (
<CompatScreen
navigation={navigation}
route={route}
component={getScreenComponent()}
/>
)}
</Pair.Screen>
);
}),
[screenProps]
);
return (
<ScreenPropsContext.Provider value={screenProps}>
<Pair.Navigator
{...(restConfig as NavigationConfig)}
screenOptions={defaultNavigationOptions}
>
{screens}
</Pair.Navigator>
</ScreenPropsContext.Provider>
);
}
Navigator.navigationOtions = parentNavigationOptions;
return Navigator;
};
createCompatNavigator.isCompat = true;
return createCompatNavigator;
}

View File

@@ -0,0 +1,28 @@
import {
useNavigationBuilder,
createNavigator,
DefaultNavigatorOptions,
} from '@react-navigation/core';
import {
TabRouter,
TabRouterOptions,
TabNavigationState,
} from '@react-navigation/routers';
import createCompatNavigatorFactory from './createCompatNavigatorFactory';
type Props = DefaultNavigatorOptions<{}> & TabRouterOptions;
function SwitchNavigator(props: Props) {
const { state, descriptors } = useNavigationBuilder<
TabNavigationState,
TabRouterOptions,
{},
{}
>(TabRouter, props);
return descriptors[state.routes[state.index].key].render();
}
export default createCompatNavigatorFactory(
createNavigator<{}, typeof SwitchNavigator>(SwitchNavigator)
);

View File

@@ -0,0 +1,86 @@
import * as NavigationActions from './NavigationActions';
import * as StackActions from './StackActions';
import * as SwitchActions from './SwitchActions';
import * as DrawerActions from './DrawerActions';
type NavigateActionPayload = Parameters<typeof NavigationActions.navigate>['0'];
type NavigateActionType = ReturnType<typeof NavigationActions.navigate>;
export function navigate(
routeName: string,
params?: object,
action?: never
): NavigateActionType;
export function navigate(options: NavigateActionPayload): NavigateActionType;
export function navigate(
options: string | NavigateActionPayload,
params?: object,
action?: never
): NavigateActionType {
if (typeof options === 'string') {
return NavigationActions.navigate({
routeName: options,
params,
action,
});
}
return NavigationActions.navigate(options);
}
export function goBack(fromKey?: null | string) {
return NavigationActions.back({ key: fromKey });
}
export function setParams(params: object) {
return NavigationActions.setParams({ params });
}
export function reset() {
return StackActions.reset();
}
export function replace(routeName: string, params?: object, action?: never) {
return StackActions.replace({
routeName,
params,
action,
});
}
export function push(routeName: string, params?: object, action?: never) {
return StackActions.push({
routeName,
params,
action,
});
}
export function pop(n: number) {
return StackActions.pop(typeof n === 'number' ? { n } : n);
}
export function popToTop() {
return StackActions.popToTop();
}
export function dismiss() {
return StackActions.dismiss();
}
export function jumpTo(routeName: string) {
return SwitchActions.jumpTo({ routeName });
}
export function openDrawer() {
return DrawerActions.openDrawer();
}
export function closeDrawer() {
return DrawerActions.closeDrawer();
}
export function toggleDrawer() {
return DrawerActions.toggleDrawer();
}

View File

@@ -0,0 +1,21 @@
import * as NavigationActions from './NavigationActions';
import * as StackActions from './StackActions';
import * as DrawerActions from './DrawerActions';
import * as SwitchActions from './SwitchActions';
export { NavigationActions, StackActions, DrawerActions, SwitchActions };
export {
default as createCompatNavigatorFactory,
} from './createCompatNavigatorFactory';
export {
default as createCompatNavigationProp,
} from './createCompatNavigationProp';
export { default as createSwitchNavigator } from './createSwitchNavigator';
export { default as withNavigation } from './withNavigation';
export { default as withNavigationFocus } from './withNavigationFocus';
export * from './types';

View File

@@ -0,0 +1,79 @@
import { ParamListBase, NavigationProp, Route } from '@react-navigation/core';
import * as helpers from './helpers';
export type CompatNavigationProp<
NavigationPropType extends NavigationProp<ParamListBase>,
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
infer P
>
? P
: ParamListBase,
RouteName extends Extract<keyof ParamList, string> = Extract<
NavigationPropType extends NavigationProp<any, infer R> ? R : string,
string
>
> = Omit<NavigationPropType, keyof typeof helpers> &
{
[method in Extract<keyof NavigationPropType, keyof typeof helpers>]: (
...args: Parameters<typeof helpers[method]>
) => void;
} & {
state: Route<RouteName> & {
routeName: RouteName;
};
getParam<T extends keyof ParamList[RouteName]>(
paramName: T,
defaultValue?: ParamList[RouteName][T]
): ParamList[RouteName][T];
dangerouslyGetParent<
T = NavigationProp<ParamListBase> | undefined
>(): T extends NavigationProp<ParamListBase>
? CompatNavigationProp<T>
: undefined;
};
export type CompatNavigationOptions<
NavigationPropType extends NavigationProp<ParamListBase>,
ScreenOptions extends {} = NavigationPropType extends NavigationProp<
any,
any,
any,
infer O
>
? O
: {}
> =
| ((options: {
navigation: CompatNavigationProp<NavigationPropType>;
navigationOptions: Partial<ScreenOptions>;
screenProps: unknown;
}) => ScreenOptions)
| ScreenOptions;
export type CompatScreenType<
NavigationPropType extends NavigationProp<ParamListBase>
> = React.ComponentType<{
navigation: CompatNavigationProp<NavigationPropType>;
screenProps: unknown;
}> & {
navigationOptions?: CompatNavigationOptions<NavigationPropType>;
};
export type CompatRouteConfig<
NavigationPropType extends NavigationProp<ParamListBase>,
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
infer P
>
? P
: ParamListBase
> = {
[RouteName in keyof ParamList]:
| React.ComponentType<any>
| ((
| { screen: React.ComponentType<any> }
| { getScreen(): React.ComponentType<any> }
) & {
navigationOptions?: CompatNavigationOptions<NavigationPropType>;
params?: ParamList[RouteName];
});
};

View File

@@ -0,0 +1,25 @@
import * as React from 'react';
import {
useNavigation,
useRoute,
NavigationProp,
ParamListBase,
} from '@react-navigation/core';
import createCompatNavigationProp from './createCompatNavigationProp';
import { CompatNavigationProp } from './types';
export default function useCompatNavigation<
T extends NavigationProp<ParamListBase>
>() {
const navigation = useNavigation();
const route = useRoute();
return React.useMemo(
() =>
createCompatNavigationProp(
navigation,
route as any
) as CompatNavigationProp<T>,
[navigation, route]
);
}

View File

@@ -0,0 +1,33 @@
import * as React from 'react';
import { NavigationProp, ParamListBase } from '@react-navigation/core';
import useCompatNavigation from './useCompatNavigation';
import { CompatNavigationProp } from './types';
type InjectedProps<T extends NavigationProp<ParamListBase>> = {
navigation: CompatNavigationProp<T>;
};
export default function withNavigation<
T extends NavigationProp<ParamListBase>,
P extends InjectedProps<T>,
C extends React.ComponentType<P>
>(Comp: C) {
const WrappedComponent = ({
onRef,
...rest
}: Exclude<P, InjectedProps<T>> & {
onRef?: C extends React.ComponentClass<any>
? React.Ref<InstanceType<C>>
: never;
}): React.ReactElement => {
const navigation = useCompatNavigation<T>();
// @ts-ignore
return <Comp ref={onRef} navigation={navigation} {...rest} />;
};
WrappedComponent.displayName = `withNavigation(${Comp.displayName ||
Comp.name})`;
return WrappedComponent;
}

View File

@@ -0,0 +1,30 @@
import * as React from 'react';
import { useIsFocused } from '@react-navigation/core';
type InjectedProps = {
isFocused: boolean;
};
export default function withNavigationFocus<
P extends InjectedProps,
C extends React.ComponentType<P>
>(Comp: C) {
const WrappedComponent = ({
onRef,
...rest
}: Exclude<P, InjectedProps> & {
onRef?: C extends React.ComponentClass<any>
? React.Ref<InstanceType<C>>
: never;
}): React.ReactElement => {
const isFocused = useIsFocused();
// @ts-ignore
return <Comp ref={onRef} isFocused={isFocused} {...rest} />;
};
WrappedComponent.displayName = `withNavigationFocus(${Comp.displayName ||
Comp.name})`;
return WrappedComponent;
}

View File

@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig"
}

View File

@@ -3,6 +3,165 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.16](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.15...@react-navigation/core@5.0.0-alpha.16) (2019-10-18)
### Bug Fixes
* rehydrate state before using it ([3e92e22](https://github.com/react-navigation/navigation-ex/commit/3e92e22))
### Features
* make it easier to navigate to a specific route in navigator ([#114](https://github.com/react-navigation/navigation-ex/issues/114)) ([a543f1b](https://github.com/react-navigation/navigation-ex/commit/a543f1b)), closes [#90](https://github.com/react-navigation/navigation-ex/issues/90)
# [5.0.0-alpha.15](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.14...@react-navigation/core@5.0.0-alpha.15) (2019-10-15)
### Features
* initial version of native stack ([#102](https://github.com/react-navigation/navigation-ex/issues/102)) ([ba3f718](https://github.com/react-navigation/navigation-ex/commit/ba3f718))
# [5.0.0-alpha.14](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.13...@react-navigation/core@5.0.0-alpha.14) (2019-10-06)
**Note:** Version bump only for package @react-navigation/core
# [5.0.0-alpha.13](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.12...@react-navigation/core@5.0.0-alpha.13) (2019-10-03)
**Note:** Version bump only for package @react-navigation/core
# [5.0.0-alpha.12](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.11...@react-navigation/core@5.0.0-alpha.12) (2019-10-03)
### Bug Fixes
* don't merge state with existing state during reset. fixes [#111](https://github.com/react-navigation/navigation-ex/issues/111) ([7393464](https://github.com/react-navigation/navigation-ex/commit/7393464))
* don't throw when switching navigator. fixes [#91](https://github.com/react-navigation/navigation-ex/issues/91) ([19be2b4](https://github.com/react-navigation/navigation-ex/commit/19be2b4))
### Features
* add a getRootState method ([#119](https://github.com/react-navigation/navigation-ex/issues/119)) ([7a5bcb4](https://github.com/react-navigation/navigation-ex/commit/7a5bcb4))
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.10...@react-navigation/core@5.0.0-alpha.11) (2019-09-27)
### Bug Fixes
* fire blur event when a route is removed with a delay ([1153d55](https://github.com/react-navigation/navigation-ex/commit/1153d55)), closes [#110](https://github.com/react-navigation/navigation-ex/issues/110)
### Features
* add a method to reset root navigator state ([e61f594](https://github.com/react-navigation/navigation-ex/commit/e61f594))
# [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.9...@react-navigation/core@5.0.0-alpha.10) (2019-09-17)
**Note:** Version bump only for package @react-navigation/core
# [5.0.0-alpha.9](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.8...@react-navigation/core@5.0.0-alpha.9) (2019-09-16)
### Features
* compatibility layer ([e0f28a4](https://github.com/react-navigation/navigation-ex/commit/e0f28a4))
* make deep link handling more flexible ([849d952](https://github.com/react-navigation/navigation-ex/commit/849d952))
* make example run as bare react-native project as well ([#85](https://github.com/react-navigation/navigation-ex/issues/85)) ([d16c20c](https://github.com/react-navigation/navigation-ex/commit/d16c20c))
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.7...@react-navigation/core@5.0.0-alpha.8) (2019-09-04)
**Note:** Version bump only for package @react-navigation/core
# [5.0.0-alpha.7](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.5...@react-navigation/core@5.0.0-alpha.7) (2019-08-31)
### Bug Fixes
* fix navigation object changing too often ([3c840bb](https://github.com/react-navigation/navigation-ex/commit/3c840bb))
### Features
* add useRoute ([#89](https://github.com/react-navigation/navigation-ex/issues/89)) ([b0a3756](https://github.com/react-navigation/navigation-ex/commit/b0a3756))
* support function in screenOptions ([eff0c04](https://github.com/react-navigation/navigation-ex/commit/eff0c04))
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.5...@react-navigation/core@5.0.0-alpha.6) (2019-08-31)
### Features
* add useRoute ([#89](https://github.com/react-navigation/navigation-ex/issues/89)) ([b0a3756](https://github.com/react-navigation/navigation-ex/commit/b0a3756))
* support function in screenOptions ([eff0c04](https://github.com/react-navigation/navigation-ex/commit/eff0c04))
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.4...@react-navigation/core@5.0.0-alpha.5) (2019-08-30)
**Note:** Version bump only for package @react-navigation/core
# [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.3...@react-navigation/core@5.0.0-alpha.4) (2019-08-29)
### Bug Fixes
* allow making params optional. fixes [#80](https://github.com/react-navigation/navigation-ex/issues/80) ([a9d4813](https://github.com/react-navigation/navigation-ex/commit/a9d4813))
### Features
* export NavigationContext ([9245c79](https://github.com/react-navigation/navigation-ex/commit/9245c79))
* handle navigating with both with both key and name ([#83](https://github.com/react-navigation/navigation-ex/issues/83)) ([6b75cba](https://github.com/react-navigation/navigation-ex/commit/6b75cba))
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.2...@react-navigation/core@5.0.0-alpha.3) (2019-08-27)

View File

@@ -6,7 +6,7 @@
"react-native",
"react-navigation"
],
"version": "5.0.0-alpha.3",
"version": "5.0.0-alpha.16",
"license": "MIT",
"repository": {
"type": "git",
@@ -29,22 +29,24 @@
"clean": "del lib"
},
"dependencies": {
"shortid": "^2.2.14",
"use-subscription": "^1.0.0"
"escape-string-regexp": "^2.0.0",
"query-string": "^6.8.3",
"shortid": "^2.2.15",
"use-subscription": "^1.1.1"
},
"devDependencies": {
"@babel/core": "^7.4.5",
"@babel/core": "^7.6.2",
"@react-native-community/bob": "^0.7.0",
"@types/react": "^16.8.19",
"@types/react": "^16.9.4",
"@types/shortid": "^0.0.29",
"del-cli": "^2.0.0",
"react": "^16.8.3",
"del-cli": "^3.0.0",
"react": "~16.8.3",
"react-native-testing-library": "^1.9.1",
"react-test-renderer": "^16.8.3",
"typescript": "^3.5.3"
"react-test-renderer": "~16.8.3",
"typescript": "^3.6.3"
},
"peerDependencies": {
"react": "^16.8.3"
"react": "~16.8.3"
},
"@react-native-community/bob": {
"source": "src",

View File

@@ -1,5 +1,5 @@
import shortid from 'shortid';
import { CommonAction, NavigationState } from './types';
import { CommonAction, NavigationState, PartialState } from './types';
/**
* Base router object that can be used when writing custom routers.
@@ -9,7 +9,7 @@ const BaseRouter = {
getStateForAction<State extends NavigationState>(
state: State,
action: CommonAction
): State | null {
): State | PartialState<State> | null {
switch (action.type) {
case 'REPLACE': {
const index = action.source
@@ -20,14 +20,16 @@ const BaseRouter = {
return null;
}
const { name, key, params } = action.payload;
return {
...state,
routes: state.routes.map((route, i) =>
i === index
? {
key: `${action.payload.name}-${shortid()}`,
name: action.payload.name,
params: action.payload.params,
key: key !== undefined ? key : `${name}-${shortid()}`,
name,
params,
}
: route
),
@@ -54,12 +56,7 @@ const BaseRouter = {
}
case 'RESET':
return {
...state,
...action.payload,
key: state.key,
routeNames: state.routeNames,
};
return action.payload as PartialState<State>;
default:
return null;

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line import/no-cycle
import { NavigationState, PartialState } from './types';
export type Action =
@@ -9,14 +10,14 @@ export type Action =
| {
type: 'NAVIGATE';
payload:
| { name: string; key?: undefined; params?: object }
| { key: string; name?: undefined; params?: object };
| { key: string; name?: undefined; params?: object }
| { name: string; key?: string; params?: object };
source?: string;
target?: string;
}
| {
type: 'REPLACE';
payload: { name: string; params?: object };
payload: { name: string; key?: string; params?: object };
source?: string;
target?: string;
}
@@ -38,7 +39,9 @@ export function goBack(): Action {
}
export function navigate(
route: { key: string; params?: object } | { name: string; params?: object }
route:
| { key: string; params?: object }
| { name: string; key?: string; params?: object }
): Action;
export function navigate(name: string, params?: object): Action;
export function navigate(...args: any): Action {
@@ -47,12 +50,9 @@ export function navigate(...args: any): Action {
} else {
const payload = args[0];
if (
(payload.hasOwnProperty('key') && payload.hasOwnProperty('name')) ||
(!payload.hasOwnProperty('key') && !payload.hasOwnProperty('name'))
) {
if (!payload.hasOwnProperty('key') && !payload.hasOwnProperty('name')) {
throw new Error(
'While calling navigate with an object as the argument, you need to specify either name or key'
'While calling navigate with an object as the argument, you need to specify name or key'
);
}

View File

@@ -18,25 +18,30 @@ export const SingleNavigatorContext = React.createContext<
* Component which ensures that there's only one navigator nested under it.
*/
export default function EnsureSingleNavigator({ children }: Props) {
const [currentKey, setCurrentKey] = React.useState<string | undefined>();
const navigatorKeyRef = React.useRef<string | undefined>();
const value = React.useMemo(
() => ({
register(key: string) {
const currentKey = navigatorKeyRef.current;
if (currentKey !== undefined && key !== currentKey) {
throw new Error(MULTIPLE_NAVIGATOR_ERROR);
}
setCurrentKey(key);
navigatorKeyRef.current = key;
},
unregister(key: string) {
if (currentKey !== undefined && key !== currentKey) {
throw new Error(MULTIPLE_NAVIGATOR_ERROR);
const currentKey = navigatorKeyRef.current;
if (key !== currentKey) {
return;
}
setCurrentKey(undefined);
navigatorKeyRef.current = undefined;
},
}),
[currentKey]
[]
);
return (

View File

@@ -1,5 +1,10 @@
import * as React from 'react';
import { NavigationAction, NavigationHelpers, ParamListBase } from './types';
import {
NavigationAction,
NavigationHelpers,
NavigationState,
ParamListBase,
} from './types';
export type ChildActionListener = (
action: NavigationAction,
@@ -14,6 +19,8 @@ export type FocusedNavigationListener = <T>(
callback: FocusedNavigationCallback<T>
) => { handled: boolean; result: T };
export type NavigatorStateGetter = () => NavigationState;
/**
* Context which holds the required helpers needed to build nested navigators.
*/
@@ -25,6 +32,7 @@ const NavigationBuilderContext = React.createContext<{
addActionListener?: (listener: ChildActionListener) => void;
addFocusedListener?: (listener: FocusedNavigationListener) => void;
onRouteFocus?: (key: string) => void;
addStateGetter?: (key: string, getter: NavigatorStateGetter) => void;
trackAction: (action: NavigationAction) => void;
}>({
trackAction: () => undefined,

View File

@@ -2,8 +2,10 @@ import * as React from 'react';
import * as CommonActions from './CommonActions';
import EnsureSingleNavigator from './EnsureSingleNavigator';
import NavigationBuilderContext from './NavigationBuilderContext';
import ResetRootContext from './ResetRootContext';
import useFocusedListeners from './useFocusedListeners';
import useDevTools from './useDevTools';
import useStateGetters from './useStateGetters';
import {
Route,
@@ -23,7 +25,9 @@ const MISSING_CONTEXT_ERROR =
export const NavigationStateContext = React.createContext<{
state?: NavigationState | PartialState<NavigationState>;
getState: () => NavigationState | PartialState<NavigationState> | undefined;
setState: (state: NavigationState | undefined) => void;
setState: (
state: NavigationState | PartialState<NavigationState> | undefined
) => void;
key?: string;
performTransaction: (action: () => void) => void;
}>({
@@ -86,46 +90,6 @@ const Container = React.forwardRef(function NavigationContainer(
getPartialState(initialState)
);
const { listeners, addListener: addFocusedListener } = useFocusedListeners();
const dispatch = (action: NavigationAction) => {
listeners[0](navigation => navigation.dispatch(action));
};
const canGoBack = () => {
const { result, handled } = listeners[0](navigation =>
navigation.canGoBack()
);
if (handled) {
return result;
} else {
return false;
}
};
React.useImperativeHandle(ref, () => ({
...(Object.keys(CommonActions) as Array<keyof typeof CommonActions>).reduce<
any
>((acc, name) => {
acc[name] = (...args: any[]) =>
dispatch(
// eslint-disable-next-line import/namespace
CommonActions[name](
// @ts-ignore
...args
)
);
return acc;
}, {}),
resetRoot: (state: PartialState<NavigationState> | NavigationState) => {
trackAction('@@RESET_ROOT');
setNavigationState(state);
},
dispatch,
canGoBack,
}));
const navigationStateRef = React.useRef<State>();
const transactionStateRef = React.useRef<State | null>(null);
const isTransactionActiveRef = React.useRef<boolean>(false);
@@ -143,12 +107,66 @@ const Container = React.forwardRef(function NavigationContainer(
state,
});
const { listeners, addListener: addFocusedListener } = useFocusedListeners();
const { getStateForRoute, addStateGetter } = useStateGetters();
const dispatch = (
action: NavigationAction | ((state: NavigationState) => NavigationAction)
) => {
listeners[0](navigation => navigation.dispatch(action));
};
const canGoBack = () => {
const { result, handled } = listeners[0](navigation =>
navigation.canGoBack()
);
if (handled) {
return result;
} else {
return false;
}
};
const resetRoot = React.useCallback(
(state: PartialState<NavigationState> | NavigationState) => {
trackAction('@@RESET_ROOT');
setNavigationState(state);
},
[trackAction]
);
const getRootState = () => {
return getStateForRoute('root');
};
React.useImperativeHandle(ref, () => ({
...(Object.keys(CommonActions) as Array<keyof typeof CommonActions>).reduce<
any
>((acc, name) => {
acc[name] = (...args: any[]) =>
dispatch(
CommonActions[name](
// @ts-ignore
...args
)
);
return acc;
}, {}),
resetRoot,
dispatch,
canGoBack,
getRootState,
}));
const builderContext = React.useMemo(
() => ({
addFocusedListener,
addStateGetter,
trackAction,
}),
[addFocusedListener, trackAction]
[addFocusedListener, trackAction, addStateGetter]
);
const performTransaction = React.useCallback((callback: () => void) => {
@@ -218,7 +236,9 @@ const Container = React.forwardRef(function NavigationContainer(
return (
<NavigationBuilderContext.Provider value={builderContext}>
<NavigationStateContext.Provider value={context}>
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
<ResetRootContext.Provider value={resetRoot}>
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
</ResetRootContext.Provider>
</NavigationStateContext.Provider>
</NavigationBuilderContext.Provider>
);

View File

@@ -0,0 +1,11 @@
import * as React from 'react';
import { Route } from './types';
/**
* Context which holds the route prop for a screen.
*/
const NavigationContext = React.createContext<Route<string> | undefined>(
undefined
);
export default NavigationContext;

View File

@@ -0,0 +1,15 @@
import * as React from 'react';
import { NavigationState, PartialState } from './types';
/**
* Context which holds the method to reset root navigator state.
*/
const ResetRootContext = React.createContext<
(state: PartialState<NavigationState> | NavigationState) => void
>(() => {
throw new Error(
"We couldn't find a way to reset root state. Have you wrapped your app with 'NavigationContainer'?"
);
});
export default ResetRootContext;

View File

@@ -1,6 +1,7 @@
import * as React from 'react';
import { NavigationStateContext } from './NavigationContainer';
import NavigationContext from './NavigationContext';
import NavigationRouteContext from './NavigationRouteContext';
import StaticContainer from './StaticContainer';
import EnsureSingleNavigator from './EnsureSingleNavigator';
import {
@@ -18,8 +19,8 @@ type Props<State extends NavigationState, ScreenOptions extends object> = {
route: Route<string> & {
state?: NavigationState | PartialState<NavigationState>;
};
getState: () => NavigationState;
setState: (state: NavigationState) => void;
getState: () => State;
setState: (state: State) => void;
};
/**
@@ -46,7 +47,7 @@ export default function SceneView<
}, [getState, route.key]);
const setCurrentState = React.useCallback(
(child: NavigationState | undefined) => {
(child: NavigationState | PartialState<NavigationState> | undefined) => {
const state = getState();
setState({
@@ -56,7 +57,7 @@ export default function SceneView<
),
});
},
[getState, route, setState]
[getState, route.key, setState]
);
const context = React.useMemo(
@@ -78,23 +79,25 @@ export default function SceneView<
return (
<NavigationContext.Provider value={navigation}>
<NavigationStateContext.Provider value={context}>
<EnsureSingleNavigator>
<StaticContainer
name={screen.name}
// @ts-ignore
render={screen.component || screen.children}
navigation={navigation}
route={route}
>
{'component' in screen && screen.component !== undefined ? (
<screen.component navigation={navigation} route={route} />
) : 'children' in screen && screen.children !== undefined ? (
screen.children({ navigation, route })
) : null}
</StaticContainer>
</EnsureSingleNavigator>
</NavigationStateContext.Provider>
<NavigationRouteContext.Provider value={route}>
<NavigationStateContext.Provider value={context}>
<EnsureSingleNavigator>
<StaticContainer
name={screen.name}
// @ts-ignore
render={screen.component || screen.children}
navigation={navigation}
route={route}
>
{'component' in screen && screen.component !== undefined ? (
<screen.component navigation={navigation} route={route} />
) : 'children' in screen && screen.children !== undefined ? (
screen.children({ navigation, route })
) : null}
</StaticContainer>
</EnsureSingleNavigator>
</NavigationStateContext.Provider>
</NavigationRouteContext.Provider>
</NavigationContext.Provider>
);
}

View File

@@ -125,15 +125,5 @@ it('resets state to new state with RESET', () => {
})
);
expect(result).toEqual({ ...STATE, index: 0, routes });
});
it('ignores key and routeNames when resetting with RESET', () => {
const result = BaseRouter.getStateForAction(
STATE,
// @ts-ignore
CommonActions.reset({ index: 2, key: 'foo', routeNames: ['test'] })
);
expect(result).toEqual({ ...STATE, index: 2 });
expect(result).toEqual({ index: 0, routes });
});

View File

@@ -7,42 +7,7 @@ import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
beforeEach(() => (MockRouterKey.current = 0));
it('throws if NAVIGATE dispatched with both key and name', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const FooScreen = (props: any) => {
React.useEffect(() => {
props.navigation.navigate({ key: '1', name: '2' });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return null;
};
const onStateChange = jest.fn();
const element = (
<NavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
<Screen
name="foo"
component={FooScreen}
initialParams={{ count: 10 }}
/>
</TestNavigator>
</NavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
'While calling navigate with an object as the argument, you need to specify either name or key'
);
});
it('throws if NAVIGATE dispatched neither both key nor name', () => {
it('throws if NAVIGATE dispatched neither key nor name', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
@@ -73,6 +38,6 @@ it('throws if NAVIGATE dispatched neither both key nor name', () => {
);
expect(() => render(element).update(element)).toThrowError(
'While calling navigate with an object as the argument, you need to specify either name or key'
'While calling navigate with an object as the argument, you need to specify name or key'
);
});

View File

@@ -321,3 +321,56 @@ it('handle resetting state with ref', () => {
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).lastCalledWith(state);
});
it('handle getRootState', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const ref = React.createRef<NavigationContainerRef>();
const element = (
<NavigationContainer ref={ref}>
<TestNavigator initialRouteName="foo">
<Screen name="foo">
{() => (
<TestNavigator>
<Screen name="qux" component={() => null} />
<Screen name="lex" component={() => null} />
</TestNavigator>
)}
</Screen>
<Screen name="bar" component={() => null} />
</TestNavigator>
</NavigationContainer>
);
render(element);
let state;
if (ref.current) {
state = ref.current.getRootState();
}
expect(state).toEqual({
index: 0,
key: '7',
routeNames: ['foo', 'bar'],
routes: [
{
key: 'foo',
name: 'foo',
state: {
index: 0,
key: '8',
routeNames: ['qux', 'lex'],
routes: [{ key: 'qux', name: 'qux' }, { key: 'lex', name: 'lex' }],
stale: false,
},
},
{ key: 'bar', name: 'bar' },
],
stale: false,
});
});

View File

@@ -108,7 +108,24 @@ export default function MockRouter(options: DefaultRouterOptions) {
return null;
}
return { ...state, index };
return {
...state,
index,
routes:
action.payload.params !== undefined
? state.routes.map((route, i) =>
i === index
? {
...route,
params: {
...route.params,
...action.payload.params,
},
}
: route
)
: state.routes,
};
}
default:

View File

@@ -1,6 +1,6 @@
import getPathFromState from '../getPathFromState';
it('converts path string to initial state', () => {
it('converts state to path string', () => {
expect(
getPathFromState({
routes: [
@@ -12,11 +12,12 @@ it('converts path string to initial state', () => {
{ name: 'boo' },
{
name: 'bar',
params: { fruit: 'apple' },
state: {
routes: [
{
name: 'baz qux',
params: { author: 'jane & co', valid: true },
params: { author: 'jane', valid: true },
},
],
},
@@ -26,9 +27,50 @@ it('converts path string to initial state', () => {
},
],
})
).toMatchInlineSnapshot(
`"/foo/bar/baz%20qux?author=%22jane%20%26%20co%22&valid=true"`
);
).toMatchInlineSnapshot(`"/foo/bar/baz%20qux?author=jane&valid=true"`);
});
it('converts state to path string with config', () => {
expect(
getPathFromState(
{
routes: [
{
name: 'Foo',
state: {
index: 1,
routes: [
{ name: 'boo' },
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet', avaliable: false },
state: {
routes: [
{
name: 'Baz',
params: { author: 'Jane', valid: true, id: 10 },
},
],
},
},
],
},
},
],
},
{
Foo: 'few',
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz/:author',
stringify: {
author: author => author.toLowerCase(),
id: id => `x${id}`,
},
},
}
)
).toMatchInlineSnapshot(`"/few/bar/sweet/apple/baz/jane?id=x10&valid=true"`);
});
it('handles route without param', () => {

View File

@@ -2,9 +2,7 @@ import getStateFromPath from '../getStateFromPath';
it('converts path string to initial state', () => {
expect(
getStateFromPath(
'foo/bar/baz%20qux?author=%22jane%20%26%20co%22&valid=true'
)
getStateFromPath('foo/bar/baz%20qux?author=jane%20%26%20co&valid=true')
).toEqual({
routes: [
{
@@ -17,7 +15,55 @@ it('converts path string to initial state', () => {
routes: [
{
name: 'baz qux',
params: { author: 'jane & co', valid: true },
params: { author: 'jane & co', valid: 'true' },
},
],
},
},
],
},
},
],
});
});
it('converts path string to initial state with config', () => {
expect(
getStateFromPath(
'/few/bar/sweet/apple/baz/jane?count=10&answer=42&valid=true',
{
Foo: 'few',
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) =>
author.replace(/^\w/, c => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
}
)
).toEqual({
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet' },
state: {
routes: [
{
name: 'Baz',
params: {
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
],
},
@@ -38,7 +84,7 @@ it('handles leading slash when converting', () => {
routes: [
{
name: 'bar',
params: { count: 42 },
params: { count: '42' },
},
],
},
@@ -56,7 +102,7 @@ it('handles ending slash when converting', () => {
routes: [
{
name: 'bar',
params: { count: 42 },
params: { count: '42' },
},
],
},

View File

@@ -5,7 +5,7 @@ import NavigationContainer from '../NavigationContainer';
import useNavigationBuilder from '../useNavigationBuilder';
import useNavigation from '../useNavigation';
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
import { NavigationState } from '../types';
import { NavigationState, NavigationContainerRef } from '../types';
beforeEach(() => (MockRouterKey.current = 0));
@@ -323,7 +323,7 @@ it('cleans up state when the navigator unmounts', () => {
expect(onStateChange).lastCalledWith(undefined);
});
it('allows arbitrary state updates by dispatching a function', () => {
it('allows state updates by dispatching a function returning an action', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
@@ -332,11 +332,11 @@ it('allows arbitrary state updates by dispatching a function', () => {
const FooScreen = (props: any) => {
React.useEffect(() => {
props.navigation.dispatch((state: any) => ({
...state,
routes: state.routes.slice().reverse(),
index: 1,
}));
props.navigation.dispatch((state: NavigationState) =>
state.index === 0
? { type: 'NAVIGATE', payload: { name: state.routeNames[1] } }
: { type: 'NOOP' }
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -364,7 +364,7 @@ it('allows arbitrary state updates by dispatching a function', () => {
index: 1,
key: '0',
routeNames: ['foo', 'bar'],
routes: [{ key: 'bar', name: 'bar' }, { key: 'foo', name: 'foo' }],
routes: [{ key: 'foo', name: 'foo' }, { key: 'bar', name: 'bar' }],
});
});
@@ -523,6 +523,80 @@ it('handles change in route names', () => {
});
});
it('navigates to nested child in a navigator', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const TestComponent = ({ route }: any): any =>
`[${route.name}, ${JSON.stringify(route.params)}]`;
const onStateChange = jest.fn();
const navigation = React.createRef<NavigationContainerRef>();
const element = render(
<NavigationContainer ref={navigation} onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo">
{() => (
<TestNavigator>
<Screen name="foo-a" component={TestComponent} />
<Screen name="foo-b" component={TestComponent} />
</TestNavigator>
)}
</Screen>
<Screen name="bar">
{() => (
<TestNavigator initialRouteName="bar-a">
<Screen
name="bar-a"
component={TestComponent}
initialParams={{ lol: 'why' }}
/>
<Screen
name="bar-b"
component={TestComponent}
initialParams={{ some: 'stuff' }}
/>
</TestNavigator>
)}
</Screen>
</TestNavigator>
</NavigationContainer>
);
expect(element).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
act(
() =>
navigation.current &&
navigation.current.navigate('bar', {
screen: 'bar-b',
params: { test: 42 },
})
);
expect(element).toMatchInlineSnapshot(
`"[bar-b, {\\"some\\":\\"stuff\\",\\"test\\":42}]"`
);
act(
() =>
navigation.current &&
navigation.current.navigate('bar', {
screen: 'bar-a',
params: { whoa: 'test' },
})
);
expect(element).toMatchInlineSnapshot(
`"[bar-a, {\\"lol\\":\\"why\\",\\"whoa\\":\\"test\\"}]"`
);
});
it('gives access to internal state', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
@@ -655,6 +729,7 @@ it('throws when a React Element is not the direct children', () => {
);
});
// eslint-disable-next-line jest/expect-expect
it("doesn't throw when direct children is Screen or empty element", () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
@@ -694,3 +769,30 @@ it('throws when multiple screens with same name are defined', () => {
"A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named 'foo')"
);
});
it('switches rendered navigators', () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
return null;
};
const root = render(
<NavigationContainer>
<TestNavigator key="a">
<Screen name="foo" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
expect(() =>
root.update(
<NavigationContainer>
<TestNavigator key="b">
<Screen name="foo" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
)
).not.toThrowError(
'Another navigator is already registered for this container.'
);
});

View File

@@ -46,11 +46,320 @@ it('sets options with options prop as an object', () => {
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
<main>
<h1>
Hello world
</h1>
<div>
Test screen
</div>
</main>
`);
});
it('sets options with options prop as a fuction', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{ title?: string },
any
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];
return (
<main>
<h1>{options.title}</h1>
<div>{render()}</div>
</main>
);
};
const TestScreen = (): any => 'Test screen';
const root = render(
<NavigationContainer>
<TestNavigator>
<Screen
name="foo"
component={TestScreen}
options={({ route }: any) => ({ title: route.params.author })}
initialParams={{ author: 'Jane' }}
/>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
<main>
<h1>
Jane
</h1>
<div>
Test screen
</div>
</main>
`);
});
it('sets options with screenOptions prop as an object', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{ title?: string },
any
>(MockRouter, props);
return (
<>
{state.routes.map(route => {
const { render, options } = descriptors[route.key];
return (
<main key={route.key}>
<h1>{options.title}</h1>
<div>{render()}</div>
</main>
);
})}
</>
);
};
const TestScreenA = (): any => 'Test screen A';
const TestScreenB = (): any => 'Test screen B';
const root = render(
<NavigationContainer>
<TestNavigator screenOptions={{ title: 'Hello world' }}>
<Screen name="foo" component={TestScreenA} />
<Screen name="bar" component={TestScreenB} />
</TestNavigator>
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
Array [
<main>
<h1>
Hello world
</h1>
<div>
Test screen A
</div>
</main>,
<main>
<h1>
Hello world
</h1>
<div>
Test screen B
</div>
</main>,
]
`);
});
it('sets options with screenOptions prop as a fuction', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{ title?: string },
any
>(MockRouter, props);
return (
<>
{state.routes.map(route => {
const { render, options } = descriptors[route.key];
return (
<main key={route.key}>
<h1>{options.title}</h1>
<div>{render()}</div>
</main>
);
})}
</>
);
};
const TestScreenA = (): any => 'Test screen A';
const TestScreenB = (): any => 'Test screen B';
const root = render(
<NavigationContainer>
<TestNavigator
screenOptions={({ route }: any) => ({
title: `${route.name}: ${route.params.author || route.params.fruit}`,
})}
>
<Screen
name="foo"
component={TestScreenA}
initialParams={{ author: 'Jane' }}
/>
<Screen
name="bar"
component={TestScreenB}
initialParams={{ fruit: 'Apple' }}
/>
</TestNavigator>
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
Array [
<main>
<h1>
foo: Jane
</h1>
<div>
Test screen A
</div>
</main>,
<main>
<h1>
bar: Apple
</h1>
<div>
Test screen B
</div>
</main>,
]
`);
});
it('sets initial options with setOptions', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{
title?: string;
color?: string;
},
any
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];
return (
<main>
<h1 color={options.color}>{options.title}</h1>
<div>{render()}</div>
</main>
);
};
const TestScreen = ({ navigation }: any): any => {
navigation.setOptions({
title: 'Hello world',
});
return 'Test screen';
};
const root = render(
<NavigationContainer>
<TestNavigator>
<Screen name="foo" options={{ color: 'blue' }}>
{props => <TestScreen {...props} />}
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
<main>
<h1
color="blue"
>
Hello world
</h1>
<div>
Test screen
</div>
</main>
`);
});
it('updates options with setOptions', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
any,
any
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];
return (
<main>
<h1 color={options.color}>{options.title}</h1>
<p>{options.description}</p>
<caption>{options.author}</caption>
<div>{render()}</div>
</main>
);
};
const TestScreen = ({ navigation }: any): any => {
navigation.setOptions({
title: 'Hello world',
description: 'Something here',
});
React.useEffect(() => {
const timer = setTimeout(() =>
navigation.setOptions({
title: 'Hello again',
author: 'Jane',
})
);
return () => clearTimeout(timer);
});
return 'Test screen';
};
const element = (
<NavigationContainer>
<TestNavigator>
<Screen name="foo" options={{ color: 'blue' }}>
{props => <TestScreen {...props} />}
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
const root = render(element);
act(() => jest.runAllTimers());
root.update(element);
expect(root).toMatchInlineSnapshot(`
<main>
<h1>
Hello world
<h1
color="blue"
>
Hello again
</h1>
<p>
Something here
</p>
<caption>
Jane
</caption>
<div>
Test screen
</div>
@@ -295,180 +604,3 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
expect(result).toBe(false);
});
it('sets options with options prop as a fuction', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{ title?: string },
any
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];
return (
<main>
<h1>{options.title}</h1>
<div>{render()}</div>
</main>
);
};
const TestScreen = (): any => 'Test screen';
const root = render(
<NavigationContainer>
<TestNavigator>
<Screen
name="foo"
component={TestScreen}
options={({ route }: any) => ({ title: route.params.author })}
initialParams={{ author: 'Jane' }}
/>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
<main>
<h1>
Jane
</h1>
<div>
Test screen
</div>
</main>
`);
});
it('sets initial options with setOptions', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{
title?: string;
color?: string;
},
any
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];
return (
<main>
<h1 color={options.color}>{options.title}</h1>
<div>{render()}</div>
</main>
);
};
const TestScreen = ({ navigation }: any): any => {
navigation.setOptions({
title: 'Hello world',
});
return 'Test screen';
};
const root = render(
<NavigationContainer>
<TestNavigator>
<Screen name="foo" options={{ color: 'blue' }}>
{props => <TestScreen {...props} />}
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
<main>
<h1
color="blue"
>
Hello world
</h1>
<div>
Test screen
</div>
</main>
`);
});
it('updates options with setOptions', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
any,
any
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];
return (
<main>
<h1 color={options.color}>{options.title}</h1>
<p>{options.description}</p>
<caption>{options.author}</caption>
<div>{render()}</div>
</main>
);
};
const TestScreen = ({ navigation }: any): any => {
navigation.setOptions({
title: 'Hello world',
description: 'Something here',
});
React.useEffect(() => {
const timer = setTimeout(() =>
navigation.setOptions({
title: 'Hello again',
author: 'Jane',
})
);
return () => clearTimeout(timer);
});
return 'Test screen';
};
const element = (
<NavigationContainer>
<TestNavigator>
<Screen name="foo" options={{ color: 'blue' }}>
{props => <TestScreen {...props} />}
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
const root = render(element);
act(() => jest.runAllTimers());
root.update(element);
expect(root).toMatchInlineSnapshot(`
<main>
<h1
color="blue"
>
Hello again
</h1>
<p>
Something here
</p>
<caption>
Jane
</caption>
<div>
Test screen
</div>
</main>
`);
});

View File

@@ -4,6 +4,7 @@ import useNavigationBuilder from '../useNavigationBuilder';
import NavigationContainer from '../NavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
import { Router, NavigationState } from '../types';
it('fires focus and blur events in root navigator', () => {
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
@@ -200,6 +201,135 @@ it('fires focus and blur events in nested navigator', () => {
expect(thirdFocusCallback).toBeCalledTimes(1);
});
it('fires blur event when a route is removed with a delay', async () => {
const TestRouter = (options: any): Router<NavigationState, any> => {
const router = MockRouter(options);
return {
...router,
getInitialState({ routeNames, routeParamList }) {
const initialRouteName =
options.initialRouteName !== undefined
? options.initialRouteName
: routeNames[0];
return {
stale: false,
key: 'stack',
index: 0,
routeNames,
routes: [
{
key: initialRouteName,
name: initialRouteName,
params: routeParamList[initialRouteName],
},
],
};
},
getStateForAction(state, action) {
switch (action.type) {
case 'PUSH':
return {
...state,
index: state.index + 1,
routes: [...state.routes, action.payload],
};
case 'POP': {
const routes = state.routes.slice(0, -1);
return {
...state,
index: routes.length - 1,
routes,
};
}
default:
return router.getStateForAction(state, action);
}
},
actionCreators: {
push(payload) {
return { type: 'PUSH', payload };
},
pop() {
return { type: 'POP' };
},
},
};
};
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
const { state, navigation, descriptors } = useNavigationBuilder(
TestRouter,
props
);
React.useImperativeHandle(ref, () => navigation, [navigation]);
const [previous, dispatch] = React.useReducer(
(state, action) => {
if (state.routes !== action.routes) {
return { ...state, ...action };
}
return state;
},
{ routes: state.routes, descriptors }
);
React.useEffect(() => {
dispatch({ routes: state.routes, descriptors });
}, [descriptors, state.routes]);
return previous.routes.map((route: any) =>
previous.descriptors[route.key].render()
);
});
const blurCallback = jest.fn();
const First = () => null;
const Second = ({ navigation }: any) => {
React.useEffect(() => navigation.addListener('blur', blurCallback), [
navigation,
]);
return null;
};
const navigation = React.createRef<any>();
const element = (
<NavigationContainer>
<TestNavigator ref={navigation}>
<Screen name="first" component={First} />
<Screen name="second" component={Second} />
</TestNavigator>
</NavigationContainer>
);
render(element);
act(() =>
navigation.current.push({
name: 'second',
key: 'second',
})
);
expect(blurCallback).toBeCalledTimes(0);
act(() => navigation.current.pop());
expect(blurCallback).toBeCalledTimes(1);
});
it('fires custom events', () => {
const eventName = 'someSuperCoolEvent';

View File

@@ -296,6 +296,7 @@ it("action doesn't bubble if target is specified", () => {
expect(onStateChange).not.toBeCalled();
});
// eslint-disable-next-line jest/expect-expect
it("doesn't crash if no navigator handled the action", () => {
const TestRouter = MockRouter;

View File

@@ -0,0 +1,34 @@
import * as React from 'react';
import { render } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import useRoute from '../useRoute';
import NavigationContainer from '../NavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
import { RouteProp } from '../types';
it('gets route prop from context', () => {
expect.assertions(1);
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return state.routes.map(route => descriptors[route.key].render());
};
const Test = () => {
const route = useRoute<RouteProp<{ sample: { x: string } }, 'sample'>>();
expect(route && route.params && route.params.x).toEqual(1);
return null;
};
render(
<NavigationContainer>
<TestNavigator>
<Screen name="foo" component={Test} initialParams={{ x: 1 }} />
</TestNavigator>
</NavigationContainer>
);
});

View File

@@ -1,14 +1,45 @@
import queryString from 'query-string';
import { NavigationState, PartialState, Route } from './types';
type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
type StringifyConfig = { [key: string]: (value: any) => string };
type Options = {
[routeName: string]: string | { path: string; stringify?: StringifyConfig };
};
/**
* Utility to serialize a navigation state object to a path string.
*
* Example:
* ```js
* getPathFromState(
* {
* routes: [
* {
* name: 'Chat',
* params: { author: 'Jane', id: 42 },
* },
* ],
* },
* {
* Chat: {
* path: 'chat/:author/:id',
* stringify: { author: author => author.toLowerCase() }
* }
* }
* )
* ```
*
* @param state Navigation state to serialize.
* @param options Extra options to fine-tune how to serialize the path.
* @returns Path representing the state, e.g. /foo/bar?count=42.
*/
export default function getPathFromState(state: State): string {
export default function getPathFromState(
state: State,
options: Options = {}
): string {
let path = '/';
let current: State | undefined = state;
@@ -19,24 +50,51 @@ export default function getPathFromState(state: State): string {
state?: State | undefined;
};
path += encodeURIComponent(route.name);
const config =
options[route.name] !== undefined
? (options[route.name] as { stringify?: StringifyConfig }).stringify
: undefined;
const params = route.params
? // Stringify all of the param values before we use them
Object.entries(route.params).reduce<{
[key: string]: string;
}>((acc, [key, value]) => {
acc[key] = config && config[key] ? config[key](value) : String(value);
return acc;
}, {})
: undefined;
if (options[route.name] !== undefined) {
const pattern =
typeof options[route.name] === 'string'
? (options[route.name] as string)
: (options[route.name] as { path: string }).path;
path += pattern
.split('/')
.map(p => {
const name = p.replace(/^:/, '');
// If the path has a pattern for a param, put the param in the path
if (params && name in params && p.startsWith(':')) {
const value = params[name];
// Remove the used value from the params object since we'll use the rest for query string
delete params[name];
return encodeURIComponent(value);
}
return encodeURIComponent(p);
})
.join('/');
} else {
path += encodeURIComponent(route.name);
}
if (route.state) {
path += '/';
} else if (route.params) {
const query = [];
for (const param in route.params) {
const value = (route.params as { [key: string]: any })[param];
query.push(
`${encodeURIComponent(param)}=${encodeURIComponent(
JSON.stringify(value)
)}`
);
}
path += `?${query.join('&')}`;
} else if (params) {
path += `?${queryString.stringify(params)}`;
}
current = route.state;

View File

@@ -1,50 +1,149 @@
import escape from 'escape-string-regexp';
import queryString from 'query-string';
import { NavigationState, PartialState } from './types';
type ParseConfig = { [key: string]: (value: string) => any };
type Options = {
[routeName: string]: string | { path: string; parse?: ParseConfig };
};
/**
* Utility to parse a path string to initial state object accepted by the container.
* This is useful for deep linking when we need to handle the incoming URL.
*
* Example:
* ```js
* getStateFromPath(
* '/chat/jane/42',
* {
* Chat: {
* path: 'chat/:author/:id',
* parse: { id: Number }
* }
* }
* )
* ```
* @param path Path string to parse and convert, e.g. /foo/bar?count=42.
* @param options Extra options to fine-tune how to parse the path.
*/
export default function getStateFromPath(
path: string
path: string,
options: Options = {}
): PartialState<NavigationState> | undefined {
const parts = path.split('?');
const segments = parts[0].split('/').filter(Boolean);
const query = parts[1] ? parts[1].split('&') : undefined;
// Create a normalized config array which will be easier to use
const routeConfig = Object.keys(options).map(key => {
const pattern =
typeof options[key] === 'string'
? (options[key] as string)
: (options[key] as { path: string }).path;
// Create a regex from the provided path pattern
// With the pattern, we can match segements containing params and extract them
const match = new RegExp(
'^' + escape(pattern).replace(/:[a-z0-9]+/gi, '([^/]+)') + '/?'
);
return {
match,
pattern,
routeName: key,
// @ts-ignore
parse: options[key].parse,
};
});
let result: PartialState<NavigationState> | undefined;
let current: PartialState<NavigationState> | undefined;
while (segments.length) {
let remaining = path
.replace(/[/]+/, '/') // Replace multiple slash (//) with single ones
.replace(/^\//, '') // Remove extra leading slash
.replace(/\?.*/, ''); // Remove query params which we will handle later
while (remaining) {
let routeName;
let params;
// Go through all configs, and see if the next path segment matches our regex
for (const config of routeConfig) {
const match = remaining.match(config.match);
// If our regex matches, we need to extract params from the path
if (match) {
routeName = config.routeName;
const paramPatterns = config.pattern
.split('/')
.filter(p => p.startsWith(':'));
if (paramPatterns.length) {
params = paramPatterns.reduce<{ [key: string]: any }>((acc, p, i) => {
const key = p.replace(/^:/, '');
const value = match[i + 1]; // The param segments start from index 1 in the regex match result
acc[key] =
config.parse && config.parse[key]
? config.parse[key](value)
: value;
return acc;
}, {});
}
// Remove the matched segment from the remaining path
remaining = remaining.replace(match[0], '');
break;
}
}
// If we hadn't matched any segments earlier, use the path as route name
if (routeName === undefined) {
const segments = remaining.split('/');
routeName = decodeURIComponent(segments[0]);
segments.shift();
remaining = segments.join('/');
}
const state = {
routes: [{ name: decodeURIComponent(segments[0]) }],
routes: [{ name: routeName, params }],
};
if (current) {
// The state should be nested inside the route we parsed before
current.routes[0].state = state;
} else {
result = state;
}
current = state;
segments.shift();
}
if (current == null || result == null) {
return undefined;
}
const query = path.split('?')[1];
if (query) {
const params = query.reduce<{ [key: string]: any }>((acc, curr) => {
const [key, value] = curr.split('=');
const route = current.routes[0];
acc[decodeURIComponent(key)] = JSON.parse(decodeURIComponent(value));
const params = queryString.parse(query);
const config = options[route.name]
? (options[route.name] as { parse?: ParseConfig }).parse
: undefined;
return acc;
}, {});
if (config) {
Object.keys(params).forEach(name => {
if (config[name] && typeof params[name] === 'string') {
params[name] = config[name](params[name] as string);
}
});
}
current.routes[0].params = params;
route.params = { ...route.params, ...params };
}
return result;

View File

@@ -6,8 +6,12 @@ export { default as BaseRouter } from './BaseRouter';
export { default as NavigationContainer } from './NavigationContainer';
export { default as createNavigator } from './createNavigator';
export { default as NavigationContext } from './NavigationContext';
export { default as NavigationRouteContext } from './NavigationRouteContext';
export { default as useNavigationBuilder } from './useNavigationBuilder';
export { default as useNavigation } from './useNavigation';
export { default as useRoute } from './useRoute';
export { default as useFocusEffect } from './useFocusEffect';
export { default as useIsFocused } from './useIsFocused';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line import/no-cycle
import * as CommonActions from './CommonActions';
import * as React from 'react';
@@ -96,7 +97,12 @@ export type DefaultNavigatorOptions<
/**
* Default options for all screens under this navigator.
*/
screenOptions?: ScreenOptions;
screenOptions?:
| ScreenOptions
| ((props: {
route: RouteProp<ParamListBase, string>;
navigation: any;
}) => ScreenOptions);
};
export type RouterFactory<
@@ -165,7 +171,10 @@ export type Router<
* @param state State object to apply the action on.
* @param action Action object to apply.
*/
getStateForAction(state: State, action: Action): State | null;
getStateForAction(
state: State,
action: Action
): State | PartialState<State> | null;
/**
* Whether the action should also change focus in parent navigator
@@ -265,7 +274,9 @@ type NavigationHelpersCommon<
*
* @param action Action object or update function.
*/
dispatch(action: NavigationAction | ((state: State) => State)): void;
dispatch(
action: NavigationAction | ((state: State) => NavigationAction)
): void;
/**
* Navigate to a route in current navigation tree.
@@ -274,8 +285,8 @@ type NavigationHelpersCommon<
* @param [params] Params object for the route.
*/
navigate<RouteName extends keyof ParamList>(
...args: ParamList[RouteName] extends undefined
? [RouteName] | [RouteName, undefined]
...args: ParamList[RouteName] extends (undefined | any)
? [RouteName] | [RouteName, ParamList[RouteName]]
: [RouteName, ParamList[RouteName]]
): void;
@@ -287,7 +298,7 @@ type NavigationHelpersCommon<
navigate<RouteName extends keyof ParamList>(
route:
| { key: string; params?: ParamList[RouteName] }
| { name: RouteName; params: ParamList[RouteName] }
| { name: RouteName; key?: string; params: ParamList[RouteName] }
): void;
/**
@@ -298,7 +309,7 @@ type NavigationHelpersCommon<
*/
replace<RouteName extends keyof ParamList>(
...args: ParamList[RouteName] extends undefined
? [RouteName] | [RouteName, undefined]
? [RouteName] | [RouteName, ParamList[RouteName]]
: [RouteName, ParamList[RouteName]]
): void;
@@ -309,6 +320,13 @@ type NavigationHelpersCommon<
*/
reset(state: PartialState<State> | State): void;
/**
* Reset the navigation state of the root navigator to the provided state.
*
* @param state Navigation state object.
*/
resetRoot(state: PartialState<NavigationState> | NavigationState): void;
/**
* Go back to the previous route in history.
*/
@@ -382,14 +400,13 @@ export type NavigationProp<
* It can be useful to decide whether to display a back button in a stack.
*/
isFirstRouteInParent(): boolean;
/**
* Returns the parent navigator, if any. Reason why the function is called
* dangerouslyGetParent is to warn developers against overusing it to eg. get parent
* of parent and other hard-to-follow patterns.
*/
dangerouslyGetParent():
| NavigationProp<ParamListBase, string, any, any>
| undefined;
dangerouslyGetParent<T = NavigationProp<ParamListBase> | undefined>(): T;
/**
* Returns the navigator's state. Reason why the function is called
@@ -521,6 +538,7 @@ export type NavigationContainerRef =
* @param state Navigation state object.
*/
resetRoot(state: PartialState<NavigationState> | NavigationState): void;
getRootState(): NavigationState;
}
| undefined
| null;
@@ -534,11 +552,24 @@ export type TypedNavigator<
* Navigator component which manages the child screens.
*/
Navigator: React.ComponentType<
React.ComponentProps<Navigator> & {
Omit<
React.ComponentProps<Navigator>,
'initialRouteName' | 'screenOptions'
> & {
/**
* Route to focus on initial render.
* Name of the route to focus by on initial render.
* If not specified, usually the first route is used.
*/
initialRouteName?: keyof ParamList;
/**
* Default options for all screens under this navigator.
*/
screenOptions?:
| ScreenOptions
| ((props: {
route: RouteProp<ParamList, keyof ParamList>;
navigation: any;
}) => ScreenOptions);
}
>;
/**

View File

@@ -3,6 +3,7 @@ import SceneView from './SceneView';
import NavigationBuilderContext, {
ChildActionListener,
FocusedNavigationListener,
NavigatorStateGetter,
} from './NavigationBuilderContext';
import { NavigationEventEmitter } from './useEventEmitter';
import useNavigationCache from './useNavigationCache';
@@ -13,24 +14,31 @@ import {
NavigationState,
ParamListBase,
RouteConfig,
RouteProp,
Router,
} from './types';
type Options<ScreenOptions extends object> = {
state: NavigationState;
type Options<State extends NavigationState, ScreenOptions extends object> = {
state: State;
screens: { [key: string]: RouteConfig<ParamListBase, string, ScreenOptions> };
navigation: NavigationHelpers<ParamListBase>;
screenOptions?: ScreenOptions;
screenOptions?:
| ScreenOptions
| ((props: {
route: RouteProp<ParamListBase, string>;
navigation: any;
}) => ScreenOptions);
onAction: (
action: NavigationAction,
visitedNavigators?: Set<string>
) => boolean;
getState: () => NavigationState;
setState: (state: NavigationState) => void;
getState: () => State;
setState: (state: State) => void;
addActionListener: (listener: ChildActionListener) => void;
addFocusedListener: (listener: FocusedNavigationListener) => void;
addStateGetter: (key: string, getter: NavigatorStateGetter) => void;
onRouteFocus: (key: string) => void;
router: Router<NavigationState, NavigationAction>;
router: Router<State, NavigationAction>;
emitter: NavigationEventEmitter;
};
@@ -55,10 +63,11 @@ export default function useDescriptors<
setState,
addActionListener,
addFocusedListener,
addStateGetter,
onRouteFocus,
router,
emitter,
}: Options<ScreenOptions>) {
}: Options<State, ScreenOptions>) {
const [options, setOptions] = React.useState<{ [key: string]: object }>({});
const { trackAction } = React.useContext(NavigationBuilderContext);
@@ -68,6 +77,7 @@ export default function useDescriptors<
onAction,
addActionListener,
addFocusedListener,
addStateGetter,
onRouteFocus,
trackAction,
}),
@@ -77,6 +87,7 @@ export default function useDescriptors<
addActionListener,
addFocusedListener,
onRouteFocus,
addStateGetter,
trackAction,
]
);
@@ -93,13 +104,15 @@ export default function useDescriptors<
return state.routes.reduce(
(acc, route) => {
const screen = screens[route.name];
const navigation = navigations[route.key];
acc[route.key] = {
navigation,
render() {
return (
<NavigationBuilderContext.Provider key={route.key} value={context}>
<SceneView
navigation={navigations[route.key]}
navigation={navigation}
route={route}
screen={screen}
getState={getState}
@@ -108,22 +121,30 @@ export default function useDescriptors<
</NavigationBuilderContext.Provider>
);
},
options: {
// The default `screenOptions` passed to the navigator
...screenOptions,
// The `options` prop passed to `Screen` elements
...(typeof screen.options === 'object' || screen.options == null
? screen.options
: screen.options({
// @ts-ignore
route,
navigation: navigations[route.key],
})),
// The options set via `navigation.setOptions`
...options[route.key],
get options() {
return {
// The default `screenOptions` passed to the navigator
...(typeof screenOptions === 'object' || screenOptions == null
? screenOptions
: screenOptions({
// @ts-ignore
route,
navigation,
})),
// The `options` prop passed to `Screen` elements
...(typeof screen.options === 'object' || screen.options == null
? screen.options
: screen.options({
// @ts-ignore
route,
navigation,
})),
// The options set via `navigation.setOptions`
...options[route.key],
};
},
navigation: navigations[route.key],
};
return acc;
},
{} as {

View File

@@ -18,6 +18,7 @@ type DevTools = {
};
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace NodeJS {
interface Global {
__REDUX_DEVTOOLS_EXTENSION__:

View File

@@ -57,19 +57,19 @@ export default function useFocusEvents({ state, emitter }: Options) {
return;
}
state.routes.forEach((route, i) => {
if (
lastFocusedKey === undefined ||
(route.key !== lastFocusedKey && route.key !== currentFocusedKey)
) {
// Only fire events after mount, or if focus state of this route changed
return;
}
if (lastFocusedKey === undefined) {
// Only fire events after initial mount
return;
}
emitter.emit({
type: i === state.index ? 'focus' : 'blur',
target: route.key,
});
emitter.emit({
type: 'focus',
target: currentFocusedKey,
});
}, [currentFocusedKey, emitter, navigation, state.index, state.routes]);
emitter.emit({
type: 'blur',
target: lastFocusedKey,
});
}, [currentFocusedKey, emitter, navigation]);
}

View File

@@ -1,6 +1,8 @@
import * as React from 'react';
import { NavigationStateContext } from './NavigationContainer';
import NavigationRouteContext from './NavigationRouteContext';
import Screen from './Screen';
import { navigate } from './CommonActions';
import useEventEmitter from './useEventEmitter';
import useRegisterNavigator from './useRegisterNavigator';
import useDescriptors from './useDescriptors';
@@ -23,11 +25,20 @@ import {
PrivateValueStore,
NavigationAction,
} from './types';
import useStateGetters from './useStateGetters';
import useOnGetState from './useOnGetState';
// This is to make TypeScript compiler happy
// eslint-disable-next-line babel/no-unused-expressions
PrivateValueStore;
type NavigatorRoute = {
params?: {
screen?: string;
params?: object;
};
};
/**
* Compare two arrays with primitive values as the content.
* We need to make sure that both values and order match.
@@ -94,9 +105,24 @@ export default function useNavigationBuilder<
) {
useRegisterNavigator();
const route = React.useContext(NavigationRouteContext) as (
| NavigatorRoute
| undefined);
const previousRouteRef = React.useRef(route);
React.useEffect(() => {
previousRouteRef.current = route;
}, [route]);
const { children, ...rest } = options;
const { current: router } = React.useRef<Router<State, any>>(
createRouter((rest as unknown) as RouterOptions)
createRouter({
...((rest as unknown) as RouterOptions),
...(route && route.params && typeof route.params.screen === 'string'
? { initialRouteName: route.params.screen }
: null),
})
);
const screens = getRouteConfigsFromChildren<ScreenOptions>(children).reduce(
@@ -116,7 +142,20 @@ export default function useNavigationBuilder<
const routeNames = Object.keys(screens);
const routeParamList = routeNames.reduce(
(acc, curr) => {
acc[curr] = screens[curr].initialParams;
const { initialParams } = screens[curr];
const initialParamsFromParams =
route && route.params && route.params.screen === curr
? route.params.params
: undefined;
acc[curr] =
initialParams !== undefined || initialParamsFromParams !== undefined
? {
...initialParams,
...initialParamsFromParams,
}
: undefined;
return acc;
},
{} as { [key: string]: object | undefined }
@@ -173,28 +212,53 @@ export default function useNavigationBuilder<
? (initializedStateRef.current as State)
: (currentState as State);
let nextState: State = state;
if (!isArrayEqual(state.routeNames, routeNames)) {
// When the list of route names change, the router should handle it to remove invalid routes
const nextState = router.getStateForRouteNamesChange(state, {
nextState = router.getStateForRouteNamesChange(state, {
routeNames,
routeParamList,
});
if (state !== nextState) {
// If the state needs to be updated, we'll schedule an update with React
// setState in render seems hacky, but that's how React docs implement getDerivedPropsFromState
// https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops
performTransaction(() => {
setState(nextState);
});
}
// The up-to-date state will come in next render, but we don't need to wait for it
// We can't use the outdated state since the screens have changed, which will cause error due to mismatched config
// So we override the state objec we return to use the latest state as soon as possible
state = nextState;
}
if (
previousRouteRef.current &&
route &&
route.params &&
typeof route.params.screen === 'string' &&
route.params !== previousRouteRef.current.params
) {
// If the route was updated with new name and/or params, we should navigate there
// The update should be limited to current navigator only, so we call the router manually
const updatedState = router.getStateForAction(
state,
navigate(route.params.screen, route.params.params)
);
nextState =
updatedState !== null
? router.getRehydratedState(updatedState, {
routeNames,
routeParamList,
})
: state;
}
if (state !== nextState) {
// If the state needs to be updated, we'll schedule an update with React
// setState in render seems hacky, but that's how React docs implement getDerivedPropsFromState
// https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops
performTransaction(() => {
setState(nextState);
});
}
// The up-to-date state will come in next render, but we don't need to wait for it
// We can't use the outdated state since the screens have changed, which will cause error due to mismatched config
// So we override the state objec we return to use the latest state as soon as possible
state = nextState;
React.useEffect(() => {
return () => {
// We need to clean up state for this navigator on unmount
@@ -227,6 +291,8 @@ export default function useNavigationBuilder<
addListener: addFocusedListener,
} = useFocusedListeners();
const { getStateForRoute, addStateGetter } = useStateGetters();
const onAction = useOnAction({
router,
getState,
@@ -245,7 +311,6 @@ export default function useNavigationBuilder<
const navigation = useNavigationHelpers<State, NavigationAction, EventMap>({
onAction,
getState,
setState,
emitter,
router,
});
@@ -255,6 +320,11 @@ export default function useNavigationBuilder<
focusedListeners,
});
useOnGetState({
getState,
getStateForRoute,
});
const descriptors = useDescriptors<State, ScreenOptions>({
state,
screens,
@@ -266,6 +336,7 @@ export default function useNavigationBuilder<
onRouteFocus,
addActionListener,
addFocusedListener,
addStateGetter,
router,
emitter,
});

View File

@@ -12,15 +12,15 @@ import {
Router,
} from './types';
type Options = {
state: NavigationState;
getState: () => NavigationState;
type Options<State extends NavigationState> = {
state: State;
getState: () => State;
navigation: NavigationHelpers<ParamListBase> &
Partial<NavigationProp<ParamListBase, string, any, any, any>>;
setOptions: (
cb: (options: { [key: string]: object }) => { [key: string]: object }
) => void;
router: Router<NavigationState, NavigationAction>;
router: Router<State, NavigationAction>;
emitter: NavigationEventEmitter;
};
@@ -39,7 +39,14 @@ type NavigationCache<
export default function useNavigationCache<
State extends NavigationState,
ScreenOptions extends object
>({ state, getState, navigation, setOptions, router, emitter }: Options) {
>({
state,
getState,
navigation,
setOptions,
router,
emitter,
}: Options<State>) {
// Cache object which holds navigation objects for each screen
// We use `React.useMemo` instead of `React.useRef` coz we want to invalidate it when deps change
// In reality, these deps will rarely change, if ever
@@ -70,13 +77,17 @@ export default function useNavigationCache<
const { emit, ...rest } = navigation;
const dispatch = (
action: NavigationAction | ((state: State) => State)
) =>
action: NavigationAction | ((state: State) => NavigationAction)
) => {
const payload =
typeof action === 'function' ? action(getState()) : action;
navigation.dispatch(
typeof action === 'object' && action != null
? { source: route.key, ...action }
: action
typeof payload === 'object' && payload != null
? { source: route.key, ...payload }
: payload
);
};
const helpers = Object.keys(actions).reduce(
(acc, name) => {
@@ -91,8 +102,8 @@ export default function useNavigationCache<
...rest,
...helpers,
...emitter.create(route.key),
dangerouslyGetParent: () => parentNavigation,
dangerouslyGetState: getState as () => State,
dangerouslyGetParent: () => parentNavigation as any,
dangerouslyGetState: getState,
dispatch,
setOptions: (options: object) =>
setOptions(o => ({

View File

@@ -1,6 +1,7 @@
import * as React from 'react';
import * as CommonActions from './CommonActions';
import NavigationContext from './NavigationContext';
import ResetRootContext from './ResetRootContext';
import { NavigationStateContext } from './NavigationContainer';
import { NavigationEventEmitter } from './useEventEmitter';
import {
@@ -23,7 +24,6 @@ type Options<State extends NavigationState, Action extends NavigationAction> = {
visitedNavigators?: Set<string>
) => boolean;
getState: () => State;
setState: (state: State) => void;
emitter: NavigationEventEmitter;
router: Router<State, Action>;
};
@@ -36,18 +36,18 @@ export default function useNavigationHelpers<
State extends NavigationState,
Action extends NavigationAction,
EventMap extends { [key: string]: any }
>({ onAction, getState, setState, emitter, router }: Options<State, Action>) {
>({ onAction, getState, emitter, router }: Options<State, Action>) {
const resetRoot = React.useContext(ResetRootContext);
const parentNavigationHelpers = React.useContext(NavigationContext);
const { performTransaction } = React.useContext(NavigationStateContext);
return React.useMemo(() => {
const dispatch = (action: Action | ((state: State) => State)) =>
const dispatch = (action: Action | ((state: State) => Action)) =>
performTransaction(() => {
if (typeof action === 'function') {
setState(action(getState()));
} else {
onAction(action);
}
const payload =
typeof action === 'function' ? action(getState()) : action;
onAction(payload);
});
const actions = {
@@ -67,6 +67,7 @@ export default function useNavigationHelpers<
return {
...parentNavigationHelpers,
...helpers,
resetRoot,
dispatch,
emit: emitter.emit,
isFocused: parentNavigationHelpers
@@ -85,9 +86,9 @@ export default function useNavigationHelpers<
router,
getState,
parentNavigationHelpers,
resetRoot,
emitter.emit,
performTransaction,
setState,
onAction,
]);
}

View File

@@ -2,13 +2,18 @@ import * as React from 'react';
import NavigationBuilderContext, {
ChildActionListener,
} from './NavigationBuilderContext';
import { NavigationAction, NavigationState, Router } from './types';
import {
NavigationAction,
NavigationState,
PartialState,
Router,
} from './types';
type Options = {
router: Router<NavigationState, NavigationAction>;
key?: string;
getState: () => NavigationState;
setState: (state: NavigationState) => void;
setState: (state: NavigationState | PartialState<NavigationState>) => void;
listeners: ChildActionListener[];
};

View File

@@ -0,0 +1,31 @@
import * as React from 'react';
import NavigationBuilderContext from './NavigationBuilderContext';
import { NavigationState } from './types';
import NavigationRouteContext from './NavigationRouteContext';
export default function useOnGetState({
getStateForRoute,
getState,
}: {
getStateForRoute: (routeName: string) => NavigationState | undefined;
getState: () => NavigationState;
}) {
const { addStateGetter } = React.useContext(NavigationBuilderContext);
const route = React.useContext(NavigationRouteContext);
const key = route ? route.key : 'root';
const getter = React.useCallback(() => {
const state = getState();
return {
...state,
routes: state.routes.map(route => ({
...route,
state: getStateForRoute(route.key),
})),
};
}, [getState, getStateForRoute]);
React.useEffect(() => {
return addStateGetter && addStateGetter(key, getter);
}, [addStateGetter, getter, key]);
}

View File

@@ -0,0 +1,22 @@
import * as React from 'react';
import NavigationRouteContext from './NavigationRouteContext';
import { ParamListBase, RouteProp } from './types';
/**
* Hook to access the route prop of the parent screen anywhere.
*
* @returns Route prop of the parent screen.
*/
export default function useRoute<
T extends RouteProp<ParamListBase, string>
>(): T {
const route = React.useContext(NavigationRouteContext);
if (route === undefined) {
throw new Error(
"We couldn't find a route object. Is your component inside a navigator?"
);
}
return route as T;
}

View File

@@ -0,0 +1,35 @@
import * as React from 'react';
import { NavigatorStateGetter } from './NavigationBuilderContext';
/**
* Hook which lets child navigators add getters to be called for obtaining rehydrated state.
*/
export default function useStateGetters() {
const stateGetters = React.useRef<Record<string, NavigatorStateGetter>>({});
const getStateForRoute = React.useCallback(
routeKey =>
stateGetters.current[routeKey] === undefined
? undefined
: stateGetters.current[routeKey](),
[stateGetters]
);
const addStateGetter = React.useCallback(
(key: string, getter: NavigatorStateGetter) => {
stateGetters.current[key] = getter;
return () => {
// @ts-ignore
stateGetters.current[key] = undefined;
};
},
[]
);
return {
getStateForRoute,
addStateGetter,
};
}

View File

@@ -3,6 +3,115 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.15](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.14...@react-navigation/drawer@5.0.0-alpha.15) (2019-10-17)
### Bug Fixes
* fix passing content options in drawer ([cab6160](https://github.com/react-navigation/navigation-ex/commit/cab6160))
# [5.0.0-alpha.14](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.13...@react-navigation/drawer@5.0.0-alpha.14) (2019-10-15)
### Bug Fixes
* add flex: 1 to drawer content ([2b57702](https://github.com/react-navigation/navigation-ex/commit/2b57702))
* fix content component not rendering in drawer ([0a5fb3e](https://github.com/react-navigation/navigation-ex/commit/0a5fb3e))
### Features
* initial version of native stack ([#102](https://github.com/react-navigation/navigation-ex/issues/102)) ([ba3f718](https://github.com/react-navigation/navigation-ex/commit/ba3f718))
# [5.0.0-alpha.13](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.12...@react-navigation/drawer@5.0.0-alpha.13) (2019-10-06)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.12](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.11...@react-navigation/drawer@5.0.0-alpha.12) (2019-10-03)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.10...@react-navigation/drawer@5.0.0-alpha.11) (2019-10-03)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.9...@react-navigation/drawer@5.0.0-alpha.10) (2019-09-27)
### Features
* export some more type aliases ([8b78d61](https://github.com/react-navigation/navigation-ex/commit/8b78d61))
# [5.0.0-alpha.9](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.8...@react-navigation/drawer@5.0.0-alpha.9) (2019-09-16)
### Features
* make example run as bare react-native project as well ([#85](https://github.com/react-navigation/navigation-ex/issues/85)) ([d16c20c](https://github.com/react-navigation/navigation-ex/commit/d16c20c))
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.7...@react-navigation/drawer@5.0.0-alpha.8) (2019-08-31)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.7](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.6...@react-navigation/drawer@5.0.0-alpha.7) (2019-08-30)
### Bug Fixes
* rename contentContainerStyle to sceneContainerStyle for drawer ([fdc24d2](https://github.com/react-navigation/navigation-ex/commit/fdc24d2))
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.5...@react-navigation/drawer@5.0.0-alpha.6) (2019-08-29)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.4...@react-navigation/drawer@5.0.0-alpha.5) (2019-08-28)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.3...@react-navigation/drawer@5.0.0-alpha.4) (2019-08-27)
**Note:** Version bump only for package @react-navigation/drawer

View File

@@ -10,44 +10,29 @@ Open a Terminal in your project's folder and run,
yarn add @react-navigation/core @react-navigation/drawer
```
Now we need to install [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler) and [`react-native-reanimated`](https://github.com/kmagiera/react-native-reanimated).
Now we need to install [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler), [`react-native-reanimated`](https://github.com/kmagiera/react-native-reanimated) and [`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context).
If you are using Expo, to ensure that you get the compatible versions of the libraries, run:
```sh
expo install react-native-gesture-handler react-native-reanimated
expo install react-native-gesture-handler react-native-reanimated react-native-safe-area-context
```
If you are not using Expo, run the following:
```sh
yarn add react-native-reanimated react-native-gesture-handler
yarn add react-native-reanimated react-native-gesture-handler react-native-safe-area-context
```
If you are using Expo, you are done. Otherwise, continue to the next steps.
Next, we need to link these libraries. The steps depends on your React Native version:
To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run:
- **React Native 0.60 and higher**
On newer versions of React Native, [linking is automatic](https://github.com/react-native-community/cli/blob/master/docs/autolinking.md).
To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run:
```sh
cd ios
pod install
cd ..
```
- **React Native 0.59**
If you're on an older React Native version, you need to manually link the dependencies. To do that, run:
```sh
react-native link react-native-reanimated
react-native link react-native-gesture-handler
```
```sh
cd ios
pod install
cd ..
```
**IMPORTANT:** There are additional steps required for `react-native-gesture-handler` on Android after linking (for all React Native versions). Check the [this guide](https://kmagiera.github.io/react-native-gesture-handler/docs/getting-started.html) to complete the installation.

View File

@@ -11,7 +11,7 @@
"material",
"drawer"
],
"version": "5.0.0-alpha.4",
"version": "5.0.0-alpha.15",
"license": "MIT",
"repository": {
"type": "git",
@@ -34,20 +34,20 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.4",
"react-native-safe-area-view": "^0.14.6"
"@react-navigation/routers": "^5.0.0-alpha.9"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",
"@types/react": "^16.8.24",
"@types/react-native": "^0.60.2",
"del-cli": "^2.0.0",
"react": "16.8.3",
"react-native": "^0.59.8",
"@types/react": "^16.9.4",
"@types/react-native": "^0.60.17",
"del-cli": "^3.0.0",
"react": "~16.8.3",
"react-native": "~0.59.10",
"react-native-gesture-handler": "^1.3.0",
"react-native-reanimated": "^1.1.0",
"react-native-screens": "^1.0.0-alpha.22",
"typescript": "^3.5.3"
"react-native-reanimated": "^1.3.0",
"react-native-safe-area-context": "^0.3.6",
"react-native-screens": "^2.0.0-alpha.3",
"typescript": "^3.6.3"
},
"peerDependencies": {
"@react-navigation/core": "^5.0.0-alpha.0",
@@ -55,7 +55,8 @@
"react-native": "*",
"react-native-gesture-handler": "^1.0.0",
"react-native-reanimated": "^1.0.0",
"react-native-screens": "^1.0.0-alpha.0"
"react-native-safe-area-context": "^0.3.6",
"react-native-screens": "^1.0.0-alpha.0 || ^2.0.0-alpha.3"
},
"@react-native-community/bob": {
"source": "src",

View File

@@ -8,8 +8,9 @@ export {
/**
* Views
*/
export { default as DrawerNavigatorItems } from './views/DrawerNavigatorItems';
export { default as DrawerSidebar } from './views/DrawerSidebar';
export { default as DrawerItem } from './views/DrawerItem';
export { default as DrawerItemList } from './views/DrawerItemList';
export { default as DrawerContent } from './views/DrawerContent';
export { default as DrawerView } from './views/DrawerView';
/**
@@ -20,4 +21,9 @@ export { default as DrawerGestureContext } from './utils/DrawerGestureContext';
/**
* Types
*/
export { DrawerNavigationOptions, DrawerNavigationProp } from './types';
export {
DrawerNavigationOptions,
DrawerNavigationProp,
DrawerContentOptions,
DrawerContentComponentProps,
} from './types';

View File

@@ -12,16 +12,11 @@ import { PanGestureHandler } from 'react-native-gesture-handler';
export type Scene = {
route: Route<string>;
index: number;
focused: boolean;
tintColor?: string;
color?: string;
};
export type DrawerNavigationConfig = {
/**
* Custom background color for the drawer. Defaults to `white`.
*/
drawerBackgroundColor: string;
export type DrawerNavigationConfig<T = DrawerContentOptions> = {
/**
* Position of the drawer on the screen. Defaults to `left`.
*/
@@ -33,11 +28,6 @@ export type DrawerNavigationConfig = {
* - `slide`: Both the screen and the drawer slide on swipe to reveal the drawer.
*/
drawerType: 'front' | 'back' | 'slide';
/**
* Number or a function which returns the width of the drawer.
* If a function is provided, it'll be called again when the screen's dimensions change.
*/
drawerWidth: number | (() => number);
/**
* How far from the edge of the screen the swipe gesture should activate.
*/
@@ -75,53 +65,58 @@ export type DrawerNavigationConfig = {
lazy: boolean;
/**
* Whether a screen should be unmounted when navigating away from it.
* Defaults to `false`..
* Defaults to `false`.
*/
unmountInactiveRoutes?: boolean;
/**
* Custom component used to render as the content of the drawer, for example, navigation items.
* Defaults to `DrawerItems`.
*/
contentComponent: React.ComponentType<ContentComponentProps>;
contentComponent: React.ComponentType<DrawerContentComponentProps<T>>;
/**
* Options for the content component which will be passed as props.
*/
contentOptions?: object;
contentContainerStyle?: StyleProp<ViewStyle>;
style?: StyleProp<ViewStyle>;
contentOptions?: T;
/**
* Style object for the component wrapping the screen content.
*/
sceneContainerStyle?: StyleProp<ViewStyle>;
/**
* Style object for the drawer component.
* You can pass a custom background color for a drawer or a custom width here.
*/
drawerStyle?: StyleProp<ViewStyle>;
};
export type DrawerNavigationOptions = {
title?: string;
drawerLabel?:
| string
| ((props: { tintColor?: string; focused: boolean }) => React.ReactElement);
| ((props: { color: string; focused: boolean }) => React.ReactNode);
drawerIcon?: (props: {
tintColor?: string;
color: string;
size: number;
focused: boolean;
}) => React.ReactElement;
}) => React.ReactNode;
drawerLockMode?: 'unlocked' | 'locked-closed' | 'locked-open';
};
export type ContentComponentProps = DrawerNavigationItemsProps & {
export type DrawerContentComponentProps<T = DrawerContentOptions> = T & {
state: DrawerNavigationState;
navigation: NavigationHelpers<ParamListBase>;
descriptors: { [key: string]: any };
descriptors: DrawerDescriptorMap;
/**
* Animated node which represents the current progress of the drawer's open state.
* `0` is closed, `1` is open.
*/
drawerOpenProgress: Animated.Node<number>;
progress: Animated.Node<number>;
/**
* Position of the drawer on the screen.
*/
drawerPosition: 'left' | 'right';
};
export type DrawerNavigationItemsProps = {
/**
* The array of routes, can be modified or overridden to control what's shown in the drawer.
*/
items: Route<string>[];
/**
* Route key identifying the currently active route.
*/
activeItemKey?: string | null;
export type DrawerContentOptions = {
/**
* Color for the icon and label in the active item in the drawer.
*/
@@ -138,37 +133,22 @@ export type DrawerNavigationItemsProps = {
* Background color for the inactive items in the drawer.
*/
inactiveBackgroundColor?: string;
/**
* Style object for the content section.
*/
itemsContainerStyle?: ViewStyle;
/**
* Style object for the single item, which can contain an icon and/or a label.
*/
itemStyle?: StyleProp<ViewStyle>;
/**
* Style object to overwrite `Text` style inside content section which renders a label.
* Style object to apply to the `Text` inside content section which renders a label.
*/
labelStyle?: StyleProp<TextStyle>;
/**
* Style object to overwrite `Text` style of the active label.
* Style object for the content section.
*/
activeLabelStyle?: StyleProp<TextStyle>;
contentContainerStyle?: StyleProp<ViewStyle>;
/**
* Style object to overwrite `Text` style of the inactive label.
* Style object for the wrapper view.
*/
inactiveLabelStyle?: StyleProp<TextStyle>;
/**
* Style object for the wrapper `View` of the icon.
*/
iconContainerStyle?: StyleProp<ViewStyle>;
/**
* Position of the drawer on the screen.
*/
drawerPosition: 'left' | 'right';
getLabel: (scene: Scene) => React.ReactNode;
renderIcon: (scene: Scene) => React.ReactNode;
onItemPress: (scene: { route: Route<string>; focused: boolean }) => void;
style?: StyleProp<ViewStyle>;
};
export type DrawerNavigationEventMap = {

View File

@@ -88,7 +88,7 @@ type Props = {
statusBarAnimation: 'slide' | 'none' | 'fade';
overlayStyle?: StyleProp<ViewStyle>;
drawerStyle?: StyleProp<ViewStyle>;
contentContainerStyle?: StyleProp<ViewStyle>;
sceneContainerStyle?: StyleProp<ViewStyle>;
renderDrawerContent: Renderer;
renderSceneContent: Renderer;
gestureHandlerProps?: React.ComponentProps<typeof PanGestureHandler>;
@@ -484,7 +484,7 @@ export default class DrawerView extends React.PureComponent<Props> {
drawerPosition,
drawerType,
swipeEdgeWidth,
contentContainerStyle,
sceneContainerStyle,
drawerStyle,
overlayStyle,
onGestureRef,
@@ -534,7 +534,7 @@ export default class DrawerView extends React.PureComponent<Props> {
{
transform: [{ translateX: contentTranslateX }],
},
contentContainerStyle as any,
sceneContainerStyle as any,
]}
>
{renderSceneContent({ progress: this.progress })}
@@ -578,7 +578,6 @@ export default class DrawerView extends React.PureComponent<Props> {
style={[
styles.container,
right ? { right: offset } : { left: offset },
// eslint-disable-next-line react-native/no-inline-styles
{
transform: [{ translateX: drawerTranslateX }],
opacity: this.drawerOpacity,

View File

@@ -0,0 +1,36 @@
import * as React from 'react';
import { ScrollView, StyleSheet } from 'react-native';
import { useSafeArea } from 'react-native-safe-area-context';
import DrawerItemList from './DrawerItemList';
import { DrawerContentComponentProps } from '../types';
export default function DrawerContent({
contentContainerStyle,
style,
drawerPosition,
...rest
}: DrawerContentComponentProps) {
const insets = useSafeArea();
return (
<ScrollView
contentContainerStyle={[
{
paddingTop: insets.top + 4,
paddingLeft: drawerPosition === 'left' ? insets.left : 0,
paddingRight: drawerPosition === 'right' ? insets.right : 0,
},
contentContainerStyle,
]}
style={[styles.container, style]}
>
<DrawerItemList {...rest} />
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});

View File

@@ -0,0 +1,139 @@
import * as React from 'react';
import {
Text,
View,
StyleSheet,
StyleProp,
ViewStyle,
TextStyle,
} from 'react-native';
import TouchableItem from './TouchableItem';
type Props = {
/**
* The label text of the item.
*/
label:
| string
| ((props: { focused: boolean; color: string }) => React.ReactNode);
/**
* Icon to display for the `DrawerItem`.
*/
icon?: (props: {
focused: boolean;
size: number;
color: string;
}) => React.ReactNode;
/**
* Whether to highlight the drawer item as active.
*/
focused?: boolean;
/**
* Function to execute on press.
*/
onPress: () => void;
/**
* Color for the icon and label when the item is active.
*/
activeTintColor?: string;
/**
* Color for the icon and label when the item is inactive.
*/
inactiveTintColor?: string;
/**
* Background color for item when its active.
*/
activeBackgroundColor?: string;
/**
* Background color for item when its inactive.
*/
inactiveBackgroundColor?: string;
/**
* Style object for the label element.
*/
labelStyle?: StyleProp<TextStyle>;
/**
* Style object for the wrapper element.
*/
style?: StyleProp<ViewStyle>;
};
/**
* A component used to show an action item with an icon and a label in a navigation drawer.
*/
export default function DrawerItem({
icon,
label,
focused = false,
activeTintColor = '#6200ee',
inactiveTintColor = 'rgba(0, 0, 0, .68)',
activeBackgroundColor = 'rgba(98, 0, 238, 0.12)',
inactiveBackgroundColor = 'transparent',
style,
onPress,
...rest
}: Props) {
const { borderRadius = 4 } = StyleSheet.flatten(style || {});
const color = focused ? activeTintColor : inactiveTintColor;
const backgroundColor = focused
? activeBackgroundColor
: inactiveBackgroundColor;
const iconNode = icon ? icon({ size: 24, focused, color }) : null;
return (
<View
collapsable={false}
{...rest}
style={[styles.container, { borderRadius, backgroundColor }, style]}
>
<TouchableItem
borderless
delayPressIn={0}
onPress={onPress}
style={[styles.wrapper, { borderRadius }]}
accessibilityTraits={focused ? ['button', 'selected'] : 'button'}
accessibilityComponentType="button"
accessibilityRole="button"
accessibilityStates={focused ? ['selected'] : []}
>
<React.Fragment>
{iconNode}
{typeof label === 'function' ? (
label({ color, focused })
) : (
<Text
numberOfLines={1}
style={[
styles.label,
{
color,
fontWeight: '500',
marginLeft: iconNode ? 32 : 0,
marginVertical: 5,
},
]}
>
{label}
</Text>
)}
</React.Fragment>
</TouchableItem>
</View>
);
}
const styles = StyleSheet.create({
container: {
marginHorizontal: 10,
marginVertical: 4,
},
wrapper: {
flexDirection: 'row',
alignItems: 'center',
padding: 8,
},
label: {
marginRight: 32,
},
});

View File

@@ -0,0 +1,67 @@
import * as React from 'react';
import { CommonActions } from '@react-navigation/core';
import {
DrawerActions,
DrawerNavigationState,
} from '@react-navigation/routers';
import DrawerItem from './DrawerItem';
import {
DrawerNavigationHelpers,
DrawerDescriptorMap,
DrawerContentOptions,
} from '../types';
type Props = Omit<DrawerContentOptions, 'contentContainerStyle' | 'style'> & {
state: DrawerNavigationState;
navigation: DrawerNavigationHelpers;
descriptors: DrawerDescriptorMap;
};
/**
* Component that renders the navigation list in the drawer.
*/
export default function DrawerItemList({
state,
navigation,
descriptors,
activeTintColor,
inactiveTintColor,
activeBackgroundColor,
inactiveBackgroundColor,
itemStyle,
labelStyle,
}: Props) {
return (state.routes.map((route, i) => {
const focused = i === state.index;
const { title, drawerLabel, drawerIcon } = descriptors[route.key].options;
return (
<DrawerItem
key={route.key}
label={
drawerLabel !== undefined
? drawerLabel
: title !== undefined
? title
: route.name
}
icon={drawerIcon}
focused={focused}
activeTintColor={activeTintColor}
inactiveTintColor={inactiveTintColor}
activeBackgroundColor={activeBackgroundColor}
inactiveBackgroundColor={inactiveBackgroundColor}
labelStyle={labelStyle}
style={itemStyle}
onPress={() => {
navigation.dispatch({
...(focused
? DrawerActions.closeDrawer()
: CommonActions.navigate(route.name)),
target: state.key,
});
}}
/>
);
}) as React.ReactNode) as React.ReactElement;
}

View File

@@ -1,120 +0,0 @@
import * as React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import SafeAreaView from 'react-native-safe-area-view';
import TouchableItem from './TouchableItem';
import { DrawerNavigationItemsProps } from '../types';
/**
* Component that renders the navigation list in the drawer.
*/
const DrawerNavigatorItems = ({
items,
activeItemKey,
activeTintColor,
activeBackgroundColor,
inactiveTintColor,
inactiveBackgroundColor,
getLabel,
renderIcon,
onItemPress,
itemsContainerStyle,
itemStyle,
labelStyle,
activeLabelStyle,
inactiveLabelStyle,
iconContainerStyle,
drawerPosition,
}: DrawerNavigationItemsProps) => (
<View style={[styles.container, itemsContainerStyle]}>
{items.map((route, index: number) => {
const focused = activeItemKey === route.key;
const color = focused ? activeTintColor : inactiveTintColor;
const backgroundColor = focused
? activeBackgroundColor
: inactiveBackgroundColor;
const scene = { route, index, focused, tintColor: color };
const icon = renderIcon(scene);
const label = getLabel(scene);
const accessibilityLabel = typeof label === 'string' ? label : undefined;
const extraLabelStyle = focused ? activeLabelStyle : inactiveLabelStyle;
return (
<TouchableItem
key={route.key}
accessible
accessibilityLabel={accessibilityLabel}
onPress={() => {
onItemPress({ route, focused });
}}
delayPressIn={0}
>
<SafeAreaView
style={[{ backgroundColor }, styles.item, itemStyle]}
forceInset={{
[drawerPosition]: 'always',
[drawerPosition === 'left' ? 'right' : 'left']: 'never',
vertical: 'never',
}}
>
{icon ? (
<View
style={[
styles.icon,
focused ? null : styles.inactiveIcon,
iconContainerStyle,
]}
>
{icon}
</View>
) : null}
{typeof label === 'string' ? (
<Text
style={[styles.label, { color }, labelStyle, extraLabelStyle]}
>
{label}
</Text>
) : (
label
)}
</SafeAreaView>
</TouchableItem>
);
})}
</View>
);
/* Material design specs - https://material.io/guidelines/patterns/navigation-drawer.html#navigation-drawer-specs */
DrawerNavigatorItems.defaultProps = {
activeTintColor: '#2196f3',
activeBackgroundColor: 'rgba(0, 0, 0, .04)',
inactiveTintColor: 'rgba(0, 0, 0, .87)',
inactiveBackgroundColor: 'transparent',
};
const styles = StyleSheet.create({
container: {
paddingVertical: 4,
},
item: {
flexDirection: 'row',
alignItems: 'center',
},
icon: {
marginHorizontal: 16,
width: 24,
alignItems: 'center',
},
inactiveIcon: {
/*
* Icons have 0.54 opacity according to guidelines
* 100/87 * 54 ~= 62
*/
opacity: 0.62,
},
label: {
margin: 16,
fontWeight: 'bold',
},
});
export default DrawerNavigatorItems;

View File

@@ -1,128 +0,0 @@
import * as React from 'react';
import { StyleSheet, View, ViewStyle, StyleProp } from 'react-native';
import Animated from 'react-native-reanimated';
import { Route, CommonActions } from '@react-navigation/core';
import {
DrawerActions,
DrawerNavigationState,
} from '@react-navigation/routers';
import {
Scene,
ContentComponentProps,
DrawerDescriptorMap,
DrawerNavigationHelpers,
} from '../types';
type Props = {
contentComponent?: React.ComponentType<ContentComponentProps>;
contentOptions?: object;
state: DrawerNavigationState;
navigation: DrawerNavigationHelpers;
descriptors: DrawerDescriptorMap;
drawerOpenProgress: Animated.Node<number>;
drawerPosition: 'left' | 'right';
style?: StyleProp<ViewStyle>;
};
/**
* Component that renders the sidebar screen of the drawer.
*/
class DrawerSidebar extends React.PureComponent<Props> {
private getScreenOptions = (routeKey: string) => {
const descriptor = this.props.descriptors[routeKey];
if (!descriptor.options) {
throw new Error(
'Cannot access screen descriptor options from drawer sidebar'
);
}
return descriptor.options;
};
private getLabel = ({ focused, tintColor, route }: Scene) => {
const { drawerLabel, title } = this.getScreenOptions(route.key);
if (drawerLabel) {
return typeof drawerLabel === 'function'
? drawerLabel({ tintColor, focused })
: drawerLabel;
}
if (typeof title === 'string') {
return title;
}
return route.name;
};
private renderIcon = ({ focused, tintColor, route }: Scene) => {
const { drawerIcon } = this.getScreenOptions(route.key);
if (drawerIcon) {
return typeof drawerIcon === 'function'
? drawerIcon({ tintColor, focused })
: drawerIcon;
}
return null;
};
private handleItemPress = ({
route,
focused,
}: {
route: Route<string>;
focused: boolean;
}) => {
const { state, navigation } = this.props;
navigation.dispatch({
...(focused
? DrawerActions.closeDrawer()
: CommonActions.navigate(route.name)),
target: state.key,
});
};
render() {
const ContentComponent = this.props.contentComponent;
if (!ContentComponent) {
return null;
}
const { state } = this.props;
if (typeof state.index !== 'number') {
throw new Error(
'The index of the route should be state in the navigation state'
);
}
return (
<View style={[styles.container, this.props.style]}>
<ContentComponent
{...this.props.contentOptions}
navigation={this.props.navigation}
descriptors={this.props.descriptors}
drawerOpenProgress={this.props.drawerOpenProgress}
items={state.routes}
activeItemKey={
state.routes[state.index] ? state.routes[state.index].key : null
}
getLabel={this.getLabel}
renderIcon={this.renderIcon}
onItemPress={this.handleItemPress}
drawerPosition={this.props.drawerPosition}
/>
</View>
);
}
}
export default DrawerSidebar;
const styles = StyleSheet.create({
container: {
flex: 1,
},
});

View File

@@ -1,30 +1,35 @@
import * as React from 'react';
import { Dimensions, StyleSheet, I18nManager, Platform } from 'react-native';
import {
Dimensions,
StyleSheet,
I18nManager,
Platform,
ScaledSize,
} from 'react-native';
import { SafeAreaProvider } from 'react-native-safe-area-context';
// eslint-disable-next-line import/no-unresolved
import { ScreenContainer } from 'react-native-screens';
import SafeAreaView from 'react-native-safe-area-view';
import { PanGestureHandler, ScrollView } from 'react-native-gesture-handler';
import { PanGestureHandler } from 'react-native-gesture-handler';
import {
DrawerNavigationState,
DrawerActions,
} from '@react-navigation/routers';
import DrawerSidebar from './DrawerSidebar';
import DrawerGestureContext from '../utils/DrawerGestureContext';
import ResourceSavingScene from './ResourceSavingScene';
import DrawerNavigatorItems from './DrawerNavigatorItems';
import DrawerContent from './DrawerContent';
import Drawer from './Drawer';
import {
DrawerDescriptorMap,
DrawerNavigationConfig,
ContentComponentProps,
DrawerNavigationHelpers,
} from '../types';
type Props = DrawerNavigationConfig & {
type Props = Omit<DrawerNavigationConfig, 'overlayColor'> & {
state: DrawerNavigationState;
navigation: DrawerNavigationHelpers;
descriptors: DrawerDescriptorMap;
overlayColor: string;
};
type State = {
@@ -32,13 +37,26 @@ type State = {
drawerWidth: number;
};
const DefaultContentComponent = (props: ContentComponentProps) => (
<ScrollView alwaysBounceVertical={false}>
<SafeAreaView forceInset={{ top: 'always', horizontal: 'never' }}>
<DrawerNavigatorItems {...props} />
</SafeAreaView>
</ScrollView>
);
const getDefaultDrawerWidth = ({
height,
width,
}: {
height: number;
width: number;
}) => {
/*
* Default drawer width is screen width - header height
* with a max width of 280 on mobile and 320 on tablet
* https://material.io/guidelines/patterns/navigation-drawer.html
*/
const smallerAxisSize = Math.min(height, width);
const isLandscape = width > height;
const isTablet = smallerAxisSize >= 600;
const appBarHeight = Platform.OS === 'ios' ? (isLandscape ? 32 : 44) : 56;
const maxWidth = isTablet ? 320 : 280;
return Math.min(smallerAxisSize - appBarHeight, maxWidth);
};
/**
* Component that renders the drawer.
@@ -46,25 +64,10 @@ const DefaultContentComponent = (props: ContentComponentProps) => (
export default class DrawerView extends React.PureComponent<Props, State> {
static defaultProps = {
lazy: true,
drawerWidth: () => {
/*
* Default drawer width is screen width - header height
* with a max width of 280 on mobile and 320 on tablet
* https://material.io/guidelines/patterns/navigation-drawer.html
*/
const { height, width } = Dimensions.get('window');
const smallerAxisSize = Math.min(height, width);
const isLandscape = width > height;
const isTablet = smallerAxisSize >= 600;
const appBarHeight = Platform.OS === 'ios' ? (isLandscape ? 32 : 44) : 56;
const maxWidth = isTablet ? 320 : 280;
return Math.min(smallerAxisSize - appBarHeight, maxWidth);
},
contentComponent: DefaultContentComponent,
contentComponent: DrawerContent,
drawerPosition: I18nManager.isRTL ? 'right' : 'left',
keyboardDismissMode: 'on-drag',
drawerBackgroundColor: 'white',
overlayColor: 'rgba(0, 0, 0, 0.5)',
drawerType: 'front',
hideStatusBar: false,
statusBarAnimation: 'slide',
@@ -83,10 +86,7 @@ export default class DrawerView extends React.PureComponent<Props, State> {
state: State = {
loaded: [this.props.state.index],
drawerWidth:
typeof this.props.drawerWidth === 'function'
? this.props.drawerWidth()
: this.props.drawerWidth,
drawerWidth: getDefaultDrawerWidth(Dimensions.get('window')),
};
componentDidMount() {
@@ -121,11 +121,8 @@ export default class DrawerView extends React.PureComponent<Props, State> {
navigation.emit({ type: 'drawerClose' });
};
private updateWidth = () => {
const drawerWidth =
typeof this.props.drawerWidth === 'function'
? this.props.drawerWidth()
: this.props.drawerWidth;
private updateWidth = ({ window }: { window: ScaledSize }) => {
const drawerWidth = getDefaultDrawerWidth(window);
if (this.state.drawerWidth !== drawerWidth) {
this.setState({ drawerWidth });
@@ -133,7 +130,25 @@ export default class DrawerView extends React.PureComponent<Props, State> {
};
private renderNavigationView = ({ progress }: any) => {
return <DrawerSidebar drawerOpenProgress={progress} {...this.props} />;
const {
state,
navigation,
descriptors,
drawerPosition,
contentComponent: ContentComponent,
contentOptions,
} = this.props;
return (
<ContentComponent
progress={progress}
state={state}
navigation={navigation}
descriptors={descriptors}
drawerPosition={drawerPosition}
{...contentOptions}
/>
);
};
private renderContent = () => {
@@ -163,7 +178,6 @@ export default class DrawerView extends React.PureComponent<Props, State> {
key={route.key}
style={[
StyleSheet.absoluteFill,
// eslint-disable-next-line react-native/no-inline-styles
{ opacity: isFocused ? 1 : 0 },
]}
isVisible={isFocused}
@@ -188,9 +202,9 @@ export default class DrawerView extends React.PureComponent<Props, State> {
descriptors,
drawerType,
drawerPosition,
drawerBackgroundColor,
overlayColor,
contentContainerStyle,
sceneContainerStyle,
drawerStyle,
edgeWidth,
minSwipeDistance,
hideStatusBar,
@@ -198,6 +212,8 @@ export default class DrawerView extends React.PureComponent<Props, State> {
gestureHandlerProps,
} = this.props;
const { drawerWidth } = this.state;
const activeKey = state.routes[state.index].key;
const { drawerLockMode } = descriptors[activeKey].options;
@@ -209,35 +225,32 @@ export default class DrawerView extends React.PureComponent<Props, State> {
: state.isDrawerOpen;
return (
<DrawerGestureContext.Provider value={this.drawerGestureRef}>
<Drawer
open={isOpen}
locked={
drawerLockMode === 'locked-open' ||
drawerLockMode === 'locked-closed'
}
onOpen={this.handleDrawerOpen}
onClose={this.handleDrawerClose}
onGestureRef={this.setDrawerGestureRef}
gestureHandlerProps={gestureHandlerProps}
drawerType={drawerType}
drawerPosition={drawerPosition}
contentContainerStyle={contentContainerStyle}
drawerStyle={{
backgroundColor: drawerBackgroundColor || 'white',
width: this.state.drawerWidth,
}}
overlayStyle={{
backgroundColor: overlayColor || 'rgba(0, 0, 0, 0.5)',
}}
swipeEdgeWidth={edgeWidth}
swipeDistanceThreshold={minSwipeDistance}
hideStatusBar={hideStatusBar}
statusBarAnimation={statusBarAnimation}
renderDrawerContent={this.renderNavigationView}
renderSceneContent={this.renderContent}
/>
</DrawerGestureContext.Provider>
<SafeAreaProvider>
<DrawerGestureContext.Provider value={this.drawerGestureRef}>
<Drawer
open={isOpen}
locked={
drawerLockMode === 'locked-open' ||
drawerLockMode === 'locked-closed'
}
onOpen={this.handleDrawerOpen}
onClose={this.handleDrawerClose}
onGestureRef={this.setDrawerGestureRef}
gestureHandlerProps={gestureHandlerProps}
drawerType={drawerType}
drawerPosition={drawerPosition}
sceneContainerStyle={sceneContainerStyle}
drawerStyle={[{ width: drawerWidth }, drawerStyle]}
overlayStyle={{ backgroundColor: overlayColor }}
swipeEdgeWidth={edgeWidth}
swipeDistanceThreshold={minSwipeDistance}
hideStatusBar={hideStatusBar}
statusBarAnimation={statusBarAnimation}
renderDrawerContent={this.renderNavigationView}
renderSceneContent={this.renderContent}
/>
</DrawerGestureContext.Provider>
</SafeAreaProvider>
);
}
}

1
packages/example/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
ios/Pods

View File

@@ -3,6 +3,127 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.14](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.13...@react-navigation/example@5.0.0-alpha.14) (2019-10-17)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.13](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.12...@react-navigation/example@5.0.0-alpha.13) (2019-10-15)
### Bug Fixes
* block GH interactions in Native Stack example ([#126](https://github.com/react-navigation/navigation-ex/issues/126)) ([386d1c0](https://github.com/react-navigation/navigation-ex/commit/386d1c0))
* make it possible to run the example on web ([7a901af](https://github.com/react-navigation/navigation-ex/commit/7a901af))
### Features
* initial version of native stack ([#102](https://github.com/react-navigation/navigation-ex/issues/102)) ([ba3f718](https://github.com/react-navigation/navigation-ex/commit/ba3f718))
# [5.0.0-alpha.12](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.11...@react-navigation/example@5.0.0-alpha.12) (2019-10-06)
### Features
* drop header: null in favor of more explitit headerShown option ([ba6b6ae](https://github.com/satya164/navigation-ex/commit/ba6b6ae))
# [5.0.0-alpha.11](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.10...@react-navigation/example@5.0.0-alpha.11) (2019-10-03)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.10](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.9...@react-navigation/example@5.0.0-alpha.10) (2019-10-03)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.9](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.8...@react-navigation/example@5.0.0-alpha.9) (2019-10-03)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.7...@react-navigation/example@5.0.0-alpha.8) (2019-09-27)
### Bug Fixes
* close drawer on navigate ([655a220](https://github.com/react-navigation/navigation-ex/commit/655a220))
# [5.0.0-alpha.7](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.6...@react-navigation/example@5.0.0-alpha.7) (2019-09-16)
### Features
* compatibility layer ([e0f28a4](https://github.com/satya164/navigation-ex/commit/e0f28a4))
* make example run as bare react-native project as well ([#85](https://github.com/satya164/navigation-ex/issues/85)) ([d16c20c](https://github.com/satya164/navigation-ex/commit/d16c20c))
# [5.0.0-alpha.6](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.4...@react-navigation/example@5.0.0-alpha.6) (2019-08-31)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.5](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.4...@react-navigation/example@5.0.0-alpha.5) (2019-08-31)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.4](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.3...@react-navigation/example@5.0.0-alpha.4) (2019-08-31)
### Bug Fixes
* handle route names change when all routes are removed ([#86](https://github.com/satya164/navigation-ex/issues/86)) ([1b2983e](https://github.com/satya164/navigation-ex/commit/1b2983e))
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.2...@react-navigation/example@5.0.0-alpha.3) (2019-08-29)
### Features
* handle more methods in useScrollToTop ([f9e8c7e](https://github.com/react-navigation/navigation-ex/commit/f9e8c7e))
# [5.0.0-alpha.2](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.1...@react-navigation/example@5.0.0-alpha.2) (2019-08-27)

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="ReactNavigationExample" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="java-gradle" name="Java-Gradle">
<configuration>
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
<option name="BUILDABLE" value="false" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,65 @@
# To learn about Buck see [Docs](https://buckbuild.com/).
# To run your application with Buck:
# - install Buck
# - `npm start` - to start the packager
# - `cd android`
# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
# - `buck install -r android/app` - compile, install and run application
#
lib_deps = []
for jarfile in glob(['libs/*.jar']):
name = 'jars__' + jarfile[jarfile.rindex('/') + 1: jarfile.rindex('.jar')]
lib_deps.append(':' + name)
prebuilt_jar(
name = name,
binary_jar = jarfile,
)
for aarfile in glob(['libs/*.aar']):
name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')]
lib_deps.append(':' + name)
android_prebuilt_aar(
name = name,
aar = aarfile,
)
android_library(
name = "all-libs",
exported_deps = lib_deps,
)
android_library(
name = "app-code",
srcs = glob([
"src/main/java/**/*.java",
]),
deps = [
":all-libs",
":build_config",
":res",
],
)
android_build_config(
name = "build_config",
package = "com.reactnavigationexample",
)
android_resource(
name = "res",
package = "com.reactnavigationexample",
res = "src/main/res",
)
android_binary(
name = "app",
keystore = "//android/keystores:debug",
manifest = "src/main/AndroidManifest.xml",
package_type = "debug",
deps = [
":app-code",
],
)

View File

@@ -0,0 +1,180 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":app" />
<option name="LAST_SUCCESSFUL_SYNC_AGP_VERSION" value="3.3.0" />
<option name="LAST_KNOWN_AGP_VERSION" value="3.3.0" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<afterSyncTasks>
<task>generateDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res;file://$MODULE_DIR$/build/generated/res/rs/debug;file://$MODULE_DIR$/build/generated/res/resValues/debug" />
<option name="TEST_RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes" />
<output-test url="file://$MODULE_DIR$/build/intermediates/javac/debugUnitTest/compileDebugUnitTestJavaWithJavac/classes" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/aidl_source_output_dir/debug/compileDebugAidl/out" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/renderscript_source_output_dir/debug/compileDebugRenderscript/out" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/react/debug" type="java-resource" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/aidl_source_output_dir/debugAndroidTest/compileDebugAndroidTestAidl/out" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/renderscript_source_output_dir/debugAndroidTest/compileDebugAndroidTestRenderscript/out" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="Android API 28 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Gradle: com.android.support:collections:28.0.0@jar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:common:1.1.1@jar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.core:common:1.1.1@jar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-annotations:28.0.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.infer.annotation:infer-annotation:0.11.2@jar" level="project" />
<orderEntry type="library" name="Gradle: javax.inject:javax.inject:1@jar" level="project" />
<orderEntry type="library" name="Gradle: com.google.code.findbugs:jsr305:3.0.2@jar" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.okhttp3:okhttp-urlconnection:3.12.1@jar" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.okhttp3:okhttp:3.12.1@jar" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.okio:okio:1.15.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.device.yearclass:yearclass:2.1.0@jar" level="project" />
<orderEntry type="library" name="Gradle: commons-codec:commons-codec:1.10@jar" level="project" />
<orderEntry type="library" name="Gradle: commons-io:commons-io:1.4@jar" level="project" />
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:disklrucache:4.9.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:annotations:4.9.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.parse.bolts:bolts-tasks:1.4.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.react:react-native:0.59.10@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:appcompat-v7:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:glide:4.9.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-location:16.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-base:16.0.1@aar" level="project" />
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-places-placereport:16.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-tasks:16.0.1@aar" level="project" />
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-basement:16.0.1@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-v4:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-fragment:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:animated-vector-drawable:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:customtabs:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-core-ui:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-core-utils:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-vector-drawable:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:loader:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-media-compat:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:viewpager:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:coordinatorlayout:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:drawerlayout:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:slidingpanelayout:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:customview:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:swiperefreshlayout:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:asynclayoutinflater:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-compat:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:versionedparcelable:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:cursoradapter:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:runtime:1.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:documentfile:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:localbroadcastmanager:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:print:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:viewmodel:1.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:gifdecoder:4.9.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:interpolator:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:livedata:1.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:livedata-core:1.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.core:runtime:1.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:fresco:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-okhttp3:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:drawee:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-base:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.soloader:soloader:0.6.0@aar" level="project" />
<orderEntry type="library" name="Gradle: io.nlopez.smartlocation:library:3.2.11@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:fbcore:1.10.0@aar" level="project" />
<orderEntry type="module" module-name="react-native-screens" />
<orderEntry type="module" module-name="react-native-reanimated" />
<orderEntry type="module" module-name="react-native-gesture-handler" />
<orderEntry type="module" module-name="expo-permissions" />
<orderEntry type="module" module-name="unimodules-core" />
<orderEntry type="module" module-name="unimodules-react-native-adapter" />
<orderEntry type="module" module-name="expo-app-loader-provider" />
<orderEntry type="module" module-name="expo-constants" />
<orderEntry type="module" module-name="expo-file-system" />
<orderEntry type="module" module-name="expo-font" />
<orderEntry type="module" module-name="expo-keep-awake" />
<orderEntry type="module" module-name="expo-linear-gradient" />
<orderEntry type="module" module-name="expo-location" />
<orderEntry type="module" module-name="expo-sqlite" />
<orderEntry type="module" module-name="expo-web-browser" />
<orderEntry type="module" module-name="unimodules-barcode-scanner-interface" />
<orderEntry type="module" module-name="unimodules-camera-interface" />
<orderEntry type="module" module-name="unimodules-constants-interface" />
<orderEntry type="module" module-name="unimodules-face-detector-interface" />
<orderEntry type="module" module-name="unimodules-file-system-interface" />
<orderEntry type="module" module-name="unimodules-font-interface" />
<orderEntry type="module" module-name="unimodules-image-loader-interface" />
<orderEntry type="module" module-name="unimodules-permissions-interface" />
<orderEntry type="module" module-name="unimodules-sensors-interface" />
<orderEntry type="module" module-name="unimodules-task-manager-interface" />
</component>
</module>

View File

@@ -0,0 +1,157 @@
apply plugin: "com.android.application"
import com.android.build.OutputFile
/**
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
* and bundleReleaseJsAndAssets).
* These basically call `react-native bundle` with the correct arguments during the Android build
* cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
* bundle directly from the development server. Below you can see all the possible configurations
* and their defaults. If you decide to add a configuration block, make sure to add it before the
* `apply from: "../../node_modules/react-native/react.gradle"` line.
*
* project.ext.react = [
* // the name of the generated asset file containing your JS bundle
* bundleAssetName: "index.android.bundle",
*
* // the entry file for bundle generation
* entryFile: "index.android.js",
*
* // whether to bundle JS and assets in debug mode
* bundleInDebug: false,
*
* // whether to bundle JS and assets in release mode
* bundleInRelease: true,
*
* // whether to bundle JS and assets in another build variant (if configured).
* // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
* // The configuration property can be in the following formats
* // 'bundleIn${productFlavor}${buildType}'
* // 'bundleIn${buildType}'
* // bundleInFreeDebug: true,
* // bundleInPaidRelease: true,
* // bundleInBeta: true,
*
* // whether to disable dev mode in custom build variants (by default only disabled in release)
* // for example: to disable dev mode in the staging build type (if configured)
* devDisabledInStaging: true,
* // The configuration property can be in the following formats
* // 'devDisabledIn${productFlavor}${buildType}'
* // 'devDisabledIn${buildType}'
*
* // the root of your project, i.e. where "package.json" lives
* root: "../../",
*
* // where to put the JS bundle asset in debug mode
* jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
*
* // where to put the JS bundle asset in release mode
* jsBundleDirRelease: "$buildDir/intermediates/assets/release",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in debug mode
* resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in release mode
* resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
*
* // by default the gradle tasks are skipped if none of the JS files or assets change; this means
* // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
* // date; if you have any other folders that you want to ignore for performance reasons (gradle
* // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
* // for example, you might want to remove it from here.
* inputExcludes: ["android/**", "ios/**"],
*
* // override which node gets called and with what additional arguments
* nodeExecutableAndArgs: ["node"],
*
* // supply additional arguments to the packager
* extraPackagerArgs: []
* ]
*/
project.ext.react = [
entryFile: "index.js"
]
apply from: '../../node_modules/react-native-unimodules/gradle.groovy'
apply from: "../../node_modules/react-native/react.gradle"
/**
* Set this to true to create two separate APKs instead of one:
* - An APK that only works on ARM devices
* - An APK that only works on x86 devices
* The advantage is the size of the APK is reduced by about 4MB.
* Upload all the APKs to the Play Store and people will download
* the correct one based on the CPU architecture of their device.
*/
def enableSeparateBuildPerCPUArchitecture = false
/**
* Run Proguard to shrink the Java bytecode in release builds.
*/
def enableProguardInReleaseBuilds = false
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId "com.reactnavigationexample"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
}
buildTypes {
release {
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
def versionCodes = ["armeabi-v7a":1, "x86":2, "arm64-v8a": 3, "x86_64": 4]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
}
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation project(':react-native-safe-area-context')
implementation project(':react-native-screens')
implementation project(':react-native-reanimated')
implementation project(':react-native-gesture-handler')
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
implementation "com.facebook.react:react-native:+" // From node_modules
addUnimodulesDependencies()
}
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.compile
into 'libs'
}

View File

@@ -0,0 +1,19 @@
"""Helper definitions to glob .aar and .jar targets"""
def create_aar_targets(aarfiles):
for aarfile in aarfiles:
name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")]
lib_deps.append(":" + name)
android_prebuilt_aar(
name = name,
aar = aarfile,
)
def create_jar_targets(jarfiles):
for jarfile in jarfiles:
name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")]
lib_deps.append(":" + name)
prebuilt_jar(
name = name,
binary_jar = jarfile,
)

View File

@@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -0,0 +1,48 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.reactnavigationexample">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<!-- OPTIONAL PERMISSIONS, REMOVE WHATEVER YOU DO NOT NEED -->
<uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!-- These require runtime permissions on M -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<!-- END OPTIONAL PERMISSIONS -->
<application
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>

View File

@@ -0,0 +1,28 @@
package com.reactnavigationexample;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
public class MainActivity extends ReactActivity {
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "ReactNavigationExample";
}
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName()) {
@Override
protected ReactRootView createRootView() {
return new RNGestureHandlerEnabledRootView(MainActivity.this);
}
};
}
}

View File

@@ -0,0 +1,68 @@
package com.reactnavigationexample;
import android.app.Application;
import com.facebook.react.ReactApplication;
import com.th3rdwave.safeareacontext.SafeAreaContextPackage;
import com.swmansion.rnscreens.RNScreensPackage;
import com.swmansion.reanimated.ReanimatedPackage;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;
import com.reactnavigationexample.generated.BasePackageList;
import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
import org.unimodules.adapters.react.ReactAdapterPackage;
import org.unimodules.adapters.react.ModuleRegistryAdapter;
import org.unimodules.adapters.react.ReactModuleRegistryProvider;
import org.unimodules.core.interfaces.Package;
import org.unimodules.core.interfaces.SingletonModule;
import expo.modules.constants.ConstantsPackage;
import expo.modules.permissions.PermissionsPackage;
import expo.modules.filesystem.FileSystemPackage;
import java.util.Arrays;
import java.util.List;
public class MainApplication extends Application implements ReactApplication {
private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider(
new BasePackageList().getPackageList(),
Arrays.<SingletonModule>asList()
);
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new SafeAreaContextPackage(),
new RNScreensPackage(),
new ReanimatedPackage(),
new RNGestureHandlerPackage(),
new ModuleRegistryAdapter(mModuleRegistryProvider)
);
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Some files were not shown because too many files have changed in this diff Show More