feat: add 'transparentModal' presentation to JS stack

This commit is contained in:
Satyajit Sahoo
2021-05-21 14:20:27 +02:00
parent 2cb44a5663
commit 3d147401e8
7 changed files with 130 additions and 57 deletions

View File

@@ -361,6 +361,29 @@ export function forBottomSheetAndroid({
};
}
/**
* Simple fade animation for dialogs
*/
export function forFadeFromCenter({
current: { progress },
}: StackCardInterpolationProps): StackCardInterpolatedStyle {
return {
cardStyle: {
opacity: progress.interpolate({
inputRange: [0, 0.5, 0.9, 1],
outputRange: [0, 0.25, 0.7, 1],
}),
},
overlayStyle: {
opacity: progress.interpolate({
inputRange: [0, 1],
outputRange: [0, 0.5],
extrapolate: 'clamp',
}),
},
};
}
export function forNoAnimation(): StackCardInterpolatedStyle {
return {};
}

View File

@@ -7,6 +7,7 @@ import {
forRevealFromBottomAndroid,
forFadeFromBottomAndroid,
forBottomSheetAndroid,
forFadeFromCenter as forFadeCard,
} from './CardStyleInterpolators';
import { forFade } from './HeaderStyleInterpolators';
import {
@@ -114,6 +115,19 @@ export const BottomSheetAndroid: TransitionPreset = {
headerStyleInterpolator: forFade,
};
/**
* Fade transition for transparent modals.
*/
export const ModalFadeTransition: TransitionPreset = {
gestureDirection: 'vertical',
transitionSpec: {
open: BottomSheetSlideInSpec,
close: BottomSheetSlideOutSpec,
},
cardStyleInterpolator: forFadeCard,
headerStyleInterpolator: forFade,
};
/**
* Default navigation transition for the current platform.
*/

View File

@@ -256,11 +256,16 @@ export type StackNavigationOptions = StackHeaderOptions &
* - `modal`: Use Modal animations. This changes a few things:
* - Sets `headerMode` to `screen` for the screen unless specified otherwise.
* - Changes the screen animation to match the platform behavior for modals.
* - `transparentModal`: Similar to `modal`. This changes following things:
* - Sets `headerMode` to `screen` for the screen unless specified otherwise.
* - Sets background color of the screen to transparent, so previous screen is visible
* - Adjusts the `detachPreviousScreen` option so that the previous screen stays rendered.
* - Prevents the previous screen from animating from its last position.
* - Changes the screen animation to a vertical slide animation.
*
* Defaults to 'card'.
*/
presentation?: 'card' | 'modal';
presentation?: 'card' | 'modal' | 'transparentModal';
/**
* Whether transition animation should be enabled the screen.
* If you set it to `false`, the screen won't animate when pushing or popping.

View File

@@ -46,10 +46,10 @@ type Props = ViewProps & {
gestureDirection: GestureDirection;
onOpen: () => void;
onClose: () => void;
onTransition?: (props: { closing: boolean; gesture: boolean }) => void;
onGestureBegin?: () => void;
onGestureCanceled?: () => void;
onGestureEnd?: () => void;
onTransition: (props: { closing: boolean; gesture: boolean }) => void;
onGestureBegin: () => void;
onGestureCanceled: () => void;
onGestureEnd: () => void;
children: React.ReactNode;
overlay: (props: {
style: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;

View File

@@ -34,14 +34,14 @@ type Props = {
renderScene: (props: { route: Route<string> }) => React.ReactNode;
onOpenRoute: (props: { route: Route<string> }) => void;
onCloseRoute: (props: { route: Route<string> }) => void;
onTransitionStart?: (
onTransitionStart: (
props: { route: Route<string> },
closing: boolean
) => void;
onTransitionEnd?: (props: { route: Route<string> }, closing: boolean) => void;
onGestureStart?: (props: { route: Route<string> }) => void;
onGestureEnd?: (props: { route: Route<string> }) => void;
onGestureCancel?: (props: { route: Route<string> }) => void;
onTransitionEnd: (props: { route: Route<string> }, closing: boolean) => void;
onGestureStart: (props: { route: Route<string> }) => void;
onGestureEnd: (props: { route: Route<string> }) => void;
onGestureCancel: (props: { route: Route<string> }) => void;
hasAbsoluteFloatHeader: boolean;
headerHeight: number;
onHeaderHeightChange: (props: {
@@ -49,6 +49,8 @@ type Props = {
height: number;
}) => void;
isParentHeaderShown: boolean;
isNextScreenTransparent: boolean;
detachCurrentScreen: boolean;
};
const EPSILON = 0.1;
@@ -66,6 +68,8 @@ function CardContainer({
headerHeight,
onHeaderHeightChange,
isParentHeaderShown,
isNextScreenTransparent,
detachCurrentScreen,
interpolationIndex,
layout,
onCloseRoute,
@@ -102,35 +106,35 @@ function CardContainer({
const handleOpen = () => {
const { route } = scene.descriptor;
onTransitionEnd?.({ route }, false);
onTransitionEnd({ route }, false);
onOpenRoute({ route });
};
const handleClose = () => {
const { route } = scene.descriptor;
onTransitionEnd?.({ route }, true);
onTransitionEnd({ route }, true);
onCloseRoute({ route });
};
const handleGestureBegin = () => {
const { route } = scene.descriptor;
onPageChangeStart?.();
onGestureStart?.({ route });
onPageChangeStart();
onGestureStart({ route });
};
const handleGestureCanceled = () => {
const { route } = scene.descriptor;
onPageChangeCancel?.();
onGestureCancel?.({ route });
onPageChangeCancel();
onGestureCancel({ route });
};
const handleGestureEnd = () => {
const { route } = scene.descriptor;
onGestureEnd?.({ route });
onGestureEnd({ route });
};
const handleTransition = ({
@@ -182,7 +186,6 @@ function CardContainer({
const {
presentation,
detachPreviousScreen,
animationEnabled,
cardOverlay,
cardOverlayEnabled,
@@ -222,7 +225,7 @@ function CardContainer({
insets={insets}
gesture={gesture}
current={scene.progress.current}
next={scene.progress.next}
next={isNextScreenTransparent ? undefined : scene.progress.next}
closing={closing}
onOpen={handleOpen}
onClose={handleClose}
@@ -248,7 +251,15 @@ function CardContainer({
? { marginTop: headerHeight }
: null
}
contentStyle={[{ backgroundColor: colors.background }, cardStyle]}
contentStyle={[
{
backgroundColor:
presentation === 'transparentModal'
? 'transparent'
: colors.background,
},
cardStyle,
]}
style={[
{
// This is necessary to avoid unfocused larger pages increasing scroll area
@@ -258,8 +269,8 @@ function CardContainer({
// Hide unfocused screens when animation isn't enabled
// This is also necessary for a11y on web
animationEnabled === false &&
detachPreviousScreen !== false &&
presentation !== 'modal' &&
isNextScreenTransparent === false &&
detachCurrentScreen !== false &&
!focused
? 'none'
: 'flex',

View File

@@ -24,6 +24,7 @@ import CardContainer from './CardContainer';
import {
DefaultTransition,
ModalTransition,
ModalFadeTransition,
} from '../../TransitionConfigs/TransitionPresets';
import {
forModalPresentationIOS,
@@ -63,9 +64,9 @@ type Props = {
closing: boolean
) => void;
onTransitionEnd: (props: { route: Route<string> }, closing: boolean) => void;
onGestureStart?: (props: { route: Route<string> }) => void;
onGestureEnd?: (props: { route: Route<string> }) => void;
onGestureCancel?: (props: { route: Route<string> }) => void;
onGestureStart: (props: { route: Route<string> }) => void;
onGestureEnd: (props: { route: Route<string> }) => void;
onGestureCancel: (props: { route: Route<string> }) => void;
detachInactiveScreens?: boolean;
};
@@ -225,6 +226,8 @@ export default class CardStack extends React.Component<Props, State> {
let defaultTransitionPreset =
optionsForTransitionConfig.presentation === 'modal'
? ModalTransition
: optionsForTransitionConfig.presentation === 'transparentModal'
? ModalFadeTransition
: DefaultTransition;
const {
@@ -238,7 +241,8 @@ export default class CardStack extends React.Component<Props, State> {
? forNoAnimationCard
: defaultTransitionPreset.cardStyleInterpolator,
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
cardOverlayEnabled = Platform.OS !== 'ios' ||
cardOverlayEnabled = (Platform.OS !== 'ios' &&
optionsForTransitionConfig.presentation !== 'transparentModal') ||
cardStyleInterpolator === forModalPresentationIOS,
} = optionsForTransitionConfig;
@@ -246,6 +250,7 @@ export default class CardStack extends React.Component<Props, State> {
descriptor.options.headerMode ??
(!(
optionsForTransitionConfig.presentation === 'modal' ||
optionsForTransitionConfig.presentation === 'transparentModal' ||
cardStyleInterpolator === forModalPresentationIOS
) &&
Platform.OS === 'ios' &&
@@ -461,7 +466,7 @@ export default class CardStack extends React.Component<Props, State> {
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.presentation === 'modal' ||
detachPreviousScreen = options.presentation === 'transparentModal' ||
options.cardStyleInterpolator === forModalPresentationIOS
? i !== scenes.length - 1
: true,
@@ -577,6 +582,14 @@ export default class CardStack extends React.Component<Props, State> {
interpolationIndex++;
}
const isNextScreenTransparent =
scenes[index + 1]?.descriptor.options.presentation ===
'transparentModal';
const detachCurrentScreen =
scenes[index + 1]?.descriptor.options.detachPreviousScreen !==
false;
return (
<MaybeScreen
key={route.key}
@@ -616,6 +629,8 @@ export default class CardStack extends React.Component<Props, State> {
onCloseRoute={onCloseRoute}
onTransitionStart={onTransitionStart}
onTransitionEnd={onTransitionEnd}
isNextScreenTransparent={isNextScreenTransparent}
detachCurrentScreen={detachCurrentScreen}
/>
</MaybeScreen>
);