Skip to content

Commit 1816333

Browse files
authored
feat: absolute headers (#16)
1 parent 6800824 commit 1816333

File tree

11 files changed

+304
-8
lines changed

11 files changed

+304
-8
lines changed

example/src/navigation/AppNavigation.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
SimpleUsageScreen,
1111
SurfaceComponentUsageScreen,
1212
TwitterProfileScreen,
13+
AbsoluteHeaderBlurSurface,
1314
} from '../screens';
1415

1516
const Stack = createNativeStackNavigator<RootStackParamList>();
@@ -31,5 +32,9 @@ export default () => (
3132
component={SurfaceComponentUsageScreen}
3233
/>
3334
<Stack.Screen name="TwitterProfileScreen" component={TwitterProfileScreen} />
35+
<Stack.Screen
36+
name="AbsoluteHeaderBlurSurfaceUsageScreen"
37+
component={AbsoluteHeaderBlurSurface}
38+
/>
3439
</Stack.Navigator>
3540
);

example/src/navigation/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export type RootStackParamList = {
1010
SectionListUsageScreen: undefined;
1111
TwitterProfileScreen: undefined;
1212
HeaderSurfaceComponentUsageScreen: undefined;
13+
AbsoluteHeaderBlurSurfaceUsageScreen: undefined;
1314
};
1415

1516
// Overrides the typing for useNavigation in @react-navigation/native to support the internal
@@ -53,3 +54,8 @@ export type TwitterProfileScreenNavigationProps = NativeStackScreenProps<
5354
RootStackParamList,
5455
'TwitterProfileScreen'
5556
>;
57+
58+
export type AbsoluteHeaderBlurSurfaceUsageScreenNavigationProps = NativeStackScreenProps<
59+
RootStackParamList,
60+
'AbsoluteHeaderBlurSurfaceUsageScreen'
61+
>;

example/src/screens/Home.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ const SCREEN_LIST_CONFIG: ScreenConfigItem[] = [
4949
route: 'TwitterProfileScreen',
5050
description: 'Rebuilding the Twitter profile header with this library.',
5151
},
52+
{
53+
name: 'Absolute Header with Blurred Surface',
54+
route: 'AbsoluteHeaderBlurSurfaceUsageScreen',
55+
description: 'An example of an absolutely-positioned header with a BlurView surface.',
56+
},
5257
];
5358

