react-native-screen-transitions 3.2.0-beta.3 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +327 -672
- package/lib/commonjs/shared/components/screen-lifecycle.js +9 -133
- package/lib/commonjs/shared/components/screen-lifecycle.js.map +1 -1
- package/lib/commonjs/shared/constants.js +1 -0
- package/lib/commonjs/shared/constants.js.map +1 -1
- package/lib/commonjs/shared/hooks/animation/use-screen-animation.js +3 -0
- package/lib/commonjs/shared/hooks/animation/use-screen-animation.js.map +1 -1
- package/lib/commonjs/shared/hooks/lifecycle/use-close-transition.js +127 -0
- package/lib/commonjs/shared/hooks/lifecycle/use-close-transition.js.map +1 -0
- package/lib/commonjs/shared/hooks/lifecycle/use-open-transition.js +35 -0
- package/lib/commonjs/shared/hooks/lifecycle/use-open-transition.js.map +1 -0
- package/lib/commonjs/shared/hooks/lifecycle/use-screen-events.js +58 -0
- package/lib/commonjs/shared/hooks/lifecycle/use-screen-events.js.map +1 -0
- package/lib/commonjs/shared/hooks/navigation/use-history.js +24 -0
- package/lib/commonjs/shared/hooks/navigation/use-history.js.map +1 -0
- package/lib/commonjs/shared/index.js +7 -0
- package/lib/commonjs/shared/index.js.map +1 -1
- package/lib/commonjs/shared/providers/screen/keys.provider.js +0 -4
- package/lib/commonjs/shared/providers/screen/keys.provider.js.map +1 -1
- package/lib/commonjs/shared/providers/screen/screen-composer.js +7 -5
- package/lib/commonjs/shared/providers/screen/screen-composer.js.map +1 -1
- package/lib/commonjs/shared/providers/screen/styles.provider.js +41 -32
- package/lib/commonjs/shared/providers/screen/styles.provider.js.map +1 -1
- package/lib/commonjs/shared/providers/stack/direct.provider.js +9 -0
- package/lib/commonjs/shared/providers/stack/direct.provider.js.map +1 -1
- package/lib/commonjs/shared/providers/stack/managed.provider.js +9 -0
- package/lib/commonjs/shared/providers/stack/managed.provider.js.map +1 -1
- package/lib/commonjs/shared/stores/animation.store.js +3 -13
- package/lib/commonjs/shared/stores/animation.store.js.map +1 -1
- package/lib/commonjs/shared/stores/history.store.js +185 -0
- package/lib/commonjs/shared/stores/history.store.js.map +1 -0
- package/lib/commonjs/shared/types/stack.types.js.map +1 -1
- package/lib/commonjs/shared/utils/animation/start-screen-transition.js +5 -1
- package/lib/commonjs/shared/utils/animation/start-screen-transition.js.map +1 -1
- package/lib/commonjs/shared/utils/bounds/index.js +19 -4
- package/lib/commonjs/shared/utils/bounds/index.js.map +1 -1
- package/lib/module/shared/components/screen-lifecycle.js +9 -132
- package/lib/module/shared/components/screen-lifecycle.js.map +1 -1
- package/lib/module/shared/constants.js +1 -0
- package/lib/module/shared/constants.js.map +1 -1
- package/lib/module/shared/hooks/animation/use-screen-animation.js +3 -0
- package/lib/module/shared/hooks/animation/use-screen-animation.js.map +1 -1
- package/lib/module/shared/hooks/lifecycle/use-close-transition.js +122 -0
- package/lib/module/shared/hooks/lifecycle/use-close-transition.js.map +1 -0
- package/lib/module/shared/hooks/lifecycle/use-open-transition.js +32 -0
- package/lib/module/shared/hooks/lifecycle/use-open-transition.js.map +1 -0
- package/lib/module/shared/hooks/lifecycle/use-screen-events.js +54 -0
- package/lib/module/shared/hooks/lifecycle/use-screen-events.js.map +1 -0
- package/lib/module/shared/hooks/navigation/use-history.js +20 -0
- package/lib/module/shared/hooks/navigation/use-history.js.map +1 -0
- package/lib/module/shared/index.js +1 -0
- package/lib/module/shared/index.js.map +1 -1
- package/lib/module/shared/providers/screen/keys.provider.js +0 -4
- package/lib/module/shared/providers/screen/keys.provider.js.map +1 -1
- package/lib/module/shared/providers/screen/screen-composer.js +7 -5
- package/lib/module/shared/providers/screen/screen-composer.js.map +1 -1
- package/lib/module/shared/providers/screen/styles.provider.js +41 -32
- package/lib/module/shared/providers/screen/styles.provider.js.map +1 -1
- package/lib/module/shared/providers/stack/direct.provider.js +10 -1
- package/lib/module/shared/providers/stack/direct.provider.js.map +1 -1
- package/lib/module/shared/providers/stack/managed.provider.js +10 -1
- package/lib/module/shared/providers/stack/managed.provider.js.map +1 -1
- package/lib/module/shared/stores/animation.store.js +4 -14
- package/lib/module/shared/stores/animation.store.js.map +1 -1
- package/lib/module/shared/stores/history.store.js +181 -0
- package/lib/module/shared/stores/history.store.js.map +1 -0
- package/lib/module/shared/types/stack.types.js.map +1 -1
- package/lib/module/shared/utils/animation/start-screen-transition.js +5 -1
- package/lib/module/shared/utils/animation/start-screen-transition.js.map +1 -1
- package/lib/module/shared/utils/bounds/index.js +19 -4
- package/lib/module/shared/utils/bounds/index.js.map +1 -1
- package/lib/typescript/blank-stack/types.d.ts +0 -3
- package/lib/typescript/blank-stack/types.d.ts.map +1 -1
- package/lib/typescript/component-stack/types.d.ts +0 -3
- package/lib/typescript/component-stack/types.d.ts.map +1 -1
- package/lib/typescript/shared/components/screen-lifecycle.d.ts +4 -1
- package/lib/typescript/shared/components/screen-lifecycle.d.ts.map +1 -1
- package/lib/typescript/shared/constants.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/animation/use-screen-animation.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/lifecycle/use-close-transition.d.ts +13 -0
- package/lib/typescript/shared/hooks/lifecycle/use-close-transition.d.ts.map +1 -0
- package/lib/typescript/shared/hooks/lifecycle/use-open-transition.d.ts +11 -0
- package/lib/typescript/shared/hooks/lifecycle/use-open-transition.d.ts.map +1 -0
- package/lib/typescript/shared/hooks/lifecycle/use-screen-events.d.ts +7 -0
- package/lib/typescript/shared/hooks/lifecycle/use-screen-events.d.ts.map +1 -0
- package/lib/typescript/shared/hooks/navigation/use-history.d.ts +37 -0
- package/lib/typescript/shared/hooks/navigation/use-history.d.ts.map +1 -0
- package/lib/typescript/shared/index.d.ts +3 -2
- package/lib/typescript/shared/index.d.ts.map +1 -1
- package/lib/typescript/shared/providers/screen/keys.provider.d.ts +0 -6
- package/lib/typescript/shared/providers/screen/keys.provider.d.ts.map +1 -1
- package/lib/typescript/shared/providers/screen/screen-composer.d.ts.map +1 -1
- package/lib/typescript/shared/providers/screen/styles.provider.d.ts.map +1 -1
- package/lib/typescript/shared/providers/stack/direct.provider.d.ts.map +1 -1
- package/lib/typescript/shared/providers/stack/managed.provider.d.ts.map +1 -1
- package/lib/typescript/shared/stores/animation.store.d.ts +3 -4
- package/lib/typescript/shared/stores/animation.store.d.ts.map +1 -1
- package/lib/typescript/shared/stores/history.store.d.ts +82 -0
- package/lib/typescript/shared/stores/history.store.d.ts.map +1 -0
- package/lib/typescript/shared/types/animation.types.d.ts +8 -0
- package/lib/typescript/shared/types/animation.types.d.ts.map +1 -1
- package/lib/typescript/shared/types/bounds.types.d.ts +1 -1
- package/lib/typescript/shared/types/bounds.types.d.ts.map +1 -1
- package/lib/typescript/shared/types/stack.types.d.ts +1 -0
- package/lib/typescript/shared/types/stack.types.d.ts.map +1 -1
- package/lib/typescript/shared/utils/animation/start-screen-transition.d.ts.map +1 -1
- package/lib/typescript/shared/utils/bounds/index.d.ts.map +1 -1
- package/package.json +28 -2
- package/src/blank-stack/types.ts +0 -8
- package/src/component-stack/types.ts +0 -9
- package/src/shared/__tests__/history.store.test.ts +550 -0
- package/src/shared/components/screen-lifecycle.tsx +13 -149
- package/src/shared/constants.ts +1 -0
- package/src/shared/hooks/animation/use-screen-animation.tsx +4 -0
- package/src/shared/hooks/lifecycle/use-close-transition.ts +147 -0
- package/src/shared/hooks/lifecycle/use-open-transition.ts +30 -0
- package/src/shared/hooks/lifecycle/use-screen-events.ts +62 -0
- package/src/shared/hooks/navigation/use-history.ts +63 -0
- package/src/shared/index.ts +1 -0
- package/src/shared/providers/screen/keys.provider.tsx +0 -16
- package/src/shared/providers/screen/screen-composer.tsx +6 -10
- package/src/shared/providers/screen/styles.provider.tsx +40 -34
- package/src/shared/providers/stack/direct.provider.tsx +11 -1
- package/src/shared/providers/stack/managed.provider.tsx +11 -1
- package/src/shared/stores/animation.store.ts +6 -20
- package/src/shared/stores/history.store.ts +201 -0
- package/src/shared/types/animation.types.ts +9 -0
- package/src/shared/types/bounds.types.ts +1 -0
- package/src/shared/types/stack.types.ts +1 -0
- package/src/shared/utils/animation/start-screen-transition.ts +4 -1
- package/src/shared/utils/bounds/index.ts +29 -3
- package/lib/commonjs/shared/utils/read-shared-value.js +0 -17
- package/lib/commonjs/shared/utils/read-shared-value.js.map +0 -1
- package/lib/module/shared/utils/read-shared-value.js +0 -14
- package/lib/module/shared/utils/read-shared-value.js.map +0 -1
- package/lib/typescript/shared/utils/read-shared-value.d.ts +0 -7
- package/lib/typescript/shared/utils/read-shared-value.d.ts.map +0 -1
- package/src/shared/utils/read-shared-value.ts +0 -15
package/README.md
CHANGED
|
@@ -9,10 +9,9 @@ Customizable screen transitions for React Native. Build gesture-driven, shared e
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
11
|
- **Full Animation Control** – Define exactly how screens enter, exit, and respond to gestures
|
|
12
|
-
- **Shared Elements** –
|
|
13
|
-
- **Gesture Support** – Swipe-to-dismiss with edge or full-screen activation
|
|
14
|
-
- **
|
|
15
|
-
- **Stack Progress** – Track animation progress across the entire stack, not just adjacent screens
|
|
12
|
+
- **Shared Elements** – Smooth transitions between screens using the Bounds API
|
|
13
|
+
- **Gesture Support** – Swipe-to-dismiss with edge or full-screen activation
|
|
14
|
+
- **Stack Progress** – Track animation progress across the entire stack
|
|
16
15
|
- **Ready-Made Presets** – Instagram, Apple Music, X (Twitter) style transitions included
|
|
17
16
|
|
|
18
17
|
## Installation
|
|
@@ -34,65 +33,7 @@ npm install react-native-reanimated react-native-gesture-handler \
|
|
|
34
33
|
|
|
35
34
|
## Quick Start
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
| Stack | Description |
|
|
40
|
-
| ----------------------------- | ---------------------------------------------------------------------------------- |
|
|
41
|
-
| **Blank Stack** (recommended) | Pure JavaScript stack with full control over transitions, overlays, and gestures. |
|
|
42
|
-
| **Native Stack** | Extends `@react-navigation/native-stack`. Full overlay support, native primitives. |
|
|
43
|
-
| **Component Stack** | Standalone navigator without React Navigation. For internal/embedded navigation. |
|
|
44
|
-
|
|
45
|
-
### Choosing a Stack
|
|
46
|
-
|
|
47
|
-
For most apps, **start with Blank Stack**. It's the most flexible and has full feature support.
|
|
48
|
-
|
|
49
|
-
| Stack | Best For |
|
|
50
|
-
| ------------------- | --------------------------------------------------------------------------------- |
|
|
51
|
-
| **Blank Stack** | Most apps. Full control, all features, JS-based with native performance. |
|
|
52
|
-
| **Native Stack** | When you need native screen primitives. Has some trade-offs. |
|
|
53
|
-
| **Component Stack** | Embedded flows (wizards, popovers). Isolated from React Navigation. Experimental. |
|
|
54
|
-
|
|
55
|
-
All three stacks share the same animation API – `screenStyleInterpolator`, gestures, overlays, and presets work identically across them.
|
|
56
|
-
|
|
57
|
-
### Blank Stack Philosophy
|
|
58
|
-
|
|
59
|
-
The Blank Stack is intentionally **blank** - transparent screens with no default animations. Unlike platform navigators that impose iOS or Android-style transitions, the Blank Stack gives you a clean slate.
|
|
60
|
-
|
|
61
|
-
**Why no defaults?**
|
|
62
|
-
|
|
63
|
-
- **Full creative control** – You define exactly how screens appear, not the OS
|
|
64
|
-
- **Consistency across platforms** – Same animation on iOS and Android
|
|
65
|
-
- **No fighting the framework** – No need to override or disable built-in behaviors
|
|
66
|
-
|
|
67
|
-
Every screen starts invisible and static. You bring it to life with your own `screenStyleInterpolator`. This encourages intentional, custom transitions rather than settling for platform defaults.
|
|
68
|
-
|
|
69
|
-
Under the hood, the Blank Stack uses `react-native-screens` for native-level performance. All animation and gesture logic runs on the UI thread via Reanimated worklets.
|
|
70
|
-
|
|
71
|
-
```tsx
|
|
72
|
-
// A screen with no options = invisible, no animation
|
|
73
|
-
<Stack.Screen name="Detail" component={DetailScreen} />
|
|
74
|
-
|
|
75
|
-
// Add your own transition
|
|
76
|
-
<Stack.Screen
|
|
77
|
-
name="Detail"
|
|
78
|
-
component={DetailScreen}
|
|
79
|
-
options={{
|
|
80
|
-
screenStyleInterpolator: ({ progress, layouts }) => {
|
|
81
|
-
"worklet";
|
|
82
|
-
return {
|
|
83
|
-
contentStyle: {
|
|
84
|
-
opacity: progress,
|
|
85
|
-
transform: [
|
|
86
|
-
{ translateY: interpolate(progress, [0, 1], [layouts.screen.height, 0]) }
|
|
87
|
-
],
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
},
|
|
91
|
-
}}
|
|
92
|
-
/>
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### Blank Stack Setup
|
|
36
|
+
### 1. Create a Stack
|
|
96
37
|
|
|
97
38
|
```tsx
|
|
98
39
|
import { createBlankStackNavigator } from "react-native-screen-transitions/blank-stack";
|
|
@@ -116,17 +57,12 @@ function App() {
|
|
|
116
57
|
}
|
|
117
58
|
```
|
|
118
59
|
|
|
119
|
-
###
|
|
60
|
+
### 2. With Expo Router
|
|
120
61
|
|
|
121
62
|
```tsx
|
|
122
|
-
import type {
|
|
123
|
-
ParamListBase,
|
|
124
|
-
StackNavigationState,
|
|
125
|
-
} from "@react-navigation/native";
|
|
126
63
|
import { withLayoutContext } from "expo-router";
|
|
127
64
|
import {
|
|
128
65
|
createBlankStackNavigator,
|
|
129
|
-
type BlankStackNavigationEventMap,
|
|
130
66
|
type BlankStackNavigationOptions,
|
|
131
67
|
} from "react-native-screen-transitions/blank-stack";
|
|
132
68
|
|
|
@@ -134,9 +70,7 @@ const { Navigator } = createBlankStackNavigator();
|
|
|
134
70
|
|
|
135
71
|
export const Stack = withLayoutContext<
|
|
136
72
|
BlankStackNavigationOptions,
|
|
137
|
-
typeof Navigator
|
|
138
|
-
StackNavigationState<ParamListBase>,
|
|
139
|
-
BlankStackNavigationEventMap
|
|
73
|
+
typeof Navigator
|
|
140
74
|
>(Navigator);
|
|
141
75
|
```
|
|
142
76
|
|
|
@@ -144,7 +78,7 @@ export const Stack = withLayoutContext<
|
|
|
144
78
|
|
|
145
79
|
## Presets
|
|
146
80
|
|
|
147
|
-
|
|
81
|
+
Use built-in presets for common transitions:
|
|
148
82
|
|
|
149
83
|
```tsx
|
|
150
84
|
<Stack.Screen
|
|
@@ -155,569 +89,486 @@ Built-in animation presets you can spread into screen options:
|
|
|
155
89
|
/>
|
|
156
90
|
```
|
|
157
91
|
|
|
158
|
-
| Preset | Description
|
|
159
|
-
| -------------------------------------- |
|
|
160
|
-
| `SlideFromTop()` | Slides in from top
|
|
161
|
-
| `SlideFromBottom()` | Slides in from bottom
|
|
162
|
-
| `ZoomIn()` | Scales in with fade
|
|
163
|
-
| `DraggableCard()` | Multi-directional drag with
|
|
164
|
-
| `ElasticCard()` | Elastic drag with overlay
|
|
165
|
-
| `SharedIGImage({ sharedBoundTag })` | Instagram-style shared image
|
|
166
|
-
| `SharedAppleMusic({ sharedBoundTag })` | Apple Music-style shared element
|
|
167
|
-
| `SharedXImage({ sharedBoundTag })` | X (Twitter)-style image transition
|
|
92
|
+
| Preset | Description |
|
|
93
|
+
| -------------------------------------- | --------------------------------------- |
|
|
94
|
+
| `SlideFromTop()` | Slides in from top |
|
|
95
|
+
| `SlideFromBottom()` | Slides in from bottom (modal-style) |
|
|
96
|
+
| `ZoomIn()` | Scales in with fade |
|
|
97
|
+
| `DraggableCard()` | Multi-directional drag with scaling |
|
|
98
|
+
| `ElasticCard()` | Elastic drag with overlay |
|
|
99
|
+
| `SharedIGImage({ sharedBoundTag })` | Instagram-style shared image |
|
|
100
|
+
| `SharedAppleMusic({ sharedBoundTag })` | Apple Music-style shared element |
|
|
101
|
+
| `SharedXImage({ sharedBoundTag })` | X (Twitter)-style image transition |
|
|
168
102
|
|
|
169
103
|
---
|
|
170
104
|
|
|
171
105
|
## Custom Animations
|
|
172
106
|
|
|
173
|
-
###
|
|
107
|
+
### The Basics
|
|
174
108
|
|
|
175
|
-
|
|
109
|
+
Every screen has a `progress` value that goes from 0 → 1 → 2:
|
|
176
110
|
|
|
177
111
|
```
|
|
178
|
-
0
|
|
179
|
-
|
|
180
|
-
Screen is Screen is Screen is
|
|
181
|
-
OFF-SCREEN FULLY VISIBLE OFF-SCREEN
|
|
182
|
-
(entering) (active) (exiting)
|
|
112
|
+
0 ─────────── 1 ─────────── 2
|
|
113
|
+
entering visible exiting
|
|
183
114
|
```
|
|
184
115
|
|
|
185
|
-
When
|
|
116
|
+
When navigating from A to B:
|
|
117
|
+
- **Screen B**: progress goes `0 → 1` (entering)
|
|
118
|
+
- **Screen A**: progress goes `1 → 2` (exiting)
|
|
186
119
|
|
|
187
|
-
|
|
188
|
-
- **Screen A** (exiting): `progress` animates from `1` → `2`
|
|
189
|
-
|
|
190
|
-
### Basic Examples
|
|
191
|
-
|
|
192
|
-
**Fade in/out:**
|
|
120
|
+
### Simple Fade
|
|
193
121
|
|
|
194
122
|
```tsx
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
};
|
|
123
|
+
options={{
|
|
124
|
+
screenStyleInterpolator: ({ progress }) => {
|
|
125
|
+
"worklet";
|
|
126
|
+
return {
|
|
127
|
+
contentStyle: {
|
|
128
|
+
opacity: interpolate(progress, [0, 1, 2], [0, 1, 0]),
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
}}
|
|
206
133
|
```
|
|
207
134
|
|
|
208
|
-
|
|
135
|
+
### Slide from Right
|
|
209
136
|
|
|
210
137
|
```tsx
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
contentStyle: {
|
|
218
|
-
transform: [
|
|
219
|
-
{
|
|
138
|
+
options={{
|
|
139
|
+
screenStyleInterpolator: ({ progress, layouts: { screen } }) => {
|
|
140
|
+
"worklet";
|
|
141
|
+
return {
|
|
142
|
+
contentStyle: {
|
|
143
|
+
transform: [{
|
|
220
144
|
translateX: interpolate(
|
|
221
145
|
progress,
|
|
222
146
|
[0, 1, 2],
|
|
223
147
|
[screen.width, 0, -screen.width * 0.3]
|
|
224
148
|
),
|
|
225
|
-
},
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
149
|
+
}],
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
},
|
|
153
|
+
}}
|
|
230
154
|
```
|
|
231
155
|
|
|
232
|
-
|
|
156
|
+
### Slide from Bottom
|
|
233
157
|
|
|
234
158
|
```tsx
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
{
|
|
159
|
+
options={{
|
|
160
|
+
screenStyleInterpolator: ({ progress, layouts: { screen } }) => {
|
|
161
|
+
"worklet";
|
|
162
|
+
return {
|
|
163
|
+
contentStyle: {
|
|
164
|
+
transform: [{
|
|
241
165
|
translateY: interpolate(progress, [0, 1], [screen.height, 0]),
|
|
242
|
-
},
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
166
|
+
}],
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
}}
|
|
247
171
|
```
|
|
248
172
|
|
|
249
|
-
###
|
|
250
|
-
|
|
251
|
-
Every `screenStyleInterpolator` must:
|
|
173
|
+
### Return Styles
|
|
252
174
|
|
|
253
|
-
|
|
254
|
-
2. Return an object with style properties
|
|
175
|
+
Your interpolator can return:
|
|
255
176
|
|
|
256
177
|
```tsx
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
screenStyleInterpolator: ({ progress, layouts: { screen } }) => {
|
|
263
|
-
"worklet";
|
|
264
|
-
|
|
265
|
-
return {
|
|
266
|
-
contentStyle: {
|
|
267
|
-
// Your animated styles here
|
|
268
|
-
},
|
|
269
|
-
};
|
|
270
|
-
},
|
|
271
|
-
transitionSpec: {
|
|
272
|
-
open: Transition.Specs.DefaultSpec,
|
|
273
|
-
close: Transition.Specs.DefaultSpec,
|
|
274
|
-
},
|
|
275
|
-
}}
|
|
276
|
-
/>;
|
|
178
|
+
return {
|
|
179
|
+
contentStyle: { ... }, // Main screen
|
|
180
|
+
overlayStyle: { ... }, // Semi-transparent backdrop
|
|
181
|
+
["my-id"]: { ... }, // Specific element via styleId
|
|
182
|
+
};
|
|
277
183
|
```
|
|
278
184
|
|
|
279
|
-
###
|
|
185
|
+
### Animation Specs
|
|
280
186
|
|
|
281
|
-
|
|
282
|
-
| ---------------- | -------------------------------------------------------- |
|
|
283
|
-
| `progress` | Combined progress (0-2). 0=entering, 1=active, 2=exiting |
|
|
284
|
-
| `stackProgress` | Accumulated progress across entire stack (0, 1, 2, 3...) |
|
|
285
|
-
| `current` | Current screen state (progress, closing, gesture, meta) |
|
|
286
|
-
| `previous` | Previous screen state (may be undefined) |
|
|
287
|
-
| `next` | Next screen state (may be undefined) |
|
|
288
|
-
| `layouts.screen` | Screen dimensions `{ width, height }` |
|
|
289
|
-
| `insets` | Safe area insets `{ top, right, bottom, left }` |
|
|
290
|
-
| `focused` | Whether current screen is the topmost |
|
|
291
|
-
| `active` | The screen driving the transition |
|
|
292
|
-
| `inactive` | The screen NOT driving the transition |
|
|
293
|
-
| `bounds` | Function to access shared element positions |
|
|
187
|
+
Control timing with spring configs:
|
|
294
188
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
| `gesture` | Gesture values (x, y, normalizedX, normalizedY, etc.) |
|
|
305
|
-
| `meta` | Custom metadata from screen options |
|
|
189
|
+
```tsx
|
|
190
|
+
options={{
|
|
191
|
+
screenStyleInterpolator: myInterpolator,
|
|
192
|
+
transitionSpec: {
|
|
193
|
+
open: { stiffness: 1000, damping: 500, mass: 3 },
|
|
194
|
+
close: { stiffness: 1000, damping: 500, mass: 3 },
|
|
195
|
+
},
|
|
196
|
+
}}
|
|
197
|
+
```
|
|
306
198
|
|
|
307
|
-
|
|
199
|
+
---
|
|
308
200
|
|
|
309
|
-
|
|
201
|
+
## Gestures
|
|
310
202
|
|
|
311
|
-
-
|
|
312
|
-
- **`inactive`** – The screen NOT driving the transition. When focused, this is `previous`. When not focused, this is `current`.
|
|
203
|
+
Enable swipe-to-dismiss:
|
|
313
204
|
|
|
314
205
|
```tsx
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const isClosing = props.active.closing;
|
|
206
|
+
options={{
|
|
207
|
+
gestureEnabled: true,
|
|
208
|
+
gestureDirection: "vertical",
|
|
209
|
+
...Transition.Presets.SlideFromBottom(),
|
|
210
|
+
}}
|
|
321
211
|
```
|
|
322
212
|
|
|
323
|
-
###
|
|
213
|
+
### Gesture Options
|
|
214
|
+
|
|
215
|
+
| Option | Description |
|
|
216
|
+
| ------------------------- | -------------------------------------- |
|
|
217
|
+
| `gestureEnabled` | Enable swipe-to-dismiss |
|
|
218
|
+
| `gestureDirection` | Direction(s) for swipe gesture |
|
|
219
|
+
| `gestureActivationArea` | Where gesture can start |
|
|
220
|
+
| `gestureResponseDistance` | Pixel threshold for activation |
|
|
221
|
+
| `gestureVelocityImpact` | How much velocity affects dismissal |
|
|
324
222
|
|
|
325
|
-
|
|
223
|
+
### Gesture Direction
|
|
326
224
|
|
|
327
225
|
```tsx
|
|
328
|
-
//
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}}
|
|
334
|
-
/>
|
|
226
|
+
gestureDirection: "horizontal" // swipe left to dismiss
|
|
227
|
+
gestureDirection: "horizontal-inverted" // swipe right to dismiss
|
|
228
|
+
gestureDirection: "vertical" // swipe down to dismiss
|
|
229
|
+
gestureDirection: "vertical-inverted" // swipe up to dismiss
|
|
230
|
+
gestureDirection: "bidirectional" // any direction
|
|
335
231
|
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
name="ScreenB"
|
|
339
|
-
options={{
|
|
340
|
-
screenStyleInterpolator: (props) => {
|
|
341
|
-
"worklet";
|
|
342
|
-
|
|
343
|
-
// When entering from ScreenA, inactive = ScreenA (previous)
|
|
344
|
-
// When going back to ScreenA, inactive = ScreenB (current)
|
|
345
|
-
const disableY = props.inactive?.meta?.disableTranslateYAnimation;
|
|
346
|
-
|
|
347
|
-
return {
|
|
348
|
-
contentStyle: {
|
|
349
|
-
transform: [{ translateY: disableY ? 0 : translateY }],
|
|
350
|
-
},
|
|
351
|
-
};
|
|
352
|
-
},
|
|
353
|
-
}}
|
|
354
|
-
/>
|
|
232
|
+
// Or combine multiple:
|
|
233
|
+
gestureDirection: ["horizontal", "vertical"]
|
|
355
234
|
```
|
|
356
235
|
|
|
357
|
-
|
|
236
|
+
### Gesture Activation Area
|
|
358
237
|
|
|
359
238
|
```tsx
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
() => animation.value,
|
|
364
|
-
(props) => {
|
|
365
|
-
// React to next screen's meta
|
|
366
|
-
if (props.next?.meta?.scalesOthers) {
|
|
367
|
-
scale.value = withTiming(0);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
);
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
### Return Value
|
|
239
|
+
// Simple - same for all edges
|
|
240
|
+
gestureActivationArea: "edge" // only from screen edges
|
|
241
|
+
gestureActivationArea: "screen" // anywhere on screen
|
|
374
242
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
243
|
+
// Per-side configuration
|
|
244
|
+
gestureActivationArea: {
|
|
245
|
+
left: "edge",
|
|
246
|
+
right: "screen",
|
|
247
|
+
top: "edge",
|
|
248
|
+
bottom: "screen",
|
|
249
|
+
}
|
|
381
250
|
```
|
|
382
251
|
|
|
383
|
-
###
|
|
252
|
+
### With ScrollViews
|
|
384
253
|
|
|
385
|
-
|
|
254
|
+
Use transition-aware scrollables so gestures work correctly:
|
|
386
255
|
|
|
387
256
|
```tsx
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
return {
|
|
392
|
-
"hero-image": {
|
|
393
|
-
opacity: interpolate(progress, [0, 1], [0, 1]),
|
|
394
|
-
transform: [{ scale: interpolate(progress, [0, 1], [0.8, 1]) }],
|
|
395
|
-
},
|
|
396
|
-
};
|
|
397
|
-
};
|
|
257
|
+
<Transition.ScrollView>
|
|
258
|
+
{/* content */}
|
|
259
|
+
</Transition.ScrollView>
|
|
398
260
|
|
|
399
|
-
|
|
400
|
-
<Transition.View styleId="hero-image">
|
|
401
|
-
<Image source={...} />
|
|
402
|
-
</Transition.View>
|
|
261
|
+
<Transition.FlatList data={items} renderItem={...} />
|
|
403
262
|
```
|
|
404
263
|
|
|
264
|
+
Gesture rules with scrollables:
|
|
265
|
+
- **vertical** – only activates when scrolled to top
|
|
266
|
+
- **vertical-inverted** – only activates when scrolled to bottom
|
|
267
|
+
- **horizontal** – only activates at left/right scroll edges
|
|
268
|
+
|
|
405
269
|
---
|
|
406
270
|
|
|
407
271
|
## Shared Elements (Bounds API)
|
|
408
272
|
|
|
409
|
-
Animate elements between screens by
|
|
273
|
+
Animate elements between screens by tagging them.
|
|
410
274
|
|
|
411
|
-
### 1. Tag
|
|
275
|
+
### 1. Tag the Source
|
|
412
276
|
|
|
413
277
|
```tsx
|
|
414
|
-
// Source screen
|
|
415
278
|
<Transition.Pressable
|
|
416
279
|
sharedBoundTag="avatar"
|
|
417
280
|
onPress={() => navigation.navigate("Profile")}
|
|
418
281
|
>
|
|
419
282
|
<Image source={avatar} style={{ width: 50, height: 50 }} />
|
|
420
283
|
</Transition.Pressable>
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### 2. Tag the Destination
|
|
421
287
|
|
|
422
|
-
|
|
288
|
+
```tsx
|
|
423
289
|
<Transition.View sharedBoundTag="avatar">
|
|
424
290
|
<Image source={avatar} style={{ width: 200, height: 200 }} />
|
|
425
291
|
</Transition.View>
|
|
426
292
|
```
|
|
427
293
|
|
|
428
|
-
###
|
|
294
|
+
### 3. Use in Interpolator
|
|
429
295
|
|
|
430
296
|
```tsx
|
|
431
297
|
screenStyleInterpolator: ({ bounds }) => {
|
|
432
298
|
"worklet";
|
|
433
|
-
|
|
434
|
-
const avatarStyles = bounds({
|
|
435
|
-
id: "avatar",
|
|
436
|
-
method: "transform", // "transform" | "size" | "content"
|
|
437
|
-
space: "relative", // "relative" | "absolute"
|
|
438
|
-
scaleMode: "match", // "match" | "none" | "uniform"
|
|
439
|
-
anchor: "center", // positioning anchor
|
|
440
|
-
});
|
|
441
|
-
|
|
442
299
|
return {
|
|
443
|
-
avatar:
|
|
300
|
+
avatar: bounds({ id: "avatar", method: "transform" }),
|
|
444
301
|
};
|
|
445
302
|
};
|
|
446
303
|
```
|
|
447
304
|
|
|
448
305
|
### Bounds Options
|
|
449
306
|
|
|
450
|
-
| Option | Values
|
|
451
|
-
| ----------- |
|
|
452
|
-
| `id` | string
|
|
453
|
-
| `method` | `"transform"` `"size"` `"content"`
|
|
454
|
-
| `space` | `"relative"` `"absolute"`
|
|
455
|
-
| `scaleMode` | `"match"` `"none"` `"uniform"`
|
|
456
|
-
| `
|
|
457
|
-
| `target` | `"bound"` `"fullscreen"` or custom | Destination target |
|
|
458
|
-
| `raw` | boolean | Return raw values instead of styles |
|
|
307
|
+
| Option | Values | Description |
|
|
308
|
+
| ----------- | ---------------------------------- | ----------------------------- |
|
|
309
|
+
| `id` | string | The `sharedBoundTag` to match |
|
|
310
|
+
| `method` | `"transform"` `"size"` `"content"` | How to animate |
|
|
311
|
+
| `space` | `"relative"` `"absolute"` | Coordinate space |
|
|
312
|
+
| `scaleMode` | `"match"` `"none"` `"uniform"` | Aspect ratio handling |
|
|
313
|
+
| `raw` | boolean | Return raw values |
|
|
459
314
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
```tsx
|
|
463
|
-
const raw = bounds({ id: "avatar", method: "transform", raw: true });
|
|
464
|
-
// { translateX, translateY, scaleX, scaleY }
|
|
465
|
-
```
|
|
315
|
+
---
|
|
466
316
|
|
|
467
|
-
|
|
317
|
+
## Overlays
|
|
468
318
|
|
|
469
|
-
|
|
319
|
+
Persistent UI that animates with the stack:
|
|
470
320
|
|
|
471
321
|
```tsx
|
|
472
|
-
|
|
473
|
-
|
|
322
|
+
const TabBar = ({ focusedIndex, progress }) => {
|
|
323
|
+
const style = useAnimatedStyle(() => ({
|
|
324
|
+
transform: [{ translateY: interpolate(progress.value, [0, 1], [100, 0]) }],
|
|
325
|
+
}));
|
|
326
|
+
return <Animated.View style={[styles.tabBar, style]} />;
|
|
327
|
+
};
|
|
474
328
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
329
|
+
<Stack.Screen
|
|
330
|
+
name="Home"
|
|
331
|
+
options={{
|
|
332
|
+
overlay: TabBar,
|
|
333
|
+
overlayShown: true,
|
|
334
|
+
}}
|
|
335
|
+
/>
|
|
336
|
+
```
|
|
478
337
|
|
|
479
|
-
|
|
480
|
-
const borderRadius = bounds.interpolateStyle("avatar", "borderRadius");
|
|
338
|
+
### Overlay Props
|
|
481
339
|
|
|
482
|
-
|
|
483
|
-
|
|
340
|
+
| Prop | Description |
|
|
341
|
+
| -------------- | ------------------------------ |
|
|
342
|
+
| `focusedRoute` | Currently focused route |
|
|
343
|
+
| `focusedIndex` | Index of focused screen |
|
|
344
|
+
| `routes` | All routes in the stack |
|
|
345
|
+
| `progress` | Stack progress (derived value) |
|
|
346
|
+
| `navigation` | Navigation prop |
|
|
347
|
+
| `meta` | Custom metadata from options |
|
|
484
348
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
borderRadius,
|
|
489
|
-
},
|
|
490
|
-
};
|
|
491
|
-
};
|
|
492
|
-
```
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Transition Components
|
|
493
352
|
|
|
494
|
-
|
|
|
495
|
-
|
|
|
496
|
-
| `
|
|
497
|
-
| `
|
|
498
|
-
| `
|
|
353
|
+
| Component | Description |
|
|
354
|
+
| ----------------------- | -------------------------------------- |
|
|
355
|
+
| `Transition.View` | Animated view with `sharedBoundTag` |
|
|
356
|
+
| `Transition.Pressable` | Pressable that measures bounds |
|
|
357
|
+
| `Transition.ScrollView` | ScrollView with gesture coordination |
|
|
358
|
+
| `Transition.FlatList` | FlatList with gesture coordination |
|
|
359
|
+
| `Transition.MaskedView` | For reveal effects (requires native) |
|
|
499
360
|
|
|
500
361
|
---
|
|
501
362
|
|
|
502
|
-
##
|
|
363
|
+
## Hooks
|
|
364
|
+
|
|
365
|
+
### useScreenAnimation
|
|
503
366
|
|
|
504
|
-
|
|
367
|
+
Access animation state inside a screen:
|
|
505
368
|
|
|
506
369
|
```tsx
|
|
507
|
-
|
|
508
|
-
name="Detail"
|
|
509
|
-
options={{
|
|
510
|
-
gestureEnabled: true,
|
|
511
|
-
gestureDirection: "vertical", // or "horizontal", ["vertical", "horizontal"]
|
|
512
|
-
gestureActivationArea: "edge", // or "screen", or { left: "edge", top: "screen" }
|
|
513
|
-
gestureResponseDistance: 50,
|
|
514
|
-
gestureVelocityImpact: 0.3,
|
|
515
|
-
}}
|
|
516
|
-
/>
|
|
517
|
-
```
|
|
370
|
+
import { useScreenAnimation } from "react-native-screen-transitions";
|
|
518
371
|
|
|
519
|
-
|
|
372
|
+
function DetailScreen() {
|
|
373
|
+
const animation = useScreenAnimation();
|
|
520
374
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
| `gestureDirection` | `"horizontal"` `"vertical"` `"horizontal-inverted"` `"vertical-inverted"` or array |
|
|
525
|
-
| `gestureActivationArea` | `"edge"` `"screen"` or per-side config |
|
|
526
|
-
| `gestureResponseDistance` | Distance threshold for gesture recognition |
|
|
527
|
-
| `gestureVelocityImpact` | How much velocity affects dismissal decision |
|
|
528
|
-
| `gestureDrivesProgress` | Whether gesture directly drives animation (default: true) |
|
|
375
|
+
const style = useAnimatedStyle(() => ({
|
|
376
|
+
opacity: animation.value.current.progress,
|
|
377
|
+
}));
|
|
529
378
|
|
|
530
|
-
|
|
379
|
+
return <Animated.View style={style}>...</Animated.View>;
|
|
380
|
+
}
|
|
381
|
+
```
|
|
531
382
|
|
|
532
|
-
|
|
383
|
+
### useScreenState
|
|
384
|
+
|
|
385
|
+
Get navigation state without animation values:
|
|
533
386
|
|
|
534
387
|
```tsx
|
|
535
|
-
import
|
|
388
|
+
import { useScreenState } from "react-native-screen-transitions";
|
|
536
389
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
390
|
+
function DetailScreen() {
|
|
391
|
+
const { index, focusedRoute, routes, navigation } = useScreenState();
|
|
392
|
+
// ...
|
|
393
|
+
}
|
|
394
|
+
```
|
|
541
395
|
|
|
542
|
-
|
|
543
|
-
data={items}
|
|
544
|
-
renderItem={...}
|
|
545
|
-
/>
|
|
396
|
+
### useHistory
|
|
546
397
|
|
|
547
|
-
|
|
548
|
-
const TransitionFlashList = Transition.createTransitionAwareComponent(
|
|
549
|
-
FlashList,
|
|
550
|
-
{ isScrollable: true }
|
|
551
|
-
);
|
|
552
|
-
```
|
|
398
|
+
Access navigation history across the app:
|
|
553
399
|
|
|
554
|
-
|
|
400
|
+
```tsx
|
|
401
|
+
import { useHistory } from "react-native-screen-transitions";
|
|
402
|
+
|
|
403
|
+
function MyComponent() {
|
|
404
|
+
const { getRecent, getPath } = useHistory();
|
|
555
405
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
406
|
+
const recentScreens = getRecent(5); // Last 5 screens
|
|
407
|
+
const path = getPath(fromKey, toKey); // Path between screens
|
|
408
|
+
}
|
|
409
|
+
```
|
|
559
410
|
|
|
560
411
|
---
|
|
561
412
|
|
|
562
|
-
##
|
|
413
|
+
## Advanced Animation Props
|
|
563
414
|
|
|
564
|
-
|
|
415
|
+
The full `screenStyleInterpolator` receives these props:
|
|
565
416
|
|
|
566
|
-
|
|
417
|
+
| Prop | Description |
|
|
418
|
+
| ---------------- | -------------------------------------------------------- |
|
|
419
|
+
| `progress` | Combined progress (0-2) |
|
|
420
|
+
| `stackProgress` | Accumulated progress across entire stack |
|
|
421
|
+
| `current` | Current screen state |
|
|
422
|
+
| `previous` | Previous screen state |
|
|
423
|
+
| `next` | Next screen state |
|
|
424
|
+
| `active` | Screen driving the transition |
|
|
425
|
+
| `inactive` | Screen NOT driving the transition |
|
|
426
|
+
| `layouts.screen` | Screen dimensions |
|
|
427
|
+
| `insets` | Safe area insets |
|
|
428
|
+
| `bounds` | Shared element bounds function |
|
|
429
|
+
|
|
430
|
+
### Screen State Properties
|
|
431
|
+
|
|
432
|
+
Each screen state (`current`, `previous`, `next`, `active`, `inactive`) contains:
|
|
433
|
+
|
|
434
|
+
| Property | Description |
|
|
435
|
+
| ----------- | ---------------------------------------- |
|
|
436
|
+
| `progress` | Animation progress (0 or 1) |
|
|
437
|
+
| `closing` | Whether closing (0 or 1) |
|
|
438
|
+
| `entering` | Whether entering (0 or 1) |
|
|
439
|
+
| `animating` | Whether animating (0 or 1) |
|
|
440
|
+
| `gesture` | Gesture values (x, y, normalized values) |
|
|
441
|
+
| `meta` | Custom metadata from options |
|
|
442
|
+
|
|
443
|
+
### Using `meta` for Conditional Logic
|
|
567
444
|
|
|
568
|
-
|
|
445
|
+
Pass custom data between screens:
|
|
569
446
|
|
|
570
447
|
```tsx
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
opacity: interpolate(overlayAnimation.value.progress, [0, 1], [0, 1]),
|
|
574
|
-
}));
|
|
448
|
+
// Screen A
|
|
449
|
+
options={{ meta: { hideTabBar: true } }}
|
|
575
450
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
</Animated.View>
|
|
582
|
-
);
|
|
451
|
+
// Screen B reads it
|
|
452
|
+
screenStyleInterpolator: (props) => {
|
|
453
|
+
"worklet";
|
|
454
|
+
const hideTabBar = props.inactive?.meta?.hideTabBar;
|
|
455
|
+
// ...
|
|
583
456
|
};
|
|
584
|
-
|
|
585
|
-
<Stack.Screen
|
|
586
|
-
name="Home"
|
|
587
|
-
options={{
|
|
588
|
-
overlay: FloatingHeader,
|
|
589
|
-
overlayMode: "float",
|
|
590
|
-
overlayShown: true,
|
|
591
|
-
}}
|
|
592
|
-
/>;
|
|
593
457
|
```
|
|
594
458
|
|
|
595
|
-
###
|
|
459
|
+
### Animate Individual Elements
|
|
596
460
|
|
|
597
|
-
|
|
461
|
+
Use `styleId` to target specific elements:
|
|
598
462
|
|
|
599
463
|
```tsx
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
464
|
+
// In options
|
|
465
|
+
screenStyleInterpolator: ({ progress }) => {
|
|
466
|
+
"worklet";
|
|
467
|
+
return {
|
|
468
|
+
"hero-image": {
|
|
469
|
+
opacity: interpolate(progress, [0, 1], [0, 1]),
|
|
470
|
+
},
|
|
471
|
+
};
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
// In component
|
|
475
|
+
<Transition.View styleId="hero-image">
|
|
476
|
+
<Image source={...} />
|
|
477
|
+
</Transition.View>
|
|
608
478
|
```
|
|
609
479
|
|
|
610
|
-
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## Stack Types
|
|
483
|
+
|
|
484
|
+
All three stacks share the same animation API. Choose based on your needs:
|
|
485
|
+
|
|
486
|
+
| Stack | Best For |
|
|
487
|
+
| ------------------- | --------------------------------------------------------- |
|
|
488
|
+
| **Blank Stack** | Most apps. Full control, all features. |
|
|
489
|
+
| **Native Stack** | When you need native screen primitives. |
|
|
490
|
+
| **Component Stack** | Embedded flows, isolated from React Navigation. *(Experimental)* |
|
|
611
491
|
|
|
612
|
-
|
|
613
|
-
| ------------------ | --------------------------------------------------------- |
|
|
614
|
-
| `focusedRoute` | Currently focused route |
|
|
615
|
-
| `focusedIndex` | Index of focused screen |
|
|
616
|
-
| `routes` | All routes in the stack |
|
|
617
|
-
| `meta` | Custom metadata passed from screen options |
|
|
618
|
-
| `navigation` | Navigation prop |
|
|
619
|
-
| `overlayAnimation` | Animation values with `progress` accumulated across stack |
|
|
620
|
-
| `screenAnimation` | Animation values for the current focused screen |
|
|
492
|
+
### Blank Stack
|
|
621
493
|
|
|
622
|
-
|
|
494
|
+
The default choice. Pure JavaScript with native-level performance via `react-native-screens`.
|
|
623
495
|
|
|
624
496
|
```tsx
|
|
497
|
+
import { createBlankStackNavigator } from "react-native-screen-transitions/blank-stack";
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Native Stack
|
|
501
|
+
|
|
502
|
+
Extends `@react-navigation/native-stack`. Requires `enableTransitions: true`.
|
|
503
|
+
|
|
504
|
+
```tsx
|
|
505
|
+
import { createNativeStackNavigator } from "react-native-screen-transitions/native-stack";
|
|
506
|
+
|
|
625
507
|
<Stack.Screen
|
|
508
|
+
name="Detail"
|
|
626
509
|
options={{
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
title: "Step 1",
|
|
630
|
-
showProgress: true,
|
|
631
|
-
},
|
|
510
|
+
enableTransitions: true,
|
|
511
|
+
...Transition.Presets.SlideFromBottom(),
|
|
632
512
|
}}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
// In overlay
|
|
636
|
-
const MyOverlay = ({ meta }) => {
|
|
637
|
-
return <Text>{meta?.title}</Text>;
|
|
638
|
-
};
|
|
513
|
+
/>
|
|
639
514
|
```
|
|
640
515
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
## Transition Components
|
|
516
|
+
### Component Stack (Experimental)
|
|
644
517
|
|
|
645
|
-
|
|
646
|
-
| ----------------------- | ------------------------------------------------------ |
|
|
647
|
-
| `Transition.View` | Animated view, supports `styleId` and `sharedBoundTag` |
|
|
648
|
-
| `Transition.Pressable` | Pressable with bounds measurement on press |
|
|
649
|
-
| `Transition.ScrollView` | ScrollView with gesture coordination |
|
|
650
|
-
| `Transition.FlatList` | FlatList with gesture coordination |
|
|
651
|
-
| `Transition.MaskedView` | For clipping during shared element transitions |
|
|
518
|
+
> **Note:** This API is experimental and may change based on community feedback.
|
|
652
519
|
|
|
653
|
-
|
|
520
|
+
Standalone navigator, not connected to React Navigation. Ideal for embedded flows.
|
|
654
521
|
|
|
655
522
|
```tsx
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
523
|
+
import { createComponentNavigator } from "react-native-screen-transitions/component-stack";
|
|
524
|
+
|
|
525
|
+
const Stack = createComponentNavigator();
|
|
526
|
+
|
|
527
|
+
<Stack.Navigator initialRoute="step1">
|
|
528
|
+
<Stack.Screen name="step1" component={Step1} />
|
|
529
|
+
<Stack.Screen name="step2" component={Step2} />
|
|
530
|
+
</Stack.Navigator>
|
|
660
531
|
```
|
|
661
532
|
|
|
662
533
|
---
|
|
663
534
|
|
|
664
|
-
##
|
|
535
|
+
## Caveats & Trade-offs
|
|
665
536
|
|
|
666
|
-
###
|
|
537
|
+
### Native Stack
|
|
667
538
|
|
|
668
|
-
|
|
539
|
+
The Native Stack uses transparent modal presentation to intercept transitions. This has trade-offs:
|
|
669
540
|
|
|
670
|
-
|
|
671
|
-
|
|
541
|
+
- **Delayed touch events** – Exiting screens may have briefly delayed touch response
|
|
542
|
+
- **beforeRemove listeners** – Relies on navigation lifecycle events
|
|
543
|
+
- **Rapid navigation** – Some edge cases with very fast navigation sequences
|
|
672
544
|
|
|
673
|
-
|
|
674
|
-
const animation = useScreenAnimation();
|
|
545
|
+
For most apps, Blank Stack avoids these issues entirely.
|
|
675
546
|
|
|
676
|
-
|
|
677
|
-
const { current } = animation.value;
|
|
678
|
-
return {
|
|
679
|
-
opacity: current.progress,
|
|
680
|
-
};
|
|
681
|
-
});
|
|
547
|
+
### Component Stack (Experimental)
|
|
682
548
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
549
|
+
- **No deep linking** – Routes aren't part of your URL structure
|
|
550
|
+
- **Isolated state** – Doesn't affect parent navigation
|
|
551
|
+
- **Touch pass-through** – Uses `pointerEvents="box-none"` by default
|
|
686
552
|
|
|
687
553
|
---
|
|
688
554
|
|
|
689
|
-
##
|
|
555
|
+
## Experimental Features
|
|
690
556
|
|
|
691
|
-
|
|
557
|
+
### High Refresh Rate
|
|
692
558
|
|
|
693
|
-
|
|
694
|
-
transitionSpec: {
|
|
695
|
-
open: {
|
|
696
|
-
stiffness: 1000,
|
|
697
|
-
damping: 500,
|
|
698
|
-
mass: 3,
|
|
699
|
-
overshootClamping: true,
|
|
700
|
-
},
|
|
701
|
-
close: {
|
|
702
|
-
stiffness: 1000,
|
|
703
|
-
damping: 500,
|
|
704
|
-
mass: 3,
|
|
705
|
-
overshootClamping: true,
|
|
706
|
-
},
|
|
707
|
-
}
|
|
559
|
+
Force maximum refresh rate during transitions (for 90Hz/120Hz displays):
|
|
708
560
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
561
|
+
```tsx
|
|
562
|
+
options={{
|
|
563
|
+
experimental_enableHighRefreshRate: true,
|
|
564
|
+
}}
|
|
714
565
|
```
|
|
715
566
|
|
|
716
567
|
---
|
|
717
568
|
|
|
718
569
|
## Masked View Setup
|
|
719
570
|
|
|
720
|
-
Required for `SharedIGImage` and `SharedAppleMusic` presets. The masked view creates the "reveal" effect where content
|
|
571
|
+
Required for `SharedIGImage` and `SharedAppleMusic` presets. The masked view creates the "reveal" effect where content expands from the shared element.
|
|
721
572
|
|
|
722
573
|
> **Note**: Requires native code. Will not work in Expo Go.
|
|
723
574
|
|
|
@@ -732,11 +583,9 @@ npm install @react-native-masked-view/masked-view
|
|
|
732
583
|
cd ios && pod install
|
|
733
584
|
```
|
|
734
585
|
|
|
735
|
-
###
|
|
736
|
-
|
|
737
|
-
Here's a full example showing how to set up an Apple Music-style shared element transition:
|
|
586
|
+
### Full Example
|
|
738
587
|
|
|
739
|
-
**1. Source Screen** – Tag pressable elements
|
|
588
|
+
**1. Source Screen** – Tag pressable elements:
|
|
740
589
|
|
|
741
590
|
```tsx
|
|
742
591
|
// app/index.tsx
|
|
@@ -767,7 +616,7 @@ export default function HomeScreen() {
|
|
|
767
616
|
}
|
|
768
617
|
```
|
|
769
618
|
|
|
770
|
-
**2. Destination Screen** – Wrap
|
|
619
|
+
**2. Destination Screen** – Wrap with MaskedView and match the tag:
|
|
771
620
|
|
|
772
621
|
```tsx
|
|
773
622
|
// app/details.tsx
|
|
@@ -795,7 +644,7 @@ export default function DetailsScreen() {
|
|
|
795
644
|
}
|
|
796
645
|
```
|
|
797
646
|
|
|
798
|
-
**3. Layout** – Apply the
|
|
647
|
+
**3. Layout** – Apply the preset with dynamic tag:
|
|
799
648
|
|
|
800
649
|
```tsx
|
|
801
650
|
// app/_layout.tsx
|
|
@@ -821,210 +670,16 @@ export default function RootLayout() {
|
|
|
821
670
|
|
|
822
671
|
### How It Works
|
|
823
672
|
|
|
824
|
-
1. `Transition.Pressable` measures its bounds
|
|
825
|
-
2. `Transition.View` on the destination
|
|
826
|
-
3. `Transition.MaskedView` clips
|
|
827
|
-
4. The preset interpolates position, size, and
|
|
828
|
-
|
|
829
|
-
---
|
|
830
|
-
|
|
831
|
-
## Native Stack
|
|
832
|
-
|
|
833
|
-
For cases where you need native screen primitives, use the native stack integration. This extends `@react-navigation/native-stack` with custom transition support.
|
|
834
|
-
|
|
835
|
-
> **Note**: The native stack has limitations. It uses `beforeRemove` listeners and transparent modals to intercept transitions. The Blank Stack is recommended for most use cases.
|
|
836
|
-
|
|
837
|
-
### Setup
|
|
838
|
-
|
|
839
|
-
```tsx
|
|
840
|
-
import { createNativeStackNavigator } from "react-native-screen-transitions/native-stack";
|
|
841
|
-
|
|
842
|
-
const Stack = createNativeStackNavigator();
|
|
843
|
-
|
|
844
|
-
<Stack.Screen
|
|
845
|
-
name="Detail"
|
|
846
|
-
options={{
|
|
847
|
-
enableTransitions: true, // Required to enable custom transitions
|
|
848
|
-
...Transition.Presets.SlideFromBottom(),
|
|
849
|
-
}}
|
|
850
|
-
/>;
|
|
851
|
-
```
|
|
852
|
-
|
|
853
|
-
### Expo Router Setup
|
|
854
|
-
|
|
855
|
-
```tsx
|
|
856
|
-
import type {
|
|
857
|
-
ParamListBase,
|
|
858
|
-
StackNavigationState,
|
|
859
|
-
} from "@react-navigation/native";
|
|
860
|
-
import { withLayoutContext } from "expo-router";
|
|
861
|
-
import {
|
|
862
|
-
createNativeStackNavigator,
|
|
863
|
-
type NativeStackNavigationEventMap,
|
|
864
|
-
type NativeStackNavigationOptions,
|
|
865
|
-
} from "react-native-screen-transitions/native-stack";
|
|
866
|
-
|
|
867
|
-
const { Navigator } = createNativeStackNavigator();
|
|
868
|
-
|
|
869
|
-
export const Stack = withLayoutContext<
|
|
870
|
-
NativeStackNavigationOptions,
|
|
871
|
-
typeof Navigator,
|
|
872
|
-
StackNavigationState<ParamListBase>,
|
|
873
|
-
NativeStackNavigationEventMap
|
|
874
|
-
>(Navigator);
|
|
875
|
-
```
|
|
876
|
-
|
|
877
|
-
### Native Stack Options
|
|
878
|
-
|
|
879
|
-
All standard `@react-navigation/native-stack` options are available, plus:
|
|
880
|
-
|
|
881
|
-
| Option | Type | Description |
|
|
882
|
-
| ------------------------- | ---------------------------------------- | ------------------------------------------------------------------ |
|
|
883
|
-
| `enableTransitions` | `boolean` | Enable custom transitions (sets presentation to transparent modal) |
|
|
884
|
-
| `screenStyleInterpolator` | `ScreenStyleInterpolator` | Function that returns animated styles |
|
|
885
|
-
| `transitionSpec` | `TransitionSpec` | Animation config for open/close |
|
|
886
|
-
| `gestureEnabled` | `boolean` | Whether swipe-to-dismiss is allowed |
|
|
887
|
-
| `gestureDirection` | `GestureDirection \| GestureDirection[]` | Allowed swipe directions |
|
|
888
|
-
| `gestureVelocityImpact` | `number` | How much velocity affects dismissal |
|
|
889
|
-
| `gestureResponseDistance` | `number` | Distance threshold for gesture |
|
|
890
|
-
| `gestureDrivesProgress` | `boolean` | Whether gesture drives animation |
|
|
891
|
-
| `gestureActivationArea` | `GestureActivationArea` | Where gesture can start |
|
|
892
|
-
| `meta` | `Record<string, unknown>` | Custom metadata for conditional animation logic |
|
|
893
|
-
|
|
894
|
-
### Renamed Native Options
|
|
895
|
-
|
|
896
|
-
To avoid collisions with custom gesture options, some native options are renamed:
|
|
897
|
-
|
|
898
|
-
| React Navigation | Renamed to |
|
|
899
|
-
| ------------------------- | ------------------------------- |
|
|
900
|
-
| `gestureDirection` | `nativeGestureDirection` |
|
|
901
|
-
| `gestureEnabled` | `nativeGestureEnabled` |
|
|
902
|
-
| `gestureResponseDistance` | `nativeGestureResponseDistance` |
|
|
903
|
-
|
|
904
|
-
### Limitations
|
|
905
|
-
|
|
906
|
-
- Relies on `beforeRemove` listener to intercept navigation
|
|
907
|
-
- Uses transparent modal presentation
|
|
908
|
-
- Some edge cases with rapid navigation
|
|
909
|
-
|
|
910
|
-
---
|
|
911
|
-
|
|
912
|
-
## Component Stack (Experimental)
|
|
913
|
-
|
|
914
|
-
A standalone navigator that operates **independently** from React Navigation. It uses React Navigation's isolation API under the hood but doesn't affect your primary navigation state.
|
|
915
|
-
|
|
916
|
-
> **Experimental**: This API may change in future versions.
|
|
917
|
-
|
|
918
|
-
### How It Differs
|
|
919
|
-
|
|
920
|
-
Unlike Blank Stack and Native Stack which integrate with React Navigation:
|
|
921
|
-
|
|
922
|
-
- **No deep linking** – Routes aren't part of your app's URL structure
|
|
923
|
-
- **No navigation state sharing** – Completely isolated from parent navigators
|
|
924
|
-
- **Transparent by default** – All screens use `pointerEvents="box-none"`, allowing touches to pass through to content behind. Your screen content handles its own touch areas.
|
|
925
|
-
|
|
926
|
-
This makes Component Stack ideal for **overlay-style navigation** within a screen – think embedded wizards, popovers, or flows that shouldn't affect the main navigation.
|
|
927
|
-
|
|
928
|
-
### Setup
|
|
929
|
-
|
|
930
|
-
```tsx
|
|
931
|
-
import {
|
|
932
|
-
createComponentNavigator,
|
|
933
|
-
useComponentNavigation,
|
|
934
|
-
} from "react-native-screen-transitions/component-stack";
|
|
935
|
-
import Transition from "react-native-screen-transitions";
|
|
936
|
-
|
|
937
|
-
const Stack = createComponentNavigator();
|
|
938
|
-
|
|
939
|
-
function OnboardingFlow() {
|
|
940
|
-
return (
|
|
941
|
-
<Stack.Navigator initialRoute="step1">
|
|
942
|
-
<Stack.Screen
|
|
943
|
-
name="step1"
|
|
944
|
-
component={Step1}
|
|
945
|
-
options={{
|
|
946
|
-
...Transition.Presets.SlideFromBottom(),
|
|
947
|
-
}}
|
|
948
|
-
/>
|
|
949
|
-
<Stack.Screen
|
|
950
|
-
name="step2"
|
|
951
|
-
component={Step2}
|
|
952
|
-
options={{
|
|
953
|
-
...Transition.Presets.SlideFromBottom(),
|
|
954
|
-
}}
|
|
955
|
-
/>
|
|
956
|
-
</Stack.Navigator>
|
|
957
|
-
);
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
function Step1() {
|
|
961
|
-
const navigation = useComponentNavigation();
|
|
962
|
-
|
|
963
|
-
return (
|
|
964
|
-
<View>
|
|
965
|
-
<Text>Step 1</Text>
|
|
966
|
-
<Button title="Next" onPress={() => navigation.push("step2")} />
|
|
967
|
-
</View>
|
|
968
|
-
);
|
|
969
|
-
}
|
|
970
|
-
```
|
|
971
|
-
|
|
972
|
-
### Navigation API
|
|
973
|
-
|
|
974
|
-
```tsx
|
|
975
|
-
const navigation = useComponentNavigation();
|
|
976
|
-
|
|
977
|
-
navigation.push("screenName", { param: "value" }); // Push a screen
|
|
978
|
-
navigation.pop(); // Go back
|
|
979
|
-
navigation.popTo("screenName"); // Pop to a specific screen
|
|
980
|
-
navigation.reset("screenName"); // Reset to a single screen
|
|
981
|
-
```
|
|
982
|
-
|
|
983
|
-
### When to Use Component Stack
|
|
984
|
-
|
|
985
|
-
- **Onboarding flows** – Self-contained multi-step experiences
|
|
986
|
-
- **Wizards/Forms** – Multi-step forms within a screen
|
|
987
|
-
- **Embedded navigation** – Navigation inside a modal or sheet
|
|
988
|
-
- **Non-URL navigation** – When you don't need deep linking
|
|
989
|
-
|
|
990
|
-
---
|
|
991
|
-
|
|
992
|
-
## Experimental Features
|
|
993
|
-
|
|
994
|
-
### High Refresh Rate
|
|
995
|
-
|
|
996
|
-
Force the display to run at maximum refresh rate during transitions. This can make animations smoother on high refresh rate displays (90Hz, 120Hz).
|
|
997
|
-
|
|
998
|
-
```tsx
|
|
999
|
-
<Stack.Screen
|
|
1000
|
-
name="Detail"
|
|
1001
|
-
options={{
|
|
1002
|
-
experimental_enableHighRefreshRate: true,
|
|
1003
|
-
...Transition.Presets.SlideFromBottom(),
|
|
1004
|
-
}}
|
|
1005
|
-
/>
|
|
1006
|
-
```
|
|
1007
|
-
|
|
1008
|
-
> **Note**: This is experimental and may have performance implications. Test on target devices.
|
|
1009
|
-
|
|
1010
|
-
---
|
|
1011
|
-
|
|
1012
|
-
## Migrating from Earlier Versions
|
|
1013
|
-
|
|
1014
|
-
### Deprecated Props
|
|
1015
|
-
|
|
1016
|
-
The following props are deprecated and will be removed in a future version:
|
|
1017
|
-
|
|
1018
|
-
| Deprecated Prop | Use Instead |
|
|
1019
|
-
| ----------------------- | ------------------ |
|
|
1020
|
-
| `isActiveTransitioning` | `active.animating` |
|
|
1021
|
-
| `isDismissing` | `active.closing` |
|
|
673
|
+
1. `Transition.Pressable` measures its bounds on press and stores them with the tag
|
|
674
|
+
2. `Transition.View` on the destination registers as the target for that tag
|
|
675
|
+
3. `Transition.MaskedView` clips content to the animating shared element bounds
|
|
676
|
+
4. The preset interpolates position, size, and mask for a seamless expand/collapse effect
|
|
1022
677
|
|
|
1023
678
|
---
|
|
1024
679
|
|
|
1025
680
|
## Support
|
|
1026
681
|
|
|
1027
|
-
This package is developed in my spare time.
|
|
682
|
+
This package is developed in my spare time.
|
|
1028
683
|
|
|
1029
684
|
If you'd like to fuel the next release, [buy me a coffee](https://buymeacoffee.com/trpfsu)
|
|
1030
685
|
|