Skip to content

Commit

Permalink
Add headerLayoutPreset, add config for back button title visibility a…
Browse files Browse the repository at this point in the history
…nd make it have reasonable defaults, better back button ripple on Android (#4588)
  • Loading branch information
brentvatne committed Jul 20, 2018
1 parent 3c36db4 commit cd3707d
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 89 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- `headerLayoutPreset: 'center' | 'left'` to provide an easy solution for [questions like this](https://github.com/react-navigation/react-navigation/issues/4615).
- `headerBackTitleEnabled` - this configuration option for stack navigator allows you to force back button titles to either be rendered or not (if you disagree with defaults for your platform and layout preset).

### Fixed
- Android back button ripple is now appropriately sized (fixes [#3955](https://github.com/react-navigation/react-navigation/issues/3955)).


## [2.8.0] - [2018-07-19](https://github.com/react-navigation/react-navigation/releases/tag/2.8.0)
### Added
Expand Down
41 changes: 24 additions & 17 deletions examples/NavigationPlayground/js/SimpleStack.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type {
} from 'react-navigation';

import * as React from 'react';
import { ScrollView, StatusBar } from 'react-native';
import { Platform, ScrollView, StatusBar } from 'react-native';
import {
createStackNavigator,
SafeAreaView,
Expand All @@ -24,6 +24,8 @@ import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
import { HeaderButtons } from './commonComponents/HeaderButtons';

const DEBUG = false;

type MyNavScreenProps = {
navigation: NavigationScreenProp<NavigationState>,
banner: React.Node,
Expand Down Expand Up @@ -133,16 +135,16 @@ class MyHomeScreen extends React.Component<MyHomeScreenProps> {
this._s3.remove();
}
_onWF = a => {
console.log('_willFocus HomeScreen', a);
DEBUG && console.log('_willFocus HomeScreen', a);
};
_onDF = a => {
console.log('_didFocus HomeScreen', a);
DEBUG && console.log('_didFocus HomeScreen', a);
};
_onWB = a => {
console.log('_willBlur HomeScreen', a);
DEBUG && console.log('_willBlur HomeScreen', a);
};
_onDB = a => {
console.log('_didBlur HomeScreen', a);
DEBUG && console.log('_didBlur HomeScreen', a);
};

render() {
Expand Down Expand Up @@ -231,18 +233,23 @@ MyProfileScreen.navigationOptions = props => {
};
};

const SimpleStack = createStackNavigator({
Home: {
screen: MyHomeScreen,
},
Profile: {
path: 'people/:name',
screen: MyProfileScreen,
},
Photos: {
path: 'photos/:name',
screen: MyPhotosScreen,
const SimpleStack = createStackNavigator(
{
Home: {
screen: MyHomeScreen,
},
Profile: {
path: 'people/:name',
screen: MyProfileScreen,
},
Photos: {
path: 'photos/:name',
screen: MyPhotosScreen,
},
},
});
{
// headerLayoutPreset: 'center',
}
);

export default SimpleStack;
104 changes: 69 additions & 35 deletions src/views/Header/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,47 @@ import withOrientation from '../withOrientation';

const APPBAR_HEIGHT = Platform.OS === 'ios' ? 44 : 56;
const STATUSBAR_HEIGHT = Platform.OS === 'ios' ? 20 : 0;
const TITLE_OFFSET = Platform.OS === 'ios' ? 70 : 56;

// These can be adjusted by using headerTitleContainerStyle on navigationOptions
const TITLE_OFFSET_CENTER_ALIGN = Platform.OS === 'ios' ? 70 : 56;
const TITLE_OFFSET_LEFT_ALIGN = Platform.OS === 'ios' ? 20 : 56;

const getTitleOffsets = (
layoutPreset,
forceBackTitle,
hasLeftComponent,
hasRightComponent
) => {
if (layoutPreset === 'left') {
// Maybe at some point we should do something different if the back title is
// explicitly enabled, for now people can control it manually

let style = {
left: TITLE_OFFSET_LEFT_ALIGN,
right: TITLE_OFFSET_LEFT_ALIGN,
};

if (!hasLeftComponent) {
style.left = 0;
}
if (!hasRightComponent) {
style.right = 0;
}

return style;
} else if (layoutPreset === 'center') {
let style = {
left: TITLE_OFFSET_CENTER_ALIGN,
right: TITLE_OFFSET_CENTER_ALIGN,
};
if (!hasLeftComponent && !hasRightComponent) {
style.left = 0;
style.right = 0;
}

return style;
}
};

const getAppBarHeight = isLandscape => {
return Platform.OS === 'ios'
Expand Down Expand Up @@ -92,6 +132,7 @@ class Header extends React.PureComponent {
}

_renderTitleComponent = props => {
const { layoutPreset } = this.props;
const { options } = props.scene.descriptor;
const headerTitle = options.headerTitle;
if (React.isValidElement(headerTitle)) {
Expand All @@ -103,10 +144,10 @@ class Header extends React.PureComponent {
const color = options.headerTintColor;
const allowFontScaling = options.headerTitleAllowFontScaling;

// On iOS, width of left/right components depends on the calculated
// size of the title.
const onLayoutIOS =
Platform.OS === 'ios'
// When title is centered, the width of left/right components depends on the
// calculated size of the title.
const onLayout =
layoutPreset === 'center'
? e => {
this.setState({
widths: {
Expand All @@ -117,18 +158,24 @@ class Header extends React.PureComponent {
}
: undefined;

const RenderedHeaderTitle =
const HeaderTitleComponent =
headerTitle && typeof headerTitle !== 'string'
? headerTitle
: HeaderTitle;
return (
<RenderedHeaderTitle
onLayout={onLayoutIOS}
<HeaderTitleComponent
onLayout={onLayout}
allowFontScaling={allowFontScaling == null ? true : allowFontScaling}
style={[color ? { color } : null, titleStyle]}
style={[
color ? { color } : null,
layoutPreset === 'center'
? { textAlign: 'center' }
: { textAlign: 'left' },
titleStyle,
]}
>
{titleString}
</RenderedHeaderTitle>
</HeaderTitleComponent>
);
};

Expand Down Expand Up @@ -167,7 +214,9 @@ class Header extends React.PureComponent {
backImage={options.headerBackImage}
title={backButtonTitle}
truncatedTitle={truncatedBackButtonTitle}
backTitleVisible={this.props.backTitleVisible}
titleStyle={options.headerBackTitleStyle}
layoutPreset={this.props.layoutPreset}
width={width}
/>
);
Expand Down Expand Up @@ -251,27 +300,16 @@ class Header extends React.PureComponent {
}

_renderTitle(props, options) {
let style = {};
const { transitionPreset } = this.props;

if (Platform.OS === 'android') {
if (!options.hasLeftComponent) {
style.left = 0;
}
if (!options.hasRightComponent) {
style.right = 0;
}
} else if (
Platform.OS === 'ios' &&
!options.hasLeftComponent &&
!options.hasRightComponent
) {
style.left = 0;
style.right = 0;
}
if (options.headerTitleContainerStyle) {
style = [style, options.headerTitleContainerStyle];
}
const { layoutPreset, transitionPreset } = this.props;
let style = [
{ justifyContent: layoutPreset === 'center' ? 'center' : 'flex-start' },
getTitleOffsets(
layoutPreset,
options.hasLeftComponent,
options.hasRightComponent
),
options.headerTitleContainerStyle,
];

return this._renderSubView(
{ ...props, style },
Expand Down Expand Up @@ -386,7 +424,6 @@ class Header extends React.PureComponent {
styles[name],
props.style,
styleInterpolator({
// todo: determine if we really need to splat all this.props
...this.props,
...props,
}),
Expand Down Expand Up @@ -636,12 +673,9 @@ const styles = StyleSheet.create({
title: {
bottom: 0,
top: 0,
left: TITLE_OFFSET,
right: TITLE_OFFSET,
position: 'absolute',
alignItems: 'center',
flexDirection: 'row',
justifyContent: Platform.OS === 'ios' ? 'center' : 'flex-start',
},
left: {
left: 0,
Expand Down
85 changes: 54 additions & 31 deletions src/views/Header/HeaderBackButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,38 @@ class HeaderBackButton extends React.PureComponent {
}

render() {
const { onPress, pressColorAndroid, layoutPreset, title } = this.props;

let button = (
<TouchableItem
accessibilityComponentType="button"
accessibilityLabel={title}
accessibilityTraits="button"
testID="header-back"
delayPressIn={0}
onPress={onPress}
pressColor={pressColorAndroid}
style={styles.container}
borderless
>
<View style={styles.container}>
{this._renderBackImage()}
{this._maybeRenderTitle()}
</View>
</TouchableItem>
);

if (Platform.OS === 'android') {
return <View style={styles.androidButtonWrapper}>{button}</View>;
} else {
return button;
}
}

_maybeRenderTitle() {
const {
onPress,
pressColorAndroid,
layoutPreset,
backTitleVisible,
width,
title,
titleStyle,
Expand All @@ -79,41 +108,35 @@ class HeaderBackButton extends React.PureComponent {

const backButtonTitle = renderTruncated ? truncatedTitle : title;

// If the left preset is used and we aren't on Android, then we
// default to disabling the label
const titleDefaultsToDisabled =
layoutPreset === 'left' ||
Platform.OS === 'android' ||
typeof backButtonTitle !== 'string';

// If the title is explicitly enabled then we respect that
if (titleDefaultsToDisabled && !backTitleVisible) {
return null;
}

return (
<TouchableItem
accessibilityComponentType="button"
accessibilityLabel={backButtonTitle}
accessibilityTraits="button"
testID="header-back"
delayPressIn={0}
onPress={onPress}
pressColor={pressColorAndroid}
style={styles.container}
borderless
<Text
onLayout={this._onTextLayout}
style={[styles.title, !!tintColor && { color: tintColor }, titleStyle]}
numberOfLines={1}
>
<View style={styles.container}>
{this._renderBackImage()}
{Platform.OS === 'ios' &&
typeof backButtonTitle === 'string' && (
<Text
onLayout={this._onTextLayout}
style={[
styles.title,
!!tintColor && { color: tintColor },
titleStyle,
]}
numberOfLines={1}
>
{backButtonTitle}
</Text>
)}
</View>
</TouchableItem>
{backButtonTitle}
</Text>
);
}
}

const styles = StyleSheet.create({
androidButtonWrapper: {
margin: 13,
backgroundColor: 'transparent',
},
container: {
alignItems: 'center',
flexDirection: 'row',
Expand All @@ -137,7 +160,7 @@ const styles = StyleSheet.create({
: {
height: 24,
width: 24,
margin: 16,
margin: 3,
resizeMode: 'contain',
transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }],
},
Expand Down
1 change: 0 additions & 1 deletion src/views/Header/HeaderTitle.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const styles = StyleSheet.create({
fontSize: Platform.OS === 'ios' ? 17 : 20,
fontWeight: Platform.OS === 'ios' ? '700' : '500',
color: 'rgba(0, 0, 0, .9)',
textAlign: Platform.OS === 'ios' ? 'center' : 'left',
marginHorizontal: 16,
},
});
Expand Down

0 comments on commit cd3707d

Please sign in to comment.