Skip to content

feat(Android, FormSheet v5): Add support for fractional detents#4251

Open
t0maboro wants to merge 10 commits into
mainfrom
@t0maboro/android-formsheet-detents-2
Open

feat(Android, FormSheet v5): Add support for fractional detents#4251
t0maboro wants to merge 10 commits into
mainfrom
@t0maboro/android-formsheet-detents-2

Conversation

@t0maboro

@t0maboro t0maboro commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Description

Adds configurable detents to the new FormSheets on Android. Due to the native limitations, it allows passing 0-3 detents from JS, which are parsed to BottomSheetBehavior params (peek/half-expanded/expanded/max-height).
I am introducing a FormSheetBehaviorController which is a stateless mapper from a resolved detent config to BottomSheetBehavior, forcing a fresh layout pass so the behavior re-settles to the new detent configuration.

Additionally, I am making some bugfixes related to or exposed by introducing the sheet detents mechanism.

  • Enter-animation flicker on API < 30 - Material's BottomSheetBehavior registers a WindowInsetsAnimationCallback which updates translationY and is causing the sheet to jump during the enter animation (I am modifying translation on a custom slide-in animation). We clear that callback after the first layout, because we manage insets ourselves via a fixed container height, preventing the conflict.
  • Material only draws the BottomSheetDialog edge-to-edge when the theme opts in, and the nav bar is translucent; otherwise, the CoordinatorLayout (and our full-bleed dimming view) is inset below the status bar. We now force edge-to-edge on the dialog window on every API level.

Closes: https://github.com/software-mansion/react-native-screens-labs/issues/1551

Changes

  • Introduced FormSheetBehaviorController responsible for mapping the raw JS detents array into specific BottomSheetBehavior properties.
  • Disabled native insets animation to explicitly clear Material's WindowInsetsAnimationCallback, unblocking our custom translationY enter-animation.
  • Forced Edge-to-Edge in onAttachedToWindow() in FormSheetDialog on both the window decor and the internal Material containers (R.id.container, R.id.coordinator), ensuring the dimming view covers the status bar area and the FormSheetContainer space is predictable across different API levels.

Before & after - visual documentation

detents.mov

Test plan

Tested on the base example &

import React, { useState } from 'react';
import { Button, ScrollView, StyleSheet, Text, View } from 'react-native';
import { FormSheet, SafeAreaView } from 'react-native-screens/experimental';
import { scenarioDescription } from './scenario-description';
import { createScenario } from '@apps/tests/shared/helpers';
import { Colors } from '@apps/shared/styling';

const DETENT_CONFIGS = [
  { label: 'Single detent (0.5)', detents: [0.5] },
  { label: 'Full screen (1.0)', detents: [1.0] },
  { label: 'Two detents (0.3, 0.8)', detents: [0.3, 0.8] },
  { label: 'Half and full (0.5, 1.0)', detents: [0.5, 1.0] },
  { label: 'Three detents (0.2, 0.5, 0.9)', detents: [0.2, 0.5, 0.9] },
  { label: 'Three tall (0.35, 0.65, 1.0)', detents: [0.35, 0.65, 1.0] },
  { label: 'Default fallback (empty array)', detents: [] },
];

function SheetScenario({ label, detents }) {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <View style={styles.scenarioContainer}>
      <Button
        title={`Open: ${label}`}
        color={Colors.primary}
        onPress={() => setIsOpen(true)}
      />

      <FormSheet
        isOpen={isOpen}
        onNativeDismiss={() => setIsOpen(false)}
        detents={detents}>
        <View style={styles.sheetContent}>
          <Text style={styles.sheetTitle}>FormSheet Content</Text>
          <Text style={styles.sheetSubtitle}>
            Static configuration: {JSON.stringify(detents)}
          </Text>
          <View style={styles.spacing} />
          <Button
            title="Close sheet from JS"
            color={Colors.primary}
            onPress={() => setIsOpen(false)}
          />
        </View>
      </FormSheet>
    </View>
  );
}

