react-native-header-motion 1.0.0-alpha.0 → 1.0.0-beta.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 (131) hide show
  1. package/README.md +395 -380
  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/src/components/Bridge.d.ts +19 -0
  51. package/lib/typescript/src/components/Bridge.d.ts.map +1 -0
  52. package/lib/typescript/src/components/FlatList.d.ts +7 -15
  53. package/lib/typescript/src/components/FlatList.d.ts.map +1 -1
  54. package/lib/typescript/src/components/Header.d.ts +73 -12
  55. package/lib/typescript/src/components/Header.d.ts.map +1 -1
  56. package/lib/typescript/src/components/HeaderDynamic.d.ts +11 -0
  57. package/lib/typescript/src/components/HeaderDynamic.d.ts.map +1 -0
  58. package/lib/typescript/src/components/HeaderMotion.d.ts +38 -23
  59. package/lib/typescript/src/components/HeaderMotion.d.ts.map +1 -1
  60. package/lib/typescript/src/components/HeaderPanBoundary.d.ts +11 -0
  61. package/lib/typescript/src/components/HeaderPanBoundary.d.ts.map +1 -0
  62. package/lib/typescript/src/components/NavigationBridge.d.ts +19 -0
  63. package/lib/typescript/src/components/NavigationBridge.d.ts.map +1 -0
  64. package/lib/typescript/src/components/ScrollManager.d.ts +13 -9
  65. package/lib/typescript/src/components/ScrollManager.d.ts.map +1 -1
  66. package/lib/typescript/src/components/ScrollView.d.ts +7 -14
  67. package/lib/typescript/src/components/ScrollView.d.ts.map +1 -1
  68. package/lib/typescript/src/components/createHeaderMotionScrollable.d.ts +86 -0
  69. package/lib/typescript/src/components/createHeaderMotionScrollable.d.ts.map +1 -0
  70. package/lib/typescript/src/components/index.d.ts +3 -1
  71. package/lib/typescript/src/components/index.d.ts.map +1 -1
  72. package/lib/typescript/src/context.d.ts +3 -17
  73. package/lib/typescript/src/context.d.ts.map +1 -1
  74. package/lib/typescript/src/hooks/index.d.ts +1 -0
  75. package/lib/typescript/src/hooks/index.d.ts.map +1 -1
  76. package/lib/typescript/src/hooks/useActiveScrollId.d.ts +7 -6
  77. package/lib/typescript/src/hooks/useActiveScrollId.d.ts.map +1 -1
  78. package/lib/typescript/src/hooks/useConsumerScrollHandlers.d.ts +64 -0
  79. package/lib/typescript/src/hooks/useConsumerScrollHandlers.d.ts.map +1 -0
  80. package/lib/typescript/src/hooks/useHeaderMotionBridge.d.ts +10 -0
  81. package/lib/typescript/src/hooks/useHeaderMotionBridge.d.ts.map +1 -0
  82. package/lib/typescript/src/hooks/useMotionProgress.d.ts +8 -25
  83. package/lib/typescript/src/hooks/useMotionProgress.d.ts.map +1 -1
  84. package/lib/typescript/src/hooks/useMotionProgress.test.d.ts +2 -0
  85. package/lib/typescript/src/hooks/useMotionProgress.test.d.ts.map +1 -0
  86. package/lib/typescript/src/hooks/useScrollManager.d.ts +61 -29
  87. package/lib/typescript/src/hooks/useScrollManager.d.ts.map +1 -1
  88. package/lib/typescript/src/index.d.ts +56 -26
  89. package/lib/typescript/src/index.d.ts.map +1 -1
  90. package/lib/typescript/src/types.d.ts +54 -17
  91. package/lib/typescript/src/types.d.ts.map +1 -1
  92. package/lib/typescript/src/utils/defaults.d.ts +3 -2
  93. package/lib/typescript/src/utils/defaults.d.ts.map +1 -1
  94. package/lib/typescript/src/utils/header.d.ts +10 -0
  95. package/lib/typescript/src/utils/header.d.ts.map +1 -0
  96. package/lib/typescript/src/utils/headerOffsetStyle.d.ts +19 -0
  97. package/lib/typescript/src/utils/headerOffsetStyle.d.ts.map +1 -0
  98. package/lib/typescript/src/utils/index.d.ts +2 -0
  99. package/lib/typescript/src/utils/index.d.ts.map +1 -1
  100. package/lib/typescript/src/utils/refreshControl.d.ts +9 -9
  101. package/package.json +7 -1
  102. package/src/components/Bridge.tsx +29 -0
  103. package/src/components/FlatList.tsx +18 -76
  104. package/src/components/Header.tsx +159 -23
  105. package/src/components/HeaderDynamic.tsx +45 -0
  106. package/src/components/HeaderMotion.tsx +47 -50
  107. package/src/components/HeaderPanBoundary.tsx +92 -0
  108. package/src/components/NavigationBridge.tsx +30 -0
  109. package/src/components/ScrollManager.tsx +23 -11
  110. package/src/components/ScrollView.tsx +16 -60
  111. package/src/components/createHeaderMotionScrollable.tsx +438 -0
  112. package/src/components/index.ts +3 -1
  113. package/src/context.ts +11 -24
  114. package/src/hooks/index.ts +1 -0
  115. package/src/hooks/useActiveScrollId.ts +7 -6
  116. package/src/hooks/useConsumerScrollHandlers.ts +148 -0
  117. package/src/hooks/useHeaderMotionBridge.ts +15 -0
  118. package/src/hooks/useMotionProgress.test.ts +67 -0
  119. package/src/hooks/useMotionProgress.ts +12 -45
  120. package/src/hooks/useScrollManager.ts +251 -114
  121. package/src/index.ts +82 -36
  122. package/src/types.ts +81 -29
  123. package/src/utils/defaults.ts +7 -1
  124. package/src/utils/header.tsx +52 -0
  125. package/src/utils/headerOffsetStyle.ts +40 -0
  126. package/src/utils/index.ts +2 -0
  127. package/lib/module/components/HeaderBase.js +0 -107
  128. package/lib/module/components/HeaderBase.js.map +0 -1
  129. package/lib/typescript/src/components/HeaderBase.d.ts +0 -41
  130. package/lib/typescript/src/components/HeaderBase.d.ts.map +0 -1
  131. package/src/components/HeaderBase.tsx +0 -140
