react-native-header-motion 1.0.0-alpha.0 → 1.0.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.
Files changed (137) hide show
  1. package/README.md +65 -528
  2. package/lib/module/components/Bridge.js +16 -0
  3. package/lib/module/components/Bridge.js.map +1 -0
  4. package/lib/module/components/FlatList.js +5 -54
  5. package/lib/module/components/FlatList.js.map +1 -1
  6. package/lib/module/components/Header.js +71 -13
  7. package/lib/module/components/Header.js.map +1 -1
  8. package/lib/module/components/HeaderDynamic.js +34 -0
  9. package/lib/module/components/HeaderDynamic.js.map +1 -0
  10. package/lib/module/components/HeaderMotion.js +14 -20
  11. package/lib/module/components/HeaderMotion.js.map +1 -1
  12. package/lib/module/components/HeaderPanBoundary.js +54 -0
  13. package/lib/module/components/HeaderPanBoundary.js.map +1 -0
  14. package/lib/module/components/NavigationBridge.js +20 -0
  15. package/lib/module/components/NavigationBridge.js.map +1 -0
  16. package/lib/module/components/ScrollManager.js +19 -7
  17. package/lib/module/components/ScrollManager.js.map +1 -1
  18. package/lib/module/components/ScrollView.js +6 -39
  19. package/lib/module/components/ScrollView.js.map +1 -1
  20. package/lib/module/components/createHeaderMotionScrollable.js +136 -0
  21. package/lib/module/components/createHeaderMotionScrollable.js.map +1 -0
  22. package/lib/module/components/index.js +3 -1
  23. package/lib/module/components/index.js.map +1 -1
  24. package/lib/module/context.js +8 -1
  25. package/lib/module/context.js.map +1 -1
  26. package/lib/module/hooks/index.js +1 -0
  27. package/lib/module/hooks/index.js.map +1 -1
  28. package/lib/module/hooks/useActiveScrollId.js +7 -6
  29. package/lib/module/hooks/useActiveScrollId.js.map +1 -1
  30. package/lib/module/hooks/useConsumerScrollHandlers.js +86 -0
  31. package/lib/module/hooks/useConsumerScrollHandlers.js.map +1 -0
  32. package/lib/module/hooks/useHeaderMotionBridge.js +14 -0
  33. package/lib/module/hooks/useHeaderMotionBridge.js.map +1 -0
  34. package/lib/module/hooks/useMotionProgress.js +12 -42
  35. package/lib/module/hooks/useMotionProgress.js.map +1 -1
  36. package/lib/module/hooks/useMotionProgress.test.js +56 -0
  37. package/lib/module/hooks/useMotionProgress.test.js.map +1 -0
  38. package/lib/module/hooks/useScrollManager.js +168 -87
  39. package/lib/module/hooks/useScrollManager.js.map +1 -1
  40. package/lib/module/index.js +21 -18
  41. package/lib/module/index.js.map +1 -1
  42. package/lib/module/utils/defaults.js +2 -1
  43. package/lib/module/utils/defaults.js.map +1 -1
  44. package/lib/module/utils/header.js +24 -0
  45. package/lib/module/utils/header.js.map +1 -0
  46. package/lib/module/utils/headerOffsetStyle.js +31 -0
  47. package/lib/module/utils/headerOffsetStyle.js.map +1 -0
  48. package/lib/module/utils/index.js +2 -0
  49. package/lib/module/utils/index.js.map +1 -1
  50. package/lib/typescript/docs/docusaurus.config.d.ts +4 -0
  51. package/lib/typescript/docs/docusaurus.config.d.ts.map +1 -0
  52. package/lib/typescript/docs/sidebars.d.ts +4 -0
  53. package/lib/typescript/docs/sidebars.d.ts.map +1 -0
  54. package/lib/typescript/docs/src/pages/index.d.ts +2 -0
  55. package/lib/typescript/docs/src/pages/index.d.ts.map +1 -0
  56. package/lib/typescript/src/components/Bridge.d.ts +19 -0
  57. package/lib/typescript/src/components/Bridge.d.ts.map +1 -0
  58. package/lib/typescript/src/components/FlatList.d.ts +7 -15
  59. package/lib/typescript/src/components/FlatList.d.ts.map +1 -1
  60. package/lib/typescript/src/components/Header.d.ts +73 -12
  61. package/lib/typescript/src/components/Header.d.ts.map +1 -1
  62. package/lib/typescript/src/components/HeaderDynamic.d.ts +11 -0
  63. package/lib/typescript/src/components/HeaderDynamic.d.ts.map +1 -0
  64. package/lib/typescript/src/components/HeaderMotion.d.ts +38 -23
  65. package/lib/typescript/src/components/HeaderMotion.d.ts.map +1 -1
  66. package/lib/typescript/src/components/HeaderPanBoundary.d.ts +11 -0
  67. package/lib/typescript/src/components/HeaderPanBoundary.d.ts.map +1 -0
  68. package/lib/typescript/src/components/NavigationBridge.d.ts +19 -0
  69. package/lib/typescript/src/components/NavigationBridge.d.ts.map +1 -0
  70. package/lib/typescript/src/components/ScrollManager.d.ts +13 -9
  71. package/lib/typescript/src/components/ScrollManager.d.ts.map +1 -1
  72. package/lib/typescript/src/components/ScrollView.d.ts +7 -14
  73. package/lib/typescript/src/components/ScrollView.d.ts.map +1 -1
  74. package/lib/typescript/src/components/createHeaderMotionScrollable.d.ts +86 -0
  75. package/lib/typescript/src/components/createHeaderMotionScrollable.d.ts.map +1 -0
  76. package/lib/typescript/src/components/index.d.ts +3 -1
  77. package/lib/typescript/src/components/index.d.ts.map +1 -1
  78. package/lib/typescript/src/context.d.ts +3 -17
  79. package/lib/typescript/src/context.d.ts.map +1 -1
  80. package/lib/typescript/src/hooks/index.d.ts +1 -0
  81. package/lib/typescript/src/hooks/index.d.ts.map +1 -1
  82. package/lib/typescript/src/hooks/useActiveScrollId.d.ts +7 -6
  83. package/lib/typescript/src/hooks/useActiveScrollId.d.ts.map +1 -1
  84. package/lib/typescript/src/hooks/useConsumerScrollHandlers.d.ts +64 -0
  85. package/lib/typescript/src/hooks/useConsumerScrollHandlers.d.ts.map +1 -0
  86. package/lib/typescript/src/hooks/useHeaderMotionBridge.d.ts +10 -0
  87. package/lib/typescript/src/hooks/useHeaderMotionBridge.d.ts.map +1 -0
  88. package/lib/typescript/src/hooks/useMotionProgress.d.ts +8 -25
  89. package/lib/typescript/src/hooks/useMotionProgress.d.ts.map +1 -1
  90. package/lib/typescript/src/hooks/useMotionProgress.test.d.ts +2 -0
  91. package/lib/typescript/src/hooks/useMotionProgress.test.d.ts.map +1 -0
  92. package/lib/typescript/src/hooks/useScrollManager.d.ts +61 -29
  93. package/lib/typescript/src/hooks/useScrollManager.d.ts.map +1 -1
  94. package/lib/typescript/src/index.d.ts +56 -26
  95. package/lib/typescript/src/index.d.ts.map +1 -1
  96. package/lib/typescript/src/types.d.ts +54 -17
  97. package/lib/typescript/src/types.d.ts.map +1 -1
  98. package/lib/typescript/src/utils/defaults.d.ts +3 -2
  99. package/lib/typescript/src/utils/defaults.d.ts.map +1 -1
  100. package/lib/typescript/src/utils/header.d.ts +10 -0
  101. package/lib/typescript/src/utils/header.d.ts.map +1 -0
  102. package/lib/typescript/src/utils/headerOffsetStyle.d.ts +19 -0
  103. package/lib/typescript/src/utils/headerOffsetStyle.d.ts.map +1 -0
  104. package/lib/typescript/src/utils/index.d.ts +2 -0
  105. package/lib/typescript/src/utils/index.d.ts.map +1 -1
  106. package/lib/typescript/src/utils/refreshControl.d.ts +12 -12
  107. package/package.json +12 -5
  108. package/src/components/Bridge.tsx +29 -0
  109. package/src/components/FlatList.tsx +18 -76
  110. package/src/components/Header.tsx +159 -23
  111. package/src/components/HeaderDynamic.tsx +45 -0
  112. package/src/components/HeaderMotion.tsx +47 -50
  113. package/src/components/HeaderPanBoundary.tsx +92 -0
  114. package/src/components/NavigationBridge.tsx +30 -0
  115. package/src/components/ScrollManager.tsx +23 -11
  116. package/src/components/ScrollView.tsx +16 -60
  117. package/src/components/createHeaderMotionScrollable.tsx +438 -0
  118. package/src/components/index.ts +3 -1
  119. package/src/context.ts +11 -24
  120. package/src/hooks/index.ts +1 -0
  121. package/src/hooks/useActiveScrollId.ts +7 -6
  122. package/src/hooks/useConsumerScrollHandlers.ts +148 -0
  123. package/src/hooks/useHeaderMotionBridge.ts +15 -0
  124. package/src/hooks/useMotionProgress.test.ts +67 -0
  125. package/src/hooks/useMotionProgress.ts +12 -45
  126. package/src/hooks/useScrollManager.ts +251 -114
  127. package/src/index.ts +82 -36
  128. package/src/types.ts +81 -29
  129. package/src/utils/defaults.ts +7 -1
  130. package/src/utils/header.tsx +52 -0
  131. package/src/utils/headerOffsetStyle.ts +40 -0
  132. package/src/utils/index.ts +2 -0
  133. package/lib/module/components/HeaderBase.js +0 -107
  134. package/lib/module/components/HeaderBase.js.map +0 -1
  135. package/lib/typescript/src/components/HeaderBase.d.ts +0 -41
  136. package/lib/typescript/src/components/HeaderBase.d.ts.map +0 -1
  137. package/src/components/HeaderBase.tsx +0 -140
