react-native-animated-header-flat-list
Version:
A React Native FlatList component with an animated collapsible header, featuring parallax effects, smooth title transitions, sticky component support, and customizable styles. Built with TypeScript and separate background/content layers in header.
195 lines (194 loc) • 6.66 kB
JavaScript
"use strict";
import React from 'react';
import { StatusBar, StyleSheet, View } from 'react-native';
import { useLayoutEffect, useCallback, useMemo } from 'react';
import Animated from 'react-native-reanimated';
import { useAnimatedHeaderFlatListAnimatedStyles } from "../hooks/useAnimatedHeaderFlatListAnimatedStyles.js";
import { getFontSizeFromStyle } from "../utils/styleUtils.js";
import { useNavigation } from '@react-navigation/native';
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
const HEADER_ITEM = 'REACT_NATIVE_ANIMATED_HEADER_FLAT_LIST_HEADER';
export function AnimatedHeaderFlatList({
title,
navigationBarColor,
headerTitleStyle,
navigationTitleStyle,
HeaderBackground,
HeaderContent,
StickyComponent,
parallax = true,
navigationTitleTranslateX = 0,
navigationTitleTranslateY = 0,
...flatListProps
}) {
const navigation = useNavigation();
const {
scrollHandler,
navigationBarHeight,
headerLayout,
setHeaderLayout,
setHeaderTitleLayout,
stickyComponentLayout,
setStickyComponentLayout,
stickyComponentAnimatedStyle,
navigationBarAnimatedStyle,
navigationTitleAnimatedStyle,
headerTitleAnimatedStyle,
stickyHeaderAnimatedStyle,
headerContentAnimatedStyle,
headerBackgroundAnimatedStyle
} = useAnimatedHeaderFlatListAnimatedStyles({
headerTitleFontSize: getFontSizeFromStyle(headerTitleStyle),
navigationTitleFontSize: getFontSizeFromStyle(navigationTitleStyle),
navigationTitleTranslateX,
navigationTitleTranslateY
});
const navigationTitle = useCallback(() => /*#__PURE__*/_jsx(Animated.Text, {
style: [navigationTitleAnimatedStyle, navigationTitleStyle],
numberOfLines: 1,
children: title
}), [navigationTitleAnimatedStyle, navigationTitleStyle, title]);
useLayoutEffect(() => {
navigation.setOptions({
headerShown: true,
headerStyle: styles.navigationBar,
headerShadowVisible: false,
headerTransparent: true,
headerTitle: navigationTitle,
headerTitleAlign: 'center'
});
}, [navigationTitle, navigation]);
const ListHeaderComponent = useMemo(() => {
return /*#__PURE__*/_jsx(View, {
style: styles.headerWrapper,
children: /*#__PURE__*/_jsxs(View, {
style: [styles.headerContainer, {
top: -navigationBarHeight
}],
onLayout: event => {
setHeaderLayout({
...event.nativeEvent.layout,
height: event.nativeEvent.layout.height + navigationBarHeight
});
},
children: [/*#__PURE__*/_jsx(Animated.View, {
style: parallax ? headerBackgroundAnimatedStyle : undefined,
children: /*#__PURE__*/_jsx(HeaderBackground, {})
}), HeaderContent && /*#__PURE__*/_jsx(Animated.View, {
style: [headerContentAnimatedStyle, styles.headerContentContainer],
children: /*#__PURE__*/_jsx(HeaderContent, {})
}), navigationBarColor && /*#__PURE__*/_jsx(Animated.View, {
style: [navigationBarAnimatedStyle, styles.animatedNavigationBar, {
backgroundColor: navigationBarColor
}]
}), /*#__PURE__*/_jsx(Animated.Text, {
onLayout: event => {
setHeaderTitleLayout(event.nativeEvent.layout);
},
numberOfLines: 1,
style: [headerTitleAnimatedStyle, styles.headerTitle, headerTitleStyle],
children: title
})]
})
});
}, [navigationBarHeight, parallax, headerBackgroundAnimatedStyle, HeaderBackground, HeaderContent, headerContentAnimatedStyle, headerTitleAnimatedStyle, headerTitleStyle, title, setHeaderLayout, setHeaderTitleLayout, navigationBarAnimatedStyle, navigationBarColor]);
const renderItem = useCallback(({
item
}) => {
if (item === HEADER_ITEM) {
return /*#__PURE__*/_jsxs(View, {
style: [styles.stickyHeaderContainer, {
height: navigationBarHeight + stickyComponentLayout.height
}],
children: [/*#__PURE__*/_jsx(Animated.View, {
style: [stickyHeaderAnimatedStyle, styles.stickyHeader, {
bottom: headerLayout.height - navigationBarHeight * 2 + stickyComponentLayout.height
}],
children: ListHeaderComponent
}), StickyComponent && /*#__PURE__*/_jsx(Animated.View, {
style: [styles.stickyComponentContainer, stickyComponentAnimatedStyle],
onLayout: event => {
setStickyComponentLayout(event.nativeEvent.layout);
},
children: /*#__PURE__*/_jsx(StickyComponent, {})
})]
});
}
return flatListProps.renderItem && typeof flatListProps.renderItem === 'function' ? flatListProps.renderItem({
item
}) : null;
}, [flatListProps, navigationBarHeight, stickyComponentLayout.height, stickyComponentAnimatedStyle, stickyHeaderAnimatedStyle, headerLayout.height, ListHeaderComponent, StickyComponent, setStickyComponentLayout]);
const data = useMemo(() => {
const listData = Array.isArray(flatListProps.data) ? flatListProps.data : [];
return [HEADER_ITEM, ...listData];
}, [flatListProps.data]);
return /*#__PURE__*/_jsxs(_Fragment, {
children: [/*#__PURE__*/_jsx(StatusBar, {
backgroundColor: "transparent",
translucent: true
}), /*#__PURE__*/_jsx(Animated.FlatList, {
...flatListProps,
stickyHeaderIndices: [1],
ListHeaderComponent: /*#__PURE__*/_jsx(Animated.View, {
style: [styles.mainHeaderContainer, {
height: headerLayout.height - navigationBarHeight * 2,
transform: [{
translateY: navigationBarHeight
}]
}],
children: ListHeaderComponent
}),
onScroll: scrollHandler,
data: data,
renderItem: renderItem
})]
});
}
const styles = StyleSheet.create({
navigationBar: {
backgroundColor: 'transparent'
},
headerWrapper: {
overflow: 'visible'
},
headerContainer: {
position: 'absolute',
left: 0,
right: 0,
overflow: 'visible'
},
stickyHeaderContainer: {
width: '100%'
},
stickyHeader: {
position: 'absolute',
left: 0,
right: 0
},
mainHeaderContainer: {
overflow: 'visible'
},
animatedNavigationBar: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0
},
headerTitle: {
position: 'absolute'
},
stickyComponentContainer: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0
},
headerContentContainer: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0
}
});
//# sourceMappingURL=AnimatedHeaderFlatList.js.map