react-native-header-motion 0.4.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 (140) hide show
  1. package/README.md +400 -335
  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 -62
  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 +59 -23
  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 +7 -5
  17. package/lib/module/components/ScrollManager.js.map +1 -1
  18. package/lib/module/components/ScrollView.js +6 -47
  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/useHeaderMotionBridge.js +14 -0
  31. package/lib/module/hooks/useHeaderMotionBridge.js.map +1 -0
  32. package/lib/module/hooks/useMotionProgress.js +10 -36
  33. package/lib/module/hooks/useMotionProgress.js.map +1 -1
  34. package/lib/module/hooks/useMotionProgress.test.js +56 -0
  35. package/lib/module/hooks/useMotionProgress.test.js.map +1 -0
  36. package/lib/module/hooks/useScrollManager.js +219 -109
  37. package/lib/module/hooks/useScrollManager.js.map +1 -1
  38. package/lib/module/index.js +21 -18
  39. package/lib/module/index.js.map +1 -1
  40. package/lib/module/utils/defaults.js +2 -1
  41. package/lib/module/utils/defaults.js.map +1 -1
  42. package/lib/module/utils/header.js +24 -0
  43. package/lib/module/utils/header.js.map +1 -0
  44. package/lib/module/utils/headerOffsetStyle.js +31 -0
  45. package/lib/module/utils/headerOffsetStyle.js.map +1 -0
  46. package/lib/module/utils/index.js +3 -0
  47. package/lib/module/utils/index.js.map +1 -1
  48. package/lib/module/utils/refreshControl.js +93 -0
  49. package/lib/module/utils/refreshControl.js.map +1 -0
  50. package/lib/module/utils/values.js +36 -0
  51. package/lib/module/utils/values.js.map +1 -1
  52. package/lib/typescript/src/components/Bridge.d.ts +19 -0
  53. package/lib/typescript/src/components/Bridge.d.ts.map +1 -0
  54. package/lib/typescript/src/components/FlatList.d.ts +7 -15
  55. package/lib/typescript/src/components/FlatList.d.ts.map +1 -1
  56. package/lib/typescript/src/components/Header.d.ts +73 -12
  57. package/lib/typescript/src/components/Header.d.ts.map +1 -1
  58. package/lib/typescript/src/components/HeaderDynamic.d.ts +11 -0
  59. package/lib/typescript/src/components/HeaderDynamic.d.ts.map +1 -0
  60. package/lib/typescript/src/components/HeaderMotion.d.ts +37 -18
  61. package/lib/typescript/src/components/HeaderMotion.d.ts.map +1 -1
  62. package/lib/typescript/src/components/HeaderPanBoundary.d.ts +11 -0
  63. package/lib/typescript/src/components/HeaderPanBoundary.d.ts.map +1 -0
  64. package/lib/typescript/src/components/NavigationBridge.d.ts +19 -0
  65. package/lib/typescript/src/components/NavigationBridge.d.ts.map +1 -0
  66. package/lib/typescript/src/components/ScrollManager.d.ts +18 -25
  67. package/lib/typescript/src/components/ScrollManager.d.ts.map +1 -1
  68. package/lib/typescript/src/components/ScrollView.d.ts +7 -14
  69. package/lib/typescript/src/components/ScrollView.d.ts.map +1 -1
  70. package/lib/typescript/src/components/createHeaderMotionScrollable.d.ts +86 -0
  71. package/lib/typescript/src/components/createHeaderMotionScrollable.d.ts.map +1 -0
  72. package/lib/typescript/src/components/index.d.ts +3 -1
  73. package/lib/typescript/src/components/index.d.ts.map +1 -1
  74. package/lib/typescript/src/context.d.ts +3 -13
  75. package/lib/typescript/src/context.d.ts.map +1 -1
  76. package/lib/typescript/src/hooks/index.d.ts +1 -0
  77. package/lib/typescript/src/hooks/index.d.ts.map +1 -1
  78. package/lib/typescript/src/hooks/useActiveScrollId.d.ts +7 -6
  79. package/lib/typescript/src/hooks/useActiveScrollId.d.ts.map +1 -1
  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 +63 -31
  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 +63 -15
  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 +3 -0
  99. package/lib/typescript/src/utils/index.d.ts.map +1 -1
  100. package/lib/typescript/src/utils/refreshControl.d.ts +150 -0
  101. package/lib/typescript/src/utils/refreshControl.d.ts.map +1 -0
  102. package/lib/typescript/src/utils/values.d.ts +4 -1
  103. package/lib/typescript/src/utils/values.d.ts.map +1 -1
  104. package/package.json +13 -5
  105. package/src/components/Bridge.tsx +29 -0
  106. package/src/components/FlatList.tsx +18 -84
  107. package/src/components/Header.tsx +159 -23
  108. package/src/components/HeaderDynamic.tsx +45 -0
  109. package/src/components/HeaderMotion.tsx +114 -41
  110. package/src/components/HeaderPanBoundary.tsx +92 -0
  111. package/src/components/NavigationBridge.tsx +30 -0
  112. package/src/components/ScrollManager.tsx +38 -43
  113. package/src/components/ScrollView.tsx +16 -68
  114. package/src/components/createHeaderMotionScrollable.tsx +438 -0
  115. package/src/components/index.ts +3 -1
  116. package/src/context.ts +12 -18
  117. package/src/hooks/index.ts +1 -0
  118. package/src/hooks/useActiveScrollId.ts +7 -6
  119. package/src/hooks/useHeaderMotionBridge.ts +15 -0
  120. package/src/hooks/useMotionProgress.test.ts +67 -0
  121. package/src/hooks/useMotionProgress.ts +12 -37
  122. package/src/hooks/useScrollManager.ts +310 -129
  123. package/src/index.ts +82 -36
  124. package/src/types.ts +85 -25
  125. package/src/utils/defaults.ts +7 -1
  126. package/src/utils/header.tsx +52 -0
  127. package/src/utils/headerOffsetStyle.ts +40 -0
  128. package/src/utils/index.ts +3 -0
  129. package/src/utils/refreshControl.tsx +118 -0
  130. package/src/utils/values.ts +57 -1
  131. package/lib/module/components/HeaderBase.js +0 -59
  132. package/lib/module/components/HeaderBase.js.map +0 -1
  133. package/lib/module/hooks/refreshControl.js +0 -31
  134. package/lib/module/hooks/refreshControl.js.map +0 -1
  135. package/lib/typescript/src/components/HeaderBase.d.ts +0 -34
  136. package/lib/typescript/src/components/HeaderBase.d.ts.map +0 -1
  137. package/lib/typescript/src/hooks/refreshControl.d.ts +0 -13
  138. package/lib/typescript/src/hooks/refreshControl.d.ts.map +0 -1
  139. package/src/components/HeaderBase.tsx +0 -51
  140. package/src/hooks/refreshControl.ts +0 -55
