mcpField
T
truesheet-usage

truesheet-usage

github.com/lodev09开发2.1k836 次安装今天更新
代码仓库

关于 truesheet-usage

truesheet-usage 是面向 React Native TrueSheet v4 使用者的编码指南,帮助开发者选择集成方式并正确使用其公开 API。内容涵盖工作表布局、控制模式、停靠点和常见交互场景。

主要功能

  1. 01说明工作表控制模式的选择
  2. 02介绍停靠点与内容布局
  3. 03涵盖滚动视图及页眉页脚
  4. 04提供背景、键盘与关闭处理指引

适用场景

  1. 01在 React Native 应用中集成 TrueSheet
  2. 02实现可调整高度的内容面板
  3. 03为导航流程配置工作表界面

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/lodev09/react-native-true-sheet --skill truesheet-usage

安装后在 Claude Code 中输入/truesheet-usage确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
advanced-patterns.md
api.md
configuration.md
troubleshooting.md

详细介绍

TrueSheet 消费者指南

使用此技能可为使用 @lodev09/react-native-true-sheet(v4)的应用编写正确、符合惯例的代码。本指南涵盖如何选择合适的集成模式、正确使用公共 API,以及如何避免平台特有的陷阱。

需要 React Native >= 0.82(Expo SDK 55+)和新架构。

快速开始

最简单的 Sheet:一个 ref、一个按钮和一些内容。

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>
  )
}

内容布局(v4)

Sheet 的内容会自然布局——像常规视图或 React Navigation 屏幕一样,按子元素的高度自适应。默认情况下,内容不会填满 Sheet。由此会产生以下情况:

  • 如果内容未填满 Sheet,flex: 1 子元素的高度会收缩为零。
  • 若要按每个 detent 的可见高度填满 Sheet(用于间隔元素、居中内容、有界滚动视图),请通过 Sheet 的 style 属性传入 flex: 1:
<TrueSheet style={{ flex: 1 }} detents={[0.5, 1]}>
  <View style={{ flex: 1, justifyContent: 'center' }}>
    <Text>Centered</Text>
  </View>
</TrueSheet>

内容会按每个 detent 的 Sheet 可见高度调整大小,并在拖动时实时跟随,因此弹性布局会逐帧贴合 Sheet 的边缘。

选择合适的控制模式

根据触发器相对于 Sheet 的位置以及目标平台选择一种模式。

模式适用场景平台
Ref触发器和 Sheet 位于同一组件中全部
命名 Sheet + 全局方法触发器远离 Sheet(位于不同屏幕或组件树深处)仅原生平台
TrueSheetProvider + useTrueSheet()需要支持 Web,或希望通过 hook 控制全部(Web 上必需)
createTrueSheetNavigator() / Expo Router SheetSheet 属于导航流程的一部分全部
ReanimatedTrueSheet需要与 Sheet 位置同步的动画值全部
基于 Ref

快速开始中已展示。通过 ref 调用 present()、dismiss()、resize(index)。

使用全局方法控制命名 Sheet(仅原生平台)

当触发器远离 Sheet 的渲染位置时:

// 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()

每个 name 都必须唯一。静态方法在 Web 上不可用——请改用 Provider 模式。

使用 Provider 控制 Web

使用 TrueSheetProvider 包装应用(在原生平台上,它只是一个开销为零的透传组件)。Web 还需要安装 @radix-ui/react-dialog 和 @radix-ui/react-presence(可选 peer 依赖):

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>
  )
}
导航(React Navigation / Expo Router)

完整配置请参阅高级模式参考,内容包括 createTrueSheetNavigator、静态 API(createTrueSheetScreen)、来自 /navigation/expo-router 的 Expo Router Sheet 布局、屏幕选项(包括通过 setOptions 设置的 scrollableRef)、以堆叠 Sheet 屏幕实现的多步骤流程,以及 useTrueSheetNavigation。

Reanimated

有关 ReanimatedTrueSheet、ReanimatedTrueSheetProvider 和动画值(animatedPosition、animatedIndex、animatedDetent),请参阅高级模式参考。

Detent

Detent 定义 Sheet 可吸附到的高度。最多可设置 3 个 detent,并按从小到大排序。默认值:[0.5, 1]。

