mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-13 22:30:41 +08:00
Compare commits
18 Commits
v1.0.0-bet
...
1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bd25cf372 | ||
|
|
20af8c688e | ||
|
|
b0dccd7e88 | ||
|
|
c69a22f10e | ||
|
|
43a1c5ddbd | ||
|
|
e97d41cccf | ||
|
|
3e4ddc685a | ||
|
|
f62c728593 | ||
|
|
616f9a56f2 | ||
|
|
3b93faa0fc | ||
|
|
333b2e4b68 | ||
|
|
7f50173cf1 | ||
|
|
8432f67529 | ||
|
|
c72b44ce10 | ||
|
|
149f5f5c66 | ||
|
|
85f77fe903 | ||
|
|
d4b759606d | ||
|
|
4d2609f4a8 |
@@ -4,7 +4,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Button, Platform, ScrollView, StatusBar } from 'react-native';
|
||||
import { DrawerNavigator, SafeAreaView } from 'react-navigation';
|
||||
import { StackNavigator, DrawerNavigator, SafeAreaView } from 'react-navigation';
|
||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
import SampleText from './SampleText';
|
||||
|
||||
@@ -16,6 +16,10 @@ const MyNavScreen = ({ navigation, banner }) => (
|
||||
onPress={() => navigation.navigate('DrawerOpen')}
|
||||
title="Open drawer"
|
||||
/>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Email')}
|
||||
title="Open other screen"
|
||||
/>
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
</SafeAreaView>
|
||||
<StatusBar barStyle="default" />
|
||||
@@ -36,6 +40,10 @@ InboxScreen.navigationOptions = {
|
||||
),
|
||||
};
|
||||
|
||||
const EmailScreen = ({ navigation }) => (
|
||||
<MyNavScreen banner={'Email Screen'} navigation={navigation} />
|
||||
);
|
||||
|
||||
const DraftsScreen = ({ navigation }) => (
|
||||
<MyNavScreen banner={'Drafts Screen'} navigation={navigation} />
|
||||
);
|
||||
@@ -46,15 +54,25 @@ DraftsScreen.navigationOptions = {
|
||||
),
|
||||
};
|
||||
|
||||
const InboxStack = StackNavigator({
|
||||
Inbox: { screen: InboxScreen },
|
||||
Email: { screen: EmailScreen },
|
||||
});
|
||||
|
||||
const DraftsStack = StackNavigator({
|
||||
Drafts: { screen: DraftsScreen },
|
||||
Email: { screen: EmailScreen },
|
||||
});
|
||||
|
||||
const DrawerExample = DrawerNavigator(
|
||||
{
|
||||
Inbox: {
|
||||
path: '/',
|
||||
screen: InboxScreen,
|
||||
screen: InboxStack,
|
||||
},
|
||||
Drafts: {
|
||||
path: '/sent',
|
||||
screen: DraftsScreen,
|
||||
screen: DraftsStack,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -24,22 +24,19 @@ class MyNavScreen extends React.Component<MyNavScreenProps> {
|
||||
<SafeAreaView>
|
||||
<SampleText>{banner}</SampleText>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Profile', { name: 'Jane' })}
|
||||
title="Go to a profile screen"
|
||||
onPress={() => navigation.push('Profile', { name: 'Jane' })}
|
||||
title="Push a profile screen"
|
||||
/>
|
||||
<Button
|
||||
onPress={() => navigation.navigate('Photos', { name: 'Jane' })}
|
||||
title="Go to a photos screen"
|
||||
title="Navigate to a photos screen"
|
||||
/>
|
||||
<Button
|
||||
onPress={() =>
|
||||
navigation.navigate('Profile', {
|
||||
name: 'Dog',
|
||||
headerBackImage: require('./assets/dog-back.png'),
|
||||
})
|
||||
}
|
||||
title="Custom back button"
|
||||
onPress={() => navigation.replace('Profile', { name: 'Lucy' })}
|
||||
title="Replace with profile"
|
||||
/>
|
||||
<Button onPress={() => navigation.popToTop()} title="Pop to top" />
|
||||
<Button onPress={() => navigation.pop()} title="Pop" />
|
||||
<Button onPress={() => navigation.goBack(null)} title="Go back" />
|
||||
<StatusBar barStyle="default" />
|
||||
</SafeAreaView>
|
||||
|
||||
86
flow/react-navigation.js
vendored
86
flow/react-navigation.js
vendored
@@ -1,7 +1,6 @@
|
||||
// @flow
|
||||
|
||||
declare module 'react-navigation' {
|
||||
|
||||
/**
|
||||
* First, a bunch of things we would love to import but instead must
|
||||
* reconstruct (mostly copy-pasted).
|
||||
@@ -68,6 +67,8 @@ declare module 'react-navigation' {
|
||||
|
||||
// The action to run inside the sub-router
|
||||
action?: NavigationNavigateAction,
|
||||
|
||||
key?: string,
|
||||
|};
|
||||
|
||||
declare type DeprecatedNavigationNavigateAction = {|
|
||||
@@ -130,8 +131,9 @@ declare module 'react-navigation' {
|
||||
type: 'Reset',
|
||||
index: number,
|
||||
key?: ?string,
|
||||
actions:
|
||||
Array<NavigationNavigateAction | DeprecatedNavigationNavigateAction>,
|
||||
actions: Array<
|
||||
NavigationNavigateAction | DeprecatedNavigationNavigateAction
|
||||
>,
|
||||
|};
|
||||
|
||||
declare export type NavigationUriAction = {|
|
||||
@@ -144,9 +146,37 @@ declare module 'react-navigation' {
|
||||
uri: string,
|
||||
|};
|
||||
|
||||
declare export type NavigationReplaceAction = {|
|
||||
+type: 'Navigation/REPLACE',
|
||||
+key: string,
|
||||
+routeName: string,
|
||||
+params?: NavigationParams,
|
||||
+action?: NavigationNavigateAction,
|
||||
|};
|
||||
declare export type NavigationPopAction = {|
|
||||
+type: 'Navigation/POP',
|
||||
+n?: number,
|
||||
+immediate?: boolean,
|
||||
|};
|
||||
declare export type NavigationPopToTopAction = {|
|
||||
+type: 'Navigation/POP_TO_TOP',
|
||||
+immediate?: boolean,
|
||||
|};
|
||||
declare export type NavigationPushAction = {|
|
||||
+type: 'Navigation/PUSH',
|
||||
+routeName: string,
|
||||
+params?: NavigationParams,
|
||||
+action?: NavigationNavigateAction,
|
||||
+key?: string,
|
||||
|};
|
||||
|
||||
declare export type NavigationAction =
|
||||
| NavigationInitAction
|
||||
| NavigationNavigateAction
|
||||
| NavigationReplaceAction
|
||||
| NavigationPopAction
|
||||
| NavigationPopToTopAction
|
||||
| NavigationPushAction
|
||||
| NavigationBackAction
|
||||
| NavigationSetParamsAction
|
||||
| NavigationResetAction;
|
||||
@@ -209,9 +239,8 @@ declare module 'react-navigation' {
|
||||
params?: NavigationParams,
|
||||
};
|
||||
|
||||
declare export type NavigationStateRoute =
|
||||
& NavigationLeafRoute
|
||||
& NavigationState;
|
||||
declare export type NavigationStateRoute = NavigationLeafRoute &
|
||||
NavigationState;
|
||||
|
||||
/**
|
||||
* Router
|
||||
@@ -291,12 +320,8 @@ declare module 'react-navigation' {
|
||||
Route: NavigationRoute,
|
||||
Options: {},
|
||||
Props: {}
|
||||
> =
|
||||
& React$ComponentType<NavigationNavigatorProps<Options, Route> & Props>
|
||||
& (
|
||||
| {}
|
||||
| { navigationOptions: NavigationScreenConfig<Options> }
|
||||
);
|
||||
> = React$ComponentType<NavigationNavigatorProps<Options, Route> & Props> &
|
||||
({} | { navigationOptions: NavigationScreenConfig<Options> });
|
||||
|
||||
declare export type NavigationNavigator<
|
||||
State: NavigationState,
|
||||
@@ -334,16 +359,18 @@ declare module 'react-navigation' {
|
||||
|
||||
declare export type HeaderMode = 'float' | 'screen' | 'none';
|
||||
|
||||
declare export type HeaderProps = $Shape<NavigationSceneRendererProps & {
|
||||
mode: HeaderMode,
|
||||
router: NavigationRouter<NavigationState, NavigationStackScreenOptions>,
|
||||
getScreenDetails: NavigationScene => NavigationScreenDetails<
|
||||
NavigationStackScreenOptions
|
||||
>,
|
||||
leftInterpolator: (props: NavigationSceneRendererProps) => {},
|
||||
titleInterpolator: (props: NavigationSceneRendererProps) => {},
|
||||
rightInterpolator: (props: NavigationSceneRendererProps) => {},
|
||||
}>;
|
||||
declare export type HeaderProps = $Shape<
|
||||
NavigationSceneRendererProps & {
|
||||
mode: HeaderMode,
|
||||
router: NavigationRouter<NavigationState, NavigationStackScreenOptions>,
|
||||
getScreenDetails: NavigationScene => NavigationScreenDetails<
|
||||
NavigationStackScreenOptions
|
||||
>,
|
||||
leftInterpolator: (props: NavigationSceneRendererProps) => {},
|
||||
titleInterpolator: (props: NavigationSceneRendererProps) => {},
|
||||
rightInterpolator: (props: NavigationSceneRendererProps) => {},
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* Stack Navigator
|
||||
@@ -493,6 +520,18 @@ declare module 'react-navigation' {
|
||||
eventName: string,
|
||||
callback: NavigationEventCallback
|
||||
) => NavigationEventSubscription,
|
||||
push: (
|
||||
routeName: string,
|
||||
params?: NavigationParams,
|
||||
action?: NavigationNavigateAction
|
||||
) => boolean,
|
||||
replace: (
|
||||
routeName: string,
|
||||
params?: NavigationParams,
|
||||
action?: NavigationNavigateAction
|
||||
) => boolean,
|
||||
pop: (n?: number, params?: { immediate?: boolean }) => boolean,
|
||||
popToTop: (params?: { immediate?: boolean }) => boolean,
|
||||
};
|
||||
|
||||
declare export type NavigationNavigatorProps<O: {}, S: {}> = $Shape<{
|
||||
@@ -767,7 +806,7 @@ declare module 'react-navigation' {
|
||||
>(
|
||||
router: NavigationRouter<S, O>,
|
||||
routeConfigs?: NavigationRouteConfigMap,
|
||||
navigatorConfig?: NavigatorConfig,
|
||||
navigatorConfig?: NavigatorConfig
|
||||
): _NavigatorCreator<NavigationViewProps, S, O>;
|
||||
|
||||
declare export function StackNavigator(
|
||||
@@ -1026,6 +1065,7 @@ declare module 'react-navigation' {
|
||||
declare type _TabBarBottomProps = {
|
||||
activeTintColor: string,
|
||||
activeBackgroundColor: string,
|
||||
adaptive?: boolean,
|
||||
inactiveTintColor: string,
|
||||
inactiveBackgroundColor: string,
|
||||
showLabel: boolean,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-navigation",
|
||||
"version": "1.0.0-beta.30",
|
||||
"description": "React Navigation",
|
||||
"version": "1.0.0",
|
||||
"description": "Routing and navigation for your React Native apps",
|
||||
"main": "src/react-navigation.js",
|
||||
"repository": {
|
||||
"url": "git@github.com:react-navigation/react-navigation.git",
|
||||
|
||||
@@ -5,6 +5,7 @@ const POP = 'Navigation/POP';
|
||||
const POP_TO_TOP = 'Navigation/POP_TO_TOP';
|
||||
const PUSH = 'Navigation/PUSH';
|
||||
const RESET = 'Navigation/RESET';
|
||||
const REPLACE = 'Navigation/REPLACE';
|
||||
const SET_PARAMS = 'Navigation/SET_PARAMS';
|
||||
const URI = 'Navigation/URI';
|
||||
const COMPLETE_TRANSITION = 'Navigation/COMPLETE_TRANSITION';
|
||||
@@ -79,6 +80,16 @@ const reset = createAction(RESET, payload => ({
|
||||
actions: payload.actions,
|
||||
}));
|
||||
|
||||
const replace = createAction(REPLACE, payload => ({
|
||||
type: REPLACE,
|
||||
key: payload.key,
|
||||
newKey: payload.newKey,
|
||||
params: payload.params,
|
||||
action: payload.action,
|
||||
routeName: payload.routeName,
|
||||
immediate: payload.immediate,
|
||||
}));
|
||||
|
||||
const setParams = createAction(SET_PARAMS, payload => ({
|
||||
type: SET_PARAMS,
|
||||
key: payload.key,
|
||||
@@ -157,6 +168,7 @@ export default {
|
||||
POP_TO_TOP,
|
||||
PUSH,
|
||||
RESET,
|
||||
REPLACE,
|
||||
SET_PARAMS,
|
||||
URI,
|
||||
COMPLETE_TRANSITION,
|
||||
@@ -169,6 +181,7 @@ export default {
|
||||
popToTop,
|
||||
push,
|
||||
reset,
|
||||
replace,
|
||||
setParams,
|
||||
uri,
|
||||
completeTransition,
|
||||
|
||||
@@ -65,5 +65,15 @@ export default function(navigation) {
|
||||
navigation.dispatch(
|
||||
NavigationActions.push({ routeName, params, action })
|
||||
),
|
||||
|
||||
replace: (routeName, params, action) =>
|
||||
navigation.dispatch(
|
||||
NavigationActions.replace({
|
||||
routeName,
|
||||
params,
|
||||
action,
|
||||
key: navigation.state.key,
|
||||
})
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,9 +24,10 @@ export default function createNavigationContainer(Component) {
|
||||
|
||||
this._validateProps(props);
|
||||
|
||||
this._initialAction = NavigationActions.init();
|
||||
this.state = {
|
||||
nav: this._isStateful()
|
||||
? Component.router.getStateForAction(NavigationActions.init())
|
||||
? Component.router.getStateForAction(this._initialAction)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
@@ -134,6 +135,15 @@ export default function createNavigationContainer(Component) {
|
||||
Linking.addEventListener('url', this._handleOpenURL);
|
||||
|
||||
Linking.getInitialURL().then(url => url && this._handleOpenURL({ url }));
|
||||
|
||||
this._actionEventSubscribers.forEach(subscriber =>
|
||||
subscriber({
|
||||
type: 'action',
|
||||
action: this._initialAction,
|
||||
state: this.state.nav,
|
||||
lastState: null,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -154,7 +164,6 @@ export default function createNavigationContainer(Component) {
|
||||
const nav = Component.router.getStateForAction(action, oldNav);
|
||||
const dispatchActionEvents = () => {
|
||||
this._actionEventSubscribers.forEach(subscriber =>
|
||||
// $FlowFixMe - Payload should probably understand generic state type
|
||||
subscriber({
|
||||
type: 'action',
|
||||
action,
|
||||
|
||||
@@ -100,6 +100,7 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
|
||||
"pop": [Function],
|
||||
"popToTop": [Function],
|
||||
"push": [Function],
|
||||
"replace": [Function],
|
||||
"setParams": [Function],
|
||||
"state": Object {
|
||||
"index": 0,
|
||||
@@ -337,6 +338,7 @@ exports[`StackNavigator renders successfully 1`] = `
|
||||
"pop": [Function],
|
||||
"popToTop": [Function],
|
||||
"push": [Function],
|
||||
"replace": [Function],
|
||||
"setParams": [Function],
|
||||
"state": Object {
|
||||
"index": 0,
|
||||
|
||||
@@ -105,68 +105,81 @@ exports[`TabNavigator renders successfully 1`] = `
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"flex": 1,
|
||||
"justifyContent": "flex-end",
|
||||
}
|
||||
}
|
||||
testID={undefined}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexGrow": 1,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
},
|
||||
Object {
|
||||
"flexDirection": "column",
|
||||
"justifyContent": "flex-end",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"bottom": 0,
|
||||
"justifyContent": "center",
|
||||
"left": 0,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
"flexGrow": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"bottom": 0,
|
||||
"justifyContent": "center",
|
||||
"left": 0,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"bottom": 0,
|
||||
"justifyContent": "center",
|
||||
"left": 0,
|
||||
"opacity": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<Text
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
collapsable={undefined}
|
||||
ellipsizeMode="tail"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"bottom": 0,
|
||||
"justifyContent": "center",
|
||||
"left": 0,
|
||||
"opacity": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"color": "rgba(52, 120, 246, 1)",
|
||||
"fontSize": 10,
|
||||
"marginBottom": 1.5,
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
/>
|
||||
>
|
||||
Welcome anonymous
|
||||
</Text>
|
||||
</View>
|
||||
<Text
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
collapsable={undefined}
|
||||
ellipsizeMode="tail"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "transparent",
|
||||
"color": "rgba(52, 120, 246, 1)",
|
||||
"fontSize": 10,
|
||||
"marginBottom": 1.5,
|
||||
"marginLeft": 0,
|
||||
"marginTop": 0,
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
Welcome anonymous
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -81,7 +81,6 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
});
|
||||
|
||||
paths = Object.entries(pathsByRouteNames);
|
||||
/* $FlowFixMe */
|
||||
paths.sort((a: [string, *], b: [string, *]) => b[1].priority - a[1].priority);
|
||||
|
||||
return {
|
||||
@@ -172,10 +171,15 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
}
|
||||
}
|
||||
|
||||
//Handle pop-to-top behavior. Make sure this happens after children have had a chance to handle the action, so that the inner stack pops to top first.
|
||||
// Handle pop-to-top behavior. Make sure this happens after children have had a chance to handle the action, so that the inner stack pops to top first.
|
||||
if (action.type === NavigationActions.POP_TO_TOP) {
|
||||
if (state.index !== 0) {
|
||||
if (state.index === 0) {
|
||||
return {
|
||||
...state,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...state,
|
||||
isTransitioning: action.immediate !== true,
|
||||
index: 0,
|
||||
routes: [state.routes[0]],
|
||||
@@ -184,6 +188,31 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
return state;
|
||||
}
|
||||
|
||||
// Handle replace action
|
||||
if (action.type === NavigationActions.REPLACE) {
|
||||
const routeIndex = state.routes.findIndex(r => r.key === action.key);
|
||||
// Only replace if the key matches one of our routes
|
||||
if (routeIndex !== -1) {
|
||||
const childRouter = childRouters[action.routeName];
|
||||
let childState = {};
|
||||
if (childRouter) {
|
||||
const childAction =
|
||||
action.action ||
|
||||
NavigationActions.init({ params: action.params });
|
||||
childState = childRouter.getStateForAction(childAction);
|
||||
}
|
||||
const routes = [...state.routes];
|
||||
routes[routeIndex] = {
|
||||
params: action.params,
|
||||
// merge the child state in this order to allow params override
|
||||
...childState,
|
||||
key: action.newKey || generateKey(),
|
||||
routeName: action.routeName,
|
||||
};
|
||||
return { ...state, routes };
|
||||
}
|
||||
}
|
||||
|
||||
// Handle explicit push navigation action. Make sure this happens after children have had a chance to handle the action
|
||||
if (
|
||||
behavesLikePushAction(action) &&
|
||||
@@ -236,6 +265,7 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
action.action || NavigationActions.init({ params: action.params });
|
||||
route = {
|
||||
params: action.params,
|
||||
// merge the child state in this order to allow params override
|
||||
...childRouter.getStateForAction(childAction),
|
||||
key,
|
||||
routeName: action.routeName,
|
||||
@@ -251,6 +281,13 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
...StateUtils.push(state, route),
|
||||
isTransitioning: action.immediate !== true,
|
||||
};
|
||||
} else if (
|
||||
action.type === NavigationActions.PUSH &&
|
||||
childRouters[action.routeName] === undefined
|
||||
) {
|
||||
return {
|
||||
...state,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -365,6 +402,7 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
const backRoute = state.routes.find(route => route.key === key);
|
||||
backRouteIndex = state.routes.indexOf(backRoute);
|
||||
}
|
||||
|
||||
if (backRouteIndex > 0) {
|
||||
return {
|
||||
...state,
|
||||
@@ -372,6 +410,13 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
index: backRouteIndex - 1,
|
||||
isTransitioning: immediate !== true,
|
||||
};
|
||||
} else if (
|
||||
backRouteIndex === 0 &&
|
||||
action.type === NavigationActions.POP
|
||||
) {
|
||||
return {
|
||||
...state,
|
||||
};
|
||||
}
|
||||
}
|
||||
return state;
|
||||
|
||||
@@ -366,6 +366,97 @@ describe('StackRouter', () => {
|
||||
expect(pushedState.routes[1].routes[1].routeName).toEqual('qux');
|
||||
});
|
||||
|
||||
test('pop does not bubble up', () => {
|
||||
const ChildNavigator = () => <div />;
|
||||
ChildNavigator.router = StackRouter({
|
||||
Baz: { screen: () => <div /> },
|
||||
Qux: { screen: () => <div /> },
|
||||
});
|
||||
const router = StackRouter({
|
||||
Foo: { screen: () => <div /> },
|
||||
Bar: { screen: ChildNavigator },
|
||||
});
|
||||
|
||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||
const state2 = router.getStateForAction(
|
||||
{
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'Bar',
|
||||
key: 'StackRouterRoot',
|
||||
},
|
||||
state
|
||||
);
|
||||
const barKey = state2.routes[1].routes[0].key;
|
||||
const state3 = router.getStateForAction(
|
||||
{
|
||||
type: NavigationActions.POP,
|
||||
},
|
||||
state2
|
||||
);
|
||||
expect(state3 && state3.index).toEqual(1);
|
||||
expect(state3 && state3.routes[1].index).toEqual(0);
|
||||
});
|
||||
|
||||
test('push does not bubble up', () => {
|
||||
const ChildNavigator = () => <div />;
|
||||
ChildNavigator.router = StackRouter({
|
||||
Baz: { screen: () => <div /> },
|
||||
Qux: { screen: () => <div /> },
|
||||
});
|
||||
const router = StackRouter({
|
||||
Foo: { screen: () => <div /> },
|
||||
Bar: { screen: ChildNavigator },
|
||||
Bad: { screen: () => <div /> },
|
||||
});
|
||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||
const state2 = router.getStateForAction(
|
||||
{
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'Bar',
|
||||
},
|
||||
state
|
||||
);
|
||||
const barKey = state2.routes[1].routes[0].key;
|
||||
const state3 = router.getStateForAction(
|
||||
{
|
||||
type: NavigationActions.PUSH,
|
||||
routeName: 'Bad',
|
||||
},
|
||||
state2
|
||||
);
|
||||
expect(state3 && state3.index).toEqual(1);
|
||||
expect(state3 && state3.routes.length).toEqual(2);
|
||||
});
|
||||
|
||||
test('popToTop does not bubble up', () => {
|
||||
const ChildNavigator = () => <div />;
|
||||
ChildNavigator.router = StackRouter({
|
||||
Baz: { screen: () => <div /> },
|
||||
Qux: { screen: () => <div /> },
|
||||
});
|
||||
const router = StackRouter({
|
||||
Foo: { screen: () => <div /> },
|
||||
Bar: { screen: ChildNavigator },
|
||||
});
|
||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||
const state2 = router.getStateForAction(
|
||||
{
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'Bar',
|
||||
},
|
||||
state
|
||||
);
|
||||
const barKey = state2.routes[1].routes[0].key;
|
||||
const state3 = router.getStateForAction(
|
||||
{
|
||||
type: NavigationActions.POP_TO_TOP,
|
||||
},
|
||||
state2
|
||||
);
|
||||
expect(state3 && state3.index).toEqual(1);
|
||||
expect(state3 && state3.routes[1].index).toEqual(0);
|
||||
});
|
||||
|
||||
test('popToTop works as expected', () => {
|
||||
const TestRouter = StackRouter({
|
||||
foo: { screen: () => <div /> },
|
||||
@@ -515,6 +606,41 @@ describe('StackRouter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('Replace action works', () => {
|
||||
const TestRouter = StackRouter({
|
||||
foo: { screen: () => <div /> },
|
||||
bar: { screen: () => <div /> },
|
||||
});
|
||||
const initState = TestRouter.getStateForAction(
|
||||
NavigationActions.navigate({ routeName: 'foo' })
|
||||
);
|
||||
const replacedState = TestRouter.getStateForAction(
|
||||
NavigationActions.replace({
|
||||
routeName: 'bar',
|
||||
params: { meaning: 42 },
|
||||
key: initState.routes[0].key,
|
||||
}),
|
||||
initState
|
||||
);
|
||||
expect(replacedState.index).toEqual(0);
|
||||
expect(replacedState.routes.length).toEqual(1);
|
||||
expect(replacedState.routes[0].key).not.toEqual(initState.routes[0].key);
|
||||
expect(replacedState.routes[0].routeName).toEqual('bar');
|
||||
expect(replacedState.routes[0].params.meaning).toEqual(42);
|
||||
const replacedState2 = TestRouter.getStateForAction(
|
||||
NavigationActions.replace({
|
||||
routeName: 'bar',
|
||||
key: initState.routes[0].key,
|
||||
newKey: 'wow',
|
||||
}),
|
||||
initState
|
||||
);
|
||||
expect(replacedState2.index).toEqual(0);
|
||||
expect(replacedState2.routes.length).toEqual(1);
|
||||
expect(replacedState2.routes[0].key).toEqual('wow');
|
||||
expect(replacedState2.routes[0].routeName).toEqual('bar');
|
||||
});
|
||||
|
||||
test('Handles push transition logic with completion action', () => {
|
||||
const FooScreen = () => <div />;
|
||||
const BarScreen = () => <div />;
|
||||
@@ -1161,9 +1287,7 @@ describe('StackRouter', () => {
|
||||
};
|
||||
const { path, params } = router.getPathAndParamsForState(state);
|
||||
expect(path).toEqual('baz/321');
|
||||
/* $FlowFixMe: params.id has to exist */
|
||||
expect(params.id).toEqual('123');
|
||||
/* $FlowFixMe: params.bazId has to exist */
|
||||
expect(params.bazId).toEqual('321');
|
||||
}
|
||||
|
||||
@@ -1323,9 +1447,7 @@ test('Handles deep navigate completion action', () => {
|
||||
);
|
||||
expect(state2 && state2.index).toEqual(0);
|
||||
expect(state2 && state2.isTransitioning).toEqual(false);
|
||||
/* $FlowFixMe */
|
||||
expect(state2 && state2.routes[0].index).toEqual(1);
|
||||
/* $FlowFixMe */
|
||||
expect(state2 && state2.routes[0].isTransitioning).toEqual(true);
|
||||
expect(!!key).toEqual(true);
|
||||
const state3 = router.getStateForAction(
|
||||
@@ -1336,8 +1458,6 @@ test('Handles deep navigate completion action', () => {
|
||||
);
|
||||
expect(state3 && state3.index).toEqual(0);
|
||||
expect(state3 && state3.isTransitioning).toEqual(false);
|
||||
/* $FlowFixMe */
|
||||
expect(state3 && state3.routes[0].index).toEqual(1);
|
||||
/* $FlowFixMe */
|
||||
expect(state3 && state3.routes[0].isTransitioning).toEqual(false);
|
||||
});
|
||||
|
||||
@@ -27,9 +27,6 @@ class Header extends React.PureComponent {
|
||||
};
|
||||
|
||||
static get HEIGHT() {
|
||||
console.warn(
|
||||
'Header.HEIGHT is deprecated and will be removed before react-navigation comes out of beta.'
|
||||
);
|
||||
return APPBAR_HEIGHT + STATUSBAR_HEIGHT;
|
||||
}
|
||||
|
||||
@@ -292,7 +289,8 @@ class Header extends React.PureComponent {
|
||||
|
||||
const { options } = this.props.getScreenDetails(scene);
|
||||
const { headerStyle } = options;
|
||||
const appBarHeight = Platform.OS === 'ios' ? (isLandscape ? 32 : 44) : 56;
|
||||
const appBarHeight =
|
||||
Platform.OS === 'ios' ? (isLandscape && !Platform.isPad ? 32 : 44) : 56;
|
||||
const containerStyles = [
|
||||
styles.container,
|
||||
{
|
||||
|
||||
@@ -13,7 +13,8 @@ import withOrientation from '../withOrientation';
|
||||
|
||||
const majorVersion = parseInt(Platform.Version, 10);
|
||||
const isIos = Platform.OS === 'ios';
|
||||
const useHorizontalTabs = majorVersion >= 11 && isIos;
|
||||
const isIOS11 = majorVersion >= 11 && isIos;
|
||||
const defaultMaxTabBarItemWidth = 125;
|
||||
|
||||
class TabBarBottom extends React.PureComponent {
|
||||
// See https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/UIKitUICatalog/UITabBar.html
|
||||
@@ -25,6 +26,7 @@ class TabBarBottom extends React.PureComponent {
|
||||
showLabel: true,
|
||||
showIcon: true,
|
||||
allowFontScaling: true,
|
||||
adaptive: isIOS11,
|
||||
};
|
||||
|
||||
_renderLabel = scene => {
|
||||
@@ -56,19 +58,18 @@ class TabBarBottom extends React.PureComponent {
|
||||
|
||||
const tintColor = scene.focused ? activeTintColor : inactiveTintColor;
|
||||
const label = this.props.getLabel({ ...scene, tintColor });
|
||||
let marginLeft = 0;
|
||||
if (isLandscape && showIcon && useHorizontalTabs) {
|
||||
marginLeft = LABEL_LEFT_MARGIN;
|
||||
}
|
||||
let marginTop = 0;
|
||||
if (!isLandscape && showIcon && useHorizontalTabs) {
|
||||
marginTop = LABEL_TOP_MARGIN;
|
||||
}
|
||||
|
||||
if (typeof label === 'string') {
|
||||
return (
|
||||
<Animated.Text
|
||||
style={[styles.label, { color, marginLeft, marginTop }, labelStyle]}
|
||||
style={[
|
||||
styles.label,
|
||||
{ color },
|
||||
showIcon && this._shouldUseHorizontalTabs()
|
||||
? styles.labelBeside
|
||||
: styles.labelBeneath,
|
||||
labelStyle,
|
||||
]}
|
||||
allowFontScaling={allowFontScaling}
|
||||
>
|
||||
{label}
|
||||
@@ -104,7 +105,7 @@ class TabBarBottom extends React.PureComponent {
|
||||
inactiveTintColor={inactiveTintColor}
|
||||
renderIcon={renderIcon}
|
||||
scene={scene}
|
||||
style={showLabel && useHorizontalTabs ? {} : styles.icon}
|
||||
style={showLabel && this._shouldUseHorizontalTabs() ? {} : styles.icon}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -115,6 +116,64 @@ class TabBarBottom extends React.PureComponent {
|
||||
return testIDProps;
|
||||
};
|
||||
|
||||
_tabItemMaxWidth() {
|
||||
const { tabStyle, layout } = this.props;
|
||||
let maxTabBarItemWidth;
|
||||
|
||||
const flattenedTabStyle = StyleSheet.flatten(tabStyle);
|
||||
|
||||
if (flattenedTabStyle) {
|
||||
if (typeof flattenedTabStyle.width === 'number') {
|
||||
maxTabBarItemWidth = flattenedTabStyle.width;
|
||||
} else if (
|
||||
typeof flattenedTabStyle.width === 'string' &&
|
||||
flattenedTabStyle.endsWith('%')
|
||||
) {
|
||||
const width = parseFloat(flattenedTabStyle.width);
|
||||
if (Number.isFinite(width)) {
|
||||
maxTabBarItemWidth = layout.width * (width / 100);
|
||||
}
|
||||
} else if (typeof flattenedTabStyle.maxWidth === 'number') {
|
||||
maxTabBarItemWidth = flattenedTabStyle.maxWidth;
|
||||
} else if (
|
||||
typeof flattenedTabStyle.maxWidth === 'string' &&
|
||||
flattenedTabStyle.endsWith('%')
|
||||
) {
|
||||
const width = parseFloat(flattenedTabStyle.maxWidth);
|
||||
if (Number.isFinite(width)) {
|
||||
maxTabBarItemWidth = layout.width * (width / 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!maxTabBarItemWidth) {
|
||||
maxTabBarItemWidth = defaultMaxTabBarItemWidth;
|
||||
}
|
||||
|
||||
return maxTabBarItemWidth;
|
||||
}
|
||||
|
||||
_shouldUseHorizontalTabs() {
|
||||
const { routes } = this.props.navigation.state;
|
||||
const { isLandscape, layout, adaptive, tabStyle } = this.props;
|
||||
|
||||
if (!adaptive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let tabBarWidth = layout.width;
|
||||
if (tabBarWidth === 0) {
|
||||
return Platform.isPad;
|
||||
}
|
||||
|
||||
if (!Platform.isPad) {
|
||||
return isLandscape;
|
||||
} else {
|
||||
const maxTabBarItemWidth = this._tabItemMaxWidth();
|
||||
return routes.length * maxTabBarItemWidth <= tabBarWidth;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
position,
|
||||
@@ -136,9 +195,9 @@ class TabBarBottom extends React.PureComponent {
|
||||
|
||||
const tabBarStyle = [
|
||||
styles.tabBar,
|
||||
isLandscape && useHorizontalTabs
|
||||
? styles.tabBarLandscape
|
||||
: styles.tabBarPortrait,
|
||||
this._shouldUseHorizontalTabs() && !Platform.isPad
|
||||
? styles.tabBarCompact
|
||||
: styles.tabBarRegular,
|
||||
style,
|
||||
];
|
||||
|
||||
@@ -178,17 +237,19 @@ class TabBarBottom extends React.PureComponent {
|
||||
: jumpToIndex(index)
|
||||
}
|
||||
>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.tab,
|
||||
isLandscape && useHorizontalTabs && styles.tabLandscape,
|
||||
!isLandscape && useHorizontalTabs && styles.tabPortrait,
|
||||
{ backgroundColor },
|
||||
tabStyle,
|
||||
]}
|
||||
>
|
||||
{this._renderIcon(scene)}
|
||||
{this._renderLabel(scene)}
|
||||
<Animated.View style={[styles.tab, { backgroundColor }]}>
|
||||
<View
|
||||
style={[
|
||||
styles.tab,
|
||||
this._shouldUseHorizontalTabs()
|
||||
? styles.tabLandscape
|
||||
: styles.tabPortrait,
|
||||
tabStyle,
|
||||
]}
|
||||
>
|
||||
{this._renderIcon(scene)}
|
||||
{this._renderLabel(scene)}
|
||||
</View>
|
||||
</Animated.View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
@@ -199,8 +260,6 @@ class TabBarBottom extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
const LABEL_LEFT_MARGIN = 20;
|
||||
const LABEL_TOP_MARGIN = 15;
|
||||
const styles = StyleSheet.create({
|
||||
tabBar: {
|
||||
backgroundColor: '#F7F7F7', // Default background color in iOS 10
|
||||
@@ -208,16 +267,15 @@ const styles = StyleSheet.create({
|
||||
borderTopColor: 'rgba(0, 0, 0, .3)',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
tabBarLandscape: {
|
||||
tabBarCompact: {
|
||||
height: 29,
|
||||
},
|
||||
tabBarPortrait: {
|
||||
tabBarRegular: {
|
||||
height: 49,
|
||||
},
|
||||
tab: {
|
||||
flex: 1,
|
||||
alignItems: isIos ? 'center' : 'stretch',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
tabPortrait: {
|
||||
justifyContent: 'flex-end',
|
||||
@@ -232,9 +290,15 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
label: {
|
||||
textAlign: 'center',
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
labelBeneath: {
|
||||
fontSize: 10,
|
||||
marginBottom: 1.5,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
labelBeside: {
|
||||
fontSize: 13,
|
||||
marginLeft: 20,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -61,68 +61,81 @@ exports[`TabBarBottom renders successfully 1`] = `
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"flex": 1,
|
||||
"justifyContent": "flex-end",
|
||||
}
|
||||
}
|
||||
testID={undefined}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexGrow": 1,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
},
|
||||
Object {
|
||||
"flexDirection": "column",
|
||||
"justifyContent": "flex-end",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"bottom": 0,
|
||||
"justifyContent": "center",
|
||||
"left": 0,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
"flexGrow": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"bottom": 0,
|
||||
"justifyContent": "center",
|
||||
"left": 0,
|
||||
"opacity": 1,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
collapsable={undefined}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"bottom": 0,
|
||||
"justifyContent": "center",
|
||||
"left": 0,
|
||||
"opacity": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<Text
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
collapsable={undefined}
|
||||
ellipsizeMode="tail"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"bottom": 0,
|
||||
"justifyContent": "center",
|
||||
"left": 0,
|
||||
"opacity": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"color": "rgba(52, 120, 246, 1)",
|
||||
"fontSize": 10,
|
||||
"marginBottom": 1.5,
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
/>
|
||||
>
|
||||
s1
|
||||
</Text>
|
||||
</View>
|
||||
<Text
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
collapsable={undefined}
|
||||
ellipsizeMode="tail"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "transparent",
|
||||
"color": "rgba(52, 120, 246, 1)",
|
||||
"fontSize": 10,
|
||||
"marginBottom": 1.5,
|
||||
"marginLeft": 0,
|
||||
"marginTop": 0,
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
s1
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -227,6 +240,7 @@ exports[`TabBarBottom renders successfully 1`] = `
|
||||
"pop": [Function],
|
||||
"popToTop": [Function],
|
||||
"push": [Function],
|
||||
"replace": [Function],
|
||||
"setParams": [Function],
|
||||
"state": Object {
|
||||
"key": "s1",
|
||||
|
||||
Reference in New Issue
Block a user