package/README.md CHANGED
@@ -1,34 +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
+
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:
13
+
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
18
+
19
+ You build the visuals yourself on top of that.
6
20
 
7
21
  <div align="center">
8
- <img src="https://github.com/user-attachments/assets/b673349a-f26a-4cc8-bfe1-60d77deb4390" width="70%" />
22
+ <img src="https://github.com/user-attachments/assets/b673349a-f26a-4cc8-bfe1-60d77deb4390" width="70%" />
9
23
  </div>
10
24
 
11
- ## What this is (and isn’t)
25
+ ## Version notes
26
+
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)
30
+
31
+ ## What's new in v1
32
+
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.
12
34
 
13
- **✅ This is**
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
14
42
 
15
- - A small set of components + hooks that expose a single `progress` shared value and a few measurement helpers.
16
- - A scroll orchestration layer that can keep multiple scrollables in sync (e.g. tabs + pager).
43
+ ## What this library is good at
17
44
 
18
- **❌ This is NOT**
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
19
49
 
20
- - An out-of-the-box “collapsible header” component with a baked-in look.
50
+ ## What this library is not trying to be
21
51
 
22
- You build any header motion you want by animating based on `progress`.
52
+ - A fully styled header component
53
+ - A page layout framework
54
+ - A general-purpose animation abstraction on top of Reanimated
23
55
 
