mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-06-16 21:21:27 +08:00
refactor: align header options for native stack and regular stack
This commit is contained in:
@@ -118,6 +118,7 @@ export default function NativeStackScreen({
|
||||
React.useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerShown: false,
|
||||
gestureEnabled: false,
|
||||
});
|
||||
}, [navigation]);
|
||||
|
||||
|
||||
185
example/src/Screens/NativeStackHeaderCustomization.tsx
Normal file
185
example/src/Screens/NativeStackHeaderCustomization.tsx
Normal 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,
|
||||
},
|
||||
});
|
||||
@@ -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',
|
||||
|
||||
@@ -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`.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user