2025-01-28 08:19:46 -06:00
|
|
|
import type { PropsWithChildren, ReactElement } from 'react';
|
2025-01-28 12:56:22 -06:00
|
|
|
import { StyleSheet, ViewStyle, StyleProp } from 'react-native';
|
2025-01-28 08:19:46 -06:00
|
|
|
import Animated, {
|
|
|
|
interpolate,
|
|
|
|
useAnimatedRef,
|
|
|
|
useAnimatedStyle,
|
|
|
|
useScrollViewOffset,
|
|
|
|
} from 'react-native-reanimated';
|
2025-01-28 12:56:22 -06:00
|
|
|
import { ThemedText, ThemedView } from '@/components/theme/Theme';
|
2025-01-28 08:19:46 -06:00
|
|
|
import { useBottomTabOverflow } from '@/components/ui/TabBarBackground';
|
2025-01-28 12:56:22 -06:00
|
|
|
import { Colors } from '@/constants/Colors';
|
2025-01-28 08:19:46 -06:00
|
|
|
import { useColorScheme } from '@/hooks/useColorScheme';
|
|
|
|
|
2025-01-28 12:56:22 -06:00
|
|
|
const HEADER_HEIGHT = 150;
|
2025-01-28 08:19:46 -06:00
|
|
|
|
|
|
|
type Props = PropsWithChildren<{
|
2025-01-28 12:56:22 -06:00
|
|
|
headerImage?: ReactElement;
|
|
|
|
headerBackgroundColor?: { dark: string; light: string };
|
2025-01-28 10:17:33 -06:00
|
|
|
headerHeight?: number;
|
2025-01-28 12:56:22 -06:00
|
|
|
headerTitle?: ReactElement;
|
2025-01-28 08:19:46 -06:00
|
|
|
}>;
|
|
|
|
|
2025-01-28 10:17:33 -06:00
|
|
|
const ParallaxScrollView = ({
|
2025-01-28 08:19:46 -06:00
|
|
|
children,
|
2025-01-28 12:56:22 -06:00
|
|
|
headerImage = <ThemedView />,
|
|
|
|
headerBackgroundColor = { light: Colors.light.accent, dark: Colors.dark.accent },
|
2025-01-28 10:17:33 -06:00
|
|
|
headerHeight = HEADER_HEIGHT,
|
2025-01-28 12:56:22 -06:00
|
|
|
headerTitle = <ThemedText />,
|
2025-01-28 10:17:33 -06:00
|
|
|
}: Props) => {
|
|
|
|
const scheme = useColorScheme() ?? 'dark';
|
2025-01-28 08:19:46 -06:00
|
|
|
const scrollRef = useAnimatedRef<Animated.ScrollView>();
|
|
|
|
const scrollOffset = useScrollViewOffset(scrollRef);
|
|
|
|
const bottom = useBottomTabOverflow();
|
|
|
|
const headerAnimatedStyle = useAnimatedStyle(() => {
|
|
|
|
return {
|
|
|
|
transform: [
|
|
|
|
{
|
|
|
|
translateY: interpolate(
|
|
|
|
scrollOffset.value,
|
2025-01-28 10:17:33 -06:00
|
|
|
[-headerHeight, 0, headerHeight],
|
|
|
|
[-headerHeight / 2, 0, headerHeight * 0.75],
|
2025-01-28 08:19:46 -06:00
|
|
|
),
|
|
|
|
},
|
|
|
|
{
|
2025-01-28 10:17:33 -06:00
|
|
|
scale: interpolate(scrollOffset.value, [-headerHeight, 0, headerHeight], [2, 1, 1]),
|
2025-01-28 08:19:46 -06:00
|
|
|
},
|
|
|
|
],
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
return (
|
|
|
|
<ThemedView style={styles.container}>
|
|
|
|
<Animated.ScrollView
|
|
|
|
ref={scrollRef}
|
|
|
|
scrollEventThrottle={16}
|
|
|
|
scrollIndicatorInsets={{ bottom }}
|
2025-01-28 08:45:02 -06:00
|
|
|
contentContainerStyle={{ paddingBottom: bottom }}
|
|
|
|
>
|
2025-01-28 08:19:46 -06:00
|
|
|
<Animated.View
|
|
|
|
style={[
|
|
|
|
styles.header,
|
2025-01-28 10:17:33 -06:00
|
|
|
{
|
|
|
|
backgroundColor: headerBackgroundColor[scheme],
|
|
|
|
height: headerHeight,
|
|
|
|
},
|
2025-01-28 08:19:46 -06:00
|
|
|
headerAnimatedStyle,
|
2025-01-28 08:45:02 -06:00
|
|
|
]}
|
|
|
|
>
|
2025-01-28 08:19:46 -06:00
|
|
|
{headerImage}
|
2025-01-28 12:56:22 -06:00
|
|
|
{headerTitle}
|
2025-01-28 08:19:46 -06:00
|
|
|
</Animated.View>
|
|
|
|
<ThemedView style={styles.content}>{children}</ThemedView>
|
|
|
|
</Animated.ScrollView>
|
|
|
|
</ThemedView>
|
|
|
|
);
|
2025-01-28 10:17:33 -06:00
|
|
|
};
|
|
|
|
export default ParallaxScrollView;
|
2025-01-28 08:19:46 -06:00
|
|
|
|
|
|
|
const styles = StyleSheet.create({
|
2025-01-28 12:56:22 -06:00
|
|
|
blank: {
|
|
|
|
|
|
|
|
},
|
2025-01-28 08:19:46 -06:00
|
|
|
container: {
|
|
|
|
flex: 1,
|
|
|
|
},
|
|
|
|
header: {
|
|
|
|
overflow: 'hidden',
|
|
|
|
},
|
|
|
|
content: {
|
|
|
|
flex: 1,
|
|
|
|
padding: 32,
|
|
|
|
gap: 16,
|
|
|
|
overflow: 'hidden',
|
|
|
|
},
|
|
|
|
});
|