Compare commits
57 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edeb2e8ad9 | ||
|
|
133b59cd17 | ||
|
|
a9e584c3b7 | ||
|
|
c46e0a9c14 | ||
|
|
418a858f23 | ||
|
|
b201fd2071 | ||
|
|
c514542305 | ||
|
|
dcc5f99ecd | ||
|
|
adbeb292f5 | ||
|
|
543679f185 | ||
|
|
cbe240eae6 | ||
|
|
7f963a74bb | ||
|
|
572beae41b | ||
|
|
15fe3ebb51 | ||
|
|
31565d5576 | ||
|
|
2c31d1705c | ||
|
|
8f9a250958 | ||
|
|
87d28ca430 | ||
|
|
fa4411a14d | ||
|
|
77b757091c | ||
|
|
6b9b999c5b | ||
|
|
8c5f84094f | ||
|
|
21709f7674 | ||
|
|
22742e4a7f | ||
|
|
3cd1aedcf4 | ||
|
|
797d3a798e | ||
|
|
59803f54d6 | ||
|
|
935659899f | ||
|
|
ef0f5d6567 | ||
|
|
499f66dba4 | ||
|
|
2ef2f1a86f | ||
|
|
0252bdc222 | ||
|
|
282f62c258 | ||
|
|
f462d67270 | ||
|
|
873afec9fe | ||
|
|
878297e52f | ||
|
|
1ea9b4524d | ||
|
|
c52a8c46a8 | ||
|
|
0635365483 | ||
|
|
9843b92e05 | ||
|
|
ebd145a09d | ||
|
|
9fc1af02c2 | ||
|
|
68a334cc93 | ||
|
|
c110570d4c | ||
|
|
d57226fd8b | ||
|
|
c3d3748143 | ||
|
|
8002d51795 | ||
|
|
31cf19912b | ||
|
|
d0e4e1f6fb | ||
|
|
00fc616de0 | ||
|
|
703edb3569 | ||
|
|
38a38b021a | ||
|
|
42bc37d2ff | ||
|
|
75ad2aaae5 | ||
|
|
eef17a801e | ||
|
|
7b8277dae5 | ||
|
|
6cddb5238c |
@@ -49,6 +49,7 @@ jobs:
|
||||
at: ~/project
|
||||
- run: |
|
||||
yarn lerna run prepare
|
||||
node scripts/check-types-path.js
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
@@ -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';
|
||||
|
||||
// ...
|
||||
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
};
|
||||
|
||||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 900 B |
@@ -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: {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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' }}
|
||||
/>
|
||||
|
||||
@@ -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'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 "de Finibus Bonorum
|
||||
et Malorum" (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, "Lorem ipsum dolor
|
||||
sit amet..", 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 "de Finibus
|
||||
Bonorum et Malorum" 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 "Content here, content here", 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
|
||||
"lorem ipsum" 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't look even slightly believable. If you
|
||||
are going to use a passage of Lorem Ipsum, you need to be sure there
|
||||
isn'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'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 "de Finibus Bonorum et Malorum" (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, "Lorem ipsum dolor sit amet..", 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 "de
|
||||
Finibus Bonorum et Malorum" 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 "Content here, content here",
|
||||
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 "lorem ipsum" 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't look even slightly believable. If you
|
||||
are going to use a passage of Lorem Ipsum, you need to be sure there
|
||||
isn'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,
|
||||
|
||||
@@ -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' }}
|
||||
/>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 "de Finibus
|
||||
Bonorum et Malorum" (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,
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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)',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
13
package.json
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
useNavigationBuilder,
|
||||
createNavigatorFactory,
|
||||
DefaultNavigatorOptions,
|
||||
} from '@react-navigation/core';
|
||||
} from '@react-navigation/native';
|
||||
import {
|
||||
TabRouter,
|
||||
TabRouterOptions,
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -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} />;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
NavigationProp,
|
||||
ParamListBase,
|
||||
RouteProp,
|
||||
} from '@react-navigation/core';
|
||||
} from '@react-navigation/native';
|
||||
import ScreenPropsContext from './ScreenPropsContext';
|
||||
import createCompatNavigationProp from './createCompatNavigationProp';
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>;
|
||||
} = {}
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
useNavigationBuilder,
|
||||
createNavigatorFactory,
|
||||
DefaultNavigatorOptions,
|
||||
} from '@react-navigation/core';
|
||||
} from '@react-navigation/native';
|
||||
import {
|
||||
TabRouter,
|
||||
TabRouterOptions,
|
||||
|
||||
@@ -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<
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
useRoute,
|
||||
NavigationProp,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/core';
|
||||
} from '@react-navigation/native';
|
||||
import createCompatNavigationProp from './createCompatNavigationProp';
|
||||
import { CompatNavigationProp } from './types';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { NavigationState, PartialState } from './types';
|
||||
|
||||
export type Action =
|
||||
|
||||
@@ -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[]) =>
|
||||
|
||||
75
packages/core/src/__tests__/getActionFromState.test.tsx
Normal 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',
|
||||
});
|
||||
});
|
||||
@@ -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' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
67
packages/core/src/getActionFromState.tsx
Normal 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,
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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]>;
|
||||
} & (
|
||||
| {
|
||||
/**
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -38,7 +38,7 @@ export default function useFocusedListenersChildrenAdapter({
|
||||
[focusedListeners, navigation]
|
||||
);
|
||||
|
||||
React.useEffect(() => addFocusedListener && addFocusedListener(listener), [
|
||||
React.useEffect(() => addFocusedListener?.(listener), [
|
||||
addFocusedListener,
|
||||
listener,
|
||||
]);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
},
|
||||
|
||||
@@ -130,10 +130,10 @@ export default function useOnAction({
|
||||
]
|
||||
);
|
||||
|
||||
React.useEffect(
|
||||
() => addActionListenerParent && addActionListenerParent(onAction),
|
||||
[addActionListenerParent, onAction]
|
||||
);
|
||||
React.useEffect(() => addActionListenerParent?.(onAction), [
|
||||
addActionListenerParent,
|
||||
onAction,
|
||||
]);
|
||||
|
||||
return onAction;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,6 @@ export default function useOnGetState({
|
||||
}, [getState, getStateForRoute]);
|
||||
|
||||
React.useEffect(() => {
|
||||
return addStateGetter && addStateGetter(key, getRehydratedState);
|
||||
return addStateGetter?.(key, getRehydratedState);
|
||||
}, [addStateGetter, getRehydratedState, key]);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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),
|
||||
[
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
41
packages/drawer/src/views/DrawerContentScrollView.tsx
Normal 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,
|
||||
},
|
||||
});
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { CommonActions } from '@react-navigation/core';
|
||||
import { CommonActions } from '@react-navigation/native';
|
||||
import {
|
||||
DrawerActions,
|
||||
DrawerNavigationState,
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
useNavigationBuilder,
|
||||
createNavigatorFactory,
|
||||
DefaultNavigatorOptions,
|
||||
} from '@react-navigation/core';
|
||||
} from '@react-navigation/native';
|
||||
import {
|
||||
TabRouter,
|
||||
TabRouterOptions,
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)..
|
||||
|
||||