Skip to content

Commit

Permalink
refactor: simplify props for stack and drawer headers
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Previously, the stack header accepted scene and previous scene which contained things such as descriptor, navigation prop, progress etc. The commit simplifies them to pass `route`, `navigation`, `options` and `progress` directly to the header. Similaryly, the `previous` argument now contains `options`, `route` and `progress`.
  • Loading branch information
satya164 committed Nov 12, 2020
1 parent d5c091c commit 4cad132
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 100 deletions.
2 changes: 1 addition & 1 deletion example/src/Screens/StackHeaderCustomization.tsx
Expand Up @@ -91,7 +91,7 @@ type Props = Partial<React.ComponentProps<typeof SimpleStack.Navigator>> &
StackScreenProps<ParamListBase>;

function CustomHeader(props: StackHeaderProps) {
const { current, next } = props.scene.progress;
const { current, next } = props.progress;

const progress = Animated.add(current, next || 0);
const opacity = progress.interpolate({
Expand Down
15 changes: 10 additions & 5 deletions packages/drawer/src/types.tsx
Expand Up @@ -278,12 +278,17 @@ export type DrawerHeaderProps = {
*/
layout: Layout;
/**
* Object representing the current scene, such as the route object and descriptor.
* Options for the current screen.
*/
scene: {
route: Route<string>;
descriptor: DrawerDescriptor;
};
options: DrawerNavigationOptions;
/**
* Route object for the current screen.
*/
route: Route<string>;
/**
* Navigation prop for the header.
*/
navigation: DrawerNavigationProp<ParamListBase>;
};

export type DrawerNavigationEventMap = {
Expand Down
7 changes: 6 additions & 1 deletion packages/drawer/src/views/DrawerView.tsx
Expand Up @@ -34,6 +34,7 @@ import type {
DrawerNavigationHelpers,
DrawerContentComponentProps,
DrawerHeaderProps,
DrawerNavigationProp,
} from '../types';

type Props = DrawerNavigationConfig & {
Expand Down Expand Up @@ -191,7 +192,11 @@ export default function DrawerView({
<NavigationRouteContext.Provider value={route}>
{header({
layout: dimensions,
scene: { route, descriptor },
route: descriptor.route,
navigation: descriptor.navigation as DrawerNavigationProp<
ParamListBase
>,
options: descriptor.options,
})}
</NavigationRouteContext.Provider>
</NavigationContext.Provider>
Expand Down
15 changes: 9 additions & 6 deletions packages/drawer/src/views/Header.tsx
Expand Up @@ -28,7 +28,12 @@ export const getDefaultHeaderHeight = (
return headerHeight + statusBarHeight;
};

export default function HeaderSegment({ scene, layout }: DrawerHeaderProps) {
export default function HeaderSegment({
route,
navigation,
options,
layout,
}: DrawerHeaderProps) {
const insets = useSafeAreaInsets();
const { colors } = useTheme();

Expand All @@ -48,14 +53,14 @@ export default function HeaderSegment({ scene, layout }: DrawerHeaderProps) {
headerPressColorAndroid,
headerStyle,
headerStatusBarHeight = insets.top,
} = scene.descriptor.options;
} = options;

const currentTitle =
typeof headerTitle !== 'function' && headerTitle !== undefined
? headerTitle
: title !== undefined
? title
: scene.route.name;
: route.name;

const defaultHeight = getDefaultHeaderHeight(layout, headerStatusBarHeight);

Expand All @@ -69,9 +74,7 @@ export default function HeaderSegment({ scene, layout }: DrawerHeaderProps) {
accessibilityLabel={headerLeftAccessibilityLabel}
accessibilityTraits="button"
delayPressIn={0}
onPress={() =>
scene.descriptor.navigation.dispatch(DrawerActions.toggleDrawer())
}
onPress={() => navigation.dispatch(DrawerActions.toggleDrawer())}
style={styles.touchable}
pressColor={headerPressColorAndroid}
hitSlop={Platform.select({
Expand Down
76 changes: 45 additions & 31 deletions packages/stack/src/types.tsx
Expand Up @@ -75,34 +75,32 @@ export type GestureDirection =
| 'vertical'
| 'vertical-inverted';

export type Scene<T> = {
export type Scene = {
/**
* Current route object,
*/
route: T;
/**
* Descriptor object for the route containing options and navigation object.
* Descriptor object for the screen.
*/
descriptor: StackDescriptor;
/**
* Animated nodes representing the progress of the animation.
*/
progress: {
/**
* Progress value of the current screen.
*/
current: Animated.AnimatedInterpolation;
/**
* Progress value for the screen after this one in the stack.
* This can be `undefined` in case the screen animating is the last one.
*/
next?: Animated.AnimatedInterpolation;
/**
* Progress value for the screen before this one in the stack.
* This can be `undefined` in case the screen animating is the first one.
*/
previous?: Animated.AnimatedInterpolation;
};
progress: SceneProgress;
};

export type SceneProgress = {
/**
* Progress value of the current screen.
*/
current: Animated.AnimatedInterpolation;
/**
* Progress value for the screen after this one in the stack.
* This can be `undefined` in case the screen animating is the last one.
*/
next?: Animated.AnimatedInterpolation;
/**
* Progress value for the screen before this one in the stack.
* This can be `undefined` in case the screen animating is the first one.
*/
previous?: Animated.AnimatedInterpolation;
};

export type StackHeaderMode = 'float' | 'screen';
Expand Down Expand Up @@ -221,11 +219,6 @@ export type StackHeaderOptions = {
};

export type StackHeaderProps = {
/**
* Mode of the header: `float` renders a single floating header across all screens,
* `screen` renders separate headers for each screen.
*/
mode: 'float' | 'screen';
/**
* Layout of the screen.
*/
Expand All @@ -235,13 +228,34 @@ export type StackHeaderProps = {
*/
insets: EdgeInsets;
/**
* Object representing the current scene, such as the route object and animation progress.
* Object representing the previous scene.
*/
scene: Scene<Route<string>>;
previous?: {
/**
* Options for the previous screen.
*/
options: StackNavigationOptions;
/**
* Route object for the current screen.
*/
route: Route<string>;
/**
* Animated nodes representing the progress of the animation of the previous screen.
*/
progress: SceneProgress;
};
/**
* Object representing the previous scene.
* Animated nodes representing the progress of the animation.
*/
progress: SceneProgress;
/**
* Options for the current screen.
*/
options: StackNavigationOptions;
/**
* Route object for the current screen.
*/
previous?: Scene<Route<string>>;
route: Route<string>;
/**
* Navigation prop for the header.
*/
Expand Down
5 changes: 1 addition & 4 deletions packages/stack/src/utils/PreviousSceneContext.tsx
@@ -1,9 +1,6 @@
import * as React from 'react';
import type { Route } from '@react-navigation/native';
import type { Scene } from '../types';

const PreviousSceneContext = React.createContext<
Scene<Route<string>> | undefined
>(undefined);
const PreviousSceneContext = React.createContext<Scene | undefined>(undefined);

export default PreviousSceneContext;
17 changes: 9 additions & 8 deletions packages/stack/src/views/Header/Header.tsx
Expand Up @@ -9,21 +9,22 @@ import debounce from '../../utils/debounce';
import type { StackHeaderProps, StackHeaderTitleProps } from '../../types';

export default React.memo(function Header({
scene,
previous,
layout,
insets,
progress,
options,
route,
navigation,
styleInterpolator,
}: StackHeaderProps) {
const { options } = scene.descriptor;
const title =
typeof options.headerTitle !== 'function' &&
options.headerTitle !== undefined
? options.headerTitle
: options.title !== undefined
? options.title
: scene.route.name;
: route.name;

let leftLabel;

Expand All @@ -32,7 +33,7 @@ export default React.memo(function Header({
if (options.headerBackTitle !== undefined) {
leftLabel = options.headerBackTitle;
} else if (previous) {
const o = previous.descriptor.options;
const o = previous.options;

leftLabel =
typeof o.headerTitle !== 'function' && o.headerTitle !== undefined
Expand All @@ -48,17 +49,17 @@ export default React.memo(function Header({
if (navigation.isFocused() && navigation.canGoBack()) {
navigation.dispatch({
...StackActions.pop(),
source: scene.route.key,
source: route.key,
});
}
}, 50),
[navigation, scene.route.key]
[navigation, route.key]
);

const isModal = React.useContext(ModalPresentationContext);
const isParentHeaderShown = React.useContext(HeaderShownContext);
const isFirstRouteInParent = useNavigationState(
(state) => state.routes[0].key === scene.route.key
(state) => state.routes[0].key === route.key
);

const statusBarHeight =
Expand All @@ -67,9 +68,9 @@ export default React.memo(function Header({
return (
<HeaderSegment
{...options}
progress={progress}
insets={insets}
layout={layout}
scene={scene}
title={title}
leftLabel={leftLabel}
headerTitle={
Expand Down
45 changes: 26 additions & 19 deletions packages/stack/src/views/Header/HeaderContainer.tsx
Expand Up @@ -21,17 +21,16 @@ import type {
Scene,
StackHeaderStyleInterpolator,
StackNavigationProp,
StackHeaderProps,
GestureDirection,
} from '../../types';

export type Props = {
mode: 'float' | 'screen';
layout: Layout;
insets: EdgeInsets;
scenes: (Scene<Route<string>> | undefined)[];
getPreviousScene: (props: {
route: Route<string>;
}) => Scene<Route<string>> | undefined;
scenes: (Scene | undefined)[];
getPreviousScene: (props: { route: Route<string> }) => Scene | undefined;
getFocusedRoute: () => Route<string>;
onContentHeightChange?: (props: {
route: Route<string>;
Expand Down Expand Up @@ -71,34 +70,42 @@ export default function HeaderContainer({
return null;
}

const isFocused = focusedRoute.key === scene.route.key;
const previous =
getPreviousScene({ route: scene.route }) ?? parentPreviousScene;
const isFocused = focusedRoute.key === scene.descriptor.route.key;
const previousScene =
getPreviousScene({ route: scene.descriptor.route }) ??
parentPreviousScene;

// 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 previousScene = self[i - 1];
const nextScene = self[i + 1];
const previousDescriptor = self[i - 1]?.descriptor;
const nextDescriptor = self[i + 1]?.descriptor;

const { headerShown: previousHeaderShown = true } =
previousScene?.descriptor.options || {};
previousDescriptor?.options || {};

const { headerShown: nextHeaderShown = true } =
nextScene?.descriptor.options || {};
nextDescriptor?.options || {};

const isHeaderStatic =
(previousHeaderShown === false &&
// We still need to animate when coming back from next scene
// A hacky way to check this is if the next scene exists
!nextScene) ||
!nextDescriptor) ||
nextHeaderShown === false;

const props = {
mode,
const props: StackHeaderProps = {
layout,
insets,
scene,
previous,
previous: previousScene
? {
progress: previousScene.progress,
options: previousScene.descriptor.options,
route: previousScene.descriptor.route,
}
: undefined,
progress: scene.progress,
options: scene.descriptor.options,
route: scene.descriptor.route,
navigation: scene.descriptor.navigation as StackNavigationProp<
ParamListBase
>,
Expand All @@ -117,18 +124,18 @@ export default function HeaderContainer({

return (
<NavigationContext.Provider
key={scene.route.key}
key={scene.descriptor.route.key}
value={scene.descriptor.navigation}
>
<NavigationRouteContext.Provider value={scene.route}>
<NavigationRouteContext.Provider value={scene.descriptor.route}>
<View
onLayout={
onContentHeightChange
? (e) => {
const { height } = e.nativeEvent.layout;

onContentHeightChange({
route: scene.route,
route: scene.descriptor.route,
height,
});
}
Expand Down

0 comments on commit 4cad132

Please sign in to comment.