react-native-screen-transitions 1.0.3 → 1.1.0

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 CHANGED
@@ -37,126 +37,106 @@ bun add react-native-screen-transitions
37
37
  npm install react-native-reanimated react-native-gesture-handler
38
38
  ```
39
39
 
40
- ## Usage
40
+ ## Your First Screen Transition
41
41
 
42
- ### Integration with expo-router
43
- In expo-router, you can define transitions in your root layout (`app/_layout.tsx`) using the `listeners` prop on `Stack.Screen`. Wrap your app in `GestureHandlerRootView` for gesture support.
42
+ Getting started with screen transitions is simple. Here's how to add your first animated transition:
43
+
44
+ ### 1. Wrap your app with GestureHandlerRootView
44
45
 
45
46
  ```tsx
46
- // app/_layout.tsx
47
- import { Stack } from "expo-router";
47
+ // app/_layout.tsx (expo-router) or App.tsx (react-navigation)
48
48
  import { GestureHandlerRootView } from "react-native-gesture-handler";
49
49
  import Transition from "react-native-screen-transitions";
50
50
 
51
+ export default function RootLayout() {
52
+ return (
53
+ <GestureHandlerRootView style={{ flex: 1 }}>
54
+ {/* Your navigation setup */}
55
+ </GestureHandlerRootView>
56
+ );
57
+ }
58
+ ```
59
+
60
+ ### 2. Add transition configuration to your screens
61
+
62
+ ```tsx
63
+ import { Stack } from "expo-router";
64
+ import Transition from "react-native-screen-transitions";
65
+
51
66
  export default function RootLayout() {
52
67
  return (
53
68
  <GestureHandlerRootView>
54
69
  <Stack>
55
70
  <Stack.Screen
56
- name="index"
57
- listeners={Transition.createConfig}
71
+ name="a"
72
+ options={{ headerShown: false }}
73
+ {...Transition.createScreenConfig()}
58
74
  />
59
75
  <Stack.Screen
60
- name="a"
61
- options={Transition.defaultScreenOptions()}
62
- listeners={(l) =>
63
- Transition.createConfig({
64
- ...l,
65
- ...Transition.presets.SlideFromTop(),
66
- })
67
- }
76
+ name="b"
77
+ {...Transition.createScreenConfig({
78
+ ...Transition.presets.SlideFromTop(),
79
+ })}
68
80
  />
69
- {/* Add more screens with presets */}
70
81
  </Stack>
71
82
  </GestureHandlerRootView>
72
83
  );
73
84
  }
74
85
  ```
75
86
 
76
- **Note**: `Transition.defaultScreenOptions()` returns the required screen options for animations to work properly. It sets `presentation: "containedTransparentModal"`, `headerShown: false`, and `animation: "none"` to ensure the library can control the transition animations.
87
+ > ⚠️ **Important**: The first screen (like "a" in the example) must include `{...Transition.createScreenConfig()}` for it to be properly controlled by incoming screens.
77
88
 
78
- For grouped routes with layouts (e.g., `app/group-a/_layout.tsx`), wrap the nested `Stack` in `Transition.View` to enable transitions:
89
+ ### 3. Use transition-aware components in your screens
79
90
 
80
91
  ```tsx
81
- // app/group-a/_layout.tsx
82
- import { Stack } from "expo-router";
92
+ // a.tsx
83
93
  import Transition from "react-native-screen-transitions";
84
94
 
