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.
- package/README.md +395 -380
- package/lib/module/components/Bridge.js +16 -0
- package/lib/module/components/Bridge.js.map +1 -0
- package/lib/module/components/FlatList.js +5 -54
- 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 +14 -20
- 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 +19 -7
- package/lib/module/components/ScrollManager.js.map +1 -1
- package/lib/module/components/ScrollView.js +6 -39
- 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/useConsumerScrollHandlers.js +86 -0
- package/lib/module/hooks/useConsumerScrollHandlers.js.map +1 -0
- package/lib/module/hooks/useHeaderMotionBridge.js +14 -0
- package/lib/module/hooks/useHeaderMotionBridge.js.map +1 -0
- package/lib/module/hooks/useMotionProgress.js +12 -42
- 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 +168 -87
- 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 +2 -0
- package/lib/module/utils/index.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 +38 -23
- 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 +13 -9
- 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 -17
- 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/useConsumerScrollHandlers.d.ts +64 -0
- package/lib/typescript/src/hooks/useConsumerScrollHandlers.d.ts.map +1 -0
- 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 +61 -29
- 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 +54 -17
- 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 +2 -0
- package/lib/typescript/src/utils/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/refreshControl.d.ts +9 -9
- package/package.json +7 -1
- package/src/components/Bridge.tsx +29 -0
- package/src/components/FlatList.tsx +18 -76
- package/src/components/Header.tsx +159 -23
- package/src/components/HeaderDynamic.tsx +45 -0
- package/src/components/HeaderMotion.tsx +47 -50
- package/src/components/HeaderPanBoundary.tsx +92 -0
- package/src/components/NavigationBridge.tsx +30 -0
- package/src/components/ScrollManager.tsx +23 -11
- package/src/components/ScrollView.tsx +16 -60
- package/src/components/createHeaderMotionScrollable.tsx +438 -0
- package/src/components/index.ts +3 -1
- package/src/context.ts +11 -24
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useActiveScrollId.ts +7 -6
- package/src/hooks/useConsumerScrollHandlers.ts +148 -0
- package/src/hooks/useHeaderMotionBridge.ts +15 -0
- package/src/hooks/useMotionProgress.test.ts +67 -0
- package/src/hooks/useMotionProgress.ts +12 -45
- package/src/hooks/useScrollManager.ts +251 -114
- package/src/index.ts +82 -36
- package/src/types.ts +81 -29
- 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 +2 -0
- package/lib/module/components/HeaderBase.js +0 -107
- package/lib/module/components/HeaderBase.js.map +0 -1
- package/lib/typescript/src/components/HeaderBase.d.ts +0 -41
- package/lib/typescript/src/components/HeaderBase.d.ts.map +0 -1
- 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
|
|
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
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19
|
+
You build the visuals yourself on top of that.
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
##
|
|
25
|
+
## Version notes
|
|
19
26
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
|
|
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
|
|
31
|
+
## What's new in v1
|
|
25
32
|
|
|
26
|
-
|
|
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
|
-
-
|
|
29
|
-
-
|
|
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
|
-
|
|
43
|
+
## What this library is good at
|
|
32
44
|
|
|
33
|
-
-
|
|
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
|
-
|
|
50
|
+
## What this library is not trying to be
|
|
36
51
|
|
|
37
|
-
|
|
52
|
+
- A fully styled header component
|
|
53
|
+
- A page layout framework
|
|
54
|
+
- A general-purpose animation abstraction on top of Reanimated
|
|
38
55
|
|
|
39
|
-
|
|
56
|
+
## Requirements
|
|
40
57
|
|
|
41
|
-
|
|
42
|
-
- `react-native-reanimated` **>= 4.0.0**
|
|
43
|
-
- `react-native-worklets` **>= 0.4.0**
|
|
58
|
+
Your app must provide:
|
|
44
59
|
|
|
45
|
-
|
|
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
|
-
|
|
78
|
+
Then follow the normal setup instructions for:
|
|
60
79
|
|
|
61
|
-
|
|
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
|
|
66
|
-
|
|
67
|
-
### 1) `progress` (SharedValue)
|
|
86
|
+
There are four concepts to understand:
|
|
68
87
|
|
|
69
|
-
|
|
88
|
+
### 1. `progress`
|
|
70
89
|
|
|
71
|
-
|
|
72
|
-
- `1` → animation end (final state)
|
|
90
|
+
`progress` is a `SharedValue<number>`.
|
|
73
91
|
|
|
74
|
-
|
|
92
|
+
- `0` means "expanded"
|
|
93
|
+
- `1` means "collapsed"
|
|
75
94
|
|
|
76
|
-
|
|
95
|
+
Most header animations should be derived from this value.
|
|
77
96
|
|
|
78
|
-
|
|
97
|
+
### 2. `progressThreshold`
|
|
79
98
|
|
|
80
|
-
|
|
81
|
-
- a function `(measuredDynamic) => threshold`
|
|
99
|
+
`progressThreshold` is the collapse distance in pixels.
|
|
82
100
|
|
|
83
|
-
|
|
101
|
+
It can be:
|
|
84
102
|
|
|
85
|
-
|
|
86
|
-
|
|
103
|
+
- a fixed number
|
|
104
|
+
- a function derived from the measured dynamic part of the header
|
|
87
105
|
|
|
88
|
-
|
|
106
|
+
At runtime, `useMotionProgress()` gives you `progressThreshold` as a `SharedValue<number>`.
|
|
89
107
|
|
|
90
|
-
|
|
108
|
+
In practice, `progress` is calculated by mapping scroll distance across that threshold:
|
|
91
109
|
|
|
92
|
-
-
|
|
93
|
-
-
|
|
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
|
-
|
|
114
|
+
### 3. Total header height vs dynamic header height
|
|
96
115
|
|
|
97
|
-
|
|
116
|
+
The library measures two different things:
|
|
98
117
|
|
|
99
|
-
|
|
118
|
+
- the total header height
|
|
119
|
+
- the dynamic part of the header that should define the collapse distance
|
|
100
120
|
|
|
101
|
-
`HeaderMotion.Header`
|
|
121
|
+
`HeaderMotion.Header` wires the total-height measurement.
|
|
102
122
|
|
|
103
|
-
|
|
123
|
+
`HeaderMotion.Header.Dynamic` wires the dynamic measurement.
|
|
104
124
|
|
|
105
|
-
|
|
125
|
+
In many designs:
|
|
106
126
|
|
|
107
|
-
-
|
|
108
|
-
-
|
|
109
|
-
|
|
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
|
-
|
|
131
|
+
### 4. Navigation headers are a separate tree
|
|
113
132
|
|
|
114
|
-
|
|
133
|
+
When a navigation library renders a header outside your screen subtree, it cannot read the `HeaderMotion` context directly.
|
|
115
134
|
|
|
116
|
-
|
|
135
|
+
That is why the library has:
|
|
117
136
|
|
|
118
|
-
-
|
|
137
|
+
- `HeaderMotion.Bridge`
|
|
138
|
+
- `HeaderMotion.NavigationBridge`
|
|
119
139
|
|
|
120
|
-
|
|
121
|
-
- `HeaderMotion.ScrollManager` for custom scrollables via render-props
|
|
140
|
+
Use them only to move HeaderMotion context across that boundary.
|
|
122
141
|
|
|
123
|
-
|
|
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
|
-
|
|
144
|
+
The library allows (and requires) you to integrate your scrollables with headers to provide animation behavior.
|
|
128
145
|
|
|
129
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
152
|
+
For custom scrollables, prefer `createHeaderMotionScrollable()` first.
|
|
137
153
|
|
|
138
|
-
|
|
154
|
+
Use the scroll managers only when the factory approach is not flexible enough.
|
|
139
155
|
|
|
140
|
-
|
|
156
|
+
## Quick start: navigation header
|
|
141
157
|
|
|
142
|
-
This is the core pattern
|
|
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.
|
|
162
|
-
{(
|
|
174
|
+
<HeaderMotion.Bridge>
|
|
175
|
+
{(ctx) => (
|
|
163
176
|
<Stack.Screen
|
|
164
177
|
options={{
|
|
165
|
-
header: () =>
|
|
178
|
+
header: () => (
|
|
179
|
+
<HeaderMotion.NavigationBridge value={ctx}>
|
|
180
|
+
<AppHeader />
|
|
181
|
+
</HeaderMotion.NavigationBridge>
|
|
182
|
+
),
|
|
166
183
|
}}
|
|
167
184
|
/>
|
|
168
185
|
)}
|
|
169
|
-
</HeaderMotion.
|
|
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
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
[
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
<
|
|
200
|
-
|
|
201
|
-
onLayout={measureTotalHeight}
|
|
202
|
-
style={[{ paddingTop: insets.top }, containerStyle]}
|
|
215
|
+
<HeaderMotion.Header
|
|
216
|
+
style={[styles.header, { paddingTop: insets.top }, containerStyle]}
|
|
203
217
|
>
|
|
204
|
-
<
|
|
205
|
-
{/*
|
|
206
|
-
</
|
|
218
|
+
<HeaderMotion.Header.Dynamic>
|
|
219
|
+
{/* collapsible part */}
|
|
220
|
+
</HeaderMotion.Header.Dynamic>
|
|
207
221
|
|
|
208
|
-
<View>{/*
|
|
209
|
-
</
|
|
222
|
+
<View>{/* sticky part */}</View>
|
|
223
|
+
</HeaderMotion.Header>
|
|
210
224
|
);
|
|
211
225
|
}
|
|
212
|
-
```
|
|
213
226
|
|
|
214
|
-
|
|
227
|
+
const styles = StyleSheet.create({
|
|
228
|
+
header: {
|
|
229
|
+
backgroundColor: '#304077',
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
```
|
|
215
233
|
|
|
216
|
-
|
|
234
|
+
## Quick start: inline header inside the screen
|
|
217
235
|
|
|
218
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
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
|
|
249
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
<
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
295
|
-
|
|
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
|
-
|
|
341
|
+
## Public API
|
|
301
342
|
|
|
302
|
-
|
|
343
|
+
### Default export: `HeaderMotion`
|
|
303
344
|
|
|
304
|
-
|
|
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
|
-
|
|
347
|
+
- `HeaderMotion.Header`
|
|
348
|
+
- `HeaderMotion.Bridge`
|
|
349
|
+
- `HeaderMotion.NavigationBridge`
|
|
350
|
+
- `HeaderMotion.ScrollView`
|
|
351
|
+
- `HeaderMotion.FlatList`
|
|
352
|
+
- `HeaderMotion.ScrollManager`
|
|
309
353
|
|
|
310
|
-
|
|
354
|
+
Provider props:
|
|
311
355
|
|
|
312
|
-
|
|
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
|
-
|
|
362
|
+
### `HeaderMotion.Header`
|
|
315
363
|
|
|
316
|
-
|
|
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
|
-
|
|
366
|
+
Responsibilities:
|
|
321
367
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
385
|
+
Marks the part of the header whose layout should define the collapsible distance.
|
|
385
386
|
|
|
386
|
-
|
|
387
|
+
Use this for the section that visually disappears during collapse.
|
|
387
388
|
|
|
388
|
-
|
|
389
|
+
Props:
|
|
389
390
|
|
|
390
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
396
|
+
Reads the current HeaderMotion context and exposes it through a render function.
|
|
399
397
|
|
|
400
|
-
|
|
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
|
-
|
|
400
|
+
Props:
|
|
415
401
|
|
|
416
|
-
|
|
402
|
+
- `children: (value) => ReactNode`: receives the bridged HeaderMotion context value that should usually be passed into `HeaderMotion.NavigationBridge`
|
|
417
403
|
|
|
418
|
-
|
|
419
|
-
<HeaderMotion.Header>
|
|
420
|
-
{(headerProps) => /* pass headerProps into navigation header */}
|
|
421
|
-
</HeaderMotion.Header>
|
|
422
|
-
```
|
|
404
|
+
### `HeaderMotion.NavigationBridge`
|
|
423
405
|
|
|
424
|
-
|
|
406
|
+
Re-provides a previously captured HeaderMotion context value in another subtree.
|
|
425
407
|
|
|
426
|
-
|
|
408
|
+
Use it together with `HeaderMotion.Bridge`.
|
|
427
409
|
|
|
428
|
-
|
|
410
|
+
Props:
|
|
429
411
|
|
|
430
|
-
- `
|
|
431
|
-
- `
|
|
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
|
-
|
|
415
|
+
### `HeaderMotion.ScrollView`
|
|
435
416
|
|
|
436
|
-
|
|
417
|
+
Pre-wired `Animated.ScrollView`.
|
|
437
418
|
|
|
438
|
-
|
|
419
|
+
Supports:
|
|
439
420
|
|
|
440
|
-
|
|
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
|
-
|
|
426
|
+
### `HeaderMotion.FlatList`
|
|
443
427
|
|
|
444
|
-
|
|
428
|
+
Pre-wired `Animated.FlatList`.
|
|
445
429
|
|
|
446
|
-
|
|
430
|
+
Supports the same HeaderMotion-specific props as `HeaderMotion.ScrollView`.
|
|
447
431
|
|
|
448
|
-
|
|
449
|
-
- `refreshing`
|
|
450
|
-
- `onRefresh`
|
|
451
|
-
- optional `progressViewOffset` if you want to force your offset.
|
|
432
|
+
### `createHeaderMotionScrollable(Component, options?)`
|
|
452
433
|
|
|
453
|
-
|
|
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
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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
|
-
|
|
463
|
+
`LegendList`
|
|
476
464
|
|
|
477
465
|
```tsx
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
|
512
|
-
- `progressThreshold
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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
|
-
|
|
503
|
+
Returns the full internal bridge value.
|
|
519
504
|
|
|
520
|
-
|
|
505
|
+
Most app code should not need this. Prefer `useMotionProgress()` unless you are explicitly bridging context across a tree boundary.
|
|
521
506
|
|
|
522
|
-
|
|
507
|
+
Returns:
|
|
523
508
|
|
|
524
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
534
|
-
- `active.state` (React state)
|
|
535
|
-
- `active.sv` (SharedValue)
|
|
542
|
+
That is why `overlay` defaults to `true`.
|
|
536
543
|
|
|
537
|
-
|
|
544
|
+
Disable it only when you intentionally want the header in normal layout flow.
|
|
538
545
|
|
|
539
|
-
|
|
546
|
+
### `ensureScrollableContentMinHeight` (experimental)
|
|
540
547
|
|
|
541
|
-
|
|
548
|
+
This is available on the pre-wired scrollables and the custom-scrollable APIs.
|
|
542
549
|
|
|
543
|
-
|
|
550
|
+
It is useful when content is too short to naturally scroll through the full collapse distance.
|
|
544
551
|
|
|
545
|
-
|
|
552
|
+
This feature is still experimental.
|
|
546
553
|
|
|
547
|
-
|
|
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
|
-
|
|
556
|
+
`scrollEventThrottle` is intentionally not managed by this library.
|
|
552
557
|
|
|
553
|
-
|
|
554
|
-
- `WithCollapsiblePagedHeaderProps` – like above, plus `activeTab` and `onTabChange`.
|
|
558
|
+
Pass it directly to your scrollable when you need it.
|
|
555
559
|
|
|
556
|
-
|
|
560
|
+
If you run into performance issues, try adjusting `scrollEventThrottle` to reduce how many scroll events this library processes.
|
|
557
561
|
|
|
558
|
-
### Refresh
|
|
562
|
+
### Refresh control
|
|
559
563
|
|
|
560
|
-
|
|
564
|
+
If you use `HeaderMotion.ScrollView` or `HeaderMotion.FlatList`, your refresh-control usage stays the same as in React Native.
|
|
561
565
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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
|
|
573
|
+
This matters because scrollable positioning affects refresh-control behavior and needs to stay coupled with the measured header height.
|
|
570
574
|
|
|
571
|
-
|
|
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
|
-
|
|
583
|
-
|
|
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)
|