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.
- package/README.md +400 -335
- package/lib/module/components/Bridge.js +16 -0
- package/lib/module/components/Bridge.js.map +1 -0
- package/lib/module/components/FlatList.js +5 -62
- package/lib/module/components/FlatList.js.map +1 -1
- package/lib/module/components/Header.js +71 -13
- package/lib/module/components/Header.js.map +1 -1
- package/lib/module/components/HeaderDynamic.js +34 -0
- package/lib/module/components/HeaderDynamic.js.map +1 -0
- package/lib/module/components/HeaderMotion.js +59 -23
- package/lib/module/components/HeaderMotion.js.map +1 -1
- package/lib/module/components/HeaderPanBoundary.js +54 -0
- package/lib/module/components/HeaderPanBoundary.js.map +1 -0
- package/lib/module/components/NavigationBridge.js +20 -0
- package/lib/module/components/NavigationBridge.js.map +1 -0
- package/lib/module/components/ScrollManager.js +7 -5
- package/lib/module/components/ScrollManager.js.map +1 -1
- package/lib/module/components/ScrollView.js +6 -47
- package/lib/module/components/ScrollView.js.map +1 -1
- package/lib/module/components/createHeaderMotionScrollable.js +136 -0
- package/lib/module/components/createHeaderMotionScrollable.js.map +1 -0
- package/lib/module/components/index.js +3 -1
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/context.js +8 -1
- package/lib/module/context.js.map +1 -1
- package/lib/module/hooks/index.js +1 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/useActiveScrollId.js +7 -6
- package/lib/module/hooks/useActiveScrollId.js.map +1 -1
- package/lib/module/hooks/useHeaderMotionBridge.js +14 -0
- package/lib/module/hooks/useHeaderMotionBridge.js.map +1 -0
- package/lib/module/hooks/useMotionProgress.js +10 -36
- package/lib/module/hooks/useMotionProgress.js.map +1 -1
- package/lib/module/hooks/useMotionProgress.test.js +56 -0
- package/lib/module/hooks/useMotionProgress.test.js.map +1 -0
- package/lib/module/hooks/useScrollManager.js +219 -109
- package/lib/module/hooks/useScrollManager.js.map +1 -1
- package/lib/module/index.js +21 -18
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/defaults.js +2 -1
- package/lib/module/utils/defaults.js.map +1 -1
- package/lib/module/utils/header.js +24 -0
- package/lib/module/utils/header.js.map +1 -0
- package/lib/module/utils/headerOffsetStyle.js +31 -0
- package/lib/module/utils/headerOffsetStyle.js.map +1 -0
- package/lib/module/utils/index.js +3 -0
- package/lib/module/utils/index.js.map +1 -1
- package/lib/module/utils/refreshControl.js +93 -0
- package/lib/module/utils/refreshControl.js.map +1 -0
- package/lib/module/utils/values.js +36 -0
- package/lib/module/utils/values.js.map +1 -1
- package/lib/typescript/src/components/Bridge.d.ts +19 -0
- package/lib/typescript/src/components/Bridge.d.ts.map +1 -0
- package/lib/typescript/src/components/FlatList.d.ts +7 -15
- package/lib/typescript/src/components/FlatList.d.ts.map +1 -1
- package/lib/typescript/src/components/Header.d.ts +73 -12
- package/lib/typescript/src/components/Header.d.ts.map +1 -1
- package/lib/typescript/src/components/HeaderDynamic.d.ts +11 -0
- package/lib/typescript/src/components/HeaderDynamic.d.ts.map +1 -0
- package/lib/typescript/src/components/HeaderMotion.d.ts +37 -18
- package/lib/typescript/src/components/HeaderMotion.d.ts.map +1 -1
- package/lib/typescript/src/components/HeaderPanBoundary.d.ts +11 -0
- package/lib/typescript/src/components/HeaderPanBoundary.d.ts.map +1 -0
- package/lib/typescript/src/components/NavigationBridge.d.ts +19 -0
- package/lib/typescript/src/components/NavigationBridge.d.ts.map +1 -0
- package/lib/typescript/src/components/ScrollManager.d.ts +18 -25
- package/lib/typescript/src/components/ScrollManager.d.ts.map +1 -1
- package/lib/typescript/src/components/ScrollView.d.ts +7 -14
- package/lib/typescript/src/components/ScrollView.d.ts.map +1 -1
- package/lib/typescript/src/components/createHeaderMotionScrollable.d.ts +86 -0
- package/lib/typescript/src/components/createHeaderMotionScrollable.d.ts.map +1 -0
- package/lib/typescript/src/components/index.d.ts +3 -1
- package/lib/typescript/src/components/index.d.ts.map +1 -1
- package/lib/typescript/src/context.d.ts +3 -13
- package/lib/typescript/src/context.d.ts.map +1 -1
- package/lib/typescript/src/hooks/index.d.ts +1 -0
- package/lib/typescript/src/hooks/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useActiveScrollId.d.ts +7 -6
- package/lib/typescript/src/hooks/useActiveScrollId.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useHeaderMotionBridge.d.ts +10 -0
- package/lib/typescript/src/hooks/useHeaderMotionBridge.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useMotionProgress.d.ts +8 -25
- package/lib/typescript/src/hooks/useMotionProgress.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useMotionProgress.test.d.ts +2 -0
- package/lib/typescript/src/hooks/useMotionProgress.test.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useScrollManager.d.ts +63 -31
- package/lib/typescript/src/hooks/useScrollManager.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +56 -26
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +63 -15
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/utils/defaults.d.ts +3 -2
- package/lib/typescript/src/utils/defaults.d.ts.map +1 -1
- package/lib/typescript/src/utils/header.d.ts +10 -0
- package/lib/typescript/src/utils/header.d.ts.map +1 -0
- package/lib/typescript/src/utils/headerOffsetStyle.d.ts +19 -0
- package/lib/typescript/src/utils/headerOffsetStyle.d.ts.map +1 -0
- package/lib/typescript/src/utils/index.d.ts +3 -0
- package/lib/typescript/src/utils/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/refreshControl.d.ts +150 -0
- package/lib/typescript/src/utils/refreshControl.d.ts.map +1 -0
- package/lib/typescript/src/utils/values.d.ts +4 -1
- package/lib/typescript/src/utils/values.d.ts.map +1 -1
- package/package.json +13 -5
- package/src/components/Bridge.tsx +29 -0
- package/src/components/FlatList.tsx +18 -84
- package/src/components/Header.tsx +159 -23
- package/src/components/HeaderDynamic.tsx +45 -0
- package/src/components/HeaderMotion.tsx +114 -41
- package/src/components/HeaderPanBoundary.tsx +92 -0
- package/src/components/NavigationBridge.tsx +30 -0
- package/src/components/ScrollManager.tsx +38 -43
- package/src/components/ScrollView.tsx +16 -68
- package/src/components/createHeaderMotionScrollable.tsx +438 -0
- package/src/components/index.ts +3 -1
- package/src/context.ts +12 -18
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useActiveScrollId.ts +7 -6
- package/src/hooks/useHeaderMotionBridge.ts +15 -0
- package/src/hooks/useMotionProgress.test.ts +67 -0
- package/src/hooks/useMotionProgress.ts +12 -37
- package/src/hooks/useScrollManager.ts +310 -129
- package/src/index.ts +82 -36
- package/src/types.ts +85 -25
- package/src/utils/defaults.ts +7 -1
- package/src/utils/header.tsx +52 -0
- package/src/utils/headerOffsetStyle.ts +40 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/refreshControl.tsx +118 -0
- package/src/utils/values.ts +57 -1
- package/lib/module/components/HeaderBase.js +0 -59
- package/lib/module/components/HeaderBase.js.map +0 -1
- package/lib/module/hooks/refreshControl.js +0 -31
- package/lib/module/hooks/refreshControl.js.map +0 -1
- package/lib/typescript/src/components/HeaderBase.d.ts +0 -34
- package/lib/typescript/src/components/HeaderBase.d.ts.map +0 -1
- package/lib/typescript/src/hooks/refreshControl.d.ts +0 -13
- package/lib/typescript/src/hooks/refreshControl.d.ts.map +0 -1
- package/src/components/HeaderBase.tsx +0 -51
- 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
|
|
3
|
+
High-level APIs for orchestrating scroll-driven header motion in React Native.
|
|
4
4
|
|
|
5
|
-
This library is
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
50
|
+
## What this library is not trying to be
|
|
21
51
|
|
|
22
|
-
|
|
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
|
|
56
|
+
## Requirements
|
|
25
57
|
|
|
26
|
-
|
|
58
|
+
Your app must provide:
|
|
27
59
|
|
|
28
|
-
- `react-native-
|
|
29
|
-
- `react-native-
|
|
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
|
-
|
|
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
|
-
|
|
78
|
+
Then follow the normal setup instructions for:
|
|
46
79
|
|
|
47
|
-
|
|
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
|
|
86
|
+
There are four concepts to understand:
|
|
52
87
|
|
|
53
|
-
### 1
|
|
88
|
+
### 1. `progress`
|
|
54
89
|
|
|
55
|
-
`progress` is a
|
|
90
|
+
`progress` is a `SharedValue<number>`.
|
|
56
91
|
|
|
57
|
-
- `0`
|
|
58
|
-
- `1`
|
|
92
|
+
- `0` means "expanded"
|
|
93
|
+
- `1` means "collapsed"
|
|
59
94
|
|
|
60
|
-
|
|
95
|
+
Most header animations should be derived from this value.
|
|
61
96
|
|
|
62
|
-
|
|
97
|
+
### 2. `progressThreshold`
|
|
63
98
|
|
|
64
|
-
|
|
99
|
+
`progressThreshold` is the collapse distance in pixels.
|
|
65
100
|
|
|
66
|
-
|
|
67
|
-
- a function `(measuredDynamic) => threshold`
|
|
101
|
+
It can be:
|
|
68
102
|
|
|
69
|
-
|
|
103
|
+
- a fixed number
|
|
104
|
+
- a function derived from the measured dynamic part of the header
|
|
70
105
|
|
|
71
|
-
|
|
106
|
+
At runtime, `useMotionProgress()` gives you `progressThreshold` as a `SharedValue<number>`.
|
|
72
107
|
|
|
73
|
-
|
|
108
|
+
In practice, `progress` is calculated by mapping scroll distance across that threshold:
|
|
74
109
|
|
|
75
|
-
-
|
|
76
|
-
-
|
|
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
|
-
|
|
114
|
+
### 3. Total header height vs dynamic header height
|
|
79
115
|
|
|
80
|
-
|
|
116
|
+
The library measures two different things:
|
|
81
117
|
|
|
82
|
-
|
|
118
|
+
- the total header height
|
|
119
|
+
- the dynamic part of the header that should define the collapse distance
|
|
83
120
|
|
|
84
|
-
`HeaderMotion.Header`
|
|
121
|
+
`HeaderMotion.Header` wires the total-height measurement.
|
|
85
122
|
|
|
86
|
-
|
|
123
|
+
`HeaderMotion.Header.Dynamic` wires the dynamic measurement.
|
|
87
124
|
|
|
88
|
-
|
|
125
|
+
In many designs:
|
|
89
126
|
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
|
|
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
|
-
|
|
131
|
+
### 4. Navigation headers are a separate tree
|
|
96
132
|
|
|
97
|
-
|
|
133
|
+
When a navigation library renders a header outside your screen subtree, it cannot read the `HeaderMotion` context directly.
|
|
98
134
|
|
|
99
|
-
|
|
135
|
+
That is why the library has:
|
|
100
136
|
|
|
101
|
-
-
|
|
137
|
+
- `HeaderMotion.Bridge`
|
|
138
|
+
- `HeaderMotion.NavigationBridge`
|
|
102
139
|
|
|
103
|
-
|
|
104
|
-
- `HeaderMotion.ScrollManager` for custom scrollables via render-props
|
|
140
|
+
Use them only to move HeaderMotion context across that boundary.
|
|
105
141
|
|
|
106
|
-
|
|
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
|
-
|
|
144
|
+
The library allows (and requires) you to integrate your scrollables with headers to provide animation behavior.
|
|
111
145
|
|
|
112
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
152
|
+
For custom scrollables, prefer `createHeaderMotionScrollable()` first.
|
|
120
153
|
|
|
121
|
-
|
|
154
|
+
Use the scroll managers only when the factory approach is not flexible enough.
|
|
122
155
|
|
|
123
|
-
|
|
156
|
+
## Quick start: navigation header
|
|
124
157
|
|
|
125
|
-
This is the core pattern
|
|
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.
|
|
145
|
-
{(
|
|
174
|
+
<HeaderMotion.Bridge>
|
|
175
|
+
{(ctx) => (
|
|
146
176
|
<Stack.Screen
|
|
147
177
|
options={{
|
|
148
|
-
header: () =>
|
|
178
|
+
header: () => (
|
|
179
|
+
<HeaderMotion.NavigationBridge value={ctx}>
|
|
180
|
+
<AppHeader />
|
|
181
|
+
</HeaderMotion.NavigationBridge>
|
|
182
|
+
),
|
|
149
183
|
}}
|
|
150
184
|
/>
|
|
151
185
|
)}
|
|
152
|
-
</HeaderMotion.
|
|
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
|
|
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
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
[
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
<
|
|
181
|
-
|
|
182
|
-
style={[{ paddingTop: insets.top }, containerStyle]}
|
|
215
|
+
<HeaderMotion.Header
|
|
216
|
+
style={[styles.header, { paddingTop: insets.top }, containerStyle]}
|
|
183
217
|
>
|
|
184
|
-
<
|
|
185
|
-
{/*
|
|
186
|
-
</
|
|
218
|
+
<HeaderMotion.Header.Dynamic>
|
|
219
|
+
{/* collapsible part */}
|
|
220
|
+
</HeaderMotion.Header.Dynamic>
|
|
187
221
|
|
|
188
|
-
<View>{/*
|
|
189
|
-
</
|
|
222
|
+
<View>{/* sticky part */}</View>
|
|
223
|
+
</HeaderMotion.Header>
|
|
190
224
|
);
|
|
191
225
|
}
|
|
192
|
-
```
|
|
193
226
|
|
|
194
|
-
|
|
227
|
+
const styles = StyleSheet.create({
|
|
228
|
+
header: {
|
|
229
|
+
backgroundColor: '#304077',
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
```
|
|
195
233
|
|
|
196
|
-
|
|
234
|
+
## Quick start: inline header inside the screen
|
|
197
235
|
|
|
198
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
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
|
|
228
|
-
|
|
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
|
|
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
|
-
|
|
244
|
-
progress,
|
|
245
|
-
measureTotalHeight,
|
|
246
|
-
measureDynamic,
|
|
247
|
-
progressThreshold,
|
|
248
|
-
}: WithCollapsibleHeaderProps) {
|
|
249
|
-
const insets = useSafeAreaInsets();
|
|
261
|
+
## Shared header across multiple scrollables
|
|
250
262
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
<
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
271
|
-
|
|
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
|
-
|
|
341
|
+
## Public API
|
|
277
342
|
|
|
278
|
-
|
|
343
|
+
### Default export: `HeaderMotion`
|
|
279
344
|
|
|
280
|
-
|
|
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
|
-
|
|
347
|
+
- `HeaderMotion.Header`
|
|
348
|
+
- `HeaderMotion.Bridge`
|
|
349
|
+
- `HeaderMotion.NavigationBridge`
|
|
350
|
+
- `HeaderMotion.ScrollView`
|
|
351
|
+
- `HeaderMotion.FlatList`
|
|
352
|
+
- `HeaderMotion.ScrollManager`
|
|
285
353
|
|
|
286
|
-
|
|
354
|
+
Provider props:
|
|
287
355
|
|
|
288
|
-
|
|
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
|
-
|
|
362
|
+
### `HeaderMotion.Header`
|
|
291
363
|
|
|
292
|
-
|
|
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
|
-
|
|
366
|
+
Responsibilities:
|
|
297
367
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
385
|
+
Marks the part of the header whose layout should define the collapsible distance.
|
|
351
386
|
|
|
352
|
-
|
|
387
|
+
Use this for the section that visually disappears during collapse.
|
|
353
388
|
|
|
354
|
-
|
|
389
|
+
Props:
|
|
355
390
|
|
|
356
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
396
|
+
Reads the current HeaderMotion context and exposes it through a render function.
|
|
365
397
|
|
|
366
|
-
|
|
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
|
-
|
|
400
|
+
Props:
|
|
379
401
|
|
|
380
|
-
|
|
402
|
+
- `children: (value) => ReactNode`: receives the bridged HeaderMotion context value that should usually be passed into `HeaderMotion.NavigationBridge`
|
|
381
403
|
|
|
382
|
-
|
|
383
|
-
<HeaderMotion.Header>
|
|
384
|
-
{(headerProps) => /* pass headerProps into navigation header */}
|
|
385
|
-
</HeaderMotion.Header>
|
|
386
|
-
```
|
|
404
|
+
### `HeaderMotion.NavigationBridge`
|
|
387
405
|
|
|
388
|
-
|
|
406
|
+
Re-provides a previously captured HeaderMotion context value in another subtree.
|
|
389
407
|
|
|
390
|
-
|
|
408
|
+
Use it together with `HeaderMotion.Bridge`.
|
|
391
409
|
|
|
392
|
-
|
|
410
|
+
Props:
|
|
393
411
|
|
|
394
|
-
- `
|
|
395
|
-
- `
|
|
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
|
-
|
|
415
|
+
### `HeaderMotion.ScrollView`
|
|
399
416
|
|
|
400
|
-
|
|
417
|
+
Pre-wired `Animated.ScrollView`.
|
|
401
418
|
|
|
402
|
-
|
|
419
|
+
Supports:
|
|
403
420
|
|
|
404
|
-
|
|
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
|
-
|
|
426
|
+
### `HeaderMotion.FlatList`
|
|
407
427
|
|
|
408
|
-
|
|
428
|
+
Pre-wired `Animated.FlatList`.
|
|
409
429
|
|
|
410
|
-
|
|
430
|
+
Supports the same HeaderMotion-specific props as `HeaderMotion.ScrollView`.
|
|
411
431
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
|
|
463
|
+
`LegendList`
|
|
437
464
|
|
|
438
465
|
```tsx
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
|
468
|
-
- `progressThreshold
|
|
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
|
-
|
|
499
|
+
This is the primary animation hook for header UI.
|
|
473
500
|
|
|
474
|
-
#### `
|
|
501
|
+
#### `useHeaderMotionBridge()`
|
|
475
502
|
|
|
476
|
-
|
|
503
|
+
Returns the full internal bridge value.
|
|
477
504
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
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
|
-
|
|
488
|
-
- `active.state` (React state)
|
|
489
|
-
- `active.sv` (SharedValue)
|
|
538
|
+
### Why `HeaderMotion.Header` is absolute by default
|
|
490
539
|
|
|
491
|
-
|
|
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
|
-
|
|
542
|
+
That is why `overlay` defaults to `true`.
|
|
494
543
|
|
|
495
|
-
|
|
544
|
+
Disable it only when you intentionally want the header in normal layout flow.
|
|
496
545
|
|
|
497
|
-
|
|
546
|
+
### `ensureScrollableContentMinHeight` (experimental)
|
|
498
547
|
|
|
499
|
-
|
|
548
|
+
This is available on the pre-wired scrollables and the custom-scrollable APIs.
|
|
500
549
|
|
|
501
|
-
|
|
550
|
+
It is useful when content is too short to naturally scroll through the full collapse distance.
|
|
502
551
|
|
|
503
|
-
|
|
504
|
-
- `WithCollapsiblePagedHeaderProps` – like above, plus `activeTab` and `onTabChange`.
|
|
552
|
+
This feature is still experimental.
|
|
505
553
|
|
|
506
|
-
|
|
554
|
+
### Scroll event frequency
|
|
507
555
|
|
|
508
|
-
|
|
556
|
+
`scrollEventThrottle` is intentionally not managed by this library.
|
|
509
557
|
|
|
510
|
-
|
|
558
|
+
Pass it directly to your scrollable when you need it.
|
|
511
559
|
|
|
512
|
-
|
|
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
|
-
|
|
562
|
+
### Refresh control
|
|
520
563
|
|
|
521
|
-
|
|
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
|
-
|
|
533
|
-
|
|
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)
|