From aeec52051697764dde2db6c646724a55164b2003 Mon Sep 17 00:00:00 2001 From: "satyajit.happy" Date: Thu, 13 Jun 2019 23:02:15 +0200 Subject: [PATCH] fix: use pure component for stack items --- .../src/TransitionConfigs/TransitionSpecs.tsx | 5 +- .../src/views/Header/HeaderContainer.tsx | 12 +- packages/stack/src/views/Stack/Stack.tsx | 100 +++++------ packages/stack/src/views/Stack/StackItem.tsx | 160 ++++++++++++++++++ packages/stack/src/views/Stack/StackView.tsx | 10 +- 5 files changed, 220 insertions(+), 67 deletions(-) create mode 100644 packages/stack/src/views/Stack/StackItem.tsx diff --git a/packages/stack/src/TransitionConfigs/TransitionSpecs.tsx b/packages/stack/src/TransitionConfigs/TransitionSpecs.tsx index 4f79dbb8..14dab85c 100644 --- a/packages/stack/src/TransitionConfigs/TransitionSpecs.tsx +++ b/packages/stack/src/TransitionConfigs/TransitionSpecs.tsx @@ -39,6 +39,9 @@ export const WipeFromBottomAndroidSpec: TransitionSpec = { duration: 425, // This is super rough approximation of the path used for the curve by android // See http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/res/res/interpolator/fast_out_extra_slow_in.xml - easing: t => Easing.bezier(0.90, 0.06, 0.57, 0)((Easing.bezier(0.06, 0.94, 0.22, 1.02)(t))), + easing: t => + Easing.bezier(0.9, 0.06, 0.57, 0)( + Easing.bezier(0.06, 0.94, 0.22, 1.02)(t) + ), }, }; diff --git a/packages/stack/src/views/Header/HeaderContainer.tsx b/packages/stack/src/views/Header/HeaderContainer.tsx index aaa87da1..911b8379 100644 --- a/packages/stack/src/views/Header/HeaderContainer.tsx +++ b/packages/stack/src/views/Header/HeaderContainer.tsx @@ -16,10 +16,10 @@ import { } from '../../types'; import Header from './Header'; -type Props = { +export type Props = { mode: 'float' | 'screen'; layout: Layout; - scenes: HeaderScene[]; + scenes: Array | undefined>; navigation: NavigationProp; getPreviousRoute: (props: { route: Route }) => Route | undefined; onLayout?: (e: LayoutChangeEvent) => void; @@ -42,7 +42,7 @@ export default function HeaderContainer({ return ( {scenes.map((scene, i, self) => { - if (mode === 'screen' && i !== self.length - 1) { + if ((mode === 'screen' && i !== self.length - 1) || !scene) { return null; } @@ -56,8 +56,10 @@ export default function HeaderContainer({ // The previous scene will be shortly before the current scene in the array // So loop back from current index to avoid looping over the full array for (let j = i - 1; j >= 0; j--) { - if (self[j].route.key === previousRoute.key) { - previous = self[j]; + const s = self[j]; + + if (s && s.route.key === previousRoute.key) { + previous = s; break; } } diff --git a/packages/stack/src/views/Stack/Stack.tsx b/packages/stack/src/views/Stack/Stack.tsx index d3e9d649..1ad17136 100755 --- a/packages/stack/src/views/Stack/Stack.tsx +++ b/packages/stack/src/views/Stack/Stack.tsx @@ -8,8 +8,8 @@ import { } from 'react-native'; import Animated from 'react-native-reanimated'; import { getDefaultHeaderHeight } from '../Header/HeaderSegment'; -import HeaderContainer from '../Header/HeaderContainer'; -import Card from './Card'; +import { Props as HeaderContainerProps } from '../Header/HeaderContainer'; +import StackItem from './StackItem'; import { Route, Layout, @@ -38,6 +38,7 @@ type Props = { onCloseRoute: (props: { route: Route }) => void; getPreviousRoute: (props: { route: Route }) => Route | undefined; getGesturesEnabled: (props: { route: Route }) => boolean; + renderHeader: (props: HeaderContainerProps) => React.ReactNode; renderScene: (props: { route: Route }) => React.ReactNode; transparentCard?: boolean; headerMode: HeaderMode; @@ -182,6 +183,7 @@ export default class Stack extends React.Component { onGoBack, getPreviousRoute, getGesturesEnabled, + renderHeader, renderScene, transparentCard, headerMode, @@ -212,29 +214,22 @@ export default class Stack extends React.Component { const scene = scenes[index]; return ( - onOpenRoute({ route })} - onClose={() => onCloseRoute({ route })} - overlayEnabled={cardOverlayEnabled} - shadowEnabled={cardShadowEnabled} + scene={scene} + previousScene={scenes[index - 1]} + navigation={navigation} + direction={direction} + transparentCard={transparentCard} + cardOverlayEnabled={cardOverlayEnabled} + cardShadowEnabled={cardShadowEnabled} gesturesEnabled={index !== 0 && getGesturesEnabled({ route })} - onTransitionStart={({ closing }) => { - onTransitionStart && - onTransitionStart( - { index: closing ? index - 1 : index }, - { index } - ); - - closing && onGoBack({ route }); - }} onGestureBegin={onGestureBegin} onGestureCanceled={onGestureCanceled} onGestureEnd={onGestureEnd} @@ -242,49 +237,34 @@ export default class Stack extends React.Component { descriptor.options.gestureResponseDistance } transitionSpec={transitionSpec} - styleInterpolator={cardStyleInterpolator} - accessibilityElementsHidden={!focused} - importantForAccessibility={ - focused ? 'auto' : 'no-hide-descendants' - } - pointerEvents="box-none" - style={[ - StyleSheet.absoluteFill, - headerMode === 'float' && - descriptor && - descriptor.options.header !== null - ? { marginTop: floaingHeaderHeight } - : null, - ]} - > - {headerMode === 'screen' ? ( - - ) : null} - {renderScene({ route })} - + headerStyleInterpolator={headerStyleInterpolator} + cardStyleInterpolator={cardStyleInterpolator} + floaingHeaderHeight={floaingHeaderHeight} + hasCustomHeader={descriptor.options.header === null} + getPreviousRoute={getPreviousRoute} + headerMode={headerMode} + renderHeader={renderHeader} + renderScene={renderScene} + onOpenRoute={onOpenRoute} + onCloseRoute={onCloseRoute} + onTransitionStart={onTransitionStart} + onGoBack={onGoBack} + /> ); })} - {headerMode === 'float' ? ( - - ) : null} + {headerMode === 'float' + ? renderHeader({ + mode: 'float', + layout, + scenes, + navigation, + getPreviousRoute, + onLayout: this.handleFloatingHeaderLayout, + styleInterpolator: headerStyleInterpolator, + style: [styles.header, styles.floating], + }) + : null} ); } diff --git a/packages/stack/src/views/Stack/StackItem.tsx b/packages/stack/src/views/Stack/StackItem.tsx new file mode 100644 index 00000000..7c4bf2c4 --- /dev/null +++ b/packages/stack/src/views/Stack/StackItem.tsx @@ -0,0 +1,160 @@ +import * as React from 'react'; +import { StyleSheet, Platform } from 'react-native'; +import Animated from 'react-native-reanimated'; +import { Props as HeaderContainerProps } from '../Header/HeaderContainer'; +import Card from './Card'; +import { + Route, + HeaderScene, + GestureDirection, + Layout, + TransitionSpec, + CardStyleInterpolator, + HeaderMode, + NavigationProp, + HeaderStyleInterpolator, +} from '../../types'; + +type Props = { + index: number; + active: boolean; + focused: boolean; + closing: boolean; + layout: Layout; + current: Animated.Value; + previousScene?: HeaderScene; + scene: HeaderScene; + navigation: NavigationProp; + transparentCard?: boolean; + cardOverlayEnabled?: boolean; + cardShadowEnabled?: boolean; + gesturesEnabled?: boolean; + direction: GestureDirection; + getPreviousRoute: (props: { route: Route }) => Route | undefined; + renderHeader: (props: HeaderContainerProps) => React.ReactNode; + renderScene: (props: { route: Route }) => React.ReactNode; + onOpenRoute: (props: { route: Route }) => void; + onCloseRoute: (props: { route: Route }) => void; + onGoBack: (props: { route: Route }) => void; + onTransitionStart?: ( + curr: { index: number }, + prev: { index: number } + ) => void; + onGestureBegin?: () => void; + onGestureCanceled?: () => void; + onGestureEnd?: () => void; + gestureResponseDistance?: { + vertical?: number; + horizontal?: number; + }; + transitionSpec: { + open: TransitionSpec; + close: TransitionSpec; + }; + headerStyleInterpolator: HeaderStyleInterpolator; + cardStyleInterpolator: CardStyleInterpolator; + headerMode: HeaderMode; + floaingHeaderHeight: number; + hasCustomHeader: boolean; +}; + +export default class StackItem extends React.PureComponent { + private handleOpen = () => + this.props.onOpenRoute({ route: this.props.scene.route }); + + private handleClose = () => + this.props.onOpenRoute({ route: this.props.scene.route }); + + private handleTransitionStart = ({ closing }: { closing: boolean }) => { + const { index, scene, onTransitionStart, onGoBack } = this.props; + + onTransitionStart && + onTransitionStart({ index: closing ? index - 1 : index }, { index }); + + closing && onGoBack({ route: scene.route }); + }; + + render() { + const { + layout, + active, + focused, + closing, + current, + navigation, + scene, + previousScene, + direction, + transparentCard, + cardOverlayEnabled, + cardShadowEnabled, + gesturesEnabled, + onGestureBegin, + onGestureCanceled, + onGestureEnd, + gestureResponseDistance, + transitionSpec, + headerStyleInterpolator, + cardStyleInterpolator, + floaingHeaderHeight, + hasCustomHeader, + getPreviousRoute, + headerMode, + renderHeader, + renderScene, + } = this.props; + + return ( + + {headerMode === 'screen' + ? renderHeader({ + mode: 'screen', + layout, + scenes: [previousScene, scene], + navigation, + getPreviousRoute, + styleInterpolator: headerStyleInterpolator, + style: styles.header, + }) + : null} + {renderScene({ route: scene.route })} + + ); + } +} + +const styles = StyleSheet.create({ + header: { + // This is needed to show elevation shadow + zIndex: Platform.OS === 'android' ? 1 : 0, + }, +}); diff --git a/packages/stack/src/views/Stack/StackView.tsx b/packages/stack/src/views/Stack/StackView.tsx index 85eed18d..318212a0 100644 --- a/packages/stack/src/views/Stack/StackView.tsx +++ b/packages/stack/src/views/Stack/StackView.tsx @@ -1,6 +1,10 @@ import * as React from 'react'; +import { Platform } from 'react-native'; import { SceneView, StackActions } from '@react-navigation/core'; import Stack from './Stack'; +import HeaderContainer, { + Props as HeaderContainerProps, +} from '../Header/HeaderContainer'; import { DefaultTransition, ModalSlideFromBottomIOS, @@ -11,7 +15,6 @@ import { NavigationConfig, Route, } from '../../types'; -import { Platform } from 'react-native'; type Descriptors = { [key: string]: SceneDescriptor }; @@ -223,6 +226,10 @@ class StackView extends React.Component { ); }; + private renderHeader = (props: HeaderContainerProps) => { + return ; + }; + private handleTransitionComplete = () => { // TODO: remove when the new event system lands this.props.navigation.dispatch(StackActions.completeTransition()); @@ -294,6 +301,7 @@ class StackView extends React.Component { onGestureBegin={onGestureBegin} onGestureCanceled={onGestureCanceled} onGestureEnd={onGestureEnd} + renderHeader={this.renderHeader} renderScene={this.renderScene} headerMode={headerMode} navigation={navigation}