Compare commits

...

57 Commits

Author SHA1 Message Date
Satyajit Sahoo
edeb2e8ad9 chore: publish
- @react-navigation/stack@5.0.0-alpha.53
2020-01-05 14:29:27 +01:00
Satyajit Sahoo
133b59cd17 feat: expose header height in context 2020-01-05 14:26:16 +01:00
Satyajit Sahoo
a9e584c3b7 fix: compare with correct height when floating header height updates 2020-01-05 13:58:24 +01:00
Satyajit Sahoo
c46e0a9c14 chore: validate path to type definitions on CI 2020-01-05 13:11:08 +01:00
Satyajit Sahoo
418a858f23 chore: publish
- @react-navigation/stack@5.0.0-alpha.52
2020-01-05 02:31:09 +01:00
Satyajit Sahoo
b201fd2071 feat: add headerStatusBarHeight option to stack 2020-01-05 02:30:09 +01:00
Satyajit Sahoo
c514542305 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.32
 - @react-navigation/compat@5.0.0-alpha.21
 - @react-navigation/drawer@5.0.0-alpha.34
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.29
 - @react-navigation/material-top-tabs@5.0.0-alpha.27
 - @react-navigation/native-stack@5.0.0-alpha.22
 - @react-navigation/routers@5.0.0-alpha.20
 - @react-navigation/stack@5.0.0-alpha.51
2020-01-05 01:40:51 +01:00
Satyajit Sahoo
dcc5f99ecd feat: add support for pager component 2020-01-05 01:20:53 +01:00
Satyajit Sahoo
adbeb292f5 fix: preserve focused route in tab on changing screens list 2020-01-05 01:14:25 +01:00
Satyajit Sahoo
543679f185 chore: fix webpack config for example 2020-01-04 01:48:34 +01:00
Satyajit Sahoo
cbe240eae6 chore: publish
- @react-navigation/stack@5.0.0-alpha.50
2020-01-03 21:42:13 +01:00
Satyajit Sahoo
7f963a74bb fix: keep screens for replace when animation is enabled 2020-01-03 21:39:03 +01:00
Satyajit Sahoo
572beae41b fix: use gesture direction when using next screen's animation 2020-01-03 21:27:48 +01:00
Satyajit Sahoo
15fe3ebb51 refactor: remove PointerEventsView 2020-01-03 21:22:44 +01:00
Satyajit Sahoo
31565d5576 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.31
 - @react-navigation/drawer@5.0.0-alpha.33
 - @react-navigation/native-stack@5.0.0-alpha.21
 - @react-navigation/stack@5.0.0-alpha.49
