Compare commits

...

13 Commits
2.7.0 ... 2.9.1

Author SHA1 Message Date
Brent Vatne
67233dc9ef Release 2.9.1 2018-07-24 10:49:56 -07:00
Brent Vatne
b0443c1861 Move more logs behind debug flag in stack playground 2018-07-24 10:47:59 -07:00
Brent Vatne
c0b637df52 Fix title offfset calculation 2018-07-24 10:47:02 -07:00
Brent Vatne
9a82706fba Fix snapshots 2018-07-20 14:44:26 -07:00
Brent Vatne
d973a26edb Release 2.9.0 2018-07-20 14:33:27 -07:00
Brent Vatne
852e7e1974 Respect custom background color in header wrapper 2018-07-20 14:30:38 -07:00
Brent Vatne
cd3707d64b Add headerLayoutPreset, add config for back button title visibility and make it have reasonable defaults, better back button ripple on Android (#4588) 2018-07-20 14:12:39 -07:00
Brent Vatne
3c36db455f Release 2.8.0 2018-07-19 15:48:09 -07:00
Brent Vatne
ec52c884c5 Update NavigationPlayground to Expo SDK 28 2018-07-19 15:46:24 -07:00
Brent Vatne
c4b3f25a0f Cleanup unused descriptors and handle the case where we might expect to have a descriptor but do not (#4723) 2018-07-19 13:16:38 -07:00
Eric Vicenti
93642e16e7 Fix createNavigator leak of old descriptors 2018-07-19 12:41:14 -07:00
Eric Vicenti
1a76556290 Fix leak in createNavigator
Previous descriptors had been retained because this binding caused `this.prevState` to remain referenced. This binds the component getter to null instead.
2018-07-19 12:21:22 -07:00
Reza Ghorbani
12b21f052e added header container styles to be customized (#4331) 2018-07-18 15:14:08 -07:00
13 changed files with 794 additions and 2211 deletions

View File

@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
## [2.9.1] - [2018-07-24](https://github.com/react-navigation/react-navigation/releases/tag/2.9.1)
### Fixed
- Incorrect parameters passed to title offset calculation led to bug in header layout when no right component (https://github.com/react-navigation/react-navigation/issues/4754)
## [2.9.0] - [2018-07-20](https://github.com/react-navigation/react-navigation/releases/tag/2.9.0)
### 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)).
- Respect header background color on container (fixes edge case where user depended on displaying content that was rendered behind the navigator, this particular behavior should not be depended on and may break in the future, but this change is still useful regardless).
## [2.8.0] - [2018-07-19](https://github.com/react-navigation/react-navigation/releases/tag/2.8.0)
### Added
- `headerLeftContainerStyle`, `headerTitleContainerStyle`, and `headerRightContainerStyle` are exposed on `navigationOptions`. These properties allow you to customize the style of the container of `headerLeft`, `headerTitle` and `headerRight` components.
### Fixed
- Fixed memory leaks in `createNavigator`: [closure scope leak](https://github.com/react-navigation/react-navigation/commit/1a765562905e93bbae0262dd20c2688221c999e8), and [clean up old descriptors](https://github.com/react-navigation/react-navigation/commit/93642e16e7ff029586b68ee732ec790504ee4862).
## [2.7.0] - [2018-07-17](https://github.com/react-navigation/react-navigation/releases/tag/2.7.0)
### Added
- The enableURLHandling prop on the top level navigator component allows you to disable deep linking handling. Currently it is always enabled. To disable it, `<RootNavigator enableURLHandling={false} />`
@@ -83,7 +104,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Changed
- Improved examples
[Unreleased]: https://github.com/react-navigation/react-navigation/compare/2.7.0...HEAD
[Unreleased]: https://github.com/react-navigation/react-navigation/compare/2.9.1...HEAD
[2.9.1]: https://github.com/react-navigation/react-navigation/compare/2.9.0...2.9.1
[2.9.0]: https://github.com/react-navigation/react-navigation/compare/2.8.0...2.9.0
[2.8.0]: https://github.com/react-navigation/react-navigation/compare/2.7.0...2.8.0
[2.7.0]: https://github.com/react-navigation/react-navigation/compare/2.6.2...2.7.0
[2.6.2]: https://github.com/react-navigation/react-navigation/compare/2.6.1...2.6.2
[2.6.1]: https://github.com/react-navigation/react-navigation/compare/2.6.0...2.6.1

View File

@@ -11,8 +11,7 @@
"splash": {
"image": "./assets/icons/splash.png"
},
"sdkVersion": "27.0.0",
"entryPoint": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
"sdkVersion": "28.0.0",
"assetBundlePatterns": [
"**/*"
],

View File

@@ -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,
@@ -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,
@@ -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() {
@@ -177,16 +179,16 @@ class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
this._s3.remove();
}
_onWF = a => {
console.log('_willFocus PhotosScreen', a);
DEBUG && console.log('_willFocus PhotosScreen', a);
};
_onDF = a => {
console.log('_didFocus PhotosScreen', a);
DEBUG && console.log('_didFocus PhotosScreen', a);
};
_onWB = a => {
console.log('_willBlur PhotosScreen', a);
DEBUG && console.log('_willBlur PhotosScreen', a);
};
_onDB = a => {
console.log('_didBlur PhotosScreen', a);
DEBUG && console.log('_didBlur PhotosScreen', a);
};
render() {
@@ -231,18 +233,23 @@ MyProfileScreen.navigationOptions = props => {
};
};
const SimpleStack = createStackNavigator({
Home: {
screen: MyHomeScreen,
const SimpleStack = createStackNavigator(
{
Home: {
screen: MyHomeScreen,
},
Profile: {
path: 'people/:name',
screen: MyProfileScreen,
},
Photos: {
path: 'photos/:name',
screen: MyPhotosScreen,
},
},
Profile: {
path: 'people/:name',
screen: MyProfileScreen,
},
Photos: {
path: 'photos/:name',
screen: MyPhotosScreen,
},
});
{
// headerLayoutPreset: 'center',
}
);
export default SimpleStack;

View File

@@ -2,23 +2,23 @@
"name": "NavigationPlayground",
"version": "0.1.0",
"private": true,
"main": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
"main": "node_modules/expo/AppEntry.js",
"private": true,
"scripts": {
"start": "react-native-scripts start",
"eject": "react-native-scripts eject",
"android": "react-native-scripts android",
"ios": "react-native-scripts ios",
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"test": "flow"
},
"dependencies": {
"expo": "^27.0.0",
"expo": "^28.0.0",
"invariant": "^2.2.4",
"react": "16.3.1",
"react-native": "^0.55.0",
"react-native-iphone-x-helper": "^1.0.2",
"react-navigation": "link:../..",
"react-navigation-header-buttons": "^0.0.4",
"react-navigation-material-bottom-tabs": "0.1.3",
"react-navigation-material-bottom-tabs": "^0.3.0",
"react-navigation-tabs": "^0.5.1"
},
"devDependencies": {
@@ -26,9 +26,8 @@
"babel-plugin-transform-remove-console": "^6.9.0",
"flow-bin": "^0.67.0",
"jest": "^22.1.3",
"jest-expo": "^26.0.0",
"react-native-scripts": "^1.5.0",
"react-test-renderer": "16.3.0-alpha.1"
"jest-expo": "^28.0.0",
"react-test-renderer": "16.3.1"
},
"jest": {
"preset": "jest-expo",

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "react-navigation",
"version": "2.7.0",
"version": "2.9.1",
"description": "Routing and navigation for your React Native apps",
"main": "src/react-navigation.js",
"repository": {

View File

@@ -81,7 +81,7 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
collapsable={undefined}
style={
Object {
"backgroundColor": "#F7F7F7",
"backgroundColor": "red",
"transform": Array [
Object {
"translateX": 0,
@@ -285,7 +285,7 @@ exports[`StackNavigator renders successfully 1`] = `
collapsable={undefined}
style={
Object {
"backgroundColor": "#F7F7F7",
"backgroundColor": "red",
"transform": Array [
Object {
"translateX": 0,

View File

@@ -24,7 +24,7 @@ function createNavigator(NavigatorView, router, navigationConfig) {
);
}
const descriptors = { ...prevState.descriptors };
const descriptors = {};
routes.forEach(route => {
if (
@@ -36,8 +36,10 @@ function createNavigator(NavigatorView, router, navigationConfig) {
descriptors[route.key] = prevDescriptors[route.key];
return;
}
const getComponent = () =>
router.getComponentForRouteName(route.routeName);
const getComponent = router.getComponentForRouteName.bind(
null,
route.routeName
);
const childNavigation = navigation.getChildNavigation(route.key);
const options = router.getScreenOptions(childNavigation, screenProps);
descriptors[route.key] = {

View File

@@ -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'
@@ -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)) {
@@ -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: {
@@ -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>
);
};
@@ -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}
/>
);
@@ -220,6 +269,11 @@ class Header extends React.PureComponent {
const { transitionPreset } = this.props;
let { style } = props;
if (options.headerLeftContainerStyle) {
style = [style, options.headerLeftContainerStyle];
}
// On Android, or if we have a custom header left, or if we have a custom back image, we
// do not use the modular header (which is the one that imitates UINavigationController)
if (
@@ -229,14 +283,14 @@ class Header extends React.PureComponent {
options.headerLeft === null
) {
return this._renderSubView(
props,
{ ...props, style },
'left',
this._renderLeftComponent,
this.props.leftInterpolator
);
} else {
return this._renderModularSubView(
props,
{ ...props, style },
'left',
this._renderModularLeftComponent,
this.props.leftLabelInterpolator,
@@ -246,24 +300,17 @@ class Header extends React.PureComponent {
}
_renderTitle(props, options) {
const 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;
}
const { layoutPreset, transitionPreset } = this.props;
let style = [
{ justifyContent: layoutPreset === 'center' ? 'center' : 'flex-start' },
getTitleOffsets(
layoutPreset,
false,
options.hasLeftComponent,
options.hasRightComponent
),
options.headerTitleContainerStyle,
];
return this._renderSubView(
{ ...props, style },
@@ -276,8 +323,15 @@ class Header extends React.PureComponent {
}
_renderRight(props) {
const { options } = props.scene.descriptor;
let { style } = props;
if (options.headerRightContainerStyle) {
style = [style, options.headerRightContainerStyle];
}
return this._renderSubView(
props,
{ ...props, style },
'right',
this._renderRightComponent,
this.props.rightInterpolator
@@ -371,7 +425,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,
}),
@@ -392,6 +445,7 @@ class Header extends React.PureComponent {
const title = this._renderTitle(props, {
hasLeftComponent: !!left,
hasRightComponent: !!right,
headerTitleContainerStyle: options.headerTitleContainerStyle,
});
const { isLandscape, transitionPreset } = this.props;
@@ -527,8 +581,11 @@ class Header extends React.PureComponent {
<Animated.View
style={[
this.props.layoutInterpolator(this.props),
Platform.OS === 'ios'
? { backgroundColor: DEFAULT_BACKGROUND_COLOR }
Platform.OS === 'ios' && !options.headerTransparent
? {
backgroundColor:
safeHeaderStyle.backgroundColor || DEFAULT_BACKGROUND_COLOR,
}
: null,
]}
>
@@ -620,12 +677,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,

View File

@@ -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,
@@ -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',
@@ -137,7 +160,7 @@ const styles = StyleSheet.create({
: {
height: 24,
width: 24,
margin: 16,
margin: 3,
resizeMode: 'contain',
transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }],
},

View File

@@ -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,
},
});

View File

@@ -133,23 +133,23 @@ export default function ScenesReducer(
// We can get into a weird place where we have a queued transition and then clobber
// that transition without ever actually rendering the scene, in which case
// there is no lastScene and so we need to grab the descriptor from elsewhere.
// Warning: changes to descriptor caching may break this, and in that case
// we may be better off just not adding it to stale scenes at all.
// there is no lastScene. If the descriptor is not available on the lastScene
// or the descriptors prop then we just skip adding it to stale scenes and it's
// not ever rendered.
const descriptor = lastScene
? lastScene.descriptor
: descriptors[route.key];
invariant(descriptor, 'Cannot add stale scene with no descriptor');
staleScenes.set(key, {
index,
isActive: false,
isStale: true,
key,
route,
descriptor,
});
if (descriptor) {
staleScenes.set(key, {
index,
isActive: false,
isStale: true,
key,
route,
descriptor,
});
}
});
}

View File

@@ -34,6 +34,12 @@ const IS_IPHONE_X =
const EaseInOut = Easing.inOut(Easing.ease);
/**
* Enumerate possible values for validation
*/
const HEADER_LAYOUT_PRESET_VALUES = ['center', 'left'];
const HEADER_TRANSITION_PRESET_VALUES = ['uikit', 'fade-in-place'];
/**
* The max duration of the card animation in milliseconds after released gesture.
* The actual duration should be always less then that because the rest distance
@@ -159,6 +165,8 @@ class StackViewLayout extends React.Component {
scene,
mode: headerMode,
transitionPreset: this._getHeaderTransitionPreset(),
layoutPreset: this._getHeaderLayoutPreset(),
backTitleVisible: this._getheaderBackTitleVisible(),
leftInterpolator: headerLeftInterpolator,
titleInterpolator: headerTitleInterpolator,
rightInterpolator: headerRightInterpolator,
@@ -477,6 +485,40 @@ class StackViewLayout extends React.Component {
return 'float';
}
_getHeaderLayoutPreset() {
const { headerLayoutPreset } = this.props;
if (headerLayoutPreset) {
if (__DEV__) {
if (
this._getHeaderTransitionPreset() === 'uitkit' &&
headerLayoutPreset === 'left' &&
Platform.OS === 'ios'
) {
console.warn(
`headerTransitionPreset with the value 'ui-kit' is incompatible with headerLayoutPreset 'left'`
);
}
}
if (HEADER_LAYOUT_PRESET_VALUES.includes(headerLayoutPreset)) {
return headerLayoutPreset;
}
if (__DEV__) {
console.error(
`Invalid configuration applied for headerLayoutPreset - expected one of ${HEADER_LAYOUT_PRESET_VALUES.join(
', '
)} but received ${JSON.stringify(headerLayoutPreset)}`
);
}
}
if (Platform.OS === 'android') {
return 'left';
} else {
return 'center';
}
}
_getHeaderTransitionPreset() {
// On Android or with header mode screen, we always just use in-place,
// we ignore the option entirely (at least until we have other presets)
@@ -484,12 +526,28 @@ class StackViewLayout extends React.Component {
return 'fade-in-place';
}
// TODO: validations: 'fade-in-place' or 'uikit' are valid
if (this.props.headerTransitionPreset) {
return this.props.headerTransitionPreset;
} else {
return 'fade-in-place';
const { headerTransitionPreset } = this.props;
if (headerTransitionPreset) {
if (HEADER_TRANSITION_PRESET_VALUES.includes(headerTransitionPreset)) {
return headerTransitionPreset;
}
if (__DEV__) {
console.error(
`Invalid configuration applied for headerTransitionPreset - expected one of ${HEADER_TRANSITION_PRESET_VALUES.join(
', '
)} but received ${JSON.stringify(headerTransitionPreset)}`
);
}
}
return 'fade-in-place';
}
_getheaderBackTitleVisible() {
const { headerBackTitleVisible } = this.props;
return headerBackTitleVisible;
}
_renderInnerScene(scene) {