UNPKG

8.9 kBTypeScriptView Raw
1import { Animated, Platform } from 'react-native';
2
3import type {
4 StackCardInterpolatedStyle,
5 StackCardInterpolationProps,
6} from '../types';
7import conditional from '../utils/conditional';
8
9const { add, multiply } = Animated;
10
11/**
12 * Standard iOS-style slide in from the right.
13 */
14export function forHorizontalIOS({
15 current,
16 next,
17 inverted,
18 layouts: { screen },
19}: StackCardInterpolationProps): StackCardInterpolatedStyle {
20 const translateFocused = multiply(
21 current.progress.interpolate({
22 inputRange: [0, 1],
23 outputRange: [screen.width, 0],
24 extrapolate: 'clamp',
25 }),
26 inverted
27 );
28
29 const translateUnfocused = next
30 ? multiply(
31 next.progress.interpolate({
32 inputRange: [0, 1],
33 outputRange: [0, screen.width * -0.3],
34 extrapolate: 'clamp',
35 }),
36 inverted
37 )
38 : 0;
39
40 const overlayOpacity = current.progress.interpolate({
41 inputRange: [0, 1],
42 outputRange: [0, 0.07],
43 extrapolate: 'clamp',
44 });
45
46 const shadowOpacity = current.progress.interpolate({
47 inputRange: [0, 1],
48 outputRange: [0, 0.3],
49 extrapolate: 'clamp',
50 });
51
52 return {
53 cardStyle: {
54 transform: [
55 // Translation for the animation of the current card
56 { translateX: translateFocused },
57 // Translation for the animation of the card on top of this
58 { translateX: translateUnfocused },
59 ],
60 },
61 overlayStyle: { opacity: overlayOpacity },
62 shadowStyle: { shadowOpacity },
63 };
64}
65
66/**
67 * Standard iOS-style slide in from the bottom (used for modals).
68 */
69export function forVerticalIOS({
70 current,
71 inverted,
72 layouts: { screen },
73}: StackCardInterpolationProps): StackCardInterpolatedStyle {
74 const translateY = multiply(
75 current.progress.interpolate({
76 inputRange: [0, 1],
77 outputRange: [screen.height, 0],
78 extrapolate: 'clamp',
79 }),
80 inverted
81 );
82
83 return {
84 cardStyle: {
85 transform: [{ translateY }],
86 },
87 };
88}
89
90/**
91 * Standard iOS-style modal animation in iOS 13.
92 */
93export function forModalPresentationIOS({
94 index,
95 current,
96 next,
97 inverted,
98 layouts: { screen },
99 insets,
100}: StackCardInterpolationProps): StackCardInterpolatedStyle {
101 const hasNotchIos =
102 Platform.OS === 'ios' &&
103 !Platform.isPad &&
104 !Platform.isTV &&
105 insets.top > 20;
106 const isLandscape = screen.width > screen.height;
107 const topOffset = isLandscape ? 0 : 10;
108 const statusBarHeight = insets.top;
109 const aspectRatio = screen.height / screen.width;
110
111 const progress = add(
112 current.progress.interpolate({
113 inputRange: [0, 1],
114 outputRange: [0, 1],
115 extrapolate: 'clamp',
116 }),
117 next
118 ? next.progress.interpolate({
119 inputRange: [0, 1],
120 outputRange: [0, 1],
121 extrapolate: 'clamp',
122 })
123 : 0
124 );
125
126 const isFirst = index === 0;
127
128 const translateY = multiply(
129 progress.interpolate({
130 inputRange: [0, 1, 2],
131 outputRange: [
132 screen.height,
133 isFirst ? 0 : topOffset,
134 (isFirst ? statusBarHeight : 0) - topOffset * aspectRatio,
135 ],
136 }),
137 inverted
138 );
139
140 const overlayOpacity = progress.interpolate({
141 inputRange: [0, 1, 1.0001, 2],
142 outputRange: [0, 0.3, 1, 1],
143 });
144
145 const scale = isLandscape
146 ? 1
147 : progress.interpolate({
148 inputRange: [0, 1, 2],
149 outputRange: [
150 1,
151 1,
152 screen.width ? 1 - (topOffset * 2) / screen.width : 1,
153 ],
154 });
155
156 const borderRadius = isLandscape
157 ? 0
158 : isFirst
159 ? progress.interpolate({
160 inputRange: [0, 1, 1.0001, 2],
161 outputRange: [0, 0, hasNotchIos ? 38 : 0, 10],
162 })
163 : 10;
164
165 return {
166 cardStyle: {
167 overflow: 'hidden',
168 borderTopLeftRadius: borderRadius,
169 borderTopRightRadius: borderRadius,
170 // We don't need these for the animation
171 // But different border radius for corners improves animation perf
172 borderBottomLeftRadius: hasNotchIos ? borderRadius : 0,
173 borderBottomRightRadius: hasNotchIos ? borderRadius : 0,
174 marginTop: isFirst ? 0 : statusBarHeight,
175 marginBottom: isFirst ? 0 : topOffset,
176 transform: [{ translateY }, { scale }],
177 },
178 overlayStyle: { opacity: overlayOpacity },
179 };
180}
181
182/**
183 * Standard Android-style fade in from the bottom for Android Oreo.
184 */
185export function forFadeFromBottomAndroid({
186 current,
187 inverted,
188 layouts: { screen },
189 closing,
190}: StackCardInterpolationProps): StackCardInterpolatedStyle {
191 const translateY = multiply(
192 current.progress.interpolate({
193 inputRange: [0, 1],
194 outputRange: [screen.height * 0.08, 0],
195 extrapolate: 'clamp',
196 }),
197 inverted
198 );
199
200 const opacity = conditional(
201 closing,
202 current.progress,
203 current.progress.interpolate({
204 inputRange: [0, 0.5, 0.9, 1],
205 outputRange: [0, 0.25, 0.7, 1],
206 extrapolate: 'clamp',
207 })
208 );
209
210 return {
211 cardStyle: {
212 opacity,
213 transform: [{ translateY }],
214 },
215 };
216}
217
218/**
219 * Standard Android-style reveal from the bottom for Android Pie.
220 */
221export function forRevealFromBottomAndroid({
222 current,
223 next,
224 inverted,
225 layouts: { screen },
226}: StackCardInterpolationProps): StackCardInterpolatedStyle {
227 const containerTranslateY = multiply(
228 current.progress.interpolate({
229 inputRange: [0, 1],
230 outputRange: [screen.height, 0],
231 extrapolate: 'clamp',
232 }),
233 inverted
234 );
235
236 const cardTranslateYFocused = multiply(
237 current.progress.interpolate({
238 inputRange: [0, 1],
239 outputRange: [screen.height * (95.9 / 100) * -1, 0],
240 extrapolate: 'clamp',
241 }),
242 inverted
243 );
244
245 const cardTranslateYUnfocused = next
246 ? multiply(
247 next.progress.interpolate({
248 inputRange: [0, 1],
249 outputRange: [0, screen.height * (2 / 100) * -1],
250 extrapolate: 'clamp',
251 }),
252 inverted
253 )
254 : 0;
255
256 const overlayOpacity = current.progress.interpolate({
257 inputRange: [0, 0.36, 1],
258 outputRange: [0, 0.1, 0.1],
259 extrapolate: 'clamp',
260 });
261
262 return {
263 containerStyle: {
264 overflow: 'hidden',
265 transform: [{ translateY: containerTranslateY }],
266 },
267 cardStyle: {
268 transform: [
269 { translateY: cardTranslateYFocused },
270 { translateY: cardTranslateYUnfocused },
271 ],
272 },
273 overlayStyle: { opacity: overlayOpacity },
274 };
275}
276
277/**
278 * Standard Android-style zoom for Android 10.
279 */
280export function forScaleFromCenterAndroid({
281 current,
282 next,
283 closing,
284}: StackCardInterpolationProps): StackCardInterpolatedStyle {
285 const progress = add(
286 current.progress.interpolate({
287 inputRange: [0, 1],
288 outputRange: [0, 1],
289 extrapolate: 'clamp',
290 }),
291 next
292 ? next.progress.interpolate({
293 inputRange: [0, 1],
294 outputRange: [0, 1],
295 extrapolate: 'clamp',
296 })
297 : 0
298 );
299
300 const opacity = progress.interpolate({
301 inputRange: [0, 0.75, 0.875, 1, 1.0825, 1.2075, 2],
302 outputRange: [0, 0, 1, 1, 1, 1, 0],
303 });
304
305 const scale = conditional(
306 closing,
307 current.progress.interpolate({
308 inputRange: [0, 1],
309 outputRange: [0.925, 1],
310 extrapolate: 'clamp',
311 }),
312 progress.interpolate({
313 inputRange: [0, 1, 2],
314 outputRange: [0.85, 1, 1.075],
315 })
316 );
317
318 return {
319 cardStyle: {
320 opacity,
321 transform: [{ scale }],
322 },
323 };
324}
325
326/**
327 * Standard bottom sheet slide in from the bottom for Android.
328 */
329export function forBottomSheetAndroid({
330 current,
331 inverted,
332 layouts: { screen },
333 closing,
334}: StackCardInterpolationProps): StackCardInterpolatedStyle {
335 const translateY = multiply(
336 current.progress.interpolate({
337 inputRange: [0, 1],
338 outputRange: [screen.height * 0.8, 0],
339 extrapolate: 'clamp',
340 }),
341 inverted
342 );
343
344 const opacity = conditional(
345 closing,
346 current.progress,
347 current.progress.interpolate({
348 inputRange: [0, 1],
349 outputRange: [0, 1],
350 extrapolate: 'clamp',
351 })
352 );
353
354 const overlayOpacity = current.progress.interpolate({
355 inputRange: [0, 1],
356 outputRange: [0, 0.3],
357 extrapolate: 'clamp',
358 });
359
360 return {
361 cardStyle: {
362 opacity,
363 transform: [{ translateY }],
364 },
365 overlayStyle: { opacity: overlayOpacity },
366 };
367}
368
369/**
370 * Simple fade animation for dialogs
371 */
372export function forFadeFromCenter({
373 current: { progress },
374}: StackCardInterpolationProps): StackCardInterpolatedStyle {
375 return {
376 cardStyle: {
377 opacity: progress.interpolate({
378 inputRange: [0, 0.5, 0.9, 1],
379 outputRange: [0, 0.25, 0.7, 1],
380 }),
381 },
382 overlayStyle: {
383 opacity: progress.interpolate({
384 inputRange: [0, 1],
385 outputRange: [0, 0.5],
386 extrapolate: 'clamp',
387 }),
388 },
389 };
390}
391
392export function forNoAnimation(): StackCardInterpolatedStyle {
393 return {};
394}