mirror of
https://github.com/status-im/react-navigation.git
synced 2025-02-24 09:08:15 +00:00
Drawer Router (#3618)
This commit is contained in:
parent
cd99dc8054
commit
2e47cbb3cb
@ -4,7 +4,11 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Button, Platform, ScrollView, StatusBar } from 'react-native';
|
||||
import { StackNavigator, DrawerNavigator, SafeAreaView } from 'react-navigation';
|
||||
import {
|
||||
StackNavigator,
|
||||
DrawerNavigator,
|
||||
SafeAreaView,
|
||||
} from 'react-navigation';
|
||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
import SampleText from './SampleText';
|
||||
|
||||
@ -12,10 +16,7 @@ const MyNavScreen = ({ navigation, banner }) => (
|
||||
<ScrollView>
|
||||
<SafeAreaView forceInset={{ top: 'always' }}>
|
||||
<SampleText>{banner}</SampleText>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('DrawerOpen')}
|
||||
title="Open drawer"
|
||||
/>
|
||||
<Button onPress={() => navigation.openDrawer()} title="Open drawer" />
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Email')}
|
||||
title="Open other screen"
|
||||
@ -76,9 +77,6 @@ const DrawerExample = DrawerNavigator(
|
||||
},
|
||||
},
|
||||
{
|
||||
drawerOpenRoute: 'DrawerOpen',
|
||||
drawerCloseRoute: 'DrawerClose',
|
||||
drawerToggleRoute: 'DrawerToggle',
|
||||
initialRouteName: 'Drafts',
|
||||
contentOptions: {
|
||||
activeTintColor: '#e91e63',
|
||||
|
@ -11,10 +11,7 @@ import SampleText from './SampleText';
|
||||
const MyNavScreen = ({ navigation, banner }) => (
|
||||
<ScrollView style={styles.container}>
|
||||
<SampleText>{banner}</SampleText>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('DrawerOpen')}
|
||||
title="Open drawer"
|
||||
/>
|
||||
<Button onPress={() => navigation.openDrawer()} title="Open drawer" />
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
</ScrollView>
|
||||
);
|
||||
@ -55,9 +52,6 @@ const DrawerExample = DrawerNavigator(
|
||||
},
|
||||
},
|
||||
{
|
||||
drawerOpenRoute: 'DrawerOpen',
|
||||
drawerCloseRoute: 'DrawerClose',
|
||||
drawerToggleRoute: 'DrawerToggle',
|
||||
initialRouteName: 'Drafts',
|
||||
contentOptions: {
|
||||
activeTintColor: '#e91e63',
|
||||
@ -69,10 +63,6 @@ const MainDrawerExample = DrawerNavigator({
|
||||
Drafts: {
|
||||
screen: DrawerExample,
|
||||
},
|
||||
}, {
|
||||
drawerOpenRoute: 'DrawerOpen',
|
||||
drawerCloseRoute: 'DrawerClose',
|
||||
drawerToggleRoute: 'DrawerToggle',
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
6
flow/react-navigation.js
vendored
6
flow/react-navigation.js
vendored
@ -766,9 +766,6 @@ declare module 'react-navigation' {
|
||||
drawerLockMode?: 'unlocked' | 'locked-closed' | 'locked-open',
|
||||
drawerWidth?: number | (() => number),
|
||||
drawerPosition?: 'left' | 'right',
|
||||
drawerOpenRoute?: string,
|
||||
drawerCloseRoute?: string,
|
||||
drawerToggleRoute?: string,
|
||||
contentComponent?: React$ElementType,
|
||||
contentOptions?: {},
|
||||
style?: ViewStyleProp,
|
||||
@ -901,9 +898,6 @@ declare module 'react-navigation' {
|
||||
drawerLockMode?: 'unlocked' | 'locked-closed' | 'locked-open',
|
||||
drawerWidth: number | (() => number),
|
||||
drawerPosition: 'left' | 'right',
|
||||
drawerOpenRoute: string,
|
||||
drawerCloseRoute: string,
|
||||
drawerToggleRoute: string,
|
||||
contentComponent: React$ElementType,
|
||||
contentOptions?: {},
|
||||
style?: ViewStyleProp,
|
||||
|
@ -9,6 +9,9 @@ const REPLACE = 'Navigation/REPLACE';
|
||||
const SET_PARAMS = 'Navigation/SET_PARAMS';
|
||||
const URI = 'Navigation/URI';
|
||||
const COMPLETE_TRANSITION = 'Navigation/COMPLETE_TRANSITION';
|
||||
const OPEN_DRAWER = 'Navigation/OPEN_DRAWER';
|
||||
const CLOSE_DRAWER = 'Navigation/CLOSE_DRAWER';
|
||||
const TOGGLE_DRAWER = 'Navigation/TOGGLE_DRAWER';
|
||||
|
||||
const createAction = (type, fn) => {
|
||||
fn.toString = () => type;
|
||||
@ -107,6 +110,16 @@ const completeTransition = createAction(COMPLETE_TRANSITION, payload => ({
|
||||
key: payload && payload.key,
|
||||
}));
|
||||
|
||||
const openDrawer = createAction(OPEN_DRAWER, payload => ({
|
||||
type: OPEN_DRAWER,
|
||||
}));
|
||||
const closeDrawer = createAction(CLOSE_DRAWER, payload => ({
|
||||
type: CLOSE_DRAWER,
|
||||
}));
|
||||
const toggleDrawer = createAction(TOGGLE_DRAWER, payload => ({
|
||||
type: TOGGLE_DRAWER,
|
||||
}));
|
||||
|
||||
export default {
|
||||
// Action constants
|
||||
BACK,
|
||||
@ -120,6 +133,9 @@ export default {
|
||||
SET_PARAMS,
|
||||
URI,
|
||||
COMPLETE_TRANSITION,
|
||||
OPEN_DRAWER,
|
||||
CLOSE_DRAWER,
|
||||
TOGGLE_DRAWER,
|
||||
|
||||
// Action creators
|
||||
back,
|
||||
@ -133,4 +149,7 @@ export default {
|
||||
setParams,
|
||||
uri,
|
||||
completeTransition,
|
||||
openDrawer,
|
||||
closeDrawer,
|
||||
toggleDrawer,
|
||||
};
|
||||
|
@ -85,5 +85,9 @@ export default function(navigation) {
|
||||
key: navigation.state.key,
|
||||
})
|
||||
),
|
||||
|
||||
openDrawer: () => navigation.dispatch(NavigationActions.openDrawer()),
|
||||
closeDrawer: () => navigation.dispatch(NavigationActions.closeDrawer()),
|
||||
toggleDrawer: () => navigation.dispatch(NavigationActions.toggleDrawer()),
|
||||
};
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import SafeAreaView from 'react-native-safe-area-view';
|
||||
|
||||
import createNavigator from './createNavigator';
|
||||
import createNavigationContainer from '../createNavigationContainer';
|
||||
import TabRouter from '../routers/TabRouter';
|
||||
import DrawerRouter from '../routers/DrawerRouter';
|
||||
import DrawerScreen from '../views/Drawer/DrawerScreen';
|
||||
import DrawerView from '../views/Drawer/DrawerView';
|
||||
import DrawerItems from '../views/Drawer/DrawerNavigatorItems';
|
||||
@ -38,9 +38,6 @@ const DefaultDrawerConfig = {
|
||||
return Math.min(smallerAxisSize - appBarHeight, maxWidth);
|
||||
},
|
||||
contentComponent: defaultContentComponent,
|
||||
drawerOpenRoute: 'DrawerOpen',
|
||||
drawerCloseRoute: 'DrawerClose',
|
||||
drawerToggleRoute: 'DrawerToggle',
|
||||
drawerPosition: 'left',
|
||||
drawerBackgroundColor: 'white',
|
||||
useNativeAnimations: true,
|
||||
@ -48,6 +45,7 @@ const DefaultDrawerConfig = {
|
||||
|
||||
const DrawerNavigator = (routeConfigs, config = {}) => {
|
||||
const mergedConfig = { ...DefaultDrawerConfig, ...config };
|
||||
|
||||
const {
|
||||
order,
|
||||
paths,
|
||||
@ -55,29 +53,15 @@ const DrawerNavigator = (routeConfigs, config = {}) => {
|
||||
backBehavior,
|
||||
...drawerConfig
|
||||
} = mergedConfig;
|
||||
const tabsConfig = {
|
||||
|
||||
const routerConfig = {
|
||||
order,
|
||||
paths,
|
||||
initialRouteName,
|
||||
backBehavior,
|
||||
};
|
||||
const contentRouter = TabRouter(routeConfigs, tabsConfig);
|
||||
const drawerRouter = TabRouter(
|
||||
{
|
||||
[drawerConfig.drawerCloseRoute]: {
|
||||
screen: createNavigator(DrawerScreen, contentRouter, config),
|
||||
},
|
||||
[drawerConfig.drawerOpenRoute]: {
|
||||
screen: () => null,
|
||||
},
|
||||
[drawerConfig.drawerToggleRoute]: {
|
||||
screen: () => null,
|
||||
},
|
||||
},
|
||||
{
|
||||
initialRouteName: drawerConfig.drawerCloseRoute,
|
||||
}
|
||||
);
|
||||
|
||||
const drawerRouter = DrawerRouter(routeConfigs, routerConfig);
|
||||
|
||||
const navigator = createNavigator(DrawerView, drawerRouter, drawerConfig);
|
||||
|
||||
|
55
src/routers/DrawerRouter.js
Normal file
55
src/routers/DrawerRouter.js
Normal file
@ -0,0 +1,55 @@
|
||||
import invariant from '../utils/invariant';
|
||||
import TabRouter from './TabRouter';
|
||||
|
||||
import NavigationActions from '../NavigationActions';
|
||||
|
||||
export default (routeConfigs, config = {}) => {
|
||||
const tabRouter = TabRouter(routeConfigs, config);
|
||||
return {
|
||||
...tabRouter,
|
||||
|
||||
getStateForAction(action, lastState) {
|
||||
const state = lastState || {
|
||||
...tabRouter.getStateForAction(action, undefined),
|
||||
isDrawerOpen: false,
|
||||
};
|
||||
|
||||
// Handle explicit drawer actions
|
||||
if (
|
||||
state.isDrawerOpen &&
|
||||
action.type === NavigationActions.CLOSE_DRAWER
|
||||
) {
|
||||
return {
|
||||
...state,
|
||||
isDrawerOpen: false,
|
||||
};
|
||||
}
|
||||
if (
|
||||
!state.isDrawerOpen &&
|
||||
action.type === NavigationActions.OPEN_DRAWER
|
||||
) {
|
||||
return {
|
||||
...state,
|
||||
isDrawerOpen: true,
|
||||
};
|
||||
}
|
||||
if (action.type === NavigationActions.TOGGLE_DRAWER) {
|
||||
return {
|
||||
...state,
|
||||
isDrawerOpen: !state.isDrawerOpen,
|
||||
};
|
||||
}
|
||||
|
||||
// Fall back on tab router for screen switching logic
|
||||
const tabState = tabRouter.getStateForAction(action, state);
|
||||
if (tabState !== null && tabState !== state) {
|
||||
// If the tabs have changed, make sure to close the drawer
|
||||
return {
|
||||
...tabState,
|
||||
isDrawerOpen: false,
|
||||
};
|
||||
}
|
||||
return state;
|
||||
},
|
||||
};
|
||||
};
|
@ -5,7 +5,6 @@ import createConfigGetter from './createConfigGetter';
|
||||
import getScreenForRouteName from './getScreenForRouteName';
|
||||
import StateUtils from '../StateUtils';
|
||||
import validateRouteConfigMap from './validateRouteConfigMap';
|
||||
import getScreenConfigDeprecated from './getScreenConfigDeprecated';
|
||||
import invariant from '../utils/invariant';
|
||||
import { generateKey } from './KeyGenerator';
|
||||
|
||||
@ -576,7 +575,5 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
routeConfigs,
|
||||
stackConfig.navigationOptions
|
||||
),
|
||||
|
||||
getScreenConfig: getScreenConfigDeprecated,
|
||||
};
|
||||
};
|
||||
|
@ -4,7 +4,6 @@ import createConfigGetter from './createConfigGetter';
|
||||
|
||||
import NavigationActions from '../NavigationActions';
|
||||
import validateRouteConfigMap from './validateRouteConfigMap';
|
||||
import getScreenConfigDeprecated from './getScreenConfigDeprecated';
|
||||
|
||||
function childrenUpdateWithoutSwitchingIndex(actionType) {
|
||||
return [
|
||||
@ -323,7 +322,5 @@ export default (routeConfigs, config = {}) => {
|
||||
routeConfigs,
|
||||
config.navigationOptions
|
||||
),
|
||||
|
||||
getScreenConfig: getScreenConfigDeprecated,
|
||||
};
|
||||
};
|
||||
|
72
src/routers/__tests__/DrawerRouter-test.js
Normal file
72
src/routers/__tests__/DrawerRouter-test.js
Normal file
@ -0,0 +1,72 @@
|
||||
/* eslint react/display-name:0 */
|
||||
|
||||
import React from 'react';
|
||||
import DrawerRouter from '../DrawerRouter';
|
||||
|
||||
import NavigationActions from '../../NavigationActions';
|
||||
|
||||
const INIT_ACTION = { type: NavigationActions.INIT };
|
||||
|
||||
describe('DrawerRouter', () => {
|
||||
test('Handles basic tab logic', () => {
|
||||
const ScreenA = () => <div />;
|
||||
const ScreenB = () => <div />;
|
||||
const router = DrawerRouter({
|
||||
Foo: { screen: ScreenA },
|
||||
Bar: { screen: ScreenB },
|
||||
});
|
||||
const state = router.getStateForAction(INIT_ACTION);
|
||||
const expectedState = {
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
routes: [
|
||||
{ key: 'Foo', routeName: 'Foo', params: undefined },
|
||||
{ key: 'Bar', routeName: 'Bar', params: undefined },
|
||||
],
|
||||
isDrawerOpen: false,
|
||||
};
|
||||
expect(state).toEqual(expectedState);
|
||||
const state2 = router.getStateForAction(
|
||||
{ type: NavigationActions.NAVIGATE, routeName: 'Bar' },
|
||||
state
|
||||
);
|
||||
const expectedState2 = {
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
routes: [
|
||||
{ key: 'Foo', routeName: 'Foo', params: undefined },
|
||||
{ key: 'Bar', routeName: 'Bar', params: undefined },
|
||||
],
|
||||
isDrawerOpen: false,
|
||||
};
|
||||
expect(state2).toEqual(expectedState2);
|
||||
expect(router.getComponentForState(expectedState)).toEqual(ScreenA);
|
||||
expect(router.getComponentForState(expectedState2)).toEqual(ScreenB);
|
||||
});
|
||||
|
||||
test('Drawer opens closes and toggles', () => {
|
||||
const ScreenA = () => <div />;
|
||||
const ScreenB = () => <div />;
|
||||
const router = DrawerRouter({
|
||||
Foo: { screen: ScreenA },
|
||||
Bar: { screen: ScreenB },
|
||||
});
|
||||
const state = router.getStateForAction(INIT_ACTION);
|
||||
expect(state.isDrawerOpen).toEqual(false);
|
||||
const state2 = router.getStateForAction(
|
||||
{ type: NavigationActions.OPEN_DRAWER },
|
||||
state
|
||||
);
|
||||
expect(state2.isDrawerOpen).toEqual(true);
|
||||
const state3 = router.getStateForAction(
|
||||
{ type: NavigationActions.CLOSE_DRAWER },
|
||||
state2
|
||||
);
|
||||
expect(state3.isDrawerOpen).toEqual(false);
|
||||
const state4 = router.getStateForAction(
|
||||
{ type: NavigationActions.TOGGLE_DRAWER },
|
||||
state3
|
||||
);
|
||||
expect(state4.isDrawerOpen).toEqual(true);
|
||||
});
|
||||
});
|
@ -1,7 +0,0 @@
|
||||
import invariant from '../utils/invariant';
|
||||
|
||||
export default () =>
|
||||
invariant(
|
||||
false,
|
||||
'`getScreenConfig` has been replaced with `getScreenOptions`'
|
||||
);
|
@ -26,45 +26,23 @@ export default class DrawerView extends React.PureComponent {
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (
|
||||
this.props.navigation.state.index !== nextProps.navigation.state.index
|
||||
) {
|
||||
const {
|
||||
drawerOpenRoute,
|
||||
drawerCloseRoute,
|
||||
drawerToggleRoute,
|
||||
} = this.props.navigationConfig;
|
||||
const { routes, index } = nextProps.navigation.state;
|
||||
if (routes[index].routeName === drawerOpenRoute) {
|
||||
const { isDrawerOpen } = nextProps.navigation.state;
|
||||
const wasDrawerOpen = this.props.navigation.state.isDrawerOpen;
|
||||
if (isDrawerOpen && !wasDrawerOpen) {
|
||||
this._drawer.openDrawer();
|
||||
} else if (routes[index].routeName === drawerToggleRoute) {
|
||||
if (this.props.navigation.state.index === 0) {
|
||||
this.props.navigation.navigate(drawerOpenRoute);
|
||||
} else {
|
||||
this.props.navigation.navigate(drawerCloseRoute);
|
||||
}
|
||||
} else {
|
||||
} else if (wasDrawerOpen && !isDrawerOpen) {
|
||||
this._drawer.closeDrawer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_handleDrawerOpen = () => {
|
||||
const { navigation, navigationConfig } = this.props;
|
||||
const { drawerOpenRoute } = navigationConfig;
|
||||
const { routes, index } = navigation.state;
|
||||
if (routes[index].routeName !== drawerOpenRoute) {
|
||||
this.props.navigation.navigate(drawerOpenRoute);
|
||||
}
|
||||
const { navigation } = this.props;
|
||||
navigation.dispatch({ type: 'DrawerOpenAction' });
|
||||
};
|
||||
|
||||
_handleDrawerClose = () => {
|
||||
const { navigation, navigationConfig } = this.props;
|
||||
const { drawerCloseRoute } = navigationConfig;
|
||||
const { routes, index } = navigation.state;
|
||||
if (routes[index].routeName !== drawerCloseRoute) {
|
||||
this.props.navigation.navigate(drawerCloseRoute);
|
||||
}
|
||||
const { navigation } = this.props;
|
||||
navigation.dispatch({ type: 'DrawerCloseAction' });
|
||||
};
|
||||
|
||||
_updateWidth = () => {
|
||||
@ -78,52 +56,12 @@ export default class DrawerView extends React.PureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
_getNavigationState = navigation => {
|
||||
const { drawerCloseRoute } = this.props.navigationConfig;
|
||||
const navigationState = navigation.state.routes.find(
|
||||
route => route.routeName === drawerCloseRoute
|
||||
);
|
||||
|
||||
return navigationState;
|
||||
};
|
||||
|
||||
_renderNavigationView = () => {
|
||||
const details = Object.values(this.props.descriptors).find(
|
||||
d => d.state.routeName === this.props.navigationConfig.drawerCloseRoute
|
||||
);
|
||||
|
||||
const router = details.getComponent().router;
|
||||
const { state, addListener, dispatch } = this.props.navigation;
|
||||
const { routes } = details.state;
|
||||
|
||||
const tabDescriptors = {};
|
||||
routes.forEach(route => {
|
||||
const getComponent = () =>
|
||||
router.getComponentForRouteName(route.routeName);
|
||||
|
||||
const childNavigation = addNavigationHelpers({
|
||||
dispatch,
|
||||
state: route,
|
||||
addListener: getChildEventSubscriber(addListener, route.key),
|
||||
});
|
||||
const options = router.getScreenOptions(
|
||||
childNavigation,
|
||||
this.props.screenProps
|
||||
);
|
||||
tabDescriptors[route.key] = {
|
||||
key: route.key,
|
||||
getComponent,
|
||||
options,
|
||||
state: route,
|
||||
navigation: childNavigation,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<DrawerSidebar
|
||||
screenProps={this.props.screenProps}
|
||||
navigation={details.navigation}
|
||||
descriptors={tabDescriptors}
|
||||
navigation={this.props.navigation}
|
||||
descriptors={this.props.descriptors}
|
||||
contentComponent={this.props.navigationConfig.contentComponent}
|
||||
contentOptions={this.props.navigationConfig.contentOptions}
|
||||
drawerPosition={this.props.navigationConfig.drawerPosition}
|
||||
@ -134,9 +72,9 @@ export default class DrawerView extends React.PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const descriptor = Object.values(this.props.descriptors).find(
|
||||
d => d.state.routeName === this.props.navigationConfig.drawerCloseRoute
|
||||
);
|
||||
const { state } = this.props.navigation;
|
||||
const activeKey = state.routes[state.index].key;
|
||||
const descriptor = this.props.descriptors[activeKey];
|
||||
|
||||
const DrawerScreen = descriptor.getComponent();
|
||||
|
||||
|
@ -14,7 +14,6 @@ import {
|
||||
import Card from './StackViewCard';
|
||||
import Header from '../Header/Header';
|
||||
import NavigationActions from '../../NavigationActions';
|
||||
import addNavigationHelpers from '../../addNavigationHelpers';
|
||||
import SceneView from '../SceneView';
|
||||
|
||||
import TransitionConfigs from './StackViewTransitionConfigs';
|
||||
|
Loading…
x
Reference in New Issue
Block a user