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
2 changes: 1 addition & 1 deletion docs/docs/01-getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
152 changes: 152 additions & 0 deletions docs/docs/03-api-reference/05-masonry-flash-list-with-headers.mdx
Original file line number Diff line number Diff line change
@@ -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);
};
```
2 changes: 2 additions & 0 deletions example/src/navigation/AppNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack';
import type { RootStackParamList } from './types';
import {
FlashListUsageScreen,
MasonryFlashListUsageScreen,
FlatListUsageScreen,
HomeScreen,
ProfileScreen,
Expand All @@ -29,6 +30,7 @@ export default () => (
<Stack.Screen name="SimpleUsageScreen" component={SimpleUsageScreen} />
<Stack.Screen name="FlatListUsageScreen" component={FlatListUsageScreen} />
<Stack.Screen name="FlashListUsageScreen" component={FlashListUsageScreen} />
<Stack.Screen name="MasonryFlashListUsageScreen" component={MasonryFlashListUsageScreen} />
<Stack.Screen name="SectionListUsageScreen" component={SectionListUsageScreen} />
<Stack.Screen name="InvertedUsageScreen" component={InvertedUsageScreen} />
<Stack.Screen
Expand Down
6 changes: 6 additions & 0 deletions example/src/navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type RootStackParamList = {
SimpleUsageScreen: undefined;
FlatListUsageScreen: undefined;
FlashListUsageScreen: undefined;
MasonryFlashListUsageScreen: undefined;
SectionListUsageScreen: undefined;
TwitterProfileScreen: undefined;
HeaderSurfaceComponentUsageScreen: undefined;
Expand Down Expand Up @@ -43,6 +44,11 @@ export type FlashListUsageScreenNavigationProps = NativeStackScreenProps<
'FlashListUsageScreen'
>;

export type MasonryFlashListUsageScreenNavigationProps = NativeStackScreenProps<
RootStackParamList,
'MasonryFlashListUsageScreen'
>;

export type SectionListUsageScreenNavigationProps = NativeStackScreenProps<
RootStackParamList,
'SectionListUsageScreen'
Expand Down
5 changes: 5 additions & 0 deletions example/src/screens/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
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 @@ -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';
Expand Down
114 changes: 114 additions & 0 deletions example/src/screens/usage/MasonryFlashList.tsx
Original file line number Diff line number Diff line change
@@ -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<ScrollHeaderProps> = ({ showNavBar }) => {
const navigation = useNavigation();
const onPressProfile = () => navigation.navigate('Profile');

return (
<Header
showNavBar={showNavBar}
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> = ({ scrollY }) => {
const navigation = useNavigation();
const onPressProfile = () => navigation.navigate('Profile');

return (
<LargeHeader>
<ScalingView scrollY={scrollY} style={styles.leftHeader}>
<Text style={styles.title}>Large Header</Text>
</ScalingView>
<TouchableOpacity onPress={onPressProfile}>
<Avatar size="sm" source={{ uri: `https://i.pravatar.cc/128?img=${RANDOM_IMAGE_NUM}` }} />
</TouchableOpacity>
</LargeHeader>
);
};

// Used for FlashList optimization
const ITEM_HEIGHT = 200;

const MasonryFlashListExample: React.FC<MasonryFlashListUsageScreenNavigationProps> = () => {
const { bottom } = useSafeAreaInsets();
const ref = useRef<MasonryFlashListRef<number>>(null);

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

const renderItem: ListRenderItem<number> = useCallback(({ item, index }) => {
const randomHeights = [100, 150, 200, 250];
const randomHeight = randomHeights[index % randomHeights.length];
return (
<View style={styles.item}>
<Image
source={`https://picsum.photos/150/${randomHeight}`}
style={{ ...styles.image, height: randomHeight }}
/>
<Text style={styles.itemText}>{item}</Text>
</View>
);
}, []);

return (
<MasonryFlashListWithHeaders
ref={ref}
HeaderComponent={HeaderComponent}
LargeHeaderComponent={LargeHeaderComponent}
contentContainerStyle={{ paddingBottom: bottom }}
data={data}
numColumns={2}
renderItem={renderItem}
estimatedItemSize={ITEM_HEIGHT}
estimatedListSize={{ height: Math.min(ITEM_HEIGHT * data.length, dHeight), width: dWidth }}
keyExtractor={(_, i) => `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' },
});
Loading