diff --git a/docs/docs/01-getting-started.mdx b/docs/docs/01-getting-started.mdx index c15d91b..e8b4cb9 100644 --- a/docs/docs/01-getting-started.mdx +++ b/docs/docs/01-getting-started.mdx @@ -21,7 +21,7 @@ Before using this package, please make sure that you install the correct version | 0.6.x | >= 0.65 | >= 2.11.0 | >= 4.1.0 | N/A | | 0.7.x | >= 0.65 | >= 2.0.0 | >= 4.1.0 | N/A | | 0.8.x | >= 0.65 | >= 2.0.0 | >= 4.1.0 | N/A | -| 0.9.x | >= 0.65 | >= 2.0.0 | >= 4.1.0 | >= 3.2.0 | +| >= 0.9.x | >= 0.65 | >= 2.0.0 | >= 4.1.0 | >= 3.2.0 | ## Pre-requisites diff --git a/docs/docs/03-api-reference/01-scroll-view-with-headers.mdx b/docs/docs/03-api-reference/01-scroll-view-with-headers.mdx index 420a38e..f39f7ca 100644 --- a/docs/docs/03-api-reference/01-scroll-view-with-headers.mdx +++ b/docs/docs/03-api-reference/01-scroll-view-with-headers.mdx @@ -87,8 +87,28 @@ want to access the layout of the large header to calculate the height of the lar This property controls whether or not the header component is absolutely positioned. This is useful if you want to render a header component that allows for transparency. +**Note**: This is only available in version >= 0.9.0. + ### initialAbsoluteHeaderHeight This property is used when `absoluteHeader` is true. This is the initial height of the absolute header. Since the header's height is computed on its layout event, this is used to set the initial height of the header so that it doesn't jump when it is initially rendered. + +**Note**: This is only available in version >= 0.9.0. + +### headerFadeInThreshold + +A number between 0 and 1 representing at what point the header should fade in, +based on the percentage of the LargeHeader's height. For example, if this is set to 0.5, +the header will fade in when the scroll position is at 50% of the LargeHeader's height. + +Defaults to `1`. + +**Note**: This is only available in version >= 0.10.0. + +### disableLargeHeaderFadeAnim + +Whether or not the LargeHeaderComponent should fade in and out. Defaults to `false`. + +**Note**: This is only available in version >= 0.10.0. diff --git a/docs/docs/03-api-reference/02-flat-list-with-headers.mdx b/docs/docs/03-api-reference/02-flat-list-with-headers.mdx index 6a002c6..6c31cc5 100644 --- a/docs/docs/03-api-reference/02-flat-list-with-headers.mdx +++ b/docs/docs/03-api-reference/02-flat-list-with-headers.mdx @@ -87,8 +87,28 @@ want to access the layout of the large header to calculate the height of the lar This property controls whether or not the header component is absolutely positioned. This is useful if you want to render a header component that allows for transparency. +**Note**: This is only available in version >= 0.9.0. + ### initialAbsoluteHeaderHeight This property is used when `absoluteHeader` is true. This is the initial height of the absolute header. Since the header's height is computed on its layout event, this is used to set the initial height of the header so that it doesn't jump when it is initially rendered. + +**Note**: This is only available in version >= 0.9.0. + +### headerFadeInThreshold + +A number between 0 and 1 representing at what point the header should fade in, +based on the percentage of the LargeHeader's height. For example, if this is set to 0.5, +the header will fade in when the scroll position is at 50% of the LargeHeader's height. + +Defaults to `1`. + +**Note**: This is only available in version >= 0.10.0. + +### disableLargeHeaderFadeAnim + +Whether or not the LargeHeaderComponent should fade in and out. Defaults to `false`. + +**Note**: This is only available in version >= 0.10.0. diff --git a/docs/docs/03-api-reference/03-section-list-with-headers.mdx b/docs/docs/03-api-reference/03-section-list-with-headers.mdx index d0937f2..eb266a3 100644 --- a/docs/docs/03-api-reference/03-section-list-with-headers.mdx +++ b/docs/docs/03-api-reference/03-section-list-with-headers.mdx @@ -87,8 +87,28 @@ want to access the layout of the large header to calculate the height of the lar This property controls whether or not the header component is absolutely positioned. This is useful if you want to render a header component that allows for transparency. +**Note**: This is only available in version >= 0.9.0. + ### initialAbsoluteHeaderHeight This property is used when `absoluteHeader` is true. This is the initial height of the absolute header. Since the header's height is computed on its layout event, this is used to set the initial height of the header so that it doesn't jump when it is initially rendered. + +**Note**: This is only available in version >= 0.9.0. + +### headerFadeInThreshold + +A number between 0 and 1 representing at what point the header should fade in, +based on the percentage of the LargeHeader's height. For example, if this is set to 0.5, +the header will fade in when the scroll position is at 50% of the LargeHeader's height. + +Defaults to `1`. + +**Note**: This is only available in version >= 0.10.0. + +### disableLargeHeaderFadeAnim + +Whether or not the LargeHeaderComponent should fade in and out. Defaults to `false`. + +**Note**: This is only available in version >= 0.10.0. diff --git a/docs/docs/03-api-reference/04-flash-list-with-headers.mdx b/docs/docs/03-api-reference/04-flash-list-with-headers.mdx index 6c88677..10856bf 100644 --- a/docs/docs/03-api-reference/04-flash-list-with-headers.mdx +++ b/docs/docs/03-api-reference/04-flash-list-with-headers.mdx @@ -92,8 +92,28 @@ want to access the layout of the large header to calculate the height of the lar This property controls whether or not the header component is absolutely positioned. This is useful if you want to render a header component that allows for transparency. +**Note**: This is only available in version >= 0.9.0. + ### initialAbsoluteHeaderHeight This property is used when `absoluteHeader` is true. This is the initial height of the absolute header. Since the header's height is computed on its layout event, this is used to set the initial height of the header so that it doesn't jump when it is initially rendered. + +**Note**: This is only available in version >= 0.9.0. + +### headerFadeInThreshold + +A number between 0 and 1 representing at what point the header should fade in, +based on the percentage of the LargeHeader's height. For example, if this is set to 0.5, +the header will fade in when the scroll position is at 50% of the LargeHeader's height. + +Defaults to `1`. + +**Note**: This is only available in version >= 0.10.0. + +### disableLargeHeaderFadeAnim + +Whether or not the LargeHeaderComponent should fade in and out. Defaults to `false`. + +**Note**: This is only available in version >= 0.10.0. diff --git a/docs/docs/03-api-reference/05-header.mdx b/docs/docs/03-api-reference/05-header.mdx index 6ae9233..7dc6309 100644 --- a/docs/docs/03-api-reference/05-header.mdx +++ b/docs/docs/03-api-reference/05-header.mdx @@ -19,7 +19,7 @@ An animated value between 0 and 1 that indicates whether the header's children s An optional style object to apply to the header's root container. -NOTE: Do not apply an `absolute` position to this container. Instead, use the `absoluteHeader` prop +**Note**: Do not apply an `absolute` position to this container. Instead, use the `absoluteHeader` prop on any of the scroll containers to absolutely position the header. ### headerLeft @@ -85,6 +85,8 @@ An optional width to use for the header's bottom border. Defaults to [StyleSheet An optional component that can act as the surface of the header. Please ensure that the styling applied to the component includes `StyleSheet.absoluteFill` or `StyleSheet.absoluteFillObject` to ensure that the surface component fills the header. +**Note**: If you want to use a [BlurView](https://github.com/Kureev/react-native-blur#blurview) as the surface and want the content below to be seen under the surface, make sure you set [absoluteHeader](/docs/components/scroll-view-with-headers#absoluteHeader) to `true` on the scroll container. + #### Example The following example will make the header have a **cyan** background when the user scrolls the scroll container up: diff --git a/example/src/navigation/AppNavigation.tsx b/example/src/navigation/AppNavigation.tsx index 8cf99a9..df2698d 100644 --- a/example/src/navigation/AppNavigation.tsx +++ b/example/src/navigation/AppNavigation.tsx @@ -10,7 +10,8 @@ import { SimpleUsageScreen, SurfaceComponentUsageScreen, TwitterProfileScreen, - AbsoluteHeaderBlurSurface, + AbsoluteHeaderBlurSurfaceUsageScreen, + ArbitraryYTransitionHeaderUsageScreen, } from '../screens'; const Stack = createNativeStackNavigator(); @@ -34,7 +35,11 @@ export default () => ( + ); diff --git a/example/src/navigation/types.ts b/example/src/navigation/types.ts index a43051a..39fa1fd 100644 --- a/example/src/navigation/types.ts +++ b/example/src/navigation/types.ts @@ -11,6 +11,7 @@ export type RootStackParamList = { TwitterProfileScreen: undefined; HeaderSurfaceComponentUsageScreen: undefined; AbsoluteHeaderBlurSurfaceUsageScreen: undefined; + ArbitraryYTransitionHeaderUsageScreen: undefined; }; // Overrides the typing for useNavigation in @react-navigation/native to support the internal @@ -59,3 +60,8 @@ export type AbsoluteHeaderBlurSurfaceUsageScreenNavigationProps = NativeStackScr RootStackParamList, 'AbsoluteHeaderBlurSurfaceUsageScreen' >; + +export type ArbitraryYTransitionHeaderUsageScreenNavigationProps = NativeStackScreenProps< + RootStackParamList, + 'ArbitraryYTransitionHeaderUsageScreen' +>; diff --git a/example/src/screens/Home.tsx b/example/src/screens/Home.tsx index 3bdb122..dc8ab57 100644 --- a/example/src/screens/Home.tsx +++ b/example/src/screens/Home.tsx @@ -54,6 +54,12 @@ const SCREEN_LIST_CONFIG: ScreenConfigItem[] = [ route: 'AbsoluteHeaderBlurSurfaceUsageScreen', description: 'An example of an absolutely-positioned header with a BlurView surface.', }, + { + name: 'Arbitrary Y Transition Header', + route: 'ArbitraryYTransitionHeaderUsageScreen', + description: + 'An example of a header that transitions based on the scroll position of the ScrollView, instead of passing the height of the large header before animating.', + }, ]; const HeaderComponent: React.FC = ({ showNavBar }) => { diff --git a/example/src/screens/index.ts b/example/src/screens/index.ts index fa8ece3..a377be9 100644 --- a/example/src/screens/index.ts +++ b/example/src/screens/index.ts @@ -8,4 +8,5 @@ export { default as FlashListUsageScreen } from './usage/FlashList'; export { default as SectionListUsageScreen } from './usage/SectionList'; export { default as SurfaceComponentUsageScreen } from './usage/SurfaceComponent'; export { default as TwitterProfileScreen } from './usage/TwitterProfile'; -export { default as AbsoluteHeaderBlurSurface } from './usage/AbsoluteHeaderBlurSurface'; +export { default as AbsoluteHeaderBlurSurfaceUsageScreen } from './usage/AbsoluteHeaderBlurSurface'; +export { default as ArbitraryYTransitionHeaderUsageScreen } from './usage/ArbitraryYTransitionHeader'; diff --git a/example/src/screens/usage/ArbitraryYTransitionHeader.tsx b/example/src/screens/usage/ArbitraryYTransitionHeader.tsx new file mode 100644 index 0000000..e8fa9cd --- /dev/null +++ b/example/src/screens/usage/ArbitraryYTransitionHeader.tsx @@ -0,0 +1,102 @@ +import React, { useMemo, useState } from 'react'; +import { RefreshControl, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { useNavigation } from '@react-navigation/native'; +import { + Header, + LargeHeader, + ScalingView, + ScrollViewWithHeaders, +} from '@codeherence/react-native-header'; +import type { ScrollHeaderProps, ScrollLargeHeaderProps } from '@codeherence/react-native-header'; +import { range } from '../../utils'; +import { Avatar, BackButton } from '../../components'; +import { RANDOM_IMAGE_NUM } from '../../constants'; +import type { ArbitraryYTransitionHeaderUsageScreenNavigationProps } from '../../navigation'; + +const HeaderComponent: React.FC = ({ showNavBar }) => { + const navigation = useNavigation(); + const onPressProfile = () => navigation.navigate('Profile'); + + return ( +
+ Header + + } + headerRight={ + + + + } + headerRightFadesIn + headerLeft={} + /> + ); +}; + +const LargeHeaderComponent: React.FC = ({ scrollY }) => { + return ( + + + Large Header + + We test a larger LargeHeader in order to test the arbitrary y-value. + + + + + + ); +}; + +const ArbitraryYTransitionHeader: React.FC< + ArbitraryYTransitionHeaderUsageScreenNavigationProps +> = () => { + const { bottom } = useSafeAreaInsets(); + const [refreshing, setRefreshing] = useState(false); + + const data = useMemo(() => range({ end: 100 }), []); + + const onRefresh = async () => { + if (refreshing) return; + + setRefreshing(true); + // Mimic some asynchronous task + await new Promise((res) => setTimeout(res, 2500)); + setRefreshing(false); + }; + + return ( + + } + > + + {data.map((i) => ( + Scroll to see header animation + ))} + + + ); +}; + +export default ArbitraryYTransitionHeader; + +const styles = StyleSheet.create({ + children: { marginTop: 16, paddingHorizontal: 16 }, + navBarTitle: { fontSize: 16, fontWeight: 'bold' }, + largeHeaderStyle: { flexDirection: 'column' }, + title: { fontSize: 32, fontWeight: 'bold' }, + leftHeader: { gap: 4 }, + subtext: { color: '#8E8E93' }, + redBox: { width: 100, height: 100, backgroundColor: 'red' }, +}); diff --git a/example/src/screens/usage/TwitterProfile.tsx b/example/src/screens/usage/TwitterProfile.tsx index dd145e2..a463751 100644 --- a/example/src/screens/usage/TwitterProfile.tsx +++ b/example/src/screens/usage/TwitterProfile.tsx @@ -366,6 +366,8 @@ const TwitterProfile: React.FC = () => { // other relevant components to respect the safe area. ignoreLeftSafeArea ignoreRightSafeArea + headerFadeInThreshold={0.2} + disableLargeHeaderFadeAnim style={styles.container} contentContainerStyle={[styles.contentContainer, { paddingBottom: bottom }]} containerStyle={styles.rootContainer} diff --git a/src/components/containers/FlashList.tsx b/src/components/containers/FlashList.tsx index 7c6f9ae..41bd6f3 100644 --- a/src/components/containers/FlashList.tsx +++ b/src/components/containers/FlashList.tsx @@ -39,6 +39,8 @@ const FlashListWithHeadersInputComp = ( initialAbsoluteHeaderHeight = 0, contentContainerStyle = {}, automaticallyAdjustsScrollIndicatorInsets, + headerFadeInThreshold = 1, + disableLargeHeaderFadeAnim = false, ...rest }: AnimatedFlashListType, ref: React.Ref> @@ -63,6 +65,7 @@ const FlashListWithHeadersInputComp = ( largeHeaderExists: !!LargeHeaderComponent, absoluteHeader, initialAbsoluteHeaderHeight, + headerFadeInThreshold, }); return ( @@ -119,9 +122,15 @@ const FlashListWithHeadersInputComp = ( if (onLargeHeaderLayout) onLargeHeaderLayout(e.nativeEvent.layout); }} > - - {LargeHeaderComponent({ scrollY, showNavBar })} - + {!disableLargeHeaderFadeAnim ? ( + + {LargeHeaderComponent({ scrollY, showNavBar })} + + ) : ( + + {LargeHeaderComponent({ scrollY, showNavBar })} + + )} ) : undefined } diff --git a/src/components/containers/FlatList.tsx b/src/components/containers/FlatList.tsx index d7ca0b7..b87f701 100644 --- a/src/components/containers/FlatList.tsx +++ b/src/components/containers/FlatList.tsx @@ -35,6 +35,8 @@ const FlatListWithHeadersInputComp = ( initialAbsoluteHeaderHeight = 0, contentContainerStyle, automaticallyAdjustsScrollIndicatorInsets, + headerFadeInThreshold = 1, + disableLargeHeaderFadeAnim = false, ...rest }: AnimatedFlatListProps & SharedScrollContainerProps, ref: React.Ref | null> @@ -59,6 +61,7 @@ const FlatListWithHeadersInputComp = ( largeHeaderExists: !!LargeHeaderComponent, absoluteHeader, initialAbsoluteHeaderHeight, + headerFadeInThreshold, }); return ( @@ -114,9 +117,15 @@ const FlatListWithHeadersInputComp = ( if (onLargeHeaderLayout) onLargeHeaderLayout(e.nativeEvent.layout); }} > - - {LargeHeaderComponent({ scrollY, showNavBar })} - + {!disableLargeHeaderFadeAnim ? ( + + {LargeHeaderComponent({ scrollY, showNavBar })} + + ) : ( + + {LargeHeaderComponent({ scrollY, showNavBar })} + + )} ) : undefined } diff --git a/src/components/containers/ScrollView.tsx b/src/components/containers/ScrollView.tsx index f7f8947..e52a69f 100644 --- a/src/components/containers/ScrollView.tsx +++ b/src/components/containers/ScrollView.tsx @@ -35,6 +35,8 @@ const ScrollViewWithHeadersInputComp = ( initialAbsoluteHeaderHeight = 0, contentContainerStyle, automaticallyAdjustsScrollIndicatorInsets, + headerFadeInThreshold = 1, + disableLargeHeaderFadeAnim = false, ...rest }: AnimatedScrollViewProps & SharedScrollContainerProps, ref: React.Ref @@ -59,6 +61,7 @@ const ScrollViewWithHeadersInputComp = ( largeHeaderExists: !!LargeHeaderComponent, absoluteHeader, initialAbsoluteHeaderHeight, + headerFadeInThreshold, }); return ( @@ -113,9 +116,15 @@ const ScrollViewWithHeadersInputComp = ( if (onLargeHeaderLayout) onLargeHeaderLayout(e.nativeEvent.layout); }} > - - {LargeHeaderComponent({ scrollY, showNavBar })} - + {!disableLargeHeaderFadeAnim ? ( + + {LargeHeaderComponent({ scrollY, showNavBar })} + + ) : ( + + {LargeHeaderComponent({ scrollY, showNavBar })} + + )} ) : null} {children} diff --git a/src/components/containers/SectionList.tsx b/src/components/containers/SectionList.tsx index b5caed5..20f100e 100644 --- a/src/components/containers/SectionList.tsx +++ b/src/components/containers/SectionList.tsx @@ -39,6 +39,8 @@ const SectionListWithHeadersInputComp = , ref: React.Ref @@ -63,6 +65,7 @@ const SectionListWithHeadersInputComp = - - {LargeHeaderComponent({ scrollY, showNavBar })} - + {!disableLargeHeaderFadeAnim ? ( + + {LargeHeaderComponent({ scrollY, showNavBar })} + + ) : ( + + {LargeHeaderComponent({ scrollY, showNavBar })} + + )} ) : undefined } diff --git a/src/components/containers/types.ts b/src/components/containers/types.ts index 0e57569..6187c46 100644 --- a/src/components/containers/types.ts +++ b/src/components/containers/types.ts @@ -135,4 +135,18 @@ export type SharedScrollContainerProps = { * to set the initial height of the header so that it doesn't jump when it is initially rendered. */ initialAbsoluteHeaderHeight?: number; + /** + * A number between 0 and 1 representing at what point the header should fade in, + * based on the percentage of the LargeHeader's height. For example, if this is set to 0.5, + * the header will fade in when the scroll position is at 50% of the LargeHeader's height. + * + * @default 1 + */ + headerFadeInThreshold?: number; + /** + * Whether or not the LargeHeaderComponent should fade in and out. + * + * @default {false} + * */ + disableLargeHeaderFadeAnim?: boolean; }; diff --git a/src/components/containers/useScrollContainerLogic.ts b/src/components/containers/useScrollContainerLogic.ts index 054afdf..452de07 100644 --- a/src/components/containers/useScrollContainerLogic.ts +++ b/src/components/containers/useScrollContainerLogic.ts @@ -58,6 +58,14 @@ interface UseScrollContainerLogicArgs { * to set the initial height of the header so that it doesn't jump when it is initially rendered. */ initialAbsoluteHeaderHeight?: number; + /** + * A number between 0 and 1 representing at what point the header should fade in, + * based on the percentage of the LargeHeader's height. For example, if this is set to 0.5, + * the header will fade in when the scroll position is at 50% of the LargeHeader's height. + * + * @default 1 + */ + headerFadeInThreshold?: number; } /** @@ -74,6 +82,7 @@ export const useScrollContainerLogic = ({ adjustmentOffset = 4, absoluteHeader = false, initialAbsoluteHeaderHeight = 0, + headerFadeInThreshold = 1, }: UseScrollContainerLogicArgs) => { const [absoluteHeaderHeight, setAbsoluteHeaderHeight] = useState(initialAbsoluteHeaderHeight); const scrollY = useSharedValue(0); @@ -90,16 +99,19 @@ export const useScrollContainerLogic = ({ if (largeHeaderShown) { largeHeaderShown.value = withTiming( - scrollY.value <= largeHeaderHeight.value - adjustmentOffset ? 0 : 1, + scrollY.value <= largeHeaderHeight.value * headerFadeInThreshold - adjustmentOffset ? 0 : 1, { duration: 250, } ); } - return withTiming(scrollY.value <= largeHeaderHeight.value - adjustmentOffset ? 0 : 1, { - duration: 250, - }); + return withTiming( + scrollY.value <= largeHeaderHeight.value * headerFadeInThreshold - adjustmentOffset ? 0 : 1, + { + duration: 250, + } + ); }, [largeHeaderExists]); const largeHeaderOpacity = useDerivedValue(() => {