diff --git a/docs/docs/01-getting-started.mdx b/docs/docs/01-getting-started.mdx
index e8b4cb9..8fb8815 100644
--- a/docs/docs/01-getting-started.mdx
+++ b/docs/docs/01-getting-started.mdx
@@ -32,7 +32,7 @@ Before you can use `react-native-header`, you need to have the following librari
If you haven't installed these libraries yet, please follow the installation instructions on their respective documentation pages.
-If you intend to use the [FlashListWithHeaders](/docs/components/flash-list-with-headers) component, please ensure that you review the [Compatibilty table](/docs/getting-started#compatibility) above and install the correct versions of each library.
+If you intend to use the [FlashListWithHeaders](/docs/components/flash-list-with-headers) or [MasonryFlashListWithHeaders](/docs/components/masonry-flash-list-with-headers) component, please ensure that you review the [Compatibilty table](/docs/getting-started#compatibility) above and install the correct versions of each library.
## Installation
diff --git a/docs/docs/03-api-reference/05-masonry-flash-list-with-headers.mdx b/docs/docs/03-api-reference/05-masonry-flash-list-with-headers.mdx
new file mode 100644
index 0000000..cc3a195
--- /dev/null
+++ b/docs/docs/03-api-reference/05-masonry-flash-list-with-headers.mdx
@@ -0,0 +1,152 @@
+---
+title: MasonryFlashListWithHeaders
+hide_table_of_contents: false
+slug: /components/masonry-flash-list-with-headers
+description: Shopify's MasonryFlashList paired with React Native Header.
+---
+
+Component that extends Shopify's [MasonryFlashListFlashList](https://shopify.github.io/flash-list/docs/guides/masonry) to add support for
+headers exported from this library.
+
+The implementation of this component relies on the [HeaderComponent](/docs/components/flash-list-with-headers#headercomponent)
+and [LargeHeaderComponent](/docs/components/flash-list-with-headers#largeheadercomponent) props.
+The [HeaderComponent](/docs/components/flash-list-with-headers#headercomponent) is rendered above
+the MasonryFlashList and the [LargeHeaderComponent](/docs/components/flash-list-with-headers#largeheadercomponent)
+is rendered as the `ListHeaderComponent` of the MasonryFlashList. Using these two props will allow for
+animations/built-in features in this library to work properly.
+
+## Note
+
+This component is only available in react-native-header version >= `0.14.x`. Please review the [Compatibility matrix](/docs/getting-started#compatibility) and ensure
+you have the correct dependencies installed in your project before using this component.
+
+## Props
+
+This component uses the MasonryFlashList under the hood, which inherits [all of the props
+from the MasonryFlashList component](https://shopify.github.io/flash-list/docs/guides/masonry).
+
+### HeaderComponent
+
+The component to render above the MasonryFlashList. This accepts a function that returns a React Element
+to display as the header. The function will be called with the following arguments:
+
+- `showNavBar`: An animated value that will be 0 when the header's subcomponents should be hidden
+ and 1 when they should be shown. This is useful for animating the header's subcomponents. The
+ [Header](/docs/components/header) component uses this value to animate its left, center, and
+ right children.
+
+### LargeHeaderComponent
+
+An optional component to render as the large header for this component. This accepts a function
+that returns a React Element to display as the large header. The function will be called with the
+following arguments:
+
+- `scrollY`: An animated value that keeps track of the current scroll position of the MasonryFlashList.
+ This prop is useful for creating custom animations on the large header. In our [example](/docs/example),
+ we use the [ScalingView](/docs/components/scaling-view) component to scale the large header
+ when the user pulls down on the MasonryFlashList (to mimic native iOS behaviour).
+- `showNavBar`: An animated value that keeps track of whether or not the small header is hidden.
+ This prop is useful if you want to create your own custom animations based on whether or not the
+ small header is hidden.
+
+### LargeHeaderSubtitleComponent
+
+An optional component to render as a subtitle for the large header for this component. This accepts a function
+that returns a React Element to display as the large header subtitle. The function will be called with the
+following arguments:
+
+- `scrollY`: An animated value that keeps track of the current scroll position of the MasonryFlashList.
+ This prop is useful for creating custom animations on the large header. In our [example](/docs/example),
+ we use the [ScalingView](/docs/components/scaling-view) component to scale the large header
+ when the user pulls down on the FlatList (to mimic native iOS behaviour).
+- `showNavBar`: An animated value that keeps track of whether or not the small header is hidden.
+ This prop is useful if you want to create your own custom animations based on whether or not the
+ small header is hidden.
+
+### ignoreLeftSafeArea
+
+An optional boolean that determines whether or not to ignore the left safe area. Defaults to
+`false`. The safe area adjustments are used to make sure that the scroll container does not
+overlap with the notch/headers on different phones - leave this prop as false if you want to
+respect those safe areas.
+
+### ignoreRightSafeArea
+
+An optional boolean that determines whether or not to ignore the right safe area. Defaults to
+`false`. The safe area adjustments are used to make sure that the scroll container does not
+overlap with the notch/headers on different phones - leave this prop as `false` if you want to
+respect those safe areas.
+
+### disableAutoFixScroll
+
+An optional to disable the auto fix scroll mechanism. This is useful if you want to disable the
+auto scroll when the large header is partially visible. Defaults to `false`.
+
+### containerStyle
+
+An optional style object that will be applied to the parent container of the scroll container.
+
+### largeHeaderContainerStyle
+
+An optional style object that will be applied to the large header container.
+
+### largeHeaderShown
+
+An optional animated [Shared Value](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/shared-values/)
+that will be mutated by the library when the large header is shown or hidden. This is useful if you
+would like to track when the large header is shown or hidden.
+
+### onLargeHeaderLayout
+
+An optional callback that will be called when the large header is laid out. This is useful if you
+want to access the layout of the large header to calculate the height of the large header.
+
+### absoluteHeader
+
+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.
+
+### 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);
+};
+```
diff --git a/docs/docs/03-api-reference/05-header.mdx b/docs/docs/03-api-reference/06-header.mdx
similarity index 100%
rename from docs/docs/03-api-reference/05-header.mdx
rename to docs/docs/03-api-reference/06-header.mdx
diff --git a/docs/docs/03-api-reference/06-large-header.mdx b/docs/docs/03-api-reference/07-large-header.mdx
similarity index 100%
rename from docs/docs/03-api-reference/06-large-header.mdx
rename to docs/docs/03-api-reference/07-large-header.mdx
diff --git a/docs/docs/03-api-reference/07-fading-view.mdx b/docs/docs/03-api-reference/08-fading-view.mdx
similarity index 100%
rename from docs/docs/03-api-reference/07-fading-view.mdx
rename to docs/docs/03-api-reference/08-fading-view.mdx
diff --git a/docs/docs/03-api-reference/08-scaling-view.mdx b/docs/docs/03-api-reference/09-scaling-view.mdx
similarity index 100%
rename from docs/docs/03-api-reference/08-scaling-view.mdx
rename to docs/docs/03-api-reference/09-scaling-view.mdx
diff --git a/example/src/navigation/AppNavigation.tsx b/example/src/navigation/AppNavigation.tsx
index 5de322f..9102025 100644
--- a/example/src/navigation/AppNavigation.tsx
+++ b/example/src/navigation/AppNavigation.tsx
@@ -3,6 +3,7 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack';
import type { RootStackParamList } from './types';
import {
FlashListUsageScreen,
+ MasonryFlashListUsageScreen,
FlatListUsageScreen,
HomeScreen,
ProfileScreen,
@@ -29,6 +30,7 @@ export default () => (
+
;
+export type MasonryFlashListUsageScreenNavigationProps = NativeStackScreenProps<
+ RootStackParamList,
+ 'MasonryFlashListUsageScreen'
+>;
+
export type SectionListUsageScreenNavigationProps = NativeStackScreenProps<
RootStackParamList,
'SectionListUsageScreen'
diff --git a/example/src/screens/Home.tsx b/example/src/screens/Home.tsx
index a678db7..0734933 100644
--- a/example/src/screens/Home.tsx
+++ b/example/src/screens/Home.tsx
@@ -33,6 +33,11 @@ const SCREEN_LIST_CONFIG: ScreenConfigItem[] = [
route: 'FlashListUsageScreen',
description: "A simple example with Shopify's FlashList.",
},
+ {
+ name: 'MasonryFlashList Example',
+ route: 'MasonryFlashListUsageScreen',
+ description: "A simple example with Shopify's MasonryFlashList.",
+ },
{
name: 'SectionList Example',
route: 'SectionListUsageScreen',
diff --git a/example/src/screens/index.ts b/example/src/screens/index.ts
index 7a6f865..a252906 100644
--- a/example/src/screens/index.ts
+++ b/example/src/screens/index.ts
@@ -5,6 +5,7 @@ export { default as ProfileScreen } from './Profile';
export { default as SimpleUsageScreen } from './usage/Simple';
export { default as FlatListUsageScreen } from './usage/FlatList';
export { default as FlashListUsageScreen } from './usage/FlashList';
+export { default as MasonryFlashListUsageScreen } from './usage/MasonryFlashList';
export { default as SectionListUsageScreen } from './usage/SectionList';
export { default as InvertedUsageScreen } from './usage/Inverted';
export { default as SurfaceComponentUsageScreen } from './usage/SurfaceComponent';
diff --git a/example/src/screens/usage/MasonryFlashList.tsx b/example/src/screens/usage/MasonryFlashList.tsx
new file mode 100644
index 0000000..419b8ea
--- /dev/null
+++ b/example/src/screens/usage/MasonryFlashList.tsx
@@ -0,0 +1,114 @@
+import React, { useCallback, useMemo, useRef } from 'react';
+import { Dimensions, 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,
+ ScrollHeaderProps,
+ ScrollLargeHeaderProps,
+ MasonryFlashListWithHeaders,
+} from '@codeherence/react-native-header';
+import { range } from '../../utils';
+import { Avatar, BackButton } from '../../components';
+import { RANDOM_IMAGE_NUM } from '../../constants';
+import type { MasonryFlashListUsageScreenNavigationProps } from '../../navigation';
+import type { ListRenderItem, MasonryFlashListRef } from '@shopify/flash-list';
+import { Image } from 'expo-image';
+
+const { width: dWidth, height: dHeight } = Dimensions.get('window');
+
+const HeaderComponent: React.FC = ({ showNavBar }) => {
+ const navigation = useNavigation();
+ const onPressProfile = () => navigation.navigate('Profile');
+
+ return (
+
+ Header
+
+ }
+ headerRight={
+ <>
+
+
+
+ >
+ }
+ headerRightFadesIn
+ headerLeft={}
+ />
+ );
+};
+
+const LargeHeaderComponent: React.FC = ({ scrollY }) => {
+ const navigation = useNavigation();
+ const onPressProfile = () => navigation.navigate('Profile');
+
+ return (
+
+
+ Large Header
+
+
+
+
+
+ );
+};
+
+// Used for FlashList optimization
+const ITEM_HEIGHT = 200;
+
+const MasonryFlashListExample: React.FC = () => {
+ const { bottom } = useSafeAreaInsets();
+ const ref = useRef>(null);
+
+ const data = useMemo(() => range({ end: 500 }), []);
+
+ const renderItem: ListRenderItem = useCallback(({ item, index }) => {
+ const randomHeights = [100, 150, 200, 250];
+ const randomHeight = randomHeights[index % randomHeights.length];
+ return (
+
+
+ {item}
+
+ );
+ }, []);
+
+ return (
+ `text-row-${i}`}
+ />
+ );
+};
+
+export default MasonryFlashListExample;
+
+const styles = StyleSheet.create({
+ navBarTitle: { fontSize: 16, fontWeight: 'bold' },
+ title: { fontSize: 32, fontWeight: 'bold' },
+ leftHeader: { gap: 2 },
+ item: { minHeight: ITEM_HEIGHT, padding: 16, justifyContent: 'center', alignItems: 'center' },
+ image: { width: 150 },
+ itemText: { textAlign: 'center' },
+});
diff --git a/src/components/containers/MasonryFlashList.tsx b/src/components/containers/MasonryFlashList.tsx
new file mode 100644
index 0000000..b22b1e7
--- /dev/null
+++ b/src/components/containers/MasonryFlashList.tsx
@@ -0,0 +1,188 @@
+import React, { useImperativeHandle } from 'react';
+import { StyleSheet, View } from 'react-native';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+import Animated, { useAnimatedRef } from 'react-native-reanimated';
+import { MasonryFlashList, MasonryFlashListProps, MasonryFlashListRef } from '@shopify/flash-list';
+
+import type { SharedScrollContainerProps } from '.';
+import FadingView from './FadingView';
+import { useScrollContainerLogic } from './useScrollContainerLogic';
+
+type AnimatedMasonryFlashListType = React.ComponentProps<
+ React.ComponentClass>, any>
+> &
+ SharedScrollContainerProps;
+
+const AnimatedMasonryFlashList = Animated.createAnimatedComponent(
+ MasonryFlashList
+) as unknown as React.ComponentClass>>;
+
+type MasonryFlashListWithHeadersProps = Omit<
+ AnimatedMasonryFlashListType,
+ 'onScroll'
+>;
+
+const MasonryFlashListWithHeadersInputComp = (
+ {
+ largeHeaderShown,
+ containerStyle,
+ LargeHeaderSubtitleComponent,
+ LargeHeaderComponent,
+ largeHeaderContainerStyle,
+ HeaderComponent,
+ onLargeHeaderLayout,
+ onScrollBeginDrag,
+ onScrollEndDrag,
+ onScrollWorklet,
+ onMomentumScrollBegin,
+ onMomentumScrollEnd,
+ ignoreLeftSafeArea,
+ ignoreRightSafeArea,
+ disableAutoFixScroll = false,
+ // We use this to ensure that the onScroll property isn't accidentally used.
+ // @ts-ignore
+ onScroll: _unusedOnScroll,
+ absoluteHeader = false,
+ initialAbsoluteHeaderHeight = 0,
+ contentContainerStyle = {},
+ automaticallyAdjustsScrollIndicatorInsets,
+ headerFadeInThreshold = 1,
+ disableLargeHeaderFadeAnim = false,
+ scrollIndicatorInsets = {},
+ ...rest
+ }: MasonryFlashListWithHeadersProps,
+ ref: React.Ref
+) => {
+ 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();
+ useImperativeHandle(ref, () => scrollRef.current);
+
+ const {
+ scrollY,
+ showNavBar,
+ largeHeaderHeight,
+ largeHeaderOpacity,
+ scrollHandler,
+ debouncedFixScroll,
+ onAbsoluteHeaderLayout,
+ scrollViewAdjustments,
+ } = useScrollContainerLogic({
+ scrollRef,
+ largeHeaderShown,
+ disableAutoFixScroll,
+ largeHeaderExists: !!LargeHeaderComponent,
+ absoluteHeader,
+ initialAbsoluteHeaderHeight,
+ headerFadeInThreshold,
+ onScrollWorklet,
+ });
+
+ return (
+
+ {!absoluteHeader && HeaderComponent({ showNavBar, scrollY })}
+ {
+ debouncedFixScroll.cancel();
+ if (onScrollBeginDrag) onScrollBeginDrag(e);
+ }}
+ onScrollEndDrag={(e) => {
+ debouncedFixScroll();
+ if (onScrollEndDrag) onScrollEndDrag(e);
+ }}
+ onMomentumScrollBegin={(e) => {
+ debouncedFixScroll.cancel();
+ if (onMomentumScrollBegin) onMomentumScrollBegin(e);
+ }}
+ onMomentumScrollEnd={(e) => {
+ debouncedFixScroll();
+ if (onMomentumScrollEnd) onMomentumScrollEnd(e);
+ }}
+ contentContainerStyle={{
+ // The reason why we do this is because FlashList does not support an array of
+ // styles (will throw a warning when you supply one).
+ ...scrollViewAdjustments.contentContainerStyle,
+ ...contentContainerStyle,
+ }}
+ automaticallyAdjustsScrollIndicatorInsets={
+ automaticallyAdjustsScrollIndicatorInsets !== undefined
+ ? automaticallyAdjustsScrollIndicatorInsets
+ : !absoluteHeader
+ }
+ scrollIndicatorInsets={{
+ ...scrollViewAdjustments.scrollIndicatorInsets,
+ ...scrollIndicatorInsets,
+ }}
+ ListHeaderComponent={
+ <>
+ {LargeHeaderComponent && (
+ {
+ largeHeaderHeight.value = e.nativeEvent.layout.height;
+
+ if (onLargeHeaderLayout) onLargeHeaderLayout(e.nativeEvent.layout);
+ }}
+ >
+ {!disableLargeHeaderFadeAnim ? (
+
+ {LargeHeaderComponent({ scrollY, showNavBar })}
+
+ ) : (
+
+ {LargeHeaderComponent({ scrollY, showNavBar })}
+
+ )}
+
+ )}
+ {LargeHeaderSubtitleComponent && LargeHeaderSubtitleComponent({ showNavBar, scrollY })}
+ >
+ }
+ {...rest}
+ />
+
+ {absoluteHeader && (
+
+ {HeaderComponent({ showNavBar, scrollY })}
+
+ )}
+
+ );
+};
+
+// The typecast is needed to make the component generic.
+const MasonryFlashListWithHeaders = React.forwardRef(MasonryFlashListWithHeadersInputComp) as <
+ ItemT = any
+>(
+ props: MasonryFlashListWithHeadersProps & {
+ ref?: React.RefObject>;
+ }
+) => React.ReactElement;
+
+export default MasonryFlashListWithHeaders;
+
+const styles = StyleSheet.create({
+ container: { flex: 1 },
+ absoluteHeader: {
+ position: 'absolute',
+ top: 0,
+ right: 0,
+ left: 0,
+ },
+});
diff --git a/src/components/containers/index.ts b/src/components/containers/index.ts
index d2885d7..ad8732e 100644
--- a/src/components/containers/index.ts
+++ b/src/components/containers/index.ts
@@ -4,6 +4,7 @@ export { default as ScrollViewWithHeaders } from './ScrollView';
export { default as FlatListWithHeaders } from './FlatList';
export { default as SectionListWithHeaders } from './SectionList';
export { default as FlashListWithHeaders } from './FlashList';
+export { default as MasonryFlashListWithHeaders } from './MasonryFlashList';
export type {
ScrollHeaderProps,
ScrollLargeHeaderProps,
diff --git a/src/components/index.ts b/src/components/index.ts
index 9b10be5..8893f81 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -5,6 +5,7 @@ export {
FlatListWithHeaders,
SectionListWithHeaders,
FlashListWithHeaders,
+ MasonryFlashListWithHeaders,
} from './containers';
export type {
ScrollHeaderProps,
diff --git a/src/index.ts b/src/index.ts
index 86769f2..7229d9c 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -7,6 +7,7 @@ export {
FlatListWithHeaders,
SectionListWithHeaders,
FlashListWithHeaders,
+ MasonryFlashListWithHeaders,
} from './components';
export type {