Skip to content

feat: added SectionList support #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 24, 2023
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
9 changes: 8 additions & 1 deletion example/src/navigation/AppNavigation.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import type { RootStackParamList } from './types';
import { FlatListUsageScreen, HomeScreen, ProfileScreen, SimpleUsageScreen } from '../screens';
import {
FlatListUsageScreen,
HomeScreen,
ProfileScreen,
SectionListUsageScreen,
SimpleUsageScreen,
} from '../screens';

const Stack = createNativeStackNavigator<RootStackParamList>();

Expand All @@ -15,5 +21,6 @@ export default () => (
/>
<Stack.Screen name="SimpleUsageScreen" component={SimpleUsageScreen} />
<Stack.Screen name="FlatListUsageScreen" component={FlatListUsageScreen} />
<Stack.Screen name="SectionListUsageScreen" component={SectionListUsageScreen} />
</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 @@ -6,6 +6,7 @@ export type RootStackParamList = {
Profile: undefined;
SimpleUsageScreen: undefined;
FlatListUsageScreen: undefined;
SectionListUsageScreen: undefined;
};

// Overrides the typing for useNavigation in @react-navigation/native to support the internal
Expand All @@ -29,3 +30,8 @@ export type FlatListUsageScreenNavigationProps = NativeStackScreenProps<
RootStackParamList,
'FlatListUsageScreen'
>;

export type SectionListUsageScreenNavigationProps = NativeStackScreenProps<
RootStackParamList,
'SectionListUsageScreen'
>;
5 changes: 5 additions & 0 deletions example/src/screens/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ const SCREEN_LIST_CONFIG: ScreenConfigItem[] = [
route: 'FlatListUsageScreen',
description: "A simple example of the library's FlatList.",
},
{
name: 'SectionList Example',
route: 'SectionListUsageScreen',
description: "A simple example of the library's SectionList.",
},
];

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 @@ -4,3 +4,4 @@ export { default as ProfileScreen } from './Profile';
// Usage screens
export { default as SimpleUsageScreen } from './usage/Simple';
export { default as FlatListUsageScreen } from './usage/FlatList';
export { default as SectionListUsageScreen } from './usage/SectionList';
130 changes: 130 additions & 0 deletions example/src/screens/usage/SectionList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import React, { useCallback } from 'react';
import { SectionListRenderItem, 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,
SectionListWithHeaders,
} from '@codeherence/react-native-header';
import { Avatar, BackButton } from '../../components';
import { RANDOM_IMAGE_NUM } from '../../constants';
import type { SectionListUsageScreenNavigationProps } from '../../navigation';

const DATA = [
{
title: 'Main dishes',
data: ['Pizza', 'Burger', 'Risotto', 'Pasta', 'Lasagna'],
},
{
title: 'Sides',
data: ['French Fries', 'Onion Rings', 'Fried Shrimps', 'Mozzarella Sticks', 'Garlic Bread'],
},
{
title: 'Drinks',
data: ['Water', 'Coke', 'Beer', 'Wine', 'Mojito', 'Cuba Libre', 'Pina Colada', 'Margarita'],
},
{
title: 'Desserts',
data: ['Cheese Cake', 'Ice Cream', 'Chocolate Cake', 'Tiramisu', 'Panna Cotta', 'Profiteroles'],
},
];

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 SectionList optimization
const ITEM_HEIGHT = 60;

const SectionList: React.FC<SectionListUsageScreenNavigationProps> = () => {
const { bottom } = useSafeAreaInsets();

const renderItem: SectionListRenderItem<string> = useCallback(({ item }) => {
return (
<View style={styles.item}>
<Text style={styles.itemText}>{item}. Scroll to see header animation</Text>
</View>
);
}, []);

return (
<SectionListWithHeaders
HeaderComponent={HeaderComponent}
LargeHeaderComponent={LargeHeaderComponent}
contentContainerStyle={{ paddingBottom: bottom }}
sections={DATA}
renderItem={renderItem}
renderSectionHeader={({ section: { title } }) => (
<Text style={styles.sectionTitle}>{title}</Text>
)}
windowSize={10}
getItemLayout={(_, index) => ({ index, length: ITEM_HEIGHT, offset: index * ITEM_HEIGHT })}
initialNumToRender={50}
maxToRenderPerBatch={100}
disableAutoFixScroll
keyExtractor={(_, i) => `text-row-${i}`}
/>
);
};

export default SectionList;

