refactor: align header options for native stack and regular stack

This commit is contained in:
Satyajit Sahoo
2021-05-22 00:04:29 +02:00
parent 6dc5364089
commit 15ede8112e
6 changed files with 286 additions and 42 deletions

View File

@@ -118,6 +118,7 @@ export default function NativeStackScreen({
React.useLayoutEffect(() => {
navigation.setOptions({
headerShown: false,
gestureEnabled: false,
});
}, [navigation]);

View File

@@ -0,0 +1,185 @@
import * as React from 'react';
import { View, Platform, StyleSheet, ScrollView, Alert } from 'react-native';
import { Appbar, Button } from 'react-native-paper';
import type { ParamListBase } from '@react-navigation/native';
import {
createNativeStackNavigator,
NativeStackScreenProps,
} from '@react-navigation/native-stack';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
import NewsFeed from '../Shared/NewsFeed';
export type NativeStackParams = {
Article: { author: string } | undefined;
NewsFeed: { date: number };
Albums: undefined;
};
const scrollEnabled = Platform.select({ web: true, default: false });
const ArticleScreen = ({
navigation,
route,
}: NativeStackScreenProps<NativeStackParams, 'Article'>) => {
return (
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('NewsFeed', { date: Date.now() })}
style={styles.button}
>
Push feed
</Button>
<Button
mode="outlined"
onPress={() => navigation.pop()}
style={styles.button}
>
Pop screen
</Button>
</View>
<Article
author={{ name: route.params?.author ?? 'Unknown' }}
scrollEnabled={scrollEnabled}
/>
</ScrollView>
);
};
const NewsFeedScreen = ({
route,
navigation,
}: NativeStackScreenProps<NativeStackParams, 'NewsFeed'>) => {
return (
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Albums')}
style={styles.button}
>
Push Albums
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<NewsFeed scrollEnabled={scrollEnabled} date={route.params.date} />
</ScrollView>
);
};
const AlbumsScreen = ({
navigation,
}: NativeStackScreenProps<NativeStackParams, 'Albums'>) => {
return (
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() =>
navigation.navigate('Article', { author: 'Babel fish' })
}
style={styles.button}
>
Navigate to article
</Button>
<Button
mode="outlined"
onPress={() => navigation.pop(2)}
style={styles.button}
>
Pop by 2
</Button>
</View>
<Albums scrollEnabled={scrollEnabled} />
</ScrollView>
);
};
const NativeStack = createNativeStackNavigator<NativeStackParams>();
export default function NativeStackScreen({
navigation,
}: NativeStackScreenProps<ParamListBase>) {
React.useLayoutEffect(() => {
navigation.setOptions({
headerShown: false,
gestureEnabled: false,
});
}, [navigation]);
const onPress = () => {
Alert.alert(
'Never gonna give you up!',
'Never gonna let you down! Never gonna run around and desert you!'
);
};
return (
<NativeStack.Navigator>
<NativeStack.Screen
name="Article"
component={ArticleScreen}
options={({ route }) => ({
title: `Article by ${route.params?.author ?? 'Unknown'}`,
headerTitle: ({ tintColor }) => (
<Appbar.Action
color={tintColor}
icon="signal-5g"
onPress={onPress}
/>
),
headerRight: ({ tintColor }) => (
<Appbar.Action
color={tintColor}
icon="bookmark"
onPress={onPress}
/>
),
})}
initialParams={{ author: 'Gandalf' }}
/>
<NativeStack.Screen
name="NewsFeed"
component={NewsFeedScreen}
options={{
title: 'Feed',
headerLeft: ({ tintColor }) => (
<Appbar.Action color={tintColor} icon="spa" onPress={onPress} />
),
}}
/>
<NativeStack.Screen
name="Albums"
component={AlbumsScreen}
options={{
title: 'Albums',
headerTintColor: 'tomato',
headerStyle: { backgroundColor: 'papayawhip' },
headerBackVisible: true,
headerLeft: ({ tintColor }) => (
<Appbar.Action color={tintColor} icon="music" onPress={onPress} />
),
}}
/>
</NativeStack.Navigator>
);
}
const styles = StyleSheet.create({
buttons: {
flexDirection: 'row',
flexWrap: 'wrap',
padding: 8,
},
button: {
margin: 8,
},
});