值含义
'auto'调整尺寸以适应内容(iOS 16+、Android、Web)。支持滚动容器——尺寸会适应滚动内容,并随内容增长或缩小而调整
'peek'折叠高度由测量得到的 header、绝对定位的 footer 以及截至 TrueSheetPeek 标记的内容共同决定(iOS 16+、Android、Web)。未提供这些内容时回退为 150
0 – 1屏幕高度的比例
// 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]} />

常用方案

可滚动内容

通过 scrollableRef 将工作表指向你的滚动视图。对于固定停靠点,在工作表的 style 上设置 flex: 1,以约束滚动视图:

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>
  • 使用 detents={['auto']} 时,无需设置 flex: 1——工作表会根据滚动内容调整大小,并自动约束视口
  • 例外:会用 flex: 1 包裹滚动视图的列表(例如 FlashList),在 'auto' 模式下会渲染为空白,除非工作表设置了 style={{ flex: 1 }}——工作表仍会根据列表调整大小
  • 滚动视图不必是直接子元素——可以用 View 包裹
  • 键盘处理、嵌套滚动(Android)和底部安全区域内边距会自动配置
  • 滚动到顶部会展开到下一个停靠点——使用 scrollableOptions={{ scrollingExpandsSheet: false }} 可禁用此行为(YouTube 风格的评论)
  • 底部安全区域内边距(以及绝对定位页脚的高度)会在内容可滚动时由原生端应用——不要手动添加内边距,也可以通过 scrollableOptions={{ contentInsetAdjustment: 'never' }} 关闭('safe-area' / '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>
  • 使用 header 和 footer 属性——它们会在原生容器视图中渲染,因此布局计算会自动处理。不要用绝对定位模拟。
  • 默认情况下,两者都是相对定位:页眉占据内容上方的空间,页脚占据内容下方的空间,并且两者都会计入 'auto' 停靠点。
  • 页脚会由原生端吸收底部安全区域内边距——从页脚内容中移除手动设置的 paddingBottom: insets.bottom,否则会重复叠加。通过 footerStyle 设置页脚背景,使其填满安全区域内边距。
  • 相对定位的页脚会布局在内容下方——对于固定停靠点,将内容限制在 flex: 1(工作表的 style)内,这样页脚才能保持可见。
  • 若要让它们浮在内容上方(不计入 'auto' 高度),请使用 headerOptions={{ position: 'absolute' }} / footerOptions={{ position: 'absolute' }}。绝对定位的页脚会升到键盘上方(通过 avoidKeyboard: false 关闭此行为);相对定位的页脚则会在键盘后方保持在布局流中。
  • 绝对定位的页脚会遮住已接入的滚动视图底部——滚动内容会自动按测得的页脚高度增加内边距(默认 contentInsetAdjustment: 'automatic'),且页脚会计入 'auto',因此不要硬编码 paddingBottom: FOOTER_HEIGHT。使用 'safe-area' 可让内容滚动到页脚下方。
  • 设置 avoidKeyboard: false 时,只有键盘上方的页脚部分会增加滚动内边距,并计入展开后的 'auto' 高度。必须启用页脚内边距调整。
Peek(地图样式的折叠工作表)
import { TrueSheet, TrueSheetPeek } from '@lodev09/react-native-true-sheet'

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

折叠时,工作表会显示从顶部到 TrueSheetPeek 底边的所有内容;下方内容会一直隐藏,直到工作表展开。空的 <TrueSheetPeek /> 可用作现有视图之间的折叠标记。每个工作表只能有一个 peek 组件。绝对定位的页脚会计入 peek 高度;相对定位的页脚不会(peek 状态下会被推到屏幕外)。

工作表上方的 Toast 和对话框
import { TrueSheet, TrueSheetOverlay } from '@lodev09/react-native-true-sheet'

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

在所有已呈现的工作表上方的原生图层中渲染子元素。通过有条件地渲染子元素来显示/隐藏。填满整个窗口;未落在子元素上的触摸事件会穿透。此后不要再使用 RN Modal/FullWindowOverlay。不要在其中放置 react-native-screens 容器(原生堆栈、标签页、抽屉)——Android 会崩溃。

不可取消的确认
<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>
取消前确认(未保存的更改)
<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>

不要试图通过回调来阻止取消——原生端无法等待 JS。通过状态切换 dismissible;被阻止的拖动以及返回/转义操作会触发 onDismissAttempt(点击遮罩不会),然后以编程方式调用 dismiss()。

背景
<TrueSheet backgroundBlur="system-material" /> {/* iOS-only blur, wins over backgroundColor */}
<TrueSheet backgroundStyle={{ backgroundColor: 'rgba(0, 122, 255, 0.25)' }} /> {/* tint glass/blur */}
<TrueSheet backgroundStyle={{ backgroundColor: '#18202b' }} /> {/* exact opaque color */}
<TrueSheet backgroundColor="transparent" /> {/* clear, no glass on supported iOS 26.1+ */}
<TrueSheet background={<BlurView intensity={40} style={StyleSheet.absoluteFill} />} /> {/* custom node */}
<TrueSheet detentBackgrounds={[null, { blur: 'system-material' }, '#18202b']} /> {/* per detent */}

backgroundStyle.backgroundColor 会覆盖玻璃或模糊效果。background 会在工作表效果上方渲染任意节点;包装器会忽略触摸事件。 v4 中已移除 blurOptions。如需自定义强度,请在 background 中使用第三方模糊效果。 有关图层、按停靠点过渡和 iOS 回退方案,请参阅 背景。

挂载时呈现
<TrueSheet detents={['auto', 1]} initialDetentIndex={0} initialDetentAnimated>
  <WelcomeContent />
</TrueSheet>
为 'auto' 停靠点预先挂载内容

默认情况下,内容会在首次调用 present() 时挂载。如果内容需要异步加载完成(例如获取数据、加载图像),'auto' 会过早测量。设置 lazy={false} 可预先挂载内容而不呈现,然后在准备就绪后再呈现(仅限原生端):

<TrueSheet ref={sheet} lazy={false} detents={['auto']}>
  <AsyncContent onReady={() => sheet.current?.present()} />
</TrueSheet>
控制变暗效果
// No dimming (allows background interaction)
<TrueSheet dimmed={false} detents={['auto']} />

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

// Custom dim color and opacity
<TrueSheet detents={['auto']} dimColor="#1b0a3c" dimOpacity={0.3} />

// Invisible dim that still blocks touches and dismisses on tap
<TrueSheet detents={['auto']} dimOpacity={0} />
以编程方式调整大小

resize() 接受停靠点索引,而非数值:

const sheet = useRef<TrueSheet>(null)

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

有助于节省调试时间的规则

  1. 最多 3 个停靠点,按从小到大排序。
  2. 内容默认不会填满工作表——如果子元素使用 flex: 1,请在工作表上设置 style={{ flex: 1 }},否则它们会以零高度渲染。
  3. scrollableRef 取代了 v3 的 scrollable 属性——传入 ScrollView/FlatList 的 ref。对于固定停靠点,在工作表的 style 上设置 flex: 1 以约束它;'auto' 不需要 flex,但 FlashList 和其他包装在 flex: 1 中的列表除外。
  4. resize() 接受索引,而非比例。resize(1) 表示“切换到第二个停靠点”。
  5. 整个应用中的工作表名称必须唯一。
  6. 静态方法仅限原生端——在 Web 上使用 useTrueSheet()。
  7. 不要为页脚或插入的滚动内容添加手动安全区域内边距——页脚会吸收底部内边距,滚动区域会在原生端获得该内边距。重复添加是 v4 最常见的布局问题。
  8. autoFocus 仅在挂载时触发——默认的延迟工作表可以正常使用(键盘会随工作表升起),但使用 lazy={false} 时,它只会在首次呈现时聚焦。要在每次呈现时聚焦,请在 onWillPresent(键盘随工作表升起)或 onDidPresent(工作表显示后)中执行:
    <TrueSheet lazy={false} onWillPresent={() => inputRef.current?.focus()}>
    
  9. 在 Android 上,工作表内的 GestureHandlerRootView 应使用 flexGrow: 1(而非 flex: 1)——除非工作表本身设置了 style={{ flex: 1 }}。
  10. 在 iOS 上,关闭 Modal 前先关闭工作表——React Native 存在一个 bug:工作表仍可见时关闭 Modal 会导致屏幕空白。
  11. 使用 header/footer 属性设置固定界面元素——不要使用绝对定位。需要让它们覆盖内容时,可通过 headerOptions/footerOptions 设置 position: 'absolute' 使其浮动。
  12. 背景效果:在受支持的 iOS 26.1+ 版本上,模糊效果优先于颜色,其次是默认的 Liquid Glass。要实现透明效果,请使用 backgroundColor="transparent"。可通过 backgroundStyle.backgroundColor 为玻璃/模糊效果添加色调,或绘制指定颜色。缺少效果设置器或在 iOS 26 上启用设计兼容模式时,会使用回退方案。iOS 27+ 会忽略 UIDesignRequiresCompatibility。如需自定义强度,请在 background 中使用第三方模糊效果;blurOptions 已移除。
  13. 原生堆栈应放在基础屏幕中,而不是工作表屏幕中(Sheet Navigator / Expo Router)。第一个屏幕是常规视图,可以承载 createNativeStackNavigator(这是包装应用的方式)。工作表屏幕无法承载它——Android 会因 ScreenContainer is not attached under ReactRootView 而崩溃。对于多步骤流程,请将每个步骤单独设置为工作表屏幕。

各平台差异一览

功能iOSAndroidWeb
'auto' detentiOS 16+是是
'peek' detent / TrueSheetPeekiOS 16+是是
TrueSheetOverlay是是是
detentBackgrounds在支持的 26.1+ 版本上使用原生效果,否则使用中点交叉淡化颜色交叉淡化颜色交叉淡化
backgroundBlur是否否
background / backgroundStyle是是是
Liquid GlassiOS 26+否否
静态全局方法是是否(使用 provider)
scrollableRef是是是
滚动边缘效果iOS 26+否否
grabberOptions是是否
dimColor / dimOpacity自定义变暗图层会替代系统变暗效果;堆叠的每个 sheet 都会各自添加一个是是
placement / 侧边 sheet边距由系统控制;在 iOS 27+ 上,'automatic' 与 'center' 的效果不同placementOffset 属性placementOffset 属性
presentationiOS 17+(iPad)不适用横屏/平板
foldOptionsiOS 27.1+ 可折叠设备(桌面模式)否否
detached 模式否否是
insetAdjustment是是否
dismissThreshold否(由系统控制)是是
lazy={false}(预先挂载内容)是是否
延伸至边缘不适用自动检测不适用
键盘处理内置内置在软键盘上方适配

事件

最常用的事件:

事件触发时机载荷
onMount内容已挂载并就绪—
onDidPresentSheet 展示完毕{ index, position, detent }
onDidDismissSheet 关闭完毕—
onDismissAttempt用户在 dismissible={false} 时尝试关闭—
onDetentChange用户拖动或 resize() 更改 detent{ index, position, detent }
onPositionChange拖动/动画期间持续更新位置{ index, position, detent, realtime }

完整事件列表(拖动事件、焦点/失焦事件、will/did 生命周期事件对、onBackPress),请参阅 API 参考。

方法

在 ref 上:

  • present(index?, animated?) — 显示 sheet
  • dismiss(animated?) — 隐藏 sheet 及其所有子级
  • dismissStack(animated?) — 仅隐藏堆叠在上方的 sheet
  • resize(index) — 按索引吸附到一个 detent

全局(仅原生平台):

  • 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()

堆叠 Sheet

当另一个 sheet 可见时展示新 sheet,第一个 sheet 会自动隐藏。关闭顶部 sheet 后,前一个 sheet 会重新显示。这是内置功能,无需额外配置。

  • dismiss() 会级联关闭:关闭当前 sheet 以及堆叠在其上方的所有 sheet
  • dismissStack() 仅关闭上方的 sheet,保留当前 sheet 可见
  • 使用 onDidFocus / onDidBlur 响应 sheet 获得或失去顶部位置

深入参考

需要了解完整信息时,请查看以下参考文件:

参考文档内容
配置每个属性的类型、默认值、平台支持情况和说明
API完整的事件和方法参考,以及载荷类型
高级模式导航、Reanimated、Web、侧边 sheet、背景、Liquid Glass、Jest 模拟、从 v3 迁移到 v4
故障排除各平台常见问题及解决方法

评论

支持 Markdown