Skip to content

Commit 0ee68ba

Browse files
authored
feat: better support inverted virtualized lists (#19)
1 parent 3dda369 commit 0ee68ba

File tree

11 files changed

+200
-22
lines changed

11 files changed

+200
-22
lines changed

example/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"web": "expo start --web"
99
},
1010
"dependencies": {
11+
"@ngneat/falso": "^6.4.0",
1112
"@react-native-community/blur": "^4.3.0",
1213
"@react-navigation/native": "^6.1.6",
1314
"@react-navigation/native-stack": "^6.9.12",

example/src/navigation/AppNavigation.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
TwitterProfileScreen,
1313
AbsoluteHeaderBlurSurfaceUsageScreen,
1414
ArbitraryYTransitionHeaderUsageScreen,
15+
InvertedUsageScreen,
1516
} from '../screens';
1617

1718
const Stack = createNativeStackNavigator<RootStackParamList>();
@@ -28,6 +29,7 @@ export default () => (
2829
<Stack.Screen name="FlatListUsageScreen" component={FlatListUsageScreen} />
2930
<Stack.Screen name="FlashListUsageScreen" component={FlashListUsageScreen} />
3031
<Stack.Screen name="SectionListUsageScreen" component={SectionListUsageScreen} />
32+
<Stack.Screen name="InvertedUsageScreen" component={InvertedUsageScreen} />
3133
<Stack.Screen
3234
name="HeaderSurfaceComponentUsageScreen"
3335
component={SurfaceComponentUsageScreen}

example/src/navigation/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export type RootStackParamList = {
1212
HeaderSurfaceComponentUsageScreen: undefined;
1313
AbsoluteHeaderBlurSurfaceUsageScreen: undefined;
1414
ArbitraryYTransitionHeaderUsageScreen: undefined;
15+
InvertedUsageScreen: undefined;
1516
};
1617

1718
// Overrides the typing for useNavigation in @react-navigation/native to support the internal
@@ -65,3 +66,8 @@ export type ArbitraryYTransitionHeaderUsageScreenNavigationProps = NativeStackSc
6566
RootStackParamList,
6667
'ArbitraryYTransitionHeaderUsageScreen'
6768
>;
69+
70+
export type InvertedUsageScreenNavigationProps = NativeStackScreenProps<
71+
RootStackParamList,
72+
'InvertedUsageScreen'
73+
>;

example/src/screens/Home.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ const SCREEN_LIST_CONFIG: ScreenConfigItem[] = [
3838
route: 'SectionListUsageScreen',
3939
description: "A simple example of the library's SectionList.",
4040
},
41+
{
42+
name: 'Inverted Example',
43+
route: 'InvertedUsageScreen',
44+
description: "A simple example of the library's Inverted FlatList.",
45+
},
4146
{
4247
name: 'Header SurfaceComponent Interpolation',
4348
route: 'HeaderSurfaceComponentUsageScreen',

example/src/screens/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export { default as SimpleUsageScreen } from './usage/Simple';
66
export { default as FlatListUsageScreen } from './usage/FlatList';
77
export { default as FlashListUsageScreen } from './usage/FlashList';
88
export { default as SectionListUsageScreen } from './usage/SectionList';
9+
export { default as InvertedUsageScreen } from './usage/Inverted';
910
export { default as SurfaceComponentUsageScreen } from './usage/SurfaceComponent';
1011
export { default as TwitterProfileScreen } from './usage/TwitterProfile';
1112
export { default as AbsoluteHeaderBlurSurfaceUsageScreen } from './usage/AbsoluteHeaderBlurSurface';
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import React from 'react';
2+
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
3+
import { useNavigation } from '@react-navigation/native';
4+
import { randParagraph, randUuid } from '@ngneat/falso';
5+
import { BlurView } from '@react-native-community/blur';
6+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
7+
import { StatusBar } from 'expo-status-bar';
8+
import {
9+
Header,
10+
ScrollHeaderProps,
11+
FlatListWithHeaders,
12+
SurfaceComponentProps,
13+
} from '@codeherence/react-native-header';
14+
import { Avatar, BackButton } from '../../components';
15+
import { RANDOM_IMAGE_NUM } from '../../constants';
16+
import { range } from '../../utils';
17+
import type { InvertedUsageScreenNavigationProps } from '../../navigation';
18+
19+
const HeaderSurface: React.FC<SurfaceComponentProps> = () => {
20+
return <BlurView style={StyleSheet.absoluteFill} blurType="chromeMaterialDark" />;
21+
};
22+
23+
const HeaderComponent: React.FC<ScrollHeaderProps> = ({ showNavBar }) => {
24+
const navigation = useNavigation();
25+
const onPressProfile = () => navigation.navigate('Profile');
26+
27+
return (
28+
<Header
29+
showNavBar={showNavBar}
30+
noBottomBorder
31+
headerCenterFadesIn={false}
32+
headerCenter={
33+
<Text style={styles.navBarTitle} numberOfLines={1}>
34+
Elon Musk
35+
</Text>
36+
}
37+
headerRight={
38+
<TouchableOpacity onPress={onPressProfile}>
39+
<Avatar size="sm" source={{ uri: `https://i.pravatar.cc/128?img=${RANDOM_IMAGE_NUM}` }} />
40+
</TouchableOpacity>
41+
}
42+
headerLeft={<BackButton color="white" />}
43+
SurfaceComponent={HeaderSurface}
44+
/>
45+
);
46+
};
47+
48+
interface ChatMessage {
49+
id: string;
50+
message: string;
51+
type: 'sent' | 'received';
52+
}
53+
54+
const data: ChatMessage[] = range({ end: 100 }).map((i) => {
55+
const id = randUuid();
56+
const message = randParagraph();
57+
const type = i % 2 === 0 ? 'sent' : 'received';
58+
59+
return { id, message, type };
60+
});
61+
62+
const ChatMessage: React.FC<ChatMessage> = ({ message, type }) => {
63+
return (
64+
<View style={type === 'sent' ? styles.sentMessageContainer : styles.receivedMessageContainer}>
65+
<Text style={styles.messageText}>{message}</Text>
66+
</View>
67+
);
68+
};
69+
70+
const Inverted: React.FC<InvertedUsageScreenNavigationProps> = () => {
71+
const { bottom } = useSafeAreaInsets();
72+
73+
return (
74+
<>
75+
<StatusBar style="light" />
76+
<FlatListWithHeaders
77+
data={data}
78+
renderItem={({ item }) => <ChatMessage {...item} />}
79+
HeaderComponent={HeaderComponent}
80+
keyExtractor={(item) => item.id}
81+
inverted
82+
absoluteHeader
83+
containerStyle={styles.container}
84+
contentContainerStyle={[styles.contentContainer, { paddingTop: bottom }]}
85+
showsVerticalScrollIndicator
86+
indicatorStyle={'white'}
87+
/>
88+
</>
89+
);
90+
};
91+
92+
export default Inverted;
93+
94+
const styles = StyleSheet.create({
95+
container: {
96+
backgroundColor: 'black',
97+
},
98+
contentContainer: {
99+
backgroundColor: 'black',
100+
paddingHorizontal: 12,
101+
},
102+
navBarTitle: { fontSize: 16, fontWeight: 'bold', color: 'white' },
103+
messageText: {
104+
color: 'white',
105+
},
106+
sentMessageContainer: {
107+
backgroundColor: '#186BE7',
108+
alignSelf: 'flex-start',
109+
borderRadius: 12,
110+
maxWidth: '75%',
111+
padding: 12,
112+
marginVertical: 12,
113+
},
114+
receivedMessageContainer: {
115+
backgroundColor: '#1F2329',
116+
alignItems: 'flex-end',
117+
justifyContent: 'flex-end',
118+
alignSelf: 'flex-end',
119+
borderRadius: 12,
120+
maxWidth: '75%',
121+
padding: 12,
122+
marginVertical: 12,
123+
},
124+
separator: {
125+
height: 24,
126+
},
127+
});

example/yarn.lock

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,6 +1715,14 @@
17151715
"@jridgewell/resolve-uri" "3.1.0"
17161716
"@jridgewell/sourcemap-codec" "1.4.14"
17171717

1718+
"@ngneat/falso@^6.4.0":
1719+
version "6.4.0"
1720+
resolved "https://registry.yarnpkg.com/@ngneat/falso/-/falso-6.4.0.tgz#7ed98a95ab2a2dc39b0a0d317fe8c24512869edc"
1721+
integrity sha512-f6r036h2fX/AoHw1eV2t8+qWQwrbSrozs3zXMhhwoO7SJBc+DGMxRWEhFeYIinfwx0uhUH8ggx5+PDLzYESLOA==
1722+
dependencies:
1723+
seedrandom "3.0.5"
1724+
uuid "8.3.2"
1725+
17181726
"@nodelib/fs.scandir@2.1.5":
17191727
version "2.1.5"
17201728
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@@ -9704,6 +9712,11 @@ schema-utils@^3.0.0, schema-utils@^3.1.1:
97049712
ajv "^6.12.5"
97059713
ajv-keywords "^3.5.2"
97069714

9715+
seedrandom@3.0.5:
9716+
version "3.0.5"
9717+
resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7"
9718+
integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==
9719+
97079720
select-hose@^2.0.0:
97089721
version "2.0.0"
97099722
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@@ -10990,6 +11003,11 @@ utils-merge@1.0.1:
1099011003
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
1099111004
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
1099211005

11006+
uuid@8.3.2, uuid@^8.0.0, uuid@^8.3.2:
11007+
version "8.3.2"
11008+
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
11009+
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
11010+
1099311011
uuid@^3.3.2, uuid@^3.4.0:
1099411012
version "3.4.0"
1099511013
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
@@ -11000,11 +11018,6 @@ uuid@^7.0.3:
1100011018
resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
1100111019
integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==
1100211020

11003-
uuid@^8.0.0, uuid@^8.3.2:
11004-
version "8.3.2"
11005-
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
11006-
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
11007-
1100811021
valid-url@~1.0.9:
1100911022
version "1.0.9"
1101011023
resolved "https://registry.yarnpkg.com/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200"

src/components/containers/FlashList.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const FlashListWithHeadersInputComp = <ItemT extends any = any>(
4242
headerFadeInThreshold = 1,
4343
disableLargeHeaderFadeAnim = false,
4444
scrollIndicatorInsets = {},
45+
inverted,
4546
...rest
4647
}: AnimatedFlashListType<ItemT>,
4748
ref: React.Ref<FlashList<ItemT>>
@@ -57,8 +58,8 @@ const FlashListWithHeadersInputComp = <ItemT extends any = any>(
5758
largeHeaderOpacity,
5859
scrollHandler,
5960
debouncedFixScroll,
60-
absoluteHeaderHeight,
6161
onAbsoluteHeaderLayout,
62+
scrollViewAdjustments,
6263
} = useScrollContainerLogic({
6364
scrollRef,
6465
largeHeaderShown,
@@ -67,6 +68,7 @@ const FlashListWithHeadersInputComp = <ItemT extends any = any>(
6768
absoluteHeader,
6869
initialAbsoluteHeaderHeight,
6970
headerFadeInThreshold,
71+
inverted: !!inverted,
7072
});
7173

7274
return (
@@ -101,20 +103,19 @@ const FlashListWithHeadersInputComp = <ItemT extends any = any>(
101103
debouncedFixScroll();
102104
if (onMomentumScrollEnd) onMomentumScrollEnd(e);
103105
}}
104-
// eslint-disable-next-line react-native/no-inline-styles
105106
contentContainerStyle={{
106107
// The reason why we do this is because FlashList does not support an array of
107108
// styles (will throw a warning when you supply one).
109+
...scrollViewAdjustments.contentContainerStyle,
108110
...contentContainerStyle,
109-
paddingTop: absoluteHeader ? absoluteHeaderHeight : 0,
110111
}}
111112
automaticallyAdjustsScrollIndicatorInsets={
112113
automaticallyAdjustsScrollIndicatorInsets !== undefined
113114
? automaticallyAdjustsScrollIndicatorInsets
114115
: !absoluteHeader
115116
}
116117
scrollIndicatorInsets={{
117-
top: absoluteHeader ? absoluteHeaderHeight : 0,
118+
...scrollViewAdjustments.scrollIndicatorInsets,
118119
...scrollIndicatorInsets,
119120
}}
120121
ListHeaderComponent={
@@ -138,6 +139,7 @@ const FlashListWithHeadersInputComp = <ItemT extends any = any>(
138139
</View>
139140
) : undefined
140141
}
142+
inverted={inverted}
141143
{...rest}
142144
/>
143145

src/components/containers/FlatList.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const FlatListWithHeadersInputComp = <ItemT extends unknown>(
3838
headerFadeInThreshold = 1,
3939
disableLargeHeaderFadeAnim = false,
4040
scrollIndicatorInsets = {},
41+
inverted,
4142
...rest
4243
}: AnimatedFlatListProps<ItemT> & SharedScrollContainerProps,
4344
ref: React.Ref<Animated.FlatList<ItemT> | null>
@@ -53,8 +54,8 @@ const FlatListWithHeadersInputComp = <ItemT extends unknown>(
5354
largeHeaderOpacity,
5455
scrollHandler,
5556
debouncedFixScroll,
56-
absoluteHeaderHeight,
5757
onAbsoluteHeaderLayout,
58+
scrollViewAdjustments,
5859
} = useScrollContainerLogic({
5960
scrollRef,
6061
largeHeaderShown,
@@ -63,6 +64,7 @@ const FlatListWithHeadersInputComp = <ItemT extends unknown>(
6364
absoluteHeader,
6465
initialAbsoluteHeaderHeight,
6566
headerFadeInThreshold,
67+
inverted: !!inverted,
6668
});
6769

6870
return (
@@ -98,18 +100,18 @@ const FlatListWithHeadersInputComp = <ItemT extends unknown>(
98100
if (onMomentumScrollEnd) onMomentumScrollEnd(e);
99101
}}
100102
contentContainerStyle={[
103+
scrollViewAdjustments.contentContainerStyle,
101104
// @ts-ignore
102105
// Unfortunately there are issues with Reanimated typings, so will ignore for now.
103106
contentContainerStyle,
104-
absoluteHeader ? { paddingTop: absoluteHeaderHeight } : undefined,
105107
]}
106108
automaticallyAdjustsScrollIndicatorInsets={
107109
automaticallyAdjustsScrollIndicatorInsets !== undefined
108110
? automaticallyAdjustsScrollIndicatorInsets
109111
: !absoluteHeader
110112
}
111113
scrollIndicatorInsets={{
112-
top: absoluteHeader ? absoluteHeaderHeight : 0,
114+
...scrollViewAdjustments.scrollIndicatorInsets,
113115
...scrollIndicatorInsets,
114116
}}
115117
ListHeaderComponent={
@@ -133,6 +135,7 @@ const FlatListWithHeadersInputComp = <ItemT extends unknown>(
133135
</View>
134136
) : undefined
135137
}
138+
inverted={inverted}
136139
{...rest}
137140
/>
138141

0 commit comments

Comments
 (0)