function TestFormSheetBase() {
  return (
    <SafeAreaView edges={{top: true, bottom: true}} style={styles.container}>
      <Text style={styles.title}>FormSheet Configuration Tests</Text>

      <ScrollView
        style={styles.scrollContainer}
        contentContainerStyle={styles.scrollContent}>
        {DETENT_CONFIGS.map((config, index) => (
          <SheetScenario
            key={index}
            label={config.label}
            detents={config.detents}
          />
        ))}
      </ScrollView>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: Colors.offBackground,
  },
  scrollContainer: {
    flex: 1,
  },
  scrollContent: {
    padding: 16,
    paddingBottom: 40,
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    marginVertical: 20,
    color: Colors.text,
    textAlign: 'center',
  },
  scenarioContainer: {
    marginBottom: 16,
    backgroundColor: 'rgba(0,0,0,0.03)',
    padding: 12,
    borderRadius: 8,
    borderWidth: 1,
    borderColor: 'rgba(0,0,0,0.1)',
  },
  sheetContent: {
    flex: 1,
    backgroundColor: Colors.background,
    padding: 24,
    justifyContent: 'center',
    alignItems: 'center',
  },
  sheetTitle: {
    fontSize: 22,
    fontWeight: '600',
    marginBottom: 8,
    color: Colors.text,
  },
  sheetSubtitle: {
    fontSize: 14,
    color: Colors.text,
    opacity: 0.7,
    fontFamily: 'monospace',
  },
  spacing: {
    height: 32,
  },
});

export default createScenario(TestFormSheetBase, scenarioDescription);

Checklist

  • Included code example that can be used to test this change.
  • For visual changes, included screenshots / GIFs / recordings documenting the change.
  • For API changes, updated relevant public types.
  • Ensured that CI passes

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds fractional detent support for the new Android FormSheet (gamma) by mapping a JS detents array into BottomSheetBehavior configuration, while also addressing visual/insets issues (enter-animation flicker on API < 30 and non-edge-to-edge dialog layout).

Changes:

  • Plumbs detents from the RN prop layer (FormSheetHostViewManager/FormSheetHost) into FormSheetConfig and FormSheetDialogManager.
  • Introduces FormSheetDetents + FormSheetBehaviorController to validate detent input and apply corresponding BottomSheetBehavior params (peek/half-expanded/expanded/maxHeight) with a forced re-layout.
  • Forces edge-to-edge rendering in FormSheetDialog and disables Material’s insets animation callback to prevent translationY conflicts with the custom enter animation.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
android/src/main/java/com/swmansion/rnscreens/gamma/modals/formsheet/FormSheetHostViewManager.kt Implements detents prop parsing from ReadableArray into a Kotlin list.
android/src/main/java/com/swmansion/rnscreens/gamma/modals/formsheet/FormSheetHost.kt Stores detents on the host and passes them into FormSheetConfig.
android/src/main/java/com/swmansion/rnscreens/gamma/modals/formsheet/FormSheetDialogManager.kt Resolves detents, triggers reconfiguration on open/config change, updates container height, and disables Material insets animation callback.
android/src/main/java/com/swmansion/rnscreens/gamma/modals/formsheet/FormSheetDialog.kt Forces edge-to-edge drawing for consistent dimming + layout across API levels.
android/src/main/java/com/swmansion/rnscreens/gamma/modals/formsheet/FormSheetDetents.kt Adds detent validation + helpers for computing pixel heights and container sizing.
android/src/main/java/com/swmansion/rnscreens/gamma/modals/formsheet/FormSheetConfig.kt Extends config with detents.
android/src/main/java/com/swmansion/rnscreens/gamma/modals/formsheet/FormSheetBehaviorController.kt Stateless mapper from resolved detents to BottomSheetBehavior properties.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

t0maboro and others added 2 commits July 2, 2026 17:13
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants