mcpField
T
truesheet-usage

truesheet-usage

github.com/lodev09開発2.1k836 回インストール今日更新
リポジトリ

truesheet-usage について

truesheet-usageは、React Native TrueSheet v4を利用する開発者向けの実装ガイドです。統合パターンの選択から、シートのレイアウトや操作、一般的なインタラクションまで扱います。

主な機能

  1. 01シート制御パターンの選択ガイド
  2. 02デテントとコンテンツ配置のガイド
  3. 03スクロール領域・ヘッダー・フッター
  4. 04背景・キーボード・閉じ方の指針

活用シーン

  1. 01React 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+)と新しいアーキテクチャが必要です。

クイックスタート

最もシンプルなシートです。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)

シートのコンテンツは自然にレイアウトされます。通常のビューや React Navigation の画面と同様に、子要素の高さに合わせて広がります。デフォルトではシート全体を埋めません。そのため、次の点に注意してください。

  • コンテンツが埋められていない場合、flex: 1 の子要素の高さはゼロになります。
  • スペーサー、中央揃えのコンテンツ、サイズ制限付きのスクロールビューなど、各デテントでシートの表示領域を埋めるには、シートの style prop に flex: 1 を渡します。
<TrueSheet style={{ flex: 1 }} detents={[0.5, 1]}>
  <View style={{ flex: 1, justifyContent: 'center' }}>
    <Text>Centered</Text>
  </View>
</TrueSheet>

コンテンツのサイズは各デテントでシートの表示領域に合わせて調整され、ドラッグ中もリアルタイムで追従します。そのため、フレックスレイアウトはシートの端にフレーム単位で追従します。

適切な制御パターンを選ぶ

トリガーがシートに対してどこにあるか、対象とするプラットフォームに応じて選択してください。

パターン用途プラットフォーム
Refトリガーとシートが同じコンポーネントにあるすべて
名前付きシート+グローバルメソッドトリガーがシートから離れた場所にある(別の画面、ツリーの深い位置)ネイティブのみ
TrueSheetProvider + useTrueSheet()Web 対応が必要な場合、またはフックベースの制御を使いたい場合すべて(Web では必須)
createTrueSheetNavigator() / Expo Router Sheetシートがナビゲーションフローの一部であるすべて
ReanimatedTrueSheetシートの位置に同期したアニメーション値が必要な場合すべて
Ref ベース

クイックスタートで紹介した方法です。ref で present()、dismiss()、resize(index) を使用します。

グローバルメソッドを使った名前付きシート(ネイティブのみ)

トリガーがシートのレンダリング位置から離れている場合に使います。

// 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 では静的メソッドを使用できません。代わりにプロバイダーパターンを使用してください。

プロバイダーを使った Web 向けの制御

アプリを TrueSheetProvider でラップします(ネイティブではオーバーヘッドなしでそのまま通過します)。Web では @radix-ui/react-dialog と @radix-ui/react-presence のインストールも必要です(オプションのピア依存関係)。

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 を含む画面オプション、シート画面をスタックとして使う複数ステップのフロー、useTrueSheetNavigation を使った完全な設定については、高度なパターンのリファレンスを参照してください。

Reanimated

ReanimatedTrueSheet、ReanimatedTrueSheetProvider、およびアニメーション値(animatedPosition、animatedIndex、animatedDetent)については、高度なパターンのリファレンスを参照してください。

デテント

デテントは、シートがスナップする高さを定義します。最大 3 つのデテントを指定でき、小さい順に並びます。デフォルトは [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)は、シートに style={{ flex: 1 }} が設定されていないと、'auto' で空白になります。シートのサイズはリストに合わせて決まります
  • スクロールビューは直接の子でなくてもかまいません。ラップする 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 の props を使用します。ネイティブコンテナビュー内にレンダリングされるため、レイアウト計算は自動的に処理されます。絶対配置で代用しないでください。
  • どちらもデフォルトで相対配置です。ヘッダーはコンテンツの上、フッターは下にスペースを取り、両方とも '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' の高さに含まれます。フッターのインセット調整を有効にする必要があります。
のぞき見(地図風の折りたたみシート)
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 コンポーネントは 1 つです。絶対配置のフッターは peek の高さに含まれますが、相対配置のフッターは含まれません(peek 時には画面外に押し出されます)。