5459
const HeaderComponent: React.FC<ScrollHeaderProps> = ({ showNavBar }) => {
@@ -83,6 +88,7 @@ const Home: React.FC<HomeScreenNavigationProps> = ({ navigation }) => {
8388

8489
return (
8590
<ScrollViewWithHeaders
91+
disableAutoFixScroll
8692
HeaderComponent={HeaderComponent}
8793
LargeHeaderComponent={LargeHeaderComponent}
8894
contentContainerStyle={{ paddingBottom: bottom }}

example/src/screens/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export { default as FlashListUsageScreen } from './usage/FlashList';
88
export { default as SectionListUsageScreen } from './usage/SectionList';
99
export { default as SurfaceComponentUsageScreen } from './usage/SurfaceComponent';
1010
export { default as TwitterProfileScreen } from './usage/TwitterProfile';
11+
export { default as AbsoluteHeaderBlurSurface } from './usage/AbsoluteHeaderBlurSurface';
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 { StyleSheet, Text, View } from 'react-native';
3+
import type { AbsoluteHeaderBlurSurfaceUsageScreenNavigationProps } from '../../navigation';
4+
import {
5+
FadingView,
6+
Header,
7+
LargeHeader,
8+
ScalingView,
9+
ScrollHeaderProps,
10+
ScrollLargeHeaderProps,
11+
ScrollViewWithHeaders,
12+
SurfaceComponentProps,
13+
} from '@codeherence/react-native-header';
14+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
15+
import { BlurView } from '@react-native-community/blur';
16+
import { BackButton } from '../../components';
17+
import { range } from '../../utils';
18+
19+
const HeaderSurface: React.FC<SurfaceComponentProps> = ({ showNavBar }) => {
20+
return (
21+
<FadingView opacity={showNavBar} style={StyleSheet.absoluteFill}>
22+
<BlurView style={StyleSheet.absoluteFill} blurType="light" />
23+
</FadingView>
24+
);
25+
};
26+
27+
const HeaderComponent: React.FC<ScrollHeaderProps> = ({ showNavBar }) => {
28+
const insets = useSafeAreaInsets();
29+
30+
return (
31+
<Header
32+
showNavBar={showNavBar}
33+
noBottomBorder
34+
headerStyle={{ height: 44 + insets.top }}
35+
headerCenter={<Text style={styles.headerTitle}>react-native-header</Text>}
36+
headerLeft={<BackButton />}
37+
SurfaceComponent={HeaderSurface}
38+
/>
39+
);
40+
};
41+
42+
const LargeHeaderComponent: React.FC<ScrollLargeHeaderProps> = ({ scrollY }) => {
43+
return (
44+
<LargeHeader>
45+
<ScalingView scrollY={scrollY} style={styles.leftHeader}>
46+
<Text style={styles.title}>Large Header</Text>
47+
</ScalingView>
48+
</LargeHeader>
49+
);
50+
};
51+
52+
const TransparentSurface: React.FC<AbsoluteHeaderBlurSurfaceUsageScreenNavigationProps> = () => {
53+
const { bottom } = useSafeAreaInsets();
54+
55+
return (
56+
<ScrollViewWithHeaders
57+
absoluteHeader
58+
HeaderComponent={HeaderComponent}
59+
LargeHeaderComponent={LargeHeaderComponent}
60+
style={styles.container}
61+
contentContainerStyle={{ paddingBottom: bottom + 12 }}
62+
>
63+
<View style={styles.boxes}>
64+
{range({ end: 20 }).map((i) => (
65+
<View key={`box-${i}`} style={i % 2 === 0 ? styles.redBox : styles.greenBox} />
66+
))}
67+
</View>
68+
</ScrollViewWithHeaders>
69+
);
70+
};
71+
72+
export default TransparentSurface;
73+
74+
const styles = StyleSheet.create({
75+
container: {
76+
flex: 1,
77+
zIndex: -100,
78+
},
79+
contentContainer: {
80+
paddingTop: 44,
81+
},
82+
redBox: {
83+
backgroundColor: 'red',
84+
height: 200,
85+
width: 200,
86+
},
87+
greenBox: {
88+
backgroundColor: 'green',
89+
height: 200,
90+
width: 200,
91+
},
92+
headerTitle: {
93+
fontSize: 16,
94+
fontWeight: 'bold',
95+
},
96+
boxes: {
97+
flexDirection: 'row',
98+
justifyContent: 'center',
99+
alignItems: 'center',
100+
gap: 12,
101+
flexWrap: 'wrap',
102+
},
103+
title: { fontSize: 32, fontWeight: 'bold' },
104+
leftHeader: { gap: 2 },
105+
});

src/components/containers/FlashList.tsx

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ const FlashListWithHeadersInputComp = <ItemT extends any = any>(
3535
disableAutoFixScroll = false,
3636
/** At the moment, we will not allow overriding of this since the scrollHandler needs it. */
3737
onScroll: _unusedOnScroll,
38+
absoluteHeader = false,
39+
initialAbsoluteHeaderHeight = 0,
40+
contentContainerStyle = {},
41+
automaticallyAdjustsScrollIndicatorInsets,
3842
...rest
3943
}: AnimatedFlashListType<ItemT>,
4044
ref: React.Ref<FlashList<ItemT>>
@@ -50,11 +54,15 @@ const FlashListWithHeadersInputComp = <ItemT extends any = any>(
5054
largeHeaderOpacity,
5155
scrollHandler,
5256
debouncedFixScroll,
57+
absoluteHeaderHeight,
58+
onAbsoluteHeaderLayout,
5359
} = useScrollContainerLogic({
5460
scrollRef,
5561
largeHeaderShown,
5662
disableAutoFixScroll,
5763
largeHeaderExists: !!LargeHeaderComponent,
64+
absoluteHeader,
65+
initialAbsoluteHeaderHeight,
5866
});
5967

6068
return (
@@ -66,7 +74,7 @@ const FlashListWithHeadersInputComp = <ItemT extends any = any>(
6674
!ignoreRightSafeArea && { paddingRight: insets.right },
6775
]}
6876
>
69-
{HeaderComponent({ showNavBar, scrollY })}
77+
{!absoluteHeader && HeaderComponent({ showNavBar, scrollY })}
7078
<AnimatedFlashList
7179
ref={scrollRef}
7280
scrollEventThrottle={16}
@@ -89,6 +97,19 @@ const FlashListWithHeadersInputComp = <ItemT extends any = any>(
8997
debouncedFixScroll();
9098
if (onMomentumScrollEnd) onMomentumScrollEnd(e);
9199
}}
100+
// eslint-disable-next-line react-native/no-inline-styles
101+
contentContainerStyle={{
102+
// The reason why we do this is because FlashList does not support an array of
103+
// styles (will throw a warning when you supply one).
104+
...contentContainerStyle,
105+
paddingTop: absoluteHeader ? absoluteHeaderHeight : 0,
106+
}}
107+
automaticallyAdjustsScrollIndicatorInsets={
108+
automaticallyAdjustsScrollIndicatorInsets !== undefined
109+
? automaticallyAdjustsScrollIndicatorInsets
110+
: !absoluteHeader
111+
}
112+
scrollIndicatorInsets={{ top: absoluteHeader ? absoluteHeaderHeight : 0 }}
92113
ListHeaderComponent={
93114
LargeHeaderComponent ? (
94115
<View
@@ -106,6 +127,12 @@ const FlashListWithHeadersInputComp = <ItemT extends any = any>(
106127
}
107128
{...rest}
108129
/>
130+
131+
{absoluteHeader && (
132+
<View style={styles.absoluteHeader} onLayout={onAbsoluteHeaderLayout}>
133+
{HeaderComponent({ showNavBar, scrollY })}
134+
</View>
135+
)}
109136
</View>
110137
);
111138
};
@@ -117,4 +144,12 @@ const FlashListWithHeaders = React.forwardRef(FlashListWithHeadersInputComp) as
117144

118145
export default FlashListWithHeaders;
119146

120-
const styles = StyleSheet.create({ container: { flex: 1 } });
147+
const styles = StyleSheet.create({
148+
container: { flex: 1 },
149+
absoluteHeader: {
150+
position: 'absolute',
151+
top: 0,
152+
right: 0,
153+
left: 0,
154+
},
155+
});

src/components/containers/FlatList.tsx

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ const FlatListWithHeadersInputComp = <ItemT extends unknown>(
3131
disableAutoFixScroll = false,
3232
/** At the moment, we will not allow overriding of this since the scrollHandler needs it. */
3333
onScroll: _unusedOnScroll,
34+
absoluteHeader = false,
35+
initialAbsoluteHeaderHeight = 0,
36+
contentContainerStyle,
37+
automaticallyAdjustsScrollIndicatorInsets,
3438
...rest
3539
}: AnimatedFlatListProps<ItemT> & SharedScrollContainerProps,
3640
ref: React.Ref<Animated.FlatList<ItemT> | null>
@@ -46,11 +50,15 @@ const FlatListWithHeadersInputComp = <ItemT extends unknown>(
4650
largeHeaderOpacity,
4751
scrollHandler,
4852
debouncedFixScroll,
53+
absoluteHeaderHeight,
54+
onAbsoluteHeaderLayout,
4955
} = useScrollContainerLogic({
5056
scrollRef,
5157
largeHeaderShown,
5258
disableAutoFixScroll,
5359
largeHeaderExists: !!LargeHeaderComponent,
60+
absoluteHeader,
61+
initialAbsoluteHeaderHeight,
5462
});
5563

5664
return (
@@ -62,7 +70,7 @@ const FlatListWithHeadersInputComp = <ItemT extends unknown>(
6270
!ignoreRightSafeArea && { paddingRight: insets.right },
6371
]}
6472
>
65-
{HeaderComponent({ showNavBar, scrollY })}
73+
{!absoluteHeader && HeaderComponent({ showNavBar, scrollY })}
6674
<Animated.FlatList
6775
ref={scrollRef}
6876
scrollEventThrottle={16}
@@ -85,6 +93,18 @@ const FlatListWithHeadersInputComp = <ItemT extends unknown>(
8593
debouncedFixScroll();
8694
if (onMomentumScrollEnd) onMomentumScrollEnd(e);
8795
}}
96+
contentContainerStyle={[
97+
// @ts-ignore
98+
// Unfortunately there are issues with Reanimated typings, so will ignore for now.
99+
contentContainerStyle,
100+
absoluteHeader ? { paddingTop: absoluteHeaderHeight } : undefined,
101+
]}
102+
automaticallyAdjustsScrollIndicatorInsets={
103+
automaticallyAdjustsScrollIndicatorInsets !== undefined
104+
? automaticallyAdjustsScrollIndicatorInsets
105+
: !absoluteHeader
106+
}
107+
scrollIndicatorInsets={{ top: absoluteHeader ? absoluteHeaderHeight : 0 }}
88108
ListHeaderComponent={
89109
LargeHeaderComponent ? (
90110
<View
@@ -102,6 +122,12 @@ const FlatListWithHeadersInputComp = <ItemT extends unknown>(
102122
}
103123
{...rest}
104124
/>
125+
126+
{absoluteHeader && (
127+
<View style={styles.absoluteHeader} onLayout={onAbsoluteHeaderLayout}>
128+
{HeaderComponent({ showNavBar, scrollY })}
129+
</View>
130+
)}
105131
</View>
106132
);
107133
};
@@ -114,4 +140,12 @@ const FlatListWithHeaders = React.forwardRef(FlatListWithHeadersInputComp) as <I
114140

115141
export default FlatListWithHeaders;
116142

117-
const styles = StyleSheet.create({ container: { flex: 1 } });
143+
const styles = StyleSheet.create({
144+
container: { flex: 1 },
145+
absoluteHeader: {
146+
position: 'absolute',
147+
top: 0,
148+
right: 0,
149+
left: 0,
150+
},
151+
});

0 commit comments

Comments
 (0)