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
19 changes: 19 additions & 0 deletions docs/docs/03-api-reference/01-scroll-view-with-headers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,22 @@ Defaults to `1`.
Whether or not the LargeHeaderComponent should fade in and out. Defaults to `false`.

**Note**: This is only available in version >= 0.10.0.

### onScrollWorklet

A custom worklet that allows custom tracking scroll container's
state (i.e., its scroll contentInset, contentOffset, etc.). Please
ensure that this function is a [worklet](https://docs.swmansion.com/react-native-reanimated/docs/2.x/fundamentals/worklets/).

Since the library uses the `onScroll` prop to handle animations internally and [reanimated
does not currently allow for multiple onScroll handlers](https://github.com/software-mansion/react-native-reanimated/discussions/1763),
you must use this property to track the state of the scroll container's state.

An example is shown below:

```tsx
const scrollHandlerWorklet = (evt: NativeScrollEvent) => {
'worklet';
console.log('offset: ', evt.contentOffset);
};
```
19 changes: 19 additions & 0 deletions docs/docs/03-api-reference/02-flat-list-with-headers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,22 @@ Defaults to `1`.
Whether or not the LargeHeaderComponent should fade in and out. Defaults to `false`.

**Note**: This is only available in version >= 0.10.0.

### onScrollWorklet

A custom worklet that allows custom tracking scroll container's
state (i.e., its scroll contentInset, contentOffset, etc.). Please
ensure that this function is a [worklet](https://docs.swmansion.com/react-native-reanimated/docs/2.x/fundamentals/worklets/).

Since the library uses the `onScroll` prop to handle animations internally and [reanimated
does not currently allow for multiple onScroll handlers](https://github.com/software-mansion/react-native-reanimated/discussions/1763),
you must use this property to track the state of the scroll container's state.

An example is shown below:

```tsx
const scrollHandlerWorklet = (evt: NativeScrollEvent) => {
'worklet';
console.log('offset: ', evt.contentOffset);
};
```
19 changes: 19 additions & 0 deletions docs/docs/03-api-reference/03-section-list-with-headers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,22 @@ Defaults to `1`.
Whether or not the LargeHeaderComponent should fade in and out. Defaults to `false`.

**Note**: This is only available in version >= 0.10.0.

### onScrollWorklet

A custom worklet that allows custom tracking scroll container's
state (i.e., its scroll contentInset, contentOffset, etc.). Please
ensure that this function is a [worklet](https://docs.swmansion.com/react-native-reanimated/docs/2.x/fundamentals/worklets/).

Since the library uses the `onScroll` prop to handle animations internally and [reanimated
does not currently allow for multiple onScroll handlers](https://github.com/software-mansion/react-native-reanimated/discussions/1763),
you must use this property to track the state of the scroll container's state.

An example is shown below:

```tsx
const scrollHandlerWorklet = (evt: NativeScrollEvent) => {
'worklet';
console.log('offset: ', evt.contentOffset);
};
```
19 changes: 19 additions & 0 deletions docs/docs/03-api-reference/04-flash-list-with-headers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,22 @@ Defaults to `1`.
Whether or not the LargeHeaderComponent should fade in and out. Defaults to `false`.

**Note**: This is only available in version >= 0.10.0.

### onScrollWorklet

A custom worklet that allows custom tracking scroll container's
state (i.e., its scroll contentInset, contentOffset, etc.). Please
ensure that this function is a [worklet](https://docs.swmansion.com/react-native-reanimated/docs/2.x/fundamentals/worklets/).

Since the library uses the `onScroll` prop to handle animations internally and [reanimated
does not currently allow for multiple onScroll handlers](https://github.com/software-mansion/react-native-reanimated/discussions/1763),
you must use this property to track the state of the scroll container's state.

An example is shown below:

```tsx
const scrollHandlerWorklet = (evt: NativeScrollEvent) => {
'worklet';
console.log('offset: ', evt.contentOffset);
};
```
5 changes: 5 additions & 0 deletions example/src/navigation/AppNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
AbsoluteHeaderBlurSurfaceUsageScreen,
ArbitraryYTransitionHeaderUsageScreen,
InvertedUsageScreen,
CustomOnScrollWorkletUsageScreen,
} from '../screens';

const Stack = createNativeStackNavigator<RootStackParamList>();
Expand Down Expand Up @@ -43,5 +44,9 @@ export default () => (
name="ArbitraryYTransitionHeaderUsageScreen"
component={ArbitraryYTransitionHeaderUsageScreen}
/>
<Stack.Screen
name="CustomOnScrollWorkletUsageScreen"
component={CustomOnScrollWorkletUsageScreen}
/>
</Stack.Navigator>
);
6 changes: 6 additions & 0 deletions example/src/navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type RootStackParamList = {
AbsoluteHeaderBlurSurfaceUsageScreen: undefined;
ArbitraryYTransitionHeaderUsageScreen: undefined;
InvertedUsageScreen: undefined;
CustomOnScrollWorkletUsageScreen: undefined;
};

// Overrides the typing for useNavigation in @react-navigation/native to support the internal
Expand Down Expand Up @@ -71,3 +72,8 @@ export type InvertedUsageScreenNavigationProps = NativeStackScreenProps<
RootStackParamList,
'InvertedUsageScreen'
>;

export type CustomOnScrollWorkletUsageScreenNavigationProps = NativeStackScreenProps<
RootStackParamList,
'CustomOnScrollWorkletUsageScreen'
>;
6 changes: 6 additions & 0 deletions example/src/screens/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ const SCREEN_LIST_CONFIG: ScreenConfigItem[] = [
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.',
},
{
name: 'Custom onScroll Worklet Example',
route: 'CustomOnScrollWorkletUsageScreen',
description:
"A simple example with a custom worklet that tracks the scroll container's offset.",
},
];

const HeaderComponent: React.FC<ScrollHeaderProps> = ({ showNavBar }) => {
Expand Down
1 change: 1 addition & 0 deletions example/src/screens/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export { default as SurfaceComponentUsageScreen } from './usage/SurfaceComponent
export { default as TwitterProfileScreen } from './usage/TwitterProfile';
export { default as AbsoluteHeaderBlurSurfaceUsageScreen } from './usage/AbsoluteHeaderBlurSurface';
export { default as ArbitraryYTransitionHeaderUsageScreen } from './usage/ArbitraryYTransitionHeader';
export { default as CustomOnScrollWorkletUsageScreen } from './usage/CustomWorklet';
82 changes: 82 additions & 0 deletions example/src/screens/usage/CustomWorklet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, { useMemo } from 'react';
import { NativeScrollEvent, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useNavigation } from '@react-navigation/native';
import { Header, LargeHeader, 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 { CustomOnScrollWorkletUsageScreenNavigationProps } from '../../navigation';

const HeaderComponent: React.FC<ScrollHeaderProps> = ({ showNavBar }) => {
const navigation = useNavigation();
const onPressProfile = () => navigation.navigate('Profile');

return (
<Header
showNavBar={showNavBar}
headerCenterFadesIn={false}
headerCenter={
<Text style={styles.navBarTitle} numberOfLines={1}>
Header
</Text>
}
headerRight={
<TouchableOpacity onPress={onPressProfile}>
<Avatar size="sm" source={{ uri: `https://i.pravatar.cc/128?img=${RANDOM_IMAGE_NUM}` }} />
</TouchableOpacity>
}
headerRightFadesIn
headerLeft={<BackButton />}
/>
);
};

const LargeHeaderComponent: React.FC<ScrollLargeHeaderProps> = () => {
const navigation = useNavigation();
const onPressProfile = () => navigation.navigate('Profile');

return (
<LargeHeader headerStyle={styles.largeHeaderStyle}>
<TouchableOpacity onPress={onPressProfile}>
<Avatar size="sm" source={{ uri: `https://i.pravatar.cc/128?img=${RANDOM_IMAGE_NUM}` }} />
</TouchableOpacity>
</LargeHeader>
);
};

const CustomWorklet: React.FC<CustomOnScrollWorkletUsageScreenNavigationProps> = () => {
const { bottom } = useSafeAreaInsets();

const data = useMemo(() => range({ end: 100 }), []);

// Example worklet that can be used to track the scroll container's state.
const scrollHandlerWorklet = (evt: NativeScrollEvent) => {
'worklet';
console.log('offset: ', evt.contentOffset);
};

return (
<ScrollViewWithHeaders
HeaderComponent={HeaderComponent}
LargeHeaderComponent={LargeHeaderComponent}
contentContainerStyle={{ paddingBottom: bottom }}
onScrollWorklet={scrollHandlerWorklet}
>
<View style={styles.children}>
{data.map((i) => (
<Text key={`text-${i}`}>Scroll to see header animation</Text>
))}
</View>
</ScrollViewWithHeaders>
);
};

export default CustomWorklet;

const styles = StyleSheet.create({
children: { marginTop: 16, paddingHorizontal: 16 },
navBarTitle: { fontSize: 16, fontWeight: 'bold' },
largeHeaderStyle: { flexDirection: 'row-reverse' },
});
17 changes: 14 additions & 3 deletions src/components/containers/FlashList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const AnimatedFlashList = Animated.createAnimatedComponent(FlashList) as React.C
unknown
>;

type FlashListWithHeadersProps<ItemT> = Omit<AnimatedFlashListType<ItemT>, 'onScroll'>;

const FlashListWithHeadersInputComp = <ItemT extends any = any>(
{
largeHeaderShown,
Expand All @@ -28,12 +30,14 @@ const FlashListWithHeadersInputComp = <ItemT extends any = any>(
onLargeHeaderLayout,
onScrollBeginDrag,
onScrollEndDrag,
onScrollWorklet,
onMomentumScrollBegin,
onMomentumScrollEnd,
ignoreLeftSafeArea,
ignoreRightSafeArea,
disableAutoFixScroll = false,
/** At the moment, we will not allow overriding of this since the scrollHandler needs it. */
// We use this to ensure that the onScroll property isn't accidentally used.
// @ts-ignore
onScroll: _unusedOnScroll,
absoluteHeader = false,
initialAbsoluteHeaderHeight = 0,
Expand All @@ -44,9 +48,15 @@ const FlashListWithHeadersInputComp = <ItemT extends any = any>(
scrollIndicatorInsets = {},
inverted,
...rest
}: AnimatedFlashListType<ItemT>,
}: FlashListWithHeadersProps<ItemT>,
ref: React.Ref<FlashList<ItemT>>
) => {
if (_unusedOnScroll) {
throw new Error(
"The 'onScroll' property is not supported. Please use onScrollWorklet to track the scroll container's state."
);
}

const insets = useSafeAreaInsets();
const scrollRef = useAnimatedRef<any>();
useImperativeHandle(ref, () => scrollRef.current);
Expand All @@ -69,6 +79,7 @@ const FlashListWithHeadersInputComp = <ItemT extends any = any>(
initialAbsoluteHeaderHeight,
headerFadeInThreshold,
inverted: !!inverted,
onScrollWorklet,
});

return (
Expand Down Expand Up @@ -154,7 +165,7 @@ const FlashListWithHeadersInputComp = <ItemT extends any = any>(

// The typecast is needed to make the component generic.
const FlashListWithHeaders = React.forwardRef(FlashListWithHeadersInputComp) as <ItemT = any>(
props: AnimatedFlashListType<ItemT> & { ref?: React.Ref<FlashList<ItemT>> }
props: FlashListWithHeadersProps<ItemT> & { ref?: React.Ref<FlashList<ItemT>> }
) => React.ReactElement;

export default FlashListWithHeaders;
Expand Down
21 changes: 17 additions & 4 deletions src/components/containers/FlatList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ type AnimatedFlatListType<ItemT = any> = React.ComponentClass<
type AnimatedFlatListBaseProps<ItemT> = React.ComponentProps<AnimatedFlatListType<ItemT>>;
type AnimatedFlatListProps<ItemT> = AnimatedFlatListBaseProps<ItemT>;

type FlatListWithHeadersProps<ItemT> = Omit<
AnimatedFlatListProps<ItemT> & SharedScrollContainerProps,
'onScroll'
>;

const FlatListWithHeadersInputComp = <ItemT extends unknown>(
{
largeHeaderShown,
Expand All @@ -24,12 +29,14 @@ const FlatListWithHeadersInputComp = <ItemT extends unknown>(
onLargeHeaderLayout,
onScrollBeginDrag,
onScrollEndDrag,
onScrollWorklet,
onMomentumScrollBegin,
onMomentumScrollEnd,
ignoreLeftSafeArea,
ignoreRightSafeArea,
disableAutoFixScroll = false,
/** At the moment, we will not allow overriding of this since the scrollHandler needs it. */
// We use this to ensure that the onScroll property isn't accidentally used.
// @ts-ignore
onScroll: _unusedOnScroll,
absoluteHeader = false,
initialAbsoluteHeaderHeight = 0,
Expand All @@ -40,9 +47,15 @@ const FlatListWithHeadersInputComp = <ItemT extends unknown>(
scrollIndicatorInsets = {},
inverted,
...rest
}: AnimatedFlatListProps<ItemT> & SharedScrollContainerProps,
}: FlatListWithHeadersProps<ItemT>,
ref: React.Ref<Animated.FlatList<ItemT> | null>
) => {
if (_unusedOnScroll) {
throw new Error(
"The 'onScroll' property is not supported. Please use onScrollWorklet to track the scroll container's state."
);
}

const insets = useSafeAreaInsets();
const scrollRef = useAnimatedRef<Animated.FlatList<ItemT>>();
useImperativeHandle(ref, () => scrollRef.current);
Expand All @@ -65,6 +78,7 @@ const FlatListWithHeadersInputComp = <ItemT extends unknown>(
initialAbsoluteHeaderHeight,
headerFadeInThreshold,
inverted: !!inverted,
onScrollWorklet,
});

return (
Expand Down Expand Up @@ -150,8 +164,7 @@ const FlatListWithHeadersInputComp = <ItemT extends unknown>(

// The typecast is needed to make the component generic.
const FlatListWithHeaders = React.forwardRef(FlatListWithHeadersInputComp) as <ItemT = any>(
props: AnimatedFlatListProps<ItemT> &
SharedScrollContainerProps & { ref?: React.Ref<Animated.FlatList<ItemT>> }
props: FlatListWithHeadersProps<ItemT> & { ref?: React.Ref<Animated.FlatList<ItemT>> }
) => React.ReactElement;

export default FlatListWithHeaders;
Expand Down
Loading