@swmansion/react-native-bottom-sheet 0.10.2 → 0.11.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/README.md +100 -25
- package/android/src/main/java/com/swmansion/reactnativebottomsheet/BottomSheetPackage.kt +1 -1
- package/android/src/main/java/com/swmansion/reactnativebottomsheet/BottomSheetSurfaceView.kt +10 -0
- package/android/src/main/java/com/swmansion/reactnativebottomsheet/BottomSheetSurfaceViewManager.kt +27 -0
- package/android/src/main/java/com/swmansion/reactnativebottomsheet/BottomSheetView.kt +48 -24
- package/common/cpp/react/renderer/components/ReactNativeBottomSheetSpec/ComponentDescriptors.h +4 -0
- package/common/cpp/react/renderer/components/ReactNativeBottomSheetSpec/ShadowNodes.h +12 -0
- package/ios/BottomSheetComponentView.mm +13 -2
- package/ios/BottomSheetContentView.h +2 -0
- package/ios/BottomSheetContentView.mm +10 -0
- package/ios/BottomSheetHostingView.swift +50 -24
- package/ios/BottomSheetSurfaceComponentView.h +13 -0
- package/ios/BottomSheetSurfaceComponentView.mm +21 -0
- package/lib/module/BottomSheet.js +10 -3
- package/lib/module/BottomSheet.js.map +1 -1
- package/lib/module/BottomSheetSurfaceNativeComponent.ts +9 -0
- package/lib/module/ModalBottomSheet.js.map +1 -1
- package/lib/typescript/src/BottomSheet.d.ts +14 -2
- package/lib/typescript/src/BottomSheet.d.ts.map +1 -1
- package/lib/typescript/src/BottomSheetSurfaceNativeComponent.d.ts +6 -0
- package/lib/typescript/src/BottomSheetSurfaceNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/ModalBottomSheet.d.ts.map +1 -1
- package/package.json +20 -18
- package/react-native.config.js +4 -1
- package/src/BottomSheet.tsx +24 -1
- package/src/BottomSheetSurfaceNativeComponent.ts +9 -0
- package/src/ModalBottomSheet.tsx +4 -2
package/README.md
CHANGED
|
@@ -18,6 +18,47 @@ React Native.
|
|
|
18
18
|
- Programmatic‍-‍only detents for snap points unreachable
|
|
19
19
|
by dragging.
|
|
20
20
|
|
|
21
|
+
## How it compares
|
|
22
|
+
|
|
23
|
+
React Native already has strong bottom‍-‍sheet options, but they make
|
|
24
|
+
different tradeoffs. React Native Bottom Sheet gives you composable React Native
|
|
25
|
+
primitives backed by native sheet mechanics: You compose the surface in React,
|
|
26
|
+
while the sheet host, gestures, snapping, and scroll negotiation run in
|
|
27
|
+
native code.
|
|
28
|
+
|
|
29
|
+
[`@gorhom/bottom-sheet`](https://gorhom.dev/react-native-bottom-sheet) is the
|
|
30
|
+
closest match in day‍-‍to‍-‍day functionality: configurable
|
|
31
|
+
detents, dynamic sizing, scrollable coordination, inline sheets, and modal
|
|
32
|
+
presentation. The main difference is the implementation model. React Native
|
|
33
|
+
Bottom Sheet moves the sheet host, gestures, snapping, and scroll negotiation
|
|
34
|
+
into native code, so heavy React rendering and busy JS work are less likely to
|
|
35
|
+
affect drag and snap performance. It also does not require Reanimated or React
|
|
36
|
+
Native Gesture Handler. Because scroll coordination is native, regular React
|
|
37
|
+
Native scrollables work inside the sheet without
|
|
38
|
+
bottom‍-‍sheet‍-‍specific list components or wrapper factories.
|
|
39
|
+
|
|
40
|
+
[Expo UI](https://docs.expo.dev/versions/latest/sdk/ui) sheets,
|
|
41
|
+
[Expo Router form sheets](https://docs.expo.dev/router/advanced/modals/#form-sheet),
|
|
42
|
+
and native modal‍-‍sheet libraries such as
|
|
43
|
+
[True Sheet](https://sheet.lodev09.com) lean into platform presentation APIs.
|
|
44
|
+
That is a good fit when you want a system‍-‍style presented sheet, but
|
|
45
|
+
it also means the platform and presentation system decide more of the behavior.
|
|
46
|
+
React Native Bottom Sheet is built as a lower‍-‍level sheet primitive
|
|
47
|
+
instead: The same native implementation powers both persistent inline sheets and
|
|
48
|
+
modal sheets, you provide the complete sheet surface in React, and detents can
|
|
49
|
+
include app‍-‍level behavior such as programmatic‍-‍only
|
|
50
|
+
snap points.
|
|
51
|
+
|
|
52
|
+
That difference also matters for layering. A platform‍-‍presented sheet
|
|
53
|
+
can disable dimming and allow background interaction, but it is still drawn as a
|
|
54
|
+
presented native sheet over the React Native view hierarchy. `BottomSheet` is
|
|
55
|
+
actually inline: It renders in your screen’s React Native hierarchy and can be
|
|
56
|
+
layered alongside nearby content. When you do need a modal, `ModalBottomSheet`
|
|
57
|
+
is rendered through `BottomSheetProvider`’s portal rather than through a
|
|
58
|
+
separate native window, so global UI such as toasts, menus, floating controls,
|
|
59
|
+
or debug overlays can be arranged above or below it by where you place them
|
|
60
|
+
relative to the provider.
|
|
61
|
+
|
|
21
62
|
## Getting started
|
|
22
63
|
|
|
23
64
|
1. Install React Native Bottom Sheet:
|
|
@@ -29,7 +70,7 @@ React Native.
|
|
|
29
70
|
2. Ensure the peer dependency is installed:
|
|
30
71
|
|
|
31
72
|
```sh
|
|
32
|
-
npm i react-native-safe-area-context
|
|
73
|
+
npm i react-native-safe-area-context
|
|
33
74
|
```
|
|
34
75
|
|
|
35
76
|
3. Wrap your app with `BottomSheetProvider`:
|
|
@@ -41,9 +82,9 @@ React Native.
|
|
|
41
82
|
## Usage
|
|
42
83
|
|
|
43
84
|
The library provides two components: `BottomSheet` (inline) and
|
|
44
|
-
`ModalBottomSheet` (modal). Both render their children as the sheet content
|
|
45
|
-
|
|
46
|
-
and `onIndexChange`. Use `onSettle` for
|
|
85
|
+
`ModalBottomSheet` (modal). Both render their children as the sheet content,
|
|
86
|
+
with a `surface` prop for the background behind it, and are controlled via
|
|
87
|
+
`detents`, `index`, and `onIndexChange`. Use `onSettle` for
|
|
47
88
|
post‍-‍snap observability.
|
|
48
89
|
|
|
49
90
|
### Inline
|
|
@@ -56,14 +97,14 @@ const insets = useSafeAreaInsets();
|
|
|
56
97
|
```
|
|
57
98
|
|
|
58
99
|
```tsx
|
|
59
|
-
<BottomSheet
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
>
|
|
100
|
+
<BottomSheet
|
|
101
|
+
index={index}
|
|
102
|
+
onIndexChange={setIndex}
|
|
103
|
+
surface={
|
|
104
|
+
<View style={[StyleSheet.absoluteFill, { backgroundColor: 'white' }]} />
|
|
105
|
+
}
|
|
106
|
+
>
|
|
107
|
+
<View style={{ padding: 16, paddingBottom: insets.bottom + 16 }}>
|
|
67
108
|
<Text>Sheet content</Text>
|
|
68
109
|
</View>
|
|
69
110
|
</BottomSheet>
|
|
@@ -80,14 +121,14 @@ const insets = useSafeAreaInsets();
|
|
|
80
121
|
```
|
|
81
122
|
|
|
82
123
|
```tsx
|
|
83
|
-
<ModalBottomSheet
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
>
|
|
124
|
+
<ModalBottomSheet
|
|
125
|
+
index={index}
|
|
126
|
+
onIndexChange={setIndex}
|
|
127
|
+
surface={
|
|
128
|
+
<View style={[StyleSheet.absoluteFill, { backgroundColor: 'white' }]} />
|
|
129
|
+
}
|
|
130
|
+
>
|
|
131
|
+
<View style={{ padding: 16, paddingBottom: insets.bottom + 16 }}>
|
|
91
132
|
<Text>Sheet content</Text>
|
|
92
133
|
</View>
|
|
93
134
|
</ModalBottomSheet>
|
|
@@ -102,12 +143,39 @@ its color:
|
|
|
102
143
|
<ModalBottomSheet
|
|
103
144
|
index={index}
|
|
104
145
|
onIndexChange={setIndex}
|
|
146
|
+
surface={/* ... */}
|
|
105
147
|
scrimColor="rgba(0, 0, 0, 0.3)"
|
|
106
148
|
>
|
|
107
149
|
{/* ... */}
|
|
108
150
|
</ModalBottomSheet>
|
|
109
151
|
```
|
|
110
152
|
|
|
153
|
+
### Surface
|
|
154
|
+
|
|
155
|
+
Provide the sheet’s background through the `surface` prop. The library renders
|
|
156
|
+
it behind your content and sizes it natively to cover the whole sheet,
|
|
157
|
+
independently of the content height.
|
|
158
|
+
|
|
159
|
+
Decoupling the surface this way keeps the sheet covered as the content height
|
|
160
|
+
changes. When content shrinks, the sheet animates to its new height without the
|
|
161
|
+
background briefly exposing blank space behind the content.
|
|
162
|
+
|
|
163
|
+
Give the surface a filling style such as `StyleSheet.absoluteFill`. It is
|
|
164
|
+
mounted in a full‍-‍size host, so a surface sized only by its own
|
|
165
|
+
content would collapse and not show.
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
<BottomSheet // Or `ModalBottomSheet`.
|
|
169
|
+
index={index}
|
|
170
|
+
onIndexChange={setIndex}
|
|
171
|
+
surface={
|
|
172
|
+
<View style={[StyleSheet.absoluteFill, { backgroundColor: 'white' }]} />
|
|
173
|
+
}
|
|
174
|
+
>
|
|
175
|
+
<Text>Sheet content</Text>
|
|
176
|
+
</BottomSheet>
|
|
177
|
+
```
|
|
178
|
+
|
|
111
179
|
### Scrollable negotiation
|
|
112
180
|
|
|
113
181
|
By default, the sheet coordinates vertical gestures with nested scrollables,
|
|
@@ -121,6 +189,7 @@ set `disableScrollableNegotiation`:
|
|
|
121
189
|
<BottomSheet
|
|
122
190
|
index={index}
|
|
123
191
|
onIndexChange={setIndex}
|
|
192
|
+
surface={/* ... */}
|
|
124
193
|
disableScrollableNegotiation
|
|
125
194
|
>
|
|
126
195
|
{/* ... */}
|
|
@@ -134,18 +203,20 @@ Detents are the points to which the sheet snaps. Each detent is either a number
|
|
|
134
203
|
the available screen height). The default detents are `[0, 'content']`.
|
|
135
204
|
|
|
136
205
|
Sheet children are laid out in a flex container. For a full‍-‍height
|
|
137
|
-
sheet, apply `flex: 1` to your
|
|
138
|
-
`
|
|
206
|
+
sheet, apply `flex: 1` to your content and use the `'content'` detent.
|
|
207
|
+
`surface` is sized by the library, so `flex: 1` only ever belongs on your
|
|
208
|
+
content, never on the surface:
|
|
139
209
|
|
|
140
210
|
```tsx
|
|
141
211
|
<BottomSheet
|
|
142
212
|
// `detents` defaults to `[0, 'content']`.
|
|
143
213
|
index={index}
|
|
144
214
|
onIndexChange={setIndex}
|
|
215
|
+
surface={
|
|
216
|
+
<View style={[StyleSheet.absoluteFill, { backgroundColor: 'white' }]} />
|
|
217
|
+
}
|
|
145
218
|
>
|
|
146
|
-
<View style={{ flex: 1
|
|
147
|
-
{/* Full-height sheet content. */}
|
|
148
|
-
</View>
|
|
219
|
+
<View style={{ flex: 1 }}>{/* Full-height sheet content. */}</View>
|
|
149
220
|
</BottomSheet>
|
|
150
221
|
```
|
|
151
222
|
|
|
@@ -168,6 +239,7 @@ const [index, setIndex] = useState(0);
|
|
|
168
239
|
detents={[0, 300, 'content']} // Collapsed, 300 px, content height.
|
|
169
240
|
index={index}
|
|
170
241
|
onIndexChange={setIndex} // Keep controlled state in sync.
|
|
242
|
+
surface={/* ... */}
|
|
171
243
|
onSettle={(nextIndex) => {
|
|
172
244
|
if (nextIndex === 0) console.log('Sheet collapsed.');
|
|
173
245
|
}}
|
|
@@ -190,6 +262,7 @@ drag snapping but can still be targeted via `index` updates.
|
|
|
190
262
|
detents={[0, programmatic(300), 'content']}
|
|
191
263
|
index={index}
|
|
192
264
|
onIndexChange={setIndex}
|
|
265
|
+
surface={/* ... */}
|
|
193
266
|
onSettle={(nextIndex) => {
|
|
194
267
|
console.log(`Settled at ${nextIndex}.`);
|
|
195
268
|
}}
|
|
@@ -207,6 +280,7 @@ pixels from the bottom of the screen to the top of the sheet).
|
|
|
207
280
|
<BottomSheet // Or `ModalBottomSheet`.
|
|
208
281
|
index={index}
|
|
209
282
|
onIndexChange={setIndex}
|
|
283
|
+
surface={/* ... */}
|
|
210
284
|
onPositionChange={(position) => {
|
|
211
285
|
console.log(position);
|
|
212
286
|
}}
|
|
@@ -226,6 +300,7 @@ const position = useSharedValue(0);
|
|
|
226
300
|
<BottomSheet
|
|
227
301
|
index={index}
|
|
228
302
|
onIndexChange={setIndex}
|
|
303
|
+
surface={/* ... */}
|
|
229
304
|
onPositionChange={(nextPosition) => {
|
|
230
305
|
position.value = nextPosition;
|
|
231
306
|
}}
|
|
@@ -10,5 +10,5 @@ class BottomSheetPackage : ReactPackage {
|
|
|
10
10
|
emptyList()
|
|
11
11
|
|
|
12
12
|
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
|
|
13
|
-
listOf(BottomSheetViewManager())
|
|
13
|
+
listOf(BottomSheetViewManager(), BottomSheetSurfaceViewManager())
|
|
14
14
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
package com.swmansion.reactnativebottomsheet
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import com.facebook.react.views.view.ReactViewGroup
|
|
5
|
+
|
|
6
|
+
// Visual surface that sits behind the sheet content. It carries no behavior of
|
|
7
|
+
// its own; the BottomSheetView identifies it by this type and owns its geometry,
|
|
8
|
+
// laying it out to cover the full sheet so a content shrink never exposes blank
|
|
9
|
+
// space. Its React children provide the appearance only.
|
|
10
|
+
class BottomSheetSurfaceView(context: Context) : ReactViewGroup(context)
|
package/android/src/main/java/com/swmansion/reactnativebottomsheet/BottomSheetSurfaceViewManager.kt
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package com.swmansion.reactnativebottomsheet
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
4
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
5
|
+
import com.facebook.react.uimanager.ViewGroupManager
|
|
6
|
+
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
7
|
+
import com.facebook.react.viewmanagers.BottomSheetSurfaceViewManagerDelegate
|
|
8
|
+
import com.facebook.react.viewmanagers.BottomSheetSurfaceViewManagerInterface
|
|
9
|
+
|
|
10
|
+
@ReactModule(name = BottomSheetSurfaceViewManager.NAME)
|
|
11
|
+
class BottomSheetSurfaceViewManager :
|
|
12
|
+
ViewGroupManager<BottomSheetSurfaceView>(),
|
|
13
|
+
BottomSheetSurfaceViewManagerInterface<BottomSheetSurfaceView> {
|
|
14
|
+
|
|
15
|
+
companion object {
|
|
16
|
+
const val NAME = "BottomSheetSurfaceView"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private val delegate = BottomSheetSurfaceViewManagerDelegate(this)
|
|
20
|
+
|
|
21
|
+
override fun getDelegate(): ViewManagerDelegate<BottomSheetSurfaceView> = delegate
|
|
22
|
+
|
|
23
|
+
override fun getName(): String = NAME
|
|
24
|
+
|
|
25
|
+
override fun createViewInstance(context: ThemedReactContext): BottomSheetSurfaceView =
|
|
26
|
+
BottomSheetSurfaceView(context)
|
|
27
|
+
}
|
|
@@ -88,6 +88,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
88
88
|
private var scrimPinnedFull = false
|
|
89
89
|
private var maxDetentHeight = Float.NaN
|
|
90
90
|
private var contentHeightMarker: View? = null
|
|
91
|
+
private var surfaceView: View? = null
|
|
91
92
|
|
|
92
93
|
private val contentHeightMarkerLayoutListener =
|
|
93
94
|
View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> refreshDetentsFromLayout() }
|
|
@@ -166,7 +167,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
166
167
|
if (animateIn && isInvalidContentDetentTarget(clampedIndex)) {
|
|
167
168
|
targetIndex = clampedIndex
|
|
168
169
|
pendingIndex = clampedIndex
|
|
169
|
-
val closedTy =
|
|
170
|
+
val closedTy = resolvedMaxDetentHeight(h)
|
|
170
171
|
sheetContainer.translationY = closedTy
|
|
171
172
|
emitPosition()
|
|
172
173
|
return
|
|
@@ -177,7 +178,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
177
178
|
targetIndex = clampedIndex
|
|
178
179
|
|
|
179
180
|
if (animateIn) {
|
|
180
|
-
val closedTy =
|
|
181
|
+
val closedTy = resolvedMaxDetentHeight(h)
|
|
181
182
|
sheetContainer.translationY = closedTy
|
|
182
183
|
emitPosition()
|
|
183
184
|
snapToIndex(targetIndex, 0f, emitIndexChange = false, emitSettle = true)
|
|
@@ -198,18 +199,25 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
198
199
|
super.dispatchDraw(canvas)
|
|
199
200
|
}
|
|
200
201
|
|
|
201
|
-
private fun layoutSheetChildren() {
|
|
202
|
+
private fun layoutSheetChildren(containerWidth: Int, containerHeight: Int) {
|
|
202
203
|
for (i in 0 until sheetContainer.childCount) {
|
|
203
204
|
val child = sheetContainer.getChildAt(i)
|
|
204
|
-
child
|
|
205
|
+
if (child === surfaceView) {
|
|
206
|
+
// The surface fills the full container so it always covers the visible
|
|
207
|
+
// sheet (the container is translated to the current sheet position),
|
|
208
|
+
// regardless of how short the content becomes.
|
|
209
|
+
child.layout(0, 0, containerWidth, containerHeight)
|
|
210
|
+
} else {
|
|
211
|
+
child.layout(0, 0, child.measuredWidth, child.measuredHeight)
|
|
212
|
+
}
|
|
205
213
|
}
|
|
206
214
|
}
|
|
207
215
|
|
|
208
216
|
private fun layoutSheetContainer(viewWidth: Int, viewHeight: Int) {
|
|
209
|
-
val maxHeight =
|
|
217
|
+
val maxHeight = resolvedMaxDetentHeight(viewHeight)
|
|
210
218
|
val containerTop = (viewHeight - maxHeight).toInt()
|
|
211
219
|
sheetContainer.layout(0, containerTop, viewWidth, containerTop + maxHeight.toInt())
|
|
212
|
-
layoutSheetChildren()
|
|
220
|
+
layoutSheetChildren(viewWidth, maxHeight.toInt())
|
|
213
221
|
}
|
|
214
222
|
|
|
215
223
|
// MARK: - Prop setters
|
|
@@ -252,6 +260,12 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
252
260
|
refreshDetentsFromLayout()
|
|
253
261
|
}
|
|
254
262
|
|
|
263
|
+
// Stable coordinate base for the sheet container. The container is sized to
|
|
264
|
+
// the full available height rather than the tallest detent, so it stays a
|
|
265
|
+
// fixed-size canvas: when content — and thus the `content` detent — shrinks,
|
|
266
|
+
// the container does not collapse underneath the sheet, leaving room to
|
|
267
|
+
// animate the sheet down to its new height. The surface fills this canvas, so
|
|
268
|
+
// the area below the shrunken content stays covered throughout.
|
|
255
269
|
private fun resolvedMaxDetentHeight(viewHeight: Int = height): Float {
|
|
256
270
|
val viewHeightPx = viewHeight.toFloat()
|
|
257
271
|
if (!maxDetentHeight.isFinite() || maxDetentHeight <= 0f) {
|
|
@@ -294,7 +308,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
294
308
|
return
|
|
295
309
|
}
|
|
296
310
|
|
|
297
|
-
val previousMaxHeight =
|
|
311
|
+
val previousMaxHeight = resolvedMaxDetentHeight()
|
|
298
312
|
// Whether the scrim is currently fully opaque, i.e. the sheet is settled at
|
|
299
313
|
// or above the first non-zero detent. If so, a detent resize must not dip
|
|
300
314
|
// the scrim while the sheet re-anchors to the new geometry.
|
|
@@ -308,7 +322,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
308
322
|
|
|
309
323
|
if (hasLaidOut && !isPanning) {
|
|
310
324
|
targetIndex = targetIndex.coerceIn(0, detentSpecs.size - 1)
|
|
311
|
-
val newMaxHeight =
|
|
325
|
+
val newMaxHeight = resolvedMaxDetentHeight()
|
|
312
326
|
val targetTy = translationY(targetIndex)
|
|
313
327
|
if (activeAnimation != null && isTargetingClosedDetent) {
|
|
314
328
|
suppressScrimForClosingTarget = true
|
|
@@ -337,20 +351,15 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
337
351
|
} else {
|
|
338
352
|
val currentVisibleHeight = previousMaxHeight - sheetContainer.translationY
|
|
339
353
|
val targetHeight = detentSpecs.getOrNull(targetIndex)?.height ?: 0f
|
|
340
|
-
|
|
341
|
-
val didShrink = targetHeight < currentVisibleHeight - 0.5f
|
|
342
|
-
if (isContentDetent && didShrink) {
|
|
343
|
-
// Content shrank: snap immediately. Animating here would expose
|
|
344
|
-
// blank space below the shrunken content.
|
|
345
|
-
sheetContainer.translationY = targetTy
|
|
346
|
-
emitPosition()
|
|
347
|
-
} else if (kotlin.math.abs(targetHeight - currentVisibleHeight) <= 0.5f) {
|
|
354
|
+
if (kotlin.math.abs(targetHeight - currentVisibleHeight) <= 0.5f) {
|
|
348
355
|
// No meaningful change.
|
|
349
356
|
sheetContainer.translationY = targetTy
|
|
350
357
|
emitPosition()
|
|
351
358
|
} else {
|
|
352
|
-
//
|
|
353
|
-
// visible height, then animate to the new target.
|
|
359
|
+
// The content detent changed (grew or shrank): re-anchor at the
|
|
360
|
+
// current visible height, then animate to the new target. The
|
|
361
|
+
// surface covers the full sheet, so a shrink no longer exposes
|
|
362
|
+
// blank space.
|
|
354
363
|
sheetContainer.translationY =
|
|
355
364
|
(newMaxHeight - currentVisibleHeight).coerceIn(0f, newMaxHeight)
|
|
356
365
|
scrimPinnedFull = scrimPinnedFull || wasScrimFull
|
|
@@ -386,6 +395,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
386
395
|
}
|
|
387
396
|
|
|
388
397
|
private fun refreshContentHeightMarker() {
|
|
398
|
+
surfaceView = findSurfaceView()
|
|
389
399
|
val marker = findContentHeightMarker()
|
|
390
400
|
if (marker === contentHeightMarker) return
|
|
391
401
|
contentHeightMarker?.removeOnLayoutChangeListener(contentHeightMarkerLayoutListener)
|
|
@@ -393,8 +403,21 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
393
403
|
contentHeightMarker?.addOnLayoutChangeListener(contentHeightMarkerLayoutListener)
|
|
394
404
|
}
|
|
395
405
|
|
|
406
|
+
private fun findSurfaceView(): View? {
|
|
407
|
+
for (i in 0 until sheetContainer.childCount) {
|
|
408
|
+
val child = sheetContainer.getChildAt(i)
|
|
409
|
+
if (child is BottomSheetSurfaceView) return child
|
|
410
|
+
}
|
|
411
|
+
return null
|
|
412
|
+
}
|
|
413
|
+
|
|
396
414
|
private fun findContentHeightMarker(): View? {
|
|
397
|
-
|
|
415
|
+
// The surface is a sibling of the content wrapper; skip it so the marker is
|
|
416
|
+
// always read from the content, never from the surface.
|
|
417
|
+
val contentView =
|
|
418
|
+
(0 until sheetContainer.childCount)
|
|
419
|
+
.map { sheetContainer.getChildAt(it) }
|
|
420
|
+
.firstOrNull { it !== surfaceView } as? ViewGroup ?: return null
|
|
398
421
|
if (contentView.childCount == 0) return null
|
|
399
422
|
return contentView.getChildAt(contentView.childCount - 1)
|
|
400
423
|
}
|
|
@@ -402,7 +425,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
402
425
|
// MARK: - Snap logic
|
|
403
426
|
|
|
404
427
|
private fun translationY(index: Int): Float {
|
|
405
|
-
val maxHeight =
|
|
428
|
+
val maxHeight = resolvedMaxDetentHeight()
|
|
406
429
|
val snapHeight = detentSpecs.getOrNull(index)?.height ?: 0f
|
|
407
430
|
return maxHeight - snapHeight
|
|
408
431
|
}
|
|
@@ -447,7 +470,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
447
470
|
}
|
|
448
471
|
|
|
449
472
|
private fun emitPosition() {
|
|
450
|
-
val maxHeight =
|
|
473
|
+
val maxHeight = resolvedMaxDetentHeight()
|
|
451
474
|
val ty = sheetContainer.translationY
|
|
452
475
|
val position = maxHeight - ty
|
|
453
476
|
updateScrim(position)
|
|
@@ -464,7 +487,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
464
487
|
private var lastShadowOffsetY = Float.NaN
|
|
465
488
|
|
|
466
489
|
private fun updateShadowState(translationY: Float) {
|
|
467
|
-
val maxDetentHeight =
|
|
490
|
+
val maxDetentHeight = resolvedMaxDetentHeight()
|
|
468
491
|
val containerTop = height.toFloat() - maxDetentHeight
|
|
469
492
|
val offsetY = ((containerTop + translationY) / density).toDouble()
|
|
470
493
|
if (offsetY.toFloat() == lastShadowOffsetY) return
|
|
@@ -703,7 +726,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
703
726
|
v
|
|
704
727
|
} ?: 0f
|
|
705
728
|
velocityTracker = null
|
|
706
|
-
val maxHeight =
|
|
729
|
+
val maxHeight = resolvedMaxDetentHeight()
|
|
707
730
|
val currentHeight = maxHeight - sheetContainer.translationY
|
|
708
731
|
val index = bestSnapIndex(currentHeight, velocity, panStartingIndex)
|
|
709
732
|
panStartingIndex = null
|
|
@@ -814,6 +837,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
814
837
|
velocityTracker = null
|
|
815
838
|
contentHeightMarker?.removeOnLayoutChangeListener(contentHeightMarkerLayoutListener)
|
|
816
839
|
contentHeightMarker = null
|
|
840
|
+
surfaceView = null
|
|
817
841
|
rawDetentSpecs = emptyList()
|
|
818
842
|
detentSpecs = emptyList()
|
|
819
843
|
targetIndex = 0
|
|
@@ -881,7 +905,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
881
905
|
}
|
|
882
906
|
|
|
883
907
|
private fun currentSheetHeight(): Float {
|
|
884
|
-
val maxHeight =
|
|
908
|
+
val maxHeight = resolvedMaxDetentHeight()
|
|
885
909
|
return maxHeight - sheetContainer.translationY
|
|
886
910
|
}
|
|
887
911
|
|
package/common/cpp/react/renderer/components/ReactNativeBottomSheetSpec/ComponentDescriptors.h
CHANGED
|
@@ -19,4 +19,8 @@ class BottomSheetViewComponentDescriptor final
|
|
|
19
19
|
}
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
// The surface needs no custom initial state, so this mirrors the codegen alias.
|
|
23
|
+
using BottomSheetSurfaceViewComponentDescriptor =
|
|
24
|
+
ConcreteComponentDescriptor<BottomSheetSurfaceViewShadowNode>;
|
|
25
|
+
|
|
22
26
|
} // namespace facebook::react
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#include <react/renderer/components/ReactNativeBottomSheetSpec/EventEmitters.h>
|
|
6
6
|
#include <react/renderer/components/ReactNativeBottomSheetSpec/Props.h>
|
|
7
7
|
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
|
|
8
|
+
#include <react/renderer/core/StateData.h>
|
|
8
9
|
|
|
9
10
|
namespace facebook::react {
|
|
10
11
|
|
|
@@ -22,4 +23,15 @@ class JSI_EXPORT BottomSheetViewShadowNode final
|
|
|
22
23
|
Point getContentOriginOffset(bool includeTransform) const override;
|
|
23
24
|
};
|
|
24
25
|
|
|
26
|
+
JSI_EXPORT extern const char BottomSheetSurfaceViewComponentName[];
|
|
27
|
+
|
|
28
|
+
// The surface needs no custom shadow-node behavior, so this mirrors the codegen
|
|
29
|
+
// alias exactly (state is the default StateData). It only lives here because the
|
|
30
|
+
// custom header search path shadows the generated ShadowNodes.h.
|
|
31
|
+
using BottomSheetSurfaceViewShadowNode = ConcreteViewShadowNode<
|
|
32
|
+
BottomSheetSurfaceViewComponentName,
|
|
33
|
+
BottomSheetSurfaceViewProps,
|
|
34
|
+
BottomSheetSurfaceViewEventEmitter,
|
|
35
|
+
StateData>;
|
|
36
|
+
|
|
25
37
|
} // namespace facebook::react
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#import "BottomSheetComponentView.h"
|
|
2
2
|
#import "BottomSheetContentView.h"
|
|
3
|
+
#import "BottomSheetSurfaceComponentView.h"
|
|
3
4
|
#import "../common/cpp/react/renderer/components/ReactNativeBottomSheetSpec/BottomSheetStateHelper.h"
|
|
4
5
|
#import "../common/cpp/react/renderer/components/ReactNativeBottomSheetSpec/ComponentDescriptors.h"
|
|
5
6
|
|
|
@@ -95,12 +96,22 @@ using namespace facebook::react;
|
|
|
95
96
|
|
|
96
97
|
- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
|
|
97
98
|
{
|
|
98
|
-
|
|
99
|
+
// Identify the visual surface by component type so the host can own its
|
|
100
|
+
// geometry. Everything else is treated as content.
|
|
101
|
+
if ([childComponentView isKindOfClass:BottomSheetSurfaceComponentView.class]) {
|
|
102
|
+
[_sheetView mountSurfaceComponentView:childComponentView atIndex:index];
|
|
103
|
+
} else {
|
|
104
|
+
[_sheetView mountChildComponentView:childComponentView atIndex:index];
|
|
105
|
+
}
|
|
99
106
|
}
|
|
100
107
|
|
|
101
108
|
- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
|
|
102
109
|
{
|
|
103
|
-
[
|
|
110
|
+
if ([childComponentView isKindOfClass:BottomSheetSurfaceComponentView.class]) {
|
|
111
|
+
[_sheetView unmountSurfaceComponentView:childComponentView];
|
|
112
|
+
} else {
|
|
113
|
+
[_sheetView unmountChildComponentView:childComponentView];
|
|
114
|
+
}
|
|
104
115
|
}
|
|
105
116
|
|
|
106
117
|
#pragma mark - BottomSheetContentViewDelegate
|
|
@@ -26,6 +26,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
26
26
|
- (CGFloat)currentContentOffsetY;
|
|
27
27
|
- (void)mountChildComponentView:(UIView *)childView atIndex:(NSInteger)index;
|
|
28
28
|
- (void)unmountChildComponentView:(UIView *)childView;
|
|
29
|
+
- (void)mountSurfaceComponentView:(UIView *)surfaceView atIndex:(NSInteger)index;
|
|
30
|
+
- (void)unmountSurfaceComponentView:(UIView *)surfaceView;
|
|
29
31
|
- (void)resetSheetState;
|
|
30
32
|
|
|
31
33
|
@end
|
|
@@ -95,6 +95,16 @@
|
|
|
95
95
|
[_impl unmountChildComponentView:childView];
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
- (void)mountSurfaceComponentView:(UIView *)surfaceView atIndex:(NSInteger)index
|
|
99
|
+
{
|
|
100
|
+
[_impl mountSurfaceComponentView:surfaceView atIndex:index];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
- (void)unmountSurfaceComponentView:(UIView *)surfaceView
|
|
104
|
+
{
|
|
105
|
+
[_impl unmountSurfaceComponentView:surfaceView];
|
|
106
|
+
}
|
|
107
|
+
|
|
98
108
|
- (void)resetSheetState
|
|
99
109
|
{
|
|
100
110
|
[_impl resetSheetState];
|
|
@@ -65,6 +65,7 @@ public final class BottomSheetHostingView: UIView {
|
|
|
65
65
|
private var panStartingIndex: Int?
|
|
66
66
|
private var isContentInteractionDisabled = false
|
|
67
67
|
private var contentHeightMarker: UIView?
|
|
68
|
+
private weak var surfaceView: UIView?
|
|
68
69
|
private static var markerObservationContext = 0
|
|
69
70
|
|
|
70
71
|
override public init(frame: CGRect) {
|
|
@@ -127,10 +128,16 @@ public final class BottomSheetHostingView: UIView {
|
|
|
127
128
|
|
|
128
129
|
scrimView.frame = bounds
|
|
129
130
|
refreshDetentsFromLayout()
|
|
130
|
-
let maxHeight =
|
|
131
|
+
let maxHeight = sheetContainerHeight
|
|
131
132
|
sheetContainer.bounds = CGRect(x: 0, y: 0, width: bounds.width, height: maxHeight)
|
|
132
133
|
sheetContainer.center = CGPoint(x: bounds.width / 2, y: bounds.height - maxHeight / 2)
|
|
133
134
|
|
|
135
|
+
// The surface fills the full container so it always covers the visible sheet
|
|
136
|
+
// (the container is translated to the current sheet position), regardless of
|
|
137
|
+
// how short the content becomes. Sized from the top via frame — never via
|
|
138
|
+
// anchorPoint.
|
|
139
|
+
surfaceView?.frame = sheetContainer.bounds
|
|
140
|
+
|
|
134
141
|
if !hasLaidOut && !detentSpecs.isEmpty {
|
|
135
142
|
let indexToApply = pendingIndex ?? targetIndex
|
|
136
143
|
let clampedIndex = max(0, min(detentSpecs.count - 1, indexToApply))
|
|
@@ -138,7 +145,7 @@ public final class BottomSheetHostingView: UIView {
|
|
|
138
145
|
if animateIn, isInvalidContentDetentTarget(clampedIndex) {
|
|
139
146
|
targetIndex = clampedIndex
|
|
140
147
|
pendingIndex = clampedIndex
|
|
141
|
-
let closedTy =
|
|
148
|
+
let closedTy = sheetContainerHeight
|
|
142
149
|
sheetContainer.transform = CGAffineTransform(translationX: 0, y: closedTy)
|
|
143
150
|
emitPosition()
|
|
144
151
|
return
|
|
@@ -149,7 +156,7 @@ public final class BottomSheetHostingView: UIView {
|
|
|
149
156
|
targetIndex = clampedIndex
|
|
150
157
|
|
|
151
158
|
if animateIn {
|
|
152
|
-
let closedTy =
|
|
159
|
+
let closedTy = sheetContainerHeight
|
|
153
160
|
sheetContainer.transform = CGAffineTransform(translationX: 0, y: closedTy)
|
|
154
161
|
emitPosition()
|
|
155
162
|
snapToIndex(targetIndex, velocity: 0, emitIndexChange: false, emitSettle: true)
|
|
@@ -231,6 +238,22 @@ public final class BottomSheetHostingView: UIView {
|
|
|
231
238
|
setNeedsLayout()
|
|
232
239
|
}
|
|
233
240
|
|
|
241
|
+
public func mountSurfaceComponentView(_ childView: UIView, atIndex index: Int) {
|
|
242
|
+
surfaceView = childView
|
|
243
|
+
sheetContainer.insertSubview(childView, at: index)
|
|
244
|
+
refreshContentHeightMarker()
|
|
245
|
+
setNeedsLayout()
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
public func unmountSurfaceComponentView(_ childView: UIView) {
|
|
249
|
+
if surfaceView === childView {
|
|
250
|
+
surfaceView = nil
|
|
251
|
+
}
|
|
252
|
+
childView.removeFromSuperview()
|
|
253
|
+
refreshContentHeightMarker()
|
|
254
|
+
setNeedsLayout()
|
|
255
|
+
}
|
|
256
|
+
|
|
234
257
|
public func resetSheetState() {
|
|
235
258
|
activeAnimator?.stopAnimation(true)
|
|
236
259
|
activeAnimator = nil
|
|
@@ -244,6 +267,7 @@ public final class BottomSheetHostingView: UIView {
|
|
|
244
267
|
panStartingIndex = nil
|
|
245
268
|
setContentInteractionEnabled(true)
|
|
246
269
|
stopObservingContentHeightMarker()
|
|
270
|
+
surfaceView = nil
|
|
247
271
|
sheetContainer.transform = .identity
|
|
248
272
|
scrimView.alpha = 0
|
|
249
273
|
scrimView.isHidden = true
|
|
@@ -260,7 +284,7 @@ public final class BottomSheetHostingView: UIView {
|
|
|
260
284
|
}
|
|
261
285
|
|
|
262
286
|
private func translationY(for index: Int) -> CGFloat {
|
|
263
|
-
let maxHeight =
|
|
287
|
+
let maxHeight = sheetContainerHeight
|
|
264
288
|
let snapHeight = detent(at: index).height
|
|
265
289
|
return maxHeight - snapHeight
|
|
266
290
|
}
|
|
@@ -297,13 +321,13 @@ public final class BottomSheetHostingView: UIView {
|
|
|
297
321
|
}
|
|
298
322
|
|
|
299
323
|
private var currentSheetHeight: CGFloat {
|
|
300
|
-
let maxHeight =
|
|
324
|
+
let maxHeight = sheetContainerHeight
|
|
301
325
|
let ty = currentTranslationY
|
|
302
326
|
return maxHeight - ty
|
|
303
327
|
}
|
|
304
328
|
|
|
305
329
|
public var currentContentOffsetY: CGFloat {
|
|
306
|
-
let maxHeight =
|
|
330
|
+
let maxHeight = sheetContainerHeight
|
|
307
331
|
let containerTop = bounds.height - maxHeight
|
|
308
332
|
let ty = currentTranslationY
|
|
309
333
|
return containerTop + ty
|
|
@@ -314,7 +338,7 @@ public final class BottomSheetHostingView: UIView {
|
|
|
314
338
|
}
|
|
315
339
|
|
|
316
340
|
private func emitPosition() {
|
|
317
|
-
let maxHeight =
|
|
341
|
+
let maxHeight = sheetContainerHeight
|
|
318
342
|
let ty = currentTranslationY
|
|
319
343
|
let position = maxHeight - ty
|
|
320
344
|
updateScrim(forPosition: position)
|
|
@@ -414,7 +438,7 @@ public final class BottomSheetHostingView: UIView {
|
|
|
414
438
|
}
|
|
415
439
|
|
|
416
440
|
@objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
|
|
417
|
-
let maxHeight =
|
|
441
|
+
let maxHeight = sheetContainerHeight
|
|
418
442
|
|
|
419
443
|
switch gesture.state {
|
|
420
444
|
case .began:
|
|
@@ -576,8 +600,14 @@ public final class BottomSheetHostingView: UIView {
|
|
|
576
600
|
return min(max(0, maxDetentHeight), bounds.height)
|
|
577
601
|
}
|
|
578
602
|
|
|
579
|
-
|
|
580
|
-
|
|
603
|
+
/// Stable coordinate base for the sheet container. The container is sized to
|
|
604
|
+
/// the full available height rather than the tallest detent, so it stays a
|
|
605
|
+
/// fixed-size canvas: when content — and thus the `content` detent — shrinks,
|
|
606
|
+
/// the container does not collapse underneath the sheet, leaving room to
|
|
607
|
+
/// animate the sheet down to its new height. The surface fills this canvas, so
|
|
608
|
+
/// the area below the shrunken content stays covered throughout.
|
|
609
|
+
private var sheetContainerHeight: CGFloat {
|
|
610
|
+
resolvedMaxDetentHeight
|
|
581
611
|
}
|
|
582
612
|
|
|
583
613
|
private func resolveDetentSpecs() -> [DetentSpec]? {
|
|
@@ -627,7 +657,7 @@ public final class BottomSheetHostingView: UIView {
|
|
|
627
657
|
return
|
|
628
658
|
}
|
|
629
659
|
|
|
630
|
-
let previousMaxHeight =
|
|
660
|
+
let previousMaxHeight = sheetContainerHeight
|
|
631
661
|
// Whether the scrim is currently fully opaque, i.e. the sheet is settled at
|
|
632
662
|
// or above the first non-zero detent. If so, a detent resize must not dip
|
|
633
663
|
// the scrim while the sheet re-anchors to the new geometry.
|
|
@@ -642,7 +672,7 @@ public final class BottomSheetHostingView: UIView {
|
|
|
642
672
|
|
|
643
673
|
if hasLaidOut, !isPanning {
|
|
644
674
|
targetIndex = max(0, min(detentSpecs.count - 1, targetIndex))
|
|
645
|
-
let newMaxHeight =
|
|
675
|
+
let newMaxHeight = sheetContainerHeight
|
|
646
676
|
let targetTy = translationY(for: targetIndex)
|
|
647
677
|
|
|
648
678
|
if let animator = activeAnimator {
|
|
@@ -669,21 +699,14 @@ public final class BottomSheetHostingView: UIView {
|
|
|
669
699
|
} else {
|
|
670
700
|
let currentVisibleHeight = previousMaxHeight - currentTranslationY
|
|
671
701
|
let targetHeight = detent(at: targetIndex).height
|
|
672
|
-
|
|
673
|
-
&& rawDetentSpecs[targetIndex].kind == .content
|
|
674
|
-
let didShrink = targetHeight < currentVisibleHeight - 0.5
|
|
675
|
-
if isContentDetent, didShrink {
|
|
676
|
-
// Content shrank: snap immediately. Animating here would expose blank
|
|
677
|
-
// space below the shrunken content.
|
|
678
|
-
sheetContainer.transform = CGAffineTransform(translationX: 0, y: targetTy)
|
|
679
|
-
emitPosition()
|
|
680
|
-
} else if abs(targetHeight - currentVisibleHeight) <= 0.5 {
|
|
702
|
+
if abs(targetHeight - currentVisibleHeight) <= 0.5 {
|
|
681
703
|
// No meaningful change.
|
|
682
704
|
sheetContainer.transform = CGAffineTransform(translationX: 0, y: targetTy)
|
|
683
705
|
emitPosition()
|
|
684
706
|
} else {
|
|
685
|
-
//
|
|
686
|
-
// visible height, then animate to the new target.
|
|
707
|
+
// The content detent changed (grew or shrank): re-anchor at the
|
|
708
|
+
// current visible height, then animate to the new target. The surface
|
|
709
|
+
// covers the full sheet, so a shrink no longer exposes blank space.
|
|
687
710
|
let startTy = min(max(newMaxHeight - currentVisibleHeight, 0), newMaxHeight)
|
|
688
711
|
sheetContainer.transform = CGAffineTransform(translationX: 0, y: startTy)
|
|
689
712
|
scrimPinnedFull = scrimPinnedFull || wasScrimFull
|
|
@@ -748,7 +771,10 @@ public final class BottomSheetHostingView: UIView {
|
|
|
748
771
|
}
|
|
749
772
|
|
|
750
773
|
private func findContentHeightMarker() -> UIView? {
|
|
751
|
-
|
|
774
|
+
// The surface is a sibling of the content wrapper; skip it so the marker is
|
|
775
|
+
// always read from the content, never from the surface.
|
|
776
|
+
guard let contentView = sheetContainer.subviews.first(where: { $0 !== surfaceView })
|
|
777
|
+
else { return nil }
|
|
752
778
|
return contentView.subviews.last
|
|
753
779
|
}
|
|
754
780
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#import <React/RCTViewComponentView.h>
|
|
2
|
+
#import <UIKit/UIKit.h>
|
|
3
|
+
|
|
4
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
5
|
+
|
|
6
|
+
// Visual surface that sits behind the sheet content. It carries no behavior of
|
|
7
|
+
// its own; the bottom sheet host identifies it by this type and owns its
|
|
8
|
+
// geometry. Its React children provide the appearance only.
|
|
9
|
+
@interface BottomSheetSurfaceComponentView : RCTViewComponentView
|
|
10
|
+
|
|
11
|
+
@end
|
|
12
|
+
|
|
13
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#import "BottomSheetSurfaceComponentView.h"
|
|
2
|
+
|
|
3
|
+
#import "../common/cpp/react/renderer/components/ReactNativeBottomSheetSpec/ComponentDescriptors.h"
|
|
4
|
+
|
|
5
|
+
#import <React/RCTFabricComponentsPlugins.h>
|
|
6
|
+
|
|
7
|
+
using namespace facebook::react;
|
|
8
|
+
|
|
9
|
+
@implementation BottomSheetSurfaceComponentView
|
|
10
|
+
|
|
11
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
12
|
+
{
|
|
13
|
+
return concreteComponentDescriptorProvider<BottomSheetSurfaceViewComponentDescriptor>();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@end
|
|
17
|
+
|
|
18
|
+
Class<RCTComponentViewProtocol> BottomSheetSurfaceViewCls(void)
|
|
19
|
+
{
|
|
20
|
+
return BottomSheetSurfaceComponentView.class;
|
|
21
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { StyleSheet, View, useWindowDimensions } from 'react-native';
|
|
4
4
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
5
|
import BottomSheetNativeComponent from './BottomSheetNativeComponent';
|
|
6
|
+
import BottomSheetSurfaceNativeComponent from './BottomSheetSurfaceNativeComponent';
|
|
6
7
|
import { Portal } from "./BottomSheetProvider.js";
|
|
7
8
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
9
|
export { programmatic } from "./bottomSheetUtils.js";
|
|
@@ -14,6 +15,7 @@ export { programmatic } from "./bottomSheetUtils.js";
|
|
|
14
15
|
/** Native bottom sheet that renders inline within the current screen layout. */
|
|
15
16
|
export const BottomSheet = ({
|
|
16
17
|
children,
|
|
18
|
+
surface,
|
|
17
19
|
style,
|
|
18
20
|
detents = [0, 'content'],
|
|
19
21
|
index,
|
|
@@ -65,7 +67,7 @@ export const BottomSheet = ({
|
|
|
65
67
|
children: /*#__PURE__*/_jsx(View, {
|
|
66
68
|
pointerEvents: "box-none",
|
|
67
69
|
style: StyleSheet.absoluteFill,
|
|
68
|
-
children: /*#__PURE__*/
|
|
70
|
+
children: /*#__PURE__*/_jsxs(BottomSheetNativeComponent, {
|
|
69
71
|
pointerEvents: "box-none",
|
|
70
72
|
style: [{
|
|
71
73
|
position: 'absolute',
|
|
@@ -87,7 +89,12 @@ export const BottomSheet = ({
|
|
|
87
89
|
onIndexChange: handleIndexChange,
|
|
88
90
|
onSettle: handleSettle,
|
|
89
91
|
onPositionChange: handlePositionChange,
|
|
90
|
-
children: /*#__PURE__*/
|
|
92
|
+
children: [surface != null && /*#__PURE__*/_jsx(BottomSheetSurfaceNativeComponent, {
|
|
93
|
+
collapsable: false,
|
|
94
|
+
pointerEvents: "box-none",
|
|
95
|
+
style: StyleSheet.absoluteFill,
|
|
96
|
+
children: surface
|
|
97
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
91
98
|
collapsable: false,
|
|
92
99
|
style: {
|
|
93
100
|
flex: 1,
|
|
@@ -97,7 +104,7 @@ export const BottomSheet = ({
|
|
|
97
104
|
collapsable: false,
|
|
98
105
|
pointerEvents: "none"
|
|
99
106
|
})]
|
|
100
|
-
})
|
|
107
|
+
})]
|
|
101
108
|
})
|
|
102
109
|
})
|
|
103
110
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["StyleSheet","View","useWindowDimensions","useSafeAreaInsets","BottomSheetNativeComponent","Portal","jsx","_jsx","jsxs","_jsxs","programmatic","BottomSheet","children","style","detents","index","animateIn","onIndexChange","onSettle","onPositionChange","modal","disableScrollableNegotiation","scrimColor","height","windowHeight","insets","maxHeight","top","nativeDetents","map","detent","isDetentProgrammatic","value","resolveDetentValue","kind","Math","max","min","clampedIndex","length","selectedDetentValue","isCollapsed","handleIndexChange","event","nativeEvent","handleSettle","handlePositionChange","position","sheet","absoluteFill","pointerEvents","left","right","bottom","maxDetentHeight","collapsable","flex"],"sourceRoot":"../../src","sources":["BottomSheet.tsx"],"mappings":";;AAEA,SAASA,UAAU,EAAEC,IAAI,EAAEC,mBAAmB,QAAQ,cAAc;AACpE,SAASC,iBAAiB,QAAQ,gCAAgC;AAElE,OAAOC,0BAA0B,MAAM,8BAA8B;AACrE,SAASC,MAAM,QAAQ,0BAAuB;AAAC,SAAAC,GAAA,IAAAC,IAAA,EAAAC,IAAA,IAAAC,KAAA;AAG/C,SAASC,YAAY,QAAQ,uBAAoB;;AAEjD;AACA;AACA;;
|
|
1
|
+
{"version":3,"names":["StyleSheet","View","useWindowDimensions","useSafeAreaInsets","BottomSheetNativeComponent","BottomSheetSurfaceNativeComponent","Portal","jsx","_jsx","jsxs","_jsxs","programmatic","BottomSheet","children","surface","style","detents","index","animateIn","onIndexChange","onSettle","onPositionChange","modal","disableScrollableNegotiation","scrimColor","height","windowHeight","insets","maxHeight","top","nativeDetents","map","detent","isDetentProgrammatic","value","resolveDetentValue","kind","Math","max","min","clampedIndex","length","selectedDetentValue","isCollapsed","handleIndexChange","event","nativeEvent","handleSettle","handlePositionChange","position","sheet","absoluteFill","pointerEvents","left","right","bottom","maxDetentHeight","collapsable","flex"],"sourceRoot":"../../src","sources":["BottomSheet.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { codegenNativeComponent, type ViewProps } from 'react-native';
|
|
2
|
+
|
|
3
|
+
// The visual surface that sits behind the sheet content. It carries no props of
|
|
4
|
+
// its own: the library identifies it by component type and owns its geometry,
|
|
5
|
+
// sizing it to cover the full sheet so a content shrink never exposes blank
|
|
6
|
+
// space. Its React children provide the visual appearance only.
|
|
7
|
+
export interface NativeProps extends ViewProps {}
|
|
8
|
+
|
|
9
|
+
export default codegenNativeComponent<NativeProps>('BottomSheetSurfaceView');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["BottomSheet","jsx","_jsx","ModalBottomSheet","props","modal"],"sourceRoot":"../../src","sources":["ModalBottomSheet.tsx"],"mappings":";;AAAA,SAASA,WAAW,QAA+B,kBAAe;;AAElE;AAAA,SAAAC,GAAA,IAAAC,IAAA;
|
|
1
|
+
{"version":3,"names":["BottomSheet","jsx","_jsx","ModalBottomSheet","props","modal"],"sourceRoot":"../../src","sources":["ModalBottomSheet.tsx"],"mappings":";;AAAA,SAASA,WAAW,QAA+B,kBAAe;;AAElE;AAAA,SAAAC,GAAA,IAAAC,IAAA;AAMA;AACA,OAAO,MAAMC,gBAAgB,GAAIC,KAA4B,iBAC3DF,IAAA,CAACF,WAAW;EAAA,GAAKI,KAAK;EAAEC,KAAK;AAAA,CAAE,CAChC","ignoreList":[]}
|
|
@@ -7,8 +7,20 @@ export { programmatic } from './bottomSheetUtils';
|
|
|
7
7
|
* Props for the inline bottom-sheet component.
|
|
8
8
|
*/
|
|
9
9
|
export interface BottomSheetProps {
|
|
10
|
-
/** Sheet contents, including any
|
|
10
|
+
/** Sheet contents, including any scrollable content. */
|
|
11
11
|
children: ReactNode;
|
|
12
|
+
/**
|
|
13
|
+
* Optional visual surface (background) rendered behind the content. The
|
|
14
|
+
* library sizes and positions the surface natively to cover the full sheet,
|
|
15
|
+
* independently of the content, so a shrinking content height never exposes
|
|
16
|
+
* blank space. Put a background View here instead of inside `children` when
|
|
17
|
+
* you want that shrink-safe behavior. When omitted, behavior is unchanged.
|
|
18
|
+
*
|
|
19
|
+
* Give the surface element a filling style such as `StyleSheet.absoluteFill`:
|
|
20
|
+
* it is mounted in a full-size host, so a surface sized only by its own
|
|
21
|
+
* content would collapse and not show.
|
|
22
|
+
*/
|
|
23
|
+
surface?: ReactNode;
|
|
12
24
|
/** Additional style applied to the native sheet host view. */
|
|
13
25
|
style?: StyleProp<ViewStyle>;
|
|
14
26
|
/** Snap points for the sheet. Defaults to `[0, 'content']`. */
|
|
@@ -35,5 +47,5 @@ export interface BottomSheetProps {
|
|
|
35
47
|
scrimColor?: string;
|
|
36
48
|
}
|
|
37
49
|
/** Native bottom sheet that renders inline within the current screen layout. */
|
|
38
|
-
export declare const BottomSheet: ({ children, style, detents, index, animateIn, onIndexChange, onSettle, onPositionChange, modal, disableScrollableNegotiation, scrimColor, }: BottomSheetProps) => import("react/jsx-runtime").JSX.Element;
|
|
50
|
+
export declare const BottomSheet: ({ children, surface, style, detents, index, animateIn, onIndexChange, onSettle, onPositionChange, modal, disableScrollableNegotiation, scrimColor, }: BottomSheetProps) => import("react/jsx-runtime").JSX.Element;
|
|
39
51
|
//# sourceMappingURL=BottomSheet.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BottomSheet.d.ts","sourceRoot":"","sources":["../../../src/BottomSheet.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"BottomSheet.d.ts","sourceRoot":"","sources":["../../../src/BottomSheet.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAOzD,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,wDAAwD;IACxD,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gEAAgE;IAChE,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,uEAAuE;IACvE,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,gDAAgD;IAChD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,gFAAgF;AAChF,eAAO,MAAM,WAAW,GAAI,sJAazB,gBAAgB,4CAiGlB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type ViewProps } from 'react-native';
|
|
2
|
+
export interface NativeProps extends ViewProps {
|
|
3
|
+
}
|
|
4
|
+
declare const _default: import("react-native/types_generated/Libraries/Utilities/codegenNativeComponent").NativeComponentType<NativeProps>;
|
|
5
|
+
export default _default;
|
|
6
|
+
//# sourceMappingURL=BottomSheetSurfaceNativeComponent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BottomSheetSurfaceNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/BottomSheetSurfaceNativeComponent.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAMtE,MAAM,WAAW,WAAY,SAAQ,SAAS;CAAG;;AAEjD,wBAA6E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ModalBottomSheet.d.ts","sourceRoot":"","sources":["../../../src/ModalBottomSheet.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEnE,qFAAqF;AACrF,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"ModalBottomSheet.d.ts","sourceRoot":"","sources":["../../../src/ModalBottomSheet.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEnE,qFAAqF;AACrF,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CACjD,gBAAgB,EAChB,OAAO,CACR;CAAG;AAEJ,gEAAgE;AAChE,eAAO,MAAM,gBAAgB,GAAI,OAAO,qBAAqB,4CAE5D,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swmansion/react-native-bottom-sheet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Provides bottom-sheet components for React Native.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"!**/.*"
|
|
34
34
|
],
|
|
35
35
|
"scripts": {
|
|
36
|
-
"example": "
|
|
36
|
+
"example": "bun run --filter @swmansion/react-native-bottom-sheet-example",
|
|
37
37
|
"clean": "del-cli lib",
|
|
38
38
|
"prepare": "lefthook install && bob build",
|
|
39
39
|
"typecheck": "tsc",
|
|
@@ -58,23 +58,23 @@
|
|
|
58
58
|
"registry": "https://registry.npmjs.org"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@eslint/compat": "1.
|
|
62
|
-
"@eslint/eslintrc": "3.3.
|
|
63
|
-
"@eslint/js": "9.39.
|
|
64
|
-
"@react-native/babel-preset": "0.
|
|
65
|
-
"@react-native/eslint-config": "0.
|
|
66
|
-
"@types/react": "19.2.
|
|
61
|
+
"@eslint/compat": "2.1.0",
|
|
62
|
+
"@eslint/eslintrc": "3.3.5",
|
|
63
|
+
"@eslint/js": "9.39.4",
|
|
64
|
+
"@react-native/babel-preset": "0.85.3",
|
|
65
|
+
"@react-native/eslint-config": "0.85.3",
|
|
66
|
+
"@types/react": "19.2.15",
|
|
67
67
|
"babel-plugin-react-compiler": "1.0.0",
|
|
68
68
|
"del-cli": "6.0.0",
|
|
69
|
-
"eslint": "9.39.
|
|
69
|
+
"eslint": "9.39.4",
|
|
70
70
|
"eslint-config-prettier": "10.1.8",
|
|
71
|
-
"eslint-plugin-prettier": "5.5.
|
|
72
|
-
"lefthook": "2.1.
|
|
73
|
-
"prettier": "
|
|
74
|
-
"react": "19.
|
|
75
|
-
"react-native": "0.
|
|
76
|
-
"react-native-builder-bob": "0.
|
|
77
|
-
"react-native-safe-area-context": "5.
|
|
71
|
+
"eslint-plugin-prettier": "5.5.6",
|
|
72
|
+
"lefthook": "2.1.9",
|
|
73
|
+
"prettier": "3.8.3",
|
|
74
|
+
"react": "19.2.3",
|
|
75
|
+
"react-native": "0.85.3",
|
|
76
|
+
"react-native-builder-bob": "0.41.0",
|
|
77
|
+
"react-native-safe-area-context": "5.7.0",
|
|
78
78
|
"typescript": "5.9.3"
|
|
79
79
|
},
|
|
80
80
|
"peerDependencies": {
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"workspaces": [
|
|
86
86
|
"example"
|
|
87
87
|
],
|
|
88
|
-
"packageManager": "
|
|
88
|
+
"packageManager": "bun@1.3.14",
|
|
89
89
|
"react-native-builder-bob": {
|
|
90
90
|
"source": "src",
|
|
91
91
|
"output": "lib",
|
|
@@ -110,7 +110,8 @@
|
|
|
110
110
|
"jsSrcsDir": "src",
|
|
111
111
|
"ios": {
|
|
112
112
|
"componentProvider": {
|
|
113
|
-
"BottomSheetView": "BottomSheetComponentView"
|
|
113
|
+
"BottomSheetView": "BottomSheetComponentView",
|
|
114
|
+
"BottomSheetSurfaceView": "BottomSheetSurfaceComponentView"
|
|
114
115
|
}
|
|
115
116
|
},
|
|
116
117
|
"android": {
|
|
@@ -118,6 +119,7 @@
|
|
|
118
119
|
}
|
|
119
120
|
},
|
|
120
121
|
"prettier": {
|
|
122
|
+
"proseWrap": "always",
|
|
121
123
|
"quoteProps": "consistent",
|
|
122
124
|
"singleQuote": true,
|
|
123
125
|
"tabWidth": 2,
|
package/react-native.config.js
CHANGED
|
@@ -3,7 +3,10 @@ module.exports = {
|
|
|
3
3
|
platforms: {
|
|
4
4
|
android: {
|
|
5
5
|
libraryName: 'ReactNativeBottomSheetSpec',
|
|
6
|
-
componentDescriptors: [
|
|
6
|
+
componentDescriptors: [
|
|
7
|
+
'BottomSheetViewComponentDescriptor',
|
|
8
|
+
'BottomSheetSurfaceViewComponentDescriptor',
|
|
9
|
+
],
|
|
7
10
|
cmakeListsPath: 'src/main/jni/CMakeLists.txt',
|
|
8
11
|
},
|
|
9
12
|
},
|
package/src/BottomSheet.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { StyleSheet, View, useWindowDimensions } from 'react-native';
|
|
|
4
4
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
5
|
|
|
6
6
|
import BottomSheetNativeComponent from './BottomSheetNativeComponent';
|
|
7
|
+
import BottomSheetSurfaceNativeComponent from './BottomSheetSurfaceNativeComponent';
|
|
7
8
|
import { Portal } from './BottomSheetProvider';
|
|
8
9
|
import { type Detent } from './bottomSheetUtils';
|
|
9
10
|
export type { Detent, DetentValue } from './bottomSheetUtils';
|
|
@@ -13,8 +14,20 @@ export { programmatic } from './bottomSheetUtils';
|
|
|
13
14
|
* Props for the inline bottom-sheet component.
|
|
14
15
|
*/
|
|
15
16
|
export interface BottomSheetProps {
|
|
16
|
-
/** Sheet contents, including any
|
|
17
|
+
/** Sheet contents, including any scrollable content. */
|
|
17
18
|
children: ReactNode;
|
|
19
|
+
/**
|
|
20
|
+
* Optional visual surface (background) rendered behind the content. The
|
|
21
|
+
* library sizes and positions the surface natively to cover the full sheet,
|
|
22
|
+
* independently of the content, so a shrinking content height never exposes
|
|
23
|
+
* blank space. Put a background View here instead of inside `children` when
|
|
24
|
+
* you want that shrink-safe behavior. When omitted, behavior is unchanged.
|
|
25
|
+
*
|
|
26
|
+
* Give the surface element a filling style such as `StyleSheet.absoluteFill`:
|
|
27
|
+
* it is mounted in a full-size host, so a surface sized only by its own
|
|
28
|
+
* content would collapse and not show.
|
|
29
|
+
*/
|
|
30
|
+
surface?: ReactNode;
|
|
18
31
|
/** Additional style applied to the native sheet host view. */
|
|
19
32
|
style?: StyleProp<ViewStyle>;
|
|
20
33
|
/** Snap points for the sheet. Defaults to `[0, 'content']`. */
|
|
@@ -44,6 +57,7 @@ export interface BottomSheetProps {
|
|
|
44
57
|
/** Native bottom sheet that renders inline within the current screen layout. */
|
|
45
58
|
export const BottomSheet = ({
|
|
46
59
|
children,
|
|
60
|
+
surface,
|
|
47
61
|
style,
|
|
48
62
|
detents = [0, 'content'],
|
|
49
63
|
index,
|
|
@@ -128,6 +142,15 @@ export const BottomSheet = ({
|
|
|
128
142
|
onSettle={handleSettle}
|
|
129
143
|
onPositionChange={handlePositionChange}
|
|
130
144
|
>
|
|
145
|
+
{surface != null && (
|
|
146
|
+
<BottomSheetSurfaceNativeComponent
|
|
147
|
+
collapsable={false}
|
|
148
|
+
pointerEvents="box-none"
|
|
149
|
+
style={StyleSheet.absoluteFill}
|
|
150
|
+
>
|
|
151
|
+
{surface}
|
|
152
|
+
</BottomSheetSurfaceNativeComponent>
|
|
153
|
+
)}
|
|
131
154
|
<View collapsable={false} style={{ flex: 1, maxHeight }}>
|
|
132
155
|
{children}
|
|
133
156
|
<View collapsable={false} pointerEvents="none" />
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { codegenNativeComponent, type ViewProps } from 'react-native';
|
|
2
|
+
|
|
3
|
+
// The visual surface that sits behind the sheet content. It carries no props of
|
|
4
|
+
// its own: the library identifies it by component type and owns its geometry,
|
|
5
|
+
// sizing it to cover the full sheet so a content shrink never exposes blank
|
|
6
|
+
// space. Its React children provide the visual appearance only.
|
|
7
|
+
export interface NativeProps extends ViewProps {}
|
|
8
|
+
|
|
9
|
+
export default codegenNativeComponent<NativeProps>('BottomSheetSurfaceView');
|
package/src/ModalBottomSheet.tsx
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { BottomSheet, type BottomSheetProps } from './BottomSheet';
|
|
2
2
|
|
|
3
3
|
/** Props for the modal bottom-sheet variant rendered through the provider portal. */
|
|
4
|
-
export interface ModalBottomSheetProps
|
|
5
|
-
|
|
4
|
+
export interface ModalBottomSheetProps extends Omit<
|
|
5
|
+
BottomSheetProps,
|
|
6
|
+
'modal'
|
|
7
|
+
> {}
|
|
6
8
|
|
|
7
9
|
/** Bottom sheet presented above the current UI with a scrim. */
|
|
8
10
|
export const ModalBottomSheet = (props: ModalBottomSheetProps) => (
|