Skip to content

Commit ac7dfde

Browse files
authored
feat: add SurfaceComponent prop to header (#10)
1 parent 45d0563 commit ac7dfde

File tree

12 files changed

+190
-8
lines changed

12 files changed

+190
-8
lines changed

docs/docs/03-api-reference/components/04-header.mdx

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ slug: /components/header
55
description: A header navigation component exported from React Native Header.
66
---
77

8-
Headers are navigation components that display information and actions relating to the current
9-
screen. This component is exported in this library and works well with the other scroll
8+
Headers are navigation components that display information and actions relating to the current
9+
screen. This component is exported in this library and works well with the other scroll
1010
containers this library provides.
1111

1212
## Props
@@ -61,8 +61,8 @@ An optional boolean to indicate whether the header's center container should fad
6161
### ignoreTopSafeArea
6262

6363
An optional boolean to indicate whether the header should ignore the top safe area. This is useful
64-
when you want to display the header behind the status bar. Defaults to `false`. If you are
65-
rendering this header in an iOS [modal](https://reactnavigation.org/docs/native-stack-navigator/#presentation),
64+
when you want to display the header behind the status bar. Defaults to `false`. If you are
65+
rendering this header in an iOS [modal](https://reactnavigation.org/docs/native-stack-navigator/#presentation),
6666
you should set this to `true`.
6767

6868
### noBottomBorder
@@ -76,4 +76,39 @@ An optional color to use for the header's bottom border. Defaults to `#E5E5E5`.
7676

7777
### borderWidth
7878

79-
An optional width to use for the header's bottom border. Defaults to [StyleSheet.hairlineWidth](https://reactnative.dev/docs/stylesheet#hairlinewidth).
79+
An optional width to use for the header's bottom border. Defaults to [StyleSheet.hairlineWidth](https://reactnative.dev/docs/stylesheet#hairlinewidth).
80+
81+
### SurfaceComponent
82+
83+
An optional component that can act as the surface of the header. Please ensure that the styling applied to the component includes `StyleSheet.absoluteFill` or `StyleSheet.absoluteFillObject` to ensure that the surface component fills the header.
84+
85+
#### Example
86+
87+
The following example will make the header have a **cyan** background when the user scrolls the scroll container up:
88+
89+
```jsx
90+
const SURFACE_BG_COLOR = 'cyan';
91+
92+
const HeaderSurface: React.FC<SurfaceComponentProps> = ({ showNavBar }) => (
93+
<FadingView
94+
opacity={showNavBar}
95+
// StyleSheet.absoluteFill is needed to ensure that the component fills up the header.
96+
style={[StyleSheet.absoluteFill, { backgroundColor: SURFACE_BG_COLOR }]}
97+
/>
98+
);
99+
100+
const HeaderComponent: React.FC<ScrollHeaderProps> = ({ showNavBar }) => {
101+
const navigation = useNavigation();
102+
const onPressProfile = () => navigation.navigate('Profile');
103+
104+
return (
105+
<Header
106+
showNavBar={showNavBar}
107+
// ...
108+
SurfaceComponent={HeaderSurface} // <- usage
109+
/>
110+
);
111+
};
112+
```
113+
114+
See the example application's _Header SurfaceComponent Interpolation_ screen in for a working example.

example/ios/Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -720,4 +720,4 @@ SPEC CHECKSUMS:
720720

721721
PODFILE CHECKSUM: 675d5c47e1336b7b792d33fad9d8fbf19b97d359
722722

723-
COCOAPODS: 1.11.3
723+
COCOAPODS: 1.12.1

example/src/navigation/AppNavigation.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
ProfileScreen,
88
SectionListUsageScreen,
99
SimpleUsageScreen,
10+
SurfaceComponentUsageScreen,
1011
TwitterProfileScreen,
1112
} from '../screens';
1213

@@ -23,6 +24,10 @@ export default () => (
2324
<Stack.Screen name="SimpleUsageScreen" component={SimpleUsageScreen} />
2425
<Stack.Screen name="FlatListUsageScreen" component={FlatListUsageScreen} />
2526
<Stack.Screen name="SectionListUsageScreen" component={SectionListUsageScreen} />
27+
<Stack.Screen
28+
name="HeaderSurfaceComponentUsageScreen"
29+
component={SurfaceComponentUsageScreen}
30+
/>
2631
<Stack.Screen name="TwitterProfileScreen" component={TwitterProfileScreen} />
2732
</Stack.Navigator>
2833
);

example/src/navigation/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export type RootStackParamList = {
88
FlatListUsageScreen: undefined;
99
SectionListUsageScreen: undefined;
1010
TwitterProfileScreen: undefined;
11+
HeaderSurfaceComponentUsageScreen: undefined;
1112
};
1213

1314
// Overrides the typing for useNavigation in @react-navigation/native to support the internal
@@ -37,6 +38,11 @@ export type SectionListUsageScreenNavigationProps = NativeStackScreenProps<
3738
'SectionListUsageScreen'
3839
>;
3940

41+
export type SurfaceComponentUsageScreenNavigationProps = NativeStackScreenProps<
42+
RootStackParamList,
43+
'HeaderSurfaceComponentUsageScreen'
44+
>;
45+
4046
export type TwitterProfileScreenNavigationProps = NativeStackScreenProps<
4147
RootStackParamList,
4248
'TwitterProfileScreen'

example/src/screens/Home.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ const SCREEN_LIST_CONFIG: ScreenConfigItem[] = [
3333
route: 'SectionListUsageScreen',
3434
description: "A simple example of the library's SectionList.",
3535
},
36+
{
37+
name: 'Header SurfaceComponent Interpolation',
38+
route: 'HeaderSurfaceComponentUsageScreen',
39+
description:
40+
'A simple example of the library using the SurfaceComponent prop for its header to animate the background color of the header.',
41+
},
3642
{
3743
name: 'Twitter Profile',
3844
route: 'TwitterProfileScreen',

example/src/screens/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ export { default as ProfileScreen } from './Profile';
55
export { default as SimpleUsageScreen } from './usage/Simple';
66
export { default as FlatListUsageScreen } from './usage/FlatList';
77
export { default as SectionListUsageScreen } from './usage/SectionList';
8+
export { default as SurfaceComponentUsageScreen } from './usage/SurfaceComponent';
89
export { default as TwitterProfileScreen } from './usage/TwitterProfile';
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import React, { useMemo, useState } from 'react';
2+
import { RefreshControl, 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+
FadingView,
7+
Header,
8+
LargeHeader,
9+
ScrollViewWithHeaders,
10+
} from '@codeherence/react-native-header';
11+
import type {
12+
ScrollHeaderProps,
13+
ScrollLargeHeaderProps,
14+
SurfaceComponentProps,
15+
} from '@codeherence/react-native-header';
16+
import { range } from '../../utils';
17+
import { Avatar, BackButton } from '../../components';
18+
import { RANDOM_IMAGE_NUM } from '../../constants';
19+
import type { SurfaceComponentUsageScreenNavigationProps } from '../../navigation';
20+
21+
const SURFACE_BG_COLOR = 'cyan';
22+
23+
const HeaderSurface: React.FC<SurfaceComponentProps> = ({ showNavBar }) => (
24+
<FadingView
25+
opacity={showNavBar}
26+
style={[StyleSheet.absoluteFill, { backgroundColor: SURFACE_BG_COLOR }]}
27+
/>
28+
);
29+
30+
const HeaderComponent: React.FC<ScrollHeaderProps> = ({ showNavBar }) => {
31+
const navigation = useNavigation();
32+
const onPressProfile = () => navigation.navigate('Profile');
33+
34+
return (
35+
<Header
36+
showNavBar={showNavBar}
37+
headerCenterFadesIn={false}
38+
headerCenter={
39+
<Text style={styles.navBarTitle} numberOfLines={1}>
40+
Header
41+
</Text>
42+
}
43+
headerRight={
44+
<TouchableOpacity onPress={onPressProfile}>
45+
<Avatar size="sm" source={{ uri: `https://i.pravatar.cc/128?img=${RANDOM_IMAGE_NUM}` }} />
46+
</TouchableOpacity>
47+
}
48+
headerRightFadesIn
49+
headerLeft={<BackButton />}
50+
SurfaceComponent={HeaderSurface}
51+
/>
52+
);
53+
};
54+
55+
const LargeHeaderComponent: React.FC<ScrollLargeHeaderProps> = () => {
56+
const navigation = useNavigation();
57+
const onPressProfile = () => navigation.navigate('Profile');
58+
59+
return (
60+
<LargeHeader headerStyle={styles.largeHeaderStyle}>
61+
<TouchableOpacity onPress={onPressProfile}>
62+
<Avatar size="sm" source={{ uri: `https://i.pravatar.cc/128?img=${RANDOM_IMAGE_NUM}` }} />
63+
</TouchableOpacity>
64+
</LargeHeader>
65+
);
66+
};
67+
68+
const SurfaceComponentUsage: React.FC<SurfaceComponentUsageScreenNavigationProps> = () => {
69+
const { bottom } = useSafeAreaInsets();
70+
const [refreshing, setRefreshing] = useState(false);
71+
72+
const data = useMemo(() => range({ end: 100 }), []);
73+
74+
const onRefresh = async () => {
75+
if (refreshing) return;
76+
77+
setRefreshing(true);
78+
// Mimic some asynchronous task
79+
await new Promise((res) => setTimeout(res, 2500));
80+
setRefreshing(false);
81+
};
82+
83+
return (
84+
<ScrollViewWithHeaders
85+
HeaderComponent={HeaderComponent}
86+
LargeHeaderComponent={LargeHeaderComponent}
87+
contentContainerStyle={{ paddingBottom: bottom }}
88+
refreshControl={
89+
<RefreshControl refreshing={refreshing} colors={['#8E8E93']} onRefresh={onRefresh} />
90+
}
91+
>
92+
<View style={styles.children}>
93+
{data.map((i) => (
94+
<Text key={`text-${i}`}>Scroll to see header animation</Text>
95+
))}
96+
</View>
97+
</ScrollViewWithHeaders>
98+
);
99+
};
100+
101+
export default SurfaceComponentUsage;
102+
103+
const styles = StyleSheet.create({
104+
children: { marginTop: 16, paddingHorizontal: 16 },
105+
navBarTitle: { fontSize: 16, fontWeight: 'bold' },
106+
largeHeaderStyle: { flexDirection: 'row-reverse' },
107+
});

src/components/containers/FadingView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type FadingViewProps = {
3131
/**
3232
* The children to be rendered inside the FadingView.
3333
*/
34-
children: React.ReactNode;
34+
children?: React.ReactNode;
3535
} & React.ComponentProps<typeof Animated.View>;
3636

3737
const FadingView = forwardRef<Animated.View, FadingViewProps>(

src/components/headers/Header.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const Header: React.FC<HeaderProps> = ({
2424
ignoreTopSafeArea = false,
2525
borderColor,
2626
borderWidth,
27+
SurfaceComponent,
2728
}) => {
2829
const { top } = useSafeAreaInsets();
2930
const dimensions = useWindowDimensions();
@@ -40,6 +41,8 @@ const Header: React.FC<HeaderProps> = ({
4041

4142
return (
4243
<View>
44+
{SurfaceComponent && SurfaceComponent({ showNavBar })}
45+
4346
<View style={[styles.container, headerStyle, !ignoreTopSafeArea && { paddingTop: top }]}>
4447
{headerLeftFadesIn ? (
4548
<FadingView

src/components/headers/types.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import type { StyleProp, ViewStyle } from 'react-native';
22
import type Animated from 'react-native-reanimated';
33

4+
export interface SurfaceComponentProps {
5+
/**
6+
* Animated value between 0 and 1 that indicates whether the small header's content should be
7+
* visible. This is used to animate the header's content in and out.
8+
*
9+
* @type {Animated.SharedValue<number>}
10+
*/
11+
showNavBar: Animated.SharedValue<number>;
12+
}
13+
414
/**
515
* The props for the navigation bar component.
616
*/
@@ -111,6 +121,14 @@ export interface HeaderProps {
111121
* @type {number}
112122
*/
113123
borderWidth?: number;
124+
/**
125+
* A custom component to be rendered as the header's surface. This is useful if you want to
126+
* customize the header's surface with a background/blur.
127+
*
128+
* @param {SurfaceComponentProps} props
129+
* @returns {React.ReactNode}
130+
*/
131+
SurfaceComponent?: (props: SurfaceComponentProps) => React.ReactNode;
114132
}
115133

116134
/**

0 commit comments

Comments
 (0)