const styles = StyleSheet.create({
navBarTitle: { fontSize: 16, fontWeight: 'bold' },
title: { fontSize: 32, fontWeight: 'bold' },
sectionTitle: {
fontSize: 18,
fontWeight: 'bold',
width: '100%',
backgroundColor: '#E6E6E6',
paddingHorizontal: 12,
paddingVertical: 4,
},
leftHeader: { gap: 2 },
item: { minHeight: ITEM_HEIGHT, padding: 16, justifyContent: 'center', alignItems: 'center' },
itemText: { textAlign: 'center' },
});
105 changes: 105 additions & 0 deletions src/components/containers/SectionList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React from 'react';
import { View, SectionList, StyleSheet, SectionListProps } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Animated, { useAnimatedRef, useScrollViewOffset } from 'react-native-reanimated';

import FadingView from '../containers/FadingView';
import { useScrollContainerLogic } from './useScrollContainerLogic';
import type { SharedScrollContainerProps } from './types';
import type { DefaultSectionT } from 'react-native';

type AnimatedSectionListType<ItemT, SectionT = DefaultSectionT> = React.ComponentProps<
React.ComponentClass<Animated.AnimateProps<SectionListProps<ItemT, SectionT>>, any>
> &
SharedScrollContainerProps;

const AnimatedSectionList = Animated.createAnimatedComponent(SectionList) as React.ComponentClass<
Animated.AnimateProps<SectionListProps<any, any>>,
any
>;

const AnimatedSectionListWithHeaders = <ItemT = any, SectionT = DefaultSectionT>({
largeHeaderShown,
containerStyle,
LargeHeaderComponent,
largeHeaderContainerStyle,
HeaderComponent,
onLargeHeaderLayout,
onScrollBeginDrag,
onScrollEndDrag,
onMomentumScrollBegin,
onMomentumScrollEnd,
ignoreLeftSafeArea,
ignoreRightSafeArea,
disableAutoFixScroll,
...rest
}: AnimatedSectionListType<ItemT, SectionT>) => {
const insets = useSafeAreaInsets();
const scrollRef = useAnimatedRef<any>();
// Need to use `any` here because useScrollViewOffset is not typed for Animated.SectionList
const scrollY = useScrollViewOffset(scrollRef as any);

const { showNavBar, largeHeaderHeight, largeHeaderOpacity, debouncedFixScroll } =
useScrollContainerLogic({
scrollRef,
scrollY,
largeHeaderShown,
disableAutoFixScroll,
largeHeaderExists: !!LargeHeaderComponent,
});

return (
<View
style={[
styles.container,
containerStyle,
!ignoreLeftSafeArea && { paddingLeft: insets.left },
!ignoreRightSafeArea && { paddingRight: insets.right },
]}
>
{HeaderComponent({ showNavBar })}
<AnimatedSectionList
ref={scrollRef}
scrollEventThrottle={16}
overScrollMode="auto"
automaticallyAdjustContentInsets={false}
onScrollBeginDrag={(e) => {
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);
}}
ListHeaderComponent={
LargeHeaderComponent ? (
<View
onLayout={(e) => {
largeHeaderHeight.value = e.nativeEvent.layout.height;

if (onLargeHeaderLayout) onLargeHeaderLayout(e.nativeEvent.layout);
}}
>
<FadingView opacity={largeHeaderOpacity} style={largeHeaderContainerStyle}>
{LargeHeaderComponent({ scrollY, showNavBar })}
</FadingView>
</View>
) : undefined
}
{...rest}
/>
</View>
);
};

export default AnimatedSectionListWithHeaders;

const styles = StyleSheet.create({ container: { flex: 1 } });
1 change: 1 addition & 0 deletions src/components/containers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { default as ScalingView } from './ScalingView';
export { default as FadingView } from './FadingView';
export { default as ScrollViewWithHeaders } from './ScrollView';
export { default as FlatListWithHeaders } from './FlatList';
export { default as SectionListWithHeaders } from './SectionList';
export type {
ScrollHeaderProps,
ScrollLargeHeaderProps,
Expand Down
8 changes: 7 additions & 1 deletion src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
export { ScrollViewWithHeaders, FlatListWithHeaders, ScalingView, FadingView } from './containers';
export {
FadingView,
ScalingView,
ScrollViewWithHeaders,
FlatListWithHeaders,
SectionListWithHeaders,
} from './containers';
export type {
ScrollHeaderProps,
ScrollLargeHeaderProps,
Expand Down