Skip to content

Commit 004ce4e

Browse files
authored
feat: added SectionList support (#2)
1 parent e5f96be commit 004ce4e

File tree

8 files changed

+263
-2
lines changed

8 files changed

+263
-2
lines changed

example/src/navigation/AppNavigation.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import React from 'react';
22
import { createNativeStackNavigator } from '@react-navigation/native-stack';
33
import type { RootStackParamList } from './types';
4-
import { FlatListUsageScreen, HomeScreen, ProfileScreen, SimpleUsageScreen } from '../screens';
4+
import {
5+
FlatListUsageScreen,
6+
HomeScreen,
7+
ProfileScreen,
8+
SectionListUsageScreen,
9+
SimpleUsageScreen,
10+
} from '../screens';
511

612
const Stack = createNativeStackNavigator<RootStackParamList>();
713

@@ -15,5 +21,6 @@ export default () => (
1521
/>
1622
<Stack.Screen name="SimpleUsageScreen" component={SimpleUsageScreen} />
1723
<Stack.Screen name="FlatListUsageScreen" component={FlatListUsageScreen} />
24+
<Stack.Screen name="SectionListUsageScreen" component={SectionListUsageScreen} />
1825
</Stack.Navigator>
1926
);

example/src/navigation/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export type RootStackParamList = {
66
Profile: undefined;
77
SimpleUsageScreen: undefined;
88
FlatListUsageScreen: undefined;
9+
SectionListUsageScreen: undefined;
910
};
1011

1112
// Overrides the typing for useNavigation in @react-navigation/native to support the internal
@@ -29,3 +30,8 @@ export type FlatListUsageScreenNavigationProps = NativeStackScreenProps<
2930
RootStackParamList,
3031
'FlatListUsageScreen'
3132
>;
33+
34+
export type SectionListUsageScreenNavigationProps = NativeStackScreenProps<
35+
RootStackParamList,
36+
'SectionListUsageScreen'
37+
>;

example/src/screens/Home.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ const SCREEN_LIST_CONFIG: ScreenConfigItem[] = [
2828
route: 'FlatListUsageScreen',
2929
description: "A simple example of the library's FlatList.",
3030
},
31+
{
32+
name: 'SectionList Example',
33+
route: 'SectionListUsageScreen',
34+
description: "A simple example of the library's SectionList.",
35+
},
3136
];
3237

