@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.
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +9 -3
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +1 -1
- package/ios/TrueSheetView.mm +7 -3
- package/ios/TrueSheetViewController.mm +15 -7
- package/lib/module/TrueSheet.js +2 -2
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/TrueSheet.web.js +333 -0
- package/lib/module/TrueSheet.web.js.map +1 -0
- package/lib/module/TrueSheetProvider.js +26 -0
- package/lib/module/TrueSheetProvider.js.map +1 -0
- package/lib/module/TrueSheetProvider.web.js +74 -0
- package/lib/module/TrueSheetProvider.web.js.map +1 -0
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +2 -2
- package/lib/module/index.js +2 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js +1 -1
- package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js.map +1 -1
- package/lib/module/navigation/screen/TrueSheetScreen.js +1 -1
- package/lib/module/navigation/screen/TrueSheetScreen.js.map +1 -1
- package/lib/module/reanimated/ReanimatedTrueSheet.js +2 -2
- package/lib/module/reanimated/ReanimatedTrueSheet.js.map +1 -1
- package/lib/module/reanimated/ReanimatedTrueSheet.web.js +81 -0
- package/lib/module/reanimated/ReanimatedTrueSheet.web.js.map +1 -0
- package/lib/module/reanimated/index.js +2 -2
- package/lib/module/reanimated/index.js.map +1 -1
- package/lib/module/reanimated/useReanimatedPositionChangeHandler.web.js +21 -0
- package/lib/module/reanimated/useReanimatedPositionChangeHandler.web.js.map +1 -0
- package/lib/typescript/src/TrueSheet.d.ts +2 -2
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +44 -0
- package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.web.d.ts +3 -0
- package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetProvider.d.ts +17 -0
- package/lib/typescript/src/TrueSheetProvider.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetProvider.web.d.ts +22 -0
- package/lib/typescript/src/TrueSheetProvider.web.d.ts.map +1 -0
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +2 -2
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/reanimated/ReanimatedTrueSheet.web.d.ts +41 -0
- package/lib/typescript/src/reanimated/ReanimatedTrueSheet.web.d.ts.map +1 -0
- package/lib/typescript/src/reanimated/useReanimatedPositionChangeHandler.web.d.ts +16 -0
- package/lib/typescript/src/reanimated/useReanimatedPositionChangeHandler.web.d.ts.map +1 -0
- package/package.json +11 -4
- package/src/TrueSheet.tsx +7 -3
- package/src/TrueSheet.types.ts +46 -0
- package/src/TrueSheet.web.tsx +407 -0
- package/src/TrueSheetProvider.tsx +29 -0
- package/src/TrueSheetProvider.web.tsx +81 -0
- package/src/fabric/TrueSheetViewNativeComponent.ts +2 -2
- package/src/index.ts +1 -0
- package/src/reanimated/ReanimatedTrueSheet.web.tsx +78 -0
- 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.
|
|
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
|
-
"@
|
|
119
|
+
"@gorhom/bottom-sheet": ">=5",
|
|
120
|
+
"@react-navigation/native": ">=7",
|
|
118
121
|
"react": "*",
|
|
119
122
|
"react-native": "*",
|
|
120
|
-
"react-native-reanimated": ">=4
|
|
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":
|
|
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,
|
|
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
|
|
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:
|
|
420
|
+
color: processColor(grabberOptions?.color),
|
|
417
421
|
}}
|
|
418
422
|
dimmed={dimmed}
|
|
419
423
|
dimmedDetentIndex={dimmedDetentIndex}
|
package/src/TrueSheet.types.ts
CHANGED
|
@@ -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?:
|
|
15
|
+
color?: ProcessedColorValue | null;
|
|
16
16
|
}>;
|
|
17
17
|
|
|
18
18
|
type BlurOptionsType = Readonly<{
|
package/src/index.ts
CHANGED