React Native TrueSheet

repository·main·Indexed 24 days ago

https://github.com/lodev09/react-native-true-sheet

A high-performance native bottom sheet experience for React Native applications built on the Fabric architecture. It supports imperative control via present/dismiss methods, a dedicated Sheet Navigator for React Navigation and Expo Router, and advanced features like Android sheet stacking, smooth dimming, and Reanimated integration. Version 3.0+ requires React Native 0.81+ and the New Architecture (Fabric).

Tokens
45.5K
Snippets
145
Records
232
Agent score
80%

What's inside @lodev09/react-native-true-sheet

  1. Overview of React Native TrueSheet

    main
    React Native TrueSheet provides a truly native bottom sheet experience for React Native applications. Unlike many libraries that use JavaScript-based animations, TrueSheet is implemented in the native realm (iOS and Android) with support for React Native's Fabric architecture. This ensures maximum performance, native accessibility, and native screen reader support.
  2. Understand the example project structure

    main

    The example/ directory is organized into three main parts:

    • bare/: A Bare React Native application using the react-native-community/cli.
    • expo/: An Expo Router application.
    • shared/: Contains shared components and utilities used across the different example apps.
    example/
    ├── bare/    # Bare React Native app (react-native-community/cli)
    ├── expo/    # Expo Router app
    └── shared/  # Shared components and utilities
  3. Configure Detents

    main

    Detents define the snap points (heights) for the sheet. You can provide up to 3 detents, which must be sorted from smallest to largest.

    Supported values:

    • 'auto': Fits the content size (iOS 16+, Android, Web).
    • 01: A fraction of the screen height.

    Critical Rule: Never combine 'auto' with the scrollable prop. Auto-sizing requires measuring full content, which is incompatible with the clipping behavior of a scrollable sheet. Use fractional detents for scrollable sheets instead.

    // Content-sized sheet
    <TrueSheet detents={['auto']} />
    
    // Half and full screen
    <TrueSheet detents={[0.5, 1]} />
    
    // Three stops: peek, half, full
    <TrueSheet detents={['0.25', '0.5', '1']} />
  4. How TrueSheet handles keyboard visibility

    main

    TrueSheet provides native keyboard avoidance on both iOS and Android without additional configuration. When a TextInput inside the sheet is focused, the sheet automatically adjusts to keep the input visible above the keyboard.

    • iOS: Leverages UISheetPresentationController's built-in keyboard avoidance.
    • Android: Uses WindowInsetsAnimationCompat to track keyboard height and reconfigure sheet detents in real-time.
    const App = () => {
      return (
        <TrueSheet ref={sheet} detents={['auto']}>
          <View style={{ padding: 16 }}>
            <TextInput
              placeholder="Type something..."
              style={{ borderWidth: 1, borderColor: '#ccc', padding: 12, borderRadius: 8 }}
            />
          </View>
        </TrueSheet>
      )
    }
  5. Handle Position events and realtime updates

    main

    The onPositionChange event provides continuous updates during drags or animations. It uses the PositionChangeEventPayload.

    Key Concept: realtime field The realtime boolean in the payload is critical for performance:

    • Use realtime: true (native animation frames) when driving Reanimated worklets.
    • Use realtime: false (JS-driven updates) when you need to drive JS-based animations.
    // PositionChangeEventPayload structure
    {
      index: number     // Continuous float — 0.5 means halfway between detent 0 and 1
      position: number
      detent: number
      realtime: boolean // true = native animation frame, false = JS-driven
    }
  6. Choose the Right TrueSheet Control Pattern

    main

    Select an integration pattern based on your platform requirements and where the sheet trigger is located in your component tree:

    • Ref: Use when the trigger and sheet are in the same component. Works on all platforms.
    • Named + global methods: Use when the trigger is far from the sheet (e.g., different screen). Native only.
    • TrueSheetProvider + useTrueSheet(): Required for Web support or if you prefer hook-based control. Works on all platforms.
    • createTrueSheetNavigator(): Use when sheets are part of a navigation flow. Works on all platforms.
    • ReanimatedTrueSheet: Use when you need animated values synced to the sheet position. Works on all platforms.
  7. Stacking multiple sheets

    main

    TrueSheet automatically handles sheet stacking. When you present a new sheet while another is already visible, the currently visible sheet is hidden (unless it is fully expanded, in which case the new sheet simply presents on top). When the top sheet is dismissed, the previous sheet is automatically shown again. Sheets do not need to be in a parent-child relationship in the component tree; they can be defined anywhere.

    const presentSheet2 = async () => {
      await sheet2.current?.present() // Sheet 2 will present, Sheet 1 will be hidden
    }
    
    return (
      <>
        <TrueSheet ref={sheet1}>
          <Button onPress={presentSheet2} title="Present Sheet 2" />
          <View />
        </TrueSheet>
        <TrueSheet ref={sheet2}>
          <View />
        </TrueSheet>
      </>
    )
  8. Understand TrueSheet's Edge-to-Edge default behavior

    main

    When edge-to-edge mode is enabled, TrueSheet is designed to respect the system status bar. When the sheet is fully expanded, it will stop at the bottom of the status bar to ensure that the sheet's content is not obscured by system UI elements. TrueSheet handles status bar detection automatically.

    <TrueSheet ref={sheet}>
      <View />
    </TrueSheet>
  9. Collapse the sheet to the footer

    main

    The footer height is included in the calculation of the "peek" detent. When you use a peek detent, the sheet will collapse to show the header, the footer, and any content designated as peeking content.

    const App = () => {
      return (
        <TrueSheet
          detents={['peek', 0.9]}
          initialDetentIndex={0}
          footer={<SheetActions />}
        >
          <BookingDetails />
        </TrueSheet>
      )
    }