Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
374 changes: 327 additions & 47 deletions MIGRATION-v1.md

Large diffs are not rendered by default.

788 changes: 373 additions & 415 deletions README.md

Large diffs are not rendered by default.

37 changes: 15 additions & 22 deletions example/src/app/animated-on-scroll.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { DynamicBox, TitleWithSubtitle, generateContent } from '@/components';
import HeaderMotion, {
AnimatedHeaderBase,
type WithCollapsibleHeaderProps,
} from 'react-native-header-motion';
import HeaderMotion, { useMotionProgress } from 'react-native-header-motion';
import { Stack } from 'expo-router';
import { StyleSheet, View } from 'react-native';
import Animated, {
Expand All @@ -23,29 +20,28 @@ export default function Screen() {

return (
<HeaderMotion>
<HeaderMotion.Header>
{(headerProps) => (
<HeaderMotion.Bridge>
{(value) => (
<Stack.Screen
options={{
header: () => <CollapsibleHeader {...headerProps} />,
header: () => (
<HeaderMotion.NavigationBridge value={value}>
<CollapsibleHeader />
</HeaderMotion.NavigationBridge>
),
}}
/>
)}
</HeaderMotion.Header>
</HeaderMotion.Bridge>
<HeaderMotion.ScrollView onScroll={onScroll}>
{content}
</HeaderMotion.ScrollView>
</HeaderMotion>
);
}

function CollapsibleHeader({
progress,
measureTotalHeight,
measureDynamic,
progressThreshold,
animatedHeaderBaseProps,
}: WithCollapsibleHeaderProps) {
function CollapsibleHeader() {
const { progress, progressThreshold } = useMotionProgress();
const insets = useSafeAreaInsets();

const containerStyle = useAnimatedStyle(() => {
Expand Down Expand Up @@ -97,9 +93,7 @@ function CollapsibleHeader({
});

return (
<AnimatedHeaderBase
animatedHeaderBaseProps={animatedHeaderBaseProps}
onLayout={measureTotalHeight}
<HeaderMotion.Header
style={[styles.headerWrapper, { paddingTop: insets.top }, containerStyle]}
>
<Animated.View style={[titleStyle]}>
Expand All @@ -110,15 +104,14 @@ function CollapsibleHeader({
</Animated.View>

<View style={styles.dynamicContent}>
<Animated.View
<HeaderMotion.Header.Dynamic
style={[styles.boxContainer, boxSectionStyle]}
onLayout={measureDynamic}
>
<DynamicBox />
<DynamicBox />
</Animated.View>
</HeaderMotion.Header.Dynamic>
</View>
</AnimatedHeaderBase>
</HeaderMotion.Header>
);
}

Expand Down
143 changes: 143 additions & 0 deletions example/src/app/as-child.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { DynamicBox, TitleWithSubtitle, generateContent } from '@/components';
import HeaderMotion, { useMotionProgress } from 'react-native-header-motion';
import { Stack } from 'expo-router';
import { StyleSheet, View } from 'react-native';
import Animated, {
Extrapolation,
interpolate,
useAnimatedStyle,
} from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

export default function Screen() {
return (
<HeaderMotion>
<HeaderMotion.Bridge>
{(value) => (
<Stack.Screen
options={{
header: () => (
<HeaderMotion.NavigationBridge value={value}>
<AsChildHeader />
</HeaderMotion.NavigationBridge>
),
}}
/>
)}
</HeaderMotion.Bridge>
<HeaderMotion.ScrollView>{content}</HeaderMotion.ScrollView>
</HeaderMotion>
);
}

function AsChildHeader() {
const { progress, progressThreshold } = useMotionProgress();
const insets = useSafeAreaInsets();

const containerStyle = useAnimatedStyle(() => {
const threshold = progressThreshold.get();
const translateY = interpolate(
progress.get(),
[0, 1],
[0, -threshold],
Extrapolation.CLAMP
);

return { transform: [{ translateY }] };
});

const titleStyle = useAnimatedStyle(() => {
const threshold = progressThreshold.get();
const translateY = interpolate(
progress.get(),
[0, 1],
[0, threshold],
Extrapolation.CLAMP
);

return { transform: [{ translateY }] };
});

const dynamicStyle = useAnimatedStyle(() => {
const threshold = progressThreshold.get();
const opacity = interpolate(
progress.get(),
[0, 0.65, 1],
[1, 0.25, 0],
Extrapolation.CLAMP
);

return {
opacity,
transform: [
{
translateY: interpolate(
progress.get(),
[0, 1],
[0, threshold * 0.45],
Extrapolation.CLAMP
),
},
],
};
});

return (
<HeaderMotion.Header asChild>
<Animated.View
style={[
styles.headerWrapper,
styles.absoluteHeaderWrapper,
{ paddingTop: insets.top },
containerStyle,
]}
>
<Animated.View style={titleStyle}>
<TitleWithSubtitle
title="asChild Header"
subtitle="Measurement injected into your own elements"
/>
</Animated.View>

<View style={styles.dynamicContent}>
<HeaderMotion.Header.Dynamic asChild>
<Animated.View style={[styles.boxContainer, dynamicStyle]}>
<DynamicBox />
<DynamicBox />
</Animated.View>
</HeaderMotion.Header.Dynamic>
</View>
</Animated.View>
</HeaderMotion.Header>
);
}

const styles = StyleSheet.create({
headerWrapper: {
backgroundColor: '#1D4ED8',
borderBottomWidth: 1,
borderBottomColor: 'rgba(15, 23, 42, 0.14)',
},
absoluteHeaderWrapper: {
top: 0,
left: 0,
right: 0,
position: 'absolute',
},
dynamicContent: {
overflow: 'hidden',
},
boxContainer: {
flexDirection: 'row',
gap: 10,
paddingHorizontal: 12,
paddingBottom: 12,
alignItems: 'stretch',
},
});

const content = generateContent({
count: 120,
backgroundColor: '#DBEAFE',
textColor: '#1E3A8A',
});
42 changes: 19 additions & 23 deletions example/src/app/collapsible-pager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import {
generateContent,
} from '@/components';
import HeaderMotion, {
AnimatedHeaderBase,
useActiveScrollId,
type WithCollapsiblePagedHeaderProps,
useMotionProgress,
} from 'react-native-header-motion';
import { Stack } from 'expo-router';
import { useRef } from 'react';
Expand Down Expand Up @@ -45,21 +44,22 @@ export default function Screen() {

return (
<HeaderMotion activeScrollId={activeScrollId.sv}>
<HeaderMotion.Header>
{(headerProps) => (
<HeaderMotion.Bridge>
{(value) => (
<Stack.Screen
options={{
header: () => (
<CollapsibleHeader
{...headerProps}
activeTab={activeScrollId.state}
onTabChange={handleTabPress}
/>
<HeaderMotion.NavigationBridge value={value}>
<CollapsibleHeader
activeTab={activeScrollId.state}
onTabChange={handleTabPress}
/>
</HeaderMotion.NavigationBridge>
),
}}
/>
)}
</HeaderMotion.Header>
</HeaderMotion.Bridge>
<PagerView
ref={pagerRef}
style={styles.pagerView}
Expand All @@ -82,14 +82,13 @@ export default function Screen() {
}

function CollapsibleHeader({
progress,
measureTotalHeight,
measureDynamic,
progressThreshold,
animatedHeaderBaseProps,
activeTab,
onTabChange,
}: WithCollapsiblePagedHeaderProps) {
}: {
activeTab: string;
onTabChange: (newTab: string) => void;
}) {
const { progress, progressThreshold } = useMotionProgress();
const insets = useSafeAreaInsets();

// 1. Container Animation (Moves UP)
Expand Down Expand Up @@ -144,23 +143,20 @@ function CollapsibleHeader({
});

return (
<AnimatedHeaderBase
animatedHeaderBaseProps={animatedHeaderBaseProps}
onLayout={measureTotalHeight}
<HeaderMotion.Header
style={[styles.headerWrapper, { paddingTop: insets.top }, containerStyle]}
>
<Animated.View style={[titleStyle]}>
<TitleWithSubtitle title="Title" subtitle="Subtitle" />
</Animated.View>

<View style={styles.dynamicContent}>
<Animated.View
<HeaderMotion.Header.Dynamic
style={[styles.boxContainer, boxSectionStyle]}
onLayout={measureDynamic}
>
<DynamicBox />
<DynamicBox />
</Animated.View>
</HeaderMotion.Header.Dynamic>
</View>

<View style={styles.tabBar}>
Expand All @@ -175,7 +171,7 @@ function CollapsibleHeader({
onPress={() => onTabChange('B')}
/>
</View>
</AnimatedHeaderBase>
</HeaderMotion.Header>
);
}

Expand Down
Loading
Loading