react-native-bottom-sheet-stack

repository·main·Indexed 41 days ago

https://github.com/arekkubaczkowski/react-native-bottom-sheet-stack

A stack manager for bottom sheets and modals in React Native (v2.2.0). It features a pluggable adapter architecture allowing developers to mix different bottom sheet libraries in a single navigation stack, supporting navigation modes like push, switch, and replace, along with iOS-style scale animations.

Tokens
57K
Snippets
164
Records
213
Agent score
51%

What's inside react-native-bottom-sheet-stack

  1. Introduction to react-native-bottom-sheet-stack

    main
    A library-agnostic stack manager for bottom sheets and modals in React Native. It allows you to manage multiple layers of sheets using push, switch, and replace navigation modes. The library provides iOS-style scale animations for stacked sheets, preserves React context via a portal-based API, and supports mixed stacking of bottom sheets and modals. It uses a pluggable adapter architecture to work with various underlying libraries.
  2. Overview of Hooks

    main

    Hooks in this library are categorized into two groups based on their intended use case:

    1. App hooks: Used by your application logic to drive, control, or subscribe to the state of bottom sheets from anywhere in your app.
    2. Adapter hooks: Used by authors of custom adapters to wire a specific UI library (like @gorhom/bottom-sheet) into the stack. You do not need these for standard usage.

    App Hooks Summary

    HookWhere to usePurpose
    useBottomSheetManagerAnywhereOpen/close sheets imperatively
    useBottomSheetControlAnywhereControl portal-based sheets
    useBottomSheetStatusAnywhereSubscribe to sheet status by ID
    useBottomSheetContextInside sheet onlyAccess current sheet's state and params
    useOnBeforeCloseInside sheet onlyIntercept close and optionally prevent it
  3. Understand the Adapter Lifecycle Flow

    main

    The lifecycle follows a strict sequence to ensure the stack manager and the overlay library stay in sync:

    1. Opening Phase:

      • Store status changes to 'opening'.
      • Coordinator calls ref.expand().
      • Adapter starts its show animation.
      • Animation completes $\rightarrow$ Adapter calls handleOpened().
      • Store status changes to 'open'.
    2. Interaction Phase:

      • User triggers dismissal (swipe/tap) $\rightarrow$ Adapter calls handleDismiss().
      • OR API close() is called $\rightarrow$ Coordinator calls ref.close().
    3. Closing Phase:

      • Store status changes to 'closing'.
      • Adapter starts its hide animation.
      • Animation completes $\rightarrow$ Adapter calls handleClosed().
      • Store removes the sheet (or sets it to 'hidden' if persistent).
  4. Use Persistent Sheets for instant access and state preservation

    main

    Persistent sheets are pre-mounted bottom sheets that remain in memory even when closed. Unlike standard portal sheets, they do not remount when opened, which provides two main benefits:

    1. Instant Open: No mount delay when triggering the sheet.
    2. State Preservation: Internal component state (like form inputs, scroll positions, or local useState values) is preserved between open/close cycles.

    When to use them:

    • Frequently accessed UI (e.g., Scanners, Cameras, Quick Actions).
    • Complex workflows (e.g., Multi-step wizards, Media players).
    • Any scenario where mount delay is noticeable.
    import {
      BottomSheetPersistent,
      useBottomSheetControl,
    } from 'react-native-bottom-sheet-stack';
    
    function App() {
      return (
        <BottomSheetManagerProvider id="main">
          <BottomSheetScaleView>
            <HomeScreen />
          </BottomSheetScaleView>
          <BottomSheetHost />
    
          {/* Persistent sheet - always mounted */}
          <BottomSheetPersistent id="scanner">
            <ScannerSheet />
          </BottomSheetPersistent>
        </BottomSheetManagerProvider>
      );
    }
    
    function HomeScreen() {
      const scanner = useBottomSheetControl('scanner');
    
      return (
        <Button
          title="Open Scanner"
          onPress={() => scanner.open({ scaleBackground: true })}
        />
      );
    }
  5. Mix bottom sheets and modals in the same stack

    main

    You can coexist bottom sheets and modals within a single stack. When a modal is pushed onto a stack containing a bottom sheet, closing the modal will automatically return the user to the bottom sheet that was previously active.

    const { open } = useBottomSheetManager();
    const modalControl = useBottomSheetControl('my-modal');
    
    // Push a bottom sheet
    open(<MyBottomSheet />, { mode: 'push' });
    
    // Then push a modal on top
    modalControl.open({ mode: 'push' });
    // Both are in the stack — closing the modal returns to the bottom sheet
  6. Key features of react-native-bottom-sheet-stack

    main

    The library provides several advanced capabilities for managing UI overlays:

    • Stack Navigation: Manage multiple sheets using push, switch, and replace modes.
    • Scale Animation: iOS-style background scaling effect when sheets are stacked.
    • Configurable Backdrop: Theme the shared stack-aware backdrop or replace it with custom components like blurs.
    • Context Preservation: Uses a portal-based API to ensure React context is preserved within bottom sheets.
    • Close Interception: Use useOnBeforeClose to confirm or prevent sheet dismissal.
    • Cascading Close: closeAll() allows closing all sheets with staggered animations while respecting interceptors.
    • Mixed Stacking: Allows bottom sheets and modals to coexist in the same stack.
    • Group Support: Create isolated stacks for different parts of your application.
    • Pluggable Adapters: Works with GorhomSheet, Swmansion (Fabric), CustomModal, react-native-modal, and ActionsSheet.
  7. How the library-agnostic architecture works

    main

    The library uses an adapter-based architecture to decouple the stack management logic from the actual UI implementation.

    • Stack Manager (core): Handles navigation modes (push, switch, replace), scale animations, portals, and state persistence via a Zustand store.
    • Adapters: Control the visual appearance and lifecycle of the sheets (how they open and close).

    This separation allows you to manage a single stack of sheets while using different UI libraries for each individual sheet.

  8. How closeAll() interacts with interceptors

    main

    When closeAll() is called, it attempts to close sheets from the top of the stack downwards. If it encounters a sheet with an active onBeforeClose interceptor, it waits for the user's decision before proceeding to the next sheet.

    Successful Cascade

    If the user confirms the interceptor (calls onConfirm()):

    1. SheetC (top) closes.
    2. SheetB (with interceptor) shows prompt $\rightarrow$ User clicks "Confirm" $\rightarrow$ SheetB closes.
    3. SheetA closes. Result: All sheets are closed.

    Interrupted Cascade

    If the user cancels the interceptor (calls onCancel()):

    1. SheetC (top) closes.
    2. SheetB (with interceptor) shows prompt $\rightarrow$ User clicks "Cancel" $\rightarrow$ SheetB stays open.
    3. The cascade stops immediately. Result: SheetA and SheetB remain open.
  9. How the adapter architecture works

    main

    The library uses a pluggable adapter architecture to manage different bottom sheet or modal libraries within a single stack. This allows you to mix different types of sheets (e.g., a @gorhom/bottom-sheet and a react-native-modal) in the same navigation stack.

    Adapters are shipped as separate subpath exports to prevent Metro resolution errors if you haven't installed the underlying library. Each sheet in your stack can use a different adapter.

  10. Configure BottomSheetPortalRegistry values

    main

    When augmenting the BottomSheetPortalRegistry interface, the value assigned to a sheet ID determines how the open() method behaves:

    • true: The sheet has no parameters. The params property in .open() is optional or unnecessary.
    • { ... } (an object type): The sheet requires parameters. The .open() method will require a params property matching this object shape.
  11. Understand the three navigation modes

    main

    The library provides three distinct modes for managing how bottom sheets are added to or removed from the stack when calling the open method. Choosing the right mode depends on whether you want to layer sheets, swap them temporarily, or replace the current context entirely.

    | Mode | Description |
    |------|-------------|
    | `push` | Stack a new sheet on top. Previous sheet remains visible underneath. |
    | `switch` | Hide current sheet and show new one. Previous sheet is restored when new one closes. |
    | `replace` | Close current sheet and open new one. Previous sheet is removed from stack. |
  12. Understand potential issues with group isolation

    main
    The BottomSheetManagerProvider is designed to support independent groups of bottom sheets, where each group maintains its own stackOrder. However, certain internal operations (like applyModeToTopSheet, getTopSheetId, and getSheetBelowId) may incorrectly interact with the global stack instead of filtering by groupId. This can cause actions in one group (e.g., switching or replacing a sheet) to unintentionally hide or close sheets in a different group.