@lodev09/react-native-true-sheet 3.2.1 → 3.3.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +9 -3
  2. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +1 -1
  3. package/ios/TrueSheetView.mm +7 -3
  4. package/ios/TrueSheetViewController.mm +15 -7
  5. package/lib/module/TrueSheet.js +2 -2
  6. package/lib/module/TrueSheet.js.map +1 -1
  7. package/lib/module/TrueSheet.web.js +333 -0
  8. package/lib/module/TrueSheet.web.js.map +1 -0
  9. package/lib/module/TrueSheetProvider.js +26 -0
  10. package/lib/module/TrueSheetProvider.js.map +1 -0
  11. package/lib/module/TrueSheetProvider.web.js +74 -0
  12. package/lib/module/TrueSheetProvider.web.js.map +1 -0
  13. package/lib/module/fabric/TrueSheetViewNativeComponent.ts +2 -2
  14. package/lib/module/index.js +2 -1
  15. package/lib/module/index.js.map +1 -1
  16. package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js +1 -1
  17. package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js.map +1 -1
  18. package/lib/module/navigation/screen/TrueSheetScreen.js +1 -1
  19. package/lib/module/navigation/screen/TrueSheetScreen.js.map +1 -1
  20. package/lib/module/reanimated/ReanimatedTrueSheet.js +2 -2
  21. package/lib/module/reanimated/ReanimatedTrueSheet.js.map +1 -1
  22. package/lib/module/reanimated/ReanimatedTrueSheet.web.js +81 -0
  23. package/lib/module/reanimated/ReanimatedTrueSheet.web.js.map +1 -0
  24. package/lib/module/reanimated/index.js +2 -2
  25. package/lib/module/reanimated/index.js.map +1 -1
  26. package/lib/module/reanimated/useReanimatedPositionChangeHandler.web.js +21 -0
  27. package/lib/module/reanimated/useReanimatedPositionChangeHandler.web.js.map +1 -0
  28. package/lib/typescript/src/TrueSheet.d.ts +2 -2
  29. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  30. package/lib/typescript/src/TrueSheet.types.d.ts +44 -0
  31. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  32. package/lib/typescript/src/TrueSheet.web.d.ts +3 -0
  33. package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -0
  34. package/lib/typescript/src/TrueSheetProvider.d.ts +17 -0
  35. package/lib/typescript/src/TrueSheetProvider.d.ts.map +1 -0
  36. package/lib/typescript/src/TrueSheetProvider.web.d.ts +22 -0
  37. package/lib/typescript/src/TrueSheetProvider.web.d.ts.map +1 -0
  38. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +2 -2
  39. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
  40. package/lib/typescript/src/index.d.ts +1 -0
  41. package/lib/typescript/src/index.d.ts.map +1 -1
  42. package/lib/typescript/src/reanimated/ReanimatedTrueSheet.web.d.ts +41 -0
  43. package/lib/typescript/src/reanimated/ReanimatedTrueSheet.web.d.ts.map +1 -0
  44. package/lib/typescript/src/reanimated/useReanimatedPositionChangeHandler.web.d.ts +16 -0
  45. package/lib/typescript/src/reanimated/useReanimatedPositionChangeHandler.web.d.ts.map +1 -0
  46. package/package.json +11 -4
  47. package/src/TrueSheet.tsx +7 -3
  48. package/src/TrueSheet.types.ts +46 -0
  49. package/src/TrueSheet.web.tsx +407 -0
  50. package/src/TrueSheetProvider.tsx +29 -0
  51. package/src/TrueSheetProvider.web.tsx +81 -0
  52. package/src/fabric/TrueSheetViewNativeComponent.ts +2 -2
  53. package/src/index.ts +1 -0
  54. package/src/reanimated/ReanimatedTrueSheet.web.tsx +78 -0
  55. package/src/reanimated/useReanimatedPositionChangeHandler.web.ts +32 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lodev09/react-native-true-sheet",
3
- "version": "3.2.1",
3
+ "version": "3.3.0-beta.0",
4
4
  "description": "The true native bottom sheet experience for your React Native Apps.",
5
5
  "source": "./src/index.ts",
6
6
  "main": "./lib/module/index.js",
@@ -43,6 +43,7 @@
43
43
  ],