package/README.md CHANGED
@@ -1,48 +1,67 @@
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
+ High-level APIs for orchestrating scroll-driven header motion in 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
+ This library is a wrapper around:
6
6
 
7
- <div align="center">
8
- <img src="https://github.com/user-attachments/assets/b673349a-f26a-4cc8-bfe1-60d77deb4390" width="70%" />
9
- </div>
7
+ - [React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/) & [React Native Worklets](https://docs.swmansion.com/react-native-worklets/docs/)
8
+ - [React Native Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/docs/)
9
+
10
+ All credit for the underlying animation engine, worklets, gestures, and low-level primitives goes to those libraries. This package focuses on composing them into a specific higher-level use case: header motion and scroll orchestration.
11
+
12
+ This library does not ship a predesigned "collapsible header" UI. It gives you the pieces to:
10
13
 
11
- ## v1 alpha status
14
+ - measure the parts of a header that matter
15
+ - derive a shared `progress` value from scroll
16
+ - keep multiple scrollables in sync when one header is shared across them
17
+ - bridge that state into navigation-rendered headers
12
18
 
13
- `v1.0.0-alpha.x` is pre-release quality.
19
+ You build the visuals yourself on top of that.
14
20
 
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).
21
+ <div align="center">
22
+ <img src="https://github.com/user-attachments/assets/b673349a-f26a-4cc8-bfe1-60d77deb4390" width="70%" />
23
+ </div>
17
24
 
18
- ## What changed since `v0.3.0`
25
+ ## Version notes
19
26
 
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.
27
+ - If you are upgrading from `v0.3.x`, read [MIGRATION-v1.md](./MIGRATION-v1.md).
28
+ - If you are still on the pre-v1 API and need the old docs, use the `v0` README:
29
+ [README on branch `v0`](https://github.com/pawicao/react-native-header-motion/blob/v0/README.md)
23
30
 
24
- ## What this is (and isn’t)
31
+ ## What's new in v1
25
32
 
26
- **✅ This is**
33
+ The API change in v1 is quite substantial, but the migration is usually straightforward and the end result gives a much better developer experience.
27
34
 
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).
35
+ - Header panning built on top of `react-native-gesture-handler`. Dragging on the header itself can initiate or continue the scroll interaction naturally instead of forcing the user to only use the scrollables.
36
+ - Context-first header API built around `HeaderMotion.Header` and `HeaderMotion.Header.Dynamic`
37
+ - Explicit navigation bridging with `HeaderMotion.Bridge` and `HeaderMotion.NavigationBridge`
38
+ - Narrower `useMotionProgress()` that focuses on `progress` and `progressThreshold`
39
+ - Reusable custom-scrollable factory via `createHeaderMotionScrollable()`
40
+ - It's now easier than ever to wire up LegendList and FlashList to Header Motion!
41
+ - `react-native-gesture-handler` added to the peer dependency surface
30
42
 
31
- **❌ This is NOT**
43
+ ## What this library is good at
32
44
 
33
- - An out-of-the-box “collapsible header” component with a baked-in look.
45
+ - Scroll-driven animated headers
46
+ - Shared header state across tabs / pagers / multiple scrollables
47
+ - Navigation headers rendered outside the provider subtree
48
+ - Reusable wrappers around custom scrollables
34
49
 
35
- You build any header motion you want by animating based on `progress`.
50
+ ## What this library is not trying to be
36
51
 
37
- ## Requirements (peer dependencies)
52
+ - A fully styled header component
53
+ - A page layout framework
54
+ - A general-purpose animation abstraction on top of Reanimated
38
55
 
39
- You must have these installed in your app:
56
+ ## Requirements
40
57
 