View File

@@ -50,6 +50,7 @@ import MixedStack from './Screens/MixedStack';
import MixedHeaderMode from './Screens/MixedHeaderMode';
import StackTransparent from './Screens/StackTransparent';
import StackHeaderCustomization from './Screens/StackHeaderCustomization';
import NativeStackHeaderCustomization from './Screens/NativeStackHeaderCustomization';
import BottomTabs from './Screens/BottomTabs';
import MaterialTopTabsScreen from './Screens/MaterialTopTabs';
import MaterialBottomTabs from './Screens/MaterialBottomTabs';
@@ -98,6 +99,10 @@ const SCREENS = {
title: 'Header Customization in Stack',
component: StackHeaderCustomization,
},
NativeStackHeaderCustomization: {
title: 'Header Customization in Native Stack',
component: NativeStackHeaderCustomization,
},
BottomTabs: { title: 'Bottom Tabs', component: BottomTabs },
MaterialTopTabs: {
title: 'Material Top Tabs',

View File

@@ -12,6 +12,7 @@ export type HeaderOptions = {
/**
* String or a function that returns a React Element to be used by the header.
* Defaults to scene `title`.
*
* It receives `allowFontScaling`, `tintColor`, `style` and `children` in the options object as an argument.
* The title string is passed in `children`.
*/

View File

@@ -62,6 +62,9 @@ export type NativeStackNavigationOptions = {
title?: string;
/**
* Whether the back button is visible in the header.
* You can use it to show a back button alongside `headerLeft` if you have specified it.
*
* This will have no effect on the first screen in the stack.
*/
headerBackVisible?: boolean;
/**
@@ -149,20 +152,6 @@ export type NativeStackNavigationOptions = {
fontWeight?: string;
color?: string;
}>;
/**
* Function which returns a React Element to display on the left side of the header.
* This is shown alongside the back button. See `headerBackVisible` to hide the back button.
*/
headerLeft?: (props: { tintColor?: string }) => React.ReactNode;
/**
* Function which returns a React Element to display on the right side of the header.
*/
headerRight?: (props: { tintColor?: string }) => React.ReactNode;
/**
* Function which returns a React Element to display in the center of the header.
* This replaces the header title on iOS.
*/
headerCenter?: (props: { tintColor?: string }) => React.ReactNode;
/**
* Whether to show the header. The header is shown by default.
* Setting this to `false` hides the header.
@@ -198,9 +187,33 @@ export type NativeStackNavigationOptions = {
*/
headerTintColor?: string;
/**
* String to display in the header as title. Defaults to scene `title`.
* 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.
*/
headerTitle?: string;
headerLeft?: (props: { tintColor?: string }) => React.ReactNode;
/**
* Function which returns a React Element to display on the right side of the header.
*/
headerRight?: (props: { tintColor?: string }) => React.ReactNode;
/**
* String or a function that returns a React Element to be used by the header.
* Defaults to scene `title`.
*
* It receives `tintColor` and`children` in the options object as an argument.
* The title string is passed in `children`.
*/
headerTitle?:
| string
| ((props: {
/**
* The title text of the header.
*/
children: string;
/**
* Tint color for the header.
*/
tintColor?: string;
}) => React.ReactNode);
/**
* Style object for header title. Supported properties:
* - fontFamily

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { StyleSheet, I18nManager, Platform } from 'react-native';
import { StyleSheet, I18nManager, Platform, View } from 'react-native';
import {
ScreenStackHeaderBackButtonImage,
ScreenStackHeaderCenterView,
@@ -10,6 +10,7 @@ import {
SearchBar,
} from 'react-native-screens';
import { Route, useTheme } from '@react-navigation/native';
import { HeaderTitle } from '@react-navigation/elements';
import type { NativeStackNavigationOptions } from '../types';
import { processFonts } from './FontProcessor';
@@ -22,7 +23,6 @@ export default function HeaderConfig({
headerBackTitle,
headerBackTitleStyle,
headerBackTitleVisible = true,
headerCenter,
headerBackVisible,
headerShadowVisible,
headerLargeStyle,
@@ -48,7 +48,12 @@ export default function HeaderConfig({
title,
}: Props): JSX.Element {
const { colors } = useTheme();
const tintColor = headerTintColor ?? colors.primary;
const tintColor =
headerTintColor != null
? headerTintColor
: Platform.OS === 'ios'
? colors.primary
: colors.text;
const headerBackTitleStyleFlattened =
StyleSheet.flatten(headerBackTitleStyle) || {};
@@ -68,9 +73,14 @@ export default function HeaderConfig({
headerTitleStyleFlattened.fontFamily,
]);
const titleText = title !== undefined ? title : route.name;
const headerLeftElement = headerLeft?.({ tintColor });
const headerRightElement = headerRight?.({ tintColor });
const headerCenterElement = headerCenter?.({ tintColor });
const headerTitleElement =
typeof headerTitle === 'function'
? headerTitle({ tintColor, children: titleText })
: null;
if (Platform.OS === 'ios' && headerSearchBar != null && SearchBar == null) {
throw new Error(
@@ -78,9 +88,18 @@ export default function HeaderConfig({
);
}
/**
* We need to set this in if:
* - Back button should stay visible when `headerLeft` is specified
* - If `headerTitle` for Android is specified, so we only need to remove the title and keep the back button
*/
const backButtonInCustomView = headerBackVisible
? headerLeftElement != null
: Platform.OS === 'android' && headerTitleElement != null;
return (
<ScreenStackHeaderConfig
backButtonInCustomView={headerLeftElement != null && !headerBackVisible}
backButtonInCustomView={backButtonInCustomView}
backgroundColor={
headerStyleFlattened.backgroundColor ??
(headerTranslucent ? 'transparent' : colors.card)
@@ -105,13 +124,7 @@ export default function HeaderConfig({
statusBarAnimation={statusBarAnimation}
statusBarHidden={statusBarHidden}
statusBarStyle={statusBarStyle}
title={
headerTitle !== undefined
? headerTitle
: title !== undefined
? title
: route.name
}
title={typeof headerTitle === 'string' ? headerTitle : titleText}
titleColor={
headerTitleStyleFlattened.color ?? headerTintColor ?? colors.text
}
@@ -124,26 +137,45 @@ export default function HeaderConfig({
headerTranslucent === true
}
>
{headerRightElement != null ? (
<ScreenStackHeaderRightView>
{headerRightElement}
</ScreenStackHeaderRightView>
) : null}
{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}
{typeof headerTitle === 'function' ? (
headerTitleElement
) : (
<HeaderTitle tintColor={tintColor}>{titleText}</HeaderTitle>
)}
</View>
</ScreenStackHeaderLeftView>
) : null}
</>
)}
{headerBackImageSource !== undefined ? (
<ScreenStackHeaderBackButtonImage
key="backImage"
source={headerBackImageSource}
/>
) : null}
{headerLeftElement != null ? (
<ScreenStackHeaderLeftView>
{headerLeftElement}
</ScreenStackHeaderLeftView>
) : null}
{headerCenterElement != null ? (
<ScreenStackHeaderCenterView>
{headerCenterElement}
</ScreenStackHeaderCenterView>
{headerRightElement != null ? (
<ScreenStackHeaderRightView>
{headerRightElement}
</ScreenStackHeaderRightView>
) : null}
{Platform.OS === 'ios' && headerSearchBar != null ? (
<ScreenStackHeaderSearchBarView>
@@ -153,3 +185,10 @@ export default function HeaderConfig({
</ScreenStackHeaderConfig>
);
}
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
alignItems: 'center',
},
});