package/README.md CHANGED
@@ -1,591 +1,128 @@
1
1
  # React Native Header Motion
2
2
 
3
- High-level APIs for **orchestrating header motion** driven by scroll built on top of [**React Native Reanimated**](https://docs.swmansion.com/react-native-reanimated/).
3
+ Scroll-driven animated headers for React Native.
4
4
 
5
- This library is **100% a wrapper around Reanimated**. All the credit for the underlying animation engine, worklets, and primitives goes to **Reanimated** (and `react-native-worklets`). This package focuses on a specific use case: **header motion + scroll orchestration** (including multi-scroll/tab scenarios).
5
+ React Native Header Motion gives you the plumbing for collapsible, progress-driven headers without forcing a prebuilt UI on you. It measures the header, derives a shared `progress` value, keeps multiple scrollables in sync, and bridges that state into navigation-rendered headers when needed.
6
6
 
7
- <div align="center">
8
- <img src="https://github.com/user-attachments/assets/b673349a-f26a-4cc8-bfe1-60d77deb4390" width="70%" />
9
- </div>
10
-
11
- ## v1 alpha status
12
-
13
- `v1.0.0-alpha.x` is pre-release quality.
14
-
15
- - Expect additional API changes (including breaking ones) before stable `1.0.0`.
16
- - If you are upgrading from `0.3.x`, use the migration doc: [MIGRATION-v1.md](./MIGRATION-v1.md).
7
+ Built on top of:
17
8
 
18
- ## What changed since `v0.3.0`
9
+ - [React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/)
10
+ - [React Native Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/docs/)
11
+ - [React Native Worklets](https://docs.swmansion.com/react-native-worklets/docs/)
19
12
 
20
- - **Performance-focused internals:** motion threshold + header height now flow through `SharedValue`s to reduce JS-side churn.
21
- - **Pannable header support:** new `enableHeaderPan` on `HeaderMotion` and required `animatedHeaderBaseProps` on `AnimatedHeaderBase`.
22
- - **Ecosystem update:** example app moved to Expo 55 + Reanimated 4.2; `react-native-gesture-handler` is now a peer dependency.
23
-
24
- ## What this is (and isn’t)
25
-
26
- **✅ This is**
27
-
28
- - A small set of components + hooks that expose a single `progress` shared value and a few measurement helpers.
29
- - A scroll orchestration layer that can keep multiple scrollables in sync (e.g. tabs + pager).
13
+ <div align="center">
14
+ <img src="https://github.com/user-attachments/assets/b673349a-f26a-4cc8-bfe1-60d77deb4390" width="70%" />
15
+ </div>
30
16
 
31
- **❌ This is NOT**
17
+ ## Documentation
32
18
 
33
- - An out-of-the-box “collapsible header” component with a baked-in look.
19
+ Full documentation lives here:
34
20
 
35
- You build any header motion you want by animating based on `progress`.
21
+ - [Docs home](https://pawicao.github.io/react-native-header-motion/)
22
+ - [Overview](https://pawicao.github.io/react-native-header-motion/docs/overview)
23
+ - [Installation](https://pawicao.github.io/react-native-header-motion/docs/installation)
24
+ - [Quick Start](https://pawicao.github.io/react-native-header-motion/docs/quick-start)
25
+ - [Guides](https://pawicao.github.io/react-native-header-motion/docs/guides/dynamic-header-measurement)
26
+ - [API Reference](https://pawicao.github.io/react-native-header-motion/docs/api/header-motion)
36
27
 
37
- ## Requirements (peer dependencies)
28
+ ## What it helps with
38
29
 
39
- You must have these installed in your app:
30
+ - Scroll-driven animated headers
31
+ - Shared header state across tabs, pagers, and multiple scrollables
32
+ - Navigation-rendered headers in Expo Router or React Navigation
33
+ - Custom scrollables via `createHeaderMotionScrollable()`
34
+ - Optional header panning
40
35
 
41
- - `react-native-gesture-handler` **>= 2.0.0**
42
- - `react-native-reanimated` **>= 4.0.0**
43
- - `react-native-worklets` **>= 0.4.0**
36
+ ## What it is not
44
37
 
45
- This package declares them as peer dependencies, so your app owns those versions. Remember to install a version of Worklets compatible with your version of Reanimated.
38
+ - A fully styled header component
39
+ - A page layout framework
40
+ - A general-purpose animation abstraction on top of Reanimated
46
41
 
47
42
  ## Installation
48
43
 
49
44
  ```bash
50
- npm i react-native-header-motion
51
- ```
52
-
53
- or
54
-
55
- ```bash
56
- yarn add react-native-header-motion
45
+ npm install react-native-header-motion
57
46
  ```
58
47
 
59
- ### Reanimated setup
60
-
61
- Follow the official Reanimated [installation instructions](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/#installation) for your environment (Expo / bare RN).
62
-
63
- ## Mental model
64
-
65
- There are three key concepts:
66
-
67
- ### 1) `progress` (SharedValue)
68
-
69
- `progress` is a Reanimated `SharedValue<number>` that represents the normalized progress of your header animation.
70
-
71
- - `0` → animation start (initial state)
72
- - `1` → animation end (final state)
73
-
74
- ### 2) `progressThreshold` (prop vs runtime value)
75
-
76
- `progressThreshold` is the distance needed for `progress` to move from `0 → 1`.
77
-
78
- As a `HeaderMotion` prop, you can provide:
79
-
80
- - a number, or
81
- - a function `(measuredDynamic) => threshold`
82
-
83
- If you provide a function, it uses the value measured by `measureDynamic`.
84
-
85
- When you read `progressThreshold` from `useMotionProgress()` / `HeaderMotion.Header`, it is a `SharedValue<number>`.
86
- Read it inside worklets via `progressThreshold.get()` (or `progressThreshold.value`).
87
-
88
- ### 3) Measurement functions
89
-
90
- The library gives you two measurement callbacks that you pass to your header layout:
91
-
92
- - `measureTotalHeight` – attach to the _outer_ header container to measure the total header height. Scrollables use this to add `paddingTop` so content starts below the header.
93
- - `measureDynamic` – attach to the part of the header that determines the threshold (often the animated/dynamic portion).
94
-
95
- ## Why `HeaderMotion.Header` exists
96
-
97
- When you pass a `header` component to React Navigation / Expo Router, that header is rendered by the navigator in a different part of the React tree.
98
-
99
- Because of that, the navigation header **cannot read the `HeaderMotion` context**, so calling `useMotionProgress()` inside that header would throw.
100
-
101
- `HeaderMotion.Header` solves this by acting as a **bridge**: it runs inside the provider, reads context, and passes the values to your navigation header via a render function.
102
-
103
- ## Why `HeaderBase` / `AnimatedHeaderBase` uses absolute positioning
104
-
105
- Navigation headers are special:
106
-
107
- - Even with `headerTransparent: true`, the navigator can still reserve layout space for the header container.
108
- - If you animate with translations without absolute positioning, you can end up with:
109
- - content below becoming unclickable (an invisible parent header still sits on top), or
110
- - content hidden under the header container.
48
+ Peer dependencies:
111
49
 
112
- `HeaderBase` and `AnimatedHeaderBase` are **absolutely positioned** to avoid those layout traps, which is especially important when you use transforms/translations.
50
+ - `react-native-reanimated@^4.0.0`
51
+ - `react-native-gesture-handler@^2.0.0`
52
+ - `react-native-worklets@>=0.4.0`
113
53
 
114
- ## When to use components vs hooks
54
+ Then complete the standard setup for:
115
55
 
116
- You can use either style; pick based on your integration needs:
56
+ - [Reanimated and Worklets](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/#installation)
57
+ - [Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/docs/fundamentals/installation)
117
58
 
118
- - Prefer **components** when you want a “batteries included” wiring:
59
+ For other package managers and full setup notes, see the [installation guide](https://pawicao.github.io/react-native-header-motion/docs/installation).
119
60
 
120
- - `HeaderMotion.ScrollView` / `HeaderMotion.FlatList` for common scrollables
121
- - `HeaderMotion.ScrollManager` for custom scrollables via render-props
122
-
123
- - Prefer **hooks** when you want to build your own wrappers:
124
- - `useScrollManager()` (same engine as `HeaderMotion.ScrollManager`, but hook-based)
125
- - `useMotionProgress()` when your header is inside the provider tree
126
-
127
- Also:
128
-
129
- - Use `HeaderMotion.Header` when your header is rendered by navigation.
130
- - Use `useMotionProgress` when your header is rendered inside the same tree as `HeaderMotion`.
131
-
132
- ## Examples
133
-
134
- ### Example app
135
-
136
- Examples live in the example app: `example/`. They demonstrate a few cases, from simple animations, to scroll orchestration and persisted header animation state between different tabs (e.g. with `react-native-pager-view`).
137
-
138
- Those examples use Expo Router as the navigation library, but it should be fairly simple to do the same with plain React Navigation.
139
-
140
- ### Expo Router
141
-
142
- This is the core pattern used in the example app (`example/src/app/simple.tsx`).
61
+ ## Quick example
143
62
 
144
63
  ```tsx
145
- import HeaderMotion, {
146
- AnimatedHeaderBase,
147
- type WithCollapsibleHeaderProps,
148
- } from 'react-native-header-motion';
64
+ import HeaderMotion from 'react-native-header-motion';
149
65
  import { Stack } from 'expo-router';
150
- import Animated, {
151
- Extrapolation,
152
- interpolate,
153
- useAnimatedStyle,
154
- } from 'react-native-reanimated';
155
- import { useSafeAreaInsets } from 'react-native-safe-area-context';
156
66
  import { View } from 'react-native';
157
67
 
158
68
  export default function Screen() {
159
69
  return (
160
70
  <HeaderMotion>
161
- <HeaderMotion.Header>
162
- {(headerProps) => (
71
+ <HeaderMotion.Bridge>
72
+ {(ctx) => (
163
73
  <Stack.Screen
164
74
  options={{
165
- header: () => <MyHeader {...headerProps} />,
75
+ header: () => (
76
+ <HeaderMotion.NavigationBridge value={ctx}>
77
+ <AppHeader />
78
+ </HeaderMotion.NavigationBridge>
79
+ ),
166
80
  }}
167
81
  />
168
82
  )}
169
- </HeaderMotion.Header>
170
-
171
- <HeaderMotion.ScrollView>
172
- {/* your scrollable content */}
173
- </HeaderMotion.ScrollView>
174
- </HeaderMotion>
175
- );
176
- }
177
-
178
- function MyHeader({
179
- progress,
180
- measureTotalHeight,
181
- measureDynamic,
182
- progressThreshold,
183
- animatedHeaderBaseProps,
184
- }: WithCollapsibleHeaderProps) {
185
- const insets = useSafeAreaInsets();
186
-
187
- const containerStyle = useAnimatedStyle(() => {
188
- const threshold = progressThreshold.get();
189
- const translateY = interpolate(
190
- progress.get(),
191
- [0, 1],
192
- [0, -threshold],
193
- Extrapolation.CLAMP
194
- );
195
- return { transform: [{ translateY }] };
196
- });
197
-
198
- return (
199
- <AnimatedHeaderBase
200
- animatedHeaderBaseProps={animatedHeaderBaseProps}
201
- onLayout={measureTotalHeight}
202
- style={[{ paddingTop: insets.top }, containerStyle]}
203
- >
204
- <Animated.View onLayout={measureDynamic}>
205
- {/* “dynamic” part of the header */}
206
- </Animated.View>
207
-
208
- <View>{/* "regular" part of the header */}</View>
209
- </AnimatedHeaderBase>
210
- );
211
- }
212
- ```
213
-
214
- ### React Navigation
215
-
216
- In React Navigation you typically configure headers via `navigation.setOptions()`.
83
+ </HeaderMotion.Bridge>
217
84
 
218
- Important: the header itself can’t call `useMotionProgress()`, so we still use `HeaderMotion.Header` as a bridge.
219
-
220
- ```tsx
221
- import React from 'react';
222
- import HeaderMotion, {
223
- AnimatedHeaderBase,
224
- type WithCollapsibleHeaderProps,
225
- } from 'react-native-header-motion';
226
- import { useNavigation } from '@react-navigation/native';
227
- import Animated, {
228
- Extrapolation,
229
- interpolate,
230
- useAnimatedStyle,
231
- } from 'react-native-reanimated';
232
- import { useSafeAreaInsets } from 'react-native-safe-area-context';
233
- import { View } from 'react-native';
234
-
235
- export function MyScreen() {
236
- return (
237
- <HeaderMotion>
238
- <HeaderMotion.Header>
239
- {(headerProps) => (
240
- <NavigationHeaderInstaller headerProps={headerProps} />
241
- )}
242
- </HeaderMotion.Header>
243
85
  <HeaderMotion.ScrollView>{/* content */}</HeaderMotion.ScrollView>
244
86
  </HeaderMotion>
245
87
  );
246
88
  }
247
89
 
248
- function NavigationHeaderInstaller({
249
- headerProps,
250
- }: {
251
- headerProps: WithCollapsibleHeaderProps;
252
- }) {
253
- const navigation = useNavigation();
254
-
255
- React.useLayoutEffect(() => {
256
- navigation.setOptions({
257
- header: () => <MyHeader {...headerProps} />,
258
- });
259
- }, [navigation, headerProps]);
260
-
261
- return null;
262
- }
263
-
264
- function MyHeader({
265
- progress,
266
- measureTotalHeight,
267
- measureDynamic,
268
- progressThreshold,
269
- animatedHeaderBaseProps,
270
- }: WithCollapsibleHeaderProps) {
271
- const insets = useSafeAreaInsets();
272
-
273
- const containerStyle = useAnimatedStyle(() => {
274
- const threshold = progressThreshold.get();
275
- const translateY = interpolate(
276
- progress.get(),
277
- [0, 1],
278
- [0, -threshold],
279
- Extrapolation.CLAMP
280
- );
281
- return { transform: [{ translateY }] };
282
- });
283
-
284
- return (
285
- <AnimatedHeaderBase
286
- animatedHeaderBaseProps={animatedHeaderBaseProps}
287
- onLayout={measureTotalHeight}
288
- style={[{ paddingTop: insets.top }, containerStyle]}
289
- >
290
- <Animated.View onLayout={measureDynamic}>
291
- {/* “dynamic” part of the header */}
292
- </Animated.View>
293
-
294
- <View>{/* "regular" part of the header */}</View>
295
- </AnimatedHeaderBase>
296
- );
297
- }
298
- ```
299
-
300
- ### Tabs / pager: synchronizing multiple scrollables
301
-
302
- If you have multiple scrollables (e.g. pages in `react-native-pager-view`), you can keep a single header progress by:
303
-
304
- 1. Creating a shared “active scroll id” using `useActiveScrollId()`
305
- 2. Passing `activeScrollId.sv` to `<HeaderMotion activeScrollId={...} />`
306
- 3. Rendering each page scrollable with a unique `scrollId`
307
-
308
- The example app shows this pattern in `example/src/app/collapsible-pager.tsx` using `HeaderMotion.ScrollManager`.
309
-
310
- ### Keeping the native header (back button/title) + custom animated header below
311
-
312
- Sometimes you want to keep the native navigation header for back buttons + title, but still animate a custom header section below it.
313
-
314
- In that case:
315
-
316
- - set `headerTransparent: true`
317
- - do **not** provide a custom `header` component
318
- - render your animated header content _inside the screen_ under the native header
319
-
320
- Sketch:
321
-
322
- ```tsx
323
- import HeaderMotion, {
324
- AnimatedHeaderBase,
325
- useMotionProgress,
326
- } from 'react-native-header-motion';
327
- import { Stack } from 'expo-router';
328
- import Animated, {
329
- Extrapolation,
330
- interpolate,
331
- useAnimatedStyle,
332
- } from 'react-native-reanimated';
333
- import { View } from 'react-native';
334
-
335
- export default function Screen() {
90
+ function AppHeader() {
336
91
  return (
337
- <>
338
- <Stack.Screen options={{ headerTransparent: true }} />
339
- <HeaderMotion>
340
- <InlineAnimatedHeader />
341
- <HeaderMotion.ScrollView>
342
- {/* rest of content */}
343
- </HeaderMotion.ScrollView>
344
- </HeaderMotion>
345
- </>
346
- );
347
- }
92
+ <HeaderMotion.Header>
93
+ <HeaderMotion.Header.Dynamic>
94
+ <View>{/* collapsible content */}</View>
95
+ </HeaderMotion.Header.Dynamic>
348
96
 
349
- function InlineAnimatedHeader() {
350
- const {
351
- progress,
352
- measureTotalHeight,
353
- measureDynamic,
354
- progressThreshold,
355
- animatedHeaderBaseProps,
356
- } = useMotionProgress();
357
-
358
- const containerStyle = useAnimatedStyle(() => {
359
- const threshold = progressThreshold.get();
360
- const translateY = interpolate(
361
- progress.get(),
362
- [0, 1],
363
- [0, -threshold],
364
- Extrapolation.CLAMP
365
- );
366
- return { transform: [{ translateY }] };
367
- });
368
-
369
- return (
370
- <AnimatedHeaderBase
371
- animatedHeaderBaseProps={animatedHeaderBaseProps}
372
- onLayout={measureTotalHeight}
373
- style={containerStyle}
374
- >
375
- <Animated.View onLayout={measureDynamic}>
376
- {/* custom animated header content below the native header */}
377
- </Animated.View>
378
- <View>{/* sticky part */}</View>
379
- </AnimatedHeaderBase>
97
+ <View>{/* sticky content */}</View>
98
+ </HeaderMotion.Header>
380
99
  );
381
100
  }
382
101
  ```
383
102
 
384
- ## API
385
-
386
- The package exports a default compound component plus hooks, types, and a couple base components.
387
-
388
- ### `HeaderMotion` (default export)
389
-
390
- `HeaderMotion` is a compound component:
391
-
392
- - `HeaderMotion` (provider)
393
- - `HeaderMotion.Header` (bridge for navigation headers)
394
- - `HeaderMotion.ScrollView` (pre-wired Animated.ScrollView)
395
- - `HeaderMotion.FlatList` (pre-wired Animated.FlatList)
396
- - `HeaderMotion.ScrollManager` (render-prop API for custom scrollables)
397
-
398
- #### Props
399
-
400
- - `progressThreshold?: number | (measuredDynamic: number) => number`
401
- - Defines how many pixels correspond to `progress` going from `0` to `1`.
402
- - If you pass a function, it uses the value measured from `measureDynamic`.
403
- - `measureDynamic?: (e) => number`
404
- - What value to read from the `onLayout` event (defaults to `height`).
405
- - `measureDynamicMode?: 'mount' | 'update'`
406
- - Whether `measureDynamic` updates only once or on every layout recalculation.
407
- - `activeScrollId?: SharedValue<string>`
408
- - Enables multi-scroll orchestration (tabs/pager).
409
- - `progressExtrapolation?: ExtrapolationType`
410
- - Controls how progress behaves outside the threshold range (useful for overscroll).
411
- - `enableHeaderPan?: boolean`
412
- - Enables direct pan gestures on `AnimatedHeaderBase` (`false` by default).
413
-
414
- #### `HeaderMotion.Header`
415
-
416
- Render-prop component that passes motion progress props to a header you render via navigation.
417
-
418
- ```tsx
419
- <HeaderMotion.Header>
420
- {(headerProps) => /* pass headerProps into navigation header */}
421
- </HeaderMotion.Header>
422
- ```
423
-
424
- Use this instead of `useMotionProgress()` when your header is rendered by React Navigation / Expo Router.
425
-
426
- #### `HeaderMotion.ScrollView`
427
-
428
- Animated ScrollView wired with:
429
-
430
- - `onScroll` handler
431
- - `ref`
432
- - automatic `paddingTop` based on measured header height
433
-
434
- Supports `scrollId?: string` for multi-scroll scenarios.
435
-
436
- #### `HeaderMotion.FlatList`
437
-
438
- Animated FlatList wired similarly to the ScrollView.
439
-
440
- Supports `scrollId?: string` for multi-scroll scenarios.
441
-
442
- #### `HeaderMotion.ScrollManager`
443
-
444
- Render-prop API for custom scrollables (pager pages, 3rd party lists, etc.).
445
-
446
- If you use `HeaderMotion.ScrollManager` directly for custom integrations, pass refresh-related props to `ScrollManager` (instead of your inner scrollable):
447
-
448
- - `refreshControl`
449
- - `refreshing`
450
- - `onRefresh`
451
- - optional `progressViewOffset` if you want to force your offset.
452
-
453
- This is required, as the positioning of scrollables is affecting Refresh Control and has to be coupled with the header heights.
454
-
455
- ```tsx
456
- <HeaderMotion.ScrollManager scrollId="A">
457
- {(
458
- scrollableProps,
459
- { originalHeaderHeight, minHeightContentContainerStyle }
460
- ) => (
461
- <Animated.ScrollView {...scrollableProps}>
462
- <Animated.View
463
- style={[
464
- minHeightContentContainerStyle,
465
- { paddingTop: originalHeaderHeight },
466
- ]}
467
- >
468
- {/* content */}
469
- </Animated.View>
470
- </Animated.ScrollView>
471
- )}
472
- </HeaderMotion.ScrollManager>
473
- ```
474
-
475
- Refresh example with explicit props on `ScrollManager`:
476
-
477
- ```tsx
478
- <HeaderMotion.ScrollManager
479
- scrollId="A"
480
- refreshing={refreshing}
481
- onRefresh={onRefresh}
482
- >
483
- {(
484
- { onScroll, refreshControl: managedRefreshControl, ...scrollableProps },
485
- { originalHeaderHeight, minHeightContentContainerStyle }
486
- ) => (
487
- <Animated.ScrollView
488
- {...scrollableProps}
489
- onScroll={onScroll}
490
- refreshControl={managedRefreshControl}
491
- >
492
- <Animated.View
493
- style={[
494
- minHeightContentContainerStyle,
495
- { paddingTop: originalHeaderHeight },
496
- ]}
497
- >
498
- {/* content */}
499
- </Animated.View>
500
- </Animated.ScrollView>
501
- )}
502
- </HeaderMotion.ScrollManager>
503
- ```
504
-
505
- ### Hooks
506
-
507
- #### `useMotionProgress()`
508
-
509
- Returns:
103
+ In a real header, use `useMotionProgress()` to drive your Reanimated styles. See the [Quick Start](https://pawicao.github.io/react-native-header-motion/docs/quick-start) for the full walkthrough, animation examples, and styling details.
510
104
 
511
- - `progress` (`SharedValue<number>`)
512
- - `progressThreshold` (`SharedValue<number>`)
513
- - `measureTotalHeight` (`onLayout` callback)
514
- - `measureDynamic` (`onLayout` callback)
515
- - `animatedHeaderBaseProps` (required by `AnimatedHeaderBase`)
516
- - `activeScrollId` (`SharedValue<string> | undefined`)
105
+ ## Version notes
517
106
 
518
- Only use inside the `HeaderMotion` provider tree.
107
+ - Upgrading from `v0.3.x`? Read [MIGRATION-v1.md](./MIGRATION-v1.md).
108
+ - Need the old API docs? See the [README on branch `v0`](https://github.com/pawicao/react-native-header-motion/blob/v0/README.md).
519
109
 
520
- #### `useScrollManager(scrollId?)`
110
+ ## Example app
521
111
 
522
- Lower-level orchestration hook that powers the component APIs. Returns:
112
+ The repository includes an Expo Router example app covering simple headers, navigation bridging, shared headers across pages, custom scrollables, overscroll, pull to refresh, and more.
523
113
 
524
- - `scrollableProps`: `{ onScroll, scrollEventThrottle, ref }`
525
- - `headerMotionContext`:
526
- - `originalHeaderHeight` (`SharedValue<number>`)
527
- - `minHeightContentContainerStyle` (helps when content is shorter than the threshold)
114
+ See:
528
115
 
529
- #### `useActiveScrollId(initialId)`
530
-
531
- Helper for multi-scroll scenarios (tabs/pager). Returns:
532
-
533
- - `[active, setActive]`
534
- - `active.state` (React state)
535
- - `active.sv` (SharedValue)
536
-
537
- ### Base components
538
-
539
- #### `HeaderBase`
540
-
541
- Non-animated absolutely positioned header base.
542
-
543
- #### `AnimatedHeaderBase`
544
-
545
- Reanimated-powered, absolutely positioned header base.
546
-
547
- - Requires `animatedHeaderBaseProps` from `useMotionProgress()` / `HeaderMotion.Header`.
548
- - It is required for header panning functionality.
549
- - Optional `withGestureHandlerRootView` can wrap this header in `GestureHandlerRootView` when needed.
550
-
551
- ### Types
552
-
553
- - `WithCollapsibleHeaderProps` – convenience type for headers using motion progress props.
554
- - `WithCollapsiblePagedHeaderProps` – like above, plus `activeTab` and `onTabChange`.
555
-
556
- ## Additional notes
557
-
558
- ### Refresh Control (v.0.3.0+)
559
-
560
- Refresh control support was improved in `v0.3.0+`.
561
-
562
- - If you use `HeaderMotion.ScrollView` or `HeaderMotion.FlatList`, your refresh-control usage stays the same as in React Native.
563
- - If you use `HeaderMotion.ScrollManager` directly for custom integrations, pass refresh-related props to `ScrollManager`:
564
- - `refreshControl`
565
- - `refreshing`
566
- - `onRefresh`
567
- - optional `progressViewOffset`
568
-
569
- This is important because scrollable positioning affects refresh-control behavior and needs to stay coupled with measured header height.
570
-
571
- #### Platform support note:
572
-
573
- - Support for Refresh Control is currently partial.
574
- - Android works well with the current implementation.
575
- - iOS behavior is still not fully deterministic.
576
- - `progressViewOffset` does not seem to be reliable on iOS in all scenarios.
577
- - Other iOS approaches tried so far introduced different issues.
578
- - Additional iOS support improvements are planned for future releases.
116
+ - [`example/`](./example/)
117
+ - [Example app docs](https://pawicao.github.io/react-native-header-motion/docs/other/example-app)
579
118
 
580
119
  ## Contributing
581
120
 
582
- - Development workflow: see [CONTRIBUTING.md](CONTRIBUTING.md)
583
- - Code of conduct: see [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
121
+ - [Contributing guide](./CONTRIBUTING.md)
122
+ - [Code of conduct](./CODE_OF_CONDUCT.md)
584
123
 
585
124
  ## License
586
125
 
587
126
  MIT
588
127
 
589
- ---
590
-
591
- Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
128
+ Made with [`create-react-native-library`](https://github.com/callstack/react-native-builder-bob)
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ import { useHeaderMotionBridge } from "../hooks/useHeaderMotionBridge.js";
4
+ /**
5
+ * Reads the current HeaderMotion context and exposes it through a render
6
+ * function so it can be forwarded into another subtree.
7
+ */
8
+ export function Bridge({
9
+ children
10
+ }) {
11
+ if (typeof children !== 'function') {
12
+ throw new Error('HeaderMotion.Bridge only accepts a render function as its child.');
13
+ }
14
+ return children(useHeaderMotionBridge());
15
+ }
16
+ //# sourceMappingURL=Bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["useHeaderMotionBridge","Bridge","children","Error"],"sourceRoot":"../../../src","sources":["components/Bridge.tsx"],"mappings":";;AAAA,SAASA,qBAAqB,QAAQ,mCAAgC;AAgBtE;AACA;AACA;AACA;AACA,OAAO,SAASC,MAAMA,CAAC;EAAEC;AAAkC,CAAC,EAAE;EAC5D,IAAI,OAAOA,QAAQ,KAAK,UAAU,EAAE;IAClC,MAAM,IAAIC,KAAK,CACb,kEACF,CAAC;EACH;EAEA,OAAOD,QAAQ,CAACF,qBAAqB,CAAC,CAAC,CAAC;AAC1C","ignoreList":[]}