41
- - `react-native-gesture-handler` **>= 2.0.0**
42
- - `react-native-reanimated` **>= 4.0.0**
43
- - `react-native-worklets` **>= 0.4.0**
58
+ Your app must provide:
44
59
 
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.
60
+ - `react-native-gesture-handler >= 2.0.0`
61
+ - `react-native-reanimated >= 4.0.0`
62
+ - `react-native-worklets >= 0.4.0`
63
+
64
+ These are peer dependencies.
46
65
 
47
66
  ## Installation
48
67
 
@@ -56,519 +75,504 @@ or
56
75
  yarn add react-native-header-motion
57
76
  ```
58
77
 
59
- ### Reanimated setup
78
+ Then follow the normal setup instructions for:
60
79
 
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).
80
+ - [Reanimated](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/#installation)
81
+ - [Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/docs/fundamentals/installation)
82
+ - [Worklets](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/#installation)
62
83
 
63
84
  ## Mental model
64
85
 
65
- There are three key concepts:
66
-
67
- ### 1) `progress` (SharedValue)
86
+ There are four concepts to understand:
68
87
 
69
- `progress` is a Reanimated `SharedValue<number>` that represents the normalized progress of your header animation.
88
+ ### 1. `progress`
70
89
 
71
- - `0` animation start (initial state)
72
- - `1` → animation end (final state)
90
+ `progress` is a `SharedValue<number>`.
73
91
 
74
- ### 2) `progressThreshold` (prop vs runtime value)
92
+ - `0` means "expanded"
93
+ - `1` means "collapsed"
75
94
 
76
- `progressThreshold` is the distance needed for `progress` to move from `0 → 1`.
95
+ Most header animations should be derived from this value.
77
96
 
78
- As a `HeaderMotion` prop, you can provide:
97
+ ### 2. `progressThreshold`
79
98
 
80
- - a number, or
81
- - a function `(measuredDynamic) => threshold`
99
+ `progressThreshold` is the collapse distance in pixels.
82
100
 
83
- If you provide a function, it uses the value measured by `measureDynamic`.
101
+ It can be:
84
102
 
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`).
103
+ - a fixed number
104
+ - a function derived from the measured dynamic part of the header
87
105
 
88
- ### 3) Measurement functions
106
+ At runtime, `useMotionProgress()` gives you `progressThreshold` as a `SharedValue<number>`.
89
107
 
90
- The library gives you two measurement callbacks that you pass to your header layout:
108
+ In practice, `progress` is calculated by mapping scroll distance across that threshold:
91
109
 
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).
110
+ - before the threshold, `progress` moves from `0` toward `1`
111
+ - at the threshold, `progress` reaches `1`
112
+ - past the threshold, behavior depends on `progressExtrapolation`
94
113
 
95
- ## Why `HeaderMotion.Header` exists
114
+ ### 3. Total header height vs dynamic header height
96
115
 
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.
116
+ The library measures two different things:
98
117
 
99
- Because of that, the navigation header **cannot read the `HeaderMotion` context**, so calling `useMotionProgress()` inside that header would throw.
118
+ - the total header height
119
+ - the dynamic part of the header that should define the collapse distance
100
120
 
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.
121
+ `HeaderMotion.Header` wires the total-height measurement.
102
122
 
103
- ## Why `HeaderBase` / `AnimatedHeaderBase` uses absolute positioning
123
+ `HeaderMotion.Header.Dynamic` wires the dynamic measurement.
104
124
 
105
- Navigation headers are special:
125
+ In many designs:
106
126
 
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.
127
+ - the sticky/top part stays visible
128
+ - the dynamic part slides away
129
+ - the dynamic part is what should feed `progressThreshold`
111
130
 
112
- `HeaderBase` and `AnimatedHeaderBase` are **absolutely positioned** to avoid those layout traps, which is especially important when you use transforms/translations.
131
+ ### 4. Navigation headers are a separate tree
113
132
 
114
- ## When to use components vs hooks
133
+ When a navigation library renders a header outside your screen subtree, it cannot read the `HeaderMotion` context directly.
115
134
 
116
- You can use either style; pick based on your integration needs:
135
+ That is why the library has:
117
136
 
118
- - Prefer **components** when you want a “batteries included” wiring:
137
+ - `HeaderMotion.Bridge`
138
+ - `HeaderMotion.NavigationBridge`
119
139
 
120
- - `HeaderMotion.ScrollView` / `HeaderMotion.FlatList` for common scrollables
121
- - `HeaderMotion.ScrollManager` for custom scrollables via render-props
140
+ Use them only to move HeaderMotion context across that boundary.
122
141
 
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
142
+ ## Recommended integration order
126
143
 
127
- Also:
144
+ The library allows (and requires) you to integrate your scrollables with headers to provide animation behavior.
128
145
 
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`.
146
+ Use the simplest integration that fits your case:
131
147
 
132
- ## Examples
133
-
134
- ### Example app
148
+ 1. `HeaderMotion.ScrollView` or `HeaderMotion.FlatList` - exported directly from the library
149
+ 2. `createHeaderMotionScrollable()` - to easily create custom integrated scrollables on top of other scrollables (e.g. LegendList or FlashList)
150
+ 3. `HeaderMotion.ScrollManager` / `useScrollManager()` - for even more custom scenarios
135
151
 
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`).
152
+ For custom scrollables, prefer `createHeaderMotionScrollable()` first.
137
153
 