44
44
  "scripts": {
45
45
  "example": "yarn workspace react-native-true-sheet-example",
46
+ "expo-example": "yarn workspace expo-example",
46
47
  "docs": "yarn workspace docs",
47
48
  "test": "jest",
48
49
  "typecheck": "tsc",
@@ -84,6 +85,7 @@
84
85
  "@eslint/eslintrc": "^3.3.1",
85
86
  "@eslint/js": "^9.35.0",
86
87
  "@evilmartians/lefthook": "^2.0.4",
88
+ "@gorhom/bottom-sheet": "^5.2.8",
87
89
  "@react-native/babel-preset": "^0.82.1",
88
90
  "@react-native/eslint-config": "^0.82.1",
89
91
  "@react-navigation/native": "^7.1.6",
@@ -114,13 +116,17 @@
114
116
  "@types/react": "19.1.1"
115
117
  },
116
118
  "peerDependencies": {
117
- "@react-navigation/native": ">=6.0.0",
119
+ "@gorhom/bottom-sheet": ">=5",
120
+ "@react-navigation/native": ">=7",
118
121
  "react": "*",
119
122
  "react-native": "*",
120
- "react-native-reanimated": ">=4.0.0",
123
+ "react-native-reanimated": ">=4",
121
124
  "react-native-worklets": "*"
122
125
  },
123
126
  "peerDependenciesMeta": {
127
+ "@gorhom/bottom-sheet": {
128
+ "optional": true
129
+ },
124
130
  "@react-navigation/native": {
125
131
  "optional": true
126
132
  },
@@ -133,6 +139,7 @@
133
139
  },
134
140
  "workspaces": [
135
141
  "example",
142
+ "expo-example",
136
143
  "docs"
137
144
  ],
138
145
  "packageManager": "yarn@4.11.0",
@@ -166,7 +173,7 @@
166
173
  "tagName": "v${version}"
167
174
  },
168
175
  "npm": {
169
- "publish": true
176
+ "publish": false
170
177
  },
