Skip to content

Commit

Permalink
feat(native-stack): add support for header background image
Browse files Browse the repository at this point in the history
  • Loading branch information
Johan-dutoit authored and satya164 committed Jan 26, 2022
1 parent 3b4edf7 commit 393773b
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 101 deletions.
17 changes: 16 additions & 1 deletion example/src/Screens/NativeStackHeaderCustomization.tsx
Expand Up @@ -4,7 +4,14 @@ import {
NativeStackScreenProps,
} from '@react-navigation/native-stack';
import * as React from 'react';
import { Alert, Platform, ScrollView, StyleSheet, View } from 'react-native';
import {
Alert,
Image,
Platform,
ScrollView,
StyleSheet,
View,
} from 'react-native';
import { Appbar, Button } from 'react-native-paper';

import Albums from '../Shared/Albums';
Expand Down Expand Up @@ -130,6 +137,7 @@ export default function NativeStackScreen({
component={ArticleScreen}
options={({ route }) => ({
title: `Article by ${route.params?.author ?? 'Unknown'}`,
headerTintColor: 'white',
headerTitle: ({ tintColor }) => (
<Appbar.Action
color={tintColor}
Expand All @@ -144,6 +152,13 @@ export default function NativeStackScreen({
onPress={onPress}
/>
),
headerBackground: () => (
<Image
source={require('../../assets/album-art-24.jpg')}
height={100}
style={{ height: 100 }}
/>
),
})}
initialParams={{ author: 'Gandalf' }}
/>
Expand Down
2 changes: 1 addition & 1 deletion example/src/Screens/StackHeaderCustomization.tsx
Expand Up @@ -164,7 +164,7 @@ export default function HeaderCustomizationScreen({ navigation }: Props) {
headerBackground: () => (
<HeaderBackground
style={{
backgroundColor: 'transparent',
backgroundColor: 'blue',
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: colors.border,
}}
Expand Down
2 changes: 1 addition & 1 deletion packages/elements/src/types.tsx
Expand Up @@ -77,7 +77,7 @@ export type HeaderOptions = {
headerTintColor?: string;
/**
* Function which returns a React Element to render as the background of the header.
* This is useful for using backgrounds such as an image or a gradient.
* This is useful for using backgrounds such as an image, a gradient, blur effect etc.
* You can use this with `headerTransparent` to render a blur view, for example, to create a translucent header.
*/
headerBackground?: (props: {
Expand Down
6 changes: 6 additions & 0 deletions packages/native-stack/src/types.tsx
Expand Up @@ -248,6 +248,12 @@ export type NativeStackNavigationOptions = {
* Tint color for the header. Changes the color of back button and title.
*/
headerTintColor?: string;
/**
* Function which returns a React Element to render as the background of the header.
* This is useful for using backgrounds such as an image, a gradient, blur effect etc.
* You can use this with `headerTransparent` to render content underneath a translucent header.
*/
headerBackground?: () => React.ReactNode;
/**
* Function which returns a React Element to display on the left side of the header.
* This replaces the back button. See `headerBackVisible` to show the back button along side left element.
Expand Down
224 changes: 127 additions & 97 deletions packages/native-stack/src/views/HeaderConfig.tsx
Expand Up @@ -23,11 +23,13 @@ import type { NativeStackNavigationOptions } from '../types';
import { processFonts } from './FontProcessor';

type Props = NativeStackNavigationOptions & {
headerHeight: number;
route: Route<string>;
canGoBack: boolean;
};

export default function HeaderConfig({
headerHeight,
headerBackImageSource,
headerBackButtonMenuEnabled,
headerBackTitle,
Expand All @@ -39,6 +41,7 @@ export default function HeaderConfig({
headerLargeTitle,
headerLargeTitleShadowVisible,
headerLargeTitleStyle,
headerBackground,
headerLeft,
headerRight,
headerShown,
Expand All @@ -55,6 +58,7 @@ export default function HeaderConfig({
canGoBack,
}: Props): JSX.Element {
const insets = useSafeAreaInsets();

const { colors } = useTheme();
const tintColor =
headerTintColor ?? (Platform.OS === 'ios' ? colors.primary : colors.text);
Expand Down Expand Up @@ -128,104 +132,120 @@ export default function HeaderConfig({
: Platform.OS === 'android' && headerTitleElement != null;

return (
<ScreenStackHeaderConfig
backButtonInCustomView={backButtonInCustomView}
backgroundColor={
headerStyleFlattened.backgroundColor ??
(headerTransparent ? 'transparent' : colors.card)
}
backTitle={headerBackTitleVisible ? headerBackTitle : ' '}
backTitleFontFamily={backTitleFontFamily}
backTitleFontSize={headerBackTitleStyleFlattened.fontSize}
blurEffect={headerBlurEffect}
color={tintColor}
direction={I18nManager.isRTL ? 'rtl' : 'ltr'}
disableBackButtonMenu={headerBackButtonMenuEnabled === false}
hidden={headerShown === false}
hideBackButton={headerBackVisible === false}
hideShadow={headerShadowVisible === false}
largeTitle={headerLargeTitle}
largeTitleBackgroundColor={headerLargeStyleFlattened.backgroundColor}
largeTitleColor={headerLargeTitleStyleFlattened.color}
largeTitleFontFamily={largeTitleFontFamily}
largeTitleFontSize={headerLargeTitleStyleFlattened.fontSize}
largeTitleFontWeight={headerLargeTitleStyleFlattened.fontWeight}
largeTitleHideShadow={headerLargeTitleShadowVisible === false}
title={typeof headerTitle === 'string' ? headerTitle : titleText}
titleColor={titleColor}
titleFontFamily={titleFontFamily}
titleFontSize={titleFontSize}
titleFontWeight={titleFontWeight}
topInsetEnabled={insets.top !== 0}
translucent={
// This defaults to `true`, so we can't pass `undefined`
headerTransparent === true
}
>
{Platform.OS === 'ios' ? (
<>
{headerLeftElement != null ? (
<ScreenStackHeaderLeftView>
{headerLeftElement}
</ScreenStackHeaderLeftView>
) : null}
{headerTitleElement != null ? (
<ScreenStackHeaderCenterView>
{headerTitleElement}
</ScreenStackHeaderCenterView>
) : null}
</>
) : (
<>
{headerLeftElement != null || typeof headerTitle === 'function' ? (
<ScreenStackHeaderLeftView>
<View style={styles.row}>
{headerLeftElement}
{headerTitleAlign !== 'center' ? (
typeof headerTitle === 'function' ? (
headerTitleElement
) : (
<HeaderTitle
tintColor={tintColor}
style={headerTitleStyleSupported}
>
{titleText}
</HeaderTitle>
)
) : null}
</View>
</ScreenStackHeaderLeftView>
) : null}
{headerTitleAlign === 'center' ? (
<ScreenStackHeaderCenterView>
{typeof headerTitle === 'function' ? (
headerTitleElement
) : (
<HeaderTitle
tintColor={tintColor}
style={headerTitleStyleSupported}
>
{titleText}
</HeaderTitle>
)}
</ScreenStackHeaderCenterView>
) : null}
</>
)}
{headerBackImageSource !== undefined ? (
<ScreenStackHeaderBackButtonImage source={headerBackImageSource} />
) : null}
{headerRightElement != null ? (
<ScreenStackHeaderRightView>
{headerRightElement}
</ScreenStackHeaderRightView>
) : null}
{Platform.OS === 'ios' && headerSearchBarOptions != null ? (
<ScreenStackHeaderSearchBarView>
<SearchBar {...headerSearchBarOptions} />
</ScreenStackHeaderSearchBarView>
<>
{headerBackground != null ? (
<View
style={[
styles.background,
headerTransparent ? styles.translucent : null,
{ height: headerHeight },
]}
>
{headerBackground()}
</View>
) : null}
</ScreenStackHeaderConfig>
<ScreenStackHeaderConfig
backButtonInCustomView={backButtonInCustomView}
backgroundColor={
headerStyleFlattened.backgroundColor ??
(headerBackground != null || headerTransparent
? 'transparent'
: colors.card)
}
backTitle={headerBackTitleVisible ? headerBackTitle : ' '}
backTitleFontFamily={backTitleFontFamily}
backTitleFontSize={headerBackTitleStyleFlattened.fontSize}
blurEffect={headerBlurEffect}
color={tintColor}
direction={I18nManager.isRTL ? 'rtl' : 'ltr'}
disableBackButtonMenu={headerBackButtonMenuEnabled === false}
hidden={headerShown === false}
hideBackButton={headerBackVisible === false}
hideShadow={headerShadowVisible === false}
largeTitle={headerLargeTitle}
largeTitleBackgroundColor={headerLargeStyleFlattened.backgroundColor}
largeTitleColor={headerLargeTitleStyleFlattened.color}
largeTitleFontFamily={largeTitleFontFamily}
largeTitleFontSize={headerLargeTitleStyleFlattened.fontSize}
largeTitleFontWeight={headerLargeTitleStyleFlattened.fontWeight}
largeTitleHideShadow={headerLargeTitleShadowVisible === false}
title={typeof headerTitle === 'string' ? headerTitle : titleText}
titleColor={titleColor}
titleFontFamily={titleFontFamily}
titleFontSize={titleFontSize}
titleFontWeight={titleFontWeight}
topInsetEnabled={insets.top !== 0}
translucent={
headerBackground != null ||
// This defaults to `true`, so we can't pass `undefined`
headerTransparent === true
}
>
{Platform.OS === 'ios' ? (
<>
{headerLeftElement != null ? (
<ScreenStackHeaderLeftView>
{headerLeftElement}
</ScreenStackHeaderLeftView>
) : null}
{headerTitleElement != null ? (
<ScreenStackHeaderCenterView>
{headerTitleElement}
</ScreenStackHeaderCenterView>
) : null}
</>
) : (
<>
{headerLeftElement != null || typeof headerTitle === 'function' ? (
<ScreenStackHeaderLeftView>
<View style={styles.row}>
{headerLeftElement}
{headerTitleAlign !== 'center' ? (
typeof headerTitle === 'function' ? (
headerTitleElement
) : (
<HeaderTitle
tintColor={tintColor}
style={headerTitleStyleSupported}
>
{titleText}
</HeaderTitle>
)
) : null}
</View>
</ScreenStackHeaderLeftView>
) : null}
{headerTitleAlign === 'center' ? (
<ScreenStackHeaderCenterView>
{typeof headerTitle === 'function' ? (
headerTitleElement
) : (
<HeaderTitle
tintColor={tintColor}
style={headerTitleStyleSupported}
>
{titleText}
</HeaderTitle>
)}
</ScreenStackHeaderCenterView>
) : null}
</>
)}
{headerBackImageSource !== undefined ? (
<ScreenStackHeaderBackButtonImage source={headerBackImageSource} />
) : null}
{headerRightElement != null ? (
<ScreenStackHeaderRightView>
{headerRightElement}
</ScreenStackHeaderRightView>
) : null}
{Platform.OS === 'ios' && headerSearchBarOptions != null ? (
<ScreenStackHeaderSearchBarView>
<SearchBar {...headerSearchBarOptions} />
</ScreenStackHeaderSearchBarView>
) : null}
</ScreenStackHeaderConfig>
</>
);
}

Expand All @@ -234,4 +254,14 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
},
translucent: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
zIndex: 1,
},
background: {
overflow: 'hidden',
},
});
11 changes: 10 additions & 1 deletion packages/native-stack/src/views/NativeStackView.native.tsx
Expand Up @@ -40,11 +40,13 @@ const MaybeNestedStack = ({
options,
route,
presentation,
headerHeight,
children,
}: {
options: NativeStackNavigationOptions;
route: Route<string>;
presentation: Exclude<StackPresentationTypes, 'push'> | 'card';
headerHeight: number;
children: React.ReactNode;
}) => {
const { colors } = useTheme();
Expand Down Expand Up @@ -87,7 +89,12 @@ const MaybeNestedStack = ({
return (
<ScreenStack style={styles.container}>
<Screen enabled style={StyleSheet.absoluteFill}>
<HeaderConfig {...options} route={route} canGoBack />
<HeaderConfig
{...options}
route={route}
headerHeight={headerHeight}
canGoBack
/>
{content}
</Screen>
</ScreenStack>
Expand Down Expand Up @@ -221,13 +228,15 @@ const SceneView = ({
{...options}
route={route}
headerShown={isHeaderInPush}
headerHeight={headerHeight}
canGoBack={index !== 0}
/>
)}
<MaybeNestedStack
options={options}
route={route}
presentation={presentation}
headerHeight={headerHeight}
>
{render()}
</MaybeNestedStack>
Expand Down

0 comments on commit 393773b

Please sign in to comment.