2020-01-03 18:12:02 +01:00
Satyajit Sahoo
2c31d1705c fix: dismiss keyboard on page change 2020-01-03 18:11:44 +01:00
Janic Duplessis
8f9a250958 Use space instead of empty string to hide back button on iOS (#234) 2020-01-03 17:16:48 +01:00
Satyajit Sahoo
87d28ca430 refactor: minor tweaks 2020-01-03 17:07:42 +01:00
Michał Osadnik
fa4411a14d feat: add interaction handle to drawer (#239) 2020-01-03 17:05:37 +01:00
Satyajit Sahoo
77b757091c fix: provide initial values for safe area to prevent blank screen (#238)
https://github.com/react-navigation/stack/issues/328
2020-01-03 16:25:59 +01:00
Michał Osadnik
6b9b999c5b fix: interaction manager in stack (#237) 2020-01-03 15:48:06 +01:00
imgbot[bot]
8c5f84094f chore: optimize images (#236)
*Total -- 272.34kb -> 260.85kb (4.22%)

/packages/stack/src/views/assets/back-icon-mask.png -- 1.58kb -> 0.89kb (43.61%)
/example/assets/avatar-1.png -- 3.33kb -> 2.40kb (27.96%)
/example/assets/avatar-2.png -- 2.29kb -> 1.67kb (26.87%)
/example/assets/icon.png -- 1.07kb -> 0.88kb (17.51%)
/example/assets/album-art-4.jpg -- 34.70kb -> 33.05kb (4.75%)
/example/assets/album-art-1.jpg -- 28.85kb -> 27.58kb (4.38%)
/example/assets/album-art-3.jpg -- 28.53kb -> 27.37kb (4.05%)
/example/assets/book.jpg -- 97.60kb -> 94.13kb (3.56%)
/example/assets/album-art-2.jpg -- 20.94kb -> 20.33kb (2.94%)
/example/assets/album-art-6.jpg -- 22.07kb -> 21.57kb (2.26%)
/example/assets/album-art-8.jpg -- 21.28kb -> 20.92kb (1.69%)
/example/assets/album-art-7.jpg -- 10.10kb -> 10.05kb (0.5%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>

Co-authored-by: Imgbot <help@imgbot.net>
2020-01-03 11:37:33 +01:00
Satyajit Sahoo
21709f7674 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.30
 - @react-navigation/drawer@5.0.0-alpha.32
2020-01-03 11:11:09 +01:00
Satyajit Sahoo
22742e4a7f chore: bump peer dep version for safe-area-context 2020-01-03 11:08:22 +01:00
Satyajit Sahoo
3cd1aedcf4 fix: pass backBehavior to the router in drawer. fixes #230 2020-01-03 11:00:35 +01:00
Satyajit Sahoo
797d3a798e chore: publish
- @react-navigation/stack@5.0.0-alpha.48
2020-01-01 16:28:28 +01:00
Satyajit Sahoo
59803f54d6 fix: improve gesture performance 2020-01-01 16:26:51 +01:00
Satyajit Sahoo
935659899f fix: use native driver for gestures 2020-01-01 15:48:26 +01:00
Satyajit Sahoo
ef0f5d6567 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.29
 - @react-navigation/compat@5.0.0-alpha.20
 - @react-navigation/core@5.0.0-alpha.30
 - @react-navigation/drawer@5.0.0-alpha.31
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.28
 - @react-navigation/material-top-tabs@5.0.0-alpha.26
 - @react-navigation/native-stack@5.0.0-alpha.20
 - @react-navigation/native@5.0.0-alpha.22
 - @react-navigation/routers@5.0.0-alpha.19
 - @react-navigation/stack@5.0.0-alpha.47
2020-01-01 13:33:50 +01:00
Satyajit Sahoo
499f66dba4 chore: fix type error 2020-01-01 13:32:50 +01:00
Satyajit Sahoo
2ef2f1a86f refactor: navigate instead of reset when we can when handling links 2020-01-01 13:28:31 +01:00
Satyajit Sahoo
0252bdc222 fix: show error if an action was not handled 2020-01-01 13:12:41 +01:00
Satyajit Sahoo
282f62c258 refactor: use animated instead of reanimated 2020-01-01 12:53:01 +01:00
Satyajit Sahoo
f462d67270 fix: cleanup transaction even if action wasn't handled 2019-12-25 01:41:14 +01:00
Satyajit Sahoo
873afec9fe chore: tweak route names in example 2019-12-19 23:25:58 +01:00
Satyajit Sahoo
878297e52f chore: update eslint config 2019-12-19 23:13:23 +01:00
Satyajit Sahoo
1ea9b4524d chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.28
 - @react-navigation/compat@5.0.0-alpha.19
 - @react-navigation/core@5.0.0-alpha.29
 - @react-navigation/drawer@5.0.0-alpha.30
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.27
 - @react-navigation/material-top-tabs@5.0.0-alpha.25
 - @react-navigation/native-stack@5.0.0-alpha.19
 - @react-navigation/native@5.0.0-alpha.21
 - @react-navigation/routers@5.0.0-alpha.18
 - @react-navigation/stack@5.0.0-alpha.46
2019-12-19 14:45:13 +01:00
Satyajit Sahoo
c52a8c46a8 fix: fix typescript issues 2019-12-19 14:39:35 +01:00
Satyajit Sahoo
0635365483 fix: set screen background in drawer from theme 2019-12-17 00:01:47 +01:00
Satyajit Sahoo
9843b92e05 chore: upgrade deps 2019-12-16 23:42:19 +01:00
Satyajit Sahoo
ebd145a09d fix: fix backgroundColor in sceneContainerStyle overriden by theme
Closes #215
2019-12-16 23:00:12 +01:00
Satyajit Sahoo
9fc1af02c2 refactor: remove extra prop 2019-12-16 15:43:38 +01:00
Satyajit Sahoo
68a334cc93 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.27
 - @react-navigation/compat@5.0.0-alpha.18
 - @react-navigation/core@5.0.0-alpha.28
 - @react-navigation/drawer@5.0.0-alpha.29
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.26
 - @react-navigation/material-top-tabs@5.0.0-alpha.24
 - @react-navigation/native-stack@5.0.0-alpha.18
 - @react-navigation/native@5.0.0-alpha.20
 - @react-navigation/routers@5.0.0-alpha.17
 - @react-navigation/stack@5.0.0-alpha.45
2019-12-16 15:27:11 +01:00
Satyajit Sahoo
c110570d4c fix: disable style interpolation for card when animation is disabled 2019-12-16 15:25:19 +01:00
Satyajit Sahoo
d57226fd8b refactor: remove cardTransparent in favor of cardStyle 2019-12-16 15:09:10 +01:00
Oliver Winter
c3d3748143 fix: use Partial type for initialParam (#206) 2019-12-16 13:58:37 +01:00
Wojciech Lewicki
8002d51795 feat: add nested config in deep linking (#210)
Fixes #154
2019-12-16 13:30:28 +01:00
Erivelton Elias
31cf19912b chore: update README expo and native-stack (#212) 2019-12-16 12:29:31 +01:00
Satyajit Sahoo
d0e4e1f6fb chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.26
 - @react-navigation/drawer@5.0.0-alpha.28
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.25
 - @react-navigation/material-top-tabs@5.0.0-alpha.23
 - @react-navigation/native-stack@5.0.0-alpha.17
 - @react-navigation/native@5.0.0-alpha.19
 - @react-navigation/stack@5.0.0-alpha.44
2019-12-14 22:43:24 +01:00
Satyajit Sahoo
00fc616de0 feat: add custom theme support (#211) 2019-12-14 22:25:25 +01:00
Satyajit Sahoo
703edb3569 chore: fix loading back icon on Android 2019-12-14 06:37:21 +01:00
Satyajit Sahoo
38a38b021a refactor: use function component for bottom tab bar 2019-12-13 17:11:50 +01:00
Satyajit Sahoo
42bc37d2ff chore: update auth flow example 2019-12-12 13:36:43 +01:00
Satyajit Sahoo
75ad2aaae5 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.25
 - @react-navigation/compat@5.0.0-alpha.17
 - @react-navigation/drawer@5.0.0-alpha.27
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.24
 - @react-navigation/material-top-tabs@5.0.0-alpha.22
 - @react-navigation/native-stack@5.0.0-alpha.16
 - @react-navigation/native@5.0.0-alpha.18
 - @react-navigation/routers@5.0.0-alpha.16
 - @react-navigation/stack@5.0.0-alpha.43
2019-12-11 17:45:04 +01:00
Satyajit Sahoo
eef17a801e refactor: import from /native instead of /core 2019-12-11 17:44:21 +01:00
Satyajit Sahoo
7b8277dae5 chore: publish
- @react-navigation/stack@5.0.0-alpha.42
2019-12-10 15:53:53 +01:00
Christian Falch
6cddb5238c feat: expose animation related values in context 2019-12-10 15:53:27 +01:00
164 changed files with 3898 additions and 2776 deletions

View File

@@ -49,6 +49,7 @@ jobs:
at: ~/project
- run: |
yarn lerna run prepare
node scripts/check-types-path.js
workflows:
version: 2

View File

@@ -35,7 +35,7 @@ Navigators bundle a router and a view which takes the navigation state and decid
A simple navigator could look like this:
```js
import { createNavigatorFactory } from '@react-navigation/core';
import { createNavigatorFactory } from '@react-navigation/native';
function StackNavigator({ initialRouteName, children, ...rest }) {
// The `navigation` object contains the navigation state and some helpers (e.g. push, pop)
@@ -256,7 +256,7 @@ Sometimes we want to run side-effects when a screen is focused. A side effect ma
To make this easier, the library exports a `useFocusEffect` hook:
```js
import { useFocusEffect } from '@react-navigation/core';
import { useFocusEffect } from '@react-navigation/native';
function Profile({ userId }) {
const [user, setUser] = React.useState(null);
@@ -285,7 +285,7 @@ The `useFocusEffect` is analogous to React's `useEffect` hook. The only differen
We might want to render different content based on the current focus state of the screen. The library exports a `useIsFocused` hook to make this easier:
```js
import { useIsFocused } from '@react-navigation/core';
import { useIsFocused } from '@react-navigation/native';
// ...

View File

@@ -13,5 +13,8 @@ module.exports = {
'@babel/preset-react',
'@babel/preset-typescript',
],
plugins: ['@babel/plugin-proposal-class-properties'],
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-optional-chaining',
],
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 900 B

View File

@@ -54,6 +54,23 @@ module.exports = {
}, {}),
},
server: {
enhanceMiddleware: middleware => {
return (req, res, next) => {
const assets = '/packages/stack/src/views/assets';
if (req.url.startsWith(assets)) {
req.url = req.url.replace(
assets,
'/assets/../packages/stack/src/views/assets'
);
}
return middleware(req, res, next);
};
},
},
transformer: {
getTransformOptions: async () => ({
transform: {

View File

@@ -20,6 +20,7 @@
"dependencies": {
"@expo/vector-icons": "^10.0.0",
"@react-native-community/masked-view": "0.1.5",
"color": "^3.1.2",
"expo": "^36.0.0",
"expo-asset": "~8.0.0",
"query-string": "^6.9.0",
@@ -27,24 +28,25 @@
"react-dom": "~16.9.0",
"react-native": "~0.61.5",
"react-native-gesture-handler": "~1.5.0",
"react-native-paper": "^3.2.1",
"react-native-iphone-x-helper": "^1.2.1",
"react-native-paper": "^3.3.0",
"react-native-reanimated": "^1.4.0",
"react-native-safe-area-context": "^0.6.0",
"react-native-screens": "^2.0.0-alpha.12",
"react-native-screens": "^2.0.0-alpha.19",
"react-native-tab-view": "2.11.0",
"react-native-unimodules": "^0.6.0",
"react-native-unimodules": "^0.7.0",
"react-native-web": "^0.11.7",
"scheduler": "^0.18.0",
"shortid": "^2.2.15",
"use-subscription": "^1.3.0"
},
"devDependencies": {
"@babel/core": "^7.7.2",
"@expo/webpack-config": "^0.10.1",
"@types/react": "^16.9.11",
"@types/react-native": "^0.60.22",
"babel-preset-expo": "^7.1.0",
"expo-cli": "^3.8.0",
"typescript": "^3.7.2"
"@babel/core": "^7.7.5",
"@expo/webpack-config": "^0.10.6",
"@types/react": "^16.9.16",
"@types/react-native": "^0.60.25",
"babel-preset-expo": "^8.0.0",
"expo-cli": "^3.11.1",
"typescript": "^3.7.3"
}
}

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import { View, TextInput, ActivityIndicator, StyleSheet } from 'react-native';
import { Title, Button } from 'react-native-paper';
import { ParamListBase } from '@react-navigation/core';
import { ParamListBase } from '@react-navigation/native';
import {
createStackNavigator,
HeaderBackButton,
@@ -9,11 +9,27 @@ import {
} from '@react-navigation/stack';
type AuthStackParams = {
splash: undefined;
home: undefined;
'sign-in': undefined;
Splash: undefined;
Home: undefined;
SignIn: undefined;
PostSignOut: undefined;
};
const AUTH_CONTEXT_ERROR =
'Authentication context not found. Have your wrapped your components with AuthContext.Consumer?';
const AuthContext = React.createContext<{
signIn: () => void;
signOut: () => void;
}>({
signIn: () => {
throw new Error(AUTH_CONTEXT_ERROR);
},
signOut: () => {
throw new Error(AUTH_CONTEXT_ERROR);
},
});
const SplashScreen = () => {
return (
<View style={styles.content}>
@@ -22,27 +38,27 @@ const SplashScreen = () => {
);
};
const SignInScreen = ({ onSignIn }: { onSignIn: (token: string) => void }) => {
const SignInScreen = () => {
const { signIn } = React.useContext(AuthContext);
return (
<View style={styles.content}>
<TextInput placeholder="Username" style={styles.input} />
<TextInput placeholder="Password" secureTextEntry style={styles.input} />
<Button
mode="contained"
onPress={() => onSignIn('token')}
style={styles.button}
>
<Button mode="contained" onPress={signIn} style={styles.button}>
Sign in
</Button>
</View>
);
};
const HomeScreen = ({ onSignOut }: { onSignOut: () => void }) => {
const HomeScreen = () => {
const { signOut } = React.useContext(AuthContext);
return (
<View style={styles.content}>
<Title style={styles.text}>Signed in successfully 🎉</Title>
<Button onPress={onSignOut} style={styles.button}>
<Button onPress={signOut} style={styles.button}>
Sign out
</Button>
</View>
@@ -105,36 +121,44 @@ export default function SimpleStackScreen({ navigation }: Props) {
headerShown: false,
});
const authContext = React.useMemo(
() => ({
signIn: () => dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' }),
signOut: () => dispatch({ type: 'SIGN_OUT' }),
}),
[]
);
return (
<SimpleStack.Navigator
screenOptions={{
headerLeft: () => (
<HeaderBackButton onPress={() => navigation.goBack()} />
),
}}
>
{state.isLoading ? (
<SimpleStack.Screen
name="splash"
component={SplashScreen}
options={{ title: `Auth Flow` }}
/>
) : state.userToken === undefined ? (
<SimpleStack.Screen name="sign-in" options={{ title: `Sign in` }}>
{() => (
<SignInScreen
onSignIn={token => dispatch({ type: 'SIGN_IN', token })}
/>
)}
</SimpleStack.Screen>
) : (
<SimpleStack.Screen name="home" options={{ title: 'Home' }}>
{() => (
<HomeScreen onSignOut={() => dispatch({ type: 'SIGN_OUT' })} />
)}
</SimpleStack.Screen>
)}
</SimpleStack.Navigator>
<AuthContext.Provider value={authContext}>
<SimpleStack.Navigator
screenOptions={{
headerLeft: () => (
<HeaderBackButton onPress={() => navigation.goBack()} />
),
}}
>
{state.isLoading ? (
<SimpleStack.Screen
name="Splash"
component={SplashScreen}
options={{ title: 'Auth Flow' }}
/>
) : state.userToken === undefined ? (
<SimpleStack.Screen
name="SignIn"
options={{ title: 'Sign in' }}
component={SignInScreen}
/>
) : (
<SimpleStack.Screen
name="Home"
options={{ title: 'Home' }}
component={HomeScreen}
/>
)}
</SimpleStack.Navigator>
</AuthContext.Provider>
);
}

View File

@@ -16,10 +16,10 @@ const getTabBarIcon = (name: string) => ({
}) => <MaterialCommunityIcons name={name} color={color} size={size} />;
type BottomTabParams = {
article: undefined;
albums: undefined;
contacts: undefined;
chat: undefined;
Article: undefined;
Albums: undefined;
Contacts: undefined;
Chat: undefined;
};
const BottomTabs = createBottomTabNavigator<BottomTabParams>();
@@ -32,7 +32,7 @@ export default function BottomTabsScreen() {
}}
>
<BottomTabs.Screen
name="article"
name="Article"
options={{
title: 'Article',
tabBarIcon: getTabBarIcon('file-document-box'),
@@ -41,7 +41,7 @@ export default function BottomTabsScreen() {
{props => <SimpleStackScreen {...props} headerMode="none" />}
</BottomTabs.Screen>
<BottomTabs.Screen
name="chat"
name="Chat"
component={Chat}
options={{
tabBarLabel: 'Chat',
@@ -49,7 +49,7 @@ export default function BottomTabsScreen() {
}}
/>
<BottomTabs.Screen
name="contacts"
name="Contacts"
component={Contacts}
options={{
title: 'Contacts',
@@ -57,7 +57,7 @@ export default function BottomTabsScreen() {
}}
/>
<BottomTabs.Screen
name="albums"
name="Albums"
component={Albums}
options={{
title: 'Albums',

View File

@@ -7,10 +7,10 @@ import Chat from '../Shared/Chat';
import SimpleStackScreen from './SimpleStack';
type MaterialBottomTabParams = {
article: undefined;
albums: undefined;
contacts: undefined;
chat: undefined;
Article: undefined;
Albums: undefined;
Contacts: undefined;
Chat: undefined;
};
const MaterialBottomTabs = createMaterialBottomTabNavigator<
@@ -21,7 +21,7 @@ export default function MaterialBottomTabsScreen() {
return (
<MaterialBottomTabs.Navigator barStyle={styles.tabBar}>
<MaterialBottomTabs.Screen
name="article"
name="Article"
options={{
tabBarLabel: 'Article',
tabBarIcon: 'file-document-box',
@@ -31,7 +31,7 @@ export default function MaterialBottomTabsScreen() {
{props => <SimpleStackScreen {...props} headerMode="none" />}
</MaterialBottomTabs.Screen>
<MaterialBottomTabs.Screen
name="chat"
name="Chat"
component={Chat}
options={{
tabBarLabel: 'Chat',
@@ -41,7 +41,7 @@ export default function MaterialBottomTabsScreen() {
}}
/>
<MaterialBottomTabs.Screen
name="contacts"
name="Contacts"
component={Contacts}
options={{
tabBarLabel: 'Contacts',
@@ -50,7 +50,7 @@ export default function MaterialBottomTabsScreen() {
}}
/>
<MaterialBottomTabs.Screen
name="albums"
name="Albums"
component={Albums}
options={{
tabBarLabel: 'Albums',

View File

@@ -1,54 +1,35 @@
import * as React from 'react';
import { StyleSheet } from 'react-native';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
import Albums from '../Shared/Albums';
import Contacts from '../Shared/Contacts';
import Chat from '../Shared/Chat';
type MaterialTopTabParams = {
albums: undefined;
contacts: undefined;
chat: undefined;
Albums: undefined;
Contacts: undefined;
Chat: undefined;
};
const MaterialTopTabs = createMaterialTopTabNavigator<MaterialTopTabParams>();
export default function MaterialTopTabsScreen() {
return (
<MaterialTopTabs.Navigator
tabBarOptions={{
style: styles.tabBar,
labelStyle: styles.tabLabel,
indicatorStyle: styles.tabIndicator,
}}
>
<MaterialTopTabs.Navigator>
<MaterialTopTabs.Screen
name="chat"
name="Chat"
component={Chat}
options={{ title: 'Chat' }}
/>
<MaterialTopTabs.Screen
name="contacts"
name="Contacts"
component={Contacts}
options={{ title: 'Contacts' }}
/>
<MaterialTopTabs.Screen
name="albums"
name="Albums"
component={Albums}
options={{ title: 'Albums' }}
/>
</MaterialTopTabs.Navigator>
);
}
const styles = StyleSheet.create({
tabBar: {
backgroundColor: 'white',
},
tabLabel: {
color: 'black',
},
tabIndicator: {
backgroundColor: 'tomato',
},
});

View File

@@ -1,8 +1,7 @@
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { Button } from 'react-native-paper';
import { useSafeArea } from 'react-native-safe-area-context';
import { RouteProp, ParamListBase } from '@react-navigation/core';
import { RouteProp, ParamListBase } from '@react-navigation/native';
import {
createStackNavigator,
StackNavigationProp,
@@ -11,28 +10,26 @@ import {
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
type SimpleStackParams = {
article: { author: string };
album: undefined;
type ModalStackParams = {
Article: { author: string };
Album: undefined;
};
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
type ModalStackNavigation = StackNavigationProp<ModalStackParams>;
const ArticleScreen = ({
navigation,
route,
}: {
navigation: SimpleStackNavigation;
route: RouteProp<SimpleStackParams, 'article'>;
navigation: ModalStackNavigation;
route: RouteProp<ModalStackParams, 'Article'>;
}) => {
const insets = useSafeArea();
return (
<React.Fragment>
<View style={[styles.buttons, { marginTop: insets.top }]}>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('album')}
onPress={() => navigation.push('Album')}
style={styles.button}
>
Push album
@@ -50,19 +47,13 @@ const ArticleScreen = ({
);
};
const AlbumsScreen = ({
navigation,
}: {
navigation: SimpleStackNavigation;
}) => {
const insets = useSafeArea();
const AlbumsScreen = ({ navigation }: { navigation: ModalStackNavigation }) => {
return (
<React.Fragment>
<View style={[styles.buttons, { marginTop: insets.top }]}>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('article', { author: 'Babel fish' })}
onPress={() => navigation.push('Article', { author: 'Babel fish' })}
style={styles.button}
>
Push article
@@ -80,7 +71,7 @@ const AlbumsScreen = ({
);
};
const ModalPresentationStack = createStackNavigator<SimpleStackParams>();
const ModalPresentationStack = createStackNavigator<ModalStackParams>();
type Props = {
options?: React.ComponentProps<typeof ModalPresentationStack.Navigator>;
@@ -95,16 +86,20 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
return (
<ModalPresentationStack.Navigator
mode="modal"
headerMode="none"
screenOptions={{
headerMode="screen"
screenOptions={({ route, navigation }) => ({
...TransitionPresets.ModalPresentationIOS,
cardOverlayEnabled: true,
gestureEnabled: true,
}}
headerStatusBarHeight:
navigation.dangerouslyGetState().routes.indexOf(route) > 0
? 0
: undefined,
})}
{...options}
>
<ModalPresentationStack.Screen
name="article"
name="Article"
component={ArticleScreen}
options={({ route }) => ({
title: `Article by ${route.params.author}`,
@@ -112,7 +107,7 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
initialParams={{ author: 'Gandalf' }}
/>
<ModalPresentationStack.Screen
name="album"
name="Album"
component={AlbumsScreen}
options={{ title: 'Album' }}
/>

View File

@@ -7,7 +7,8 @@ import {
RouteProp,
ParamListBase,
useFocusEffect,
} from '@react-navigation/core';
useTheme,
} from '@react-navigation/native';
import { DrawerNavigationProp } from '@react-navigation/drawer';
import { StackNavigationProp } from '@react-navigation/stack';
import {
@@ -17,96 +18,119 @@ import {
import Albums from '../Shared/Albums';
type NativeStackParams = {
article: { author: string };
album: undefined;
Article: { author: string };
Album: undefined;
};
type NativeStackNavigation = NativeStackNavigationProp<NativeStackParams>;
const Title = ({ children }: { children: React.ReactNode }) => {
const { colors } = useTheme();
return <Text style={[styles.title, { color: colors.text }]}>{children}</Text>;
};
const Paragraph = ({ children }: { children: React.ReactNode }) => {
const { colors } = useTheme();
return (
<Text style={[styles.paragraph, { color: colors.text }]}>{children}</Text>
);
};
const ArticleScreen = ({
navigation,
}: {
navigation: NativeStackNavigation;
route: RouteProp<NativeStackParams, 'article'>;
}) => (
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('album')}
style={styles.button}
>
Push album
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Text style={styles.title}>What is Lorem Ipsum?</Text>
<Text style={styles.paragraph}>
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
Lorem Ipsum has been the industry&apos;s standard dummy text ever since
the 1500s, when an unknown printer took a galley of type and scrambled it
to make a type specimen book. It has survived not only five centuries, but
also the leap into electronic typesetting, remaining essentially
unchanged. It was popularised in the 1960s with the release of Letraset
sheets containing Lorem Ipsum passages, and more recently with desktop
publishing software like Aldus PageMaker including versions of Lorem
Ipsum.
</Text>
<Text style={styles.title}>Where does it come from?</Text>
<Text style={styles.paragraph}>
Contrary to popular belief, Lorem Ipsum is not simply random text. It has
roots in a piece of classical Latin literature from 45 BC, making it over
2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney
College in Virginia, looked up one of the more obscure Latin words,
consectetur, from a Lorem Ipsum passage, and going through the cites of
the word in classical literature, discovered the undoubtable source. Lorem
Ipsum comes from sections 1.10.32 and 1.10.33 of &quot;de Finibus Bonorum
et Malorum&quot; (The Extremes of Good and Evil) by Cicero, written in 45
BC. This book is a treatise on the theory of ethics, very popular during
the Renaissance. The first line of Lorem Ipsum, &quot;Lorem ipsum dolor
sit amet..&quot;, comes from a line in section 1.10.32.
</Text>
<Text style={styles.paragraph}>
The standard chunk of Lorem Ipsum used since the 1500s is reproduced below
for those interested. Sections 1.10.32 and 1.10.33 from &quot;de Finibus
Bonorum et Malorum&quot; by Cicero are also reproduced in their exact
original form, accompanied by English versions from the 1914 translation
by H. Rackham.
</Text>
<Text style={styles.title}>Why do we use it?</Text>
<Text style={styles.paragraph}>
It is a long established fact that a reader will be distracted by the
readable content of a page when looking at its layout. The point of using
Lorem Ipsum is that it has a more-or-less normal distribution of letters,
as opposed to using &quot;Content here, content here&quot;, making it look
like readable English. Many desktop publishing packages and web page
editors now use Lorem Ipsum as their default model text, and a search for
&quot;lorem ipsum&quot; will uncover many web sites still in their
infancy. Various versions have evolved over the years, sometimes by
accident, sometimes on purpose (injected humour and the like).
</Text>
<Text style={styles.title}>Where can I get some?</Text>
<Text style={styles.paragraph}>
There are many variations of passages of Lorem Ipsum available, but the
majority have suffered alteration in some form, by injected humour, or
randomised words which don&apos;t look even slightly believable. If you
are going to use a passage of Lorem Ipsum, you need to be sure there
isn&apos;t anything embarrassing hidden in the middle of text. All the
Lorem Ipsum generators on the Internet tend to repeat predefined chunks as
necessary, making this the first true generator on the Internet. It uses a
dictionary of over 200 Latin words, combined with a handful of model
sentence structures, to generate Lorem Ipsum which looks reasonable. The
generated Lorem Ipsum is therefore always free from repetition, injected
humour, or non-characteristic words etc.
</Text>
</ScrollView>
);
route: RouteProp<NativeStackParams, 'Article'>;
}) => {
const { colors } = useTheme();
return (
<ScrollView
style={{ backgroundColor: colors.card }}
contentContainerStyle={styles.content}
>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Album')}
style={styles.button}
>
Push album
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Title>What is Lorem Ipsum?</Title>
<Paragraph>
Lorem Ipsum is simply dummy text of the printing and typesetting
industry. Lorem Ipsum has been the industry&apos;s standard dummy text
ever since the 1500s, when an unknown printer took a galley of type and
scrambled it to make a type specimen book. It has survived not only five
centuries, but also the leap into electronic typesetting, remaining
essentially unchanged. It was popularised in the 1960s with the release
of Letraset sheets containing Lorem Ipsum passages, and more recently
with desktop publishing software like Aldus PageMaker including versions
of Lorem Ipsum.
</Paragraph>
<Title>Where does it come from?</Title>
<Paragraph>
Contrary to popular belief, Lorem Ipsum is not simply random text. It
has roots in a piece of classical Latin literature from 45 BC, making it
over 2000 years old. Richard McClintock, a Latin professor at
Hampden-Sydney College in Virginia, looked up one of the more obscure
Latin words, consectetur, from a Lorem Ipsum passage, and going through
the cites of the word in classical literature, discovered the
undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33
of &quot;de Finibus Bonorum et Malorum&quot; (The Extremes of Good and
Evil) by Cicero, written in 45 BC. This book is a treatise on the theory
of ethics, very popular during the Renaissance. The first line of Lorem
Ipsum, &quot;Lorem ipsum dolor sit amet..&quot;, comes from a line in
section 1.10.32.
</Paragraph>
<Paragraph>
The standard chunk of Lorem Ipsum used since the 1500s is reproduced
below for those interested. Sections 1.10.32 and 1.10.33 from &quot;de
Finibus Bonorum et Malorum&quot; by Cicero are also reproduced in their
exact original form, accompanied by English versions from the 1914
translation by H. Rackham.
</Paragraph>
<Title>Why do we use it?</Title>
<Paragraph>
It is a long established fact that a reader will be distracted by the
readable content of a page when looking at its layout. The point of
using Lorem Ipsum is that it has a more-or-less normal distribution of
letters, as opposed to using &quot;Content here, content here&quot;,
making it look like readable English. Many desktop publishing packages
and web page editors now use Lorem Ipsum as their default model text,
and a search for &quot;lorem ipsum&quot; will uncover many web sites
still in their infancy. Various versions have evolved over the years,
sometimes by accident, sometimes on purpose (injected humour and the
like).
</Paragraph>
<Title>Where can I get some?</Title>
<Paragraph>
There are many variations of passages of Lorem Ipsum available, but the
majority have suffered alteration in some form, by injected humour, or
randomised words which don&apos;t look even slightly believable. If you
are going to use a passage of Lorem Ipsum, you need to be sure there
isn&apos;t anything embarrassing hidden in the middle of text. All the
Lorem Ipsum generators on the Internet tend to repeat predefined chunks
as necessary, making this the first true generator on the Internet. It
uses a dictionary of over 200 Latin words, combined with a handful of
model sentence structures, to generate Lorem Ipsum which looks
reasonable. The generated Lorem Ipsum is therefore always free from
repetition, injected humour, or non-characteristic words etc.
</Paragraph>
</ScrollView>
);
};
const AlbumsScreen = ({
navigation,
@@ -117,7 +141,7 @@ const AlbumsScreen = ({
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('article', { author: 'Babel fish' })}
onPress={() => navigation.push('Article', { author: 'Babel fish' })}
style={styles.button}
>
Push article
@@ -164,7 +188,7 @@ export default function NativeStackScreen({ navigation }: Props) {
return (
<NativeStack.Navigator>
<NativeStack.Screen
name="article"
name="Article"
component={ArticleScreen}
options={{
title: 'Lorem Ipsum',
@@ -173,7 +197,7 @@ export default function NativeStackScreen({ navigation }: Props) {
}}
/>
<NativeStack.Screen
name="album"
name="Album"
component={AlbumsScreen}
options={{ title: 'Album' }}
/>
@@ -191,21 +215,16 @@ const styles = StyleSheet.create({
button: {
margin: 8,
},
container: {
backgroundColor: 'white',
},
content: {
paddingVertical: 16,
},
title: {
color: '#000',
fontWeight: 'bold',
fontSize: 24,
marginVertical: 8,
marginHorizontal: 16,
},
paragraph: {
color: '#000',
fontSize: 16,
lineHeight: 24,
marginVertical: 8,

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { Button } from 'react-native-paper';
import { RouteProp, ParamListBase } from '@react-navigation/core';
import { RouteProp, ParamListBase } from '@react-navigation/native';
import {
createStackNavigator,
StackNavigationProp,
@@ -10,8 +10,8 @@ import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
type SimpleStackParams = {
article: { author: string };
album: undefined;
Article: { author: string };
Album: undefined;
};
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
@@ -21,14 +21,14 @@ const ArticleScreen = ({
route,
}: {
navigation: SimpleStackNavigation;
route: RouteProp<SimpleStackParams, 'article'>;
route: RouteProp<SimpleStackParams, 'Article'>;
}) => {
return (
<React.Fragment>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('album')}
onPress={() => navigation.push('Album')}
style={styles.button}
>
Push album
@@ -56,7 +56,7 @@ const AlbumsScreen = ({
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('article', { author: 'Babel fish' })}
onPress={() => navigation.push('Article', { author: 'Babel fish' })}
style={styles.button}
>
Push article
@@ -88,7 +88,7 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
return (
<SimpleStack.Navigator {...rest}>
<SimpleStack.Screen
name="article"
name="Article"
component={ArticleScreen}
options={({ route }) => ({
title: `Article by ${route.params.author}`,
@@ -96,7 +96,7 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
initialParams={{ author: 'Gandalf' }}
/>
<SimpleStack.Screen
name="album"
name="Album"
component={AlbumsScreen}
options={{ title: 'Album' }}
/>

View File

@@ -1,3 +1,5 @@
/* eslint-disable import/no-commonjs */
import * as React from 'react';
import { Image, Dimensions, ScrollView, StyleSheet } from 'react-native';
import { useScrollToTop } from '@react-navigation/native';
@@ -38,7 +40,7 @@ export default function Albums() {
const styles = StyleSheet.create({
container: {
backgroundColor: '#343C46',
backgroundColor: '#000',
},
content: {
flexDirection: 'row',

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import { View, Text, Image, ScrollView, StyleSheet } from 'react-native';
import { useScrollToTop } from '@react-navigation/native';
import { useScrollToTop, useTheme } from '@react-navigation/native';
type Props = {
date?: string;
@@ -19,10 +19,12 @@ export default function Article({
useScrollToTop(ref);
const { colors } = useTheme();
return (
<ScrollView
ref={ref}
style={styles.container}
style={{ backgroundColor: colors.card }}
contentContainerStyle={styles.content}
>
<View style={styles.author}>
@@ -31,24 +33,26 @@ export default function Article({
source={require('../../assets/avatar-1.png')}
/>
<View style={styles.meta}>
<Text style={styles.name}>{author.name}</Text>
<Text style={styles.timestamp}>{date}</Text>
<Text style={[styles.name, { color: colors.text }]}>
{author.name}
</Text>
<Text style={[styles.timestamp, { color: colors.text }]}>{date}</Text>
</View>
</View>
<Text style={styles.title}>Lorem Ipsum</Text>
<Text style={styles.paragraph}>
<Text style={[styles.title, { color: colors.text }]}>Lorem Ipsum</Text>
<Text style={[styles.paragraph, { color: colors.text }]}>
Contrary to popular belief, Lorem Ipsum is not simply random text. It
has roots in a piece of classical Latin literature from 45 BC, making it
over 2000 years old.
</Text>
<Image style={styles.image} source={require('../../assets/book.jpg')} />
<Text style={styles.paragraph}>
<Text style={[styles.paragraph, { color: colors.text }]}>
Richard McClintock, a Latin professor at Hampden-Sydney College in
Virginia, looked up one of the more obscure Latin words, consectetur,
from a Lorem Ipsum passage, and going through the cites of the word in
classical literature, discovered the undoubtable source.
</Text>
<Text style={styles.paragraph}>
<Text style={[styles.paragraph, { color: colors.text }]}>
Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of &quot;de Finibus
Bonorum et Malorum&quot; (The Extremes of Good and Evil) by Cicero,
written in 45 BC. This book is a treatise on the theory of ethics, very
@@ -61,9 +65,6 @@ export default function Article({
}
const styles = StyleSheet.create({
container: {
backgroundColor: 'white',
},
content: {
paddingVertical: 16,
},
@@ -77,13 +78,12 @@ const styles = StyleSheet.create({
justifyContent: 'center',
},
name: {
color: '#000',
fontWeight: 'bold',
fontSize: 16,
lineHeight: 24,
},
timestamp: {
color: '#999',
opacity: 0.5,
fontSize: 14,
lineHeight: 21,
},
@@ -93,14 +93,12 @@ const styles = StyleSheet.create({
borderRadius: 24,
},
title: {
color: '#000',
fontWeight: 'bold',
fontSize: 36,
marginVertical: 8,
marginHorizontal: 16,
},
paragraph: {
color: '#000',
fontSize: 16,
lineHeight: 24,
marginVertical: 8,

View File

@@ -7,7 +7,8 @@ import {
ScrollView,
StyleSheet,
} from 'react-native';
import { useScrollToTop } from '@react-navigation/native';
import { useScrollToTop, useTheme } from '@react-navigation/native';
import Color from 'color';
const MESSAGES = [
'okay',
@@ -21,6 +22,8 @@ export default function Chat() {
useScrollToTop(ref);
const { colors } = useTheme();
return (
<View style={styles.container}>
<ScrollView
@@ -45,9 +48,12 @@ export default function Chat() {
}
/>
<View
style={[styles.bubble, odd ? styles.received : styles.sent]}
style={[
styles.bubble,
{ backgroundColor: odd ? colors.primary : colors.card },
]}
>
<Text style={odd ? styles.receivedText : styles.sentText}>
<Text style={{ color: odd ? 'white' : colors.text }}>
{text}
</Text>
</View>
@@ -56,7 +62,14 @@ export default function Chat() {
})}
</ScrollView>
<TextInput
style={styles.input}
style={[
styles.input,
{ backgroundColor: colors.card, color: colors.text },
]}
placeholderTextColor={Color(colors.text)
.alpha(0.5)
.rgb()
.string()}
placeholder="Write a message"
underlineColorAndroid="transparent"
/>
@@ -67,7 +80,6 @@ export default function Chat() {
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#eceff1',
},
inverted: {
transform: [{ scaleY: -1 }],
@@ -97,22 +109,9 @@ const styles = StyleSheet.create({
paddingHorizontal: 16,
borderRadius: 20,
},
sent: {
backgroundColor: '#cfd8dc',
},
received: {
backgroundColor: '#2196F3',
},
sentText: {
color: 'black',
},
receivedText: {
color: 'white',
},
input: {
height: 48,
paddingVertical: 12,
paddingHorizontal: 24,
backgroundColor: 'white',
},
});

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import { View, Text, FlatList, StyleSheet } from 'react-native';
import { useScrollToTop } from '@react-navigation/native';
import { useScrollToTop, useTheme } from '@react-navigation/native';
type Item = { name: string; number: number };
@@ -57,27 +57,35 @@ const CONTACTS: Item[] = [
{ name: 'Vincent Sandoval', number: 2606111495 },
];
class ContactItem extends React.PureComponent<{
item: { name: string; number: number };
}> {
render() {
const { item } = this.props;
const ContactItem = React.memo(
({ item }: { item: { name: string; number: number } }) => {
const { colors } = useTheme();
return (
<View style={styles.item}>
<View style={[styles.item, { backgroundColor: colors.card }]}>
<View style={styles.avatar}>
<Text style={styles.letter}>
{item.name.slice(0, 1).toUpperCase()}
</Text>
</View>
<View style={styles.details}>
<Text style={styles.name}>{item.name}</Text>
<Text style={styles.number}>{item.number}</Text>
<Text style={[styles.name, { color: colors.text }]}>{item.name}</Text>
<Text style={[styles.number, { color: colors.text, opacity: 0.5 }]}>
{item.number}
</Text>
</View>
</View>
);
}
}
);
const ItemSeparator = () => {
const { colors } = useTheme();
return (
<View style={[styles.separator, { backgroundColor: colors.border }]} />
);
};
export default function Contacts() {
const ref = React.useRef<FlatList<Item>>(null);
@@ -86,8 +94,6 @@ export default function Contacts() {
const renderItem = ({ item }: { item: Item }) => <ContactItem item={item} />;
const ItemSeparator = () => <View style={styles.separator} />;
return (
<FlatList
ref={ref}
@@ -101,7 +107,6 @@ export default function Contacts() {
const styles = StyleSheet.create({
item: {
backgroundColor: 'white',
flexDirection: 'row',
alignItems: 'center',
padding: 8,
@@ -124,14 +129,11 @@ const styles = StyleSheet.create({
name: {
fontWeight: 'bold',
fontSize: 14,
color: 'black',
},
number: {
fontSize: 12,
color: '#999',
},
separator: {
height: StyleSheet.hairlineWidth,
backgroundColor: 'rgba(0, 0, 0, .08)',
},
});

View File

@@ -1,16 +1,31 @@
import * as React from 'react';
import { ScrollView, AsyncStorage, YellowBox } from 'react-native';
import {
View,
ScrollView,
AsyncStorage,
YellowBox,
Platform,
StatusBar,
} from 'react-native';
import { MaterialIcons } from '@expo/vector-icons';
import { Appbar, List } from 'react-native-paper';
import {
Provider as PaperProvider,
DefaultTheme as PaperLightTheme,
DarkTheme as PaperDarkTheme,
Subheading,
Appbar,
List,
Switch,
Divider,
} from 'react-native-paper';
import { Asset } from 'expo-asset';
import {
InitialState,
getStateFromPath,
NavigationContainerRef,
} from '@react-navigation/core';
import {
useLinking,
NavigationContainerRef,
NavigationNativeContainer,
DefaultTheme,
DarkTheme,
} from '@react-navigation/native';
import {
createDrawerNavigator,
@@ -74,7 +89,8 @@ const SCREENS = {
const Drawer = createDrawerNavigator<RootDrawerParamList>();
const Stack = createStackNavigator<RootStackParamList>();
const PERSISTENCE_KEY = 'NAVIGATION_STATE';
const NAVIGATION_PERSISTENCE_KEY = 'NAVIGATION_STATE';
const THEME_PERSISTENCE_KEY = 'THEME_TYPE';
Asset.loadAsync(StackAssets);
@@ -87,23 +103,24 @@ export default function App() {
// The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`)
const { getInitialState } = useLinking(containerRef, {
prefixes: LinkingPrefixes,
getStateFromPath: path => {
const state = getStateFromPath(path);
config: {
Root: Object.keys(SCREENS).reduce<{ [key: string]: string }>(
(acc, name) => {
// Convert screen names such as SimpleStack to kebab case (simple-stack)
acc[name] = name
.replace(/([A-Z]+)/g, '-$1')
.replace(/^-/, '')
.toLowerCase();
return {
routes: [
{
name: 'root',
state: {
...state,
routes: [{ name: 'home' }, ...(state ? state.routes : [])],
},
},
],
};
return acc;
},
{}
),
},
});
const [theme, setTheme] = React.useState(DefaultTheme);
const [isReady, setIsReady] = React.useState(false);
const [initialState, setInitialState] = React.useState<
InitialState | undefined
@@ -115,7 +132,9 @@ export default function App() {
let state = await getInitialState();
if (state === undefined) {
const savedState = await AsyncStorage.getItem(PERSISTENCE_KEY);
const savedState = await AsyncStorage.getItem(
NAVIGATION_PERSISTENCE_KEY
);
state = savedState ? JSON.parse(savedState) : undefined;
}
@@ -123,6 +142,14 @@ export default function App() {
setInitialState(state);
}
} finally {
try {
const themeName = await AsyncStorage.getItem(THEME_PERSISTENCE_KEY);
setTheme(themeName === 'dark' ? DarkTheme : DefaultTheme);
} catch (e) {
// Ignore
}
setIsReady(true);
}
};
@@ -130,78 +157,126 @@ export default function App() {
restoreState();
}, [getInitialState]);
const paperTheme = React.useMemo(() => {
const t = theme.dark ? PaperDarkTheme : PaperLightTheme;
return {
...t,
colors: {
...t.colors,
...theme.colors,
surface: theme.colors.card,
accent: theme.dark ? 'rgb(255, 55, 95)' : 'rgb(255, 45, 85)',
},
};
}, [theme.colors, theme.dark]);
if (!isReady) {
return null;
}
return (
<NavigationNativeContainer
ref={containerRef}
initialState={initialState}
onStateChange={state =>
AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state))
}
>
<Drawer.Navigator>
<Drawer.Screen
name="Root"
options={{
title: 'Examples',
drawerIcon: ({ size, color }) => (
<MaterialIcons size={size} color={color} name="folder" />
),
}}
>
{({
navigation,
}: {
navigation: DrawerNavigationProp<RootDrawerParamList>;
}) => (
<Stack.Navigator>
<Stack.Screen
name="Home"
options={{
title: 'Examples',
headerLeft: () => (
<Appbar.Action
icon="menu"
onPress={() => navigation.toggleDrawer()}
/>
),
}}
>
{({
navigation,
}: {
navigation: StackNavigationProp<RootStackParamList>;
}) => (
<ScrollView>
{(Object.keys(SCREENS) as Array<keyof typeof SCREENS>).map(
name => (
<List.Item
key={name}
title={SCREENS[name].title}
onPress={() => navigation.push(name)}
<PaperProvider theme={paperTheme}>
{Platform.OS === 'ios' && (
<StatusBar barStyle={theme.dark ? 'light-content' : 'dark-content'} />
)}
<NavigationNativeContainer
ref={containerRef}
initialState={initialState}
onStateChange={state =>
AsyncStorage.setItem(
NAVIGATION_PERSISTENCE_KEY,
JSON.stringify(state)
)
}
theme={theme}
>
<Drawer.Navigator>
<Drawer.Screen
name="Root"
options={{
title: 'Examples',
drawerIcon: ({ size, color }) => (
<MaterialIcons size={size} color={color} name="folder" />
),
}}
>
{({
navigation,
}: {
navigation: DrawerNavigationProp<RootDrawerParamList>;
}) => (
<Stack.Navigator>
<Stack.Screen
name="Home"
options={{
title: 'Examples',
headerLeft: () => (
<Appbar.Action
color={theme.colors.text}
icon="menu"
onPress={() => navigation.toggleDrawer()}
/>
),
}}
>
{({
navigation,
}: {
navigation: StackNavigationProp<RootStackParamList>;
}) => (
<ScrollView
style={{ backgroundColor: theme.colors.background }}
>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: 16,
}}
>
<Subheading>Dark theme</Subheading>
<Switch
value={theme.dark}
onValueChange={() => {
AsyncStorage.setItem(
THEME_PERSISTENCE_KEY,
theme.dark ? 'light' : 'dark'
);
setTheme(t => (t.dark ? DefaultTheme : DarkTheme));
}}
/>
)
)}
</ScrollView>
</View>
<Divider />
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
name => (
<List.Item
key={name}
title={SCREENS[name].title}
onPress={() => navigation.push(name)}
/>
)
)}
</ScrollView>
)}
</Stack.Screen>
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
name => (
<Stack.Screen
key={name}
name={name}
component={SCREENS[name].component}
options={{ title: SCREENS[name].title }}
/>
)
)}
</Stack.Screen>
{(Object.keys(SCREENS) as Array<keyof typeof SCREENS>).map(
name => (
<Stack.Screen
key={name}
name={name}
component={SCREENS[name].component}
options={{ title: SCREENS[name].title }}
/>
)
)}
</Stack.Navigator>
)}
</Drawer.Screen>
</Drawer.Navigator>
</NavigationNativeContainer>
</Stack.Navigator>
)}
</Drawer.Screen>
</Drawer.Navigator>
</NavigationNativeContainer>
</PaperProvider>
);
}

View File

@@ -4,6 +4,9 @@ const createExpoWebpackConfigAsync = require('@expo/webpack-config');
// eslint-disable-next-line import/no-extraneous-dependencies
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const node_modules = path.resolve(__dirname, 'node_modules');
const packages = path.resolve(__dirname, '..', 'packages');
module.exports = async function(env, argv) {
const config = await createExpoWebpackConfigAsync(env, argv);
@@ -19,28 +22,15 @@ module.exports = async function(env, argv) {
);
Object.assign(config.resolve.alias, {
react: path.resolve(__dirname, 'node_modules', 'react'),
'react-native': path.resolve(__dirname, 'node_modules', 'react-native-web'),
'react-native-web': path.resolve(
__dirname,
'node_modules',
'react-native-web'
),
'react-native-reanimated': path.resolve(
__dirname,
'node_modules',
'react-native-reanimated-web'
),
'@expo/vector-icons/MaterialCommunityIcons': require.resolve(
'@expo/vector-icons/MaterialCommunityIcons'
),
react: path.resolve(node_modules, 'react'),
'react-native': path.resolve(node_modules, 'react-native-web'),
'react-native-web': path.resolve(node_modules, 'react-native-web'),
'@expo/vector-icons': path.resolve(node_modules, '@expo/vector-icons'),
});
fs.readdirSync(path.join(__dirname, '..')).forEach(name => {
fs.readdirSync(packages).forEach(name => {
config.resolve.alias[`@react-navigation/${name}`] = path.resolve(
__dirname,
'..',
'packages',
packages,
name,
'src'
);

View File

@@ -2,7 +2,7 @@ const error = console.error;
console.error = (...args) =>
// Supress error messages regarding error boundary in tests
/Consider adding an error boundary to your tree to customize error handling behavior/m.test(
/(Consider adding an error boundary to your tree to customize error handling behavior|React will try to recreate this component tree from scratch using the error boundary you provided|Error boundaries should implement getDerivedStateFromError)/m.test(
args[0]
)
? void 0

View File

@@ -24,22 +24,23 @@
},
"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.7.0",
"@babel/preset-env": "^7.7.1",
"@babel/plugin-proposal-optional-chaining": "^7.7.5",
"@babel/preset-env": "^7.7.6",
"@babel/preset-react": "^7.7.0",
"@babel/preset-typescript": "^7.7.2",
"@babel/runtime": "^7.7.2",
"@babel/runtime": "^7.7.6",
"@commitlint/config-conventional": "^8.2.0",
"@types/jest": "^24.0.23",
"codecov": "^3.6.1",
"commitlint": "^8.2.0",
"core-js": "^3.4.1",
"eslint": "^6.6.0",
"eslint-config-satya164": "^3.1.2",
"core-js": "^3.5.0",
"eslint": "^6.7.2",
"eslint-config-satya164": "^3.1.5",
"husky": "^3.0.9",
"jest": "^24.8.0",
"lerna": "^3.18.4",
"prettier": "^1.19.1",
"typescript": "^3.7.2"
"typescript": "^3.7.3"
},
"resolutions": {
"react": "~16.9.0",

View File

@@ -3,6 +3,76 @@
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.32](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.31...@react-navigation/bottom-tabs@5.0.0-alpha.32) (2020-01-05)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.31](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.30...@react-navigation/bottom-tabs@5.0.0-alpha.31) (2020-01-03)
### Bug Fixes
* provide initial values for safe area to prevent blank screen ([#238](https://github.com/react-navigation/navigation-ex/issues/238)) ([77b7570](https://github.com/react-navigation/navigation-ex/commit/77b757091c0451e20bca01138629669c7da544a8))
# [5.0.0-alpha.30](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.29...@react-navigation/bottom-tabs@5.0.0-alpha.30) (2020-01-03)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.29](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.28...@react-navigation/bottom-tabs@5.0.0-alpha.29) (2020-01-01)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.28](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.27...@react-navigation/bottom-tabs@5.0.0-alpha.28) (2019-12-19)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.27](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.26...@react-navigation/bottom-tabs@5.0.0-alpha.27) (2019-12-16)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.26](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.25...@react-navigation/bottom-tabs@5.0.0-alpha.26) (2019-12-14)
### Features
* add custom theme support ([#211](https://github.com/react-navigation/navigation-ex/issues/211)) ([00fc616](https://github.com/react-navigation/navigation-ex/commit/00fc616de0572bade8aa85052cdc8290360b1d7f))
# [5.0.0-alpha.25](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.24...@react-navigation/bottom-tabs@5.0.0-alpha.25) (2019-12-11)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.24](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.23...@react-navigation/bottom-tabs@5.0.0-alpha.24) (2019-12-10)

View File

@@ -7,7 +7,7 @@ Bottom tab navigator for React Navigation following iOS design guidelines.
Open a Terminal in your project's folder and run,
```sh
yarn add @react-navigation/core @react-navigation/bottom-tabs
yarn add @react-navigation/native @react-navigation/bottom-tabs
```
Now we need to install [`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context).

View File

@@ -10,7 +10,7 @@
"android",
"tab"
],
"version": "5.0.0-alpha.24",
"version": "5.0.0-alpha.32",
"license": "MIT",
"repository": {
"type": "git",
@@ -33,23 +33,26 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.15"
"@react-navigation/routers": "^5.0.0-alpha.20",
"color": "^3.1.2",
"react-native-iphone-x-helper": "^1.2.1"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",
"@types/react": "^16.9.11",
"@types/react-native": "^0.60.22",
"@types/color": "^3.0.0",
"@types/react": "^16.9.16",
"@types/react-native": "^0.60.25",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native": "~0.61.5",
"react-native-safe-area-context": "^0.6.0",
"typescript": "^3.7.2"
"typescript": "^3.7.3"
},
"peerDependencies": {
"@react-navigation/core": "^5.0.0-alpha.0",
"@react-navigation/native": "^5.0.0-alpha.0",
"react": "*",
"react-native": "*",
"react-native-safe-area-context": "^0.3.6"
"react-native-safe-area-context": "^0.6.0"
},
"@react-native-community/bob": {
"source": "src",

View File

@@ -3,7 +3,7 @@ import {
useNavigationBuilder,
createNavigatorFactory,
DefaultNavigatorOptions,
} from '@react-navigation/core';
} from '@react-navigation/native';
import {
TabRouter,
TabRouterOptions,

View File

@@ -10,7 +10,7 @@ import {
NavigationProp,
ParamListBase,
Descriptor,
} from '@react-navigation/core';
} from '@react-navigation/native';
import { TabNavigationState } from '@react-navigation/routers';
export type BottomTabNavigationEventMap = {

View File

@@ -10,22 +10,15 @@ import {
Dimensions,
} from 'react-native';
import {
Route,
NavigationContext,
CommonActions,
} from '@react-navigation/core';
useTheme,
} from '@react-navigation/native';
import { SafeAreaConsumer } from 'react-native-safe-area-context';
import BottomTabItem from './BottomTabItem';
import { BottomTabBarProps } from '../types';
type State = {
dimensions: { height: number; width: number };
layout: { height: number; width: number };
keyboard: boolean;
visible: Animated.Value;
};
type Props = BottomTabBarProps & {
activeTintColor?: string;
inactiveTintColor?: string;
@@ -34,97 +27,101 @@ type Props = BottomTabBarProps & {
const DEFAULT_TABBAR_HEIGHT = 50;
const DEFAULT_MAX_TAB_ITEM_WIDTH = 125;
export default class BottomTabBar extends React.Component<Props, State> {
static defaultProps = {
keyboardHidesTabBar: false,
adaptive: true,
};
export default function BottomTabBar({
state,
navigation,
descriptors,
activeBackgroundColor,
activeTintColor,
adaptive = true,
allowFontScaling,
inactiveBackgroundColor,
inactiveTintColor,
keyboardHidesTabBar = false,
labelPosition,
labelStyle,
showIcon,
showLabel,
style,
tabStyle,
}: Props) {
const { colors } = useTheme();
state = {
dimensions: Dimensions.get('window'),
layout: { height: 0, width: 0 },
keyboard: false,
visible: new Animated.Value(1),
};
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
const [layout, setLayout] = React.useState({ height: 0, width: 0 });
const [keyboardShown, setKeyboardShown] = React.useState(false);
componentDidMount() {
Dimensions.addEventListener('change', this.handleOrientationChange);
const [visible] = React.useState(() => new Animated.Value(0));
if (Platform.OS === 'ios') {
Keyboard.addListener('keyboardWillShow', this.handleKeyboardShow);
Keyboard.addListener('keyboardWillHide', this.handleKeyboardHide);
} else {
Keyboard.addListener('keyboardDidShow', this.handleKeyboardShow);
Keyboard.addListener('keyboardDidHide', this.handleKeyboardHide);
}
}
const { routes } = state;
componentWillUnmount() {
Dimensions.removeEventListener('change', this.handleOrientationChange);
if (Platform.OS === 'ios') {
Keyboard.removeListener('keyboardWillShow', this.handleKeyboardShow);
Keyboard.removeListener('keyboardWillHide', this.handleKeyboardHide);
} else {
Keyboard.removeListener('keyboardDidShow', this.handleKeyboardShow);
Keyboard.removeListener('keyboardDidHide', this.handleKeyboardHide);
}
}
private handleOrientationChange = ({ window }: { window: ScaledSize }) => {
this.setState({ dimensions: window });
};
private handleKeyboardShow = () =>
this.setState({ keyboard: true }, () =>
Animated.timing(this.state.visible, {
React.useEffect(() => {
if (keyboardShown) {
Animated.timing(visible, {
toValue: 0,
duration: 200,
useNativeDriver: true,
}).start()
);
}).start();
}
}, [keyboardShown, visible]);
private handleKeyboardHide = () =>
Animated.timing(this.state.visible, {
toValue: 1,
duration: 250,
useNativeDriver: true,
}).start(({ finished }) => {
if (finished) {
this.setState({ keyboard: false });
}
});
React.useEffect(() => {
const handleOrientationChange = ({ window }: { window: ScaledSize }) => {
setDimensions(window);
};
private handleLayout = (e: LayoutChangeEvent) => {
const { layout } = this.state;
const { height, width } = e.nativeEvent.layout;
const handleKeyboardShow = () => setKeyboardShown(true);
if (height === layout.height && width === layout.width) {
return;
const handleKeyboardHide = () =>
Animated.timing(visible, {
toValue: 1,
duration: 250,
useNativeDriver: true,
}).start(({ finished }) => {
if (finished) {
setKeyboardShown(false);
}
});
Dimensions.addEventListener('change', handleOrientationChange);
if (Platform.OS === 'ios') {
Keyboard.addListener('keyboardWillShow', handleKeyboardShow);
Keyboard.addListener('keyboardWillHide', handleKeyboardHide);
} else {
Keyboard.addListener('keyboardDidShow', handleKeyboardShow);
Keyboard.addListener('keyboardDidHide', handleKeyboardHide);
}
this.setState({
layout: {
height,
width,
},
return () => {
Dimensions.removeEventListener('change', handleOrientationChange);
if (Platform.OS === 'ios') {
Keyboard.removeListener('keyboardWillShow', handleKeyboardShow);
Keyboard.removeListener('keyboardWillHide', handleKeyboardHide);
} else {
Keyboard.removeListener('keyboardDidShow', handleKeyboardShow);
Keyboard.removeListener('keyboardDidHide', handleKeyboardHide);
}
};
}, [visible]);
const handleLayout = (e: LayoutChangeEvent) => {
const { height, width } = e.nativeEvent.layout;
setLayout(layout => {
if (height === layout.height && width === layout.width) {
return layout;
} else {
return {
height,
width,
};
}
});
};
private getLabelText = ({ route }: { route: Route<string> }) => {
const { options } = this.props.descriptors[route.key];
return options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;
};
private shouldUseHorizontalLabels = () => {
const { state, adaptive, tabStyle, labelPosition } = this.props;
const { dimensions } = this.state;
const { routes } = state;
const shouldUseHorizontalLabels = () => {
const isLandscape = dimensions.width > dimensions.height;
if (labelPosition) {
@@ -165,126 +162,114 @@ export default class BottomTabBar extends React.Component<Props, State> {
}
};
render() {
const {
state,
navigation,
descriptors,
keyboardHidesTabBar,
activeTintColor,
inactiveTintColor,
activeBackgroundColor,
inactiveBackgroundColor,
labelStyle,
showIcon,
showLabel,
allowFontScaling,
style,
tabStyle,
} = this.props;
const { routes } = state;
return (
<SafeAreaConsumer>
{insets => (
<Animated.View
style={[
styles.tabBar,
{
backgroundColor: colors.card,
borderTopColor: colors.border,
},
keyboardHidesTabBar
? {
// When the keyboard is shown, slide down the tab bar
transform: [
{
translateY: visible.interpolate({
inputRange: [0, 1],
outputRange: [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: keyboardShown ? 'absolute' : null,
}
: null,
{
height: DEFAULT_TABBAR_HEIGHT + (insets ? insets.bottom : 0),
paddingBottom: insets ? insets.bottom : 0,
},
style,
]}
pointerEvents={keyboardHidesTabBar && keyboardShown ? 'none' : 'auto'}
>
<View style={styles.content} onLayout={handleLayout}>
{routes.map((route, index) => {
const focused = index === state.index;
const { options } = descriptors[route.key];
return (
<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,
{
height: DEFAULT_TABBAR_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 { options } = descriptors[route.key];
const onPress = () => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
});
const onPress = () => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
if (!focused && !event.defaultPrevented) {
navigation.dispatch({
...CommonActions.navigate(route.name),
target: state.key,
});
}
};
if (!focused && !event.defaultPrevented) {
navigation.dispatch({
...CommonActions.navigate(route.name),
target: state.key,
});
}
};
const onLongPress = () => {
navigation.emit({
type: 'tabLongPress',
target: route.key,
});
};
const onLongPress = () => {
navigation.emit({
type: 'tabLongPress',
target: route.key,
});
};
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;
const label = this.getLabelText({ route });
const accessibilityLabel =
options.tabBarAccessibilityLabel !== undefined
? options.tabBarAccessibilityLabel
: typeof label === 'string'
? `${label}, tab, ${index + 1} of ${routes.length}`
: undefined;
const accessibilityLabel =
options.tabBarAccessibilityLabel !== undefined
? options.tabBarAccessibilityLabel
: typeof label === 'string'
? `${label}, tab, ${index + 1} of ${routes.length}`
: undefined;
return (
<NavigationContext.Provider
key={route.key}
value={descriptors[route.key].navigation}
>
<BottomTabItem
route={route}
focused={focused}
horizontal={this.shouldUseHorizontalLabels()}
onPress={onPress}
onLongPress={onLongPress}
accessibilityLabel={accessibilityLabel}
testID={options.tabBarTestID}
allowFontScaling={allowFontScaling}
activeTintColor={activeTintColor}
inactiveTintColor={inactiveTintColor}
activeBackgroundColor={activeBackgroundColor}
inactiveBackgroundColor={inactiveBackgroundColor}
button={options.tabBarButton}
icon={options.tabBarIcon}
label={label}
showIcon={showIcon}
showLabel={showLabel}
labelStyle={labelStyle}
style={tabStyle}
/>
</NavigationContext.Provider>
);
})}
</View>
</Animated.View>
)}
</SafeAreaConsumer>
);
}
return (
<NavigationContext.Provider
key={route.key}
value={descriptors[route.key].navigation}
>
<BottomTabItem
route={route}
focused={focused}
horizontal={shouldUseHorizontalLabels()}
onPress={onPress}
onLongPress={onLongPress}
accessibilityLabel={accessibilityLabel}
testID={options.tabBarTestID}
allowFontScaling={allowFontScaling}
activeTintColor={activeTintColor}
inactiveTintColor={inactiveTintColor}
activeBackgroundColor={activeBackgroundColor}
inactiveBackgroundColor={inactiveBackgroundColor}
button={options.tabBarButton}
icon={options.tabBarIcon}
label={label}
showIcon={showIcon}
showLabel={showLabel}
labelStyle={labelStyle}
style={tabStyle}
/>
</NavigationContext.Provider>
);
})}
</View>
</Animated.View>
)}
</SafeAreaConsumer>
);
}
const styles = StyleSheet.create({
@@ -292,9 +277,7 @@ const styles = StyleSheet.create({
left: 0,
right: 0,
bottom: 0,
backgroundColor: '#fff',
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: 'rgba(0, 0, 0, .3)',
elevation: 8,
},
content: {

View File

@@ -8,7 +8,8 @@ import {
ViewStyle,
TextStyle,
} from 'react-native';
import { Route } from '@react-navigation/core';
import { Route, useTheme } from '@react-navigation/native';
import Color from 'color';
import TabBarIcon from './TabBarIcon';
import { BottomTabBarButtonProps } from '../types';
@@ -113,8 +114,8 @@ export default function BottomTabBarItem({
onPress,
onLongPress,
horizontal,
activeTintColor = '#007AFF',
inactiveTintColor = '#8E8E93',
activeTintColor: customActiveTintColor,
inactiveTintColor: customInactiveTintColor,
activeBackgroundColor = 'transparent',
inactiveBackgroundColor = 'transparent',
showLabel = true,
@@ -123,6 +124,20 @@ export default function BottomTabBarItem({
labelStyle,
style,
}: Props) {
const { colors } = useTheme();
const activeTintColor =
customActiveTintColor === undefined
? colors.primary
: customActiveTintColor;
const inactiveTintColor =
customInactiveTintColor === undefined
? Color(colors.text)
.mix(Color(colors.card), 0.5)
.hex()
: customInactiveTintColor;
const renderLabel = ({ focused }: { focused: boolean }) => {
if (showLabel === false) {
return null;

View File

@@ -2,6 +2,7 @@ import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { TabNavigationState } from '@react-navigation/routers';
import { useTheme } from '@react-navigation/native';
// eslint-disable-next-line import/no-unresolved
import { ScreenContainer } from 'react-native-screens';
@@ -25,6 +26,26 @@ type State = {
loaded: number[];
};
function SceneContent({
isFocused,
children,
}: {
isFocused: boolean;
children: React.ReactNode;
}) {
const { colors } = useTheme();
return (
<View
accessibilityElementsHidden={!isFocused}
importantForAccessibility={isFocused ? 'auto' : 'no-hide-descendants'}
style={[styles.content, { backgroundColor: colors.background }]}
>
{children}
</View>
);
}
export default class BottomTabView extends React.Component<Props, State> {
static defaultProps = {
lazy: true,
@@ -97,15 +118,9 @@ export default class BottomTabView extends React.Component<Props, State> {
style={StyleSheet.absoluteFill}
isVisible={isFocused}
>
<View
accessibilityElementsHidden={!isFocused}
importantForAccessibility={
isFocused ? 'auto' : 'no-hide-descendants'
}
style={styles.content}
>
<SceneContent isFocused={isFocused}>
{descriptors[route.key].render()}
</View>
</SceneContent>
</ResourceSavingScene>
);
})}

View File

@@ -14,7 +14,7 @@ const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view o
export default class ResourceSavingScene extends React.Component<Props> {
render() {
if (screensEnabled && screensEnabled()) {
if (screensEnabled?.()) {
const { isVisible, ...rest } = this.props;
// @ts-ignore
return <Screen active={isVisible ? 1 : 0} {...rest} />;

View File

@@ -3,6 +3,17 @@ import {
SafeAreaProvider,
SafeAreaConsumer,
} from 'react-native-safe-area-context';
import {
getStatusBarHeight,
getBottomSpace,
} from 'react-native-iphone-x-helper';
const initialSafeAreaInsets = {
top: getStatusBarHeight(true),
bottom: getBottomSpace(),
right: 0,
left: 0,
};
type Props = {
children: React.ReactNode;
@@ -19,7 +30,11 @@ export default function SafeAreaProviderCompat({ children }: Props) {
return children;
}
return <SafeAreaProvider>{children}</SafeAreaProvider>;
return (
<SafeAreaProvider initialSafeAreaInsets={initialSafeAreaInsets}>
{children}
</SafeAreaProvider>
);
}}
</SafeAreaConsumer>
);

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
import { Route } from '@react-navigation/core';
import { Route } from '@react-navigation/native';
type Props = {
route: Route<string>;

View File

@@ -3,6 +3,46 @@
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.21](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.20...@react-navigation/compat@5.0.0-alpha.21) (2020-01-05)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.20](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.19...@react-navigation/compat@5.0.0-alpha.20) (2020-01-01)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.19](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.18...@react-navigation/compat@5.0.0-alpha.19) (2019-12-19)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.18](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.17...@react-navigation/compat@5.0.0-alpha.18) (2019-12-16)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.17](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.16...@react-navigation/compat@5.0.0-alpha.17) (2019-12-11)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.16](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.15...@react-navigation/compat@5.0.0-alpha.16) (2019-12-10)
**Note:** Version bump only for package @react-navigation/compat

View File

@@ -7,7 +7,7 @@ Compatibility layer to write navigator definitions in static configuration forma
Open a Terminal in your project's folder and run,
```sh
yarn add @react-navigation/core @react-navigation/compat
yarn add @react-navigation/native @react-navigation/compat
```
## Usage

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/compat",
"description": "Compatibility layer to write navigator definitions in static configuration format",
"version": "5.0.0-alpha.16",
"version": "5.0.0-alpha.21",
"license": "MIT",
"repository": {
"type": "git",
@@ -24,15 +24,15 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.15"
"@react-navigation/routers": "^5.0.0-alpha.20"
},
"devDependencies": {
"@types/react": "^16.9.11",
"@types/react": "^16.9.16",
"react": "~16.9.0",
"typescript": "^3.7.2"
"typescript": "^3.7.3"
},
"peerDependencies": {
"@react-navigation/core": "^5.0.0-alpha.0",
"@react-navigation/native": "^5.0.0-alpha.0",
"react": "~16.9.0"
},
"@react-native-community/bob": {

View File

@@ -3,7 +3,7 @@ import {
NavigationProp,
ParamListBase,
RouteProp,
} from '@react-navigation/core';
} from '@react-navigation/native';
import ScreenPropsContext from './ScreenPropsContext';
import createCompatNavigationProp from './createCompatNavigationProp';

View File

@@ -1,4 +1,4 @@
import { CommonActions, NavigationState } from '@react-navigation/core';
import { CommonActions, NavigationState } from '@react-navigation/native';
export function navigate({
routeName,
@@ -25,7 +25,7 @@ export function navigate({
}
export function back(options?: { key?: null | string }) {
return options && options.key != null
return options?.key != null
? (state: NavigationState) => ({
...CommonActions.goBack(),
source: options.key,

View File

@@ -1,4 +1,4 @@
import { CommonActions } from '@react-navigation/core';
import { CommonActions } from '@react-navigation/native';
import { StackActions, StackActionType } from '@react-navigation/routers';
export function reset(): CommonActions.Action {

View File

@@ -4,7 +4,7 @@ import {
ParamListBase,
NavigationProp,
RouteProp,
} from '@react-navigation/core';
} from '@react-navigation/native';
import * as helpers from './helpers';
import { CompatNavigationProp } from './types';
@@ -109,17 +109,17 @@ export default function createCompatNavigationProp<
break;
case 'didFocus': {
const unsubscribe = focusSubscriptions.get(callback);
unsubscribe && unsubscribe();
unsubscribe?.();
break;
}
case 'didBlur': {
const unsubscribe = blurSubscriptions.get(callback);
unsubscribe && unsubscribe();
unsubscribe?.();
break;
}
case 'refocus': {
const unsubscribe = refocusSubscriptions.get(callback);
unsubscribe && unsubscribe();
unsubscribe?.();
break;
}
default:

View File

@@ -6,7 +6,7 @@ import {
TypedNavigator,
NavigationProp,
RouteProp,
} from '@react-navigation/core';
} from '@react-navigation/native';
import CompatScreen from './CompatScreen';
import ScreenPropsContext from './ScreenPropsContext';
import createCompatNavigationProp from './createCompatNavigationProp';
@@ -47,7 +47,7 @@ export default function createCompatNavigatorFactory<
>(
routeConfig: CompatRouteConfig<NavigationPropType>,
navigationConfig: Partial<Omit<NavigationConfig, 'screenOptions'>> & {
order?: Array<Extract<keyof ParamList, string>>;
order?: Extract<keyof ParamList, string>[];
defaultNavigationOptions?: ScreenOptions;
navigationOptions?: Record<string, any>;
} = {}

View File

@@ -2,7 +2,7 @@ import {
useNavigationBuilder,
createNavigatorFactory,
DefaultNavigatorOptions,
} from '@react-navigation/core';
} from '@react-navigation/native';
import {
TabRouter,
TabRouterOptions,

View File

@@ -1,4 +1,4 @@
import { ParamListBase, NavigationProp, Route } from '@react-navigation/core';
import { ParamListBase, NavigationProp, Route } from '@react-navigation/native';
import * as helpers from './helpers';
export type CompatNavigationProp<

View File

@@ -4,7 +4,7 @@ import {
useRoute,
NavigationProp,
ParamListBase,
} from '@react-navigation/core';
} from '@react-navigation/native';
import createCompatNavigationProp from './createCompatNavigationProp';
import { CompatNavigationProp } from './types';

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { NavigationProp, ParamListBase } from '@react-navigation/core';
import { NavigationProp, ParamListBase } from '@react-navigation/native';
import useCompatNavigation from './useCompatNavigation';
import { CompatNavigationProp } from './types';

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { useIsFocused } from '@react-navigation/core';
import { useIsFocused } from '@react-navigation/native';
type InjectedProps = {
isFocused: boolean;

View File

@@ -3,6 +3,42 @@
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.30](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.29...@react-navigation/core@5.0.0-alpha.30) (2020-01-01)
### Bug Fixes
* cleanup transaction even if action wasn't handled ([f462d67](https://github.com/react-navigation/navigation-ex/commit/f462d672708cabfb0477c3a48505bd194ea626fd))
* show error if an action was not handled ([0252bdc](https://github.com/react-navigation/navigation-ex/commit/0252bdc2222ebe7410a0ed593bf03b2bdf5dc7ca))
# [5.0.0-alpha.29](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.28...@react-navigation/core@5.0.0-alpha.29) (2019-12-19)
**Note:** Version bump only for package @react-navigation/core
# [5.0.0-alpha.28](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.27...@react-navigation/core@5.0.0-alpha.28) (2019-12-16)
### Bug Fixes
* use Partial type for initialParam ([#206](https://github.com/react-navigation/navigation-ex/issues/206)) ([c3d3748](https://github.com/react-navigation/navigation-ex/commit/c3d374814308b0bd6d259099444f0f24593f4d7e))
### Features
* add nested config in deep linking ([#210](https://github.com/react-navigation/navigation-ex/issues/210)) ([8002d51](https://github.com/react-navigation/navigation-ex/commit/8002d5179524a7211c37760a4ed45e8c12af4358)), closes [#154](https://github.com/react-navigation/navigation-ex/issues/154)
# [5.0.0-alpha.27](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.26...@react-navigation/core@5.0.0-alpha.27) (2019-12-10)
**Note:** Version bump only for package @react-navigation/core

View File

@@ -6,7 +6,7 @@
"react-native",
"react-navigation"
],
"version": "5.0.0-alpha.27",
"version": "5.0.0-alpha.30",
"license": "MIT",
"repository": {
"type": "git",
@@ -35,15 +35,15 @@
"use-subscription": "^1.3.0"
},
"devDependencies": {
"@babel/core": "^7.7.2",
"@babel/core": "^7.7.5",
"@react-native-community/bob": "^0.7.0",
"@types/react": "^16.9.11",
"@types/react": "^16.9.16",
"@types/shortid": "^0.0.29",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native-testing-library": "^1.9.1",
"react-native-testing-library": "^1.12.0",
"react-test-renderer": "~16.9.0",
"typescript": "^3.7.2"
"typescript": "^3.7.3"
},
"peerDependencies": {
"react": "~16.9.0"

View File

@@ -1,4 +1,3 @@
// eslint-disable-next-line import/no-cycle
import { NavigationState, PartialState } from './types';
export type Action =

View File

@@ -123,9 +123,11 @@ const Container = React.forwardRef(function NavigationContainer(
isTransactionActiveRef.current = true;
transactionStateRef.current = navigationState;
callback();
isTransactionActiveRef.current = false;
try {
callback();
} finally {
isTransactionActiveRef.current = false;
}
return transactionStateRef.current;
});
@@ -202,7 +204,7 @@ const Container = React.forwardRef(function NavigationContainer(
}, [getStateForRoute]);
React.useImperativeHandle(ref, () => ({
...(Object.keys(CommonActions) as Array<keyof typeof CommonActions>).reduce<
...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce<
any
>((acc, name) => {
acc[name] = (...args: any[]) =>

View File

@@ -0,0 +1,75 @@
import getActionFromState from '../getActionFromState';
it('gets navigate action from state', () => {
const state = {
routes: [
{
name: 'foo',
state: {
routes: [
{
name: 'bar',
params: { answer: 42 },
state: {
routes: [
{
name: 'qux',
params: { author: 'jane' },
},
],
},
},
],
},
},
],
};
expect(getActionFromState(state)).toEqual({
payload: {
name: 'foo',
params: {
params: {
answer: 42,
params: {
author: 'jane',
},
screen: 'qux',
},
screen: 'bar',
},
},
type: 'NAVIGATE',
});
});
it('gets reset action from state', () => {
const state = {
routes: [
{
name: 'foo',
state: {
routes: [
{
name: 'bar',
state: {
routes: [
{
name: 'qux',
params: { author: 'jane' },
},
{ name: 'quz' },
],
},
},
],
},
},
],
};
expect(getActionFromState(state)).toEqual({
payload: state,
type: 'RESET_ROOT',
});
});

View File

@@ -127,3 +127,238 @@ it('handles route without param', () => {
it('returns undefined for invalid path', () => {
expect(getStateFromPath('//')).toBe(undefined);
});
it('converts path string to initial state with config with nested screens', () => {
expect(
getStateFromPath(
'/few/bar/sweet/apple/baz/jane?count=10&answer=42&valid=true',
{
Foo: {
Foe: '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: 'Foe',
state: {
routes: [
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet' },
state: {
routes: [
{
name: 'Baz',
params: {
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
],
},
},
],
},
},
],
},
},
],
});
});
it('converts path string to initial state with config with nested screens and unused configs', () => {
expect(
getStateFromPath('/few/baz/jane?count=10&answer=42&valid=true', {
Foo: {
Foe: 'few',
},
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: 'Foe',
state: {
routes: [
{
name: 'Baz',
params: {
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
],
},
},
],
},
},
],
});
});
it('handles parse in nested object with parse in it', () => {
expect(
getStateFromPath(
'/bar/sweet/apple/few/bis/jane?count=10&answer=42&valid=true',
{
Foo: {
Foe: 'few',
},
Bar: 'bar/:type/:fruit',
Baz: {
Bis: {
path: 'bis/:author',
parse: {
author: (author: string) =>
author.replace(/^\w/, c => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
},
}
)
).toEqual({
routes: [
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet' },
state: {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Foe',
state: {
routes: [
{
name: 'Baz',
state: {
routes: [
{
name: 'Bis',
params: {
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
],
},
},
],
},
},
],
},
},
],
},
},
],
});
});
it('handles parse in nested object for second route depth', () => {
expect(
getStateFromPath('/baz', {
Foo: {
path: 'foo',
Foe: 'foe',
Bar: {
Baz: 'baz',
},
},
})
).toEqual({
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
state: {
routes: [{ name: 'Baz' }],
},
},
],
},
},
],
});
});
it('handles parse in nested object for second route depth and and path and parse in roots', () => {
expect(
getStateFromPath('/baz', {
Foo: {
path: 'foo/:id',
parse: {
id: Number,
},
Foe: 'foe',
Bar: {
path: 'bar/:id',
parse: {
id: Number,
},
Baz: 'baz',
},
},
})
).toEqual({
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
state: {
routes: [{ name: 'Baz' }],
},
},
],
},
},
],
});
});

View File

@@ -357,6 +357,8 @@ it("doesn't update state if action wasn't handled", () => {
const onStateChange = jest.fn();
const spy = jest.spyOn(console, 'error').mockImplementation();
render(
<NavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
@@ -367,6 +369,12 @@ it("doesn't update state if action wasn't handled", () => {
);
expect(onStateChange).toBeCalledTimes(0);
expect(spy.mock.calls[0][0]).toMatch(
"The action 'INVALID' with payload 'undefined' was not handled by any navigator."
);
spy.mockRestore();
});
it('cleans up state when the navigator unmounts', () => {

View File

@@ -305,8 +305,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", () => {
it('logs error if no navigator handled the action', () => {
const TestRouter = MockRouter;
const TestNavigator = (props: any) => {
@@ -366,5 +365,13 @@ it("doesn't crash if no navigator handled the action", () => {
</NavigationContainer>
);
const spy = jest.spyOn(console, 'error').mockImplementation();
render(element).update(element);
expect(spy.mock.calls[0][0]).toMatch(
"The action 'UNKNOWN' with payload 'undefined' was not handled by any navigator."
);
spy.mockRestore();
});

View File

@@ -19,7 +19,7 @@ it('gets route prop from context', () => {
const Test = () => {
const route = useRoute<RouteProp<{ sample: { x: string } }, 'sample'>>();
expect(route && route.params && route.params.x).toEqual(1);
expect(route?.params?.x).toEqual(1);
return null;
};

View File

@@ -0,0 +1,67 @@
import { PartialState, NavigationState } from './types';
type NavigateParams = {
screen?: string;
params?: NavigateParams;
};
type Action =
| {
type: 'NAVIGATE';
payload: { name: string; params: NavigateParams };
}
| {
type: 'RESET_ROOT';
payload: PartialState<NavigationState>;
};
export default function getActionFromState(
state: PartialState<NavigationState>
): Action {
let payload: { name: string; params: NavigateParams } | undefined;
if (state.routes.length === 1) {
// Try to construct payload for a `NAVIGATE` action from the state
// This lets us preserve the navigation state and not lose it
let route = state.routes[0];
payload = {
name: route.name,
params: { ...route.params },
};
let current = state.routes[0].state;
let params = payload.params;
while (current) {
if (current.routes.length === 1) {
route = current.routes[0];
params.screen = route.name;
if (route.state) {
params.params = { ...route.params };
params = params.params;
} else {
params.params = route.params;
}
current = route.state;
} else {
payload = undefined;
break;
}
}
}
if (payload) {
return {
type: 'NAVIGATE',
payload,
};
}
return {
type: 'RESET_ROOT',
payload: state,
};
}

View File

@@ -60,7 +60,7 @@ export default function getPathFromState(
Object.entries(route.params).reduce<{
[key: string]: string;
}>((acc, [key, value]) => {
acc[key] = config && config[key] ? config[key](value) : String(value);
acc[key] = config?.[key] ? config[key](value) : String(value);
return acc;
}, {})
: undefined;
@@ -80,6 +80,7 @@ export default function getPathFromState(
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
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete params[name];
return encodeURIComponent(value);
}

View File

@@ -1,11 +1,22 @@
import escape from 'escape-string-regexp';
import queryString from 'query-string';
import { NavigationState, PartialState } from './types';
import { NavigationState, PartialState, InitialState } from './types';
type ParseConfig = Record<string, (value: string) => any>;
type Options = {
[routeName: string]: string | { path: string; parse?: ParseConfig };
[routeName: string]: string | { path: string; parse?: ParseConfig } | Options;
};
type RouteConfig = {
match: RegExp;
pattern: string;
routeNames: string[];
parse: Record<string, (value: string) => any> | undefined;
};
type ResultState = PartialState<NavigationState> & {
state?: ResultState;
};
/**
@@ -30,28 +41,11 @@ type Options = {
export default function getStateFromPath(
path: string,
options: Options = {}
): PartialState<NavigationState> | 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,
};
});
): ResultState | undefined {
// Create a normalized configs array which will be easier to use
const configs = ([] as RouteConfig[]).concat(
...Object.keys(options).map(key => createNormalizedConfigs(key, options))
);
let result: PartialState<NavigationState> | undefined;
let current: PartialState<NavigationState> | undefined;
@@ -62,16 +56,16 @@ export default function getStateFromPath(
.replace(/\?.*/, ''); // Remove query params which we will handle later
while (remaining) {
let routeName;
let routeNames;
let params;
// Go through all configs, and see if the next path segment matches our regex
for (const config of routeConfig) {
for (const config of configs) {
const match = remaining.match(config.match);
// If our regex matches, we need to extract params from the path
if (match) {
routeName = config.routeName;
routeNames = config.routeNames;
const paramPatterns = config.pattern
.split('/')
@@ -99,20 +93,54 @@ export default function getStateFromPath(
}
// If we hadn't matched any segments earlier, use the path as route name
if (routeName === undefined) {
if (routeNames === undefined) {
const segments = remaining.split('/');
routeName = decodeURIComponent(segments[0]);
routeNames = [decodeURIComponent(segments[0])];
segments.shift();
remaining = segments.join('/');
}
const state = {
routes: [{ name: routeName, params }],
};
let state: InitialState;
if (routeNames.length === 1) {
state = {
routes: [
{ name: routeNames.shift() as string, ...(params && { params }) },
],
};
} else {
state = {
routes: [{ name: routeNames.shift() as string, state: { routes: [] } }],
};
let helper = state.routes[0].state as InitialState;
let routeName;
while ((routeName = routeNames.shift())) {
if (routeNames.length === 0) {
helper.routes.push({
name: routeName,
...(params && { params }),
});
} else {
helper.routes[0] = {
name: routeName,
state: {
routes: [],
},
};
helper = helper.routes[0].state as InitialState;
}
}
}
if (current) {
// The state should be nested inside the route we parsed before
// The state should be nested inside the deepest route we parsed before
while (current.routes[0].state) {
current = current.routes[0].state;
}
current.routes[0].state = state;
} else {
result = state;
@@ -128,17 +156,20 @@ export default function getStateFromPath(
const query = path.split('?')[1];
if (query) {
while (current.routes[0].state) {
// The query params apply to the deepest route
current = current.routes[0].state;
}
const route = current.routes[0];
const params = queryString.parse(query);
const config = options[route.name]
? (options[route.name] as { parse?: ParseConfig }).parse
: undefined;
const parseFunction = findParseConfigForRoute(route.name, options);
if (config) {
if (parseFunction) {
Object.keys(params).forEach(name => {
if (config[name] && typeof params[name] === 'string') {
params[name] = config[name](params[name] as string);
if (parseFunction[name] && typeof params[name] === 'string') {
params[name] = parseFunction[name](params[name] as string);
}
});
}
@@ -148,3 +179,89 @@ export default function getStateFromPath(
return result;
}
function createNormalizedConfigs(
key: string,
routeConfig: Options,
routeNames: string[] = []
): RouteConfig[] {
const configs = [];
routeNames.push(key);
const value = routeConfig[key];
if (typeof value === 'string') {
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
configs.push(createConfigItem(routeNames, value));
} else if (typeof value === 'object') {
// if an object is specified as the value (e.g. Foo: { ... }),
// it could have config object and optionally nested config
Object.keys(value).forEach(nestedKey => {
if (nestedKey === 'path') {
configs.push(
createConfigItem(
routeNames,
value[nestedKey] as string,
value.parse ? (value.parse as ParseConfig) : undefined
)
);
} else if (nestedKey === 'parse') {
// We handle custom parse function when a `path` is specified (in nestedKey === path)
} else {
// If the name of the key is not `path` or `parse`, it's a nested config for route
// So we need to traverse into it and collect the configs
const result = createNormalizedConfigs(
nestedKey,
routeConfig[key] as Options,
routeNames
);
configs.push(...result);
}
});
}
routeNames.pop();
return configs;
}
function createConfigItem(
routeNames: string[],
pattern: string,
parse?: ParseConfig
): RouteConfig {
const match = new RegExp(
'^' + escape(pattern).replace(/:[a-z0-9]+/gi, '([^/]+)') + '/?'
);
return {
match,
pattern,
// The routeNames array is mutated, so copy it to keep the current state
routeNames: [...routeNames],
parse,
};
}
function findParseConfigForRoute(
routeName: string,
config: Options
): ParseConfig | undefined {
if (config[routeName]) {
return (config[routeName] as { parse?: ParseConfig }).parse;
}
for (const name in config) {
if (typeof config[name] === 'object') {
const parse = findParseConfigForRoute(routeName, config[name] as Options);
if (parse) {
return parse;
}
}
}
return undefined;
}

View File

@@ -17,5 +17,6 @@ export { default as useIsFocused } from './useIsFocused';
export { default as getStateFromPath } from './getStateFromPath';
export { default as getPathFromState } from './getPathFromState';
export { default as getActionFromState } from './getActionFromState';
export * from './types';

View File

@@ -1,4 +1,3 @@
// eslint-disable-next-line import/no-cycle
import * as CommonActions from './CommonActions';
import * as React from 'react';
@@ -20,9 +19,9 @@ export type NavigationState = {
/**
* List of rendered routes.
*/
routes: Array<
Route<string> & { state?: NavigationState | PartialState<NavigationState> }
>;
routes: (Route<string> & {
state?: NavigationState | PartialState<NavigationState>;
})[];
/**
* Custom type for the state, whether it's for tab, stack, drawer etc.
* During rehydration, the state will be discarded if type doesn't match with router type.
@@ -38,7 +37,7 @@ export type NavigationState = {
export type InitialState = Partial<
Omit<NavigationState, 'stale' | 'routes'>
> & {
routes: Array<Omit<Route<string>, 'key'> & { state?: InitialState }>;
routes: (Omit<Route<string>, 'key'> & { state?: InitialState })[];
};
export type PartialState<State extends NavigationState> = Partial<
@@ -46,9 +45,10 @@ export type PartialState<State extends NavigationState> = Partial<
> & {
stale?: true;
type?: string;
routes: Array<
Omit<Route<string>, 'key'> & { key?: string; state?: InitialState }
>;
routes: (Omit<Route<string>, 'key'> & {
key?: string;
state?: InitialState;
})[];
};
export type Route<RouteName extends string> = {
@@ -71,6 +71,10 @@ export type NavigationAction = {
* Type of the action (e.g. `NAVIGATE`)
*/
type: string;
/**
* Additional data for the action
*/
payload?: object;
/**
* Key of the route which dispatched this action.
*/
@@ -533,7 +537,7 @@ export type RouteConfig<
/**
* Initial params object for the route.
*/
initialParams?: ParamList[RouteName];
initialParams?: Partial<ParamList[RouteName]>;
} & (
| {
/**

View File

@@ -44,16 +44,15 @@ export default function useDevTools({ name, reset, state }: Options) {
const devTools = devToolsRef.current;
const lastStateRef = React.useRef<State>(state);
const actions = React.useRef<Array<NavigationAction | string>>([]);
const actions = React.useRef<(NavigationAction | string)[]>([]);
React.useEffect(() => {
devTools && devTools.init(lastStateRef.current);
devTools?.init(lastStateRef.current);
}, [devTools]);
React.useEffect(
() =>
devTools &&
devTools.subscribe(message => {
devTools?.subscribe(message => {
if (message.type === 'DISPATCH' && message.state) {
reset(JSON.parse(message.state));
}

View File

@@ -5,7 +5,7 @@ export type NavigationEventEmitter = EventEmitter<Record<string, any>> & {
create: (target: string) => EventConsumer<Record<string, any>>;
};
type Listeners = Array<(data: any) => void>;
type Listeners = ((data: any) => void)[];
/**
* Hook to manage the event system used by the navigator to notify screens of various events.
@@ -69,7 +69,7 @@ export default function useEventEmitter(): NavigationEventEmitter {
},
};
callbacks && callbacks.forEach(cb => cb(event));
callbacks?.forEach(cb => cb(event));
return event;
},

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import useNavigation from './useNavigation';
type EffectCallback = (() => void) | (() => () => void);
type EffectCallback = (() => undefined) | (() => () => void);
/**
* Hook to run an effect in a focused screen, similar to `React.useEffect`.
@@ -15,7 +15,7 @@ export default function useFocusEffect(callback: EffectCallback) {
React.useEffect(() => {
let isFocused = false;
let cleanup: (() => void) | void;
let cleanup: (() => void) | undefined;
// We need to run the effect on intial render/dep changes if the screen is focused
if (navigation.isFocused()) {
@@ -30,19 +30,19 @@ export default function useFocusEffect(callback: EffectCallback) {
return;
}
cleanup && cleanup();
cleanup?.();
cleanup = callback();
isFocused = true;
});
const unsubscribeBlur = navigation.addListener('blur', () => {
cleanup && cleanup();
cleanup?.();
cleanup = undefined;
isFocused = false;
});
return () => {
cleanup && cleanup();
cleanup?.();
unsubscribeFocus();
unsubscribeBlur();
};

View File

@@ -21,8 +21,7 @@ export default function useFocusEvents({ state, emitter }: Options) {
// Coz the child screen can't be focused if the parent screen is out of fcous
React.useEffect(
() =>
navigation &&
navigation.addListener('focus', () =>
navigation?.addListener('focus', () =>
emitter.emit({ type: 'focus', target: currentFocusedKey })
),
[currentFocusedKey, emitter, navigation]
@@ -30,8 +29,7 @@ export default function useFocusEvents({ state, emitter }: Options) {
React.useEffect(
() =>
navigation &&
navigation.addListener('blur', () =>
navigation?.addListener('blur', () =>
emitter.emit({ type: 'blur', target: currentFocusedKey })
),
[currentFocusedKey, emitter, navigation]

View File

@@ -38,7 +38,7 @@ export default function useFocusedListenersChildrenAdapter({
[focusedListeners, navigation]
);
React.useEffect(() => addFocusedListener && addFocusedListener(listener), [
React.useEffect(() => addFocusedListener?.(listener), [
addFocusedListener,
listener,
]);

View File

@@ -117,7 +117,7 @@ export default function useNavigationBuilder<
const { current: router } = React.useRef<Router<State, any>>(
createRouter({
...((rest as unknown) as RouterOptions),
...(route && route.params && typeof route.params.screen === 'string'
...(route?.params && typeof route.params.screen === 'string'
? { initialRouteName: route.params.screen }
: null),
})
@@ -141,7 +141,7 @@ export default function useNavigationBuilder<
(acc, curr) => {
const { initialParams } = screens[curr];
const initialParamsFromParams =
route && route.params && route.params.screen === curr
route?.params && route.params.screen === curr
? route.params.params
: undefined;

View File

@@ -42,13 +42,22 @@ export default function useNavigationHelpers<
const { performTransaction } = React.useContext(NavigationStateContext);
return React.useMemo(() => {
const dispatch = (action: Action | ((state: State) => Action)) =>
const dispatch = (action: Action | ((state: State) => Action)) => {
performTransaction(() => {
const payload =
typeof action === 'function' ? action(getState()) : action;
onAction(payload);
const handled = onAction(payload);
if (!handled && process.env.NODE_ENV !== 'production') {
console.error(
`The action '${payload.type}' with payload '${JSON.stringify(
payload.payload
)}' was not handled by any navigator.`
);
}
});
};
const actions = {
...router.actionCreators,
@@ -81,7 +90,7 @@ export default function useNavigationHelpers<
routeNames: state.routeNames,
routeParamList: {},
}) !== null ||
(parentNavigationHelpers && parentNavigationHelpers.canGoBack()) ||
parentNavigationHelpers?.canGoBack() ||
false
);
},

