react-native-screens icon indicating copy to clipboard operation
react-native-screens copied to clipboard

fix(Android, FormSheet): Fix dynamic height change for fitToContents

Open t0maboro opened this issue 2 weeks ago • 1 comments

Description

This PR introduces improved support for fitToContents in BottomSheet by modifying its style to absoluteWithNoBottom. This style provides a reference to the current top position of the sheet, making it easier to handle dynamic content within FormSheet.

Having improved control over maxHeight (added in previous PRs), we are now able to implement a smooth resizing animation for the BottomSheet.

[!IMPORTANT] Key assumptions (these should be carefully reviewed)

  • Unmounting of components remains the responsibility of the application code. We do not include default enter/exit animations at the component level.
  • Visual styles such as backgroundColor should be applied via contentStyle on the Screen level. In fitToContents mode, the height of the ScreenContentWrapper should always match the height of the content.
  • When a component unmounts, the ScreenContentWrapper's size shrinks before we can start the transition. Since its size is necessary to calculate the animation delta, styles should be applied at the Screen level, where we have better control over when layout occurs.

Fixes: https://github.com/software-mansion/react-native-screens/issues/2560

Changes

  • Updated style to absoluteWithNoBottom for android fitToContents
  • Added expand/shrink animations

Screenshots / GIFs

Here you can add screenshots / GIFs documenting your change.

You can add before / after section if you're changing some behavior.

Before

https://github.com/user-attachments/assets/b9fc3461-31c7-4a71-9a00-6badd1ba3633

After

https://github.com/user-attachments/assets/ef2fcdd9-7942-40c3-8528-d4d8fdcf4e0e

Test code and steps to reproduce

Test2560

Checklist

  • [x] Included code example that can be used to test this change
  • [x] Ensured that CI passes

t0maboro avatar Dec 16 '25 14:12 t0maboro

NOW it's feels like Christmas.

🎉

RohovDmytro avatar Dec 17 '25 13:12 RohovDmytro

Also, not sure if this is a real concern but I was able to make the form sheet not adapt to content by changing the content quickly:

Screen.Recording.2025-12-18.at.09.04.29.mov I used TestFormSheet with fitToContents and just added some text at the bottom.

checking..

t0maboro avatar Dec 18 '25 08:12 t0maboro

Also, not sure if this is a real concern but I was able to make the form sheet not adapt to content by changing the content quickly:

Screen.Recording.2025-12-18.at.09.04.29.mov I used TestFormSheet with fitToContents and just added some text at the bottom.

Should be fixed, tested with

import React, { useRef, useState } from 'react';
import {
  Button,
  View,
  Text,
  StyleSheet,
  Animated,
} from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import {
  createNativeStackNavigator,
  NativeStackScreenProps,
} from '@react-navigation/native-stack';
import Colors from '../shared/styling/Colors';

type StackParamList = {
  Home: undefined;
  FormSheet: undefined;
};

const Stack = createNativeStackNavigator<StackParamList>();

const HomeScreen = ({ navigation }: NativeStackScreenProps<StackParamList>) => (
  <View style={styles.screen}>
    <Text style={styles.title}>Home Screen</Text>
    <Button
      title="Open Form Sheet"
      onPress={() => navigation.navigate('FormSheet')}
    />
  </View>
);

const FormSheetScreen = () => {
  const [isTextVisible, setIsTextVisible] = useState(true);

  React.useEffect(() => {
    const timer = setTimeout(() => {
      setIsTextVisible(false);
    }, 1000);

    return () => clearTimeout(timer);
  }, []);

  React.useEffect(() => {
    const timer = setTimeout(() => {
      setIsTextVisible(true);
    }, 1016);

    return () => clearTimeout(timer);
  }, []);

  return (
    <View style={styles.formSheetContainer}>
      <Text style={styles.formSheetTitle}>Form Sheet Content</Text>
      {isTextVisible && (
        <Text>
          Lorem, ipsum dolor sit amet consectetur adipisicing elit. Velit deserunt
          qui fugit unde assumenda. Non id facere mollitia sequi aliquid nostrum,
          tenetur eligendi quos at natus eius, corporis quidem eaque libero
          reiciendis, labore sed minima doloremque temporibus! Veritatis harum
          provident molestias incidunt at temporibus quod ipsam totam optio,
          quisquam amet quae molestiae. Exercitationem animi facere iure, nobis
          nulla repudiandae aut nam natus! Qui, id? Nobis quia recusandae
          repellendus illum voluptas, ipsa, doloribus reprehenderit nulla quas
          facere eos! Alias, accusantium voluptatibus doloremque unde eius totam
          et officia asperiores? Voluptate totam provident, quas inventore
          doloribus autem nihil quisquam soluta eum, dolorum nobis.{' '}
        </Text>
      )}
    </View>
  );
};

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen
          name="FormSheet"
          component={FormSheetScreen}
          options={{
            presentation: 'formSheet',
            sheetAllowedDetents: 'fitToContents',
            contentStyle: {
              backgroundColor: Colors.YellowLight40,
            },
            // TODO(@t0maboro) - add `sheetContentDefaultResizeAnimationEnabled` prop here when possible
          }}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

const styles = StyleSheet.create({
  screen: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    padding: 20,
  },
  title: {
    fontSize: 24,
    marginBottom: 20,
  },
  formSheetContainer: {
    alignItems: 'center',
    justifyContent: 'center',
    padding: 20,
    gap: 20,
  },
  formSheetTitle: {
    fontSize: 20,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  text: {
    fontSize: 16,
    marginBottom: 8,
    color: Colors.White,
  },
  rectangle: {
    width: '100%',
    backgroundColor: Colors.NavyLight80,
    height: 200,
    alignItems: 'center',
    justifyContent: 'center',
  },
  input: {
    width: 100,
    height: 20,
    borderColor: Colors.BlueDark100,
    borderWidth: 1,
  },
});

t0maboro avatar Dec 18 '25 16:12 t0maboro

Thank you very much for this PR — looking forward to seeing it merged. :)

GaylordP avatar Dec 29 '25 08:12 GaylordP