truesheet-usage

GitHub

指导在React Native中集成、配置和调试@lodev09/react-native-trueSheet底部弹窗,涵盖API使用、布局陷阱及多平台控制模式。

skills/truesheet-usage/SKILL.md lodev09/react-native-true-sheet

Trigger Scenarios

集成TrueSheet底部弹窗 配置弹窗布局和交互 解决手势或动画问题 迁移版本或测试

Install

npx skills add lodev09/react-native-true-sheet --skill truesheet-usage -g -y
More Options

Use without installing

npx skills use lodev09/react-native-true-sheet@truesheet-usage

指定 Agent (Claude Code)

npx skills add lodev09/react-native-true-sheet --skill truesheet-usage -a claude-code -g -y

安装 repo 全部 skill

npx skills add lodev09/react-native-true-sheet --all -g -y

预览 repo 内 skill

npx skills add lodev09/react-native-true-sheet --list

SKILL.md

Frontmatter
{
    "name": "truesheet-usage",
    "description": "Consumer-side guide for integrating @lodev09\/react-native-true-sheet into a React Native app. Use this skill whenever the user wants to add, configure, control, or debug a bottom sheet using TrueSheet — including ref-based sheets, named global sheets, web support with TrueSheetProvider\/useTrueSheet, React Navigation or Expo Router sheet flows, Reanimated-driven animations, scrolling content, stacking, headers\/footers, detents, peeking, side sheets, keyboard handling, dimming, liquid glass, and Jest testing. Also use when the user is migrating from v3 to v4, troubleshooting layout or gesture issues, or asking about any TrueSheet prop, event, or method — even if they don't mention \"TrueSheet\" by name but describe a bottom sheet in a React Native context."
}

TrueSheet Consumer Guide

Use this skill to produce correct, idiomatic code for apps that consume @lodev09/react-native-true-sheet (v4). It covers choosing the right integration pattern, applying the public API correctly, and avoiding platform-specific pitfalls.

Requires React Native >= 0.82 (Expo SDK 55+) with the New Architecture.

Quick Start

The simplest sheet: a ref, a button, and some content.

import { useRef } from 'react'
import { Button, Text, View } from 'react-native'
import { TrueSheet } from '@lodev09/react-native-true-sheet'

export function App() {
  const sheet = useRef<TrueSheet>(null)

  return (
    <View>
      <Button title="Open" onPress={() => sheet.current?.present()} />
      <TrueSheet ref={sheet} detents={['auto']} cornerRadius={24} grabber>
        <View style={{ padding: 16 }}>
          <Text>Hello from the sheet</Text>
          <Button title="Close" onPress={() => sheet.current?.dismiss()} />
        </View>
      </TrueSheet>
    </View>
  )
}

Content Layout (v4)

The sheet's content lays out naturally — it wraps its children's height like a regular view or a React Navigation screen. It does not fill the sheet by default. Consequences:

  • A flex: 1 child collapses to zero height unless the content is filled.
  • To fill the sheet's visible height per detent (spacers, centered content, bounded scroll views), pass flex: 1 via the sheet's style prop:
<TrueSheet style={{ flex: 1 }} detents={[0.5, 1]}>
  <View style={{ flex: 1, justifyContent: 'center' }}>
    <Text>Centered</Text>
  </View>
</TrueSheet>

The content is sized to the sheet's visible height per detent and tracks it in realtime while dragging, so flex layouts follow the sheet's edge frame-by-frame.

Choose the Right Control Pattern

Pick one based on where the trigger lives relative to the sheet and which platforms you target.

Pattern When to use Platform
Ref Trigger and sheet in the same component All
Named + global methods Trigger is far from the sheet (different screen, deep in tree) Native only
TrueSheetProvider + useTrueSheet() Web support needed, or you want hook-based control All (required on web)
createTrueSheetNavigator() / Expo Router Sheet Sheets are part of a navigation flow All
ReanimatedTrueSheet You need animated values synced to sheet position All

Ref-based

Already shown in Quick Start. Use present(), dismiss(), resize(index) on the ref.

Named sheet with global methods (native only)

When the trigger is far from where the sheet renders:

// Somewhere in the tree
<TrueSheet name="profile" detents={['auto', 1]}>
  <ProfileContent />
</TrueSheet>

// Anywhere else (native only)
await TrueSheet.present('profile')
await TrueSheet.dismiss('profile')
await TrueSheet.resize('profile', 1)
await TrueSheet.dismissAll()

Every name must be unique. Static methods don't exist on web — use the provider pattern instead.

Web control with provider

Wrap your app with TrueSheetProvider (on native this is a pass-through with zero overhead). Web also needs @radix-ui/react-dialog and @radix-ui/react-presence installed (optional peer deps):

