Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 89 additions & 79 deletions example/src/sheets/ScannerSheet.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import type { BottomSheetMethods } from '@gorhom/bottom-sheet/lib/typescript/types';
import { forwardRef, useCallback, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import {
useBottomSheetContext,
useBottomSheetManager,
} from 'react-native-bottom-sheet-stack';

import { Badge, Button, SecondaryButton, Sheet } from '../components';
import type { SheetAdapterRef } from '../../../src/adapter.types';
import { SwmansionSheetAdapter } from '../../../src/adapters/swmansion';
import { Badge, Button, SecondaryButton } from '../components';
import { ScannerNestedSheet1 } from './ScannerNestedSheets';
import { colors, sharedStyles } from '../styles/theme';

export const ScannerSheet = forwardRef<BottomSheetMethods>((_, ref) => {
export const ScannerSheet = forwardRef<SheetAdapterRef>((_, ref) => {
const { close, params } = useBottomSheetContext<'scanner-sheet'>();
const { open } = useBottomSheetManager();
const [isScanning, setIsScanning] = useState(false);
Expand Down Expand Up @@ -39,92 +40,101 @@ export const ScannerSheet = forwardRef<BottomSheetMethods>((_, ref) => {
}, []);

return (
<Sheet ref={ref} enableDynamicSizing>
<View style={styles.badgeRow}>
<Badge label="Persistent" color={colors.cyan} />
<Badge
label={sourceLabel}
color={
params?.source === 'navigation' ? colors.purple : colors.primary
}
/>
</View>
<Text style={sharedStyles.h1}>{title}</Text>
<Text style={sharedStyles.text}>
This sheet is always mounted (keepMounted). It opens instantly without
mount delay and preserves state between open/close cycles.
</Text>
<SwmansionSheetAdapter ref={ref} detents={[0, 'content']} handle>
<View style={styles.content}>
<View style={styles.badgeRow}>
<Badge label="Persistent" color={colors.cyan} />
<Badge
label={sourceLabel}
color={
params?.source === 'navigation' ? colors.purple : colors.primary
}
/>
</View>
<Text style={sharedStyles.h1}>{title}</Text>
<Text style={sharedStyles.text}>
This sheet is always mounted (keepMounted). It opens instantly without
mount delay and preserves state between open/close cycles.
</Text>

{/* Scanner viewport */}
<View style={styles.scannerViewport}>
{isScanning ? (
<View style={styles.scanningOverlay}>
<View style={styles.scanLine} />
<Text style={styles.scanningText}>Scanning...</Text>
</View>
) : scanResult ? (
<View style={styles.resultContainer}>
<Text style={styles.resultLabel}>Scanned Code:</Text>
<Text style={styles.resultValue}>{scanResult}</Text>
</View>
) : (
<View style={styles.placeholderContainer}>
<Text style={styles.placeholderIcon}>[ ]</Text>
<Text style={styles.placeholderText}>Ready to scan</Text>
</View>
)}
</View>
{/* Scanner viewport */}
<View style={styles.scannerViewport}>
{isScanning ? (
<View style={styles.scanningOverlay}>
<View style={styles.scanLine} />
<Text style={styles.scanningText}>Scanning...</Text>
</View>
) : scanResult ? (
<View style={styles.resultContainer}>
<Text style={styles.resultLabel}>Scanned Code:</Text>
<Text style={styles.resultValue}>{scanResult}</Text>
</View>
) : (
<View style={styles.placeholderContainer}>
<Text style={styles.placeholderIcon}>[ ]</Text>
<Text style={styles.placeholderText}>Ready to scan</Text>
</View>
)}
</View>

{/* Actions */}
<View style={styles.actions}>
{scanResult ? (
<>
<Button title="Scan Again" onPress={handleReset} />
<Button
title="Open Nested Sheet"
onPress={() =>
open(<ScannerNestedSheet1 />, {
scaleBackground: true,
mode: 'switch',
})
}
/>
<SecondaryButton title="Close" onPress={close} />
</>
) : (
<>
<Button
title={isScanning ? 'Scanning...' : 'Start Scan'}
onPress={isScanning ? () => {} : handleStartScan}
/>
<Button
title="Open Nested Sheet"
onPress={() =>
open(<ScannerNestedSheet1 />, {
scaleBackground: true,
mode: 'switch',
})
}
/>
<SecondaryButton title="Close" onPress={close} />
</>
)}
</View>
{/* Actions */}
<View style={styles.actions}>
{scanResult ? (
<>
<Button title="Scan Again" onPress={handleReset} />
<Button
title="Open Nested Sheet"
onPress={() =>
open(<ScannerNestedSheet1 />, {
scaleBackground: true,
mode: 'switch',
})
}
/>
<SecondaryButton title="Close" onPress={close} />
</>
) : (
<>
<Button
title={isScanning ? 'Scanning...' : 'Start Scan'}
onPress={isScanning ? () => {} : handleStartScan}
/>
<Button
title="Open Nested Sheet"
onPress={() =>
open(<ScannerNestedSheet1 />, {
scaleBackground: true,
mode: 'switch',
})
}
/>
<SecondaryButton title="Close" onPress={close} />
</>
)}
</View>

{/* Info box */}
<View style={styles.infoBox}>
<Text style={styles.infoText}>
Try closing and reopening - the sheet appears instantly because it's
pre-mounted with keepMounted flag.
</Text>
{/* Info box */}
<View style={styles.infoBox}>
<Text style={styles.infoText}>
Try closing and reopening - the sheet appears instantly because it's
pre-mounted with keepMounted flag.
</Text>
</View>
</View>
</Sheet>
</SwmansionSheetAdapter>
);
});

ScannerSheet.displayName = 'ScannerSheet';

const styles = StyleSheet.create({
content: {
backgroundColor: colors.surface,
paddingHorizontal: 20,
paddingTop: 8,
paddingBottom: 32,
gap: 8,
},
badgeRow: {
flexDirection: 'row',
gap: 8,
Expand Down
17 changes: 12 additions & 5 deletions src/adapters/gorhom-sheet/GorhomSheetAdapter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import BottomSheetOriginal, {
import type { BottomSheetMethods } from '@gorhom/bottom-sheet/lib/typescript/types';
import React, { useEffect, useImperativeHandle, useRef } from 'react';
import { useAnimatedReaction } from 'react-native-reanimated';
import { scheduleOnRN } from 'react-native-worklets';

import type { SheetAdapterRef } from '../../adapter.types';
import {
Expand Down Expand Up @@ -57,6 +58,9 @@ export const GorhomSheetAdapter = React.forwardRef<
return () => setBackdrop(id, true);
}, [id, usesCustomBackdrop, setBackdrop]);

const { handleDismiss, handleOpened, handleClosed } =
createSheetEventHandlers(id);

useImperativeHandle(
ref,
() => ({
Expand All @@ -68,14 +72,18 @@ export const GorhomSheetAdapter = React.forwardRef<

useAnimatedReaction(
() => contextAnimatedIndex.value,
(value) => {
(value, prev) => {
externalAnimatedIndex?.set(value);
// gorhom can drop its onChange under rapid open/close interruptions
// (e.g. switch then immediate dismiss), leaving the sheet stuck mid-open.
// The animated index is the reliable signal: report opened when it
// crosses into an open snap point (idempotent via the status guard).
if (typeof prev === 'number' && prev < 0 && value >= 0) {
scheduleOnRN(handleOpened);
}
}
);

const { handleDismiss, handleOpened, handleClosed } =
createSheetEventHandlers(id);

useBackHandler(id, handleDismiss);

const wrappedOnAnimate: BottomSheetProps['onAnimate'] = (
Expand All @@ -96,7 +104,6 @@ export const GorhomSheetAdapter = React.forwardRef<
position,
type
) => {
// index >= 0 means sheet reached a valid snap point (opened)
if (index >= 0) {
handleOpened();
}
Expand Down
9 changes: 1 addition & 8 deletions src/adapters/swmansion/SwmansionSheetAdapter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import React, {
type ReactNode,
useEffect,
useImperativeHandle,
useRef,
useState,
} from 'react';
import {
Expand Down Expand Up @@ -356,9 +355,6 @@ export const SwmansionSheetAdapter = React.forwardRef<
);
}

// Guards against reporting the initial collapsed-detent settle as a close.
const hasOpenedRef = useRef(false);

useImperativeHandle(
ref,
() => ({
Expand All @@ -370,11 +366,8 @@ export const SwmansionSheetAdapter = React.forwardRef<

const handleNativeSettle = (settledIndex: number) => {
if (settledIndex <= 0) {
if (hasOpenedRef.current) {
handleClosed();
}
handleClosed();
} else {
hasOpenedRef.current = true;
handleOpened();
}
onSettle?.(settledIndex);
Expand Down
Loading