View File

@@ -130,10 +130,10 @@ export default function useOnAction({
]
);
React.useEffect(
() => addActionListenerParent && addActionListenerParent(onAction),
[addActionListenerParent, onAction]
);
React.useEffect(() => addActionListenerParent?.(onAction), [
addActionListenerParent,
onAction,
]);
return onAction;
}

View File

@@ -26,6 +26,6 @@ export default function useOnGetState({
}, [getState, getStateForRoute]);
React.useEffect(() => {
return addStateGetter && addStateGetter(key, getRehydratedState);
return addStateGetter?.(key, getRehydratedState);
}, [addStateGetter, getRehydratedState, key]);
}

View File

@@ -3,6 +3,87 @@
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.34](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.33...@react-navigation/drawer@5.0.0-alpha.34) (2020-01-05)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.33](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.32...@react-navigation/drawer@5.0.0-alpha.33) (2020-01-03)
### Bug Fixes
* provide initial values for safe area to prevent blank screen ([#238](https://github.com/react-navigation/navigation-ex/issues/238)) ([77b7570](https://github.com/react-navigation/navigation-ex/commit/77b757091c0451e20bca01138629669c7da544a8))
### Features
* add interaction handle to drawer ([#239](https://github.com/react-navigation/navigation-ex/issues/239)) ([fa4411a](https://github.com/react-navigation/navigation-ex/commit/fa4411a14dc4aae568794e4b884088e3276a2876))
# [5.0.0-alpha.32](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.31...@react-navigation/drawer@5.0.0-alpha.32) (2020-01-03)
### Bug Fixes
* pass backBehavior to the router in drawer. fixes [#230](https://github.com/react-navigation/navigation-ex/issues/230) ([3cd1aed](https://github.com/react-navigation/navigation-ex/commit/3cd1aedcf490a4c7962b2d36873d714637f3b9b0))
# [5.0.0-alpha.31](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.30...@react-navigation/drawer@5.0.0-alpha.31) (2020-01-01)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.30](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.29...@react-navigation/drawer@5.0.0-alpha.30) (2019-12-19)
### Bug Fixes
* set screen background in drawer from theme ([0635365](https://github.com/react-navigation/navigation-ex/commit/0635365483bf5ac38e75191b4ba8f52cf6d73896))
# [5.0.0-alpha.29](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.28...@react-navigation/drawer@5.0.0-alpha.29) (2019-12-16)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.28](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.27...@react-navigation/drawer@5.0.0-alpha.28) (2019-12-14)
### Features
* add custom theme support ([#211](https://github.com/react-navigation/navigation-ex/issues/211)) ([00fc616](https://github.com/react-navigation/navigation-ex/commit/00fc616de0572bade8aa85052cdc8290360b1d7f))
# [5.0.0-alpha.27](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.26...@react-navigation/drawer@5.0.0-alpha.27) (2019-12-11)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.26](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.25...@react-navigation/drawer@5.0.0-alpha.26) (2019-12-10)
**Note:** Version bump only for package @react-navigation/drawer

View File

@@ -7,7 +7,7 @@ Bottom tab navigator for React Navigation following iOS design guidelines.
Open a Terminal in your project's folder and run,
```sh
yarn add @react-navigation/core @react-navigation/drawer
yarn add @react-navigation/native @react-navigation/drawer
```
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).

View File

@@ -11,7 +11,7 @@
"material",
"drawer"
],
"version": "5.0.0-alpha.26",
"version": "5.0.0-alpha.34",
"license": "MIT",
"repository": {
"type": "git",
@@ -34,28 +34,30 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.15"
"@react-navigation/routers": "^5.0.0-alpha.20",
"color": "^3.1.2",
"react-native-iphone-x-helper": "^1.2.1"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",
"@types/react": "^16.9.11",
"@types/react-native": "^0.60.22",
"@types/react": "^16.9.16",
"@types/react-native": "^0.60.25",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native": "~0.61.5",
"react-native-gesture-handler": "^1.5.0",
"react-native-reanimated": "^1.4.0",
"react-native-safe-area-context": "^0.6.0",
"react-native-screens": "^2.0.0-alpha.11",
"typescript": "^3.7.2"
"react-native-screens": "^2.0.0-alpha.19",
"typescript": "^3.7.3"
},
"peerDependencies": {
"@react-navigation/core": "^5.0.0-alpha.0",
"@react-navigation/native": "^5.0.0-alpha.0",
"react": "*",
"react-native": "*",
"react-native-gesture-handler": "^1.0.0",
"react-native-reanimated": "^1.0.0",
"react-native-safe-area-context": "^0.3.6",
"react-native-safe-area-context": "^0.6.0",
"react-native-screens": "^1.0.0-alpha.0 || ^2.0.0-alpha.0"
},
"@react-native-community/bob": {

View File

@@ -10,6 +10,7 @@ export { default as DrawerView } from './views/DrawerView';
export { default as DrawerItem } from './views/DrawerItem';
export { default as DrawerItemList } from './views/DrawerItemList';
export { default as DrawerContent } from './views/DrawerContent';
export { default as DrawerContentScrollView } from './views/DrawerContentScrollView';
/**
* Utilities

View File

@@ -3,7 +3,7 @@ import {
createNavigatorFactory,
useNavigationBuilder,
DefaultNavigatorOptions,
} from '@react-navigation/core';
} from '@react-navigation/native';
import {
DrawerNavigationState,
DrawerRouterOptions,
@@ -19,10 +19,11 @@ import {
type Props = DefaultNavigatorOptions<DrawerNavigationOptions> &
DrawerRouterOptions &
Partial<DrawerNavigationConfig>;
DrawerNavigationConfig;
function DrawerNavigator({
initialRouteName,
backBehavior,
children,
screenOptions,
...rest
@@ -34,6 +35,7 @@ function DrawerNavigator({
DrawerNavigationEventMap
>(DrawerRouter, {
initialRouteName,
backBehavior,
children,
screenOptions,
});

View File

@@ -6,7 +6,7 @@ import {
NavigationProp,
Descriptor,
NavigationHelpers,
} from '@react-navigation/core';
} from '@react-navigation/native';
import { DrawerNavigationState } from '@react-navigation/routers';
import { PanGestureHandler } from 'react-native-gesture-handler';
@@ -20,14 +20,14 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
/**
* Position of the drawer on the screen. Defaults to `left`.
*/
drawerPosition: 'left' | 'right';
drawerPosition?: 'left' | 'right';
/**
* Type of the drawer. It determines how the drawer looks and animates.
* - `front`: Traditional drawer which covers the screen with a overlay behind it.
* - `back`: The drawer is revealed behind the screen on swipe.
* - `slide`: Both the screen and the drawer slide on swipe to reveal the drawer.
*/
drawerType: 'front' | 'back' | 'slide';
drawerType?: 'front' | 'back' | 'slide';
/**
* How far from the edge of the screen the swipe gesture should activate.
*/
@@ -35,12 +35,12 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
/**
* Whether the statusbar should be hidden when the drawer is pulled or opens,
*/
hideStatusBar: boolean;
hideStatusBar?: boolean;
/**
* Whether the keyboard should be dismissed when the swipe gesture begins.
* Defaults to `'on-drag'`. Set to `'none'` to disable keyboard handling.
*/
keyboardDismissMode: 'on-drag' | 'none';
keyboardDismissMode?: 'on-drag' | 'none';
/**
* Minimum swipe distance threshold that should activate opening the drawer.
*/
@@ -53,7 +53,7 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
/**
* Animation of the statusbar when hiding it. use in combination with `hideStatusBar`.
*/
statusBarAnimation: 'slide' | 'none' | 'fade';
statusBarAnimation?: 'slide' | 'none' | 'fade';
/**
* Props to pass to the underlying pan gesture handler.
*/
@@ -62,7 +62,7 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
* Whether the screens should render the first time they are accessed. Defaults to `true`.
* Set it to `false` if you want to render all screens on initial render.
*/
lazy: boolean;
lazy?: boolean;
/**
* Whether a screen should be unmounted when navigating away from it.
* Defaults to `false`.
@@ -72,7 +72,7 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
* Function that returns React element to render as the content of the drawer, for example, navigation items.
* Defaults to `DrawerContent`.
*/
drawerContent: (props: DrawerContentComponentProps<T>) => React.ReactNode;
drawerContent?: (props: DrawerContentComponentProps<T>) => React.ReactNode;
/**
* Options for the content component which will be passed as props.
*/
@@ -121,7 +121,7 @@ export type DrawerNavigationOptions = {
export type DrawerContentComponentProps<T = DrawerContentOptions> = T & {
state: DrawerNavigationState;
navigation: NavigationHelpers<ParamListBase>;
navigation: DrawerNavigationHelpers;
descriptors: DrawerDescriptorMap;
/**
* Animated node which represents the current progress of the drawer's open state.

View File

@@ -9,6 +9,7 @@ import {
StatusBar,
StyleProp,
View,
InteractionManager,
} from 'react-native';
import {
PanGestureHandler,
@@ -161,9 +162,24 @@ export default class DrawerView extends React.PureComponent<Props> {
componentWillUnmount() {
this.toggleStatusBar(false);
this.handleEndInteraction();
}
private handleEndInteraction = () => {
if (this.interactionHandle !== undefined) {
InteractionManager.clearInteractionHandle(this.interactionHandle);
this.interactionHandle = undefined;
}
};
private handleStartInteraction = () => {
if (this.interactionHandle === undefined) {
this.interactionHandle = InteractionManager.createInteractionHandle();
}
};
private clock = new Clock();
private interactionHandle: number | undefined;
private isDrawerTypeFront = new Value<Binary>(
this.props.drawerType === 'front' ? TRUE : FALSE
@@ -277,6 +293,7 @@ export default class DrawerView extends React.PureComponent<Props> {
set(state.velocity, this.velocityX),
set(this.isOpen, isOpen),
startClock(this.clock),
call([], this.handleStartInteraction),
set(this.manuallyTriggerSpring, FALSE),
]),
spring(this.clock, state, { ...SPRING_CONFIG, toValue }),
@@ -288,8 +305,9 @@ export default class DrawerView extends React.PureComponent<Props> {
set(this.offsetX, 0),
// When the animation finishes, stop the clock
stopClock(this.clock),
call([this.isOpen], ([value]: ReadonlyArray<Binary>) => {
call([this.isOpen], ([value]: readonly Binary[]) => {
const open = Boolean(value);
this.handleEndInteraction();
if (open !== this.props.open) {
// Sync drawer's state after animation finished
@@ -304,7 +322,7 @@ export default class DrawerView extends React.PureComponent<Props> {
private dragX = block([
onChange(
this.isOpen,
call([this.isOpen], ([value]: ReadonlyArray<Binary>) => {
call([this.isOpen], ([value]: readonly Binary[]) => {
const open = Boolean(value);
this.currentOpenValue = open;
@@ -344,7 +362,7 @@ export default class DrawerView extends React.PureComponent<Props> {
// Listen to updates for this value only when it changes
// Without `onChange`, this will fire even if the value didn't change
// We don't want to call the listeners if the value didn't change
call([this.isSwiping], ([value]: ReadonlyArray<Binary>) => {
call([this.isSwiping], ([value]: readonly Binary[]) => {
const { keyboardDismissMode } = this.props;
if (value === TRUE) {
@@ -358,6 +376,13 @@ export default class DrawerView extends React.PureComponent<Props> {
}
})
),
onChange(
this.gestureState,
cond(
eq(this.gestureState, State.ACTIVE),
call([], this.handleStartInteraction)
)
),
cond(
eq(this.gestureState, State.ACTIVE),
[

View File

@@ -1,36 +1,12 @@
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';
import DrawerContentScrollView from './DrawerContentScrollView';
export default function DrawerContent({
contentContainerStyle,
style,
drawerPosition,
...rest
}: DrawerContentComponentProps) {
const insets = useSafeArea();
export default function DrawerContent(props: DrawerContentComponentProps) {
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>
<DrawerContentScrollView {...props}>
<DrawerItemList {...props} />
</DrawerContentScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});

View File

@@ -0,0 +1,41 @@
import * as React from 'react';
import { ScrollView, StyleSheet, ScrollViewProps } from 'react-native';
import { useSafeArea } from 'react-native-safe-area-context';
type Props = ScrollViewProps & {
drawerPosition: 'left' | 'right';
children: React.ReactNode;
};
export default function DrawerContentScrollView({
contentContainerStyle,
style,
drawerPosition,
children,
...rest
}: Props) {
const insets = useSafeArea();
return (
<ScrollView
{...rest}
contentContainerStyle={[
{
paddingTop: insets.top + 4,
paddingLeft: drawerPosition === 'left' ? insets.left : 0,
paddingRight: drawerPosition === 'right' ? insets.right : 0,
},
contentContainerStyle,
]}
style={[styles.container, style]}
>
{children}
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});

View File

@@ -7,6 +7,8 @@ import {
ViewStyle,
TextStyle,
} from 'react-native';
import { useTheme } from '@react-navigation/native';
import Color from 'color';
import TouchableItem from './TouchableItem';
type Props = {
@@ -61,19 +63,29 @@ type Props = {
/**
* A component used to show an action item with an icon and a label in a navigation drawer.
*/
export default function DrawerItem({
icon,
label,
labelStyle,
focused = false,
activeTintColor = '#6200ee',
inactiveTintColor = 'rgba(0, 0, 0, .68)',
activeBackgroundColor = 'rgba(98, 0, 238, 0.12)',
inactiveBackgroundColor = 'transparent',
style,
onPress,
...rest
}: Props) {
export default function DrawerItem(props: Props) {
const { colors } = useTheme();
const {
icon,
label,
labelStyle,
focused = false,
activeTintColor = colors.primary,
inactiveTintColor = Color(colors.text)
.alpha(0.68)
.rgb()
.string(),
activeBackgroundColor = Color(activeTintColor)
.alpha(0.12)
.rgb()
.string(),
inactiveBackgroundColor = 'transparent',
style,
onPress,
...rest
} = props;
const { borderRadius = 4 } = StyleSheet.flatten(style || {});
const color = focused ? activeTintColor : inactiveTintColor;
const backgroundColor = focused

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { CommonActions } from '@react-navigation/core';
import { CommonActions } from '@react-navigation/native';
import {
DrawerActions,
DrawerNavigationState,

View File

@@ -13,6 +13,7 @@ import {
DrawerNavigationState,
DrawerActions,
} from '@react-navigation/routers';
import { useTheme } from '@react-navigation/native';
import DrawerGestureContext from '../utils/DrawerGestureContext';
import SafeAreaProviderCompat from './SafeAreaProviderCompat';
@@ -26,16 +27,10 @@ import {
DrawerContentComponentProps,
} from '../types';
type Props = Omit<DrawerNavigationConfig, 'overlayColor'> & {
type Props = DrawerNavigationConfig & {
state: DrawerNavigationState;
navigation: DrawerNavigationHelpers;
descriptors: DrawerDescriptorMap;
overlayColor: string;
};
type State = {
loaded: number[];
drawerWidth: number;
};
const getDefaultDrawerWidth = ({
@@ -62,49 +57,52 @@ const getDefaultDrawerWidth = ({
/**
* Component that renders the drawer.
*/
export default class DrawerView extends React.PureComponent<Props, State> {
static defaultProps = {
lazy: true,
drawerContent: (props: DrawerContentComponentProps) => (
<DrawerContent {...props} />
),
drawerPosition: I18nManager.isRTL ? 'right' : 'left',
keyboardDismissMode: 'on-drag',
overlayColor: 'rgba(0, 0, 0, 0.5)',
drawerType: 'front',
hideStatusBar: false,
statusBarAnimation: 'slide',
};
export default function DrawerView({
state,
navigation,
descriptors,
lazy = true,
drawerContent = (props: DrawerContentComponentProps) => (
<DrawerContent {...props} />
),
drawerPosition = I18nManager.isRTL ? 'right' : 'left',
keyboardDismissMode = 'on-drag',
overlayColor = 'rgba(0, 0, 0, 0.5)',
drawerType = 'front',
hideStatusBar = false,
statusBarAnimation = 'slide',
drawerContentOptions,
drawerStyle,
edgeWidth,
gestureHandlerProps,
minSwipeDistance,
sceneContainerStyle,
unmountInactiveScreens,
}: Props) {
const [loaded, setLoaded] = React.useState([state.index]);
const [drawerWidth, setDrawerWidth] = React.useState(() =>
getDefaultDrawerWidth(Dimensions.get('window'))
);
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
const { index } = nextProps.state;
const drawerGestureRef = React.useRef<PanGestureHandler>(null);
return {
// Set the current tab to be loaded if it was not loaded before
loaded: prevState.loaded.includes(index)
? prevState.loaded
: [...prevState.loaded, index],
const { colors } = useTheme();
React.useEffect(() => {
const updateWidth = ({ window }: { window: ScaledSize }) => {
setDrawerWidth(getDefaultDrawerWidth(window));
};
Dimensions.addEventListener('change', updateWidth);
return () => Dimensions.removeEventListener('change', updateWidth);
}, []);
if (!loaded.includes(state.index)) {
setLoaded([...loaded, state.index]);
}
state: State = {
loaded: [this.props.state.index],
drawerWidth: getDefaultDrawerWidth(Dimensions.get('window')),
};
componentDidMount() {
Dimensions.addEventListener('change', this.updateWidth);
}
componentWillUnmount() {
Dimensions.removeEventListener('change', this.updateWidth);
}
private drawerGestureRef = React.createRef<PanGestureHandler>();
private handleDrawerOpen = () => {
const { state, navigation } = this.props;
const handleDrawerOpen = () => {
navigation.dispatch({
...DrawerActions.openDrawer(),
target: state.key,
@@ -113,9 +111,7 @@ export default class DrawerView extends React.PureComponent<Props, State> {
navigation.emit({ type: 'drawerOpen' });
};
private handleDrawerClose = () => {
const { state, navigation } = this.props;
const handleDrawerClose = () => {
navigation.dispatch({
...DrawerActions.closeDrawer(),
target: state.key,
@@ -124,24 +120,7 @@ export default class DrawerView extends React.PureComponent<Props, State> {
navigation.emit({ type: 'drawerClose' });
};
private updateWidth = ({ window }: { window: ScaledSize }) => {
const drawerWidth = getDefaultDrawerWidth(window);
if (this.state.drawerWidth !== drawerWidth) {
this.setState({ drawerWidth });
}
};
private renderNavigationView = ({ progress }: any) => {
const {
state,
navigation,
descriptors,
drawerPosition,
drawerContent,
drawerContentOptions,
} = this.props;
const renderNavigationView = ({ progress }: any) => {
return drawerContent({
...drawerContentOptions,
progress: progress,
@@ -152,11 +131,7 @@ export default class DrawerView extends React.PureComponent<Props, State> {
});
};
private renderContent = () => {
let { lazy, state, descriptors, unmountInactiveScreens } = this.props;
const { loaded } = this.state;
const renderContent = () => {
return (
<ScreenContainer style={styles.content}>
{state.routes.map((route, index) => {
@@ -164,7 +139,7 @@ export default class DrawerView extends React.PureComponent<Props, State> {
return null;
}
if (lazy && !loaded.includes(index)) {
if (lazy && !loaded.includes(index) && index !== state.index) {
// Don't render a screen if we've never navigated to it
return null;
}
@@ -186,58 +161,45 @@ export default class DrawerView extends React.PureComponent<Props, State> {
);
};
private setDrawerGestureRef = (ref: PanGestureHandler | null) => {
// @ts-ignore
this.drawerGestureRef.current = ref;
};
const activeKey = state.routes[state.index].key;
const { gestureEnabled } = descriptors[activeKey].options;
render() {
const {
state,
descriptors,
drawerType,
drawerPosition,
overlayColor,
sceneContainerStyle,
drawerStyle,
edgeWidth,
minSwipeDistance,
hideStatusBar,
statusBarAnimation,
gestureHandlerProps,
} = this.props;
const { drawerWidth } = this.state;
const activeKey = state.routes[state.index].key;
const { gestureEnabled } = descriptors[activeKey].options;
return (
<SafeAreaProviderCompat>
<DrawerGestureContext.Provider value={this.drawerGestureRef}>
<Drawer
open={state.isDrawerOpen}
gestureEnabled={gestureEnabled !== false}
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>
</SafeAreaProviderCompat>
);
}
return (
<SafeAreaProviderCompat>
<DrawerGestureContext.Provider value={drawerGestureRef}>
<Drawer
open={state.isDrawerOpen}
gestureEnabled={gestureEnabled !== false}
onOpen={handleDrawerOpen}
onClose={handleDrawerClose}
onGestureRef={ref => {
// @ts-ignore
drawerGestureRef.current = ref;
}}
gestureHandlerProps={gestureHandlerProps}
drawerType={drawerType}
drawerPosition={drawerPosition}
sceneContainerStyle={[
{ backgroundColor: colors.background },
sceneContainerStyle,
]}
drawerStyle={[
{ width: drawerWidth, backgroundColor: colors.card },
drawerStyle,
]}
overlayStyle={{ backgroundColor: overlayColor }}
swipeEdgeWidth={edgeWidth}
swipeDistanceThreshold={minSwipeDistance}
hideStatusBar={hideStatusBar}
statusBarAnimation={statusBarAnimation}
renderDrawerContent={renderNavigationView}
renderSceneContent={renderContent}
keyboardDismissMode={keyboardDismissMode}
drawerPostion={drawerPosition}
/>
</DrawerGestureContext.Provider>
</SafeAreaProviderCompat>
);
}
const styles = StyleSheet.create({

View File

@@ -13,7 +13,7 @@ const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view o
export default class ResourceSavingScene extends React.Component<Props> {
render() {
if (screensEnabled && screensEnabled()) {
if (screensEnabled?.()) {
const { isVisible, ...rest } = this.props;
// @ts-ignore

View File

@@ -3,6 +3,17 @@ import {
SafeAreaProvider,
SafeAreaConsumer,
} from 'react-native-safe-area-context';
import {
getStatusBarHeight,
getBottomSpace,
} from 'react-native-iphone-x-helper';
const initialSafeAreaInsets = {
top: getStatusBarHeight(true),
bottom: getBottomSpace(),
right: 0,
left: 0,
};
type Props = {
children: React.ReactNode;
@@ -19,7 +30,11 @@ export default function SafeAreaProviderCompat({ children }: Props) {
return children;
}
return <SafeAreaProvider>{children}</SafeAreaProvider>;
return (
<SafeAreaProvider initialSafeAreaInsets={initialSafeAreaInsets}>
{children}
</SafeAreaProvider>
);
}}
</SafeAreaConsumer>
);

View File

@@ -3,6 +3,57 @@
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.29](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.28...@react-navigation/material-bottom-tabs@5.0.0-alpha.29) (2020-01-05)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
# [5.0.0-alpha.28](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.27...@react-navigation/material-bottom-tabs@5.0.0-alpha.28) (2020-01-01)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
# [5.0.0-alpha.27](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.26...@react-navigation/material-bottom-tabs@5.0.0-alpha.27) (2019-12-19)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
# [5.0.0-alpha.26](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.25...@react-navigation/material-bottom-tabs@5.0.0-alpha.26) (2019-12-16)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
# [5.0.0-alpha.25](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.24...@react-navigation/material-bottom-tabs@5.0.0-alpha.25) (2019-12-14)
### Features
* add custom theme support ([#211](https://github.com/react-navigation/navigation-ex/issues/211)) ([00fc616](https://github.com/react-navigation/navigation-ex/commit/00fc616de0572bade8aa85052cdc8290360b1d7f))
# [5.0.0-alpha.24](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.23...@react-navigation/material-bottom-tabs@5.0.0-alpha.24) (2019-12-11)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
# [5.0.0-alpha.23](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.22...@react-navigation/material-bottom-tabs@5.0.0-alpha.23) (2019-12-10)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs

View File

@@ -7,7 +7,7 @@ React Navigation integration for [bottom navigation](https://material.io/design/
Open a Terminal in your project's folder and run,
```sh
yarn add @react-navigation/core @react-navigation/material-bottom-tabs
yarn add @react-navigation/native @react-navigation/material-bottom-tabs
```
Setup `react-native-paper` following the [Getting Started guide](https://callstack.github.io/react-native-paper/getting-started.html).

View File

@@ -11,7 +11,7 @@
"material",
"tab"
],
"version": "5.0.0-alpha.23",
"version": "5.0.0-alpha.29",
"license": "MIT",
"repository": {
"type": "git",
@@ -34,22 +34,22 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.15"
"@react-navigation/routers": "^5.0.0-alpha.20"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",
"@types/react": "^16.9.11",
"@types/react-native": "^0.60.22",
"@types/react": "^16.9.16",
"@types/react-native": "^0.60.25",
"@types/react-native-vector-icons": "^6.4.4",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native": "~0.61.5",
"react-native-paper": "^3.2.1",
"react-native-paper": "^3.3.0",
"react-native-vector-icons": "^6.6.0",
"typescript": "^3.7.2"
"typescript": "^3.7.3"
},
"peerDependencies": {
"@react-navigation/core": "^5.0.0-alpha.0",
"@react-navigation/native": "^5.0.0-alpha.0",
"react": "*",
"react-native": "*",
"react-native-paper": "^3.0.0",

View File

@@ -3,7 +3,7 @@ import {
useNavigationBuilder,
createNavigatorFactory,
DefaultNavigatorOptions,
} from '@react-navigation/core';
} from '@react-navigation/native';
import {
TabRouter,
TabRouterOptions,

View File

@@ -4,7 +4,7 @@ import {
Descriptor,
NavigationProp,
NavigationHelpers,
} from '@react-navigation/core';
} from '@react-navigation/native';
import { TabNavigationState } from '@react-navigation/routers';
export type MaterialBottomTabNavigationEventMap = {

View File

@@ -1,8 +1,8 @@
import * as React from 'react';
import { StyleSheet } from 'react-native';
import { BottomNavigation } from 'react-native-paper';
import { BottomNavigation, DefaultTheme, DarkTheme } from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import { Route } from '@react-navigation/core';
import { Route, useTheme } from '@react-navigation/native';
import { TabNavigationState, TabActions } from '@react-navigation/routers';
import {
@@ -25,9 +25,25 @@ export default function MaterialBottomTabView({
descriptors,
...rest
}: Props) {
const { dark, colors } = useTheme();
const theme = React.useMemo(() => {
const t = dark ? DarkTheme : DefaultTheme;
return {
...t,
colors: {
...t.colors,
...colors,
surface: colors.card,
},
};
}, [colors, dark]);
return (
<BottomNavigation
{...rest}
theme={theme}
navigationState={state}
onIndexChange={(index: number) =>
navigation.dispatch({

View File

@@ -3,6 +3,63 @@
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.27](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.26...@react-navigation/material-top-tabs@5.0.0-alpha.27) (2020-01-05)
### Features
* add support for pager component ([dcc5f99](https://github.com/react-navigation/navigation-ex/commit/dcc5f99ecd495ad1903c9e99884e0d4e9b3994f1))
# [5.0.0-alpha.26](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.25...@react-navigation/material-top-tabs@5.0.0-alpha.26) (2020-01-01)
**Note:** Version bump only for package @react-navigation/material-top-tabs
# [5.0.0-alpha.25](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.24...@react-navigation/material-top-tabs@5.0.0-alpha.25) (2019-12-19)
### Bug Fixes
* fix backgroundColor in sceneContainerStyle overriden by theme ([ebd145a](https://github.com/react-navigation/navigation-ex/commit/ebd145a09d80f119070a14a8d4940b5757b5e7fb)), closes [#215](https://github.com/react-navigation/navigation-ex/issues/215)
# [5.0.0-alpha.24](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.23...@react-navigation/material-top-tabs@5.0.0-alpha.24) (2019-12-16)
**Note:** Version bump only for package @react-navigation/material-top-tabs
# [5.0.0-alpha.23](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.22...@react-navigation/material-top-tabs@5.0.0-alpha.23) (2019-12-14)
### Features
* add custom theme support ([#211](https://github.com/react-navigation/navigation-ex/issues/211)) ([00fc616](https://github.com/react-navigation/navigation-ex/commit/00fc616de0572bade8aa85052cdc8290360b1d7f))
# [5.0.0-alpha.22](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.21...@react-navigation/material-top-tabs@5.0.0-alpha.22) (2019-12-11)
**Note:** Version bump only for package @react-navigation/material-top-tabs
# [5.0.0-alpha.21](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.20...@react-navigation/material-top-tabs@5.0.0-alpha.21) (2019-12-10)
**Note:** Version bump only for package @react-navigation/material-top-tabs

View File

@@ -7,7 +7,7 @@ React Navigation integration for animated tab view component from [`react-native
Open a Terminal in your project's folder and run,
```sh
yarn add @react-navigation/core @react-navigation/material-top-tabs react-native-tab-view
yarn add @react-navigation/native @react-navigation/material-top-tabs react-native-tab-view
```
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)..

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