Make header configurable (#1220)

* Initial commit

* Remove null mention

Technically, it's possible, though it's not recommended approach. People should use `headerVisible`.

* Update SimpleStack.js

* Updates

* Remove headerVisible

* Fix docs

* Fix flow

* Bring back validation

* Fix import
This commit is contained in:
Mike Grabowski
2017-04-26 13:34:21 +02:00
parent de56f94107
commit 7ef31e0d49
8 changed files with 36 additions and 43 deletions

View File

@@ -96,9 +96,9 @@ Visual options:
Generic title that can be used as a fallback for `headerTitle` and `tabBarLabel`
#### `headerVisible`
#### `header`
True or false to show or hide the header. Only works when `headerMode` is `screen`. Default value is `true`.
React Element or a function that given `HeaderProps` returns a React Element, to display as a header. Setting to `null` hides header.
#### `headerTitle`

View File

@@ -67,12 +67,10 @@ Your `TabNavigator` represents one of the screens in the app, and is nested with
StackNavigator({
route1: { screen: RouteOne },
route2: { screen: MyTabNavigator },
}, {
headerMode: 'screen',
});
```
Now, when `route2` is active, you would like to hide the header. It's easy to hide the header for `route1`, and it should also be easy to do it for `route2`. This is what Default Navigation Options are for - they are simply `navigationOptions` set on a navigator:
Now, when `route2` is active, you would like to change the tint color of a header. It's easy to do it for `route1`, and it should also be easy to do it for `route2`. This is what Default Navigation Options are for - they are simply `navigationOptions` set on a navigator:
```js
const MyTabNavigator = TabNavigator({
@@ -80,25 +78,22 @@ const MyTabNavigator = TabNavigator({
...
}, {
navigationOptions: {
headerVisible: false,
headerTintColor: 'blue',
},
});
```
Note that you can still decide to **also** specify the `navigationOptions` on the screens at the leaf level - e.g. the `ProfileScreen` above. The `navigationOptions` from the screen will be merged key-by-key with the default options coming from the navigator. Whenever both the navigator and screen define the same option (e.g. `headerVisible`), the screen wins. Therefore, you could make the header visible again when `ProfileScreen` is active.
Note that you can still decide to **also** specify the `navigationOptions` on the screens at the leaf level - e.g. the `ProfileScreen` above. The `navigationOptions` from the screen will be merged key-by-key with the default options coming from the navigator. Whenever both the navigator and screen define the same option (e.g. `headerTintColor`), the screen wins. Therefore, you could change the tint color when `ProfileScreen` is active by doing the following:
```js
class ProfileScreen extends React.Component {
static navigationOptions = {
headerVisible: true,
headerTintColor: 'black',
};
...
}
```
The 2nd argument passed to the function are the default values for the `header` as defined on the navigator.
## Navigation Option Reference
List of available navigation options depends on the `navigator` the screen is added to.

View File

@@ -207,7 +207,6 @@ export type NavigationUriAction = {
export type NavigationStackViewConfig = {
mode?: 'card' | 'modal',
headerMode?: HeaderMode,
headerComponent?: ReactClass<HeaderProps<*>>,
cardStyle?: Style,
transitionConfig?: () => TransitionConfig,
onTransitionStart?: () => void,
@@ -215,6 +214,7 @@ export type NavigationStackViewConfig = {
};
export type NavigationStackScreenOptions = NavigationScreenOptions & {
header?: ?(React.Element<*> | ((HeaderProps) => React.Element<*>)),
headerTitle?: string | React.Element<*>,
headerTitleStyle?: Style,
headerTintColor?: string,
@@ -224,7 +224,6 @@ export type NavigationStackScreenOptions = NavigationScreenOptions & {
headerPressColorAndroid?: string,
headerRight?: React.Element<*>,
headerStyle?: Style,
headerVisible?: boolean,
gesturesEnabled?: boolean,
};

View File

@@ -26,7 +26,6 @@ export default (
initialRouteName,
initialRouteParams,
paths,
headerComponent,
headerMode,
mode,
cardStyle,
@@ -52,7 +51,6 @@ export default (
)((props: *) => (
<CardStackTransitioner
{...props}
headerComponent={headerComponent}
headerMode={headerMode}
mode={mode}
cardStyle={cardStyle}

View File

@@ -14,7 +14,7 @@ test('should get config for screen', () => {
class HomeScreen extends Component {
static navigationOptions = ({ navigation }: *) => ({
title: `Welcome ${navigation.state.params ? navigation.state.params.user : 'anonymous'}`,
headerVisible: true,
gesturesEnabled: true,
});
render() {
@@ -25,7 +25,7 @@ test('should get config for screen', () => {
class SettingsScreen extends Component {
static navigationOptions = {
title: 'Settings!!!',
headerVisible: false,
gesturesEnabled: false,
};
render() {
@@ -36,7 +36,7 @@ test('should get config for screen', () => {
class NotificationScreen extends Component {
static navigationOptions = ({ navigation }: *) => ({
title: '42',
headerVisible: navigation.state.params
gesturesEnabled: navigation.state.params
? !navigation.state.params.fullscreen
: true,
});
@@ -83,7 +83,7 @@ test('should get config for screen', () => {
getScreenOptions(
addNavigationHelpers({ state: routes[0], dispatch: () => false }),
{},
).headerVisible,
).gesturesEnabled,
).toEqual(true);
expect(
getScreenOptions(
@@ -95,7 +95,7 @@ test('should get config for screen', () => {
getScreenOptions(
addNavigationHelpers({ state: routes[2], dispatch: () => false }),
{},
).headerVisible,
).gesturesEnabled,
).toEqual(false);
expect(
getScreenOptions(
@@ -107,13 +107,13 @@ test('should get config for screen', () => {
getScreenOptions(
addNavigationHelpers({ state: routes[3], dispatch: () => false }),
{},
).headerVisible,
).gesturesEnabled,
).toEqual(true);
expect(
getScreenOptions(
addNavigationHelpers({ state: routes[4], dispatch: () => false }),
{},
).headerVisible,
).gesturesEnabled,
).toEqual(false);
});
@@ -123,7 +123,7 @@ test('should throw if the route does not exist', () => {
const HomeScreen = () => null;
HomeScreen.navigationOptions = {
title: 'Home screen',
headerVisible: true,
gesturesEnabled: true,
};
const getScreenOptions = createConfigGetter({

View File

@@ -3,7 +3,7 @@ import invariant from 'fbjs/lib/invariant';
import type { NavigationRoute } from '../TypeDefinition';
const deprecatedKeys = ['tabBar', 'header'];
const deprecatedKeys = ['tabBar'];
/**
* Make sure screen options returned by the `getScreenOption`

View File

@@ -13,6 +13,7 @@ import {
} from 'react-native';
import Card from './Card';
import Header from './Header';
import NavigationActions from '../NavigationActions';
import addNavigationHelpers from '../addNavigationHelpers';
import SceneView from './SceneView';
@@ -163,14 +164,24 @@ class CardStack extends Component {
scene: NavigationScene,
headerMode: HeaderMode,
): ?React.Element<*> {
return (
<this.props.headerComponent
{...this.props}
scene={scene}
mode={headerMode}
getScreenDetails={this._getScreenDetails}
/>
);
const { header } = this._getScreenDetails(scene).options;
if (typeof header !== 'undefined' && typeof header !== 'function') {
return header;
}
const renderHeader = header || ((props: *) => <Header {...props} />);
// We need to explicitly exclude `mode` since Flow doesn't see
// mode: headerMode override below and reports prop mismatch
const { mode, ...passProps } = this.props;
return renderHeader({
...passProps,
scene,
mode: headerMode,
getScreenDetails: this._getScreenDetails,
});
}
// eslint-disable-next-line class-methods-use-this
@@ -369,10 +380,6 @@ class CardStack extends Component {
const { screenProps } = this.props;
const headerMode = this._getHeaderMode();
if (headerMode === 'screen') {
const isHeaderHidden = options.headerVisible === false;
const maybeHeader = isHeaderHidden
? null
: this._renderHeader(scene, headerMode);
return (
<View style={styles.container}>
<View style={{ flex: 1 }}>
@@ -383,7 +390,7 @@ class CardStack extends Component {
navigationOptions={options}
/>
</View>
{maybeHeader}
{this._renderHeader(scene, headerMode)}
</View>
);
}

View File

@@ -7,7 +7,6 @@ import CardStack from './CardStack';
import CardStackStyleInterpolator from './CardStackStyleInterpolator';
import Transitioner from './Transitioner';
import TransitionConfigs from './TransitionConfigs';
import Header from './Header';
import type {
NavigationAction,
@@ -28,7 +27,6 @@ const NativeAnimatedModule = NativeModules &&
type Props = {
screenProps?: {},
headerMode: HeaderMode,
headerComponent?: ReactClass<*>,
mode: 'card' | 'modal',
navigation: NavigationScreenProp<NavigationState, NavigationAction>,
router: NavigationRouter<NavigationState, NavigationAction, NavigationStackScreenOptions>,
@@ -44,7 +42,6 @@ type Props = {
type DefaultProps = {
mode: 'card' | 'modal',
headerComponent: ReactClass<*>,
};
class CardStackTransitioner extends Component<DefaultProps, Props, void> {
@@ -52,7 +49,6 @@ class CardStackTransitioner extends Component<DefaultProps, Props, void> {
static defaultProps: DefaultProps = {
mode: 'card',
headerComponent: Header,
};
render() {
@@ -99,7 +95,6 @@ class CardStackTransitioner extends Component<DefaultProps, Props, void> {
_render = (props: NavigationTransitionProps): React.Element<*> => {
const {
screenProps,
headerComponent,
headerMode,
mode,
router,
@@ -110,7 +105,6 @@ class CardStackTransitioner extends Component<DefaultProps, Props, void> {
return (
<CardStack
screenProps={screenProps}
headerComponent={headerComponent}
headerMode={headerMode}
mode={mode}
router={router}