138
- Those examples use Expo Router as the navigation library, but it should be fairly simple to do the same with plain React Navigation.
154
+ Use the scroll managers only when the factory approach is not flexible enough.
139
155
 
140
- ### Expo Router
156
+ ## Quick start: navigation header
141
157
 
142
- This is the core pattern used in the example app (`example/src/app/simple.tsx`).
158
+ This is the core v1 pattern when your header is rendered by Expo Router / React Navigation.
143
159
 
144
160
  ```tsx
145
- import HeaderMotion, {
146
- AnimatedHeaderBase,
147
- type WithCollapsibleHeaderProps,
148
- } from 'react-native-header-motion';
161
+ import HeaderMotion, { useMotionProgress } from 'react-native-header-motion';
149
162
  import { Stack } from 'expo-router';
163
+ import { StyleSheet, View } from 'react-native';
150
164
  import Animated, {
151
165
  Extrapolation,
152
166
  interpolate,
153
167
  useAnimatedStyle,
154
168
  } from 'react-native-reanimated';
155
169
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
156
- import { View } from 'react-native';
157
170
 
158
171
  export default function Screen() {
159
172
  return (
160
173
  <HeaderMotion>
161
- <HeaderMotion.Header>
162
- {(headerProps) => (
174
+ <HeaderMotion.Bridge>
175
+ {(ctx) => (
163
176
  <Stack.Screen
164
177
  options={{
165
- header: () => <MyHeader {...headerProps} />,
178
+ header: () => (
179
+ <HeaderMotion.NavigationBridge value={ctx}>
180
+ <AppHeader />
181
+ </HeaderMotion.NavigationBridge>
182
+ ),
166
183
  }}
167
184
  />
168
185
  )}
169
- </HeaderMotion.Header>
186
+ </HeaderMotion.Bridge>
170
187
 
171
- <HeaderMotion.ScrollView>
172
- {/* your scrollable content */}
173
- </HeaderMotion.ScrollView>
188
+ <HeaderMotion.ScrollView>{/* content */}</HeaderMotion.ScrollView>
174
189
  </HeaderMotion>
175
190
  );
176
191
  }
177
192
 
178
- function MyHeader({
179
- progress,
180
- measureTotalHeight,
181
- measureDynamic,
182
- progressThreshold,
183
- animatedHeaderBaseProps,
184
- }: WithCollapsibleHeaderProps) {
193
+ function AppHeader() {
194
+ const { progress, progressThreshold } = useMotionProgress();
185
195
  const insets = useSafeAreaInsets();
186
196
 
187
197
  const containerStyle = useAnimatedStyle(() => {
188
198
  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 }] };
199
+
200
+ return {
201
+ transform: [
202
+ {
203
+ translateY: interpolate(
204
+ progress.get(),
205
+ [0, 1],
206
+ [0, -threshold],
207
+ Extrapolation.CLAMP
208
+ ),
209
+ },
210
+ ],
211
+ };
196
212
  });
197
213
 
198
214
  return (
199
- <AnimatedHeaderBase
200
- animatedHeaderBaseProps={animatedHeaderBaseProps}
201
- onLayout={measureTotalHeight}
202
- style={[{ paddingTop: insets.top }, containerStyle]}
215
+ <HeaderMotion.Header
216
+ style={[styles.header, { paddingTop: insets.top }, containerStyle]}
203
217
  >
204
- <Animated.View onLayout={measureDynamic}>
205
- {/* “dynamic” part of the header */}
206
- </Animated.View>
218
+ <HeaderMotion.Header.Dynamic>
219
+ {/* collapsible part */}
220
+ </HeaderMotion.Header.Dynamic>
207
221
 
208
- <View>{/* "regular" part of the header */}</View>
209
- </AnimatedHeaderBase>
222
+ <View>{/* sticky part */}</View>
223
+ </HeaderMotion.Header>
210
224
  );
211
225
  }
212
- ```
213
226
 
214
- ### React Navigation
227
+ const styles = StyleSheet.create({
228
+ header: {
229
+ backgroundColor: '#304077',
230
+ },
231
+ });
232
+ ```
215
233
 
216
- In React Navigation you typically configure headers via `navigation.setOptions()`.
234
+ ## Quick start: inline header inside the screen
217
235
 
218
- Important: the header itself can’t call `useMotionProgress()`, so we still use `HeaderMotion.Header` as a bridge.
236
+ If your animated header lives in the same subtree as `HeaderMotion`, you do not need bridging at all.
219
237
 
220
238
  ```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() {
239
+ function Screen() {
236
240
  return (
237
241
  <HeaderMotion>
238
- <HeaderMotion.Header>
239
- {(headerProps) => (
240
- <NavigationHeaderInstaller headerProps={headerProps} />
241
- )}
242
- </HeaderMotion.Header>
242
+ <InlineHeader />
243
243
  <HeaderMotion.ScrollView>{/* content */}</HeaderMotion.ScrollView>
244
244
  </HeaderMotion>
245
245
  );
246
246
  }
247
247
 
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]);
248
+ function InlineHeader() {
249
+ const { progress, progressThreshold } = useMotionProgress();
260
250
 
261
- return null;
251
+ return (
252
+ <HeaderMotion.Header>
253
+ <HeaderMotion.Header.Dynamic>
254
+ {/* collapsible section */}
255
+ </HeaderMotion.Header.Dynamic>
256
+ </HeaderMotion.Header>
257
+ );
262
258
  }