24
- ## Requirements (peer dependencies)
56
+ ## Requirements
25
57
 
26
- You must have these installed in your app:
58
+ Your app must provide:
27
59
 
28
- - `react-native-reanimated` **>= 4.0.0**
29
- - `react-native-worklets` **>= 0.4.0**
60
+ - `react-native-gesture-handler >= 2.0.0`
61
+ - `react-native-reanimated >= 4.0.0`
62
+ - `react-native-worklets >= 0.4.0`
30
63
 
31
- 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.
64
+ These are peer dependencies.
32
65
 
33
66
  ## Installation
34
67
 
@@ -42,483 +75,504 @@ or
42
75
  yarn add react-native-header-motion
43
76
  ```
44
77
 
45
- ### Reanimated setup
78
+ Then follow the normal setup instructions for:
46
79
 
47
- 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)
48
83
 
49
84
  ## Mental model
50
85
 
51
- There are three key concepts:
86
+ There are four concepts to understand:
52
87
 
53
- ### 1) `progress` (SharedValue)
88
+ ### 1. `progress`
54
89
 
55
- `progress` is a Reanimated `SharedValue<number>` that represents the normalized progress of your header animation.
90
+ `progress` is a `SharedValue<number>`.
56
91
 
57
- - `0` animation start (initial state)
58
- - `1` animation end (final state)
92
+ - `0` means "expanded"
93
+ - `1` means "collapsed"
59
94
 
60
- ### 2) `progressThreshold`
95
+ Most header animations should be derived from this value.
61
96
 
62
- `progressThreshold` is the distance needed for `progress` to move from `0 → 1`.
97
+ ### 2. `progressThreshold`
63
98
 
64
- You can provide it as:
99
+ `progressThreshold` is the collapse distance in pixels.
65
100
 
66
- - a number, or
67
- - a function `(measuredDynamic) => threshold`
101
+ It can be:
68
102
 
69
- If you provide a function, it uses the value measured by `measureDynamic`.
103
+ - a fixed number
104
+ - a function derived from the measured dynamic part of the header
70
105
 
71
- ### 3) Measurement functions
106
+ At runtime, `useMotionProgress()` gives you `progressThreshold` as a `SharedValue<number>`.
72
107
 
73
- 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:
74
109
 
75
- - `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.
76
- - `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`
77
113
 
78
- ## Why `HeaderMotion.Header` exists
114
+ ### 3. Total header height vs dynamic header height
79
115
 
80
- 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:
81
117
 
82
- 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
83
120
 
84
- `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.
85
122
 
86
- ## Why `HeaderBase` / `AnimatedHeaderBase` uses absolute positioning
123
+ `HeaderMotion.Header.Dynamic` wires the dynamic measurement.
87
124
 
88
- Navigation headers are special:
125
+ In many designs:
89
126
 
90
- - Even with `headerTransparent: true`, the navigator can still reserve layout space for the header container.
91
- - If you animate with translations without absolute positioning, you can end up with:
92
- - content below becoming unclickable (an invisible parent header still sits on top), or
93
- - 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`
94
130
 
