Compare commits

...

6 Commits

Author SHA1 Message Date
Satyajit Sahoo
220af93db5 chore: publish
- @react-navigation/stack@5.5.0
2020-06-08 10:56:32 +02:00
Satyajit Sahoo
1f27e4b1f6 fix: ignore onOpen from route that wasn't closing
closes #8257
2020-06-08 10:48:04 +02:00
Satyajit Sahoo
9c06a92d09 fix: fix blank screen with animationEnabled: false & headerShown: false
closes #8391
2020-06-08 10:17:02 +02:00
Satyajit Sahoo
e0e0f79793 feat: automatically hide header in nested stacks 2020-06-08 08:14:34 +02:00
Jeroen Verfallie
c7e4bf94e6 fix: pass gestureRef to PanGestureHandlerNative (#8394)
In the current implementation the ref is unused, resulting in a constant `current: {null}` on the context.
2020-06-08 08:05:59 +02:00
Satyajit Sahoo
7024d4bb81 docs: fix comment about headerBacktitleVisible 2020-06-08 01:47:09 +02:00
15 changed files with 315 additions and 241 deletions

View File

@@ -71,13 +71,12 @@ export default function BottomTabsScreen() {
> >
<BottomTabs.Screen <BottomTabs.Screen
name="Article" name="Article"
component={SimpleStackScreen}
options={{ options={{
title: 'Article', title: 'Article',
tabBarIcon: getTabBarIcon('file-document-box'), tabBarIcon: getTabBarIcon('file-document-box'),
}} }}
> />
{(props) => <SimpleStackScreen {...props} headerMode="none" />}
</BottomTabs.Screen>
<BottomTabs.Screen <BottomTabs.Screen
name="Chat" name="Chat"
component={Chat} component={Chat}

View File

@@ -22,14 +22,13 @@ export default function MaterialBottomTabsScreen() {
<MaterialBottomTabs.Navigator barStyle={styles.tabBar}> <MaterialBottomTabs.Navigator barStyle={styles.tabBar}>
<MaterialBottomTabs.Screen <MaterialBottomTabs.Screen
name="Article" name="Article"
component={SimpleStackScreen}
options={{ options={{
tabBarLabel: 'Article', tabBarLabel: 'Article',
tabBarIcon: 'file-document-box', tabBarIcon: 'file-document-box',
tabBarColor: '#C9E7F8', tabBarColor: '#C9E7F8',
}} }}
> />
{(props) => <SimpleStackScreen {...props} headerMode="none" />}
</MaterialBottomTabs.Screen>
<MaterialBottomTabs.Screen <MaterialBottomTabs.Screen
name="Chat" name="Chat"
component={Chat} component={Chat}

View File

@@ -91,7 +91,6 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
return ( return (
<ModalPresentationStack.Navigator <ModalPresentationStack.Navigator
mode="modal" mode="modal"
headerMode="screen"
screenOptions={({ route, navigation }) => ({ screenOptions={({ route, navigation }) => ({
...TransitionPresets.ModalPresentationIOS, ...TransitionPresets.ModalPresentationIOS,
cardOverlayEnabled: true, cardOverlayEnabled: true,

View File

@@ -111,17 +111,17 @@ const AlbumsScreen = ({
const SimpleStack = createStackNavigator<SimpleStackParams>(); const SimpleStack = createStackNavigator<SimpleStackParams>();
type Props = Partial<React.ComponentProps<typeof SimpleStack.Navigator>> & { type Props = {
navigation: StackNavigationProp<ParamListBase>; navigation: StackNavigationProp<ParamListBase>;
}; };
export default function SimpleStackScreen({ navigation, ...rest }: Props) { export default function SimpleStackScreen({ navigation }: Props) {
navigation.setOptions({ navigation.setOptions({
headerShown: false, headerShown: false,
}); });
return ( return (
<SimpleStack.Navigator {...rest}> <SimpleStack.Navigator>
<SimpleStack.Screen <SimpleStack.Screen
name="Article" name="Article"
component={ArticleScreen} component={ArticleScreen}

View File

@@ -9,7 +9,7 @@ import {
} from 'react-native'; } from 'react-native';
import { Button, Appbar } from 'react-native-paper'; import { Button, Appbar } from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import { RouteProp, ParamListBase } from '@react-navigation/native'; import { useTheme, RouteProp, ParamListBase } from '@react-navigation/native';
import { import {
createStackNavigator, createStackNavigator,
StackNavigationProp, StackNavigationProp,
@@ -124,6 +124,8 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
headerShown: false, headerShown: false,
}); });
const { colors, dark } = useTheme();
return ( return (
<SimpleStack.Navigator {...rest}> <SimpleStack.Navigator {...rest}>
<SimpleStack.Screen <SimpleStack.Screen
@@ -167,9 +169,15 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
headerBackTitle: 'Back', headerBackTitle: 'Back',
headerTransparent: true, headerTransparent: true,
headerBackground: () => ( headerBackground: () => (
<HeaderBackground style={{ backgroundColor: 'transparent' }}> <HeaderBackground
style={{
backgroundColor: 'transparent',
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: colors.border,
}}
>
<BlurView <BlurView
tint="light" tint={dark ? 'dark' : 'light'}
intensity={75} intensity={75}
style={StyleSheet.absoluteFill} style={StyleSheet.absoluteFill}
/> />

View File

@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.5.0](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.4.2...@react-navigation/stack@5.5.0) (2020-06-08)
### Bug Fixes
* fix blank screen with animationEnabled: false & headerShown: false ([9c06a92](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/9c06a92d092af150d653c3a2f7fdccd28090bb14)), closes [#8391](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/8391)
* ignore onOpen from route that wasn't closing ([1f27e4b](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/1f27e4b1f659e59ad15ecbf44b4fb0a80cae302f)), closes [#8257](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/8257)
* pass gestureRef to PanGestureHandlerNative ([#8394](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/8394)) ([c7e4bf9](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/c7e4bf94e664563892cbdafccc108ad519ccec50))
### Features
* automatically hide header in nested stacks ([e0e0f79](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/e0e0f79793be552e5532cd0afe9444000d21341e))
## [5.4.2](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.4.1...@react-navigation/stack@5.4.2) (2020-06-06) ## [5.4.2](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.4.1...@react-navigation/stack@5.4.2) (2020-06-06)

View File

@@ -1,7 +1,7 @@
{ {
"name": "@react-navigation/stack", "name": "@react-navigation/stack",
"description": "Stack navigator component for iOS and Android with animated transitions and gestures", "description": "Stack navigator component for iOS and Android with animated transitions and gestures",
"version": "5.4.2", "version": "5.5.0",
"keywords": [ "keywords": [
"react-native-component", "react-native-component",
"react-component", "react-component",

View File

@@ -133,7 +133,8 @@ export type StackHeaderOptions = {
*/ */
headerBackAllowFontScaling?: boolean; headerBackAllowFontScaling?: boolean;
/** /**
* Title string used by the back button on iOS, or `null` to disable label. Defaults to the previous scene's `headerTitle`. * Title string used by the back button on iOS. Defaults to the previous scene's `headerTitle`.
* Use `headerBackTitleVisible: false` to hide it.
*/ */
headerBackTitle?: string; headerBackTitle?: string;
/** /**

View File

@@ -0,0 +1,5 @@
import * as React from 'react';
const HeaderShownContext = React.createContext(false);
export default HeaderShownContext;

View File

@@ -10,7 +10,7 @@ export function PanGestureHandler(props: PanGestureHandlerProperties) {
return ( return (
<GestureHandlerRefContext.Provider value={gestureRef}> <GestureHandlerRefContext.Provider value={gestureRef}>
<PanGestureHandlerNative {...props} /> <PanGestureHandlerNative {...props} ref={gestureRef} />
</GestureHandlerRefContext.Provider> </GestureHandlerRefContext.Provider>
); );
} }

View File

@@ -15,6 +15,7 @@ import {
forNoAnimation, forNoAnimation,
forSlideRight, forSlideRight,
} from '../../TransitionConfigs/HeaderStyleInterpolators'; } from '../../TransitionConfigs/HeaderStyleInterpolators';
import HeaderShownContext from '../../utils/HeaderShownContext';
import { import {
Layout, Layout,
Scene, Scene,
@@ -54,6 +55,7 @@ export default function HeaderContainer({
style, style,
}: Props) { }: Props) {
const focusedRoute = getFocusedRoute(); const focusedRoute = getFocusedRoute();
const isParentHeaderShown = React.useContext(HeaderShownContext);
return ( return (
<View pointerEvents="box-none" style={style}> <View pointerEvents="box-none" style={style}>
@@ -62,7 +64,16 @@ export default function HeaderContainer({
return null; return null;
} }
const { options } = scene.descriptor; const {
header,
headerShown = isParentHeaderShown === false,
headerTransparent,
} = scene.descriptor.options || {};
if (!headerShown) {
return null;
}
const isFocused = focusedRoute.key === scene.route.key; const isFocused = focusedRoute.key === scene.route.key;
const previousRoute = getPreviousRoute({ route: scene.route }); const previousRoute = getPreviousRoute({ route: scene.route });
@@ -85,13 +96,20 @@ export default function HeaderContainer({
// This makes the header look like it's moving with the screen // This makes the header look like it's moving with the screen
const previousScene = self[i - 1]; const previousScene = self[i - 1];
const nextScene = self[i + 1]; const nextScene = self[i + 1];
const {
headerShown: previousHeaderShown = isParentHeaderShown === false,
} = previousScene?.descriptor.options || {};
const { headerShown: nextHeaderShown = isParentHeaderShown === false } =
nextScene?.descriptor.options || {};
const isHeaderStatic = const isHeaderStatic =
(previousScene && (previousHeaderShown === false &&
previousScene.descriptor.options.headerShown === false &&
// We still need to animate when coming back from next scene // We still need to animate when coming back from next scene
// A hacky way to check this is if the next scene exists // A hacky way to check this is if the next scene exists
!nextScene) || !nextScene) ||
(nextScene && nextScene.descriptor.options.headerShown === false); nextHeaderShown === false;
const props = { const props = {
mode, mode,
@@ -139,18 +157,12 @@ export default function HeaderContainer({
style={ style={
// Avoid positioning the focused header absolutely // Avoid positioning the focused header absolutely
// Otherwise accessibility tools don't seem to be able to find it // Otherwise accessibility tools don't seem to be able to find it
(mode === 'float' && !isFocused) || options.headerTransparent (mode === 'float' && !isFocused) || headerTransparent
? styles.header ? styles.header
: null : null
} }
> >
{options.headerShown !== false ? ( {header !== undefined ? header(props) : <Header {...props} />}
options.header !== undefined ? (
options.header(props)
) : (
<Header {...props} />
)
) : null}
</View> </View>
</NavigationRouteContext.Provider> </NavigationRouteContext.Provider>
</NavigationContext.Provider> </NavigationContext.Provider>

View File

@@ -493,6 +493,12 @@ export default class Card extends React.Component<Props> {
? Color(backgroundColor).alpha() === 0 ? Color(backgroundColor).alpha() === 0
: false; : false;
// This is a dummy style that doesn't actually change anything visually.
// Animated needs the animated value to be used somewhere, otherwise things don't update properly.
// If we disable animations and hide header, it could end up making the value unused.
// So we have this dummy style that will always be used regardless of what else changed.
const dummyStyle = { opacity: Animated.diffClamp(current, 1, 1) };
return ( return (
<CardAnimationContext.Provider value={animationContext}> <CardAnimationContext.Provider value={animationContext}>
<View pointerEvents="box-none" {...rest}> <View pointerEvents="box-none" {...rest}>
@@ -502,7 +508,12 @@ export default class Card extends React.Component<Props> {
</View> </View>
) : null} ) : null}
<Animated.View <Animated.View
style={[styles.container, containerStyle, customContainerStyle]} style={[
styles.container,
dummyStyle,
containerStyle,
customContainerStyle,
]}
pointerEvents="box-none" pointerEvents="box-none"
> >
<PanGestureHandler <PanGestureHandler

View File

@@ -4,6 +4,7 @@ import { Route, useTheme } from '@react-navigation/native';
import { Props as HeaderContainerProps } from '../Header/HeaderContainer'; import { Props as HeaderContainerProps } from '../Header/HeaderContainer';
import Card from './Card'; import Card from './Card';
import HeaderHeightContext from '../../utils/HeaderHeightContext'; import HeaderHeightContext from '../../utils/HeaderHeightContext';
import HeaderShownContext from '../../utils/HeaderShownContext';
import { import {
Scene, Scene,
Layout, Layout,
@@ -53,6 +54,7 @@ type Props = TransitionPreset & {
gestureVelocityImpact?: number; gestureVelocityImpact?: number;
mode: StackCardMode; mode: StackCardMode;
headerMode: StackHeaderMode; headerMode: StackHeaderMode;
headerShown: boolean;
hasAbsoluteHeader: boolean; hasAbsoluteHeader: boolean;
headerHeight: number; headerHeight: number;
onHeaderHeightChange: (props: { onHeaderHeightChange: (props: {
@@ -81,6 +83,7 @@ function CardContainer({
getFocusedRoute, getFocusedRoute,
mode, mode,
headerMode, headerMode,
headerShown,
headerStyleInterpolator, headerStyleInterpolator,
hasAbsoluteHeader, hasAbsoluteHeader,
headerHeight, headerHeight,
@@ -158,6 +161,9 @@ function CardContainer({
}; };
}, [pointerEvents, scene.progress.next]); }, [pointerEvents, scene.progress.next]);
const isParentHeaderShown = React.useContext(HeaderShownContext);
const isCurrentHeaderShown = headerMode !== 'none' && headerShown !== false;
return ( return (
<Card <Card
index={index} index={index}
@@ -191,9 +197,13 @@ function CardContainer({
> >
<View style={styles.container}> <View style={styles.container}>
<View style={styles.scene}> <View style={styles.scene}>
<HeaderShownContext.Provider
value={isParentHeaderShown || isCurrentHeaderShown}
>
<HeaderHeightContext.Provider value={headerHeight}> <HeaderHeightContext.Provider value={headerHeight}>
{renderScene({ route: scene.route })} {renderScene({ route: scene.route })}
</HeaderHeightContext.Provider> </HeaderHeightContext.Provider>
</HeaderShownContext.Provider>
</View> </View>
{headerMode === 'screen' {headerMode === 'screen'
? renderHeader({ ? renderHeader({

View File

@@ -19,6 +19,7 @@ import {
} from '../../TransitionConfigs/TransitionPresets'; } from '../../TransitionConfigs/TransitionPresets';
import { forNoAnimation as forNoAnimationHeader } from '../../TransitionConfigs/HeaderStyleInterpolators'; import { forNoAnimation as forNoAnimationHeader } from '../../TransitionConfigs/HeaderStyleInterpolators';
import { forNoAnimation as forNoAnimationCard } from '../../TransitionConfigs/CardStyleInterpolators'; import { forNoAnimation as forNoAnimationCard } from '../../TransitionConfigs/CardStyleInterpolators';
import HeaderShownContext from '../../utils/HeaderShownContext';
import getDistanceForDirection from '../../utils/getDistanceForDirection'; import getDistanceForDirection from '../../utils/getDistanceForDirection';
import { import {
Layout, Layout,
@@ -335,24 +336,6 @@ export default class CardStack extends React.Component<Props, State> {
return state.routes[state.index]; return state.routes[state.index];
}; };
private doesFloatHeaderNeedAbsolutePositioning = () => {
if (this.props.headerMode !== 'float') {
return false;
}
return this.state.scenes.slice(-2).some((scene) => {
const { descriptor } = scene;
const options = descriptor ? descriptor.options : {};
const { headerTransparent, headerShown } = options;
if (headerTransparent || headerShown === false) {
return true;
}
return false;
});
};
render() { render() {
const { const {
mode, mode,
@@ -381,8 +364,6 @@ export default class CardStack extends React.Component<Props, State> {
const focusedDescriptor = descriptors[focusedRoute.key]; const focusedDescriptor = descriptors[focusedRoute.key];
const focusedOptions = focusedDescriptor ? focusedDescriptor.options : {}; const focusedOptions = focusedDescriptor ? focusedDescriptor.options : {};
const isFloatHeaderAbsolute = this.doesFloatHeaderNeedAbsolutePositioning();
let defaultTransitionPreset = let defaultTransitionPreset =
mode === 'modal' ? ModalTransition : DefaultTransition; mode === 'modal' ? ModalTransition : DefaultTransition;
@@ -404,10 +385,29 @@ export default class CardStack extends React.Component<Props, State> {
// For modals, usually we want the screen underneath to be visible, so also disable it there // For modals, usually we want the screen underneath to be visible, so also disable it there
const isScreensEnabled = Platform.OS !== 'ios' && mode !== 'modal'; const isScreensEnabled = Platform.OS !== 'ios' && mode !== 'modal';
let floatingHeader; return (
<HeaderShownContext.Consumer>
{(isParentHeaderShown) => {
const isFloatHeaderAbsolute =
headerMode === 'float'
? this.state.scenes.slice(-2).some((scene) => {
const { descriptor } = scene;
const options = descriptor ? descriptor.options : {};
const {
headerTransparent,
headerShown = isParentHeaderShown === false,
} = options;
if (headerMode === 'float') { if (headerTransparent || headerShown === false) {
floatingHeader = ( return true;
}
return false;
})
: false;
const floatingHeader =
headerMode === 'float' ? (
<React.Fragment key="header"> <React.Fragment key="header">
{renderHeader({ {renderHeader({
mode: 'float', mode: 'float',
@@ -428,8 +428,7 @@ export default class CardStack extends React.Component<Props, State> {
style: isFloatHeaderAbsolute ? styles.floating : undefined, style: isFloatHeaderAbsolute ? styles.floating : undefined,
})} })}
</React.Fragment> </React.Fragment>
); ) : null;
}
return ( return (
<React.Fragment> <React.Fragment>
@@ -454,7 +453,7 @@ export default class CardStack extends React.Component<Props, State> {
const { const {
safeAreaInsets, safeAreaInsets,
headerShown, headerShown = isParentHeaderShown === false,
headerTransparent, headerTransparent,
cardShadowEnabled, cardShadowEnabled,
cardOverlayEnabled, cardOverlayEnabled,
@@ -518,7 +517,9 @@ export default class CardStack extends React.Component<Props, State> {
left: safeAreaInsetLeft = insets.left, left: safeAreaInsetLeft = insets.left,
} = safeAreaInsets || {}; } = safeAreaInsets || {};
const previousRoute = getPreviousRoute({ route: scene.route }); const previousRoute = getPreviousRoute({
route: scene.route,
});
let previousScene = scenes[index - 1]; let previousScene = scenes[index - 1];
@@ -535,6 +536,11 @@ export default class CardStack extends React.Component<Props, State> {
} }
} }
const headerHeight =
headerMode !== 'none' && headerShown !== false
? headerHeights[route.key]
: 0;
return ( return (
<MaybeScreen <MaybeScreen
key={route.key} key={route.key}
@@ -564,16 +570,15 @@ export default class CardStack extends React.Component<Props, State> {
onPageChangeConfirm={onPageChangeConfirm} onPageChangeConfirm={onPageChangeConfirm}
onPageChangeCancel={onPageChangeCancel} onPageChangeCancel={onPageChangeCancel}
gestureResponseDistance={gestureResponseDistance} gestureResponseDistance={gestureResponseDistance}
headerHeight={headerHeights[route.key]} headerHeight={headerHeight}
onHeaderHeightChange={this.handleHeaderLayout} onHeaderHeightChange={this.handleHeaderLayout}
getPreviousRoute={getPreviousRoute} getPreviousRoute={getPreviousRoute}
getFocusedRoute={this.getFocusedRoute} getFocusedRoute={this.getFocusedRoute}
mode={mode} mode={mode}
headerMode={headerMode} headerMode={headerMode}
headerShown={headerShown}
hasAbsoluteHeader={ hasAbsoluteHeader={
isFloatHeaderAbsolute && isFloatHeaderAbsolute && !headerTransparent
headerShown !== false &&
!headerTransparent
} }
renderHeader={renderHeader} renderHeader={renderHeader}
renderScene={renderScene} renderScene={renderScene}
@@ -581,7 +586,9 @@ export default class CardStack extends React.Component<Props, State> {
onCloseRoute={onCloseRoute} onCloseRoute={onCloseRoute}
onTransitionStart={onTransitionStart} onTransitionStart={onTransitionStart}
onTransitionEnd={onTransitionEnd} onTransitionEnd={onTransitionEnd}
gestureEnabled={index !== 0 && getGesturesEnabled({ route })} gestureEnabled={
index !== 0 && getGesturesEnabled({ route })
}
gestureVelocityImpact={gestureVelocityImpact} gestureVelocityImpact={gestureVelocityImpact}
{...transitionConfig} {...transitionConfig}
/> />
@@ -592,6 +599,9 @@ export default class CardStack extends React.Component<Props, State> {
{isFloatHeaderAbsolute ? floatingHeader : null} {isFloatHeaderAbsolute ? floatingHeader : null}
</React.Fragment> </React.Fragment>
); );
}}
</HeaderShownContext.Consumer>
);
} }
} }

View File

@@ -330,13 +330,15 @@ export default class StackView extends React.Component<Props, State> {
private handleOpenRoute = ({ route }: { route: Route<string> }) => { private handleOpenRoute = ({ route }: { route: Route<string> }) => {
const { state, navigation } = this.props; const { state, navigation } = this.props;
const { closingRouteKeys, replacingRouteKeys } = this.state;
if ( if (
this.state.replacingRouteKeys.every((key) => key !== route.key) && closingRouteKeys.some((key) => key === route.key) &&
replacingRouteKeys.every((key) => key !== route.key) &&
state.routeNames.includes(route.name) && state.routeNames.includes(route.name) &&
!state.routes.some((r) => r.key === route.key) !state.routes.some((r) => r.key === route.key)
) { ) {
// If route isn't present in current state, assume that a close animation was cancelled // If route isn't present in current state, but was closing, assume that a close animation was cancelled
// So we need to add this route back to the state // So we need to add this route back to the state
navigation.navigate(route); navigation.navigate(route);
} else { } else {
@@ -409,6 +411,9 @@ export default class StackView extends React.Component<Props, State> {
navigation, navigation,
keyboardHandlingEnabled, keyboardHandlingEnabled,
mode = 'card', mode = 'card',
headerMode = mode === 'card' && Platform.OS === 'ios'
? 'float'
: 'screen',
...rest ...rest
} = this.props; } = this.props;
@@ -419,9 +424,6 @@ export default class StackView extends React.Component<Props, State> {
closingRouteKeys, closingRouteKeys,
} = this.state; } = this.state;
const headerMode =
mode === 'card' && Platform.OS === 'ios' ? 'float' : 'screen';
return ( return (
<NavigationHelpersContext.Provider value={navigation}> <NavigationHelpersContext.Provider value={navigation}>
<GestureHandlerWrapper style={styles.container}> <GestureHandlerWrapper style={styles.container}>