259
+ ```
263
260
 
264
- function MyHeader({
265
- progress,
266
- measureTotalHeight,
267
- measureDynamic,
268
- progressThreshold,
269
- animatedHeaderBaseProps,
270
- }: WithCollapsibleHeaderProps) {
271
- const insets = useSafeAreaInsets();
261
+ ## Shared header across multiple scrollables
272
262
 
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
- });
263
+ If one header is shared across tabs or pager pages:
264
+
265
+ 1. Create an active scroll id with `useActiveScrollId()`
266
+ 2. Pass `activeScrollId.sv` to `HeaderMotion`
267
+ 3. Give each scrollable a unique `scrollId`
268
+
269
+ ```tsx
270
+ import { useRef } from 'react';
271
+ import PagerView from 'react-native-pager-view';
272
+
273
+ const indexToKey = new Map([
274
+ [0, 'A'],
275
+ [1, 'B'],
276
+ ]);
277
+
278
+ function Screen() {
279
+ const [activeScrollId, setActiveScrollId] = useActiveScrollId<'A' | 'B'>('A');
280
+ const pagerRef = useRef<PagerView>(null);
283
281
 
284
282
  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>
283
+ <HeaderMotion activeScrollId={activeScrollId.sv}>
284
+ <HeaderMotion.Bridge>
285
+ {(ctx) => (
286
+ <Stack.Screen
287
+ options={{
288
+ header: () => (
289
+ <HeaderMotion.NavigationBridge value={ctx}>
290
+ <Header />
291
+ </HeaderMotion.NavigationBridge>
292
+ ),
293
+ }}
294
+ />
295
+ )}
296
+ </HeaderMotion.Bridge>
297
+
298
+ <PagerView
299
+ ref={pagerRef}
300
+ style={{ flex: 1 }}
301
+ initialPage={0}
302
+ onPageSelected={(e) => {
303
+ setActiveScrollId(indexToKey.get(e.nativeEvent.position)!);
304
+ }}
305
+ >
306
+ <View key="A">
307
+ <HeaderMotion.ScrollView scrollId="A">
308
+ {/* page A content */}
309
+ </HeaderMotion.ScrollView>
310
+ </View>
311
+
312
+ <View key="B">
313
+ <HeaderMotion.ScrollView scrollId="B">
314
+ {/* page B content */}
315
+ </HeaderMotion.ScrollView>
316
+ </View>
317
+ </PagerView>
318
+ </HeaderMotion>
319
+ );
320
+ }
321
+ ```
322
+
323
+ ## Header panning
324
+
325
+ Sometimes the header itself takes up a large part of the screen, so forcing the user to move their finger back down to the scrollable can feel awkward.
326
+
327
+ In those cases, you can make the header surface itself drive the scroll interaction as well:
293
328
 
294
- <View>{/* "regular" part of the header */}</View>
295
- </AnimatedHeaderBase>
329
+ ```tsx
330
+ function Header() {
331
+ return (
332
+ <HeaderMotion.Header pannable>
333
+ <HeaderMotion.Header.Dynamic>
334
+ {/* collapsible content */}
335
+ </HeaderMotion.Header.Dynamic>
336
+ </HeaderMotion.Header>
296
337
  );
297
338
  }
298
339
  ```
299
340
 
300
- ### Tabs / pager: synchronizing multiple scrollables
341
+ ## Public API
301
342
 
302
- If you have multiple scrollables (e.g. pages in `react-native-pager-view`), you can keep a single header progress by:
343
+ ### Default export: `HeaderMotion`
303
344
 
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`
345
+ Compound component with:
307
346
 
308
- The example app shows this pattern in `example/src/app/collapsible-pager.tsx` using `HeaderMotion.ScrollManager`.
347
+ - `HeaderMotion.Header`
348
+ - `HeaderMotion.Bridge`
349
+ - `HeaderMotion.NavigationBridge`
350
+ - `HeaderMotion.ScrollView`
351
+ - `HeaderMotion.FlatList`
352
+ - `HeaderMotion.ScrollManager`
309
353
 
310
- ### Keeping the native header (back button/title) + custom animated header below
354
+ Provider props:
311
355
 