シートの上に表示するトーストとダイアログ
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 はシートのエフェクトの上に任意のノードを描画します。このラッパーはタッチを無視します。 blurOptions は v4 で削除されました。強度をカスタマイズするには、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 }} を渡してください。そうしないと、高さが 0 で描画されます。
  3. scrollableRef は v3 の scrollable プロパティの代わりです — ScrollView/FlatList の ref を渡します。固定デテントではシートの style に flex: 1 を指定してバインドしてください。'auto' では不要ですが、FlashList や flex: 1 でラップされたほかのリストは例外です。
  4. resize() が受け取るのはインデックスであり、割合ではありません。resize(1) は「2 番目のデテントに移動する」という意味です。
  5. シート名はアプリ全体で一意にする必要があります。
  6. 静的メソッドはネイティブ専用です — Web では useTrueSheet() を使用してください。
  7. フッターや、シートに組み込んだスクロールコンテンツに手動でセーフエリアのパディングを追加しないでください — フッターが下部インセットを吸収し、スクロール可能な領域にはネイティブに適用されます。二重に適用することは、v4 で最もよくあるレイアウトの不具合です。
  8. autoFocus が動作するのはマウント時のみです — デフォルトの遅延マウントシートでは問題ありません(シートと一緒にキーボードが表示されます)が、lazy={false} の場合は最初の表示時にしかフォーカスしません。毎回表示時にフォーカスするには、onWillPresent(シートと一緒にキーボードが表示されます)または onDidPresent(シートが表示された後)でフォーカスしてください。
    <TrueSheet lazy={false} onWillPresent={() => inputRef.current?.focus()}>
    
  9. Android でシート内に GestureHandlerRootView を配置する場合、シート自体に style={{ flex: 1 }} を指定していない限り、flex: 1 ではなく flexGrow: 1 を使用してください。
  10. iOS では、Modal を閉じる前にシートを閉じてください — シートが表示されている状態で Modal を閉じると、画面が真っ白になる React Native の不具合があります。
  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+非対応非対応
静的なグローバルメソッド対応対応非対応(プロバイダーを使用)
scrollableRef対応対応対応
スクロールエッジ効果iOS 26+非対応非対応
grabberOptions対応対応非対応
dimColor / dimOpacityカスタムの暗転レイヤーがシステムの暗転を置き換えます。重ねたシートごとに暗転レイヤーが追加されます対応対応
placement / サイドシートマージンはシステムが制御します。iOS 27+ では 'automatic' と 'center' の動作が異なりますplacementOffset propplacementOffset prop
presentationiOS 17+(iPad)該当なし横向き/タブレット
foldOptionsiOS 27.1+ の折りたたみ式デバイス(テーブルトップ)非対応非対応
detached モード非対応非対応対応
insetAdjustment対応対応非対応
dismissThreshold非対応(システムが制御)対応対応
lazy={false}(コンテンツの事前マウント)対応対応非対応
エッジ・トゥ・エッジ該当なし自動検出該当なし
キーボード処理組み込み組み込みソフトキーボードの上に表示

イベント

最もよく使用されるイベント:

イベント発生タイミングペイロード
onMountコンテンツがマウントされ、使用可能になったとき—
onDidPresentシートの表示が完了したとき{ index, position, detent }
onDidDismissシートの非表示が完了したとき—
onDismissAttemptdismissible={false} の状態でユーザーが非表示にしようとしたとき—
onDetentChangeユーザーのドラッグまたは resize() によって detent が変化したとき{ index, position, detent }
onPositionChangeドラッグ/アニメーション中に位置が継続的に更新されたとき{ index, position, detent, realtime }

イベントの全一覧(ドラッグイベント、フォーカス/ぼかしイベント、will/did ライフサイクルのペア、onBackPress)については、API リファレンスを参照してください。

メソッド

ref で使用:

  • present(index?, animated?) — シートを表示します
  • dismiss(animated?) — シートとそのすべての子シートを非表示にします
  • dismissStack(animated?) — 上に重なっているシートのみを非表示にします
  • resize(index) — インデックスで指定した detent にスナップします

グローバル(ネイティブのみ):

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

Web フック:

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

シートの重ね合わせ

別のシートが表示されている状態で新しいシートを表示すると、最初のシートは自動的に隠れます。一番上のシートを非表示にすると、前のシートが再び表示されます。この機能は組み込み済みのため、追加の設定は不要です。

  • dismiss() は連鎖して動作し、現在のシートとその上に重なっているすべてのシートを非表示にします
  • dismissStack() は上に重なっているシートのみを非表示にし、現在のシートは表示したままにします
  • シートが一番上に移動したとき、または一番上から外れたときの処理には、onDidFocus / onDidBlur を使用します

詳細リファレンス

全体像を確認するには、次のリファレンスファイルを参照してください。

リファレンス内容
設定各 prop の型、デフォルト値、プラットフォーム対応状況、注記
APIペイロードの型を含む、イベントとメソッドの完全なリファレンス
高度なパターンナビゲーション、Reanimated、Web、サイドシート、背景、Liquid Glass、Jest のモック、v3→v4 の移行
トラブルシューティングプラットフォーム別の一般的な問題と解決方法

コメント