mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-04-24 04:25:34 +08:00
refactor: move more header stuff to elements package
This commit is contained in:
@@ -48,6 +48,7 @@
|
||||
"typescript": "^4.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/native": "^5.0.5",
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-safe-area-context": ">= 3.0.0"
|
||||
|
||||
212
packages/elements/src/Header.tsx
Normal file
212
packages/elements/src/Header.tsx
Normal file
@@ -0,0 +1,212 @@
|
||||
import * as React from 'react';
|
||||
import { Text, View, StyleSheet, Platform } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { useTheme } from '@react-navigation/native';
|
||||
import HeaderShownContext from './HeaderShownContext';
|
||||
import HeaderHeightContext from './HeaderHeightContext';
|
||||
import type { HeaderOptions } from './types';
|
||||
|
||||
type Layout = { width: number; height: number };
|
||||
|
||||
type Props = HeaderOptions & {
|
||||
/**
|
||||
* Layout of the screen.
|
||||
*/
|
||||
layout: Layout;
|
||||
};
|
||||
|
||||
const getDefaultHeight = (layout: Layout, statusBarHeight: number): number => {
|
||||
const isLandscape = layout.width > layout.height;
|
||||
|
||||
let headerHeight;
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
if (isLandscape && !Platform.isPad) {
|
||||
headerHeight = 32;
|
||||
} else {
|
||||
headerHeight = 44;
|
||||
}
|
||||
} else if (Platform.OS === 'android') {
|
||||
headerHeight = 56;
|
||||
} else {
|
||||
headerHeight = 64;
|
||||
}
|
||||
|
||||
return headerHeight + statusBarHeight;
|
||||
};
|
||||
|
||||
export default function Header(props: Props) {
|
||||
const insets = useSafeAreaInsets();
|
||||
const { colors } = useTheme();
|
||||
|
||||
const isParentHeaderShown = React.useContext(HeaderShownContext);
|
||||
|
||||
const {
|
||||
layout,
|
||||
headerTitle,
|
||||
headerTitleAlign = Platform.select({
|
||||
ios: 'center',
|
||||
default: 'left',
|
||||
}),
|
||||
headerLeft,
|
||||
headerLeftAccessibilityLabel,
|
||||
headerRight,
|
||||
headerRightAccessibilityLabel,
|
||||
headerPressColor,
|
||||
headerPressOpacity,
|
||||
headerTitleAllowFontScaling,
|
||||
headerTitleStyle,
|
||||
headerTintColor,
|
||||
headerStyle,
|
||||
headerStatusBarHeight = isParentHeaderShown ? 0 : insets.top,
|
||||
} = props;
|
||||
|
||||
const defaultHeight = getDefaultHeight(layout, headerStatusBarHeight);
|
||||
|
||||
const leftButton = headerLeft
|
||||
? headerLeft({
|
||||
tintColor: headerTintColor,
|
||||
pressColor: headerPressColor,
|
||||
pressOpacity: headerPressOpacity,
|
||||
accessibilityLabel: headerLeftAccessibilityLabel,
|
||||
})
|
||||
: null;
|
||||
|
||||
const rightButton = headerRight
|
||||
? headerRight({
|
||||
tintColor: headerTintColor,
|
||||
pressColor: headerPressColor,
|
||||
pressOpacity: headerPressOpacity,
|
||||
accessibilityLabel: headerRightAccessibilityLabel,
|
||||
})
|
||||
: null;
|
||||
|
||||
return (
|
||||
<View
|
||||
pointerEvents="box-none"
|
||||
style={[
|
||||
{
|
||||
height: defaultHeight,
|
||||
backgroundColor: colors.card,
|
||||
borderBottomColor: colors.border,
|
||||
shadowColor: colors.border,
|
||||
},
|
||||
styles.container,
|
||||
headerStyle,
|
||||
]}
|
||||
>
|
||||
<View pointerEvents="none" style={{ height: headerStatusBarHeight }} />
|
||||
<View pointerEvents="box-none" style={styles.content}>
|
||||
<View
|
||||
pointerEvents="box-none"
|
||||
style={[styles.left, { marginLeft: insets.left }]}
|
||||
>
|
||||
{leftButton}
|
||||
</View>
|
||||
<View
|
||||
pointerEvents="box-none"
|
||||
style={[
|
||||
headerTitleAlign === 'left'
|
||||
? {
|
||||
marginLeft: (leftButton ? 72 : 16) + insets.left,
|
||||
marginRight: (rightButton ? 72 : 16) + insets.right,
|
||||
}
|
||||
: {
|
||||
marginHorizontal:
|
||||
(leftButton ? 32 : 16) +
|
||||
Math.max(insets.left, insets.right),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{typeof headerTitle === 'function' ? (
|
||||
headerTitle({
|
||||
allowFontScaling: headerTitleAllowFontScaling,
|
||||
tintColor: headerTintColor,
|
||||
style: headerTitleStyle,
|
||||
})
|
||||
) : (
|
||||
<Text
|
||||
accessibilityRole="header"
|
||||
aria-level="1"
|
||||
numberOfLines={1}
|
||||
allowFontScaling={headerTitleAllowFontScaling}
|
||||
style={[
|
||||
styles.title,
|
||||
{ color: headerTintColor ?? colors.text },
|
||||
styles.title,
|
||||
headerTitleStyle,
|
||||
]}
|
||||
>
|
||||
{headerTitle}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<View
|
||||
pointerEvents="box-none"
|
||||
style={[styles.right, { marginRight: insets.right }]}
|
||||
>
|
||||
{rightButton}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
Header.getDefaultHeight = getDefaultHeight;
|
||||
Header.ShownContext = HeaderShownContext;
|
||||
Header.HeightContext = HeaderHeightContext;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
...Platform.select({
|
||||
android: {
|
||||
elevation: 4,
|
||||
},
|
||||
ios: {
|
||||
shadowOpacity: 0.85,
|
||||
shadowRadius: 0,
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: StyleSheet.hairlineWidth,
|
||||
},
|
||||
},
|
||||
default: {
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
},
|
||||
}),
|
||||
zIndex: 1,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
title: Platform.select({
|
||||
ios: {
|
||||
fontSize: 17,
|
||||
fontWeight: '600',
|
||||
},
|
||||
android: {
|
||||
fontSize: 20,
|
||||
fontFamily: 'sans-serif-medium',
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
default: {
|
||||
fontSize: 18,
|
||||
fontWeight: '500',
|
||||
},
|
||||
}),
|
||||
left: {
|
||||
flexGrow: 1,
|
||||
flexBasis: 0,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
right: {
|
||||
flexGrow: 1,
|
||||
flexBasis: 0,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
});
|
||||
8
packages/elements/src/HeaderHeightContext.tsx
Normal file
8
packages/elements/src/HeaderHeightContext.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import { getNamedContext } from '@react-navigation/native';
|
||||
|
||||
const HeaderHeightContext = getNamedContext<number | undefined>(
|
||||
'HeaderHeightContext',
|
||||
undefined
|
||||
);
|
||||
|
||||
export default HeaderHeightContext;
|
||||
5
packages/elements/src/HeaderShownContext.tsx
Normal file
5
packages/elements/src/HeaderShownContext.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { getNamedContext } from '@react-navigation/native';
|
||||
|
||||
const HeaderShownContext = getNamedContext('HeaderShownContext', false);
|
||||
|
||||
export default HeaderShownContext;
|
||||
@@ -1,3 +1,8 @@
|
||||
export { default as Header } from './Header';
|
||||
export { default as PlatformPressable } from './PlatformPressable';
|
||||
export { default as ResourceSavingScene } from './ResourceSavingScene';
|
||||
export { default as SafeAreaProviderCompat } from './SafeAreaProviderCompat';
|
||||
|
||||
export { default as useHeaderHeight } from './useHeaderHeight';
|
||||
|
||||
export * from './types';
|
||||
|
||||
87
packages/elements/src/types.tsx
Normal file
87
packages/elements/src/types.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import type { StyleProp, ViewStyle, TextStyle } from 'react-native';
|
||||
|
||||
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`.
|
||||
*/
|
||||
headerTitle?:
|
||||
| string
|
||||
| ((props: {
|
||||
/**
|
||||
* Whether title font should scale to respect Text Size accessibility settings.
|
||||
*/
|
||||
allowFontScaling?: boolean;
|
||||
/**
|
||||
* Tint color for the header.
|
||||
*/
|
||||
tintColor?: string;
|
||||
/**
|
||||
* Style object for the title element.
|
||||
*/
|
||||
style?: StyleProp<TextStyle>;
|
||||
}) => React.ReactNode);
|
||||
/**
|
||||
* How to align the the header title.
|
||||
* Defaults to `center` on iOS and `left` on Android.
|
||||
*/
|
||||
headerTitleAlign?: 'left' | 'center';
|
||||
/**
|
||||
* Style object for the title component.
|
||||
*/
|
||||
headerTitleStyle?: StyleProp<TextStyle>;
|
||||
/**
|
||||
* Whether header title font should scale to respect Text Size accessibility settings. Defaults to `false`.
|
||||
*/
|
||||
headerTitleAllowFontScaling?: boolean;
|
||||
/**
|
||||
* Function which returns a React Element to display on the left side of the header.
|
||||
*/
|
||||
headerLeft?: (props: {
|
||||
tintColor?: string;
|
||||
pressColor?: string;
|
||||
pressOpacity?: number;
|
||||
accessibilityLabel?: string;
|
||||
}) => React.ReactNode;
|
||||
/**
|
||||
* Accessibility label for the header left button.
|
||||
*/
|
||||
headerLeftAccessibilityLabel?: string;
|
||||
/**
|
||||
* Function which returns a React Element to display on the right side of the header.
|
||||
*/
|
||||
headerRight?: (props: {
|
||||
tintColor?: string;
|
||||
pressColor?: string;
|
||||
pressOpacity?: number;
|
||||
accessibilityLabel?: string;
|
||||
}) => React.ReactNode;
|
||||
/**
|
||||
* Accessibility label for the header right button.
|
||||
*/
|
||||
headerRightAccessibilityLabel?: string;
|
||||
/**
|
||||
* Color for material ripple (Android >= 5.0 only).
|
||||
*/
|
||||
headerPressColor?: string;
|
||||
/**
|
||||
* Color for material ripple (Android >= 5.0 only).
|
||||
*/
|
||||
headerPressOpacity?: number;
|
||||
/**
|
||||
* Tint color for the header.
|
||||
*/
|
||||
headerTintColor?: string;
|
||||
/**
|
||||
* Style object for the header. You can specify a custom background color here, for example.
|
||||
*/
|
||||
headerStyle?: StyleProp<ViewStyle>;
|
||||
/**
|
||||
* Extra padding to add at the top of header to account for translucent status bar.
|
||||
* By default, it uses the top value from the safe area insets of the device.
|
||||
* Pass 0 or a custom value to disable the default behaviour, and customize the height.
|
||||
*/
|
||||
headerStatusBarHeight?: number;
|
||||
};
|
||||
14
packages/elements/src/useHeaderHeight.tsx
Normal file
14
packages/elements/src/useHeaderHeight.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import HeaderHeightContext from './HeaderHeightContext';
|
||||
|
||||
export default function useHeaderHeight() {
|
||||
const height = React.useContext(HeaderHeightContext);
|
||||
|
||||
if (height === undefined) {
|
||||
throw new Error(
|
||||
"Couldn't find the header height. Are you inside a screen in a navigator with a header?"
|
||||
);
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig",
|
||||
"references": [
|
||||
{ "path": "../core" },
|
||||
{ "path": "../routers" },
|
||||
{ "path": "../native" }
|
||||
],
|
||||
"compilerOptions": {
|
||||
"outDir": "./lib/typescript"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user