312
- Sometimes you want to keep the native navigation header for back buttons + title, but still animate a custom header section below it.
356
+ - `progressThreshold?: number | ((measuredDynamic: number) => number)`: collapse distance in pixels; when passed as a function, it is derived from the value measured by `HeaderMotion.Header.Dynamic`
357
+ - `measureDynamic?: (e) => number`: controls what value is read from the dynamic section's layout event; defaults to its height
358
+ - `measureDynamicMode?: 'mount' | 'update'`: `'mount'` measures once; `'update'` re-measures when the dynamic section lays out again
359
+ - `activeScrollId?: SharedValue<string>`: identifies which scrollable currently owns header progress in multi-scroll setups
360
+ - `progressExtrapolation?: ExtrapolationType`: controls how `progress` behaves outside the normal collapse range
313
361
 
314
- In that case:
362
+ ### `HeaderMotion.Header`
315
363
 
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
364
+ Main header container.
319
365
 
320
- Sketch:
366
+ Responsibilities:
321
367
 
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';
368
+ - measures total header height
369
+ - applies overlay positioning by default
370
+ - can make the header surface pannable
334
371
 
335
- export default function Screen() {
336
- 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
- }
372
+ Props:
348
373
 
349
- function InlineAnimatedHeader() {
350
- const {
351
- progress,
352
- measureTotalHeight,
353
- measureDynamic,
354
- progressThreshold,
355
- animatedHeaderBaseProps,
356
- } = useMotionProgress();
374
+ - all normal `Animated.View` props in default mode: styles, accessibility props, pointer events, and other normal animated view props work as expected
375
+ - `overlay?: boolean`: keeps the header absolutely positioned above content; disable only if you intentionally want it in normal layout flow
376
+ - `pannable?: boolean`: allows dragging directly on the header surface to continue the scroll interaction
377
+ - `panDecayConfig?: WithDecayConfig | ((event) => WithDecayConfig)`: customizes the momentum animation after a header pan ends
378
+ - `withGestureHandlerRootView?: boolean`: wraps the gesture subtree in `GestureHandlerRootView` when that part of the tree is not already under one
379
+ - `asChild?: boolean`: injects the total-height measurement into a single child instead of rendering the default `Animated.View`
357
380
 
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
- });
381
+ Use `asChild` when you want to inject the total-height measurement into a single child instead of rendering the default `Animated.View`.
368
382
 
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>
380
- );
381
- }
382
- ```
383
+ ### `HeaderMotion.Header.Dynamic`
383
384
 
384
- ## API
385
+ Marks the part of the header whose layout should define the collapsible distance.
385
386
 
386
- The package exports a default compound component plus hooks, types, and a couple base components.
387
+ Use this for the section that visually disappears during collapse.
387
388
 
388
- ### `HeaderMotion` (default export)
389
+ Props:
389
390
 
390
- `HeaderMotion` is a compound component:
391
+ - all normal `Animated.View` props in default mode: use these as you would on any animated view
392
+ - `asChild?: boolean`: injects the dynamic measurement into a single child instead of rendering the default `Animated.View`
391
393
 
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)
394
+ ### `HeaderMotion.Bridge`
397
395
 
398
- #### Props
396
+ Reads the current HeaderMotion context and exposes it through a render function.
399
397
 
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).
398
+ Use it to move the context into a navigation-rendered header subtree.
413
399
 
414
- #### `HeaderMotion.Header`
400
+ Props:
415
401
 
416
- Render-prop component that passes motion progress props to a header you render via navigation.
402
+ - `children: (value) => ReactNode`: receives the bridged HeaderMotion context value that should usually be passed into `HeaderMotion.NavigationBridge`
417
403
 
418
- ```tsx
419
- <HeaderMotion.Header>
420
- {(headerProps) => /* pass headerProps into navigation header */}
421
- </HeaderMotion.Header>
422
- ```
404
+ ### `HeaderMotion.NavigationBridge`
423
405
 
424
- Use this instead of `useMotionProgress()` when your header is rendered by React Navigation / Expo Router.
406
+ Re-provides a previously captured HeaderMotion context value in another subtree.
425
407
 
426
- #### `HeaderMotion.ScrollView`
408
+ Use it together with `HeaderMotion.Bridge`.
427
409
 
428
- Animated ScrollView wired with:
410
+ Props:
429
411
 
430
- - `onScroll` handler
431
- - `ref`
432
- - automatic `paddingTop` based on measured header height
412
+ - `value`: the bridged HeaderMotion context captured by `HeaderMotion.Bridge`
413
+ - `children`: the subtree that should regain access to HeaderMotion context
433
414
 
434
- Supports `scrollId?: string` for multi-scroll scenarios.
415
+ ### `HeaderMotion.ScrollView`
435
416
 
436
- #### `HeaderMotion.FlatList`
417
+ Pre-wired `Animated.ScrollView`.
437
418
 
438
- Animated FlatList wired similarly to the ScrollView.
419
+ Supports:
439
420
 
440
- Supports `scrollId?: string` for multi-scroll scenarios.
421
+ - `scrollId?: string`: unique id for this scrollable when one header is shared across multiple scrollables
422
+ - `headerOffsetStrategy?: 'padding' | 'margin' | 'top' | 'translate' | 'none'`: controls how content is pushed below the measured header
423
+ - `ensureScrollableContentMinHeight?: boolean`: experimental fallback for short content that otherwise could not scroll far enough to collapse the header fully
424
+ - `animatedRef?: AnimatedRef`: lets you reuse your own animated ref instead of letting HeaderMotion create one
441
425
 
442
- #### `HeaderMotion.ScrollManager`
426
+ ### `HeaderMotion.FlatList`
443
427
 
444
- Render-prop API for custom scrollables (pager pages, 3rd party lists, etc.).
428
+ Pre-wired `Animated.FlatList`.
445
429
 
446
- If you use `HeaderMotion.ScrollManager` directly for custom integrations, pass refresh-related props to `ScrollManager` (instead of your inner scrollable):
430
+ Supports the same HeaderMotion-specific props as `HeaderMotion.ScrollView`.
447
431
 
448
- - `refreshControl`
449
- - `refreshing`
450
- - `onRefresh`
451
- - optional `progressViewOffset` if you want to force your offset.
432
+ ### `createHeaderMotionScrollable(Component, options?)`
452
433
 
453
- This is required, as the positioning of scrollables is affecting Refresh Control and has to be coupled with the header heights.
434
+ Factory for creating reusable HeaderMotion-aware wrappers around custom scrollables.
435
+
436
+ Prefer this over the scroll managers whenever it is enough.
437
+
438
+ Useful options:
439
+
440
+ - `displayName`: custom component name shown in React DevTools
441
+ - `isComponentAnimated`: set this when the input component is already animated and should not be wrapped again
442
+ - `contentContainerMode: 'children' | 'renderScrollComponent'`: tells HeaderMotion how to inject content offsetting for that scrollable shape
443
+
444
+ Use:
445
+
446
+ - `'children'` for ScrollView-like components
447
+ - `'renderScrollComponent'` for FlatList-like components
448
+
449
+ Examples:
450
+
451
+ `FlashList`
454
452
 
455
453
  ```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>