95
- `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
96
132
 
97
- ## 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.
98
134
 
99
- You can use either style; pick based on your integration needs:
135
+ That is why the library has:
100
136
 
101
- - Prefer **components** when you want a “batteries included” wiring:
137
+ - `HeaderMotion.Bridge`
138
+ - `HeaderMotion.NavigationBridge`
102
139
 
103
- - `HeaderMotion.ScrollView` / `HeaderMotion.FlatList` for common scrollables
104
- - `HeaderMotion.ScrollManager` for custom scrollables via render-props
140
+ Use them only to move HeaderMotion context across that boundary.
105
141
 
106
- - Prefer **hooks** when you want to build your own wrappers:
107
- - `useScrollManager()` (same engine as `HeaderMotion.ScrollManager`, but hook-based)
108
- - `useMotionProgress()` when your header is inside the provider tree
142
+ ## Recommended integration order
109
143
 
110
- Also:
144
+ The library allows (and requires) you to integrate your scrollables with headers to provide animation behavior.
111
145
 
112
- - Use `HeaderMotion.Header` when your header is rendered by navigation.
113
- - Use `useMotionProgress` when your header is rendered inside the same tree as `HeaderMotion`.
146
+ Use the simplest integration that fits your case:
114
147
 
115
- ## Examples
116
-
117
- ### 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
118
151
 
119
- 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.
120
153
 
121
- 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.
122
155
 
123
- ### Expo Router
156
+ ## Quick start: navigation header
124
157
 
125
- 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.
126
159
 
127
160
  ```tsx
128
- import HeaderMotion, {
129
- AnimatedHeaderBase,
130
- type WithCollapsibleHeaderProps,
131
- } from 'react-native-header-motion';
161
+ import HeaderMotion, { useMotionProgress } from 'react-native-header-motion';
132
162
  import { Stack } from 'expo-router';
163
+ import { StyleSheet, View } from 'react-native';
133
164
  import Animated, {
134
165
  Extrapolation,
135
166
  interpolate,
136
167
  useAnimatedStyle,
137
168
  } from 'react-native-reanimated';
138
169
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
139
- import { View } from 'react-native';
140
170
 
