Skip to content

Commit 9412d65

Browse files
authored
feat!: API refactor (#17)
* feat: first codex pass * feat: add headerPanDecayConfig and move enableHeaderPan * chore: enableHeaderPan -> pannable * chore: rearrange files * chore: better JS Doc * chore: types adjustments * chore: updated docs * chore: added asChild example BREAKING CHANGE: API has been refactored. Refer to the documentation for a migration guide.
1 parent 7eb121b commit 9412d65

57 files changed

Lines changed: 2328 additions & 1436 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

MIGRATION-v1.md

Lines changed: 327 additions & 47 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 373 additions & 415 deletions
Large diffs are not rendered by default.

example/src/app/animated-on-scroll.tsx

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import { DynamicBox, TitleWithSubtitle, generateContent } from '@/components';
2-
import HeaderMotion, {
3-
AnimatedHeaderBase,
4-
type WithCollapsibleHeaderProps,
5-
} from 'react-native-header-motion';
2+
import HeaderMotion, { useMotionProgress } from 'react-native-header-motion';
63
import { Stack } from 'expo-router';
74
import { StyleSheet, View } from 'react-native';
85
import Animated, {
@@ -23,29 +20,28 @@ export default function Screen() {
2320

2421
return (
2522
<HeaderMotion>
26-
<HeaderMotion.Header>
27-
{(headerProps) => (
23+
<HeaderMotion.Bridge>
24+
{(value) => (
2825
<Stack.Screen
2926
options={{
30-
header: () => <CollapsibleHeader {...headerProps} />,
27+
header: () => (
28+
<HeaderMotion.NavigationBridge value={value}>
29+
<CollapsibleHeader />
30+
</HeaderMotion.NavigationBridge>
31+
),
3132
}}
3233
/>
3334
)}
34-
</HeaderMotion.Header>
35+
</HeaderMotion.Bridge>
3536
<HeaderMotion.ScrollView onScroll={onScroll}>
3637
{content}
3738
</HeaderMotion.ScrollView>
3839
</HeaderMotion>
3940
);
4041
}
4142

42-
function CollapsibleHeader({
43-
progress,
44-
measureTotalHeight,
45-
measureDynamic,
46-
progressThreshold,
47-
animatedHeaderBaseProps,
48-
}: WithCollapsibleHeaderProps) {
43+
function CollapsibleHeader() {
44+
const { progress, progressThreshold } = useMotionProgress();
4945
const insets = useSafeAreaInsets();
5046

5147
const containerStyle = useAnimatedStyle(() => {
@@ -97,9 +93,7 @@ function CollapsibleHeader({
9793
});
9894

9995
return (
100-
<AnimatedHeaderBase
101-
animatedHeaderBaseProps={animatedHeaderBaseProps}
102-
onLayout={measureTotalHeight}
96+
<HeaderMotion.Header
10397
style={[styles.headerWrapper, { paddingTop: insets.top }, containerStyle]}
10498
>
10599
<Animated.View style={[titleStyle]}>
@@ -110,15 +104,14 @@ function CollapsibleHeader({
110104
</Animated.View>
111105

112106
<View style={styles.dynamicContent}>
113-
<Animated.View
107+
<HeaderMotion.Header.Dynamic
114108
style={[styles.boxContainer, boxSectionStyle]}
115-
onLayout={measureDynamic}
116109
>
117110
<DynamicBox />
118111
<DynamicBox />
119-
</Animated.View>
112+
</HeaderMotion.Header.Dynamic>
120113
</View>
121-
</AnimatedHeaderBase>
114+
</HeaderMotion.Header>
122115
);
123116
}
124117

example/src/app/as-child.tsx

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { DynamicBox, TitleWithSubtitle, generateContent } from '@/components';
2+
import HeaderMotion, { useMotionProgress } from 'react-native-header-motion';
3+
import { Stack } from 'expo-router';
4+
import { StyleSheet, View } from 'react-native';
5+
import Animated, {
6+
Extrapolation,
7+
interpolate,
8+
useAnimatedStyle,
9+
} from 'react-native-reanimated';
10+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
11+
12+
export default function Screen() {
13+
return (
14+
<HeaderMotion>
15+
<HeaderMotion.Bridge>
16+
{(value) => (
17+
<Stack.Screen
18+
options={{
19+
header: () => (
20+
<HeaderMotion.NavigationBridge value={value}>
21+
<AsChildHeader />
22+
</HeaderMotion.NavigationBridge>
23+
),
24+
}}
25+
/>
26+
)}
27+
</HeaderMotion.Bridge>
28+
<HeaderMotion.ScrollView>{content}</HeaderMotion.ScrollView>
29+
</HeaderMotion>
30+
);
31+
}
32+
33+
function AsChildHeader() {
34+
const { progress, progressThreshold } = useMotionProgress();
35+
const insets = useSafeAreaInsets();
36+
37+
const containerStyle = useAnimatedStyle(() => {
38+
const threshold = progressThreshold.get();
39+
const translateY = interpolate(
40+
progress.get(),
41+
[0, 1],
42+
[0, -threshold],
43+
Extrapolation.CLAMP
44+
);
45+
46+
return { transform: [{ translateY }] };
47+
});
48+
49+
const titleStyle = useAnimatedStyle(() => {
50+
const threshold = progressThreshold.get();
51+
const translateY = interpolate(
52+
progress.get(),
53+
[0, 1],
54+
[0, threshold],
55+
Extrapolation.CLAMP
56+
);
57+
58+
return { transform: [{ translateY }] };
59+
});
60+
61+
const dynamicStyle = useAnimatedStyle(() => {
62+
const threshold = progressThreshold.get();
63+
const opacity = interpolate(
64+
progress.get(),
65+
[0, 0.65, 1],
66+
[1, 0.25, 0],
67+
Extrapolation.CLAMP
68+
);
69+
70+
return {
71+
opacity,
72+
transform: [
73+
{
74+
translateY: interpolate(
75+
progress.get(),
76+
[0, 1],
77+
[0, threshold * 0.45],
78+
Extrapolation.CLAMP
79+
),
80+
},
81+
],
82+
};
83+
});
84+
85+
return (
86+
<HeaderMotion.Header asChild>
87+
<Animated.View
88+
style={[
89+
styles.headerWrapper,
90+
styles.absoluteHeaderWrapper,
91+
{ paddingTop: insets.top },
92+
containerStyle,
93+
]}
94+
>
95+
<Animated.View style={titleStyle}>
96+
<TitleWithSubtitle
97+
title="asChild Header"
98+
subtitle="Measurement injected into your own elements"
99+
/>
100+
</Animated.View>
101+
102+
<View style={styles.dynamicContent}>
103+
<HeaderMotion.Header.Dynamic asChild>
104+
<Animated.View style={[styles.boxContainer, dynamicStyle]}>
105+
<DynamicBox />
106+
<DynamicBox />
107+
</Animated.View>
108+
</HeaderMotion.Header.Dynamic>
109+
</View>
110+
</Animated.View>
111+
</HeaderMotion.Header>
112+
);
113+
}
114+
115+
const styles = StyleSheet.create({
116+
headerWrapper: {
117+
backgroundColor: '#1D4ED8',
118+
borderBottomWidth: 1,
119+
borderBottomColor: 'rgba(15, 23, 42, 0.14)',
120+
},
121+
absoluteHeaderWrapper: {
122+
top: 0,
123+
left: 0,
124+
right: 0,
125+
position: 'absolute',
126+
},
127+
dynamicContent: {
128+
overflow: 'hidden',
129+
},
130+
boxContainer: {
131+
flexDirection: 'row',
132+
gap: 10,
133+
paddingHorizontal: 12,
134+
paddingBottom: 12,
135+
alignItems: 'stretch',
136+
},
137+
});
138+
139+
const content = generateContent({
140+
count: 120,
141+
backgroundColor: '#DBEAFE',
142+
textColor: '#1E3A8A',
143+
});

example/src/app/collapsible-pager.tsx

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ import {
55
generateContent,
66
} from '@/components';
77
import HeaderMotion, {
8-
AnimatedHeaderBase,
98
useActiveScrollId,
10-
type WithCollapsiblePagedHeaderProps,
9+
useMotionProgress,
1110
} from 'react-native-header-motion';
1211
import { Stack } from 'expo-router';
1312
import { useRef } from 'react';
@@ -45,21 +44,22 @@ export default function Screen() {
4544

4645
return (
4746
<HeaderMotion activeScrollId={activeScrollId.sv}>
48-
<HeaderMotion.Header>
49-
{(headerProps) => (
47+
<HeaderMotion.Bridge>
48+
{(value) => (
5049
<Stack.Screen
5150
options={{
5251
header: () => (
53-
<CollapsibleHeader
54-
{...headerProps}
55-
activeTab={activeScrollId.state}
56-
onTabChange={handleTabPress}
57-
/>
52+
<HeaderMotion.NavigationBridge value={value}>
53+
<CollapsibleHeader
54+
activeTab={activeScrollId.state}
55+
onTabChange={handleTabPress}
56+
/>
57+
</HeaderMotion.NavigationBridge>
5858
),
5959
}}
6060
/>
6161
)}
62-
</HeaderMotion.Header>
62+
</HeaderMotion.Bridge>
6363
<PagerView
6464
ref={pagerRef}
6565
style={styles.pagerView}
@@ -82,14 +82,13 @@ export default function Screen() {
8282
}
8383

8484
function CollapsibleHeader({
85-
progress,
86-
measureTotalHeight,
87-
measureDynamic,
88-
progressThreshold,
89-
animatedHeaderBaseProps,
9085
activeTab,
9186
onTabChange,
92-
}: WithCollapsiblePagedHeaderProps) {
87+
}: {
88+
activeTab: string;
89+
onTabChange: (newTab: string) => void;
90+
}) {
91+
const { progress, progressThreshold } = useMotionProgress();
9392
const insets = useSafeAreaInsets();
9493

9594
// 1. Container Animation (Moves UP)
@@ -144,23 +143,20 @@ function CollapsibleHeader({
144143
});
145144

146145
return (
147-
<AnimatedHeaderBase
148-
animatedHeaderBaseProps={animatedHeaderBaseProps}
149-
onLayout={measureTotalHeight}
146+
<HeaderMotion.Header
150147
style={[styles.headerWrapper, { paddingTop: insets.top }, containerStyle]}
151148
>
152149
<Animated.View style={[titleStyle]}>
153150
<TitleWithSubtitle title="Title" subtitle="Subtitle" />
154151
</Animated.View>
155152

156153
<View style={styles.dynamicContent}>
157-
<Animated.View
154+
<HeaderMotion.Header.Dynamic
158155
style={[styles.boxContainer, boxSectionStyle]}
159-
onLayout={measureDynamic}
160156
>
161157
<DynamicBox />
162158
<DynamicBox />
163-
</Animated.View>
159+
</HeaderMotion.Header.Dynamic>
164160
</View>
165161

166162
<View style={styles.tabBar}>
@@ -175,7 +171,7 @@ function CollapsibleHeader({
175171
onPress={() => onTabChange('B')}
176172
/>
177173
</View>
178-
</AnimatedHeaderBase>
174+
</HeaderMotion.Header>
179175
);
180176
}
181177

0 commit comments

Comments
 (0)