454
+ import { FlashList } from '@shopify/flash-list';
455
+ import { createHeaderMotionScrollable } from 'react-native-header-motion';
456
+
457
+ const HeaderMotionFlashList = createHeaderMotionScrollable(FlashList, {
458
+ displayName: 'HeaderMotionFlashList',
459
+ contentContainerMode: 'renderScrollComponent',
460
+ });
473
461
  ```
474
462
 
475
- Refresh example with explicit props on `ScrollManager`:
463
+ `LegendList`
476
464
 
477
465
  ```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>
466
+ import { LegendList } from '@legendapp/list';
467
+ import { createHeaderMotionScrollable } from 'react-native-header-motion';
468
+
469
+ const HeaderMotionLegendList = createHeaderMotionScrollable(LegendList, {
470
+ displayName: 'HeaderMotionLegendList',
471
+ isComponentAnimated: true,
472
+ contentContainerMode: 'renderScrollComponent',
473
+ });
503
474
  ```
504
475
 
476
+ ### `HeaderMotion.ScrollManager`
477
+
478
+ Render-prop fallback for complex custom integrations.
479
+
480
+ Most code should prefer `createHeaderMotionScrollable()`.
481
+
482
+ Use `ScrollManager` only when you need a custom composition that the factory API cannot express cleanly.
483
+
484
+ Props:
485
+
486
+ - `scrollId?: string`: unique id for this scrollable when one header is shared across multiple scrollables
487
+ - `children`: render function that receives `scrollableProps` and `headerMotionContext`
488
+ - plus the same refresh / ref options accepted by `useScrollManager()`
489
+
505
490
  ### Hooks
506
491
 
507
492
  #### `useMotionProgress()`
508
493
 
509
494
  Returns:
510
495
 
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`)
496
+ - `progress`: `SharedValue<number>` that typically moves from `0` at expanded state to `1` at collapsed state
497
+ - `progressThreshold`: `SharedValue<number>` representing the collapse distance in pixels
498
+
499
+ This is the primary animation hook for header UI.
500
+
501
+ #### `useHeaderMotionBridge()`
517
502
 
518
- Only use inside the `HeaderMotion` provider tree.
503
+ Returns the full internal bridge value.
519
504
 
520
- #### `useScrollManager(scrollId?)`
505
+ Most app code should not need this. Prefer `useMotionProgress()` unless you are explicitly bridging context across a tree boundary.
521
506
 
522
- Lower-level orchestration hook that powers the component APIs. Returns:
507
+ Returns:
523
508
 
524
- - `scrollableProps`: `{ onScroll, scrollEventThrottle, ref }`
525
- - `headerMotionContext`:
526
- - `originalHeaderHeight` (`SharedValue<number>`)
527
- - `minHeightContentContainerStyle` (helps when content is shorter than the threshold)
509
+ - full HeaderMotion context value, including measurement callbacks and scroll synchronization internals
528
510
 
529
511
  #### `useActiveScrollId(initialId)`
530
512
 
