Skip to content

Instantly share code, notes, and snippets.

@davispuh
Created November 16, 2022 20:11
Show Gist options
  • Select an option

  • Save davispuh/e141fb1ebfa220dccb6a8c1e593db80c to your computer and use it in GitHub Desktop.

Select an option

Save davispuh/e141fb1ebfa220dccb6a8c1e593db80c to your computer and use it in GitHub Desktop.

Revisions

  1. davispuh created this gist Nov 16, 2022.
    459 changes: 459 additions & 0 deletions experiments.patch
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,459 @@
    diff --git a/packages/drawer/src/views/DrawerView.tsx b/packages/drawer/src/views/DrawerView.tsx
    index 19363aad..f53897c4 100644
    --- a/packages/drawer/src/views/DrawerView.tsx
    +++ b/packages/drawer/src/views/DrawerView.tsx
    @@ -343,5 +343,7 @@ export default function DrawerView({ navigation, ...rest }: Props) {
    const styles = StyleSheet.create({
    content: {
    flex: 1,
    + maxHeight: '100%',
    + overflow: 'clip' as any,
    },
    });
    diff --git a/packages/drawer/src/views/modern/Drawer.tsx b/packages/drawer/src/views/modern/Drawer.tsx
    index 52eefb5b..2b9d2005 100644
    --- a/packages/drawer/src/views/modern/Drawer.tsx
    +++ b/packages/drawer/src/views/modern/Drawer.tsx
    @@ -381,6 +381,7 @@ const styles = StyleSheet.create({
    top: 0,
    bottom: 0,
    maxWidth: '100%',
    + maxHeight: '100%',
    width: DEFAULT_DRAWER_WIDTH,
    },
    content: {
    diff --git a/packages/elements/src/Screen.tsx b/packages/elements/src/Screen.tsx
    index 9ace55b5..cfbdc4ef 100644
    --- a/packages/elements/src/Screen.tsx
    +++ b/packages/elements/src/Screen.tsx
    @@ -25,6 +25,7 @@ type Props = {
    header: React.ReactNode;
    headerShown?: boolean;
    headerStatusBarHeight?: number;
    + // eslint-disable-next-line react/no-unused-prop-types
    headerTransparent?: boolean;
    style?: StyleProp<ViewStyle>;
    children: React.ReactNode;
    @@ -42,7 +43,6 @@ export default function Screen(props: Props) {
    modal = false,
    header,
    headerShown = true,
    - headerTransparent,
    headerStatusBarHeight = isParentHeaderShown ? 0 : insets.top,
    navigation,
    route,
    @@ -60,17 +60,6 @@ export default function Screen(props: Props) {
    importantForAccessibility={focused ? 'auto' : 'no-hide-descendants'}
    style={[styles.container, style]}
    >
    - <View style={styles.content}>
    - <HeaderShownContext.Provider
    - value={isParentHeaderShown || headerShown !== false}
    - >
    - <HeaderHeightContext.Provider
    - value={headerShown ? headerHeight : parentHeaderHeight ?? 0}
    - >
    - {children}
    - </HeaderHeightContext.Provider>
    - </HeaderShownContext.Provider>
    - </View>
    {headerShown ? (
    <NavigationContext.Provider value={navigation}>
    <NavigationRouteContext.Provider value={route}>
    @@ -80,13 +69,31 @@ export default function Screen(props: Props) {

    setHeaderHeight(height);
    }}
    - style={headerTransparent ? styles.absolute : styles.sticky}
    + style={styles.sticky}
    >
    {header}
    </View>
    </NavigationRouteContext.Provider>
    </NavigationContext.Provider>
    ) : null}
    + <View
    + style={{
    + ...styles.content,
    + // need this to fix scrolling, so that we can scroll to last item
    + // and prevent that NativeStack has extra scrollbar
    + maxHeight: `calc(100% - ${headerHeight}px)`,
    + }}
    + >
    + <HeaderShownContext.Provider
    + value={isParentHeaderShown || headerShown !== false}
    + >
    + <HeaderHeightContext.Provider
    + value={headerShown ? headerHeight : parentHeaderHeight ?? 0}
    + >
    + {children}
    + </HeaderHeightContext.Provider>
    + </HeaderShownContext.Provider>
    + </View>
    </Background>
    );
    }
    @@ -94,17 +101,9 @@ export default function Screen(props: Props) {
    const styles = StyleSheet.create({
    container: {
    flex: 1,
    - flexDirection: 'column-reverse',
    },
    - // This is necessary to avoid applying 'column-reverse' to screen content
    content: {
    - flex: 1,
    - },
    - absolute: {
    - position: 'absolute',
    - top: 0,
    - left: 0,
    - right: 0,
    + overflow: 'scroll',
    },
    sticky: {
    position: 'sticky' as any,
    diff --git a/packages/stack/src/views/Header/HeaderContainer.tsx b/packages/stack/src/views/Header/HeaderContainer.tsx
    index bbad9f4e..d4d73e08 100644
    --- a/packages/stack/src/views/Header/HeaderContainer.tsx
    +++ b/packages/stack/src/views/Header/HeaderContainer.tsx
    @@ -49,144 +49,136 @@ export default function HeaderContainer({
    const parentHeaderBack = React.useContext(HeaderBackContext);

    return (
    - <Animated.View pointerEvents="box-none" style={[styles.sticky, style]}>
    - {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) => {
    + <View style={styles.header}>
    + <Animated.View pointerEvents="box-none" style={style}>
    + {scenes.slice(-3).map((scene, i, self) => {
    + if ((mode === 'screen' && i !== self.length - 1) || !scene) {
    + return null;
    + }
    +
    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<ParamListBase>,
    - styleInterpolator:
    - mode === 'float'
    - ? isHeaderStatic
    - ? nextHeaderlessGestureDirection === 'vertical' ||
    - nextHeaderlessGestureDirection === 'vertical-inverted'
    - ? forSlideUp
    - : nextHeaderlessGestureDirection === 'horizontal-inverted'
    - ? forSlideRight
    - : forSlideLeft
    - : headerStyleInterpolator
    - : forNoAnimation,
    - };
    -
    - return (
    - <NavigationContext.Provider
    - key={scene.descriptor.route.key}
    - value={scene.descriptor.navigation}
    - >
    - <NavigationRouteContext.Provider value={scene.descriptor.route}>
    - <View
    - onLayout={
    - onContentHeightChange
    - ? (e) => {
    - 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) : <Header {...props} />}
    - </View>
    - </NavigationRouteContext.Provider>
    - </NavigationContext.Provider>
    - );
    - })}
    - </Animated.View>
    + header,
    + headerMode,
    + headerShown = true,
    + 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<ParamListBase>,
    + styleInterpolator:
    + mode === 'float'
    + ? isHeaderStatic
    + ? nextHeaderlessGestureDirection === 'vertical' ||
    + nextHeaderlessGestureDirection === 'vertical-inverted'
    + ? forSlideUp
    + : nextHeaderlessGestureDirection === 'horizontal-inverted'
    + ? forSlideRight
    + : forSlideLeft
    + : headerStyleInterpolator
    + : forNoAnimation,
    + };
    +
    + return (
    + <NavigationContext.Provider
    + key={scene.descriptor.route.key}
    + value={scene.descriptor.navigation}
    + >
    + <NavigationRouteContext.Provider value={scene.descriptor.route}>
    + <View
    + onLayout={
    + onContentHeightChange
    + ? (e) => {
    + 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'
    + }
    + >
    + {header !== undefined ? header(props) : <Header {...props} />}
    + </View>
    + </NavigationRouteContext.Provider>
    + </NavigationContext.Provider>
    + );
    + })}
    + </Animated.View>
    + </View>
    );
    }

    const styles = StyleSheet.create({
    header: {
    - position: 'absolute',
    - top: 0,
    - left: 0,
    - right: 0,
    - },
    - sticky: {
    position: 'sticky' as any,
    top: 0,
    left: 0,
    right: 0,
    + zIndex: 10,
    },
    });
    diff --git a/packages/stack/src/views/Stack/CardContainer.tsx b/packages/stack/src/views/Stack/CardContainer.tsx
    index 44907d22..aa98d1f1 100644
    --- a/packages/stack/src/views/Stack/CardContainer.tsx
    +++ b/packages/stack/src/views/Stack/CardContainer.tsx
    @@ -274,11 +274,22 @@ function CardContainer({
    : 'flex',
    ...StyleSheet.absoluteFillObject,
    position: 'relative',
    + maxHeight: '100%',
    },
    ]}
    >
    <View style={styles.container}>
    <ModalPresentationContext.Provider value={modal}>
    + {headerMode !== 'float'
    + ? renderHeader({
    + mode: 'screen',
    + layout,
    + scenes: [previousScene, scene],
    + getPreviousScene,
    + getFocusedRoute,
    + onContentHeightChange: onHeaderHeightChange,
    + })
    + : null}
    <View style={styles.scene}>
    <HeaderBackContext.Provider value={headerBack}>
    <HeaderShownContext.Provider
    @@ -292,16 +303,6 @@ function CardContainer({
    </HeaderShownContext.Provider>
    </HeaderBackContext.Provider>
    </View>
    - {headerMode !== 'float'
    - ? renderHeader({
    - mode: 'screen',
    - layout,
    - scenes: [previousScene, scene],
    - getPreviousScene,
    - getFocusedRoute,
    - onContentHeightChange: onHeaderHeightChange,
    - })
    - : null}
    </ModalPresentationContext.Provider>
    </View>
    </Card>
    @@ -313,7 +314,6 @@ export default React.memo(CardContainer);
    const styles = StyleSheet.create({
    container: {
    flex: 1,
    - flexDirection: 'column-reverse',
    },
    scene: {
    flex: 1,
    diff --git a/packages/stack/src/views/Stack/CardStack.tsx b/packages/stack/src/views/Stack/CardStack.tsx
    index 1cdb7100..7e07c1b8 100755
    --- a/packages/stack/src/views/Stack/CardStack.tsx
    +++ b/packages/stack/src/views/Stack/CardStack.tsx
    @@ -494,12 +494,17 @@ export default class CardStack extends React.Component<Props, State> {
    const { scenes, layout, gestures, headerHeights } = this.state;

    const focusedRoute = state.routes[state.index];
    - const focusedHeaderHeight = headerHeights[focusedRoute.key];
    + let focusedHeaderHeight = headerHeights[focusedRoute.key];

    const isFloatHeaderAbsolute = this.state.scenes.slice(-2).some((scene) => {
    const options = scene.descriptor.options ?? {};
    const { headerMode, headerTransparent, headerShown = true } = options;

    + if (!headerShown) {
    + // otherwise page's height is extended even when header is not visible
    + focusedHeaderHeight = 0;
    + }
    +
    if (
    headerTransparent ||
    headerShown === false ||