141
171
  export default function Screen() {
142
172
  return (
143
173
  <HeaderMotion>
144
- <HeaderMotion.Header>
145
- {(headerProps) => (
174
+ <HeaderMotion.Bridge>
175
+ {(ctx) => (
146
176
  <Stack.Screen
147
177
  options={{
148
- header: () => <MyHeader {...headerProps} />,
178
+ header: () => (
179
+ <HeaderMotion.NavigationBridge value={ctx}>
180
+ <AppHeader />
181
+ </HeaderMotion.NavigationBridge>
182
+ ),
149
183
  }}
150
184
  />
151
185
  )}
152
- </HeaderMotion.Header>
186
+ </HeaderMotion.Bridge>
153
187
 
154
- <HeaderMotion.ScrollView>
155
- {/* your scrollable content */}
156
- </HeaderMotion.ScrollView>
188
+ <HeaderMotion.ScrollView>{/* content */}</HeaderMotion.ScrollView>
157
189
  </HeaderMotion>
158
190
  );
159
191
  }
160
192
 
161
- function MyHeader({
162
- progress,
163
- measureTotalHeight,
164
- measureDynamic,
165
- progressThreshold,
166
- }: WithCollapsibleHeaderProps) {
193
+ function AppHeader() {
194
+ const { progress, progressThreshold } = useMotionProgress();
167
195
  const insets = useSafeAreaInsets();
168
196
 
169
197
  const containerStyle = useAnimatedStyle(() => {
170
- const translateY = interpolate(
171
- progress.value,
172
- [0, 1],
173
- [0, -progressThreshold],
174
- Extrapolation.CLAMP
175
- );
176
- return { transform: [{ translateY }] };
198
+ const threshold = progressThreshold.get();
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
+ };
177
212
  });
178
213
 
179
214
  return (
180
- <AnimatedHeaderBase
181
- onLayout={measureTotalHeight}
182
- style={[{ paddingTop: insets.top }, containerStyle]}
215
+ <HeaderMotion.Header
216
+ style={[styles.header, { paddingTop: insets.top }, containerStyle]}
183
217
  >
184
- <Animated.View onLayout={measureDynamic}>
185
- {/* “dynamic” part of the header */}
186
- </Animated.View>
218
+ <HeaderMotion.Header.Dynamic>
219
+ {/* collapsible part */}
220
+ </HeaderMotion.Header.Dynamic>
187
221
 
188
- <View>{/* "regular" part of the header */}</View>
189
- </AnimatedHeaderBase>
222
+ <View>{/* sticky part */}</View>
223
+ </HeaderMotion.Header>
190
224
  );
191
225
  }
192
- ```
193
226
 
194
- ### React Navigation
227
+ const styles = StyleSheet.create({
228
+ header: {
229
+ backgroundColor: '#304077',
230
+ },
231
+ });
232
+ ```
195
233
 
196
- In React Navigation you typically configure headers via `navigation.setOptions()`.
234
+ ## Quick start: inline header inside the screen
197
235
 
198
- 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.
199
237
 
200
238
  ```tsx
201
- import React from 'react';
202
- import HeaderMotion, {
203
- AnimatedHeaderBase,
204
- type WithCollapsibleHeaderProps,
205
- } from 'react-native-header-motion';
206
- import { useNavigation } from '@react-navigation/native';
207
- import Animated, {
208
- Extrapolation,
209
- interpolate,
210
- useAnimatedStyle,
211
- } from 'react-native-reanimated';
212
- import { View } from 'react-native';
213
-
214
- export function MyScreen() {
239
+ function Screen() {
215
240
  return (
216
241
  <HeaderMotion>
217
- <HeaderMotion.Header>
218
- {(headerProps) => (
219
- <NavigationHeaderInstaller headerProps={headerProps} />
220
- )}
221
- </HeaderMotion.Header>
242
+ <InlineHeader />
222
243
  <HeaderMotion.ScrollView>{/* content */}</HeaderMotion.ScrollView>
223
244
  </HeaderMotion>
224
245
  );
225
246
  }
226
247
 
227
- function NavigationHeaderInstaller({
228
- headerProps,
229
- }: {
230
- headerProps: WithCollapsibleHeaderProps;
231
- }) {
232
- const navigation = useNavigation();
233
-
234
- React.useLayoutEffect(() => {
235
- navigation.setOptions({
236
- header: () => <MyHeader {...headerProps} />,
237
- });
238
- }, [navigation, headerProps]);
248
+ function InlineHeader() {
249
+ const { progress, progressThreshold } = useMotionProgress();
239
250
 
240
- return null;
251
+ return (
252
+ <HeaderMotion.Header>
253
+ <HeaderMotion.Header.Dynamic>
254
+ {/* collapsible section */}
255
+ </HeaderMotion.Header.Dynamic>
256
+ </HeaderMotion.Header>
257
+ );
241
258
  }
259
+ ```
242
260
 
243
- function MyHeader({
244
- progress,
245
- measureTotalHeight,
246
- measureDynamic,
247
- progressThreshold,
248
- }: WithCollapsibleHeaderProps) {
249
- const insets = useSafeAreaInsets();
261
+ ## Shared header across multiple scrollables
250
262
 
251
- const containerStyle = useAnimatedStyle(() => {
252
- const translateY = interpolate(
253
- progress.value,
254
- [0, 1],
255
- [0, -progressThreshold],
256
- Extrapolation.CLAMP
257
- );
258
- return { transform: [{ translateY }] };
259
- });
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);
260
281
 
261
282
  return (
262
- <AnimatedHeaderBase
263
- onLayout={measureTotalHeight}
264
- style={[{ paddingTop: insets.top }, containerStyle]}
265
- >
266
- <Animated.View onLayout={measureDynamic}>
267
- {/* “dynamic” part of the header */}
268
- </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
+ ```
269
322
 
270
- <View>{/* "regular" part of the header */}</View>
271
- </AnimatedHeaderBase>
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:
328
+
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>
272
337
  );
273
338
  }
274
339
  ```
275
340
 
276
- ### Tabs / pager: synchronizing multiple scrollables
341
+ ## Public API
277
342
 
278
- 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`
279
344
 
280
- 1. Creating a shared “active scroll id” using `useActiveScrollId()`
281
- 2. Passing `activeScrollId.sv` to `<HeaderMotion activeScrollId={...} />`
282
- 3. Rendering each page scrollable with a unique `scrollId`
345
+ Compound component with:
283
346
 
284
- 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`
285
353
 
286
- ### Keeping the native header (back button/title) + custom animated header below
354
+ Provider props:
287
355
 
288
- 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
289
361
 
290
- In that case:
362
+ ### `HeaderMotion.Header`
291
363
 
292
- - set `headerTransparent: true`
293
- - do **not** provide a custom `header` component
294
- - render your animated header content _inside the screen_ under the native header
364
+ Main header container.
295
365
 
296
- Sketch:
366
+ Responsibilities:
297
367
 
298
- ```tsx
299
- import HeaderMotion, {
300
- AnimatedHeaderBase,
301
- useMotionProgress,
302
- } from 'react-native-header-motion';
303
- import { Stack } from 'expo-router';
304
- import Animated, {
305
- Extrapolation,
306
- interpolate,
307
- useAnimatedStyle,
308
- } from 'react-native-reanimated';
309
- import { View } from 'react-native';
368
+ - measures total header height
369
+ - applies overlay positioning by default
370
+ - can make the header surface pannable
310
371
 
311
- export default function Screen() {
312
- return (
313
- <>
314
- <Stack.Screen options={{ headerTransparent: true }} />
315
- <HeaderMotion>
316
- <InlineAnimatedHeader />
317
- <HeaderMotion.ScrollView>
318
- {/* rest of content */}
319
- </HeaderMotion.ScrollView>
320
- </HeaderMotion>
321
- </>
322
- );
323
- }
372
+ Props:
324
373
 
325
- function InlineAnimatedHeader() {
326
- const { progress, measureTotalHeight, measureDynamic, progressThreshold } =
327
- 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`
328
380
 
329
- const containerStyle = useAnimatedStyle(() => {
330
- const translateY = interpolate(
331
- progress.value,
332
- [0, 1],
333
- [0, -progressThreshold],
334
- Extrapolation.CLAMP
335
- );
336
- return { transform: [{ translateY }] };
337
- });
381
+ Use `asChild` when you want to inject the total-height measurement into a single child instead of rendering the default `Animated.View`.
338
382
 
339
- return (
340
- <AnimatedHeaderBase onLayout={measureTotalHeight} style={containerStyle}>
341
- <Animated.View onLayout={measureDynamic}>
342
- {/* custom animated header content below the native header */}
343
- </Animated.View>
344
- <View>{/* sticky part */}</View>
345
- </AnimatedHeaderBase>
346
- );
347
- }
348
- ```
383
+ ### `HeaderMotion.Header.Dynamic`
349
384
 
350
- ## API
385
+ Marks the part of the header whose layout should define the collapsible distance.
351
386
 
352
- 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.
353
388
 
354
- ### `HeaderMotion` (default export)
389
+ Props:
355
390
 
356
- `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`
357
393
 
358
- - `HeaderMotion` (provider)
359
- - `HeaderMotion.Header` (bridge for navigation headers)
360
- - `HeaderMotion.ScrollView` (pre-wired Animated.ScrollView)
361
- - `HeaderMotion.FlatList` (pre-wired Animated.FlatList)
362
- - `HeaderMotion.ScrollManager` (render-prop API for custom scrollables)
394
+ ### `HeaderMotion.Bridge`
363
395
 
364
- #### Props
396
+ Reads the current HeaderMotion context and exposes it through a render function.
365
397
 
366
- - `progressThreshold?: number | (measuredDynamic: number) => number`
367
- - Defines how many pixels correspond to `progress` going from `0` to `1`.
368
- - If you pass a function, it uses the value measured from `measureDynamic`.
369
- - `measureDynamic?: (e) => number`
370
- - What value to read from the `onLayout` event (defaults to `height`).
371
- - `measureDynamicMode?: 'mount' | 'update'`
372
- - Whether `measureDynamic` updates only once or on every layout recalculation.
373
- - `activeScrollId?: SharedValue<string>`
374
- - Enables multi-scroll orchestration (tabs/pager).
375
- - `progressExtrapolation?: ExtrapolationType`
376
- - Controls how progress behaves outside the threshold range (useful for overscroll).
398
+ Use it to move the context into a navigation-rendered header subtree.
377
399
 
378
- #### `HeaderMotion.Header`
400
+ Props:
379
401
 
380
- 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`
381
403
 
382
- ```tsx
383
- <HeaderMotion.Header>
384
- {(headerProps) => /* pass headerProps into navigation header */}
385
- </HeaderMotion.Header>
386
- ```
404
+ ### `HeaderMotion.NavigationBridge`
387
405
 
388
- 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.
389
407
 
390
- #### `HeaderMotion.ScrollView`
408
+ Use it together with `HeaderMotion.Bridge`.
391
409
 
392
- Animated ScrollView wired with:
410
+ Props:
393
411
 
394
- - `onScroll` handler
395
- - `ref`
396
- - 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
397
414
 
398
- Supports `scrollId?: string` for multi-scroll scenarios.
415
+ ### `HeaderMotion.ScrollView`
399
416
 
400
- #### `HeaderMotion.FlatList`
417
+ Pre-wired `Animated.ScrollView`.
401
418
 
402
- Animated FlatList wired similarly to the ScrollView.
419
+ Supports:
403
420
 
404
- 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
405
425
 
406
- #### `HeaderMotion.ScrollManager`
426
+ ### `HeaderMotion.FlatList`
407
427
 
408
- Render-prop API for custom scrollables (pager pages, 3rd party lists, etc.).
428
+ Pre-wired `Animated.FlatList`.
409
429
 
410
- 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`.
411
431
 
412
- - `refreshControl`
413
- - `refreshing`
414
- - `onRefresh`
415
- - optional `progressViewOffset` if you want to force your offset.
432
+ ### `createHeaderMotionScrollable(Component, options?)`
433
+
434
+ Factory for creating reusable HeaderMotion-aware wrappers around custom scrollables.
416
435
 
417
- This is required, as the positioning of scrollables is affecting Refresh Control and has to be coupled with the header heights.
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`
418
452
 
419
453
  ```tsx
420
- <HeaderMotion.ScrollManager scrollId="A">
421
- {(
422
- scrollableProps,
423
- { originalHeaderHeight, minHeightContentContainerStyle }
424
- ) => (
425
- <Animated.ScrollView
426
- {...scrollableProps}
427
- contentContainerStyle={[
428
- minHeightContentContainerStyle,
429
- { paddingTop: originalHeaderHeight },
430
- ]}
431
- />
432
- )}
433
- </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
+ });
434
461
  ```
435
462
 
436
- Refresh example with explicit props on `ScrollManager`:
463
+ `LegendList`
437
464
 
438
465
  ```tsx
439
- <HeaderMotion.ScrollManager
440
- scrollId="A"
441
- refreshing={refreshing}
442
- onRefresh={onRefresh}
443
- >
444
- {(
445
- { onScroll, refreshControl: managedRefreshControl, ...scrollableProps },
446
- { originalHeaderHeight, minHeightContentContainerStyle }
447
- ) => (
448
- <Animated.ScrollView
449
- {...scrollableProps}
450
- onScroll={onScroll}
451
- refreshControl={managedRefreshControl}
452
- contentContainerStyle={[
453
- minHeightContentContainerStyle,
454
- { paddingTop: originalHeaderHeight },
455
- ]}
456
- />
457
- )}
458
- </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
+ });
459
474
  ```
460
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
+
461
490
  ### Hooks
462
491
 
463
492
  #### `useMotionProgress()`
464
493
 
465
494
  Returns:
466
495
 
467
- - `progress` (`SharedValue<number>`)
468
- - `progressThreshold` (`number`)
469
- - `measureTotalHeight` (`onLayout` callback)
470
- - `measureDynamic` (`onLayout` callback)
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
471
498
 
472
- Only use inside the `HeaderMotion` provider tree.
499
+ This is the primary animation hook for header UI.
473
500
 
474
- #### `useScrollManager(scrollId?)`
501
+ #### `useHeaderMotionBridge()`
475
502
 
476
- Lower-level orchestration hook that powers the component APIs. Returns:
503
+ Returns the full internal bridge value.
477
504
 
478
- - `scrollableProps`: `{ onScroll, scrollEventThrottle, ref }`
479
- - `headerMotionContext`:
480
- - `originalHeaderHeight`
481
- - `minHeightContentContainerStyle` (helps when content is shorter than the threshold)
505
+ Most app code should not need this. Prefer `useMotionProgress()` unless you are explicitly bridging context across a tree boundary.
506
+
507
+ Returns:
508
+
509
+ - full HeaderMotion context value, including measurement callbacks and scroll synchronization internals
482
510
 
483
511
  #### `useActiveScrollId(initialId)`
484
512
 
485
- 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
486
537
 
487
- - `[active, setActive]`
488
- - `active.state` (React state)
489
- - `active.sv` (SharedValue)
538
+ ### Why `HeaderMotion.Header` is absolute by default
490
539
 
491
- ### Base components
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.
492
541
 
493
- #### `HeaderBase`
542
+ That is why `overlay` defaults to `true`.
494
543
 
495
- Non-animated absolutely positioned header base.
544
+ Disable it only when you intentionally want the header in normal layout flow.
496
545
 
497
- #### `AnimatedHeaderBase`
546
+ ### `ensureScrollableContentMinHeight` (experimental)
498
547
 
499
- Reanimated-powered, absolutely positioned header base.
548
+ This is available on the pre-wired scrollables and the custom-scrollable APIs.
500
549
 
501
- ### Types
550
+ It is useful when content is too short to naturally scroll through the full collapse distance.
502
551
 
503
- - `WithCollapsibleHeaderProps` convenience type for headers using motion progress props.
504
- - `WithCollapsiblePagedHeaderProps` – like above, plus `activeTab` and `onTabChange`.
552
+ This feature is still experimental.
505
553
 
506
- ## Additional notes
554
+ ### Scroll event frequency
507
555
 
508
- ### Refresh Control (v.0.3.0+)
556
+ `scrollEventThrottle` is intentionally not managed by this library.
509
557
 
510
- Refresh control support was improved in `v0.3.0+`.
558
+ Pass it directly to your scrollable when you need it.
511
559
 
512
- - If you use `HeaderMotion.ScrollView` or `HeaderMotion.FlatList`, your refresh-control usage stays the same as in React Native.
513
- - If you use `HeaderMotion.ScrollManager` directly for custom integrations, pass refresh-related props to `ScrollManager`:
514
- - `refreshControl`
515
- - `refreshing`
516
- - `onRefresh`
517
- - optional `progressViewOffset`
560
+ If you run into performance issues, try adjusting `scrollEventThrottle` to reduce how many scroll events this library processes.
518
561
 
519
- This is important because scrollable positioning affects refresh-control behavior and needs to stay coupled with measured header height.
562
+ ### Refresh control
520
563
 
521
- #### Platform support note:
564
+ If you use `HeaderMotion.ScrollView` or `HeaderMotion.FlatList`, your refresh-control usage stays the same as in React Native.
565
+
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`
572
+
573
+ This matters because scrollable positioning affects refresh-control behavior and needs to stay coupled with the measured header height.
574
+
575
+ Platform support note:
522
576
 
523
577
  - Support for Refresh Control is currently partial.
524
578
  - Android works well with the current implementation.
@@ -527,15 +581,26 @@ This is important because scrollable positioning affects refresh-control behavio
527
581
  - Other iOS approaches tried so far introduced different issues.
528
582
  - Additional iOS support improvements are planned for future releases.
529
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
+
530
596
  ## Contributing
531
597
 
532
- - Development workflow: see [CONTRIBUTING.md](CONTRIBUTING.md)
533
- - 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)
534
601
 
535
602
  ## License
536
603
 
537
604
  MIT
538
605
 
539
- ---
540
-
541
- 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)