mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-04-24 04:25:34 +08:00
feat: add 'transparentModal' presentation to JS stack
This commit is contained in:
@@ -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 {};
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>>;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user