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