import { TrueSheet, TrueSheetProvider, useTrueSheet } from '@lodev09/react-native-true-sheet'

function Toolbar() {
  const { present, dismiss } = useTrueSheet()
  return <Button title="Open" onPress={() => present('settings')} />
}

export function App() {
  return (
    <TrueSheetProvider>
      <Toolbar />
      <TrueSheet name="settings" detents={[0.5, 1]}>
        <SettingsContent />
      </TrueSheet>
    </TrueSheetProvider>
  )
}

Navigation (React Navigation / Expo Router)

See advanced patterns reference for full setup with createTrueSheetNavigator, the static API (createTrueSheetScreen), the Expo Router Sheet layout from /navigation/expo-router, screen options (including scrollableRef via setOptions), multi-step flows as stacked sheet screens, and useTrueSheetNavigation.

Reanimated

See advanced patterns reference for ReanimatedTrueSheet, ReanimatedTrueSheetProvider, and animated values (animatedPosition, animatedIndex, animatedDetent).

Detents

Detents define the heights the sheet can snap to. You get up to 3 detents, sorted smallest to largest. Default: [0.5, 1].

Value Meaning
'auto' Size to fit the content (iOS 16+, Android, Web). Works with scrollables — sizes to the scroll content and resizes as it grows/shrinks
'peek' Collapsed height from the measured header + absolute footer + content through a TrueSheetPeek marker (iOS 16+, Android, Web). Falls back to 150 when none provided
0 – 1 Fraction of the screen height
// Content-sized sheet
<TrueSheet detents={['auto']} />

// Half and full screen
<TrueSheet detents={[0.5, 1]} />

// Collapsed summary that expands to near-full
<TrueSheet detents={['peek', 0.9]} />

Common Recipes

Scrollable content

Point the sheet at your scroll view with scrollableRef. For fixed detents, bound the scroll view with flex: 1 on the sheet's style:

const scrollableRef = useRef<ScrollView>(null)

<TrueSheet scrollableRef={scrollableRef} style={{ flex: 1 }} detents={[0.5, 1]} grabber>
  <ScrollView ref={scrollableRef}>
    {items.map(item => <ItemRow key={item.id} item={item} />)}
  </ScrollView>
