FlexibleBottomSheet

repository·main·Indexed 22 days ago

https://github.com/skydoves/flexiblebottomsheet

An advanced Compose Multiplatform bottom sheet library supporting segmented sizing and non-modal interactions. It allows for custom height ratios via FlexibleSheetSize, wrap-content behavior, and nested scrolling support. Compatible with Android, iOS, Desktop, and Web (JS/WASM), with implementations available for both Material and Material3 design systems.

Tokens
8.9K
Snippets
27
Records
31
Agent score
73%

What's inside FlexibleBottomSheet

  1. Core Features of FlexibleBottomSheet

    main

    FlexibleBottomSheet provides several advanced capabilities beyond the standard Material 3 ModalBottomSheet:

    • Segmented Sizing: Define custom heights for fully expanded, intermediately expanded, and slightly expanded states.
    • Non-Modal Support: Allows users to interact with the content behind the bottom sheet.
    • Wrap Content: The sheet can automatically adjust its size based on the height of its content.
    • Initial Value: Ability to start the sheet at a specific expanded state without animation.
    • State Monitoring: Provides mechanisms to track and respond to changes in the sheet's state.
    • Nested Scroll Support: Works seamlessly with scrollable components like LazyColumn.
  2. Understand FlexibleSheetValue expanded states

    main

    The bottom sheet transitions between four primary states defined by FlexibleSheetValue:

    StateDescription
    Fully ExpandedThe sheet is at its maximum height. This state is mandatory and cannot be skipped.
    Intermediately ExpandedAn intermediate height. Can be disabled by setting skipIntermediatelyExpanded = true.
    Slightly ExpandedA low-height expansion. Enabled by setting skipSlightlyExpanded = false (it is skipped by default).
    HiddenThe sheet is not visible. To prevent the sheet from ever hiding, set skipHiddenState = true (if available).
  3. Understand FlexibleSheetValue expanded statuses

    main

    The bottom sheet transitions between four primary states defined by FlexibleSheetValue:

    1. FullyExpanded: The sheet is at its maximum defined height. This state is mandatory.
    2. IntermediatelyExpanded: An intermediate height. Can be disabled via skipIntermediatelyExpanded = true.
    3. SlightlyExpanded: A minimal height. Disabled by default; enable via skipSlightlyExpanded = false.
    4. Hidden: The sheet is off-screen. To prevent the sheet from ever being hidden, use skipHiddenState = true in the state configuration.
  4. Prevent a Non-Modal Bottom Sheet from being dismissed

    main

    If you want a non-modal sheet to act as a persistent panel that cannot be swiped away or dismissed, set skipHiddenState = true in rememberFlexibleBottomSheetState. Note that in this configuration, onDismissRequest will not be triggered because the sheet cannot enter the hidden state.

    FlexibleBottomSheet(
      onDismissRequest = { /* won't be called */ },
      sheetState = rememberFlexibleBottomSheetState(
        isModal = false,
        skipHiddenState = true,  // Sheet cannot be dismissed
        skipSlightlyExpanded = false,
      ),
    ) {
      // Persistent panel content
    }
  5. Enable or disable nested scrolling in FlexibleBottomSheet

    main

    FlexibleBottomSheet supports nested scrolling by default, which allows components like LazyColumn or LazyRow to work seamlessly within the sheet. To disable this behavior, set allowNestedScroll = false within the rememberFlexibleBottomSheetState function.

    FlexibleBottomSheet(
      onDismissRequest = onDismissRequest,
      sheetState = rememberFlexibleBottomSheetState(
        allowNestedScroll = false
      ),
    ) {
      LazyColumn {
        items(100) { index ->
          Text("Item $index")
        }
      }
    }
  6. Set the initial expansion state for a Non-Modal Bottom Sheet

    main

    You can control which expansion state the non-modal sheet starts in by providing an initialValue to rememberFlexibleBottomSheetState.

    Best Practice: For non-modal sheets, consider starting with FlexibleSheetValue.SlightlyExpanded or FlexibleSheetValue.IntermediatelyExpanded to provide immediate access to both the sheet content and the underlying content.

    FlexibleBottomSheet(
      onDismissRequest = onDismissRequest,
      sheetState = rememberFlexibleBottomSheetState(
        isModal = false,
        initialValue = FlexibleSheetValue.SlightlyExpanded,
        skipSlightlyExpanded = false,
      ),
    ) {
      // Sheet starts at slightly expanded state
    }
  7. Use WrapContent to size the sheet based on content height

    main

    By setting a state to FlexibleSheetSize.WrapContent, the bottom sheet will automatically adjust its height to fit its content.

    Behavior:

    • If content is smaller than the screen: The sheet sizes itself to fit the content height.
    • If content is larger than the screen: The sheet is constrained to the screen height.

    Best Practice: WrapContent works best with content that has a known, static height. For dynamic content like LazyColumn, it is recommended to use fixed ratios instead.

    FlexibleBottomSheet(
      onDismissRequest = onDismissRequest,
      sheetState = rememberFlexibleBottomSheetState(
        flexibleSheetSize = FlexibleSheetSize(
          fullyExpanded = FlexibleSheetSize.WrapContent,
          intermediatelyExpanded = 0.5f,
          slightlyExpanded = 0.15f,
        ),
      )
    ) {
      // The sheet will wrap this content when fully expanded
      Column {
        Text("Item 1")
        Text("Item 2")
        Text("Item 3")
      }
    }
  8. Update dynamic content using onTargetChanges

    main

    You can react to changes in the bottom sheet's expansion state by using the onTargetChanges callback. This callback provides the new FlexibleSheetValue, allowing you to dynamically adjust your UI (e.g., changing text size or visibility) based on whether the sheet is FullyExpanded, IntermediatelyExpanded, or in another state.

    var currentSheetTarget by remember {
      mutableStateOf(FlexibleSheetValue.IntermediatelyExpanded)
    }
    
    FlexibleBottomSheet(
      onDismissRequest = onDismissRequest,
      sheetState = rememberFlexibleBottomSheetState(
        skipSlightlyExpanded = false
      ),
      onTargetChanges = {
        currentSheetTarget = it
      },
      containerColor = Color.Black,
    ) {
      Text(
        modifier = Modifier
          .fillMaxWidth()
          .padding(8.dp),
        text = "This is Flexible Bottom Sheet",
        textAlign = TextAlign.Center,
        color = Color.White,
        fontSize = when (currentSheetTarget) {
          FlexibleSheetValue.FullyExpanded -> 28.sp
          FlexibleSheetValue.IntermediatelyExpanded -> 20.sp
          else -> 12.sp
        },
      )
    }
  9. Install FlexibleBottomSheet via Gradle

    main

    To use FlexibleBottomSheet in your Android or Compose Multiplatform project, add the appropriate dependency to your module's build.gradle or build.gradle.kts file. Choose between the Material or Material3 implementation depending on your project's design system.

    // For Compose Material
    implementation("com.github.skydoves:flexible-bottomsheet-material:0.3.0")
    
    // For Compose Material3
    implementation("com.github.skydoves:flexible-bottomsheet-material3:0.3.0")
  10. Quick Start with FlexibleBottomSheet

    main

    To implement a FlexibleBottomSheet, use the FlexibleBottomSheet composable and manage its state with rememberFlexibleBottomSheetState. You can define custom segmented sizes using FlexibleSheetSize and control whether the sheet is modal (blocking interaction with the background) or non-modal (allowing interaction with the background).

    Key parameters for rememberFlexibleBottomSheetState:

    • flexibleSheetSize: A FlexibleSheetSize object defining the fraction of the screen for fullyExpanded, intermediatelyExpanded, and slightlyExpanded states.
    • isModal: Set to true to block background interaction, or false to allow interaction behind the sheet (similar to Google Maps).
    • skipSlightlyExpanded: A boolean to determine if the slightlyExpanded state should be bypassed.
    FlexibleBottomSheet(
      onDismissRequest = onDismissRequest,
      sheetState = rememberFlexibleBottomSheetState(
        flexibleSheetSize = FlexibleSheetSize(
          fullyExpanded = 0.9f,
          intermediatelyExpanded = 0.5f,
          slightlyExpanded = 0.15f,
        ),
        isModal = true,
        skipSlightlyExpanded = false,
      ),
      containerColor = Color.Black,
    ) {
      Text(
        modifier = Modifier
          .fillMaxWidth()
          .padding(8.dp),
        text = "This is Flexible Bottom Sheet",
        textAlign = TextAlign.Center,
        color = Color.White,
      )
    }
  11. Create a Non-Modal Bottom Sheet

    main

    A non-modal bottom sheet allows users to interact with the underlying content while the sheet is visible, unlike modal sheets which block interaction and use a dimming scrim. To implement a non-modal sheet, set isModal = false within the rememberFlexibleBottomSheetState function.

    Key Differences:

    • Modal: Uses a scrim (dim overlay), blocks underlying content, and prevents touch passthrough. Best for dialogs and selections.
    • Non-Modal: No scrim, allows touch passthrough, and does not block underlying content. Best for persistent panels or maps.
    FlexibleBottomSheet(
      onDismissRequest = onDismissRequest,
      sheetState = rememberFlexibleBottomSheetState(
        isModal = false,
        skipSlightlyExpanded = false,
      ),
    ) {
      // content
    }