refactor: move more header stuff to elements package

This commit is contained in:
Satyajit Sahoo
2021-02-04 15:07:22 +01:00
parent 07ba7a9687
commit 509ca49b64
22 changed files with 324 additions and 200 deletions

View File

@@ -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"

View 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',
},
});

View File

@@ -0,0 +1,8 @@
import { getNamedContext } from '@react-navigation/native';
const HeaderHeightContext = getNamedContext<number | undefined>(
'HeaderHeightContext',
undefined
);
export default HeaderHeightContext;

View File

@@ -0,0 +1,5 @@
import { getNamedContext } from '@react-navigation/native';
const HeaderShownContext = getNamedContext('HeaderShownContext', false);
export default HeaderShownContext;

View File

@@ -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';

View 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;
};

View 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;
}

View File

@@ -1,5 +1,10 @@
{
"extends": "../../tsconfig",
"references": [
{ "path": "../core" },
{ "path": "../routers" },
{ "path": "../native" }
],
"compilerOptions": {
"outDir": "./lib/typescript"
}