171
178
  "hooks": {
172
179
  "before:init": [
package/src/TrueSheet.tsx CHANGED
@@ -10,6 +10,7 @@ import {
10
10
 
11
11
  import type {
12
12
  TrueSheetProps,
13
+ TrueSheetRef,
13
14
  DragBeginEvent,
14
15
  DragChangeEvent,
15
16
  DragEndEvent,
@@ -34,7 +35,7 @@ import TrueSheetFooterViewNativeComponent from './fabric/TrueSheetFooterViewNati
34
35
 
35
36
  import TrueSheetModule from './specs/NativeTrueSheetModule';
36
37
 
37
- import { Platform, processColor, StyleSheet, findNodeHandle, View } from 'react-native';
38
+ import { Platform, StyleSheet, findNodeHandle, View, processColor } from 'react-native';
38
39
 
39
40
  const LINKING_ERROR =
40
41
  `The package '@lodev09/react-native-true-sheet' doesn't seem to be linked. Make sure: \n\n` +
@@ -56,7 +57,10 @@ interface TrueSheetState {
56
57
  shouldRenderNativeView: boolean;
57
58
  }
58
59
 
59
- export class TrueSheet extends PureComponent<TrueSheetProps, TrueSheetState> {
60
+ export class TrueSheet
61
+ extends PureComponent<TrueSheetProps, TrueSheetState>
62
+ implements TrueSheetRef
63
+ {
60
64
  displayName = 'TrueSheet';
61
65
 
62
66
  private readonly nativeRef: RefObject<NativeRef | null>;
@@ -413,7 +417,7 @@ export class TrueSheet extends PureComponent<TrueSheetProps, TrueSheetState> {
413
417
  grabber={grabber}
414
418
  grabberOptions={{
415
419
  ...grabberOptions,
416
- color: (processColor(grabberOptions?.color) as number) ?? 0,
420
+ color: processColor(grabberOptions?.color),
417
421
  }}
418
422
  dimmed={dimmed}
419
423
  dimmedDetentIndex={dimmedDetentIndex}
@@ -40,6 +40,52 @@ export type WillFocusEvent = NativeSyntheticEvent<null>;
40
40
  export type WillBlurEvent = NativeSyntheticEvent<null>;
41
41
  export type BackPressEvent = NativeSyntheticEvent<null>;
42
42
 
43
+ /**
44
+ * Ref methods exposed by a TrueSheet instance.
45
+ */
46
+ export interface TrueSheetRef {
47
+ /**
48
+ * Present the sheet at a given detent index.
49
+ * @param index - The detent index to present at (default: 0)
50
+ * @param animated - Whether to animate the presentation (default: true)
51
+ */
52
+ present: (index?: number, animated?: boolean) => Promise<void>;
53
+ /**
54
+ * Dismiss the sheet.
55
+ * @param animated - Whether to animate the dismissal (default: true)
56
+ */
57
+ dismiss: (animated?: boolean) => Promise<void>;
58
+ /**
59
+ * Resize the sheet to a given detent index.
60
+ * @param index - The detent index to resize to
61
+ */
62
+ resize: (index: number) => Promise<void>;
63
+ }
64
+
65
+ /**
66
+ * Methods for controlling TrueSheet instances by name.
67
+ * Returned by the `useTrueSheet` hook.
68
+ */
69
+ export interface TrueSheetContextMethods {
70
+ /**
71
+ * Present a sheet by name.
72
+ * @param name - The name of the sheet to present
73
+ * @param index - The detent index to present at (default: 0)
74
+ */
75
+ present: (name: string, index?: number) => Promise<void>;
76
+ /**
77
+ * Dismiss a sheet by name.
78
+ * @param name - The name of the sheet to dismiss
79
+ */
80
+ dismiss: (name: string) => Promise<void>;
81
+ /**
82
+ * Resize a sheet by name.
83
+ * @param name - The name of the sheet to resize
84
+ * @param index - The detent index to resize to
85
+ */
86
+ resize: (name: string, index: number) => Promise<void>;
87
+ }
88
+
43
89
  /**
44
90
  * Options for customizing the grabber (drag handle) appearance.
45
91
  */
@@ -0,0 +1,407 @@
1
+ import {
2
+ createElement,
3
+ Fragment,
4
+ forwardRef,
5
+ isValidElement,
6
+ useCallback,
7
+ useContext,
8
+ useEffect,
9
+ useImperativeHandle,
10
+ useMemo,
11
+ useRef,
12
+ useState,
13
+ } from 'react';
14
+ import { View, StyleSheet, useWindowDimensions } from 'react-native';
15
+
16
+ import {
17
+ BottomSheetBackdrop,
18
+ type BottomSheetBackdropProps,
19
+ BottomSheetFooter,
20
+ type BottomSheetFooterProps,
21
+ BottomSheetHandle,
22
+ type BottomSheetHandleProps,
23
+ BottomSheetModal,
24
+ BottomSheetView,
25
+ type SNAP_POINT_TYPE,
26
+ } from '@gorhom/bottom-sheet';
27
+ import { useDerivedValue, useSharedValue } from 'react-native-reanimated';
28
+
29
+ import { BottomSheetContext } from './TrueSheetProvider.web';
30
+ import type {
31
+ TrueSheetProps,
32
+ TrueSheetRef,
33
+ DetentChangeEvent,
34
+ DidBlurEvent,
35
+ DidDismissEvent,
36
+ DidFocusEvent,
37
+ DidPresentEvent,
38
+ MountEvent,
39
+ PositionChangeEvent,
40
+ WillBlurEvent,
41
+ WillDismissEvent,
42
+ WillFocusEvent,
43
+ WillPresentEvent,
44
+ DragBeginEvent,
45
+ DragChangeEvent,
46
+ DragEndEvent,
47
+ } from './TrueSheet.types';
48
+
49
+ const DEFAULT_CORNER_RADIUS = 16;
50
+ const DEFAULT_GRABBER_COLOR = 'rgba(0, 0, 0, 0.3)';
51
+
52
+ const renderSlot = (slot: TrueSheetProps['header'] | TrueSheetProps['footer']) => {
53
+ if (!slot) return null;
54
+ if (isValidElement(slot)) return slot;
55
+ return createElement(slot);
56
+ };
57
+
58
+ export const TrueSheet = forwardRef<TrueSheetRef, TrueSheetProps>((props, ref) => {
59
+ const {
60
+ name,
61
+ detents = [0.5, 1],
62
+ dismissible = true,
63
+ draggable = true,
64
+ dimmed = true,
65
+ dimmedDetentIndex = 0,
66
+ children,
67
+ scrollable = false,
68
+ initialDetentIndex = -1,
69
+ backgroundColor = '#ffffff',
70
+ cornerRadius = DEFAULT_CORNER_RADIUS,
71
+ grabber = true,
72
+ grabberOptions,
73
+ maxHeight,
74
+ header,
75
+ footer,
76
+ onMount,
77
+ onWillPresent,
78
+ onDidPresent,
79
+ onWillDismiss,
80
+ onDidDismiss,
81
+ onDetentChange,
82
+ onPositionChange,
83
+ onDragBegin,
84
+ onDragChange,
85
+ onDragEnd,
86
+ onWillFocus,
87
+ onDidFocus,
88
+ onWillBlur,
89
+ onDidBlur,
90
+ style,
91
+ } = props;
92
+
93
+ const { height: windowHeight } = useWindowDimensions();
94
+ const bottomSheetContext = useContext(BottomSheetContext);
95
+ const modalRef = useRef<BottomSheetModal>(null);
96
+ const initialDetentIndexRef = useRef(initialDetentIndex);
97
+ const currentIndexRef = useRef(0);
98
+ const isPresenting = useRef(false);
99
+ const isDismissing = useRef(false);
100
+ const isMinimized = useRef(false);
101
+ const isDragging = useRef(false);
102
+
103
+ const animatedPosition = useSharedValue(windowHeight);
104
+ const animatedIndex = useSharedValue(0);
105
+
106
+ const [snapIndex, setSnapIndex] = useState(initialDetentIndex);
107
+ const [isMounted, setIsMounted] = useState(false);
108
+
109
+ useDerivedValue(() => {
110
+ onPositionChange?.({
111
+ nativeEvent: {
112
+ position: animatedPosition.value,
113
+ index: animatedIndex.value,
114
+ detent: detents[animatedIndex.value] ?? 0,
115
+ realtime: true,
116
+ },
117
+ } as PositionChangeEvent);
118
+ });
119
+
120
+ const hasAutoDetent = detents.includes('auto');
121
+
122
+ const containerHeight = maxHeight ?? windowHeight;
123
+ const snapPoints = useMemo(
124
+ () =>
125
+ detents
126
+ .filter((detent): detent is number => detent !== 'auto' && typeof detent === 'number')
127
+ .map((detent) => Math.min(1, Math.max(0.1, detent)) * containerHeight),
128
+ [detents, containerHeight]
129
+ );
130
+
131
+ const handleChange = useCallback(
132
+ (index: number, _position: number, _type: SNAP_POINT_TYPE) => {
133
+ const previousIndex = currentIndexRef.current;
134
+ currentIndexRef.current = index;
135
+
136
+ // Handle drag end
137
+ if (isDragging.current && !isPresenting.current) {
138
+ isDragging.current = false;
139
+ onDragEnd?.({
140
+ nativeEvent: {
141
+ index,
142
+ position: animatedPosition.value,
143
+ detent: detents[index] ?? 0,
144
+ },
145
+ } as DragEndEvent);
146
+ }
147
+
148
+ if (!isPresenting.current && !isMinimized.current && previousIndex !== index && index >= 0) {
149
+ onDetentChange?.({
150
+ nativeEvent: {
151
+ index,
152
+ position: animatedPosition.value,
153
+ detent: detents[index] ?? 0,
154
+ },
155
+ } as DetentChangeEvent);
156
+ }
157
+
158
+ if (isPresenting.current) {
159
+ isPresenting.current = false;
160
+
161
+ onDidPresent?.({
162
+ nativeEvent: {
163
+ index,
164
+ position: animatedPosition.value,
165
+ detent: detents[index] ?? 0,
166
+ },
167
+ } as DidPresentEvent);
168
+
169
+ onDidFocus?.({ nativeEvent: null } as DidFocusEvent);
170
+ }
171
+
172
+ // Fire onDidBlur when sheet reaches minimized state (index -1 but still mounted)
173
+ if (isMinimized.current && index === -1) {
174
+ onDidBlur?.({ nativeEvent: null } as DidBlurEvent);
175
+ }
176
+
177
+ // Fire onDidFocus when sheet is restored from minimized state
178
+ if (isMinimized.current && index >= 0) {
179
+ isMinimized.current = false;
180
+ onDidFocus?.({ nativeEvent: null } as DidFocusEvent);
181
+ }
182
+ },
183
+ [detents, animatedPosition]
184
+ );
185
+
186
+ const handleDismiss = useCallback(() => {
187
+ onDidDismiss?.({ nativeEvent: null } as DidDismissEvent);
188
+
189
+ // Reset states since sheet is being dismissed
190
+ isMinimized.current = false;
191
+ isDismissing.current = false;
192
+ isDragging.current = false;
193
+ }, []);
194
+
195
+ const handleAnimate = useCallback(
196
+ (_fromIndex: number, toIndex: number) => {
197
+ // Detect drag begin (when not presenting or dismissing)
198
+ if (!isPresenting.current && !isDismissing.current && !isDragging.current && toIndex >= 0) {
199
+ isDragging.current = true;
200
+ onDragBegin?.({
201
+ nativeEvent: {
202
+ index: currentIndexRef.current,
203
+ position: animatedPosition.value,
204
+ detent: detents[currentIndexRef.current] ?? 0,
205
+ },
206
+ } as DragBeginEvent);
207
+ }
208
+
209
+ // Drag change during animation
210
+ if (isDragging.current && toIndex >= 0) {
211
+ onDragChange?.({
212
+ nativeEvent: {
213
+ index: toIndex,
214
+ position: animatedPosition.value,
215
+ detent: detents[toIndex] ?? 0,
216
+ },
217
+ } as DragChangeEvent);
218
+ }
219
+
220
+ if (isPresenting.current) {
221
+ onWillPresent?.({
222
+ nativeEvent: {
223
+ index: toIndex,
224
+ position: animatedPosition.value,
225
+ detent: detents[toIndex] ?? 0,
226
+ },
227
+ } as WillPresentEvent);
228
+
229
+ // Focus events fire together with present events
230
+ onWillFocus?.({ nativeEvent: null } as WillFocusEvent);
231
+ }
232
+
233
+ // Detect if sheet is being restored (will focus)
234
+ if (isMinimized.current && toIndex >= 0) {
235
+ onWillFocus?.({ nativeEvent: null } as WillFocusEvent);
236
+ }
237
+
238
+ if (toIndex === -1 && !isPresenting.current) {
239
+ // Will be handled as blur if the sheet doesn't actually dismiss
240
+ isMinimized.current = true;
241
+ onWillBlur?.({ nativeEvent: null } as WillBlurEvent);
242
+
243
+ if (isDismissing.current) {
244
+ onWillDismiss?.({ nativeEvent: null } as WillDismissEvent);
245
+ }
246
+ }
247
+ },
248
+ [detents, animatedPosition]
249
+ );
250
+
251
+ const backdropComponent = useCallback(
252
+ (backdropProps: BottomSheetBackdropProps) => {
253
+ if (!dimmed) {
254
+ return null;
255
+ }
256
+ return (
257
+ <BottomSheetBackdrop
258
+ {...backdropProps}
259
+ opacity={0.5}
260
+ appearsOnIndex={dimmedDetentIndex}
261
+ disappearsOnIndex={dimmedDetentIndex - 1}
262
+ pressBehavior={dismissible ? 'close' : 'none'}
263
+ />
264
+ );
265
+ },
266
+ [dimmed, dimmedDetentIndex, dismissible]
267
+ );
268
+
269
+ const handleComponent = useCallback(
270
+ (handleProps: BottomSheetHandleProps) => {
271
+ if (!grabber) {
272
+ return null;
273
+ }
274
+ return (
275
+ <BottomSheetHandle
276
+ {...handleProps}
277
+ style={[
278
+ styles.handle,
279
+ grabberOptions?.topMargin !== undefined && { paddingTop: grabberOptions.topMargin },
280
+ ]}
281
+ indicatorStyle={[
282
+ styles.handleIndicator,
283
+ grabberOptions?.width !== undefined && { width: grabberOptions.width },
284
+ grabberOptions?.height !== undefined && { height: grabberOptions.height },
285
+ grabberOptions?.cornerRadius !== undefined && {
286
+ borderRadius: grabberOptions.cornerRadius,
287
+ },
288
+ { backgroundColor: grabberOptions?.color ?? DEFAULT_GRABBER_COLOR },
289
+ ]}
290
+ />
291
+ );
292
+ },
293
+ [grabber, grabberOptions]
294
+ );
295
+
296
+ const footerComponent = useMemo(
297
+ () =>
298
+ footer
299
+ ? (footerProps: BottomSheetFooterProps) => (
300
+ <BottomSheetFooter {...footerProps}>{renderSlot(footer)}</BottomSheetFooter>
301
+ )
302
+ : undefined,
303
+ [footer]
304
+ );
305
+
306
+ // For scrollable, we render the child directly
307
+ const ContainerComponent = scrollable ? Fragment : BottomSheetView;
308
+
309
+ const sheetMethodsRef = useRef<TrueSheetRef>({
310
+ present: async (index = 0) => {
311
+ setSnapIndex(index);
312
+ isPresenting.current = true;
313
+ modalRef.current?.present();
314
+ },
315
+ dismiss: async () => {
316
+ isDismissing.current = true;
317
+ modalRef.current?.dismiss();
318
+ },
319
+ resize: async (index: number) => {
320
+ modalRef.current?.snapToIndex(index);
321
+ },
322
+ });
323
+
324
+ useImperativeHandle(ref, () => sheetMethodsRef.current);
325
+
326
+ // Register with context provider
327
+ useEffect(() => {
328
+ if (name) {
329
+ bottomSheetContext?.register(name, sheetMethodsRef);
330
+ }
331
+ return () => {
332
+ if (name) {
333
+ bottomSheetContext?.unregister(name);
334
+ }
335
+ };
336
+ }, [name]);
337
+
338
+ // Auto-present on mount if initialDetentIndex is set
339
+ useEffect(() => {
340
+ if (initialDetentIndexRef.current >= 0) {
341
+ sheetMethodsRef.current.present(initialDetentIndexRef.current);
342
+ }
343
+ }, []);
344
+
345
+ // Handle mount event after first render
346
+ useEffect(() => {
347
+ if (!isMounted) {
348
+ setIsMounted(true);
349
+ onMount?.({ nativeEvent: null } as MountEvent);
350
+ }
351
+ }, [isMounted, onMount]);
352
+
353
+ return (
354
+ <BottomSheetModal
355
+ ref={modalRef}
356
+ name={name}
357
+ style={[
358
+ styles.root,
359
+ { backgroundColor, borderTopLeftRadius: cornerRadius, borderTopRightRadius: cornerRadius },
360
+ ]}
361
+ index={snapIndex}
362
+ animateOnMount
363
+ enablePanDownToClose={dismissible}
364
+ enableContentPanningGesture={draggable}
365
+ enableHandlePanningGesture={draggable}
366
+ animatedPosition={animatedPosition}
367
+ animatedIndex={animatedIndex}
368
+ handleComponent={handleComponent}
369
+ onChange={handleChange}
370
+ onAnimate={handleAnimate}
371
+ enableDynamicSizing={hasAutoDetent}
372
+ maxDynamicContentSize={maxHeight}
373
+ snapPoints={snapPoints.length > 0 ? snapPoints : undefined}
374
+ onDismiss={handleDismiss}
375
+ stackBehavior="switch"
376
+ backdropComponent={backdropComponent}
377
+ footerComponent={footerComponent}
378
+ >
379
+ <ContainerComponent>
380
+ <View style={[styles.container, style]}>
381
+ {renderSlot(header)}
382
+ {children}
383
+ </View>
384
+ </ContainerComponent>
385
+ </BottomSheetModal>
386
+ );
387
+ });
388
+
389
+ const styles = StyleSheet.create({
390
+ root: {
391
+ overflow: 'hidden',
392
+ },
393
+ container: {},
394
+ handle: {
395
+ position: 'absolute',
396
+ top: 0,
397
+ left: 0,
398
+ right: 0,
399
+ zIndex: 1,
400
+ paddingVertical: 10,
401
+ pointerEvents: 'none',
402
+ },
403
+ handleIndicator: {
404
+ width: 36,
405
+ height: 5,
406
+ },
407
+ });
@@ -0,0 +1,29 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ import { TrueSheet } from './TrueSheet';
4
+ import type { TrueSheetContextMethods } from './TrueSheet.types';
5
+
6
+ export interface TrueSheetProviderProps {
7
+ children: ReactNode;
8
+ }
9
+
10
+ /**
11
+ * Provider for TrueSheet on native platforms.
12
+ * This is a pass-through component - no context is needed on native
13
+ * since TrueSheet uses static instance methods internally.
14
+ */
15
+ export function TrueSheetProvider({ children }: TrueSheetProviderProps) {
16
+ return children;
17
+ }
18
+
19
+ /**
20
+ * Hook to control TrueSheet instances by name.
21
+ * On native, this maps directly to TrueSheet static methods.
22
+ */
23
+ export function useTrueSheet(): TrueSheetContextMethods {
24
+ return {
25
+ present: TrueSheet.present,
26
+ dismiss: TrueSheet.dismiss,
27
+ resize: TrueSheet.resize,
28
+ };
29
+ }
@@ -0,0 +1,81 @@
1
+ import { createContext, useContext, useRef, type ReactNode, type RefObject } from 'react';
2
+ import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
3
+ import type { TrueSheetContextMethods, TrueSheetRef } from './TrueSheet.types';
4
+
5
+ interface BottomSheetContextValue extends TrueSheetContextMethods {
6
+ register: (name: string, methods: RefObject<TrueSheetRef>) => void;
7
+ unregister: (name: string) => void;
8
+ }
9
+
10
+ export const BottomSheetContext = createContext<BottomSheetContextValue | null>(null);
11
+
12
+ export interface TrueSheetProviderProps {
13
+ children: ReactNode;
14
+ }
15
+
16
+ /**
17
+ * Provider for TrueSheet on web.
18
+ * Required to wrap your app for sheet management via useTrueSheet hook.
19
+ */
20
+ export function TrueSheetProvider({ children }: TrueSheetProviderProps) {
21
+ const sheetsRef = useRef<Map<string, RefObject<TrueSheetRef>>>(new Map());
22
+
23
+ const register = (name: string, methods: RefObject<TrueSheetRef>) => {
24
+ sheetsRef.current.set(name, methods);
25
+ };
26
+
27
+ const unregister = (name: string) => {
28
+ sheetsRef.current.delete(name);
29
+ };
30
+
31
+ const present = async (name: string, index: number = 0) => {
32
+ const sheet = sheetsRef.current.get(name);
33
+ if (!sheet?.current) {
34
+ console.warn(`TrueSheet: Could not find sheet with name "${name}"`);
35
+ return;
36
+ }
37
+ return sheet.current.present(index);
38
+ };
39
+
40
+ const dismiss = async (name: string) => {
41
+ const sheet = sheetsRef.current.get(name);
42
+ if (!sheet?.current) {
43
+ console.warn(`TrueSheet: Could not find sheet with name "${name}"`);
44
+ return;
45
+ }
46
+ return sheet.current.dismiss();
47
+ };
48
+
49
+ const resize = async (name: string, index: number) => {
50
+ const sheet = sheetsRef.current.get(name);
51
+ if (!sheet?.current) {
52
+ console.warn(`TrueSheet: Could not find sheet with name "${name}"`);
53
+ return;
54
+ }
55
+ return sheet.current.resize(index);
56
+ };
57
+
58
+ return (
59
+ <BottomSheetContext.Provider value={{ register, unregister, present, dismiss, resize }}>
60
+ <BottomSheetModalProvider>{children}</BottomSheetModalProvider>
61
+ </BottomSheetContext.Provider>
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Hook to control TrueSheet instances by name.
67
+ * On web, this uses the TrueSheetContext from TrueSheetProvider.
68
+ */
69
+ export function useTrueSheet(): TrueSheetContextMethods {
70
+ const context = useContext(BottomSheetContext);
71
+
72
+ if (!context) {
73
+ throw new Error('useTrueSheet must be used within a TrueSheetProvider');
74
+ }
75
+
76
+ return {
77
+ present: context.present,
78
+ dismiss: context.dismiss,
79
+ resize: context.resize,
80
+ };
81
+ }
@@ -1,4 +1,4 @@
1
- import type { ColorValue, ViewProps } from 'react-native';
1
+ import type { ColorValue, ProcessedColorValue, ViewProps } from 'react-native';
2
2
  import type {
3
3
  DirectEventHandler,
4
4
  Double,
@@ -12,7 +12,7 @@ type GrabberOptionsType = Readonly<{
12
12
  height?: Double;
13
13
  topMargin?: Double;
14
14
  cornerRadius?: WithDefault<Double, -1>;
15
- color?: Int32;
15
+ color?: ProcessedColorValue | null;
16
16
  }>;
17
17
 
18
18
  type BlurOptionsType = Readonly<{
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './TrueSheet';
2
2
  export * from './TrueSheet.types';
3
+ export * from './TrueSheetProvider';