truesheet-usage
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTrueSheet Consumer Guide
TrueSheet用户指南
Use this skill to produce correct, idiomatic code for apps that consume . It covers choosing the right integration pattern, applying the public API correctly, and avoiding platform-specific pitfalls.
@lodev09/react-native-true-sheet本指南用于为使用的应用生成正确、符合规范的代码,涵盖选择合适的集成模式、正确使用公共API以及规避各平台的常见踩坑点。
@lodev09/react-native-true-sheetQuick Start
快速开始
The simplest sheet: a ref, a button, and some content.
tsx
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>
)
}最简单的弹窗实现:一个ref、一个按钮加少量内容。
tsx
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="打开" onPress={() => sheet.current?.present()} />
<TrueSheet ref={sheet} detents={['auto']} cornerRadius={24} grabber>
<View style={{ padding: 16 }}>
<Text>来自弹窗的问候</Text>
<Button title="关闭" onPress={() => sheet.current?.dismiss()} />
</View>
</TrueSheet>
</View>
)
}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 |
| Web support needed, or you want hook-based control | All (required on web) |
| Sheets are part of a navigation flow | All |
| You need animated values synced to sheet position | All |
根据触发按钮和弹窗的相对位置、以及你要支持的平台选择对应的模式:
| 模式 | 适用场景 | 支持平台 |
|---|---|---|
| Ref | 触发按钮和弹窗在同一个组件内 | 全平台 |
| 命名+全局方法 | 触发按钮和弹窗距离较远(不同页面、组件树深层) | 仅原生端 |
| 需要支持web端,或是想要基于hook的控制方式 | 全平台(web端必填) |
| 弹窗属于导航流程的一部分 | 全平台 |
| 需要让动画值和弹窗位置同步 | 全平台 |
Ref-based
基于Ref的模式
Already shown in Quick Start. Use , , on the ref.
present()dismiss()resize(index)已经在快速开始部分演示过,直接在ref上调用、、即可。
present()dismiss()resize(index)Named sheet with global methods (native only)
带全局方法的命名弹窗(仅原生端)
When the trigger is far from where the sheet renders:
tsx
// 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 must be unique. Static methods don't exist on web — use the provider pattern instead.
name当触发按钮和弹窗渲染位置距离较远时使用:
tsx
// 组件树的任意位置
<TrueSheet name="profile" detents={['auto', 1]}>
<ProfileContent />
</TrueSheet>
// 其他任意位置(仅原生端)
await TrueSheet.present('profile')
await TrueSheet.dismiss('profile')
await TrueSheet.resize('profile', 1)
await TrueSheet.dismissAll()每个必须唯一。静态方法在web端不存在——请改用Provider模式。
nameWeb control with provider
用Provider实现Web端控制
Wrap your app with (on native this is a pass-through with zero overhead):
TrueSheetProvidertsx
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>
)
}用包裹你的应用(原生端这个组件是无开销的透传组件):
TrueSheetProvidertsx
import { TrueSheet, TrueSheetProvider, useTrueSheet } from '@lodev09/react-native-true-sheet'
function Toolbar() {
const { present, dismiss } = useTrueSheet()
return <Button title="打开" onPress={() => present('settings')} />
}
export function App() {
return (
<TrueSheetProvider>
<Toolbar />
<TrueSheet name="settings" detents={[0.5, 1]}>
<SettingsContent />
</TrueSheet>
</TrueSheetProvider>
)
}Navigation (React Navigation / Expo Router)
导航(React Navigation / Expo Router)
See advanced patterns reference for full setup with , Expo Router layouts, screen options, and .
createTrueSheetNavigatoruseTrueSheetNavigation查看高级模式参考了解、Expo Router布局、页面配置、的完整配置方式。
createTrueSheetNavigatoruseTrueSheetNavigationReanimated
Reanimated
See advanced patterns reference for , , and animated values (, , ).
ReanimatedTrueSheetReanimatedTrueSheetProvideranimatedPositionanimatedIndexanimatedDetent查看高级模式参考了解、以及动画值(、、)的用法。
ReanimatedTrueSheetReanimatedTrueSheetProvideranimatedPositionanimatedIndexanimatedDetentDetents
锚点(Detents)
Detents define the heights the sheet can snap to. You get up to 3 detents, sorted smallest to largest.
| Value | Meaning |
|---|---|
| Size to fit the content (iOS 16+, Android, Web) |
| Fraction of the screen height |
tsx
// 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]} />The one rule you can't break: never combine with . Auto-sizing needs to measure the full content, but a scrollable sheet clips it — they're fundamentally incompatible. Use fractional detents for scrollable sheets.
'auto'scrollable锚点定义了弹窗可以停靠的高度,最多支持3个锚点,需要按从小到大排序。
| 取值 | 含义 |
|---|---|
| 大小适配内容高度(iOS 16+、Android、Web) |
| 屏幕高度的占比 |
tsx
// 适配内容高度的弹窗
<TrueSheet detents={['auto']} />
// 半屏和全屏
<TrueSheet detents={[0.5, 1]} />
// 三个停靠位置:预览、半屏、全屏
<TrueSheet detents={[0.25, 0.5, 1]} />不可违反的规则: 永远不要同时使用和。自动高度需要测量完整内容高度,但可滚动弹窗会裁剪内容——二者本质上不兼容。可滚动弹窗请使用占比类型的锚点。
'auto'scrollableCommon Recipes
常见实现方案
Scrollable content
可滚动内容
tsx
<TrueSheet detents={[0.5, 1]} scrollable cornerRadius={24} grabber>
<ScrollView>
{items.map(item => <ItemRow key={item.id} item={item} />)}
</ScrollView>
</TrueSheet>- The prop auto-detects ScrollView/FlatList up to 2 levels deep
scrollable - On iOS, scrolling to top expands to next detent — disable with
scrollableOptions={{ scrollingExpandsSheet: false }} - On Android, nested scrolling is handled automatically
tsx
<TrueSheet detents={[0.5, 1]} scrollable cornerRadius={24} grabber>
<ScrollView>
{items.map(item => <ItemRow key={item.id} item={item} />)}
</ScrollView>
</TrueSheet>- 属性会自动检测最深2层内的ScrollView/FlatList
scrollable - iOS上滚动到顶部会自动展开到下一个锚点——可以通过关闭该特性
scrollableOptions={{ scrollingExpandsSheet: false }} - Android上的嵌套滚动会自动处理
Fixed header and footer
固定头部和底部
tsx
<TrueSheet
detents={[0.5, 1]}
scrollable
header={
<View style={{ padding: 16 }}>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>Title</Text>
</View>
}
footer={<BottomActions />}
>
<ScrollView>{/* ... */}</ScrollView>
</TrueSheet>Use the and props — they render in native container views, so the layout math is handled for you. Don't fake it with absolute positioning.
headerfootertsx
<TrueSheet
detents={[0.5, 1]}
scrollable
header={
<View style={{ padding: 16 }}>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>标题</Text>
</View>
}
footer={<BottomActions />}
>
<ScrollView>{/* ... */}</ScrollView>
</TrueSheet>请使用和属性——它们会渲染在原生容器视图中,布局计算会自动处理。不要用绝对定位自己实现。
headerfooterNon-dismissible confirmation
不可关闭的确认弹窗
tsx
<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>tsx
<TrueSheet
ref={sheet}
detents={['auto']}
dismissible={false}
draggable={false}
dimmed
grabber={false}
>
<View style={{ padding: 24 }}>
<Text>你确定吗?</Text>
<Button title="确认" onPress={handleConfirm} />
<Button title="取消" onPress={() => sheet.current?.dismiss()} />
</View>
</TrueSheet>iOS blur background
iOS模糊背景
tsx
<TrueSheet detents={['auto']} backgroundBlur="system-material">
<View style={{ padding: 16 }}>
<Text>Blurred sheet</Text>
</View>
</TrueSheet>Fine-tune with . Blur is iOS-only.
blurOptions={{ intensity: 80, interaction: true }}tsx
<TrueSheet detents={['auto']} backgroundBlur="system-material">
<View style={{ padding: 16 }}>
<Text>模糊背景弹窗</Text>
</View>
</TrueSheet>可以通过微调效果。模糊效果仅iOS支持。
blurOptions={{ intensity: 80, interaction: true }}Present on mount
组件挂载时自动展示
tsx
<TrueSheet detents={['auto', 1]} initialDetentIndex={0} initialDetentAnimated>
<WelcomeContent />
</TrueSheet>tsx
<TrueSheet detents={['auto', 1]} initialDetentIndex={0} initialDetentAnimated>
<WelcomeContent />
</TrueSheet>Dimming control
遮罩控制
tsx
// No dimming (allows background interaction)
<TrueSheet dimmed={false} detents={['auto']} />
// Dim only above a certain detent
<TrueSheet detents={['auto', 0.7, 1]} dimmedDetentIndex={1} />tsx
// 无遮罩(允许和背景交互)
<TrueSheet dimmed={false} detents={['auto']} />
// 仅超过指定锚点时显示遮罩
<TrueSheet detents={['auto', 0.7, 1]} dimmedDetentIndex={1} />Resize programmatically
代码控制尺寸调整
resize()tsx
const sheet = useRef<TrueSheet>(null)
// detents={[0.3, 0.6, 1]}
await sheet.current?.resize(2) // expands to full (index 2)resize()tsx
const sheet = useRef<TrueSheet>(null)
// detents={[0.3, 0.6, 1]}
await sheet.current?.resize(2) // 展开到全屏(索引2)Rules That Save Debugging Time
节省调试时间的规则
- Max 3 detents, sorted smallest → largest.
- Never +
'auto'— they're incompatible.scrollable - takes an index, not a fraction.
resize()means "go to the second detent."resize(1) - Sheet names must be unique across your entire app.
- Static methods are native-only — use on web.
useTrueSheet() - Don't use on TextInputs inside sheets. Focus in
autoFocusinstead:onDidPresenttsx<TrueSheet onDidPresent={() => inputRef.current?.focus()}> - Use (not
flexGrow: 1) insideflex: 1on Android.GestureHandlerRootView - 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.
- Use /
headerprops for fixed chrome — don't reach for absolute positioning.footer - Liquid Glass is automatic on iOS 26+. Set to disable it per-sheet, or add
backgroundColorto Info.plist to disable app-wide.UIDesignRequiresCompatibility
- 最多3个锚点,按从小到大排序。
- 永远不要同时用和
'auto'——二者不兼容。scrollable - 接收的是索引,不是占比值。
resize()表示「跳转到第二个锚点」。resize(1) - 弹窗名称在整个应用内必须唯一。
- 静态方法仅原生端支持——web端请使用。
useTrueSheet() - 不要在弹窗内的TextInputs上用。请在
autoFocus中处理聚焦:onDidPresenttsx<TrueSheet onDidPresent={() => inputRef.current?.focus()}> - Android平台的内部请使用
GestureHandlerRootView(不要用flexGrow: 1)。flex: 1 - iOS平台关闭Modal前请先关闭弹窗——React Native存在bug,当弹窗可见时关闭Modal会导致白屏。
- 固定元素请用/
header属性——不要用绝对定位实现。footer - 液态玻璃效果在iOS 26+上自动开启。设置可以单弹窗禁用,或是在Info.plist中添加
backgroundColor全局禁用。UIDesignRequiresCompatibility
Platform Differences at a Glance
平台差异速览
| Feature | iOS | Android | Web |
|---|---|---|---|
| iOS 16+ | Yes | Yes |
| Yes | No | No |
| Liquid Glass | iOS 26+ | No | No |
| Static global methods | Yes | Yes | No (use provider) |
| Yes | Yes | No (use |
| System-controlled margins | | |
| iOS 17+ | N/A | N/A |
| No | No | Yes |
| N/A | N/A | |
| Edge-to-edge | N/A | Auto-detected | N/A |
| Keyboard handling | Built-in | Built-in | N/A |
| 功能 | iOS | Android | Web |
|---|---|---|---|
| iOS 16+ | 支持 | 支持 |
| 支持 | 不支持 | 不支持 |
| 液态玻璃效果 | iOS 26+ | 不支持 | 不支持 |
| 静态全局方法 | 支持 | 支持 | 不支持(用Provider) |
| 支持 | 支持 | 不支持(用 |
| 系统控制边距 | 支持 | 支持 |
| iOS 17+ | 不适用 | 不适用 |
| 不支持 | 不支持 | 支持 |
| 不适用 | 不适用 | |
| 边到边显示 | 不适用 | 自动检测 | 不适用 |
| 键盘处理 | 内置 | 内置 | 不适用 |
Events
事件
The most commonly used events:
| Event | When it fires | Payload |
|---|---|---|
| Content is mounted and ready | — |
| Sheet finished presenting | |
| Sheet finished dismissing | — |
| User dragged or | |
| Continuous position updates during drag/animation | |
For the full event list (drag events, focus/blur events, will/did lifecycle pairs, ), see the API reference.
onBackPress最常用的事件:
| 事件 | 触发时机 | 携带参数 |
|---|---|---|
| 内容挂载完成就绪 | — |
| 弹窗完全展示完成 | |
| 弹窗完全关闭完成 | — |
| 用户拖动或 | |
| 拖动/动画过程中持续触发位置更新 | |
完整事件列表(拖动事件、聚焦/失焦事件、will/did生命周期对、)请查看API参考。
onBackPressMethods
方法
On a ref:
- — show the sheet
present(index?, animated?) - — hide the sheet and all its children
dismiss(animated?) - — hide only sheets stacked on top
dismissStack(animated?) - — snap to a detent by index
resize(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:
tsx
const { present, dismiss, dismissStack, resize, dismissAll } = useTrueSheet()Ref上的方法:
- — 展示弹窗
present(index?, animated?) - — 隐藏弹窗及其所有子弹窗
dismiss(animated?) - — 仅隐藏堆叠在当前弹窗之上的弹窗
dismissStack(animated?) - — 按索引跳转到指定锚点
resize(index)
全局方法(仅原生端):
TrueSheet.present(name, index?, animated?)TrueSheet.dismiss(name, animated?)TrueSheet.dismissStack(name, animated?)TrueSheet.resize(name, index)TrueSheet.dismissAll(animated?)
Web端Hook:
tsx
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.
- cascades: it dismisses the current sheet plus everything stacked on top
dismiss() - dismisses only the sheets on top, keeping the current one visible
dismissStack() - Use /
onDidFocusto react to a sheet gaining or losing the top positiononDidBlur
在已有弹窗可见的情况下展示新弹窗,旧弹窗会自动隐藏。关闭最上层弹窗后,下层弹窗会自动恢复。该特性是内置的——无需额外配置。
- 会级联关闭:会关闭当前弹窗以及所有堆叠在其之上的弹窗
dismiss() - 仅关闭上层弹窗,保留当前弹窗可见
dismissStack() - 可以用/
onDidFocus监听弹窗获得/失去顶层位置的事件onDidBlur
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 v2→v3 |
| Troubleshooting | Common issues and fixes by platform |
当你需要完整信息时,可以查看这些参考文件:
| 参考文档 | 内容 |
|---|---|
| 配置说明 | 所有属性的类型、默认值、平台支持和注意事项 |
| API参考 | 完整的事件和方法参考,包含参数类型 |
| 高级模式 | 导航、Reanimated、Web、侧边弹窗、液态玻璃、Jest mock、v2→v3迁移 |
| 问题排查 | 各平台常见问题和解决方案 |