531
- Helper for multi-scroll scenarios (tabs/pager). Returns:
513
+ Returns:
514
+
515
+ - `{ state, sv }`: `state` is the React value for UI logic, `sv` is the matching shared value for HeaderMotion
516
+ - setter function: updates both in sync
517
+
518
+ Use this for multi-scroll setups.
519
+
520
+ #### `useScrollManager(scrollId?, options?)`
521
+
522
+ Hook-level fallback for complex custom scrollables.
523
+
524
+ Most code should prefer `createHeaderMotionScrollable()`.
525
+
526
+ Parameters:
527
+
528
+ - `scrollId`: unique id for this scrollable when one header is shared across multiple scrollables
529
+ - `options`: optional ref, refresh, and event-handler configuration
530
+
531
+ Returns:
532
+
533
+ - `scrollableProps`: props to spread onto the scrollable itself, including the managed ref, scroll handlers, and resolved refresh control
534
+ - `headerMotionContext`: layout values for offsetting content below the measured header, including `originalHeaderHeight` and optional `contentContainerMinHeight`
535
+
536
+ ## Notes
537
+
538
+ ### Why `HeaderMotion.Header` is absolute by default
539
+
540
+ Headers rendered by navigation are often easier to animate and interact with when they are visually overlayed above content rather than participating in normal layout flow.
532
541
 
533
- - `[active, setActive]`
534
- - `active.state` (React state)
535
- - `active.sv` (SharedValue)
542
+ That is why `overlay` defaults to `true`.
536
543
 
537
- ### Base components
544
+ Disable it only when you intentionally want the header in normal layout flow.
538
545
 
539
- #### `HeaderBase`
546
+ ### `ensureScrollableContentMinHeight` (experimental)
540
547
 
541
- Non-animated absolutely positioned header base.
548
+ This is available on the pre-wired scrollables and the custom-scrollable APIs.
542
549
 
543
- #### `AnimatedHeaderBase`
550
+ It is useful when content is too short to naturally scroll through the full collapse distance.
544
551
 
545
- Reanimated-powered, absolutely positioned header base.
552
+ This feature is still experimental.
546
553
 
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.
554
+ ### Scroll event frequency
550
555
 
551
- ### Types
556
+ `scrollEventThrottle` is intentionally not managed by this library.
552
557
 
553
- - `WithCollapsibleHeaderProps` convenience type for headers using motion progress props.
554
- - `WithCollapsiblePagedHeaderProps` – like above, plus `activeTab` and `onTabChange`.
558
+ Pass it directly to your scrollable when you need it.
555
559
 
556
- ## Additional notes
560
+ If you run into performance issues, try adjusting `scrollEventThrottle` to reduce how many scroll events this library processes.
557
561
 
558
- ### Refresh Control (v.0.3.0+)
562
+ ### Refresh control
559
563
 
560
- Refresh control support was improved in `v0.3.0+`.
564
+ If you use `HeaderMotion.ScrollView` or `HeaderMotion.FlatList`, your refresh-control usage stays the same as in React Native.
561
565
 
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`
566
+ If you use `HeaderMotion.ScrollManager` directly for custom integrations, pass refresh-related props to `ScrollManager` itself:
567
+
568
+ - `refreshControl`
569
+ - `refreshing`
570
+ - `onRefresh`
571
+ - optional `progressViewOffset`
568
572
 
569
- This is important because scrollable positioning affects refresh-control behavior and needs to stay coupled with measured header height.
573
+ This matters because scrollable positioning affects refresh-control behavior and needs to stay coupled with the measured header height.
570
574
 
571
- #### Platform support note:
575
+ Platform support note:
572
576
 
573
577
  - Support for Refresh Control is currently partial.
574
578
  - Android works well with the current implementation.
@@ -577,15 +581,26 @@ This is important because scrollable positioning affects refresh-control behavio
577
581
  - Other iOS approaches tried so far introduced different issues.
578
582
  - Additional iOS support improvements are planned for future releases.
579
583
 
584
+ ## Examples
585
+
586
+ See the example app in [`example/`](./example/).
587
+
588
+ Useful files:
589
+
590
+ - [`example/src/app/simple.tsx`](./example/src/app/simple.tsx)
591
+ - [`example/src/app/flashlist.tsx`](./example/src/app/flashlist.tsx)
592
+ - [`example/src/app/legend-list.tsx`](./example/src/app/legend-list.tsx)
593
+ - [`example/src/app/pager-header-pan.tsx`](./example/src/app/pager-header-pan.tsx)
594
+ - [`example/src/app/collapsible-pager.tsx`](./example/src/app/collapsible-pager.tsx)
595
+
580
596
  ## Contributing
581
597
 
582
- - Development workflow: see [CONTRIBUTING.md](CONTRIBUTING.md)
583
- - Code of conduct: see [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
598
+ Development workflow: see [CONTRIBUTING.md](./CONTRIBUTING.md)
599
+
600
+ Code of conduct: see [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md)
584
601
 
585
602
  ## License
586
603
 
587
604
  MIT
588
605
 
589
- ---
590
-
591
- Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
606
+ Made with [`create-react-native-library`](https://github.com/callstack/react-native-builder-bob)