</TrueSheet>
  • With detents={['auto']} no flex: 1 is needed — the sheet sizes to the scroll content and bounds the viewport automatically
  • The scroll view doesn't have to be a direct child — a wrapping View works
  • Keyboard handling, nested scrolling (Android), and the bottom safe-area inset are wired automatically
  • Scrolling to top expands to the next detent — disable with scrollableOptions={{ scrollingExpandsSheet: false }} (YouTube-style comments)
  • The bottom safe-area inset (and an absolute footer's height) is applied natively while content can scroll — don't add manual padding, or opt out with scrollableOptions={{ contentInsetAdjustment: 'never' }} ('safe-area' / 'footer' keep one of the two)

Fixed header and footer

<TrueSheet
  detents={[0.5, 1]}
  scrollableRef={scrollableRef}
  style={{ flex: 1 }}
  header={
    <View style={{ padding: 16 }}>
      <Text style={{ fontSize: 18, fontWeight: 'bold' }}>Title</Text>
    </View>
  }
  footer={<BottomActions />}
>
  <ScrollView ref={scrollableRef}>{/* ... */}</ScrollView>
</TrueSheet>
  • Use the header and footer props — they render in native container views, so the layout math is handled for you. Don't fake it with absolute positioning.
  • Both are relative by default: header takes space above the content, footer below it, and both count toward the 'auto' detent.
  • The footer absorbs the bottom safe-area inset natively — remove manual paddingBottom: insets.bottom from footer content or it doubles up. Set the footer background via footerStyle so it fills the inset.
  • A relative footer is laid out below the content — with fixed detents, bound the content with flex: 1 (sheet style) so the footer stays visible.
  • To float them over the content instead (excluded from 'auto' height), use headerOptions={{ position: 'absolute' }} / footerOptions={{ position: 'absolute' }}. An absolute footer rises above the keyboard (opt out with avoidKeyboard: false); a relative one stays in the layout flow behind it.
  • An absolute footer covers the bottom of a plugged scrollable — the scroll content is padded by the measured footer height automatically (default contentInsetAdjustment: 'automatic') and the footer counts toward 'auto', so don't hard-code paddingBottom: FOOTER_HEIGHT. Use 'safe-area' to let content scroll under the footer.
  • With avoidKeyboard: false, only the footer portion above the keyboard adds scroll padding and contributes to the expanded 'auto' height. Footer inset adjustment must be enabled.

Peeking (map-style collapsed sheet)

import { TrueSheet, TrueSheetPeek } from '@lodev09/react-native-true-sheet'

<TrueSheet detents={['peek', 0.9]} initialDetentIndex={0} header={<Summary />}>
  <TrueSheetPeek>
    <QuickActions />
  </TrueSheetPeek>
  <FullDetails />
</TrueSheet>

Collapsed, the sheet shows everything from the top through the bottom edge of TrueSheetPeek; content below stays hidden until expanded. An empty <TrueSheetPeek /> works as a fold marker between existing views. One peek component per sheet. Absolute footers count toward the peek height; relative footers don't (pushed off-screen at peek).

Toasts and dialogs above sheets

import { TrueSheet, TrueSheetOverlay } from '@lodev09/react-native-true-sheet'

<TrueSheetOverlay>
  {toast && <Toast message={toast} />}
</TrueSheetOverlay>

Renders children in a native layer above every presented sheet. Show/hide by conditionally rendering children. Fills the window; touches that miss the children pass through. Never use RN Modal/FullWindowOverlay for this anymore. Don't put react-native-screens containers (native stack, tabs, drawer) inside it — Android crashes.

Non-dismissible confirmation

<TrueSheet
  ref={sheet}
  detents={['auto']}
  dismissible={false}
  draggable={false}
  dimmed
  grabber={false}
>
  <View style={{ padding: 24 }}>
    <Text>Are you sure?</Text>
    <Button title="Confirm" onPress={handleConfirm} />
    <Button title="Cancel" onPress={() => sheet.current?.dismiss()} />
  </View>
</TrueSheet>

Confirm before dismiss (unsaved changes)

<TrueSheet
  ref={sheet}
  dismissible={!isDirty}
  onDismissAttempt={() =>
    Alert.alert('Discard changes?', undefined, [
      { text: 'Keep editing', style: 'cancel' },
      { text: 'Discard', style: 'destructive', onPress: () => sheet.current?.dismiss() },
    ])
  }
>
  {/* form */}
</TrueSheet>

Don't try to block dismissal from a callback — native can't wait on JS. Toggle dismissible from state; blocked drags and back/escape fire onDismissAttempt (dim taps don't), then dismiss() programmatically.

iOS blur background

<TrueSheet detents={['auto']} backgroundBlur="system-material">
  <View style={{ padding: 16 }}>
    <Text>Blurred sheet</Text>
  </View>
</TrueSheet>

Fine-tune with blurOptions={{ intensity: 80, interaction: true }}. Blur is iOS-only.

iOS tinted or flat Liquid Glass

<TrueSheet backgroundColor="rgba(0, 122, 255, 0.25)">   {/* tinted glass */}
<TrueSheet backgroundColor="#ffffff" glass={false}>     {/* flat, no glass (iOS 26.1+) */}

backgroundColor paints over the glass on iOS 26+, so translucent colors tint it. glass={false} removes the glass for a flat color.

Present on mount

<TrueSheet detents={['auto', 1]} initialDetentIndex={0} initialDetentAnimated>
  <WelcomeContent />
</TrueSheet>

Pre-mount content for 'auto' detents

Content mounts on first present() by default. If it settles asynchronously (data fetch, images), 'auto' measures too early. Set lazy={false} to mount content up front without presenting, then present once ready (native only):

<TrueSheet ref={sheet} lazy={false} detents={['auto']}>
  <AsyncContent onReady={() => sheet.current?.present()} />
</TrueSheet>

Dimming control

// No dimming (allows background interaction)
<TrueSheet dimmed={false} detents={['auto']} />

// Dim only above a certain detent
<TrueSheet detents={['auto', 0.7, 1]} dimmedDetentIndex={1} />

Resize programmatically

resize() takes a detent index, not a value:

const sheet = useRef<TrueSheet>(null)

// detents={[0.3, 0.6, 1]}
await sheet.current?.resize(2) // expands to full (index 2)

Rules That Save Debugging Time

  1. Max 3 detents, sorted smallest → largest.
  2. Content doesn't fill the sheet by default — pass style={{ flex: 1 }} on the sheet when children use flex: 1, or they render with zero height.
  3. scrollableRef replaces the v3 scrollable prop — pass a ref of the ScrollView/FlatList. Bound it with flex: 1 on the sheet style for fixed detents; 'auto' needs no flex.
  4. resize() takes an index, not a fraction. resize(1) means "go to the second detent."
  5. Sheet names must be unique across your entire app.
  6. Static methods are native-only — use useTrueSheet() on web.
  7. Don't add manual safe-area padding to footers or plugged scroll content — the footer absorbs the bottom inset and the scrollable gets it natively. Doubling up is the most common v4 layout bug.
  8. Don't use autoFocus on TextInputs inside sheets. Focus in onDidPresent instead:
    <TrueSheet onDidPresent={() => inputRef.current?.focus()}>
    
  9. Use flexGrow: 1 (not flex: 1) on GestureHandlerRootView inside the sheet on Android — unless the sheet itself has style={{ flex: 1 }}.
  10. Dismiss sheets before closing Modals on iOS — React Native has a bug where dismissing a Modal while a sheet is visible causes a blank screen.
  11. Use header/footer props for fixed chrome — don't reach for absolute positioning. Float them with headerOptions/footerOptions position: 'absolute' when they should overlay content.
  12. Liquid Glass is automatic on iOS 26+. backgroundColor paints over it, so translucent colors tint the glass. Set glass={false} or backgroundBlur to remove it per-sheet (iOS 26.1+), or add UIDesignRequiresCompatibility to Info.plist to disable app-wide.
  13. Native stacks go in the base screen, not in sheet screens (Sheet Navigator / Expo Router). The first screen is a regular view and can host createNativeStackNavigator (that's how you wrap an app). A sheet screen can't — Android crashes with ScreenContainer is not attached under ReactRootView. For multi-step flows, make each step its own sheet screen.

Platform Differences at a Glance

Feature iOS Android Web
'auto' detent iOS 16+ Yes Yes
'peek' detent / TrueSheetPeek iOS 16+ Yes Yes
TrueSheetOverlay Yes Yes Yes
backgroundBlur Yes No No
Liquid Glass iOS 26+ No No
Static global methods Yes Yes No (use provider)
scrollableRef Yes Yes Yes
Scroll edge effects iOS 26+ No No
grabberOptions Yes Yes No
placement / side sheets System-controlled margins; 'automatic' vs 'center' differ on iOS 27+ placementOffset prop placementOffset prop
presentation iOS 17+ (iPad) N/A Landscape/tablet
detached mode No No Yes
insetAdjustment Yes Yes No
lazy={false} (pre-mount content) Yes Yes No
Edge-to-edge N/A Auto-detected N/A
Keyboard handling Built-in Built-in N/A

Events

The most commonly used events:

Event When it fires Payload
onMount Content is mounted and ready —
onDidPresent Sheet finished presenting { index, position, detent }
onDidDismiss Sheet finished dismissing —
onDismissAttempt User tried to dismiss while dismissible={false} —
onDetentChange User dragged or resize() changed the detent { index, position, detent }
onPositionChange Continuous position updates during drag/animation { index, position, detent, realtime }

For the full event list (drag events, focus/blur events, will/did lifecycle pairs, onBackPress), see the API reference.

Methods

On a ref:

  • present(index?, animated?) — show the sheet
  • dismiss(animated?) — hide the sheet and all its children
  • dismissStack(animated?) — hide only sheets stacked on top
  • resize(index) — snap to a detent by index

Global (native only):

  • TrueSheet.present(name, index?, animated?)
  • TrueSheet.dismiss(name, animated?)
  • TrueSheet.dismissStack(name, animated?)
  • TrueSheet.resize(name, index)
  • TrueSheet.dismissAll(animated?)

Web hook:

const { present, dismiss, dismissStack, resize, dismissAll } = useTrueSheet()

Stacking Sheets

Present a new sheet while another is visible and the first one hides automatically. Dismiss the top sheet and the previous one comes back. This is built-in — no extra config needed.

  • dismiss() cascades: it dismisses the current sheet plus everything stacked on top
  • dismissStack() dismisses only the sheets on top, keeping the current one visible
  • Use onDidFocus / onDidBlur to react to a sheet gaining or losing the top position

Deep-Dive References

When you need the full picture, load these reference files:

Reference What's inside
Configuration Every prop with type, default, platform support, and notes
API Complete events and methods reference with payload types
Advanced Patterns Navigation, Reanimated, Web, Side sheets, Liquid Glass, Jest mocking, Migration v3→v4
Troubleshooting Common issues and fixes by platform

Version History

  • d72e7d3 Current 2026-09-22 06:05

    新增footerOptions.avoidKeyboard支持避免键盘遮挡;重构contentInsetAdjustment为枚举;重命名anchor为placement并优化iOS放置逻辑;新增glass属性和onDismissAttempt事件。

  • 1912589 2026-09-03 02:07

    新增 TrueSheetOverlay 功能,支持在弹窗上方渲染内容并处理多平台触摸与帧同步。

  • 197924f 2026-08-27 14:22

    更新为 TrueSheet v4 指南,新增同步布局、Yoga 适配、自动检测 ScrollView 及 Android 实时拖拽调整等特性说明。

  • 3e60c8f 2026-07-05 18:37

Metadata

Files
0
Version
c826d20
Hash
9ce2dc32
Indexed
2026-07-05 18:37

ホーム - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-09-30 19:53
浙ICP备14020137号-1