This project uses React Compiler (babel-plugin-react-compiler v1.0.0) with React 19.
React.memo()useMemo()useCallback()memo()HOC- Any manual memoization patterns
React Compiler automatically handles all memoization at build time. Manual memoization is:
- Redundant - Compiler does it better
- Harmful - Can conflict with compiler optimizations
- Unnecessary - Compiler tracks dependencies automatically
plugins: [
['babel-plugin-react-compiler', {
target: '19',
panicThreshold: 'all_errors', // Strict mode
}],
]Use the 'use no memo' directive at the top of the file (see BottomSheetPortal.tsx for example). This is RARE and only needed when:
- Dynamic ref cloning breaks compiler analysis
- External library integration requires it
A library-agnostic stack manager for bottom sheets and modals in React Native. Provides:
- Adapter architecture: Pluggable adapters for any bottom sheet/modal library
- Navigation modes: push, switch, replace
- iOS-style scale animations: Background content scales when sheets open
- Context preservation: Via portals (
react-native-teleport) - Persistent sheets: Pre-mounted sheets that maintain state across open/close cycles
- Type-safe APIs: TypeScript with augmentable type registry
| Category | Package | Version |
|---|---|---|
| React | react | 19.1.0 |
| React Native | react-native | 0.81.5 |
| Animation | react-native-reanimated | ^4.2.1 |
| State | zustand | ^5.0.3 |
| Portals | react-native-teleport | ^0.5.6 |
| Adapter | Import subpath | Wraps |
|---|---|---|
GorhomSheetAdapter |
react-native-bottom-sheet-stack/gorhom |
@gorhom/bottom-sheet |
CustomModalAdapter |
react-native-bottom-sheet-stack (main) |
Custom animated modal (zero deps) |
ReactNativeModalAdapter |
react-native-bottom-sheet-stack/react-native-modal |
react-native-modal |
ActionsSheetAdapter |
react-native-bottom-sheet-stack/actions-sheet |
react-native-actions-sheet |
┌─────────────────────────────────────────────────────────────────┐
│ BottomSheetManagerProvider │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ PortalProvider │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ BottomSheetManagerContext │ │ │
│ │ │ (groupId, scaleConfig) │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
┌────────────────────┴────────────────────┐
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ BottomSheetScaleView │ │ BottomSheetHost │
│ (wraps app content) │ │ (renders sheets) │
└─────────────────────┘ └─────────────────────┘
│
┌──────────────────────────────┴──────────────────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ QueueItem │ │ QueueItem │ │ QueueItem │
│ (sheet slot) │ │ (sheet slot) │ │ (sheet slot) │
│ zIndex: 0,1 │ │ zIndex: 2,3 │ │ zIndex: 4,5 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
┌──────────┴──────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ PortalHost │ │ Inline Content │
│ (portal mode) │ │ (dynamic mode) │
└─────────────────┘ └─────────────────┘
Purpose: Single source of truth for all sheet state and stack ordering.
State Structure:
interface BottomSheetStoreState {
sheetsById: Record<string, BottomSheetState>; // All sheets by ID
stackOrder: string[]; // Visible sheet IDs in order
}
interface BottomSheetState {
groupId: string; // Manager group ID
id: string; // Unique sheet ID
content?: ReactNode; // For inline mode only
status: BottomSheetStatus; // 'opening' | 'open' | 'closing' | 'hidden'
scaleBackground?: boolean; // Enable iOS-style scale
usePortal?: boolean; // Portal mode flag
params?: Record<string, unknown>; // Type-safe params
keepMounted?: boolean; // Persistent sheet flag
}Key Actions:
open(sheet, mode)- Opens sheet with navigation modemarkOpen(id)- Transitions 'opening' → 'open'startClosing(id)- Initiates close animationfinishClosing(id)- Completes close (hides if keepMounted, removes otherwise)mount(sheet)- Pre-mounts persistent sheet with 'hidden' statusunmount(id)- Removes persistent sheet
Navigation Modes (OpenMode):
push- Keeps previous sheet visible (stacking)switch- Hides previous sheet (hidden status, not removed)replace- Closes previous sheet (closing status, then removed)
Purpose: Bidirectional sync between Zustand store and sheet adapters.
Two Directions:
-
Store → Adapter (
initBottomSheetCoordinator):- Subscribes to store changes
- Calls
ref.expand()when status becomes 'opening' - Calls
ref.close()when status becomes 'hidden' or 'closing'
-
Adapter → Store (
createSheetEventHandlers):handleDismiss: User swipes down / back button →startClosing()handleOpened: Show animation completes →markOpen()handleClosed: Hide animation completes →finishClosing()
const sheetRefsMap = new Map<string, RefObject<BottomSheetMethods>>();Why: Refs cannot be stored in Zustand (not serializable). Global map allows coordinator to access refs by sheet ID.
const animatedIndexRegistry = new Map<string, SharedValue<number>>();Why: Shared animated values for backdrop opacity interpolation. Created lazily via getAnimatedIndex(id).
Wraps app with:
PortalProvider(from react-native-teleport)BottomSheetManagerContext(groupId, scaleConfig)
Purpose: Renders active sheets from store. Responsibilities:
- Initializes coordinator subscription
- Clears group on unmount
- Renders QueueItems for each sheet
Purpose: Single sheet rendering with proper z-index layering.
Z-Index Strategy:
const backdropZIndex = stackIndex * 2; // Even numbers: 0, 2, 4...
const contentZIndex = stackIndex * 2 + 1; // Odd numbers: 1, 3, 5...This ensures backdrop always renders below its sheet's content.
Rendering Modes:
- Portal Mode (
usePortal: true): Renders<PortalHost>that receives content fromBottomSheetPortal - Inline Mode (
usePortal: false): Renders content directly withBottomSheetContext.Provider
Uses 'use no memo' directive - Compiler cannot optimize due to dynamic ref cloning.
Purpose: Defines portal-based sheet content. Renders into PortalHost in QueueItem. When to use: When sheet needs access to parent React context (Redux, custom contexts, etc.)
Purpose: Sheet that stays mounted even when closed.
Lifecycle:
- On mount:
mount()action creates sheet withstatus: 'hidden',keepMounted: true - On open: Store moves to stack, status → 'opening'
- On close: Status → 'hidden' (NOT removed from sheetsById)
- On unmount:
unmount()action removes from store
Use Case: Sheets with heavy state (forms, media players) that need to preserve state.
Purpose: Wraps app content to apply iOS-style scale animation.
Note: Must be sibling to BottomSheetHost, not parent.
Purpose: Animated backdrop with opacity based on sheet's animatedIndex. Key: Only interactive when status is 'open' (prevents animation conflicts).
Purpose: Imperative API for opening sheets with content.
const { open, close, clear } = useBottomSheetManager();
// Open with inline content (content cloned with ref)
const id = open(<MySheet />, { mode: 'push', scaleBackground: true });
close(id);When to use: Opening sheets dynamically with content as parameter.
Purpose: Type-safe control for portal-based sheets.
const { open, close, updateParams } = useBottomSheetControl('user-sheet');
open({ params: { userId: '123' } });
updateParams({ userId: '456' });When to use: Controlling pre-defined portal sheets with type-safe params.
Purpose: Access current sheet's ID and params from within the sheet.
// Inside a sheet component
const { id, params, close } = useBottomSheetContext<'user-sheet'>();Purpose: Observe sheet status from outside the sheet.
Works with all sheet types: Portal, persistent, and inline sheets.
// Portal/persistent sheet (registered ID)
const { status, isOpen } = useBottomSheetStatus('user-sheet');
// Inline sheet (dynamic ID from useBottomSheetManager)
const { open } = useBottomSheetManager();
const sheetId = open(<MySheet />);
// Later...
const { status, isOpen } = useBottomSheetStatus(sheetId);Purpose: Calculates scale animation values based on sheet depth.
Key Concept - Power Scaling:
const currentScale = Math.pow(scale, depth); // e.g., 0.92^1 = 0.92, 0.92^2 = 0.85Creates cascading scale effect for nested sheets.
useScaleDepth: Returns number of scaleBackground: true sheets above current position.
Purpose: Determines which sheets to render and in what order.
Render Order:
- Hidden persistent sheets (keepMounted=true, not in stack)
- Active sheets (in stackOrder)
This prevents React from unmounting/remounting during state transitions.
Purpose: RFC useEvent implementation - stable function identity with latest closure.
Usage: Used in BottomSheetPersistent for mount callback.
Provides current sheet ID to children. Used by useBottomSheetContext.
Provides groupId and scaleConfig to all components within a manager.
Passes sheet ref from Persistent/Portal to Managed without user intervention.
Purpose: Enables type-safe sheet IDs and params via module augmentation.
// In your app:
declare module 'react-native-bottom-sheet-stack' {
interface BottomSheetPortalRegistry {
'simple-sheet': true; // No params
'user-sheet': { userId: string }; // With params
}
}Key Types:
BottomSheetPortalId- Union of registered sheet IDs (orstringif no registry)BottomSheetPortalParams<T>- Params type for specific sheet IDHasParams<T>- Boolean type for param requirement checking
mount() (persistent only)
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 'hidden' │
│ (persistent sheets only - not rendered, but in sheetsById) │
└─────────────────────────────────────────────────────────────┘
│
open() action
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 'opening' │
│ (coordinator calls ref.expand(), animation starting) │
└─────────────────────────────────────────────────────────────┘
│
handleChange(index >= 0)
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 'open' │
│ (fully visible, interactive) │
└─────────────────────────────────────────────────────────────┘
│
startClosing() (user swipe or API call)
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 'closing' │
│ (coordinator calls ref.close(), animation running) │
└─────────────────────────────────────────────────────────────┘
│
handleClose()
│
┌────────────────┴────────────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ keepMounted=true │ │ keepMounted=false│
│ → 'hidden' │ │ → REMOVED │
└──────────────────┘ └──────────────────┘
This library provides three distinct ways to use bottom sheets. Each mode has different trade-offs:
| Mode | Component | Context Preserved | State Preserved | Use Case |
|---|---|---|---|---|
| Inline | useBottomSheetManager |
No | No | Dynamic, one-off sheets |
| Portal | BottomSheetPortal |
Yes | No | Pre-defined sheets needing context |
| Persistent | BottomSheetPersistent |
Yes | Yes | Heavy state sheets (forms, media) |
Component: useBottomSheetManager hook
Flags: usePortal: false, keepMounted: false
import { GorhomSheetAdapter } from 'react-native-bottom-sheet-stack/gorhom';
const { open, close } = useBottomSheetManager();
// Open with inline content
const id = open(
<GorhomSheetAdapter snapPoints={['50%']}>
<DynamicContent data={someData} />
</GorhomSheetAdapter>,
{ scaleBackground: true, mode: 'push' }
);
// Close by ID
close(id);Data Flow:
useBottomSheetManager.open()clones content with ref attached- Content stored in
sheetsById[id].content QueueItemrenders content directly withBottomSheetContext.Provider- On close: Sheet removed from store entirely
Characteristics:
- Content passed as JSX parameter at runtime
- Sheet unmounted on close (state lost)
- No access to parent React context (Redux, etc.)
- Random ID generated if not provided
Use When:
- Sheet content determined at runtime
- Quick confirmation dialogs, alerts
- No need for parent context access
- State doesn't need to persist
Component: BottomSheetPortal
Flags: usePortal: true, keepMounted: false
import { GorhomSheetAdapter } from 'react-native-bottom-sheet-stack/gorhom';
// 1. Define sheet at declaration site (near context providers)
<BottomSheetPortal id="user-sheet">
<GorhomSheetAdapter snapPoints={['50%']}>
<UserSheetContent /> {/* Has access to parent contexts! */}
</GorhomSheetAdapter>
</BottomSheetPortal>
// 2. Control from anywhere
const { open, close, updateParams } = useBottomSheetControl('user-sheet');
open({ params: { userId: '123' }, scaleBackground: true });
updateParams({ userId: '456' });
close();Data Flow:
BottomSheetPortaldefines content at declaration site (renders<Portal>)- Content stays in React tree where declared (context preserved)
QueueItemprovides<PortalHost>when sheet is in stackreact-native-teleportteleports content into PortalHost- On close: Sheet removed from store (content unmounts)
Characteristics:
- Content defined once, controlled imperatively
- Sheet unmounted on close (state lost)
- Full access to parent React context
- Type-safe params via registry augmentation
- Uses
'use no memo'directive (React Compiler exception)
Use When:
- Sheet needs Redux, React Query, or custom context
- Sheet ID and structure known at compile time
- Type-safe params desired
- State doesn't need to persist across open/close
Component: BottomSheetPersistent
Flags: usePortal: true, keepMounted: true
import { GorhomSheetAdapter } from 'react-native-bottom-sheet-stack/gorhom';
// 1. Define persistent sheet (content stays mounted!)
<BottomSheetPersistent id="scanner-sheet">
<GorhomSheetAdapter snapPoints={['90%']}>
<ScannerWithHeavyState /> {/* State preserved across close/open! */}
</GorhomSheetAdapter>
</BottomSheetPersistent>
// 2. Control from anywhere
const { open, close } = useBottomSheetControl('scanner-sheet');
open({ scaleBackground: true });
// User interacts, builds up state...
close();
// State is NOT lost!
open(); // Reopens with previous state intactData Flow:
- On component mount:
mount()action creates sheet withstatus: 'hidden' - Sheet exists in
sheetsByIdbut NOT instackOrder - On
open(): Sheet added tostackOrder, status → 'opening' - On close: Status → 'hidden', removed from
stackOrderbut KEPT insheetsById - Content stays mounted (just hidden), state preserved
- On component unmount:
unmount()removes from store completely
Lifecycle Diagram:
Component Mount → store.mount() → status: 'hidden' (in sheetsById, not in stackOrder)
│
open() called
│
▼
status: 'opening' (added to stackOrder)
│
animation done
│
▼
status: 'open'
│
close() called
│
▼
status: 'closing' → 'hidden'
(removed from stackOrder, kept in sheetsById)
Content stays mounted! State preserved!
│
open() again
│
▼
status: 'opening' (same content, same state)
Characteristics:
- Content stays mounted even when sheet is closed
- Full state preservation (form inputs, scroll position, media playback)
- Full access to parent React context
- Uses own
useReffor sheet reference (not createRef) - Re-mounts automatically if cleared by
clearGroup()during fast refresh
Use When:
- Heavy initialization (camera, media player, complex forms)
- User expects to return to same state
- Performance-critical (avoid remount cost)
- Long-lived sheets opened/closed frequently
INLINE MODE (useBottomSheetManager):
┌─────────────────────────────────────────────────────┐
│ sheetsById: { 'abc123': { content: <JSX>, ... } } │
│ stackOrder: ['abc123'] │
└─────────────────────────────────────────────────────┘
After close: Sheet DELETED from sheetsById
PORTAL MODE (BottomSheetPortal):
┌─────────────────────────────────────────────────────┐
│ sheetsById: { 'user-sheet': { usePortal: true } } │
│ stackOrder: ['user-sheet'] │
└─────────────────────────────────────────────────────┘
After close: Sheet DELETED from sheetsById
PERSISTENT MODE (BottomSheetPersistent):
┌──────────────────────────────────────────────────────────────────┐
│ sheetsById: { 'scanner': { usePortal: true, keepMounted: true } }│
│ stackOrder: ['scanner'] │
└──────────────────────────────────────────────────────────────────┘
After close: Sheet KEPT in sheetsById with status: 'hidden'
Removed from stackOrder only
┌─────────────────────────────┐
│ Need parent React context? │
└─────────────┬───────────────┘
│
┌─────────────┴───────────────┐
│ │
NO YES
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────────────┐
│ INLINE MODE │ │ Need state preservation │
│ useBottomSheet- │ │ across open/close? │
│ Manager │ └───────────┬─────────────┘
└─────────────────┘ │
┌────────────┴────────────┐
│ │
NO YES
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ PORTAL MODE │ │ PERSISTENT MODE │
│ BottomSheet- │ │ BottomSheet- │
│ Portal │ │ Persistent │
└─────────────────┘ └─────────────────┘
interface ScaleConfig {
scale?: number; // Default: 0.92 (scale factor per depth level)
translateY?: number; // Default: 10 (vertical shift per depth level)
borderRadius?: number; // Default: 12 (corner radius when scaled)
animation?: ScaleAnimationConfig; // Timing or spring
}
type ScaleAnimationConfig =
| { type: 'timing'; config?: WithTimingConfig }
| { type: 'spring'; config?: WithSpringConfig };-
Depth Calculation (
useScaleDepth):- Counts sheets with
scaleBackground: trueabove current position - For
BottomSheetScaleView: Counts all scaleBackground sheets (binary 0 or 1) - For sheets: Counts scaleBackground sheets above it in stack
- Counts sheets with
-
Power Scaling:
currentScale = scale^depth // e.g., 0.92^2 = 0.8464 currentTranslateY = translateY * depth currentBorderRadius = min(borderRadius * depth, borderRadius)
-
Animation:
useDerivedValuecomputes animated progressuseAnimatedStyleapplies transforms
Multiple BottomSheetManagerProvider instances can run independently:
<BottomSheetManagerProvider id="main-group">
{/* Main app sheets */}
</BottomSheetManagerProvider>
<BottomSheetManagerProvider id="modal-group">
{/* Modal-specific sheets */}
</BottomSheetManagerProvider>Each group:
- Has its own stackOrder
- Sheets filtered by groupId in coordinator
clearGroup(groupId)clears only that group
- Strict mode enabled
noUncheckedIndexedAccess: true- Always check map/array accessverbatimModuleSyntax: true- Explicittypeimports
- Use
shallowcomparison for object selectors - Use
subscribeWithSelectorfor coordinator subscription - Never store refs in store (use global maps instead)
- Use
react-native-reanimatedworklets - Shared values via
makeMutable()for global access useAnimatedStylefor animated components
- Global refs map instead of context drilling
createReffor dynamic sheetsuseReffor persistent sheets
import { __resetSheetRefs, __resetAnimatedIndexes } from 'react-native-bottom-sheet-stack';
beforeEach(() => {
__resetSheetRefs();
__resetAnimatedIndexes();
});// Portal mode
const { open } = useBottomSheetControl('my-sheet');
open({ scaleBackground: true });
// Inline mode
const { open } = useBottomSheetManager();
open(<MySheet />, { scaleBackground: true });const { updateParams, resetParams } = useBottomSheetControl('user-sheet');
// Update
updateParams({ userId: newId });
// Reset to undefined
resetParams();function UserSheet() {
const { params, close } = useBottomSheetContext<'user-sheet'>();
// params is typed as { userId: string } | undefined
}// Push: Stack sheets (both visible)
open({ mode: 'push' });
// Switch: Hide previous, show new (can restore)
open({ mode: 'switch' });
// Replace: Close previous, show new (cannot restore)
open({ mode: 'replace' });src/
├── index.tsx # Public exports (no 3rd-party adapter deps)
├── bottomSheet.store.ts # Zustand store (state + actions)
├── bottomSheetCoordinator.ts # Store ↔ adapter sync
├── refsMap.ts # Global sheet refs registry
├── animatedRegistry.ts # Global animated values registry
├── adapter.types.ts # SheetAdapterRef, SheetAdapterEvents types
├── portal.types.ts # Type-safe portal registry types
│
├── BottomSheetManager.provider.tsx # Root provider component
├── BottomSheetManager.context.tsx # Manager context definition
├── BottomSheet.context.ts # Sheet context definition
├── BottomSheetRef.context.ts # Ref context definition
│
├── BottomSheetHost.tsx # Sheet queue renderer
├── QueueItem.tsx # Individual sheet slot
├── BottomSheetPortal.tsx # Portal mode definition ('use no memo')
├── BottomSheetPersistent.tsx # Persistent sheet component
├── BottomSheetScaleView.tsx # Background scale wrapper
├── BottomSheetBackdrop.tsx # Custom backdrop component
│
├── useBottomSheetManager.tsx # Dynamic sheet opening hook
├── useBottomSheetControl.ts # Portal sheet control hook
├── useBottomSheetContext.ts # Sheet internal context hook
├── useBottomSheetStatus.ts # External status monitoring hook
├── useAdapterRef.ts # Adapter ref helper hook
├── useAnimatedIndex.ts # Animated index context hook
├── useBackHandler.ts # Android back button handler
├── useScaleAnimation.ts # Scale animation hooks
├── useSheetRenderData.ts # Render order computation hook
├── useEvent.ts # Stable callback utility
│
└── adapters/ # Each adapter is a separate subpath export
├── gorhom-sheet/ # → 'react-native-bottom-sheet-stack/gorhom'
│ ├── index.ts
│ └── GorhomSheetAdapter.tsx
├── custom-modal/ # → 'react-native-bottom-sheet-stack' (main)
│ ├── index.ts
│ └── CustomModalAdapter.tsx
├── react-native-modal/ # → 'react-native-bottom-sheet-stack/react-native-modal'
│ ├── index.ts
│ └── ReactNativeModalAdapter.tsx
└── actions-sheet/ # → 'react-native-bottom-sheet-stack/actions-sheet'
├── index.ts
└── ActionsSheetAdapter.tsx
CORE (main entry — no 3rd-party bottom sheet deps):
react-native-reanimated ──────▶ bottomSheetCoordinator, useScaleAnimation
zustand ──────────────────────▶ bottomSheet.store
react-native-teleport ────────▶ BottomSheetPortal, BottomSheetPersistent, QueueItem
react-native-safe-area-context ▶ QueueItem (useSafeAreaFrame)
ADAPTERS (separate subpath exports — isolated dependency trees):
react-native-bottom-sheet-stack/gorhom:
@gorhom/bottom-sheet ────────▶ GorhomSheetAdapter
react-native-gesture-handler ─▶ (peer of @gorhom/bottom-sheet)
react-native-bottom-sheet-stack/react-native-modal:
react-native-modal ──────────▶ ReactNativeModalAdapter
react-native-bottom-sheet-stack/actions-sheet:
react-native-actions-sheet ──▶ ActionsSheetAdapter
Adapters with 3rd-party dependencies are shipped as separate subpath exports so the main entry point never causes Metro resolution errors for uninstalled libraries.
package.json exports field:
{
".": "./lib/commonjs/index.js",
"./gorhom": "./lib/commonjs/adapters/gorhom-sheet/index.js",
"./react-native-modal": "./lib/commonjs/adapters/react-native-modal/index.js",
"./actions-sheet": "./lib/commonjs/adapters/actions-sheet/index.js"
}Import patterns:
// Core — safe to import without any adapter deps installed
import { BottomSheetManagerProvider, useBottomSheetManager } from 'react-native-bottom-sheet-stack';
import { CustomModalAdapter } from 'react-native-bottom-sheet-stack'; // zero deps
// Adapters — import only when the underlying library is installed
import { GorhomSheetAdapter } from 'react-native-bottom-sheet-stack/gorhom';
import { ReactNativeModalAdapter } from 'react-native-bottom-sheet-stack/react-native-modal';
import { ActionsSheetAdapter } from 'react-native-bottom-sheet-stack/actions-sheet';Backward compatibility: BottomSheetManaged and BottomSheetManagedProps are available as deprecated re-exports from the gorhom subpath.
Example app (monorepo dev): RNBB's babel-plugin-module-resolver alias breaks subpath imports. The example's babel.config.js adds a separate module-resolver plugin with explicit subpath aliases that runs before RNBB's override. Consumer apps do NOT need this — Metro reads exports from package.json directly.
- DO NOT memoize - React Compiler handles it
- DO NOT store refs in Zustand - Use refsMap instead
- DO NOT forget
BottomSheetHost- Sheets won't render without it - DO NOT nest
BottomSheetScaleViewaroundBottomSheetHost- They must be siblings - DO NOT use same sheet ID in multiple groups - IDs must be globally unique
- DO NOT call
open()on already-open sheet - It's a no-op by design - DO NOT export 3rd-party adapters from
src/index.tsx- They must use subpath exports to avoid Metro resolution errors