import { getHeaderTitle, HeaderBackContext } from '@react-navigation/elements'; import { NavigationContext, NavigationRouteContext, ParamListBase, Route, } from '@react-navigation/native'; import * as React from 'react'; import { Animated, StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; import { forNoAnimation, forSlideLeft, forSlideRight, forSlideUp, } from '../../TransitionConfigs/HeaderStyleInterpolators'; import type { Layout, Scene, StackHeaderMode, StackHeaderProps, StackNavigationProp, } from '../../types'; import Header from './Header'; export type Props = { mode: StackHeaderMode; layout: Layout; scenes: (Scene | undefined)[]; getPreviousScene: (props: { route: Route }) => Scene | undefined; getFocusedRoute: () => Route; onContentHeightChange?: (props: { route: Route; height: number; }) => void; style?: Animated.WithAnimatedValue>; }; export default function HeaderContainer({ mode, scenes, layout, getPreviousScene, getFocusedRoute, onContentHeightChange, style, }: Props) { const focusedRoute = getFocusedRoute(); const parentHeaderBack = React.useContext(HeaderBackContext); return ( {scenes.slice(-3).map((scene, i, self) => { if ((mode === 'screen' && i !== self.length - 1) || !scene) { return null; } const { header, headerMode, headerShown = true, headerTransparent, headerStyleInterpolator, } = scene.descriptor.options; if (headerMode !== mode || !headerShown) { return null; } const isFocused = focusedRoute.key === scene.descriptor.route.key; const previousScene = getPreviousScene({ route: scene.descriptor.route, }); let headerBack = parentHeaderBack; if (previousScene) { const { options, route } = previousScene.descriptor; headerBack = previousScene ? { title: getHeaderTitle(options, route.name) } : parentHeaderBack; } // If the screen is next to a headerless screen, we need to make the header appear static // This makes the header look like it's moving with the screen const previousDescriptor = self[i - 1]?.descriptor; const nextDescriptor = self[i + 1]?.descriptor; const { headerShown: previousHeaderShown = true, headerMode: previousHeaderMode, } = previousDescriptor?.options || {}; // If any of the next screens don't have a header or header is part of the screen // Then we need to move this header offscreen so that it doesn't cover it const nextHeaderlessScene = self.slice(i + 1).find((scene) => { const { headerShown: currentHeaderShown = true, headerMode: currentHeaderMode, } = scene?.descriptor.options || {}; return currentHeaderShown === false || currentHeaderMode === 'screen'; }); const { gestureDirection: nextHeaderlessGestureDirection } = nextHeaderlessScene?.descriptor.options || {}; const isHeaderStatic = ((previousHeaderShown === false || previousHeaderMode === 'screen') && // We still need to animate when coming back from next scene // A hacky way to check this is if the next scene exists !nextDescriptor) || nextHeaderlessScene; const props: StackHeaderProps = { layout, back: headerBack, progress: scene.progress, options: scene.descriptor.options, route: scene.descriptor.route, navigation: scene.descriptor .navigation as StackNavigationProp, styleInterpolator: mode === 'float' ? isHeaderStatic ? nextHeaderlessGestureDirection === 'vertical' || nextHeaderlessGestureDirection === 'vertical-inverted' ? forSlideUp : nextHeaderlessGestureDirection === 'horizontal-inverted' ? forSlideRight : forSlideLeft : headerStyleInterpolator : forNoAnimation, }; return ( { const { height } = e.nativeEvent.layout; onContentHeightChange({ route: scene.descriptor.route, height, }); } : undefined } pointerEvents={isFocused ? 'box-none' : 'none'} accessibilityElementsHidden={!isFocused} importantForAccessibility={ isFocused ? 'auto' : 'no-hide-descendants' } style={ // Avoid positioning the focused header absolutely // Otherwise accessibility tools don't seem to be able to find it (mode === 'float' && !isFocused) || headerTransparent ? styles.header : null } > {header !== undefined ? header(props) :
} ); })} ); } const styles = StyleSheet.create({ header: { position: 'absolute', top: 0, left: 0, right: 0, }, });