mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-11 22:33:32 +08:00
feat: use modal presentation style for modals on iOS by default
This commit is contained in:
@@ -116,6 +116,6 @@ export const DefaultTransition = Platform.select({
|
||||
* Default modal transition for the current platform.
|
||||
*/
|
||||
export const ModalTransition = Platform.select({
|
||||
ios: ModalSlideFromBottomIOS,
|
||||
ios: ModalPresentationIOS,
|
||||
default: DefaultTransition,
|
||||
});
|
||||
|
||||
5
packages/stack/src/utils/ModalPresentationContext.tsx
Normal file
5
packages/stack/src/utils/ModalPresentationContext.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as React from 'react';
|
||||
|
||||
const ModalPresentationContext = React.createContext(false);
|
||||
|
||||
export default ModalPresentationContext;
|
||||
@@ -1,20 +1,21 @@
|
||||
import * as React from 'react';
|
||||
import { StackActions } from '@react-navigation/native';
|
||||
import { StackActions, useNavigationState } from '@react-navigation/native';
|
||||
|
||||
import HeaderSegment from './HeaderSegment';
|
||||
import HeaderTitle from './HeaderTitle';
|
||||
import HeaderShownContext from '../../utils/HeaderShownContext';
|
||||
import ModalPresentationContext from '../../utils/ModalPresentationContext';
|
||||
import debounce from '../../utils/debounce';
|
||||
import type { StackHeaderProps, StackHeaderTitleProps } from '../../types';
|
||||
|
||||
export default React.memo(function Header(props: StackHeaderProps) {
|
||||
const {
|
||||
scene,
|
||||
previous,
|
||||
layout,
|
||||
insets,
|
||||
navigation,
|
||||
styleInterpolator,
|
||||
} = props;
|
||||
export default React.memo(function Header({
|
||||
scene,
|
||||
previous,
|
||||
layout,
|
||||
insets,
|
||||
navigation,
|
||||
styleInterpolator,
|
||||
}: StackHeaderProps) {
|
||||
const { options } = scene.descriptor;
|
||||
const title =
|
||||
typeof options.headerTitle !== 'function' &&
|
||||
@@ -54,6 +55,15 @@ export default React.memo(function Header(props: StackHeaderProps) {
|
||||
[navigation, scene.route.key]
|
||||
);
|
||||
|
||||
const isModal = React.useContext(ModalPresentationContext);
|
||||
const isParentHeaderShown = React.useContext(HeaderShownContext);
|
||||
const isFirstRouteInParent = useNavigationState(
|
||||
(state) => state.routes[0].key === scene.route.key
|
||||
);
|
||||
|
||||
const statusBarHeight =
|
||||
(isModal && !isFirstRouteInParent) || isParentHeaderShown ? 0 : insets.top;
|
||||
|
||||
return (
|
||||
<HeaderSegment
|
||||
{...options}
|
||||
@@ -67,6 +77,7 @@ export default React.memo(function Header(props: StackHeaderProps) {
|
||||
? (props: StackHeaderTitleProps) => <HeaderTitle {...props} />
|
||||
: options.headerTitle
|
||||
}
|
||||
headerStatusBarHeight={statusBarHeight}
|
||||
onGoBack={previous ? goBack : undefined}
|
||||
styleInterpolator={styleInterpolator}
|
||||
/>
|
||||
|
||||
@@ -11,7 +11,6 @@ import type { EdgeInsets } from 'react-native-safe-area-context';
|
||||
import type { Route } from '@react-navigation/native';
|
||||
import HeaderBackButton from './HeaderBackButton';
|
||||
import HeaderBackground from './HeaderBackground';
|
||||
import HeaderShownContext from '../../utils/HeaderShownContext';
|
||||
import memoize from '../../utils/memoize';
|
||||
import type {
|
||||
Layout,
|
||||
@@ -22,8 +21,12 @@ import type {
|
||||
Scene,
|
||||
} from '../../types';
|
||||
|
||||
type Props = StackHeaderOptions & {
|
||||
type Props = Omit<
|
||||
StackHeaderOptions,
|
||||
'headerTitle' | 'headerStatusBarHeight'
|
||||
> & {
|
||||
headerTitle: (props: StackHeaderTitleProps) => React.ReactNode;
|
||||
headerStatusBarHeight: number;
|
||||
layout: Layout;
|
||||
insets: EdgeInsets;
|
||||
onGoBack?: () => void;
|
||||
@@ -81,8 +84,6 @@ export default function HeaderSegment(props: Props) {
|
||||
undefined
|
||||
);
|
||||
|
||||
const isParentHeaderShown = React.useContext(HeaderShownContext);
|
||||
|
||||
const handleTitleLayout = (e: LayoutChangeEvent) => {
|
||||
const { height, width } = e.nativeEvent.layout;
|
||||
|
||||
@@ -171,7 +172,7 @@ export default function HeaderSegment(props: Props) {
|
||||
headerRightContainerStyle: rightContainerStyle,
|
||||
headerTitleContainerStyle: titleContainerStyle,
|
||||
headerStyle: customHeaderStyle,
|
||||
headerStatusBarHeight = isParentHeaderShown ? 0 : insets.top,
|
||||
headerStatusBarHeight,
|
||||
styleInterpolator,
|
||||
} = props;
|
||||
|
||||
|
||||
@@ -90,7 +90,6 @@ const hasOpacityStyle = (style: any) => {
|
||||
|
||||
export default class Card extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
overlayEnabled: Platform.OS !== 'ios',
|
||||
shadowEnabled: true,
|
||||
gestureEnabled: true,
|
||||
gestureVelocityImpact: GESTURE_VELOCITY_IMPACT,
|
||||
|
||||
@@ -3,9 +3,11 @@ import { Animated, View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
||||
import { Route, useTheme } from '@react-navigation/native';
|
||||
import type { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
||||
import Card from './Card';
|
||||
import { forModalPresentationIOS } from '../../TransitionConfigs/CardStyleInterpolators';
|
||||
import HeaderHeightContext from '../../utils/HeaderHeightContext';
|
||||
import HeaderShownContext from '../../utils/HeaderShownContext';
|
||||
import PreviousSceneContext from '../../utils/PreviousSceneContext';
|
||||
import ModalPresentationContext from '../../utils/ModalPresentationContext';
|
||||
import type {
|
||||
Scene,
|
||||
Layout,
|
||||
@@ -29,7 +31,7 @@ type Props = TransitionPreset & {
|
||||
cardOverlay?: (props: {
|
||||
style: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
||||
}) => React.ReactNode;
|
||||
cardOverlayEnabled?: boolean;
|
||||
cardOverlayEnabled: boolean;
|
||||
cardShadowEnabled?: boolean;
|
||||
cardStyle?: StyleProp<ViewStyle>;
|
||||
getPreviousScene: (props: {
|
||||
@@ -184,6 +186,7 @@ function CardContainer({
|
||||
}, [pointerEvents, scene.progress.next]);
|
||||
|
||||
const previousScene = getPreviousScene({ route: scene.route });
|
||||
const isModalPresentation = cardStyleInterpolator === forModalPresentationIOS;
|
||||
|
||||
return (
|
||||
<Card
|
||||
@@ -236,8 +239,9 @@ function CardContainer({
|
||||
</HeaderShownContext.Provider>
|
||||
</PreviousSceneContext.Provider>
|
||||
</View>
|
||||
{headerMode !== 'float'
|
||||
? renderHeader({
|
||||
{headerMode !== 'float' ? (
|
||||
<ModalPresentationContext.Provider value={isModalPresentation}>
|
||||
{renderHeader({
|
||||
mode: 'screen',
|
||||
layout,
|
||||
insets,
|
||||
@@ -247,8 +251,9 @@ function CardContainer({
|
||||
gestureDirection,
|
||||
styleInterpolator: headerStyleInterpolator,
|
||||
onContentHeightChange: onHeaderHeightChange,
|
||||
})
|
||||
: null}
|
||||
})}
|
||||
</ModalPresentationContext.Provider>
|
||||
) : null}
|
||||
</View>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
StyleSheet,
|
||||
LayoutChangeEvent,
|
||||
Dimensions,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import type { EdgeInsets } from 'react-native-safe-area-context';
|
||||
import type {
|
||||
@@ -542,7 +543,7 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
headerShown = true,
|
||||
headerTransparent,
|
||||
cardShadowEnabled,
|
||||
cardOverlayEnabled,
|
||||
cardOverlayEnabled = Platform.OS !== 'ios' || mode === 'modal',
|
||||
cardOverlay,
|
||||
cardStyle,
|
||||
animationEnabled,
|
||||
|
||||
Reference in New Issue
Block a user