mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-04-29 04:45:19 +08:00
refactor: move more header stuff to elements package
This commit is contained in:
@@ -14,10 +14,10 @@ import {
|
|||||||
createStackNavigator,
|
createStackNavigator,
|
||||||
StackScreenProps,
|
StackScreenProps,
|
||||||
HeaderBackground,
|
HeaderBackground,
|
||||||
useHeaderHeight,
|
|
||||||
Header,
|
Header,
|
||||||
StackHeaderProps,
|
StackHeaderProps,
|
||||||
} from '@react-navigation/stack';
|
} from '@react-navigation/stack';
|
||||||
|
import { useHeaderHeight } from '@react-navigation/elements';
|
||||||
import BlurView from '../Shared/BlurView';
|
import BlurView from '../Shared/BlurView';
|
||||||
import Article from '../Shared/Article';
|
import Article from '../Shared/Article';
|
||||||
import Albums from '../Shared/Albums';
|
import Albums from '../Shared/Albums';
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type {
|
|||||||
DrawerActionHelpers,
|
DrawerActionHelpers,
|
||||||
RouteProp,
|
RouteProp,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
|
import type { HeaderOptions } from '@react-navigation/elements';
|
||||||
|
|
||||||
export type Scene = {
|
export type Scene = {
|
||||||
route: Route<string>;
|
route: Route<string>;
|
||||||
@@ -34,79 +35,7 @@ export type DrawerNavigationConfig = {
|
|||||||
detachInactiveScreens?: boolean;
|
detachInactiveScreens?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DrawerHeaderOptions = {
|
export type DrawerNavigationOptions = 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;
|
|
||||||
/**
|
|
||||||
* Content of the title element. Usually the title string.
|
|
||||||
*/
|
|
||||||
children?: 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 }) => 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 }) => React.ReactNode;
|
|
||||||
/**
|
|
||||||
* Color for material ripple (Android >= 5.0 only).
|
|
||||||
*/
|
|
||||||
headerPressColorAndroid?: string;
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DrawerNavigationOptions = DrawerHeaderOptions & {
|
|
||||||
/**
|
/**
|
||||||
* Title text for the screen.
|
* Title text for the screen.
|
||||||
*/
|
*/
|
||||||
|
|||||||
54
packages/drawer/src/views/DrawerToggleButton.tsx
Normal file
54
packages/drawer/src/views/DrawerToggleButton.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Image, Platform, StyleSheet } from 'react-native';
|
||||||
|
import { PlatformPressable } from '@react-navigation/elements';
|
||||||
|
import {
|
||||||
|
useNavigation,
|
||||||
|
DrawerActions,
|
||||||
|
ParamListBase,
|
||||||
|
} from '@react-navigation/native';
|
||||||
|
import type { DrawerNavigationProp } from '../types';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
accessibilityLabel?: string;
|
||||||
|
pressColor?: string;
|
||||||
|
pressOpacity?: number;
|
||||||
|
tintColor?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DrawerToggleButton({ tintColor, ...rest }: Props) {
|
||||||
|
const navigation = useNavigation<DrawerNavigationProp<ParamListBase>>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PlatformPressable
|
||||||
|
{...rest}
|
||||||
|
accessible
|
||||||
|
accessibilityRole="button"
|
||||||
|
delayPressIn={0}
|
||||||
|
onPress={() => navigation.dispatch(DrawerActions.toggleDrawer())}
|
||||||
|
style={styles.touchable}
|
||||||
|
hitSlop={Platform.select({
|
||||||
|
ios: undefined,
|
||||||
|
default: { top: 16, right: 16, bottom: 16, left: 16 },
|
||||||
|
})}
|
||||||
|
borderless
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
style={[styles.icon, tintColor ? { tintColor } : null]}
|
||||||
|
source={require('./assets/toggle-drawer-icon.png')}
|
||||||
|
fadeDuration={0}
|
||||||
|
/>
|
||||||
|
</PlatformPressable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
icon: {
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
|
margin: 3,
|
||||||
|
resizeMode: 'contain',
|
||||||
|
},
|
||||||
|
touchable: {
|
||||||
|
marginHorizontal: 11,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -8,7 +8,10 @@ import {
|
|||||||
NativeEventSubscription,
|
NativeEventSubscription,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { ScreenContainer } from 'react-native-screens';
|
import { ScreenContainer } from 'react-native-screens';
|
||||||
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
import {
|
||||||
|
useSafeAreaFrame,
|
||||||
|
useSafeAreaInsets,
|
||||||
|
} from 'react-native-safe-area-context';
|
||||||
import {
|
import {
|
||||||
NavigationHelpersContext,
|
NavigationHelpersContext,
|
||||||
NavigationContext,
|
NavigationContext,
|
||||||
@@ -18,11 +21,11 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
ParamListBase,
|
ParamListBase,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import { SafeAreaProviderCompat } from '@react-navigation/elements';
|
import { Header, SafeAreaProviderCompat } from '@react-navigation/elements';
|
||||||
|
|
||||||
import { GestureHandlerRootView } from './GestureHandler';
|
import { GestureHandlerRootView } from './GestureHandler';
|
||||||
import ScreenFallback from './ScreenFallback';
|
import ScreenFallback from './ScreenFallback';
|
||||||
import Header from './Header';
|
import DrawerToggleButton from './DrawerToggleButton';
|
||||||
import DrawerContent from './DrawerContent';
|
import DrawerContent from './DrawerContent';
|
||||||
import Drawer from './Drawer';
|
import Drawer from './Drawer';
|
||||||
import DrawerStatusContext from '../utils/DrawerStatusContext';
|
import DrawerStatusContext from '../utils/DrawerStatusContext';
|
||||||
@@ -93,7 +96,9 @@ function DrawerViewBase({
|
|||||||
} = descriptors[activeKey].options;
|
} = descriptors[activeKey].options;
|
||||||
|
|
||||||
const [loaded, setLoaded] = React.useState([activeKey]);
|
const [loaded, setLoaded] = React.useState([activeKey]);
|
||||||
|
|
||||||
const dimensions = useSafeAreaFrame();
|
const dimensions = useSafeAreaFrame();
|
||||||
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
|
|
||||||
@@ -149,6 +154,12 @@ function DrawerViewBase({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [headerHeights, setHeaderHeights] = React.useState<
|
||||||
|
Record<string, number>
|
||||||
|
>({});
|
||||||
|
|
||||||
|
const isParentHeaderShown = React.useContext(Header.ShownContext);
|
||||||
|
|
||||||
const renderSceneContent = () => {
|
const renderSceneContent = () => {
|
||||||
return (
|
return (
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -168,10 +179,32 @@ function DrawerViewBase({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
header = (props: DrawerHeaderProps) => <Header {...props} />,
|
header = ({ layout, options }: DrawerHeaderProps) => (
|
||||||
|
<Header
|
||||||
|
{...options}
|
||||||
|
layout={layout}
|
||||||
|
headerTitle={
|
||||||
|
options.headerTitle !== undefined
|
||||||
|
? options.headerTitle
|
||||||
|
: options.title !== undefined
|
||||||
|
? options.title
|
||||||
|
: route.name
|
||||||
|
}
|
||||||
|
headerLeft={
|
||||||
|
options.headerLeft ??
|
||||||
|
((props) => <DrawerToggleButton {...props} />)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
headerShown = true,
|
headerShown = true,
|
||||||
|
headerStatusBarHeight = isParentHeaderShown ? 0 : insets.top,
|
||||||
} = descriptor.options;
|
} = descriptor.options;
|
||||||
|
|
||||||
|
const headerHeight = headerShown
|
||||||
|
? headerHeights[route.key] ??
|
||||||
|
Header.getDefaultHeight(dimensions, headerStatusBarHeight)
|
||||||
|
: 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScreenFallback
|
<ScreenFallback
|
||||||
key={route.key}
|
key={route.key}
|
||||||
@@ -182,16 +215,36 @@ function DrawerViewBase({
|
|||||||
{headerShown ? (
|
{headerShown ? (
|
||||||
<NavigationContext.Provider value={descriptor.navigation}>
|
<NavigationContext.Provider value={descriptor.navigation}>
|
||||||
<NavigationRouteContext.Provider value={route}>
|
<NavigationRouteContext.Provider value={route}>
|
||||||
{header({
|
<View
|
||||||
layout: dimensions,
|
onLayout={(e) => {
|
||||||
route: descriptor.route,
|
const { height } = e.nativeEvent.layout;
|
||||||
navigation: descriptor.navigation as DrawerNavigationProp<ParamListBase>,
|
|
||||||
options: descriptor.options,
|
setHeaderHeights((heights) => {
|
||||||
})}
|
if (heights[route.key] === height) {
|
||||||
|
return heights;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...heights, [route.key]: height };
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{header({
|
||||||
|
layout: dimensions,
|
||||||
|
route: descriptor.route,
|
||||||
|
navigation: descriptor.navigation as DrawerNavigationProp<ParamListBase>,
|
||||||
|
options: descriptor.options,
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
</NavigationRouteContext.Provider>
|
</NavigationRouteContext.Provider>
|
||||||
</NavigationContext.Provider>
|
</NavigationContext.Provider>
|
||||||
) : null}
|
) : null}
|
||||||
{descriptor.render()}
|
<Header.ShownContext.Provider
|
||||||
|
value={isParentHeaderShown || headerShown !== false}
|
||||||
|
>
|
||||||
|
<Header.HeightContext.Provider value={headerHeight}>
|
||||||
|
{descriptor.render()}
|
||||||
|
</Header.HeightContext.Provider>
|
||||||
|
</Header.ShownContext.Provider>
|
||||||
</ScreenFallback>
|
</ScreenFallback>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"typescript": "^4.1.3"
|
"typescript": "^4.1.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
"@react-navigation/native": "^5.0.5",
|
||||||
"react": "*",
|
"react": "*",
|
||||||
"react-native": "*",
|
"react-native": "*",
|
||||||
"react-native-safe-area-context": ">= 3.0.0"
|
"react-native-safe-area-context": ">= 3.0.0"
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Text, View, Image, StyleSheet, Platform } from 'react-native';
|
import { Text, View, StyleSheet, Platform } from 'react-native';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { PlatformPressable } from '@react-navigation/elements';
|
import { useTheme } from '@react-navigation/native';
|
||||||
import { DrawerActions, useTheme } from '@react-navigation/native';
|
import HeaderShownContext from './HeaderShownContext';
|
||||||
import type { Layout, DrawerHeaderProps } from '../types';
|
import HeaderHeightContext from './HeaderHeightContext';
|
||||||
|
import type { HeaderOptions } from './types';
|
||||||
|
|
||||||
export const getDefaultHeaderHeight = (
|
type Layout = { width: number; height: number };
|
||||||
layout: Layout,
|
|
||||||
statusBarHeight: number
|
type Props = HeaderOptions & {
|
||||||
): number => {
|
/**
|
||||||
|
* Layout of the screen.
|
||||||
|
*/
|
||||||
|
layout: Layout;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDefaultHeight = (layout: Layout, statusBarHeight: number): number => {
|
||||||
const isLandscape = layout.width > layout.height;
|
const isLandscape = layout.width > layout.height;
|
||||||
|
|
||||||
let headerHeight;
|
let headerHeight;
|
||||||
@@ -28,17 +35,14 @@ export const getDefaultHeaderHeight = (
|
|||||||
return headerHeight + statusBarHeight;
|
return headerHeight + statusBarHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function HeaderSegment({
|
export default function Header(props: Props) {
|
||||||
route,
|
|
||||||
navigation,
|
|
||||||
options,
|
|
||||||
layout,
|
|
||||||
}: DrawerHeaderProps) {
|
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
|
|
||||||
|
const isParentHeaderShown = React.useContext(HeaderShownContext);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title,
|
layout,
|
||||||
headerTitle,
|
headerTitle,
|
||||||
headerTitleAlign = Platform.select({
|
headerTitleAlign = Platform.select({
|
||||||
ios: 'center',
|
ios: 'center',
|
||||||
@@ -47,52 +51,34 @@ export default function HeaderSegment({
|
|||||||
headerLeft,
|
headerLeft,
|
||||||
headerLeftAccessibilityLabel,
|
headerLeftAccessibilityLabel,
|
||||||
headerRight,
|
headerRight,
|
||||||
|
headerRightAccessibilityLabel,
|
||||||
|
headerPressColor,
|
||||||
|
headerPressOpacity,
|
||||||
headerTitleAllowFontScaling,
|
headerTitleAllowFontScaling,
|
||||||
headerTitleStyle,
|
headerTitleStyle,
|
||||||
headerTintColor,
|
headerTintColor,
|
||||||
headerPressColorAndroid,
|
|
||||||
headerStyle,
|
headerStyle,
|
||||||
headerStatusBarHeight = insets.top,
|
headerStatusBarHeight = isParentHeaderShown ? 0 : insets.top,
|
||||||
} = options;
|
} = props;
|
||||||
|
|
||||||
const currentTitle =
|
const defaultHeight = getDefaultHeight(layout, headerStatusBarHeight);
|
||||||
typeof headerTitle !== 'function' && headerTitle !== undefined
|
|
||||||
? headerTitle
|
|
||||||
: title !== undefined
|
|
||||||
? title
|
|
||||||
: route.name;
|
|
||||||
|
|
||||||
const defaultHeight = getDefaultHeaderHeight(layout, headerStatusBarHeight);
|
const leftButton = headerLeft
|
||||||
|
? headerLeft({
|
||||||
|
tintColor: headerTintColor,
|
||||||
|
pressColor: headerPressColor,
|
||||||
|
pressOpacity: headerPressOpacity,
|
||||||
|
accessibilityLabel: headerLeftAccessibilityLabel,
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
const leftButton = headerLeft ? (
|
|
||||||
headerLeft({ tintColor: headerTintColor })
|
|
||||||
) : (
|
|
||||||
<PlatformPressable
|
|
||||||
accessible
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={headerLeftAccessibilityLabel}
|
|
||||||
delayPressIn={0}
|
|
||||||
onPress={() => navigation.dispatch(DrawerActions.toggleDrawer())}
|
|
||||||
style={styles.touchable}
|
|
||||||
pressColor={headerPressColorAndroid}
|
|
||||||
hitSlop={Platform.select({
|
|
||||||
ios: undefined,
|
|
||||||
default: { top: 16, right: 16, bottom: 16, left: 16 },
|
|
||||||
})}
|
|
||||||
borderless
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
style={[
|
|
||||||
styles.icon,
|
|
||||||
headerTintColor ? { tintColor: headerTintColor } : null,
|
|
||||||
]}
|
|
||||||
source={require('./assets/toggle-drawer-icon.png')}
|
|
||||||
fadeDuration={0}
|
|
||||||
/>
|
|
||||||
</PlatformPressable>
|
|
||||||
);
|
|
||||||
const rightButton = headerRight
|
const rightButton = headerRight
|
||||||
? headerRight({ tintColor: headerTintColor })
|
? headerRight({
|
||||||
|
tintColor: headerTintColor,
|
||||||
|
pressColor: headerPressColor,
|
||||||
|
pressOpacity: headerPressOpacity,
|
||||||
|
accessibilityLabel: headerRightAccessibilityLabel,
|
||||||
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -134,7 +120,6 @@ export default function HeaderSegment({
|
|||||||
>
|
>
|
||||||
{typeof headerTitle === 'function' ? (
|
{typeof headerTitle === 'function' ? (
|
||||||
headerTitle({
|
headerTitle({
|
||||||
children: currentTitle,
|
|
||||||
allowFontScaling: headerTitleAllowFontScaling,
|
allowFontScaling: headerTitleAllowFontScaling,
|
||||||
tintColor: headerTintColor,
|
tintColor: headerTintColor,
|
||||||
style: headerTitleStyle,
|
style: headerTitleStyle,
|
||||||
@@ -152,7 +137,7 @@ export default function HeaderSegment({
|
|||||||
headerTitleStyle,
|
headerTitleStyle,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{currentTitle}
|
{headerTitle}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@@ -167,6 +152,10 @@ export default function HeaderSegment({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Header.getDefaultHeight = getDefaultHeight;
|
||||||
|
Header.ShownContext = HeaderShownContext;
|
||||||
|
Header.HeightContext = HeaderHeightContext;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
...Platform.select({
|
...Platform.select({
|
||||||
@@ -208,15 +197,6 @@ const styles = StyleSheet.create({
|
|||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
icon: {
|
|
||||||
height: 24,
|
|
||||||
width: 24,
|
|
||||||
margin: 3,
|
|
||||||
resizeMode: 'contain',
|
|
||||||
},
|
|
||||||
touchable: {
|
|
||||||
marginHorizontal: 11,
|
|
||||||
},
|
|
||||||
left: {
|
left: {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
flexBasis: 0,
|
flexBasis: 0,
|
||||||
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 PlatformPressable } from './PlatformPressable';
|
||||||
export { default as ResourceSavingScene } from './ResourceSavingScene';
|
export { default as ResourceSavingScene } from './ResourceSavingScene';
|
||||||
export { default as SafeAreaProviderCompat } from './SafeAreaProviderCompat';
|
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;
|
||||||
|
};
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import HeaderHeightContext from './HeaderHeightContext';
|
import HeaderHeightContext from './HeaderHeightContext';
|
||||||
|
|
||||||
export default function useFloatingHeaderHeight() {
|
export default function useHeaderHeight() {
|
||||||
const height = React.useContext(HeaderHeightContext);
|
const height = React.useContext(HeaderHeightContext);
|
||||||
|
|
||||||
if (height === undefined) {
|
if (height === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Couldn't find the header height. Are you inside a screen in Stack?"
|
"Couldn't find the header height. Are you inside a screen in a navigator with a header?"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig",
|
"extends": "../../tsconfig",
|
||||||
|
"references": [
|
||||||
|
{ "path": "../core" },
|
||||||
|
{ "path": "../routers" },
|
||||||
|
{ "path": "../native" }
|
||||||
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./lib/typescript"
|
"outDir": "./lib/typescript"
|
||||||
}
|
}
|
||||||
|
|||||||
21
packages/native/src/getNamedContext.tsx
Normal file
21
packages/native/src/getNamedContext.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
const contexts = new Map<string, React.Context<any>>();
|
||||||
|
|
||||||
|
export default function getNamedContext<T>(
|
||||||
|
name: string,
|
||||||
|
initialValue: T
|
||||||
|
): React.Context<T> {
|
||||||
|
let context = contexts.get(name);
|
||||||
|
|
||||||
|
if (context) {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
context = React.createContext<T>(initialValue);
|
||||||
|
context.displayName = name;
|
||||||
|
|
||||||
|
contexts.set(name, context);
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@@ -17,4 +17,6 @@ export { default as useLinkBuilder } from './useLinkBuilder';
|
|||||||
|
|
||||||
export { default as ServerContainer } from './ServerContainer';
|
export { default as ServerContainer } from './ServerContainer';
|
||||||
|
|
||||||
|
export { default as getNamedContext } from './getNamedContext';
|
||||||
|
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
|||||||
@@ -38,11 +38,9 @@ export {
|
|||||||
* Utilities
|
* Utilities
|
||||||
*/
|
*/
|
||||||
export { default as CardAnimationContext } from './utils/CardAnimationContext';
|
export { default as CardAnimationContext } from './utils/CardAnimationContext';
|
||||||
export { default as HeaderHeightContext } from './utils/HeaderHeightContext';
|
|
||||||
export { default as GestureHandlerRefContext } from './utils/GestureHandlerRefContext';
|
export { default as GestureHandlerRefContext } from './utils/GestureHandlerRefContext';
|
||||||
|
|
||||||
export { default as useCardAnimation } from './utils/useCardAnimation';
|
export { default as useCardAnimation } from './utils/useCardAnimation';
|
||||||
export { default as useHeaderHeight } from './utils/useHeaderHeight';
|
|
||||||
export { default as useGestureHandlerRef } from './utils/useGestureHandlerRef';
|
export { default as useGestureHandlerRef } from './utils/useGestureHandlerRef';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
export default React.createContext<number | undefined>(undefined);
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
const HeaderShownContext = React.createContext(false);
|
|
||||||
|
|
||||||
export default HeaderShownContext;
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { StackActions, useNavigationState } from '@react-navigation/native';
|
import { StackActions, useNavigationState } from '@react-navigation/native';
|
||||||
|
import { Header as BaseHeader } from '@react-navigation/elements';
|
||||||
|
|
||||||
import HeaderSegment from './HeaderSegment';
|
import HeaderSegment from './HeaderSegment';
|
||||||
import HeaderTitle from './HeaderTitle';
|
import HeaderTitle from './HeaderTitle';
|
||||||
import HeaderShownContext from '../../utils/HeaderShownContext';
|
|
||||||
import ModalPresentationContext from '../../utils/ModalPresentationContext';
|
import ModalPresentationContext from '../../utils/ModalPresentationContext';
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from '../../utils/debounce';
|
||||||
import type { StackHeaderProps, StackHeaderTitleProps } from '../../types';
|
import type { StackHeaderProps, StackHeaderTitleProps } from '../../types';
|
||||||
@@ -57,7 +57,7 @@ export default React.memo(function Header({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const isModal = React.useContext(ModalPresentationContext);
|
const isModal = React.useContext(ModalPresentationContext);
|
||||||
const isParentHeaderShown = React.useContext(HeaderShownContext);
|
const isParentHeaderShown = React.useContext(BaseHeader.ShownContext);
|
||||||
const isFirstRouteInParent = useNavigationState(
|
const isFirstRouteInParent = useNavigationState(
|
||||||
(state) => state.routes[0].key === route.key
|
(state) => state.routes[0].key === route.key
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
ViewStyle,
|
ViewStyle,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import type { EdgeInsets } from 'react-native-safe-area-context';
|
import type { EdgeInsets } from 'react-native-safe-area-context';
|
||||||
|
import { Header as BaseHeader } from '@react-navigation/elements';
|
||||||
import HeaderBackButton from './HeaderBackButton';
|
import HeaderBackButton from './HeaderBackButton';
|
||||||
import HeaderBackground from './HeaderBackground';
|
import HeaderBackground from './HeaderBackground';
|
||||||
import memoize from '../../utils/memoize';
|
import memoize from '../../utils/memoize';
|
||||||
@@ -51,29 +52,6 @@ const warnIfHeaderStylesDefined = (styles: Record<string, any>) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDefaultHeaderHeight = (
|
|
||||||
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 HeaderSegment(props: Props) {
|
export default function HeaderSegment(props: Props) {
|
||||||
const [leftLabelLayout, setLeftLabelLayout] = React.useState<
|
const [leftLabelLayout, setLeftLabelLayout] = React.useState<
|
||||||
Layout | undefined
|
Layout | undefined
|
||||||
@@ -176,7 +154,10 @@ export default function HeaderSegment(props: Props) {
|
|||||||
styleInterpolator,
|
styleInterpolator,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const defaultHeight = getDefaultHeaderHeight(layout, headerStatusBarHeight);
|
const defaultHeight = BaseHeader.getDefaultHeight(
|
||||||
|
layout,
|
||||||
|
headerStatusBarHeight
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
height = defaultHeight,
|
height = defaultHeight,
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Animated, View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
import { Animated, View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
||||||
import { Route, useTheme } from '@react-navigation/native';
|
import { Route, useTheme } from '@react-navigation/native';
|
||||||
|
import { Header as BaseHeader } from '@react-navigation/elements';
|
||||||
import type { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
import type { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
||||||
import Card from './Card';
|
import Card from './Card';
|
||||||
import { forModalPresentationIOS } from '../../TransitionConfigs/CardStyleInterpolators';
|
import { forModalPresentationIOS } from '../../TransitionConfigs/CardStyleInterpolators';
|
||||||
import HeaderHeightContext from '../../utils/HeaderHeightContext';
|
|
||||||
import HeaderShownContext from '../../utils/HeaderShownContext';
|
|
||||||
import PreviousSceneContext from '../../utils/PreviousSceneContext';
|
import PreviousSceneContext from '../../utils/PreviousSceneContext';
|
||||||
import ModalPresentationContext from '../../utils/ModalPresentationContext';
|
import ModalPresentationContext from '../../utils/ModalPresentationContext';
|
||||||
import type {
|
import type {
|
||||||
@@ -244,13 +243,13 @@ function CardContainer({
|
|||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<View style={styles.scene}>
|
<View style={styles.scene}>
|
||||||
<PreviousSceneContext.Provider value={previousScene}>
|
<PreviousSceneContext.Provider value={previousScene}>
|
||||||
<HeaderShownContext.Provider
|
<BaseHeader.ShownContext.Provider
|
||||||
value={isParentHeaderShown || headerShown !== false}
|
value={isParentHeaderShown || headerShown !== false}
|
||||||
>
|
>
|
||||||
<HeaderHeightContext.Provider value={headerHeight}>
|
<BaseHeader.HeightContext.Provider value={headerHeight}>
|
||||||
{renderScene({ route: scene.descriptor.route })}
|
{renderScene({ route: scene.descriptor.route })}
|
||||||
</HeaderHeightContext.Provider>
|
</BaseHeader.HeightContext.Provider>
|
||||||
</HeaderShownContext.Provider>
|
</BaseHeader.ShownContext.Provider>
|
||||||
</PreviousSceneContext.Provider>
|
</PreviousSceneContext.Provider>
|
||||||
</View>
|
</View>
|
||||||
{headerMode !== 'float' ? (
|
{headerMode !== 'float' ? (
|
||||||
|
|||||||
@@ -11,14 +11,16 @@ import type {
|
|||||||
Route,
|
Route,
|
||||||
StackNavigationState,
|
StackNavigationState,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import { SafeAreaProviderCompat } from '@react-navigation/elements';
|
import {
|
||||||
|
Header as BaseHeader,
|
||||||
|
SafeAreaProviderCompat,
|
||||||
|
} from '@react-navigation/elements';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MaybeScreenContainer,
|
MaybeScreenContainer,
|
||||||
MaybeScreen,
|
MaybeScreen,
|
||||||
shouldUseActivityState,
|
shouldUseActivityState,
|
||||||
} from '../Screens';
|
} from '../Screens';
|
||||||
import { getDefaultHeaderHeight } from '../Header/HeaderSegment';
|
|
||||||
import type { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
import type { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
||||||
import CardContainer from './CardContainer';
|
import CardContainer from './CardContainer';
|
||||||
import {
|
import {
|
||||||
@@ -118,7 +120,7 @@ const getHeaderHeights = (
|
|||||||
acc[curr.key] =
|
acc[curr.key] =
|
||||||
typeof height === 'number'
|
typeof height === 'number'
|
||||||
? height
|
? height
|
||||||
: getDefaultHeaderHeight(layout, headerStatusBarHeight);
|
: BaseHeader.getDefaultHeight(layout, headerStatusBarHeight);
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ import {
|
|||||||
Route,
|
Route,
|
||||||
ParamListBase,
|
ParamListBase,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import { SafeAreaProviderCompat } from '@react-navigation/elements';
|
import {
|
||||||
|
Header as BaseHeader,
|
||||||
|
SafeAreaProviderCompat,
|
||||||
|
} from '@react-navigation/elements';
|
||||||
|
|
||||||
import { GestureHandlerRootView } from '../GestureHandler';
|
import { GestureHandlerRootView } from '../GestureHandler';
|
||||||
import CardStack from './CardStack';
|
import CardStack from './CardStack';
|
||||||
@@ -19,7 +22,6 @@ import KeyboardManager from '../KeyboardManager';
|
|||||||
import HeaderContainer, {
|
import HeaderContainer, {
|
||||||
Props as HeaderContainerProps,
|
Props as HeaderContainerProps,
|
||||||
} from '../Header/HeaderContainer';
|
} from '../Header/HeaderContainer';
|
||||||
import HeaderShownContext from '../../utils/HeaderShownContext';
|
|
||||||
import type {
|
import type {
|
||||||
StackNavigationHelpers,
|
StackNavigationHelpers,
|
||||||
StackNavigationConfig,
|
StackNavigationConfig,
|
||||||
@@ -460,7 +462,7 @@ export default class StackView extends React.Component<Props, State> {
|
|||||||
{(insets) => (
|
{(insets) => (
|
||||||
<KeyboardManager enabled={keyboardHandlingEnabled !== false}>
|
<KeyboardManager enabled={keyboardHandlingEnabled !== false}>
|
||||||
{(props) => (
|
{(props) => (
|
||||||
<HeaderShownContext.Consumer>
|
<BaseHeader.ShownContext.Consumer>
|
||||||
{(isParentHeaderShown) => (
|
{(isParentHeaderShown) => (
|
||||||
<CardStack
|
<CardStack
|
||||||
mode={mode}
|
mode={mode}
|
||||||
@@ -487,7 +489,7 @@ export default class StackView extends React.Component<Props, State> {
|
|||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</HeaderShownContext.Consumer>
|
</BaseHeader.ShownContext.Consumer>
|
||||||
)}
|
)}
|
||||||
</KeyboardManager>
|
</KeyboardManager>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user