From 9ac709ea1e5a63c3a48abfa334ff6a6925095a72 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Sat, 8 May 2021 06:53:09 +0200 Subject: [PATCH] refactor: drop mode prop in favor of animationPresentation option BREAKING CHANGE: This drops the mode prop on the navigator in favor of a per-screen option animationPresentation --- example/src/Screens/ModalStack.tsx | 2 +- example/src/Screens/StackTransparent.tsx | 25 ++-- .../src/navigators/createStackNavigator.tsx | 2 +- packages/stack/src/types.tsx | 11 +- .../stack/src/views/Stack/CardContainer.tsx | 10 +- packages/stack/src/views/Stack/CardStack.tsx | 129 ++++++++---------- packages/stack/src/views/Stack/StackView.tsx | 2 - 7 files changed, 91 insertions(+), 90 deletions(-) diff --git a/example/src/Screens/ModalStack.tsx b/example/src/Screens/ModalStack.tsx index 279d6a1e..69191be4 100644 --- a/example/src/Screens/ModalStack.tsx +++ b/example/src/Screens/ModalStack.tsx @@ -82,7 +82,7 @@ export default function ModalStackScreen({ navigation }: Props) { }, [navigation]); return ( - + ) => { +}: StackScreenProps) => { return ( @@ -45,7 +45,9 @@ const ArticleScreen = ({ ); }; -const DialogScreen = ({ navigation }: StackScreenProps) => { +const DialogScreen = ({ + navigation, +}: StackScreenProps) => { const { colors } = useTheme(); return ( @@ -70,12 +72,11 @@ const DialogScreen = ({ navigation }: StackScreenProps) => { ); }; -const SimpleStack = createStackNavigator(); +const TransparentStack = createStackNavigator(); -type Props = Partial> & - StackScreenProps; +type Props = StackScreenProps; -export default function SimpleStackScreen({ navigation, ...rest }: Props) { +export default function TransparentStackScreen({ navigation }: Props) { React.useLayoutEffect(() => { navigation.setOptions({ headerShown: false, @@ -83,13 +84,15 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) { }, [navigation]); return ( - - + - - + ); } diff --git a/packages/stack/src/navigators/createStackNavigator.tsx b/packages/stack/src/navigators/createStackNavigator.tsx index 3b84e341..8fd15eec 100644 --- a/packages/stack/src/navigators/createStackNavigator.tsx +++ b/packages/stack/src/navigators/createStackNavigator.tsx @@ -59,7 +59,7 @@ function StackNavigator({ headerMode: headerMode && headerMode !== 'none' ? headerMode - : rest.mode !== 'modal' && + : options.animationPresentation !== 'modal' && Platform.OS === 'ios' && options.header === undefined ? 'float' diff --git a/packages/stack/src/types.tsx b/packages/stack/src/types.tsx index 0c13472e..efa5ce33 100644 --- a/packages/stack/src/types.tsx +++ b/packages/stack/src/types.tsx @@ -102,7 +102,7 @@ export type SceneProgress = { export type StackHeaderMode = 'float' | 'screen'; -export type StackCardMode = 'card' | 'modal'; +export type StackPresentationMode = 'card' | 'modal'; export type StackHeaderOptions = HeaderOptions & { /** @@ -238,6 +238,14 @@ export type StackNavigationOptions = StackHeaderOptions & * Defaults to `true` on Android and iOS, `false` on Web. */ animationEnabled?: boolean; + /** + * Whether this screen should be presented as a modal or a regular card. + * If you haven't customized the animations, the animation will be: + * - If set to 'modal' - modal animation on iOS and Android + * - If set to 'card' - horizontal slide animation on iOS, OS-default animation on Android + * Defaults to 'card' + */ + animationPresentation?: 'card' | 'modal'; /** * The type of animation to use when this screen replaces another screen. Defaults to `push`. * When `pop` is used, the `pop` animation is applied to the screen being replaced. @@ -268,7 +276,6 @@ export type StackNavigationOptions = StackHeaderOptions & }; export type StackNavigationConfig = { - mode?: StackCardMode; /** * If `false`, the keyboard will NOT automatically dismiss when navigating to a new screen. * Defaults to `true`. diff --git a/packages/stack/src/views/Stack/CardContainer.tsx b/packages/stack/src/views/Stack/CardContainer.tsx index be457295..b5f1e9d2 100644 --- a/packages/stack/src/views/Stack/CardContainer.tsx +++ b/packages/stack/src/views/Stack/CardContainer.tsx @@ -14,7 +14,7 @@ import ModalPresentationContext from '../../utils/ModalPresentationContext'; import type { Layout, StackHeaderMode, - StackCardMode, + StackPresentationMode, TransitionPreset, Scene, } from '../../types'; @@ -58,7 +58,7 @@ type Props = TransitionPreset & { gestureEnabled?: boolean; gestureResponseDistance?: number; gestureVelocityImpact?: number; - mode: StackCardMode; + animationPresentation?: StackPresentationMode; headerMode: StackHeaderMode; headerShown: boolean; hasAbsoluteFloatHeader: boolean; @@ -88,7 +88,7 @@ function CardContainer({ gestureVelocityImpact, getPreviousScene, getFocusedRoute, - mode, + animationPresentation, headerDarkContent, headerMode, headerShown, @@ -246,7 +246,9 @@ function CardContainer({ accessibilityElementsHidden={!focused} importantForAccessibility={focused ? 'auto' : 'no-hide-descendants'} pointerEvents={active ? 'box-none' : pointerEvents} - pageOverflowEnabled={headerMode !== 'float' && mode === 'card'} + pageOverflowEnabled={ + headerMode !== 'float' && animationPresentation !== 'modal' + } headerDarkContent={headerDarkContent} containerStyle={ hasAbsoluteFloatHeader && headerMode !== 'screen' diff --git a/packages/stack/src/views/Stack/CardStack.tsx b/packages/stack/src/views/Stack/CardStack.tsx index 728a2fa3..1c9982d6 100755 --- a/packages/stack/src/views/Stack/CardStack.tsx +++ b/packages/stack/src/views/Stack/CardStack.tsx @@ -36,7 +36,6 @@ import { import getDistanceForDirection from '../../utils/getDistanceForDirection'; import type { Layout, - StackCardMode, StackDescriptorMap, StackNavigationOptions, StackDescriptor, @@ -48,7 +47,6 @@ type GestureValues = { }; type Props = { - mode: StackCardMode; insets: EdgeInsets; state: StackNavigationState; descriptors: StackDescriptorMap; @@ -124,27 +122,25 @@ const getHeaderHeights = ( }; const getDistanceFromOptions = ( - mode: StackCardMode, layout: Layout, descriptor?: StackDescriptor ) => { const { - gestureDirection = mode === 'modal' + animationPresentation, + gestureDirection = animationPresentation === 'modal' ? ModalTransition.gestureDirection : DefaultTransition.gestureDirection, - } = descriptor?.options || {}; + } = (descriptor?.options || {}) as StackNavigationOptions; return getDistanceForDirection(layout, gestureDirection); }; const getProgressFromGesture = ( - mode: StackCardMode, gesture: Animated.Value, layout: Layout, descriptor?: StackDescriptor ) => { const distance = getDistanceFromOptions( - mode, { // Make sure that we have a non-zero distance, otherwise there will be incorrect progress // This causes blank screen on web if it was previously inside container with display: none @@ -185,7 +181,7 @@ export default class CardStack extends React.Component { new Animated.Value( props.openingRouteKeys.includes(curr.key) && animationEnabled !== false - ? getDistanceFromOptions(props.mode, state.layout, descriptor) + ? getDistanceFromOptions(state.layout, descriptor) : 0 ); @@ -224,14 +220,12 @@ export default class CardStack extends React.Component { descriptor, progress: { current: getProgressFromGesture( - props.mode, currentGesture, state.layout, descriptor ), next: nextGesture ? getProgressFromGesture( - props.mode, nextGesture, state.layout, nextDescriptor @@ -239,7 +233,6 @@ export default class CardStack extends React.Component { : undefined, previous: previousGesture ? getProgressFromGesture( - props.mode, previousGesture, state.layout, previousDescriptor @@ -372,9 +365,7 @@ export default class CardStack extends React.Component { render() { const { - mode, insets, - descriptors, state, routes, closingRouteKeys, @@ -402,32 +393,8 @@ export default class CardStack extends React.Component { const { scenes, layout, gestures, headerHeights } = this.state; const focusedRoute = state.routes[state.index]; - const focusedDescriptor = descriptors[focusedRoute.key]; - const focusedOptions = focusedDescriptor ? focusedDescriptor.options : {}; const focusedHeaderHeight = headerHeights[focusedRoute.key]; - let defaultTransitionPreset = - mode === 'modal' ? ModalTransition : DefaultTransition; - - let activeScreensLimit = 1; - - for (let i = scenes.length - 1; i >= 0; i--) { - const { - // By default, we don't want to detach the previous screen of the active one for modals - detachPreviousScreen = mode === 'modal' || - scenes[i].descriptor.options.cardStyleInterpolator === - forModalPresentationIOS - ? i !== scenes.length - 1 - : true, - } = scenes[i].descriptor.options; - - if (detachPreviousScreen === false) { - activeScreensLimit++; - } else { - break; - } - } - const isFloatHeaderAbsolute = this.state.scenes.slice(-2).some((scene) => { const options = scene.descriptor.options ?? {}; const { @@ -447,33 +414,15 @@ export default class CardStack extends React.Component { return false; }); - const floatingHeader = ( - - {renderHeader({ - mode: 'float', - layout, - scenes, - getPreviousScene: this.getPreviousScene, - getFocusedRoute: this.getFocusedRoute, - onContentHeightChange: this.handleHeaderLayout, - styleInterpolator: - focusedOptions.headerStyleInterpolator !== undefined - ? focusedOptions.headerStyleInterpolator - : defaultTransitionPreset.headerStyleInterpolator, - style: [ - styles.floating, - isFloatHeaderAbsolute && [ - // Without this, the header buttons won't be touchable on Android when headerTransparent: true - { height: focusedHeaderHeight }, - styles.absolute, - ], - ], - })} - - ); - - const cardTransitionConfigsList = routes.map((_, index, self) => { + const transitionConfigsList = routes.map((_, index, self) => { const scene = scenes[index]; + const options = + scene.descriptor?.options || ({} as StackNavigationOptions); + + let defaultTransitionPreset = + options.animationPresentation === 'modal' + ? ModalTransition + : DefaultTransition; const { animationEnabled, @@ -485,9 +434,7 @@ export default class CardStack extends React.Component { headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator, cardOverlayEnabled = Platform.OS !== 'ios' || cardStyleInterpolator === forModalPresentationIOS, - } = scene.descriptor - ? scene.descriptor.options - : ({} as StackNavigationOptions); + } = options; let transitionConfig = { gestureDirection, @@ -534,6 +481,49 @@ export default class CardStack extends React.Component { return transitionConfig; }); + let activeScreensLimit = 1; + + for (let i = scenes.length - 1; i >= 0; i--) { + const { options } = scenes[i].descriptor; + const { + // By default, we don't want to detach the previous screen of the active one for modals + detachPreviousScreen = options.animationPresentation === 'modal' || + transitionConfigsList[i].cardStyleInterpolator === + forModalPresentationIOS + ? i !== scenes.length - 1 + : true, + } = options; + + if (detachPreviousScreen === false) { + activeScreensLimit++; + } else { + break; + } + } + + const focusedTransitionConfig = transitionConfigsList[state.index]; + const floatingHeader = ( + + {renderHeader({ + mode: 'float', + layout, + scenes, + getPreviousScene: this.getPreviousScene, + getFocusedRoute: this.getFocusedRoute, + onContentHeightChange: this.handleHeaderLayout, + styleInterpolator: focusedTransitionConfig.headerStyleInterpolator, + style: [ + styles.floating, + isFloatHeaderAbsolute && [ + // Without this, the header buttons won't be touchable on Android when headerTransparent: true + { height: focusedHeaderHeight }, + styles.absolute, + ], + ], + })} + + ); + return ( {isFloatHeaderAbsolute ? null : floatingHeader} @@ -583,9 +573,10 @@ export default class CardStack extends React.Component { : 1; } - const transitionConfig = cardTransitionConfigsList[index]; + const transitionConfig = transitionConfigsList[index]; const { + animationPresentation, gestureResponseDistance, gestureVelocityImpact, headerShown = true, @@ -626,7 +617,7 @@ export default class CardStack extends React.Component { for (let i = index - 1; i >= 0; i--) { const cardStyleInterpolatorCurrent = - cardTransitionConfigsList[i].cardStyleInterpolator; + transitionConfigsList[i].cardStyleInterpolator; if ( cardStyleInterpolatorCurrent !== @@ -673,7 +664,6 @@ export default class CardStack extends React.Component { onHeaderHeightChange={this.handleHeaderLayout} getPreviousScene={this.getPreviousScene} getFocusedRoute={this.getFocusedRoute} - mode={mode} headerMode={headerMode} headerShown={headerShown} headerDarkContent={headerDarkContent} @@ -688,6 +678,7 @@ export default class CardStack extends React.Component { onTransitionEnd={onTransitionEnd} gestureEnabled={index !== 0 && getGesturesEnabled({ route })} gestureVelocityImpact={gestureVelocityImpact} + animationPresentation={animationPresentation} {...transitionConfig} /> diff --git a/packages/stack/src/views/Stack/StackView.tsx b/packages/stack/src/views/Stack/StackView.tsx index aa3c86af..2fcd5d9b 100644 --- a/packages/stack/src/views/Stack/StackView.tsx +++ b/packages/stack/src/views/Stack/StackView.tsx @@ -438,7 +438,6 @@ export default class StackView extends React.Component { state, navigation, keyboardHandlingEnabled, - mode = 'card', // eslint-disable-next-line @typescript-eslint/no-unused-vars descriptors: _, ...rest @@ -462,7 +461,6 @@ export default class StackView extends React.Component { {(isParentHeaderShown) => (