Skip to content
This repository was archived by the owner on Feb 25, 2020. It is now read-only.

Commit 243839d

Browse files
committed
feat: add iOS modal presentation style
1 parent e84d62f commit 243839d

File tree

9 files changed

+169
-23
lines changed

9 files changed

+169
-23
lines changed

example/App.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import WipeStack from './src/WipeStack';
1818
import ImageStack from './src/ImageStack';
1919
import TransparentStack from './src/TransparentStack';
2020
import ModalStack from './src/ModalStack';
21+
import ModalPresentation from './src/ModalPresentation';
2122
import LifecycleInteraction from './src/LifecycleInteraction';
2223
import GestureInteraction from './src/GestureInteraction';
2324
import SwitchWithStacks from './src/SwitchWithStacks';
@@ -43,6 +44,11 @@ const data = [
4344
{ component: WipeStack, title: 'Wipe Preset', routeName: 'Wipe' },
4445
{ component: ImageStack, title: 'Image', routeName: 'ImageStack' },
4546
{ component: ModalStack, title: 'Modal', routeName: 'ModalStack' },
47+
{
48+
component: ModalPresentation,
49+
title: 'Modal (iOS style)',
50+
routeName: 'ModalPresentation',
51+
},
4652
{ component: FullScreen, title: 'Full Screen', routeName: 'FullScreen' },
4753
{
4854
component: LifecycleInteraction,

example/src/ModalPresentation.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import * as React from 'react';
2+
import { Button, View, Text } from 'react-native';
3+
import {
4+
createStackNavigator,
5+
TransitionPresets,
6+
} from 'react-navigation-stack';
7+
8+
class ListScreen extends React.Component {
9+
static navigationOptions = {
10+
title: 'My Modal',
11+
};
12+
13+
render() {
14+
return (
15+
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
16+
<Text>List Screen</Text>
17+
<Text>A list may go here</Text>
18+
<Button
19+
title="Go to Details"
20+
onPress={() => this.props.navigation.navigate('Details')}
21+
/>
22+
<Button
23+
title="Go back to all examples"
24+
onPress={() => this.props.navigation.navigate('Home')}
25+
/>
26+
</View>
27+
);
28+
}
29+
}
30+
31+
class DetailsScreen extends React.Component {
32+
static navigationOptions = {
33+
header: null,
34+
};
35+
36+
render() {
37+
return (
38+
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
39+
<Text>Details Screen</Text>
40+
<Button
41+
title="Go to Details... again"
42+
onPress={() => this.props.navigation.push('Details')}
43+
/>
44+
<Button
45+
title="Go to List"
46+
onPress={() => this.props.navigation.navigate('List')}
47+
/>
48+
<Button
49+
title="Go back"
50+
onPress={() => this.props.navigation.goBack()}
51+
/>
52+
<Button
53+
title="Go back to all examples"
54+
onPress={() => this.props.navigation.navigate('Home')}
55+
/>
56+
</View>
57+
);
58+
}
59+
}
60+
61+
export default createStackNavigator(
62+
{
63+
List: ListScreen,
64+
Details: DetailsScreen,
65+
},
66+
{
67+
...TransitionPresets.ModalPresentationIOS,
68+
mode: 'modal',
69+
defaultNavigationOptions: {
70+
cardOverlayEnabled: true,
71+
gesturesEnabled: true,
72+
},
73+
}
74+
);

src/TransitionConfigs/CardStyleInterpolators.tsx

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { I18nManager } from 'react-native';
22
import Animated from 'react-native-reanimated';
33
import { CardInterpolationProps, CardInterpolatedStyle } from '../types';
4+
import { getStatusBarHeight } from 'react-native-safe-area-view';
45

5-
const { cond, multiply, interpolate } = Animated;
6+
const { cond, add, multiply, interpolate } = Animated;
67

78
/**
89
* Standard iOS-style slide in from the right.
@@ -71,6 +72,58 @@ export function forVerticalIOS({
7172
};
7273
}
7374

75+
/**
76+
* Standard iOS-style modal animation in iOS 13.
77+
*/
78+
export function forModalPresentationIOS({
79+
index,
80+
progress: { current, next },
81+
layouts: { screen },
82+
}: CardInterpolationProps): CardInterpolatedStyle {
83+
const topOffset = 10;
84+
const statusBarHeight = getStatusBarHeight(screen.width > screen.height);
85+
const aspectRatio = screen.height / screen.width;
86+
87+
const progress = add(current, next ? next : 0);
88+
89+
const translateY = interpolate(progress, {
90+
inputRange: [0, 1, 2],
91+
outputRange: [
92+
screen.height,
93+
index === 0 ? 0 : topOffset,
94+
(index === 0 ? statusBarHeight : 0) - topOffset * aspectRatio,
95+
],
96+
});
97+
98+
const overlayOpacity = interpolate(progress, {
99+
inputRange: [0, 1, 2],
100+
outputRange: [0, 0.3, 1],
101+
});
102+
103+
const scale = interpolate(progress, {
104+
inputRange: [0, 1, 2],
105+
outputRange: [1, 1, screen.width ? 1 - (topOffset * 2) / screen.width : 1],
106+
});
107+
108+
const borderRadius =
109+
index === 0
110+
? interpolate(progress, {
111+
inputRange: [0, 1, 2],
112+
outputRange: [0, 0, 10],
113+
})
114+
: 10;
115+
116+
return {
117+
cardStyle: {
118+
borderTopLeftRadius: borderRadius,
119+
borderTopRightRadius: borderRadius,
120+
marginTop: index === 0 ? 0 : statusBarHeight,
121+
transform: [{ translateY }, { scale }],
122+
},
123+
overlayStyle: { opacity: overlayOpacity },
124+
};
125+
}
126+
74127
/**
75128
* Standard Android-style fade in from the bottom for Android Oreo.
76129
*/

src/TransitionConfigs/TransitionPresets.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
forVerticalIOS,
44
forWipeFromBottomAndroid,
55
forFadeFromBottomAndroid,
6+
forModalPresentationIOS,
67
} from './CardStyleInterpolators';
78
import { forNoAnimation, forFade } from './HeaderStyleInterpolators';
89
import {
@@ -38,6 +39,17 @@ export const ModalSlideFromBottomIOS: TransitionPreset = {
3839
headerStyleInterpolator: forNoAnimation,
3940
};
4041

42+
// Standard iOS modal presentation style (introduced in iOS 13)
43+
export const ModalPresentationIOS: TransitionPreset = {
44+
direction: 'vertical',
45+
transitionSpec: {
46+
open: TransitionIOSSpec,
47+
close: TransitionIOSSpec,
48+
},
49+
cardStyleInterpolator: forModalPresentationIOS,
50+
headerStyleInterpolator: forNoAnimation,
51+
};
52+
4153
// Standard Android navigation transition when opening or closing an Activity on Android < 9
4254
export const FadeFromBottomAndroid: TransitionPreset = {
4355
direction: 'vertical',

src/navigators/__tests__/__snapshots__/NestedNavigator.test.tsx.snap

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,8 @@ Array [
100100
style={
101101
Array [
102102
Object {
103-
"bottom": 0,
104-
"left": 0,
105-
"position": "absolute",
106-
"right": 0,
107-
"top": 0,
103+
"flex": 1,
104+
"overflow": "hidden",
108105
},
109106
Object {
110107
"transform": Array [
@@ -256,11 +253,8 @@ Array [
256253
style={
257254
Array [
258255
Object {
259-
"bottom": 0,
260-
"left": 0,
261-
"position": "absolute",
262-
"right": 0,
263-
"top": 0,
256+
"flex": 1,
257+
"overflow": "hidden",
264258
},
265259
Object {
266260
"transform": Array [

src/navigators/__tests__/__snapshots__/StackNavigator.test.tsx.snap

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,8 @@ Array [
100100
style={
101101
Array [
102102
Object {
103-
"bottom": 0,
104-
"left": 0,
105-
"position": "absolute",
106-
"right": 0,
107-
"top": 0,
103+
"flex": 1,
104+
"overflow": "hidden",
108105
},
109106
Object {
110107
"transform": Array [
@@ -419,11 +416,8 @@ Array [
419416
style={
420417
Array [
421418
Object {
422-
"bottom": 0,
423-
"left": 0,
424-
"position": "absolute",
425-
"right": 0,
426-
"top": 0,
419+
"flex": 1,
420+
"overflow": "hidden",
427421
},
428422
Object {
429423
"transform": Array [

src/types.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ export type TransitionSpec =
162162
| { timing: 'timing'; config: TimingConfig };
163163

164164
export type CardInterpolationProps = {
165+
index: number;
165166
progress: {
166167
current: Animated.Node<number>;
167168
next?: Animated.Node<number>;

src/views/Stack/Card.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import StackGestureContext from '../../utils/StackGestureContext';
1919
import PointerEventsView from './PointerEventsView';
2020

2121
type Props = ViewProps & {
22+
index: number;
2223
active: boolean;
2324
closing?: boolean;
2425
transparent?: boolean;
@@ -404,11 +405,13 @@ export default class Card extends React.Component<Props> {
404405
private getInterpolatedStyle = memoize(
405406
(
406407
styleInterpolator: CardStyleInterpolator,
408+
index: number,
407409
current: Animated.Node<number>,
408410
next: Animated.Node<number> | undefined,
409411
layout: Layout
410412
) =>
411413
styleInterpolator({
414+
index,
412415
progress: {
413416
current,
414417
next,
@@ -460,6 +463,7 @@ export default class Card extends React.Component<Props> {
460463

461464
render() {
462465
const {
466+
index,
463467
active,
464468
transparent,
465469
layout,
@@ -481,7 +485,13 @@ export default class Card extends React.Component<Props> {
481485
cardStyle,
482486
overlayStyle,
483487
shadowStyle,
484-
} = this.getInterpolatedStyle(styleInterpolator, current, next, layout);
488+
} = this.getInterpolatedStyle(
489+
styleInterpolator,
490+
index,
491+
current,
492+
next,
493+
layout
494+
);
485495

486496
const handleGestureEvent =
487497
direction === 'vertical'
@@ -509,7 +519,7 @@ export default class Card extends React.Component<Props> {
509519
onHandlerStateChange={handleGestureEvent}
510520
{...this.gestureActivationCriteria()}
511521
>
512-
<Animated.View style={[StyleSheet.absoluteFill, cardStyle]}>
522+
<Animated.View style={[styles.container, cardStyle]}>
513523
{shadowEnabled && !transparent ? (
514524
<Animated.View
515525
style={[styles.shadow, shadowStyle]}

src/views/Stack/StackItem.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export default class StackItem extends React.PureComponent<Props> {
7878

7979
render() {
8080
const {
81+
index,
8182
layout,
8283
active,
8384
focused,
@@ -110,6 +111,7 @@ export default class StackItem extends React.PureComponent<Props> {
110111

111112
return (
112113
<Card
114+
index={index}
113115
active={active}
114116
transparent={cardTransparent}
115117
direction={direction}

0 commit comments

Comments
 (0)