85
- export default function GroupLayout() {
95
+ export default function A() {
86
96
  return (
87
- <Transition.View>
88
- <Stack>
89
- <Stack.Screen name="a" />
90
- {/* Nested screens */}
91
- </Stack>
97
+ <Transition.View> {/* By default has flex: 1 */}
98
+ {/* Your content */}
92
99
  </Transition.View>
93
100
  );
94
101
  }
95
- ```
96
102
 
97
- ### Integration with react-navigation
98
- For react-navigation, use `createNativeStackNavigator` and apply transitions via `listeners` and `options`.
99
-
100
- ```tsx
101
- // App.tsx
102
- import { createStaticNavigation } from "@react-navigation/native";
103
- import { createNativeStackNavigator } from "@react-navigation/native-stack";
104
- import { GestureHandlerRootView } from "react-native-gesture-handler";
103
+ // b.tsx
105
104
  import Transition from "react-native-screen-transitions";
106
- import { Home } from "./screens/Home"; // Your screens
107
- import { ScreenA } from "./screens/ScreenA";
108
-
109
- const RootStack = createNativeStackNavigator({
110
- screens: {
111
- Home: {
112
- screen: Home,
113
- listeners: Transition.createConfig,
114
- },
115
- ScreenA: {
116
- screen: ScreenA,
117
- options: Transition.defaultScreenOptions(),
118
- listeners: (l) =>
119
- Transition.createConfig({
120
- ...l,
121
- ...Transition.presets.SlideFromTop(),
122
- }),
123
- },
124
- // Add more screens
125
- },
126
- });
127
105
 
128
- const Navigation = createStaticNavigation(RootStack);
129
-
130
- export default function App() {
106
+ export default function B() {
131
107
  return (
132
- <GestureHandlerRootView>
133
- <Navigation />
134
- </GestureHandlerRootView>
108
+ <Transition.View>
109
+ {/* Your content */}
110
+ </Transition.View>
135
111
  );
136
112
  }
137
113
  ```
138
114
 
139
- For nested navigators, wrap them in `Transition.View` similar to expo-router.
140
-
141
- ### Predefined Presets
142
- Use these out-of-the-box animations via `Transition.presets`:
115
+ ### For Nested Navigators
143
116
 
144
- - `SlideFromTop()`: Screen slides in from the top.
145
- - `ZoomIn()`: Screen zooms in from the center.
146
- - `SlideFromBottom()`: Screen slides in from the bottom.
147
- - `DraggableCard()`: Interactive card-like drag gesture.
148
- - `ElasticCard()`: Elastic bounce effect on drag.
117
+ When using nested navigators, wrap the nested Stack in a transition-aware component:
149
118
 
150
- Example:
151
119
  ```tsx
152
- listeners={(l) => Transition.createConfig({ ...l, ...Transition.presets.DraggableCard() })}
120
+ // app/nested/_layout.tsx
121
+ import Transition from "react-native-screen-transitions";
122
+
123
+ export default function TabLayout() {
124
+ return (
125
+ <Transition.View>
126
+ <Stack>
127
+ <Stack.Screen name="nested-a" {...Transition.createScreenConfig()} />
128
+ <Stack.Screen name="nested-b" {...Transition.createScreenConfig()} />
129
+ </Stack>
130
+ </Transition.View>
131
+ );
132
+ }
153
133
  ```
154
134
 
155
- ### Defining your own screen animations
156
- There are two ways to define custom animations: at the navigator level using `screenStyleInterpolator` (recommended for animating both screens simultaneously), or at the screen level using `useScreenAnimation` hook.
135
+ ## Advanced Usage
157
136
 
158
- #### Method 1: Navigator-Level Interpolator (Recommended)
159
- Define a `screenStyleInterpolator` at the navigator level to animate both the entering and exiting screens simultaneously. This approach provides the most control.
137
+ ### Method 1: Navigator-Level Interpolator (Recommended)
138
+
139
+ Define a `screenStyleInterpolator` at the navigator level to animate both entering and exiting screens simultaneously. This approach is much cleaner.
160
140
 
161
141
  ```tsx
162
142
  // app/_layout.tsx
@@ -171,53 +151,47 @@ export default function RootLayout() {
171
151
  <Stack>
172
152
  <Stack.Screen
173
153
  name="a"
174
- listeners={Transition.createConfig} // Add blank config so system knows what to animate
154
+ {...Transition.createScreenConfig()} // Initialize
175
155
  />
176
156
  <Stack.Screen
177
157
  name="b"
178
- options={Transition.defaultScreenOptions()}
179
- listeners={(l) =>
180
- Transition.createConfig({
181
- ...l,
182
- gestureDirection: "horizontal",
183
- gestureEnabled: true,
184
- gestureResponseDistance: 50,
185
- gestureVelocityImpact: 0.3,
186
- screenStyleInterpolator: ({
187
- current,
188
- next,
189
- layouts: { screen: { width } },
190
- }) => {
191
- "worklet";
192
-
193
- const progress = current.progress.value + (next?.progress.value || 0);
194
-
195
- const translateX = interpolate(
196
- progress,
197
- [0, 1, 2],
198
- [width, 0, width * -0.3],
199
- "clamp"
200
- );
201
-
202
- return {
203
- contentStyle: {
204
- transform: [{ translateX }],
205
- },
206
- };
207
- },
208
- transitionSpec: {
209
- open: {
210
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
211
- duration: 1000,
212
- },
213
- close: {
214
- damping: 10,
215
- mass: 0.5,
216
- stiffness: 100,
158
+ {...Transition.createScreenConfig({
159
+ gestureDirection: "horizontal",
160
+ gestureEnabled: true,
161
+ gestureResponseDistance: 50,
162
+ gestureVelocityImpact: 0.3,
163
+ screenStyleInterpolator: ({
164
+ current,
165
+ next,
166
+ layouts: { screen: { width } },
167
+ }) => {
168
+ "worklet";
169
+ // Mimics the iOS stack slide animation
170
+ const progress = current.progress.value + (next?.progress.value || 0);
171
+ const translateX = interpolate(
172
+ progress,
173
+ [0, 1, 2],
174
+ [width, 0, width * -0.3],
175
+ "clamp"
176
+ );
177
+ return {
178
+ contentStyle: {
179
+ transform: [{ translateX }],
217
180
  },
181
+ };
182
+ },
183
+ transitionSpec: {
184
+ open: {
185
+ easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
186
+ duration: 1000,
187
+ },
188
+ close: {
189
+ damping: 10,
190
+ mass: 0.5,
191
+ stiffness: 100,
218
192
  },
219
- })
220
- }
193
+ },
194
+ })}
221
195
  />
222
196
  </Stack>
223
197
  </GestureHandlerRootView>
@@ -225,60 +199,33 @@ export default function RootLayout() {
225
199
  }
226
200
  ```
227
201
 
228
- When using `screenStyleInterpolator`, both screens must wrap their content in `Transition.View`:
229
-
230
- ```tsx
231
- // a.tsx
232
- import Transition from 'react-native-screen-transitions';
233
-
234
- export default function A() {
235
- return (
236
- <Transition.View>
237
- {/* Your content */}
238
- </Transition.View>
239
- );
240
- }
241
-
242
- // b.tsx
243
- import Transition from 'react-native-screen-transitions';
202
+ When using `screenStyleInterpolator`, both screens must wrap their content in a transition-aware component.
244
203
 
245
- export default function B() {
246
- return (
247
- <Transition.View>
248
- {/* Your content */}
249
- </Transition.View>
250
- );
251
- }
252
- ```
204
+ ### Method 2: Screen-Level Animations
253
205
 
254
- #### Method 2: Screen-Level Animations
255
- Alternatively, define animations at the screen level using the `useScreenAnimation` hook. This is useful for screen-specific effects or when you don't need to animate both screens.
206
+ Alternatively, define animations at the screen level using the `useScreenAnimation` hook. This is useful for screen-specific effects or when you don't need to animate both screens. You CAN combine this with `screenStyleInterpolator` for more advanced animations, but for this example, we'll leave `screenStyleInterpolator` undefined.
256
207
 
257
208
  ```tsx
258
209
  // app/_layout.tsx
259
210
  <Stack.Screen
260
211
  name="a"
261
- listeners={Transition.createConfig}
212
+ {...Transition.createScreenConfig()}
262
213
  />
263
214
  <Stack.Screen
264
215
  name="b"
265
- options={Transition.defaultScreenOptions()}
266
- listeners={(l) =>
267
- Transition.createConfig({
268
- ...l,
269
- transitionSpec: {
270
- open: {
271
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
272
- duration: 1000,
273
- },
274
- close: {
275
- damping: 10,
276
- mass: 0.5,
277
- stiffness: 100,
278
- },
216
+ {...Transition.createScreenConfig({
217
+ transitionSpec: {
218
+ open: {
219
+ easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
220
+ duration: 1000,
279
221
  },
280
- })
281
- }
222
+ close: {
223
+ damping: 10,
224
+ mass: 0.5,
225
+ stiffness: 100,
226
+ },
227
+ },
228
+ })}
282
229
  />
283
230
  ```
284
231
 
@@ -299,7 +246,7 @@ export default function A() {
299
246
  });
300
247
 
301
248
  return (
302
- <Animated.View style={animatedStyle}>
249
+ <Animated.View style={[{ flex: 1 }, animatedStyle]}>
303
250
  {/* Your content */}
304
251
  </Animated.View>
305
252
  );
@@ -326,30 +273,129 @@ export default function B() {
326
273
  </Animated.View>
327
274
  );
328
275
  }
276
+ ```
277
+
278
+
329
279
 
280
+ ## Dismissible Screens with Scrollables
330
281
 
282
+ Screen transitions can be dismissed based on defined gesture directions. Integration with scrollable components is seamless:
283
+
284
+ ### Create Custom Transition-Aware Scrollables
285
+
286
+ You can use built-in scrollables or create your own
287
+
288
+ ```tsx
289
+ import { FlashList } from "@shopify/flash-list";
290
+ import { LegendList } from "@legendapp/list";
291
+ import { FlatList, ScrollView } from "react-native";
292
+ import Transition from "react-native-screen-transitions";
293
+
294
+ // Built-in transition-aware scrollables
295
+ const MyScrollView = Transition.ScrollView;
296
+ const MyFlatList = Transition.FlatList;
297
+
298
+ // Create custom transition-aware scrollables
299
+ const TransitionFlashList = Transition.createTransitionAwareScrollable(FlashList);
300
+ const TransitionLegendList = Transition.createTransitionAwareScrollable(LegendList);
331
301
  ```
332
302
 
303
+ These components now integrate seamlessly with your transition system and provide smart gesture handling.
304
+
305
+ ### Configure Gesture Directions
306
+
307
+ ```tsx
308
+ <Stack.Screen
309
+ name="scrollable-screen"
310
+ {...Transition.createScreenConfig({
311
+ gestureDirection: ["vertical", "vertical-inverted"],
312
+ gestureEnabled: true,
313
+ // ... other config
314
+ })}
315
+ />
316
+ ```
317
+
318
+ ### Use in Your Screen
319
+
320
+ ```tsx
321
+ // scrollable-screen.tsx
322
+ export default function ScrollableScreen() {
323
+ return (
324
+ <Transition.ScrollView>
325
+ {/* Your scrollable content */}
326
+ </Transition.ScrollView>
327
+ );
328
+ }
329
+ ```
330
+
331
+ **Smart Gesture Handling**: The screen will only start the dismissal process when:
332
+ - `vertical`: ScrollView is at the top (scrollY <= 0)
333
+ - `vertical-inverted`: ScrollView is at the bottom (scrollY >= maxScroll)
334
+ - `horizontal`: ScrollView is at the left edge
335
+ - `horizontal-inverted`: ScrollView is at the right edge
336
+
337
+ This prevents gesture conflicts and provides intuitive user interaction.
338
+
339
+ ## Performance Considerations
340
+
341
+ This package is designed for optimal performance, but since underlying screens remain active during transitions, following these guidelines will help maintain smooth 60fps animations:
342
+
343
+ ### Screen Optimization
344
+ - **Keep screens lightweight**: Minimize heavy computations and complex layouts in screens that will be animated
345
+
346
+ ### Animation Properties
347
+ Prioritize transform and opacity properties over layout-affecting properties for the smoothest animations:
348
+
349
+ **✅ Performant properties:**
350
+ - `transform` (translateX, translateY, scale, rotate)
351
+ - `opacity`
352
+ - `backgroundColor` (with caution)
353
+
354
+ **❌ Avoid when possible:**
355
+ - Layout properties (`width`, `height`, `padding`, `margin`)
356
+ - `borderRadius` on large elements
357
+ - Complex `shadowOffset` or `elevation` changes
358
+ - Frequent `zIndex` modifications
359
+
360
+ ### Easing and Timing Configuration
361
+
362
+ Choose balanced easing curves to avoid perceived delays:
363
+
364
+ ```tsx
365
+ // ✅ Good - Smooth and responsive
366
+ transitionSpec: {
367
+ open: {
368
+ duration: 300,
369
+ easing: Easing.bezier(0.25, 0.1, 0.25, 1), // iOS-like easing
370
+ },
371
+ close: {
372
+ duration: 250,
373
+ easing: Easing.out(Easing.quad),
374
+ },
375
+ }
376
+
377
+ // ❌ Avoid - Too snappy, may cause perceived delays
378
+ transitionSpec: {
379
+ close: {
380
+ duration: 400,
381
+ easing: Easing.bezierFn(0, 0.98, 0, 1), // Too abrupt
382
+ },
383
+ }
384
+ ```
385
+
386
+ **Why timing matters**: Screen dismissal callbacks execute when animations complete. Overly snappy configurations can create a perceived delay between gesture end and actual screen dismissal. Find the sweet spot that matches your app's personality while feeling responsive.
387
+
333
388
 
334
- ### Known Issues and Roadmap
335
- This is a WIP package, so expect improvements. Current known issues:
336
- - **Gestures with ScrollViews**: Gestures can be wonky when combined with scrollable content. For example, if a screen defines vertical dismissal gestures and contains a vertical `ScrollView`, the gesture may not trigger reliably (conflicts with scroll handling).
337
- - **Gestures Dismisall with Nested Navigators**: When using nested navigators with gesture dismissal enabled, dismissing a nested screen via gesture may cause the transparent modal to appear dismissed while remaining open. This affects the visual state but not the actual navigation state.
338
- - **Web Support**: Not intended or tested for web—focus is on mobile (iOS/Android). Web may have issues with gestures and animations.
389
+ ## Support and Development
339
390
 
391
+ This package is provided as-is and is developed in my free time. While I strive to maintain and improve it, please understand that:
340
392
 
341
- ### Note:
393
+ - **Updates and bug fixes** may take time to implement
394
+ - **Feature requests** will be considered but may not be prioritized immediately
342
395
 
343
- This package is something I'll work on in my freetime. However, you can see the roadmap I plan on following in the coming future.
396
+ I apologize for any inconvenience this may cause. If you encounter issues or have suggestions, please feel free to open an issue on the repository.
344
397
 
345
- Roadmap:
346
- - Fix gesture conflicts with ScrollViews (e.g., better gesture priority handling).
347
- - Add more presets and customization options.
348
- - Improve documentation and examples.
349
- - Potential web support if demand arises.
350
- - Testing for more edge cases (e.g., modals, tabs).
351
398
 
352
- See the examples in `/examples/expo-router-example` and `/examples/react-nav-example` for full demos.
353
399
 
354
400
  ## License
355
401
  MIT