3338
const HeaderComponent: React.FC<ScrollHeaderProps> = ({ showNavBar }) => {

example/src/screens/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export { default as ProfileScreen } from './Profile';
44
// Usage screens
55
export { default as SimpleUsageScreen } from './usage/Simple';
66
export { default as FlatListUsageScreen } from './usage/FlatList';
7+
export { default as SectionListUsageScreen } from './usage/SectionList';
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import React, { useCallback } from 'react';
2+
import { SectionListRenderItem, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
3+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
4+
import { useNavigation } from '@react-navigation/native';
5+
import {
6+
Header,
7+
LargeHeader,
8+
ScalingView,
9+
ScrollHeaderProps,
10+
ScrollLargeHeaderProps,
11+
SectionListWithHeaders,
12+
} from '@codeherence/react-native-header';
13+
import { Avatar, BackButton } from '../../components';
14+
import { RANDOM_IMAGE_NUM } from '../../constants';
15+
import type { SectionListUsageScreenNavigationProps } from '../../navigation';
16+
17+
const DATA = [
18+
{
19+
title: 'Main dishes',
20+
data: ['Pizza', 'Burger', 'Risotto', 'Pasta', 'Lasagna'],
21+
},
22+
{
23+
title: 'Sides',
24+
data: ['French Fries', 'Onion Rings', 'Fried Shrimps', 'Mozzarella Sticks', 'Garlic Bread'],
25+
},
26+
{
27+
title: 'Drinks',
28+
data: ['Water', 'Coke', 'Beer', 'Wine', 'Mojito', 'Cuba Libre', 'Pina Colada', 'Margarita'],
29+
},
30+
{
31+
title: 'Desserts',
32+
data: ['Cheese Cake', 'Ice Cream', 'Chocolate Cake', 'Tiramisu', 'Panna Cotta', 'Profiteroles'],
33+
},
34+
];
35+
36+
const HeaderComponent: React.FC<ScrollHeaderProps> = ({ showNavBar }) => {
37+
const navigation = useNavigation();
38+
const onPressProfile = () => navigation.navigate('Profile');
39+
40+
return (
41+
<Header
42+
showNavBar={showNavBar}
43+
headerCenter={
44+
<Text style={styles.navBarTitle} numberOfLines={1}>
45+
Header
46+
</Text>
47+
}
48+
headerRight={
49+
<>
50+
<TouchableOpacity onPress={onPressProfile}>
51+
<Avatar
52+
size="sm"
53+
source={{ uri: `https://i.pravatar.cc/128?img=${RANDOM_IMAGE_NUM}` }}
54+
/>
55+
</TouchableOpacity>
56+
</>
57+
}
58+
headerRightFadesIn
59+
headerLeft={<BackButton />}
60+
/>
61+
);
62+
};
63+
64+
const LargeHeaderComponent: React.FC<ScrollLargeHeaderProps> = ({ scrollY }) => {
65+
const navigation = useNavigation();
66+
const onPressProfile = () => navigation.navigate('Profile');
67+
68+
return (
69+
<LargeHeader>
70+
<ScalingView scrollY={scrollY} style={styles.leftHeader}>
71+
<Text style={styles.title}>Large Header</Text>
72+
</ScalingView>
73+
<TouchableOpacity onPress={onPressProfile}>
74+
<Avatar size="sm" source={{ uri: `https://i.pravatar.cc/128?img=${RANDOM_IMAGE_NUM}` }} />
75+
</TouchableOpacity>
76+
</LargeHeader>
77+
);
78+
};
79+
80+
// Used for SectionList optimization
81+
const ITEM_HEIGHT = 60;
82+
83+
const SectionList: React.FC<SectionListUsageScreenNavigationProps> = () => {
84+
const { bottom } = useSafeAreaInsets();
85+
86+
const renderItem: SectionListRenderItem<string> = useCallback(({ item }) => {
87+
return (
88+
<View style={styles.item}>
89+
<Text style={styles.itemText}>{item}. Scroll to see header animation</Text>
90+
</View>
91+
);
92+
}, []);
93+
94+
return (
95+
<SectionListWithHeaders
96+
HeaderComponent={HeaderComponent}
97+
LargeHeaderComponent={LargeHeaderComponent}
98+
contentContainerStyle={{ paddingBottom: bottom }}
99+
sections={DATA}
100+
renderItem={renderItem}
101+
renderSectionHeader={({ section: { title } }) => (
102+
<Text style={styles.sectionTitle}>{title}</Text>
103+
)}
104+
windowSize={10}
105+
getItemLayout={(_, index) => ({ index, length: ITEM_HEIGHT, offset: index * ITEM_HEIGHT })}
106+
initialNumToRender={50}
107+
maxToRenderPerBatch={100}
108+
disableAutoFixScroll
109+
keyExtractor={(_, i) => `text-row-${i}`}
110+
/>
111+
);
112+
};
113+
114+
export default SectionList;
115+
116+
const styles = StyleSheet.create({
117+
navBarTitle: { fontSize: 16, fontWeight: 'bold' },
118+
title: { fontSize: 32, fontWeight: 'bold' },
119+
sectionTitle: {
120+
fontSize: 18,
121+
fontWeight: 'bold',
122+
width: '100%',
123+
backgroundColor: '#E6E6E6',
124+
paddingHorizontal: 12,
125+
paddingVertical: 4,
126+
},
127+
leftHeader: { gap: 2 },
128+
item: { minHeight: ITEM_HEIGHT, padding: 16, justifyContent: 'center', alignItems: 'center' },
129+
itemText: { textAlign: 'center' },
130+
});
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import React from 'react';
2+
import { View, SectionList, StyleSheet, SectionListProps } from 'react-native';
3+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
4+
import Animated, { useAnimatedRef, useScrollViewOffset } from 'react-native-reanimated';
5+
6+
import FadingView from '../containers/FadingView';
7+
import { useScrollContainerLogic } from './useScrollContainerLogic';
8+
import type { SharedScrollContainerProps } from './types';
9+
import type { DefaultSectionT } from 'react-native';
10+
11+
type AnimatedSectionListType<ItemT, SectionT = DefaultSectionT> = React.ComponentProps<
12+
React.ComponentClass<Animated.AnimateProps<SectionListProps<ItemT, SectionT>>, any>
13+
> &
14+
SharedScrollContainerProps;
15+
16+
const AnimatedSectionList = Animated.createAnimatedComponent(SectionList) as React.ComponentClass<
17+
Animated.AnimateProps<SectionListProps<any, any>>,
18+
any
19+
>;
20+
21+
const AnimatedSectionListWithHeaders = <ItemT = any, SectionT = DefaultSectionT>({
22+
largeHeaderShown,
23+
containerStyle,
24+
LargeHeaderComponent,
25+
largeHeaderContainerStyle,
26+
HeaderComponent,
27+
onLargeHeaderLayout,
28+
onScrollBeginDrag,
29+
onScrollEndDrag,
30+
onMomentumScrollBegin,
31+
onMomentumScrollEnd,
32+
ignoreLeftSafeArea,
33+
ignoreRightSafeArea,
34+
disableAutoFixScroll,
35+
...rest
36+
}: AnimatedSectionListType<ItemT, SectionT>) => {
37+
const insets = useSafeAreaInsets();
38+
const scrollRef = useAnimatedRef<any>();
39+
// Need to use `any` here because useScrollViewOffset is not typed for Animated.SectionList
40+
const scrollY = useScrollViewOffset(scrollRef as any);
41+
42+
const { showNavBar, largeHeaderHeight, largeHeaderOpacity, debouncedFixScroll } =
43+
useScrollContainerLogic({
44+
scrollRef,
45+
scrollY,
46+
largeHeaderShown,
47+
disableAutoFixScroll,
48+
largeHeaderExists: !!LargeHeaderComponent,
49+
});
50+
51+
return (
52+
<View
53+
style={[
54+
styles.container,
55+
containerStyle,
56+
!ignoreLeftSafeArea && { paddingLeft: insets.left },
57+
!ignoreRightSafeArea && { paddingRight: insets.right },
58+
]}
59+
>
60+
{HeaderComponent({ showNavBar })}
61+
<AnimatedSectionList
62+
ref={scrollRef}
63+
scrollEventThrottle={16}
64+
overScrollMode="auto"
65+
automaticallyAdjustContentInsets={false}
66+
onScrollBeginDrag={(e) => {
67+
debouncedFixScroll.cancel();
68+
if (onScrollBeginDrag) onScrollBeginDrag(e);
69+
}}
70+
onScrollEndDrag={(e) => {
71+
debouncedFixScroll();
72+
if (onScrollEndDrag) onScrollEndDrag(e);
73+
}}
74+
onMomentumScrollBegin={(e) => {
75+
debouncedFixScroll.cancel();
76+
if (onMomentumScrollBegin) onMomentumScrollBegin(e);
77+
}}
78+
onMomentumScrollEnd={(e) => {
79+
debouncedFixScroll();
80+
if (onMomentumScrollEnd) onMomentumScrollEnd(e);
81+
}}
82+
ListHeaderComponent={
83+
LargeHeaderComponent ? (
84+
<View
85+
onLayout={(e) => {
86+
largeHeaderHeight.value = e.nativeEvent.layout.height;
87+
88+
if (onLargeHeaderLayout) onLargeHeaderLayout(e.nativeEvent.layout);
89+
}}
90+
>
91+
<FadingView opacity={largeHeaderOpacity} style={largeHeaderContainerStyle}>
92+
{LargeHeaderComponent({ scrollY, showNavBar })}
93+
</FadingView>
94+
</View>
95+
) : undefined
96+
}
97+
{...rest}
98+
/>
99+
</View>
100+
);
101+
};
102+
103+
export default AnimatedSectionListWithHeaders;
104+
105+
const styles = StyleSheet.create({ container: { flex: 1 } });

src/components/containers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export { default as ScalingView } from './ScalingView';
22
export { default as FadingView } from './FadingView';
33
export { default as ScrollViewWithHeaders } from './ScrollView';
44
export { default as FlatListWithHeaders } from './FlatList';
5+
export { default as SectionListWithHeaders } from './SectionList';
56
export type {
67
ScrollHeaderProps,
78
ScrollLargeHeaderProps,

src/components/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
export { ScrollViewWithHeaders, FlatListWithHeaders, ScalingView, FadingView } from './containers';
1+
export {
2+
FadingView,
3+
ScalingView,
4+
ScrollViewWithHeaders,
5+
FlatListWithHeaders,
6+
SectionListWithHeaders,
7+
} from './containers';
28
export type {
39
ScrollHeaderProps,
410
ScrollLargeHeaderProps,

0 commit comments

Comments
 (0)