@oxyhq/bloom 0.45.0 → 0.48.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/lib/commonjs/bottom-sheet/BottomSheetBase.js +34 -12
- package/lib/commonjs/bottom-sheet/BottomSheetBase.js.map +1 -1
- package/lib/commonjs/combobox/Combobox.js +3 -3
- package/lib/commonjs/combobox/Combobox.js.map +1 -1
- package/lib/commonjs/command/Command.js +3 -3
- package/lib/commonjs/command/Command.js.map +1 -1
- package/lib/commonjs/dialog/Dialog.js +86 -8
- package/lib/commonjs/dialog/Dialog.js.map +1 -1
- package/lib/commonjs/dialog/Dialog.web.js +176 -18
- package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
- package/lib/commonjs/dialog/DialogBottomSheet.js +84 -29
- package/lib/commonjs/dialog/DialogBottomSheet.js.map +1 -1
- package/lib/commonjs/dialog/DialogHeader.js +365 -0
- package/lib/commonjs/dialog/DialogHeader.js.map +1 -0
- package/lib/commonjs/dialog/index.js +7 -0
- package/lib/commonjs/dialog/index.js.map +1 -1
- package/lib/commonjs/dialog/index.web.js +7 -0
- package/lib/commonjs/dialog/index.web.js.map +1 -1
- package/lib/commonjs/index.js +40 -4
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +84 -48
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/react-native-className.d.js +6 -0
- package/lib/commonjs/react-native-className.d.js.map +1 -0
- package/lib/commonjs/{search-input → search}/index.js +5 -3
- package/lib/commonjs/search/index.js.map +1 -0
- package/lib/commonjs/settings-list/SettingsList.js +5 -1
- package/lib/commonjs/settings-list/SettingsList.js.map +1 -1
- package/lib/commonjs/surfaces/SurfaceHost.js +202 -0
- package/lib/commonjs/surfaces/SurfaceHost.js.map +1 -0
- package/lib/commonjs/surfaces/api.js +83 -0
- package/lib/commonjs/surfaces/api.js.map +1 -0
- package/lib/commonjs/surfaces/index.js +82 -0
- package/lib/commonjs/surfaces/index.js.map +1 -0
- package/lib/commonjs/surfaces/index.web.js +77 -0
- package/lib/commonjs/surfaces/index.web.js.map +1 -0
- package/lib/commonjs/surfaces/prompts.js +154 -0
- package/lib/commonjs/surfaces/prompts.js.map +1 -0
- package/lib/commonjs/surfaces/surfaceStore.js +172 -0
- package/lib/commonjs/surfaces/surfaceStore.js.map +1 -0
- package/lib/commonjs/surfaces/types.js +6 -0
- package/lib/commonjs/surfaces/types.js.map +1 -0
- package/lib/commonjs/surfaces/useSurface.js +36 -0
- package/lib/commonjs/surfaces/useSurface.js.map +1 -0
- package/lib/commonjs/text-field/index.js +22 -6
- package/lib/commonjs/text-field/index.js.map +1 -1
- package/lib/module/bottom-sheet/BottomSheetBase.js +34 -12
- package/lib/module/bottom-sheet/BottomSheetBase.js.map +1 -1
- package/lib/module/combobox/Combobox.js +3 -3
- package/lib/module/combobox/Combobox.js.map +1 -1
- package/lib/module/command/Command.js +3 -3
- package/lib/module/command/Command.js.map +1 -1
- package/lib/module/dialog/Dialog.js +88 -10
- package/lib/module/dialog/Dialog.js.map +1 -1
- package/lib/module/dialog/Dialog.web.js +179 -21
- package/lib/module/dialog/Dialog.web.js.map +1 -1
- package/lib/module/dialog/DialogBottomSheet.js +85 -30
- package/lib/module/dialog/DialogBottomSheet.js.map +1 -1
- package/lib/module/dialog/DialogHeader.js +356 -0
- package/lib/module/dialog/DialogHeader.js.map +1 -0
- package/lib/module/dialog/index.js +1 -0
- package/lib/module/dialog/index.js.map +1 -1
- package/lib/module/dialog/index.web.js +1 -0
- package/lib/module/dialog/index.web.js.map +1 -1
- package/lib/module/index.js +7 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +7 -2
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/react-native-className.d.js +20 -0
- package/lib/module/react-native-className.d.js.map +1 -0
- package/lib/module/{search-input → search}/index.js +6 -2
- package/lib/module/search/index.js.map +1 -0
- package/lib/module/settings-list/SettingsList.js +5 -1
- package/lib/module/settings-list/SettingsList.js.map +1 -1
- package/lib/module/surfaces/SurfaceHost.js +197 -0
- package/lib/module/surfaces/SurfaceHost.js.map +1 -0
- package/lib/module/surfaces/api.js +48 -0
- package/lib/module/surfaces/api.js.map +1 -0
- package/lib/module/surfaces/index.js +24 -0
- package/lib/module/surfaces/index.js.map +1 -0
- package/lib/module/surfaces/index.web.js +19 -0
- package/lib/module/surfaces/index.web.js.map +1 -0
- package/lib/module/surfaces/prompts.js +148 -0
- package/lib/module/surfaces/prompts.js.map +1 -0
- package/lib/module/surfaces/surfaceStore.js +159 -0
- package/lib/module/surfaces/surfaceStore.js.map +1 -0
- package/lib/module/surfaces/types.js +4 -0
- package/lib/module/surfaces/types.js.map +1 -0
- package/lib/module/surfaces/useSurface.js +31 -0
- package/lib/module/surfaces/useSurface.js.map +1 -0
- package/lib/module/text-field/index.js +22 -6
- package/lib/module/text-field/index.js.map +1 -1
- package/lib/typescript/commonjs/bottom-sheet/BottomSheetBase.d.ts +23 -0
- package/lib/typescript/commonjs/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
- package/lib/typescript/commonjs/combobox/Combobox.d.ts +1 -1
- package/lib/typescript/commonjs/command/Command.d.ts +1 -1
- package/lib/typescript/commonjs/dialog/Dialog.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/Dialog.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/DialogBottomSheet.d.ts +2 -2
- package/lib/typescript/commonjs/dialog/DialogBottomSheet.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/DialogHeader.d.ts +86 -0
- package/lib/typescript/commonjs/dialog/DialogHeader.d.ts.map +1 -0
- package/lib/typescript/commonjs/dialog/index.d.ts +2 -1
- package/lib/typescript/commonjs/dialog/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/index.web.d.ts +2 -1
- package/lib/typescript/commonjs/dialog/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/types.d.ts +85 -0
- package/lib/typescript/commonjs/dialog/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/icons/common.d.ts +1 -0
- package/lib/typescript/commonjs/icons/common.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +5 -3
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +5 -3
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/input-group/types.d.ts +1 -1
- package/lib/typescript/commonjs/{search-input → search}/index.d.ts +1 -1
- package/lib/typescript/commonjs/search/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/surfaces/SurfaceHost.d.ts +25 -0
- package/lib/typescript/commonjs/surfaces/SurfaceHost.d.ts.map +1 -0
- package/lib/typescript/commonjs/surfaces/api.d.ts +41 -0
- package/lib/typescript/commonjs/surfaces/api.d.ts.map +1 -0
- package/lib/typescript/commonjs/surfaces/index.d.ts +25 -0
- package/lib/typescript/commonjs/surfaces/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/surfaces/index.web.d.ts +20 -0
- package/lib/typescript/commonjs/surfaces/index.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/surfaces/prompts.d.ts +13 -0
- package/lib/typescript/commonjs/surfaces/prompts.d.ts.map +1 -0
- package/lib/typescript/commonjs/surfaces/surfaceStore.d.ts +51 -0
- package/lib/typescript/commonjs/surfaces/surfaceStore.d.ts.map +1 -0
- package/lib/typescript/commonjs/surfaces/types.d.ts +161 -0
- package/lib/typescript/commonjs/surfaces/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/surfaces/useSurface.d.ts +21 -0
- package/lib/typescript/commonjs/surfaces/useSurface.d.ts.map +1 -0
- package/lib/typescript/commonjs/text-field/index.d.ts +2 -1
- package/lib/typescript/commonjs/text-field/index.d.ts.map +1 -1
- package/lib/typescript/module/bottom-sheet/BottomSheetBase.d.ts +23 -0
- package/lib/typescript/module/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
- package/lib/typescript/module/combobox/Combobox.d.ts +1 -1
- package/lib/typescript/module/command/Command.d.ts +1 -1
- package/lib/typescript/module/dialog/Dialog.d.ts.map +1 -1
- package/lib/typescript/module/dialog/Dialog.web.d.ts.map +1 -1
- package/lib/typescript/module/dialog/DialogBottomSheet.d.ts +2 -2
- package/lib/typescript/module/dialog/DialogBottomSheet.d.ts.map +1 -1
- package/lib/typescript/module/dialog/DialogHeader.d.ts +86 -0
- package/lib/typescript/module/dialog/DialogHeader.d.ts.map +1 -0
- package/lib/typescript/module/dialog/index.d.ts +2 -1
- package/lib/typescript/module/dialog/index.d.ts.map +1 -1
- package/lib/typescript/module/dialog/index.web.d.ts +2 -1
- package/lib/typescript/module/dialog/index.web.d.ts.map +1 -1
- package/lib/typescript/module/dialog/types.d.ts +85 -0
- package/lib/typescript/module/dialog/types.d.ts.map +1 -1
- package/lib/typescript/module/icons/common.d.ts +1 -0
- package/lib/typescript/module/icons/common.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +5 -3
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +5 -3
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/lib/typescript/module/input-group/types.d.ts +1 -1
- package/lib/typescript/module/{search-input → search}/index.d.ts +1 -1
- package/lib/typescript/module/search/index.d.ts.map +1 -0
- package/lib/typescript/module/surfaces/SurfaceHost.d.ts +25 -0
- package/lib/typescript/module/surfaces/SurfaceHost.d.ts.map +1 -0
- package/lib/typescript/module/surfaces/api.d.ts +41 -0
- package/lib/typescript/module/surfaces/api.d.ts.map +1 -0
- package/lib/typescript/module/surfaces/index.d.ts +25 -0
- package/lib/typescript/module/surfaces/index.d.ts.map +1 -0
- package/lib/typescript/module/surfaces/index.web.d.ts +20 -0
- package/lib/typescript/module/surfaces/index.web.d.ts.map +1 -0
- package/lib/typescript/module/surfaces/prompts.d.ts +13 -0
- package/lib/typescript/module/surfaces/prompts.d.ts.map +1 -0
- package/lib/typescript/module/surfaces/surfaceStore.d.ts +51 -0
- package/lib/typescript/module/surfaces/surfaceStore.d.ts.map +1 -0
- package/lib/typescript/module/surfaces/types.d.ts +161 -0
- package/lib/typescript/module/surfaces/types.d.ts.map +1 -0
- package/lib/typescript/module/surfaces/useSurface.d.ts +21 -0
- package/lib/typescript/module/surfaces/useSurface.d.ts.map +1 -0
- package/lib/typescript/module/text-field/index.d.ts +2 -1
- package/lib/typescript/module/text-field/index.d.ts.map +1 -1
- package/package.json +24 -8
- package/src/__tests__/BottomSheet.test.tsx +47 -0
- package/src/__tests__/DialogBottomSheet.test.tsx +30 -5
- package/src/__tests__/surfaceStore.test.ts +219 -0
- package/src/bottom-sheet/BottomSheetBase.tsx +59 -10
- package/src/combobox/Combobox.tsx +3 -3
- package/src/command/Command.tsx +3 -3
- package/src/dialog/Dialog.tsx +88 -7
- package/src/dialog/Dialog.web.tsx +179 -29
- package/src/dialog/DialogBottomSheet.tsx +90 -28
- package/src/dialog/DialogHeader.tsx +420 -0
- package/src/dialog/index.ts +2 -0
- package/src/dialog/index.web.ts +2 -0
- package/src/dialog/types.ts +86 -0
- package/src/index.ts +18 -1
- package/src/index.web.ts +18 -1
- package/src/input-group/types.ts +1 -1
- package/src/react-native-className.d.ts +38 -0
- package/src/{search-input → search}/index.tsx +8 -5
- package/src/settings-list/SettingsList.tsx +5 -1
- package/src/surfaces/SurfaceHost.tsx +222 -0
- package/src/surfaces/api.ts +55 -0
- package/src/surfaces/index.ts +38 -0
- package/src/surfaces/index.web.ts +33 -0
- package/src/surfaces/prompts.tsx +178 -0
- package/src/surfaces/surfaceStore.ts +168 -0
- package/src/surfaces/types.ts +172 -0
- package/src/surfaces/useSurface.ts +33 -0
- package/src/text-field/index.tsx +24 -5
- package/lib/commonjs/search-input/index.js.map +0 -1
- package/lib/module/search-input/index.js.map +0 -1
- package/lib/typescript/commonjs/search-input/index.d.ts.map +0 -1
- package/lib/typescript/module/search-input/index.d.ts.map +0 -1
package/src/dialog/Dialog.tsx
CHANGED
|
@@ -25,11 +25,19 @@ import Animated, {
|
|
|
25
25
|
} from 'react-native-reanimated';
|
|
26
26
|
|
|
27
27
|
import { BottomSheet, type BottomSheetRef } from '../bottom-sheet';
|
|
28
|
-
import { Z_INDEX } from '../styles/z-index';
|
|
28
|
+
import { Z_INDEX, Z_INDEX_LAYER_STEP } from '../styles/z-index';
|
|
29
29
|
import { useTheme } from '../theme/use-theme';
|
|
30
30
|
import { Context, useDialogControl } from './context';
|
|
31
31
|
import { DialogBody } from './DialogContent';
|
|
32
32
|
import { DialogBottomSheet } from './DialogBottomSheet';
|
|
33
|
+
import {
|
|
34
|
+
DIALOG_NAV_BAR_HEIGHT,
|
|
35
|
+
type DialogHeaderController,
|
|
36
|
+
DialogHeaderProvider,
|
|
37
|
+
DialogLargeTitle,
|
|
38
|
+
DialogNavHeader,
|
|
39
|
+
useDialogHeaderController,
|
|
40
|
+
} from './DialogHeader';
|
|
33
41
|
import {
|
|
34
42
|
ANIMATION_DURATION,
|
|
35
43
|
DEFAULT_DIALOG_CONTENT_PADDING,
|
|
@@ -103,7 +111,10 @@ function CenteredOrSideDialog({
|
|
|
103
111
|
title,
|
|
104
112
|
description,
|
|
105
113
|
actions,
|
|
114
|
+
header,
|
|
115
|
+
scrollable,
|
|
106
116
|
placement,
|
|
117
|
+
layer,
|
|
107
118
|
width = DEFAULT_SIDE_WIDTH,
|
|
108
119
|
inset,
|
|
109
120
|
dismissOnBackdrop = true,
|
|
@@ -118,6 +129,8 @@ function CenteredOrSideDialog({
|
|
|
118
129
|
}: Omit<DialogProps, 'placement'> & { placement: 'center' | 'left' | 'right' }) {
|
|
119
130
|
const isControlled = controlledOpen !== undefined;
|
|
120
131
|
const isSide = placement === 'left' || placement === 'right';
|
|
132
|
+
const headerController = useDialogHeaderController();
|
|
133
|
+
const scrollableResolved = scrollable ?? true;
|
|
121
134
|
|
|
122
135
|
// Imperative open state for the side placement (the BottomSheet path owns its
|
|
123
136
|
// own visibility via its ref instead).
|
|
@@ -234,6 +247,24 @@ function CenteredOrSideDialog({
|
|
|
234
247
|
</DialogBody>
|
|
235
248
|
);
|
|
236
249
|
|
|
250
|
+
// Nav-header mode: the Dialog OWNS a sticky gradient nav bar + a large
|
|
251
|
+
// collapsing title over its own scroll content. The large title + screen body
|
|
252
|
+
// scroll under the bar, feeding the shared collapse controller. `contentPadding`
|
|
253
|
+
// is dropped (the large title + screens own their padding).
|
|
254
|
+
const headerBody = header ? (
|
|
255
|
+
scrollableResolved ? (
|
|
256
|
+
<>
|
|
257
|
+
<DialogLargeTitle controller={headerController} header={header} />
|
|
258
|
+
<DialogHeaderProvider controller={headerController}>{bodyNode}</DialogHeaderProvider>
|
|
259
|
+
</>
|
|
260
|
+
) : (
|
|
261
|
+
<>
|
|
262
|
+
<View style={{ height: DIALOG_NAV_BAR_HEIGHT }} />
|
|
263
|
+
<DialogHeaderProvider controller={headerController}>{bodyNode}</DialogHeaderProvider>
|
|
264
|
+
</>
|
|
265
|
+
)
|
|
266
|
+
) : null;
|
|
267
|
+
|
|
237
268
|
if (isSide) {
|
|
238
269
|
return (
|
|
239
270
|
<Context.Provider value={context}>
|
|
@@ -241,6 +272,7 @@ function CenteredOrSideDialog({
|
|
|
241
272
|
open={sideOpen}
|
|
242
273
|
onDismiss={handleDismiss}
|
|
243
274
|
side={placement}
|
|
275
|
+
layer={layer}
|
|
244
276
|
width={width}
|
|
245
277
|
inset={inset}
|
|
246
278
|
dismissOnBackdrop={dismissOnBackdrop}
|
|
@@ -251,6 +283,9 @@ function CenteredOrSideDialog({
|
|
|
251
283
|
description={description}
|
|
252
284
|
titleId={titleId}
|
|
253
285
|
descriptionId={descriptionId}
|
|
286
|
+
header={header}
|
|
287
|
+
headerController={headerController}
|
|
288
|
+
onHeaderDismiss={close}
|
|
254
289
|
panelStyle={panelStyle}
|
|
255
290
|
panelClassName={panelClassName}
|
|
256
291
|
containerStyle={containerStyle}
|
|
@@ -269,7 +304,21 @@ function CenteredOrSideDialog({
|
|
|
269
304
|
onDismiss={handleDismiss}
|
|
270
305
|
enablePanDownToClose
|
|
271
306
|
detached
|
|
272
|
-
|
|
307
|
+
// Nav-header mode hides the drag handle (the nav bar sits at the very top)
|
|
308
|
+
// and coordinates the body pan with the scroll so content scrolls.
|
|
309
|
+
showHandle={!header}
|
|
310
|
+
manualActivation={!!header}
|
|
311
|
+
scrollY={header ? headerController.scrollY : undefined}
|
|
312
|
+
headerOverlay={
|
|
313
|
+
header ? (
|
|
314
|
+
<DialogNavHeader
|
|
315
|
+
controller={headerController}
|
|
316
|
+
header={header}
|
|
317
|
+
onDismiss={close}
|
|
318
|
+
collapse={scrollableResolved}
|
|
319
|
+
/>
|
|
320
|
+
) : undefined
|
|
321
|
+
}
|
|
273
322
|
// Stronger dim when a Dialog is stacked over another sheet so the
|
|
274
323
|
// underlying sheet's handle/content doesn't bleed through.
|
|
275
324
|
backdropOpacity={0.7}
|
|
@@ -286,13 +335,14 @@ function CenteredOrSideDialog({
|
|
|
286
335
|
// Detached BottomSheet already adds `marginBottom: insets.bottom + 16`
|
|
287
336
|
// to the sheet container — the floating card sits ABOVE the
|
|
288
337
|
// system gesture bar, so we don't add `insets.bottom` here.
|
|
289
|
-
|
|
338
|
+
// Nav-header mode: the large title + screens own their padding.
|
|
339
|
+
header ? null : { padding: contentPadding },
|
|
290
340
|
{ backgroundColor: theme.colors.background },
|
|
291
341
|
style,
|
|
292
342
|
panelStyle,
|
|
293
343
|
]}
|
|
294
344
|
>
|
|
295
|
-
{bodyNode}
|
|
345
|
+
{header ? headerBody : bodyNode}
|
|
296
346
|
</View>
|
|
297
347
|
</Context.Provider>
|
|
298
348
|
</BottomSheet>
|
|
@@ -311,6 +361,7 @@ function SideSheet({
|
|
|
311
361
|
open,
|
|
312
362
|
onDismiss,
|
|
313
363
|
side,
|
|
364
|
+
layer,
|
|
314
365
|
width,
|
|
315
366
|
inset,
|
|
316
367
|
dismissOnBackdrop,
|
|
@@ -321,6 +372,9 @@ function SideSheet({
|
|
|
321
372
|
description,
|
|
322
373
|
titleId,
|
|
323
374
|
descriptionId,
|
|
375
|
+
header,
|
|
376
|
+
headerController,
|
|
377
|
+
onHeaderDismiss,
|
|
324
378
|
panelStyle,
|
|
325
379
|
panelClassName,
|
|
326
380
|
containerStyle,
|
|
@@ -331,6 +385,7 @@ function SideSheet({
|
|
|
331
385
|
open: boolean;
|
|
332
386
|
onDismiss: () => void;
|
|
333
387
|
side: 'left' | 'right';
|
|
388
|
+
layer?: number;
|
|
334
389
|
width: number;
|
|
335
390
|
inset?: DialogInset;
|
|
336
391
|
dismissOnBackdrop: boolean;
|
|
@@ -341,6 +396,9 @@ function SideSheet({
|
|
|
341
396
|
description?: string;
|
|
342
397
|
titleId: string;
|
|
343
398
|
descriptionId: string;
|
|
399
|
+
header?: DialogProps['header'];
|
|
400
|
+
headerController: DialogHeaderController;
|
|
401
|
+
onHeaderDismiss: () => void;
|
|
344
402
|
panelStyle?: StyleProp<ViewStyle>;
|
|
345
403
|
panelClassName?: string;
|
|
346
404
|
containerStyle?: StyleProp<ViewStyle>;
|
|
@@ -433,7 +491,13 @@ function SideSheet({
|
|
|
433
491
|
|
|
434
492
|
return (
|
|
435
493
|
<View
|
|
436
|
-
style={[
|
|
494
|
+
style={[
|
|
495
|
+
sideStyles.root,
|
|
496
|
+
// Per-layer offset so a side dialog stacked on top of another (surface
|
|
497
|
+
// stack) paints above it. Layer 0 → offset 0 → unchanged.
|
|
498
|
+
{ zIndex: Z_INDEX.fullscreen + Z_INDEX_LAYER_STEP * (layer ?? 0) },
|
|
499
|
+
containerStyle,
|
|
500
|
+
]}
|
|
437
501
|
{...(containerClassName ? ({ className: containerClassName } as Record<string, string>) : {})}
|
|
438
502
|
pointerEvents="box-none"
|
|
439
503
|
>
|
|
@@ -466,16 +530,33 @@ function SideSheet({
|
|
|
466
530
|
]}
|
|
467
531
|
pointerEvents="auto"
|
|
468
532
|
>
|
|
469
|
-
|
|
533
|
+
{header ? (
|
|
534
|
+
// Nav-header mode on a side drawer: a static titled bar (the drawer body
|
|
535
|
+
// has no Dialog scroll offset to drive a collapse) over content inset
|
|
536
|
+
// below the bar.
|
|
537
|
+
<View style={{ flex: 1, minHeight: 0 }}>
|
|
538
|
+
<DialogNavHeader
|
|
539
|
+
controller={headerController}
|
|
540
|
+
header={header}
|
|
541
|
+
onDismiss={onHeaderDismiss}
|
|
542
|
+
collapse={false}
|
|
543
|
+
/>
|
|
544
|
+
<View style={{ height: DIALOG_NAV_BAR_HEIGHT }} />
|
|
545
|
+
<DialogHeaderProvider controller={headerController}>{children}</DialogHeaderProvider>
|
|
546
|
+
</View>
|
|
547
|
+
) : (
|
|
548
|
+
<View style={{ padding: contentPadding }}>{children}</View>
|
|
549
|
+
)}
|
|
470
550
|
</Animated.View>
|
|
471
551
|
</View>
|
|
472
552
|
);
|
|
473
553
|
}
|
|
474
554
|
|
|
475
555
|
const sideStyles = StyleSheet.create({
|
|
556
|
+
// `zIndex` is applied inline (`Z_INDEX.fullscreen + layer offset`) so a side
|
|
557
|
+
// dialog stacked on top of another paints above it in the surface stack.
|
|
476
558
|
root: {
|
|
477
559
|
...StyleSheet.absoluteFill,
|
|
478
|
-
zIndex: Z_INDEX.fullscreen,
|
|
479
560
|
},
|
|
480
561
|
backdrop: {
|
|
481
562
|
...StyleSheet.absoluteFill,
|
|
@@ -10,6 +10,7 @@ import React, {
|
|
|
10
10
|
} from 'react';
|
|
11
11
|
import {
|
|
12
12
|
Pressable,
|
|
13
|
+
ScrollView,
|
|
13
14
|
useWindowDimensions,
|
|
14
15
|
View,
|
|
15
16
|
type StyleProp,
|
|
@@ -20,9 +21,16 @@ import { RemoveScrollBar } from 'react-remove-scroll-bar';
|
|
|
20
21
|
import { Portal } from '../portal/index.web';
|
|
21
22
|
import { createOverlayZIndex } from '../styles/z-index';
|
|
22
23
|
import { useTheme } from '../theme/use-theme';
|
|
23
|
-
import { Context, useDialogControl } from './context';
|
|
24
|
+
import { Context, useDialogContext, useDialogControl } from './context';
|
|
24
25
|
import { DialogBody } from './DialogContent';
|
|
25
26
|
import { DialogBottomSheet } from './DialogBottomSheet';
|
|
27
|
+
import {
|
|
28
|
+
DIALOG_NAV_BAR_HEIGHT,
|
|
29
|
+
DialogHeaderProvider,
|
|
30
|
+
DialogLargeTitle,
|
|
31
|
+
DialogNavHeader,
|
|
32
|
+
useDialogHeaderController,
|
|
33
|
+
} from './DialogHeader';
|
|
26
34
|
import {
|
|
27
35
|
ANIMATION_DURATION,
|
|
28
36
|
CENTER_FADE_OUT_DURATION,
|
|
@@ -44,7 +52,6 @@ import type {
|
|
|
44
52
|
} from './types';
|
|
45
53
|
|
|
46
54
|
const FADE_OUT_DURATION = CENTER_FADE_OUT_DURATION;
|
|
47
|
-
const dialogZIndex = createOverlayZIndex();
|
|
48
55
|
|
|
49
56
|
const stopPropagation = (e: { stopPropagation: () => void }) => e.stopPropagation();
|
|
50
57
|
|
|
@@ -126,17 +133,22 @@ export function Dialog({ placement, ...rest }: DialogProps) {
|
|
|
126
133
|
function CenterOrSideDialog({
|
|
127
134
|
control,
|
|
128
135
|
open: controlledOpen,
|
|
136
|
+
startOpen,
|
|
129
137
|
onClose,
|
|
130
138
|
testID,
|
|
131
139
|
title,
|
|
132
140
|
description,
|
|
133
141
|
actions,
|
|
142
|
+
header,
|
|
134
143
|
placement,
|
|
144
|
+
layer,
|
|
135
145
|
width = DEFAULT_SIDE_WIDTH,
|
|
136
146
|
maxWidth = DEFAULT_CENTER_MAX_WIDTH,
|
|
137
147
|
inset,
|
|
138
148
|
dismissOnBackdrop = true,
|
|
139
149
|
contentPadding = DEFAULT_DIALOG_CONTENT_PADDING,
|
|
150
|
+
maxHeightRatio,
|
|
151
|
+
scrollable,
|
|
140
152
|
style,
|
|
141
153
|
panelStyle,
|
|
142
154
|
panelClassName,
|
|
@@ -150,12 +162,23 @@ function CenterOrSideDialog({
|
|
|
150
162
|
// stylesheet is present by the time the animated surface mounts).
|
|
151
163
|
useDialogCss();
|
|
152
164
|
|
|
165
|
+
// Per-layer overlay z-indices, so a dialog presented on top of another (via
|
|
166
|
+
// the surface stack) paints above it. Defaults to layer 0 — the offset is 0,
|
|
167
|
+
// so a lone dialog's z is byte-for-byte unchanged.
|
|
168
|
+
const dialogZIndex = useMemo(() => createOverlayZIndex(layer ?? 0), [layer]);
|
|
169
|
+
|
|
153
170
|
// Controlled mode is opt-in: when `open` is a boolean the host owns the
|
|
154
171
|
// visible state; otherwise the legacy imperative `control` path drives it.
|
|
155
172
|
const isControlled = controlledOpen !== undefined;
|
|
156
173
|
const resolvedPlacement = placement;
|
|
157
174
|
|
|
158
|
-
|
|
175
|
+
// Seed the initial visible state so a branch that mounts while the surface is
|
|
176
|
+
// already open (the imperative `startOpen` intent) or under a truthy
|
|
177
|
+
// controlled `open` renders open on its FIRST commit — this is what lets a
|
|
178
|
+
// responsive Dialog survive a placement swap (centered card ↔ bottom sheet on
|
|
179
|
+
// resize) without going blank. `startOpen` never flips the close semantics;
|
|
180
|
+
// the imperative `control.close()` still drives the exit + post-exit onClose.
|
|
181
|
+
const [isOpen, setIsOpen] = useState(() => controlledOpen ?? startOpen ?? false);
|
|
159
182
|
const [isClosing, setIsClosing] = useState(false);
|
|
160
183
|
const closeCallbacksRef = useRef<(() => void)[]>([]);
|
|
161
184
|
// Read the latest controlled flag inside stable callbacks without re-binding.
|
|
@@ -294,9 +317,13 @@ function CenterOrSideDialog({
|
|
|
294
317
|
title={title}
|
|
295
318
|
description={description}
|
|
296
319
|
actions={actions}
|
|
320
|
+
header={header}
|
|
297
321
|
style={style}
|
|
298
322
|
maxWidth={maxWidth}
|
|
299
323
|
contentPadding={contentPadding}
|
|
324
|
+
maxHeightRatio={maxHeightRatio}
|
|
325
|
+
scrollable={scrollable}
|
|
326
|
+
surfaceZIndex={dialogZIndex.surface}
|
|
300
327
|
isClosing={isClosing}
|
|
301
328
|
>
|
|
302
329
|
{children}
|
|
@@ -319,12 +346,16 @@ function CenterOrSideDialog({
|
|
|
319
346
|
title={title}
|
|
320
347
|
description={description}
|
|
321
348
|
actions={actions}
|
|
349
|
+
header={header}
|
|
350
|
+
scrollable={scrollable}
|
|
322
351
|
placement={resolvedPlacement}
|
|
323
352
|
shown={!isClosing}
|
|
324
353
|
width={width}
|
|
325
354
|
inset={inset}
|
|
326
355
|
dismissOnBackdrop={dismissOnBackdrop}
|
|
327
356
|
contentPadding={contentPadding}
|
|
357
|
+
backdropZIndex={dialogZIndex.backdrop}
|
|
358
|
+
surfaceZIndex={dialogZIndex.surface}
|
|
328
359
|
onDismiss={close}
|
|
329
360
|
panelStyle={panelStyle}
|
|
330
361
|
panelClassName={panelClassName}
|
|
@@ -346,9 +377,13 @@ function DialogPanel({
|
|
|
346
377
|
title,
|
|
347
378
|
description,
|
|
348
379
|
actions,
|
|
380
|
+
header,
|
|
349
381
|
style,
|
|
350
382
|
maxWidth,
|
|
351
383
|
contentPadding,
|
|
384
|
+
maxHeightRatio,
|
|
385
|
+
scrollable,
|
|
386
|
+
surfaceZIndex,
|
|
352
387
|
isClosing,
|
|
353
388
|
children,
|
|
354
389
|
}: {
|
|
@@ -357,15 +392,33 @@ function DialogPanel({
|
|
|
357
392
|
title?: string;
|
|
358
393
|
description?: string;
|
|
359
394
|
actions?: DialogAction[];
|
|
395
|
+
header?: DialogProps['header'];
|
|
360
396
|
style?: DialogProps['style'];
|
|
361
397
|
maxWidth: number;
|
|
362
398
|
contentPadding: number;
|
|
399
|
+
maxHeightRatio?: number;
|
|
400
|
+
scrollable?: boolean;
|
|
401
|
+
surfaceZIndex: number;
|
|
363
402
|
isClosing: boolean;
|
|
364
403
|
children?: React.ReactNode;
|
|
365
404
|
}) {
|
|
366
405
|
const theme = useTheme();
|
|
406
|
+
const { close } = useDialogContext();
|
|
367
407
|
const titleId = useId();
|
|
368
408
|
const descriptionId = useId();
|
|
409
|
+
const headerController = useDialogHeaderController();
|
|
410
|
+
|
|
411
|
+
const body = (
|
|
412
|
+
<DialogBody
|
|
413
|
+
titleId={titleId}
|
|
414
|
+
descriptionId={descriptionId}
|
|
415
|
+
title={title}
|
|
416
|
+
description={description}
|
|
417
|
+
actions={actions}
|
|
418
|
+
>
|
|
419
|
+
{children}
|
|
420
|
+
</DialogBody>
|
|
421
|
+
);
|
|
369
422
|
|
|
370
423
|
return (
|
|
371
424
|
<View
|
|
@@ -383,6 +436,13 @@ function DialogPanel({
|
|
|
383
436
|
borderRadius: 20,
|
|
384
437
|
width: '100%',
|
|
385
438
|
maxWidth,
|
|
439
|
+
// The Dialog OWNS the size cap + scroll boundary (its content renders
|
|
440
|
+
// bare, edge-to-edge). Short content stays natural; tall content is
|
|
441
|
+
// capped at `maxHeightRatio` of the viewport and scrolls inside the
|
|
442
|
+
// rounded card (via the ScrollView below) — consumers never add their
|
|
443
|
+
// own height cap / ScrollView. `overflow: hidden` clips to the radius.
|
|
444
|
+
maxHeight: `${Math.round((maxHeightRatio ?? 0.9) * 100)}%`,
|
|
445
|
+
overflow: 'hidden',
|
|
386
446
|
backgroundColor: theme.colors.background,
|
|
387
447
|
borderWidth: 1,
|
|
388
448
|
borderColor: theme.isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)',
|
|
@@ -390,8 +450,7 @@ function DialogPanel({
|
|
|
390
450
|
shadowOpacity: theme.isDark ? 0.4 : 0.1,
|
|
391
451
|
shadowRadius: 30,
|
|
392
452
|
shadowOffset: { width: 0, height: 4 },
|
|
393
|
-
|
|
394
|
-
zIndex: dialogZIndex.surface,
|
|
453
|
+
zIndex: surfaceZIndex,
|
|
395
454
|
},
|
|
396
455
|
isClosing
|
|
397
456
|
? ({ animation: `bloomDialogZoomFadeOut ease-in ${FADE_OUT_DURATION}ms forwards` } as ViewStyle)
|
|
@@ -399,15 +458,56 @@ function DialogPanel({
|
|
|
399
458
|
style,
|
|
400
459
|
]}
|
|
401
460
|
>
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
title
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
461
|
+
{header ? (
|
|
462
|
+
// Nav-header mode: the Dialog OWNS a sticky gradient nav bar + a large
|
|
463
|
+
// collapsing title over its own scroll content (see `DialogHeader`). The
|
|
464
|
+
// bar overlays; the large title + screen body scroll under it, feeding
|
|
465
|
+
// the shared scroll offset that drives the small-title cross-fade.
|
|
466
|
+
<>
|
|
467
|
+
<DialogNavHeader
|
|
468
|
+
controller={headerController}
|
|
469
|
+
header={header}
|
|
470
|
+
onDismiss={close}
|
|
471
|
+
collapse={scrollable !== false}
|
|
472
|
+
/>
|
|
473
|
+
{scrollable === false ? (
|
|
474
|
+
<View style={{ flex: 1, minHeight: 0 }}>
|
|
475
|
+
<View style={{ height: DIALOG_NAV_BAR_HEIGHT }} />
|
|
476
|
+
<DialogHeaderProvider controller={headerController}>{body}</DialogHeaderProvider>
|
|
477
|
+
</View>
|
|
478
|
+
) : (
|
|
479
|
+
<ScrollView
|
|
480
|
+
style={{ flexShrink: 1 }}
|
|
481
|
+
contentContainerStyle={{ paddingBottom: contentPadding }}
|
|
482
|
+
showsVerticalScrollIndicator={false}
|
|
483
|
+
scrollEventThrottle={16}
|
|
484
|
+
onScroll={(e) => {
|
|
485
|
+
headerController.scrollY.value = e.nativeEvent.contentOffset.y;
|
|
486
|
+
}}
|
|
487
|
+
>
|
|
488
|
+
<DialogLargeTitle controller={headerController} header={header} />
|
|
489
|
+
<DialogHeaderProvider controller={headerController}>{body}</DialogHeaderProvider>
|
|
490
|
+
</ScrollView>
|
|
491
|
+
)}
|
|
492
|
+
</>
|
|
493
|
+
) : scrollable === false ? (
|
|
494
|
+
// The content owns its own scrolling (a FlatList / VirtualizedList or a
|
|
495
|
+
// nested ScrollView). Render it bare in a bounded flex box — the card's
|
|
496
|
+
// `maxHeight` + `overflow: hidden` cap it and `flex: 1` + `minHeight: 0`
|
|
497
|
+
// hand the child a bounded height to scroll into — with NO wrapping
|
|
498
|
+
// ScrollView, so a VirtualizedList never nests inside one.
|
|
499
|
+
<View style={{ flex: 1, minHeight: 0, padding: contentPadding }}>
|
|
500
|
+
{body}
|
|
501
|
+
</View>
|
|
502
|
+
) : (
|
|
503
|
+
<ScrollView
|
|
504
|
+
style={{ flexShrink: 1 }}
|
|
505
|
+
contentContainerStyle={{ padding: contentPadding }}
|
|
506
|
+
showsVerticalScrollIndicator={false}
|
|
507
|
+
>
|
|
508
|
+
{body}
|
|
509
|
+
</ScrollView>
|
|
510
|
+
)}
|
|
411
511
|
</View>
|
|
412
512
|
);
|
|
413
513
|
}
|
|
@@ -429,12 +529,16 @@ function SheetSurface({
|
|
|
429
529
|
title,
|
|
430
530
|
description,
|
|
431
531
|
actions,
|
|
532
|
+
header,
|
|
533
|
+
scrollable,
|
|
432
534
|
placement,
|
|
433
535
|
shown,
|
|
434
536
|
width,
|
|
435
537
|
inset,
|
|
436
538
|
dismissOnBackdrop,
|
|
437
539
|
contentPadding,
|
|
540
|
+
backdropZIndex,
|
|
541
|
+
surfaceZIndex,
|
|
438
542
|
onDismiss,
|
|
439
543
|
panelStyle,
|
|
440
544
|
panelClassName,
|
|
@@ -448,12 +552,16 @@ function SheetSurface({
|
|
|
448
552
|
title?: string;
|
|
449
553
|
description?: string;
|
|
450
554
|
actions?: DialogAction[];
|
|
555
|
+
header?: DialogProps['header'];
|
|
556
|
+
scrollable?: boolean;
|
|
451
557
|
placement: 'left' | 'right';
|
|
452
558
|
shown: boolean;
|
|
453
559
|
width: number;
|
|
454
560
|
inset?: DialogInset;
|
|
455
561
|
dismissOnBackdrop: boolean;
|
|
456
562
|
contentPadding: number;
|
|
563
|
+
backdropZIndex: number;
|
|
564
|
+
surfaceZIndex: number;
|
|
457
565
|
onDismiss: () => void;
|
|
458
566
|
panelStyle?: StyleProp<ViewStyle>;
|
|
459
567
|
panelClassName?: string;
|
|
@@ -465,6 +573,7 @@ function SheetSurface({
|
|
|
465
573
|
const theme = useTheme();
|
|
466
574
|
const titleId = useId();
|
|
467
575
|
const descriptionId = useId();
|
|
576
|
+
const headerController = useDialogHeaderController();
|
|
468
577
|
const { width: viewportWidth } = useWindowDimensions();
|
|
469
578
|
|
|
470
579
|
// Defer the entry transition by a frame so the start state paints before the
|
|
@@ -532,7 +641,7 @@ function SheetSurface({
|
|
|
532
641
|
|
|
533
642
|
return (
|
|
534
643
|
<View
|
|
535
|
-
style={[sheetStyles.root, containerStyle]}
|
|
644
|
+
style={[sheetStyles.root, { zIndex: backdropZIndex }, containerStyle]}
|
|
536
645
|
{...(containerClassName ? ({ className: containerClassName } as Record<string, string>) : {})}
|
|
537
646
|
pointerEvents="box-none"
|
|
538
647
|
>
|
|
@@ -562,7 +671,11 @@ function SheetSurface({
|
|
|
562
671
|
{...(panelClassName ? ({ className: panelClassName } as Record<string, string>) : {})}
|
|
563
672
|
style={[
|
|
564
673
|
sheetStyles.panel,
|
|
565
|
-
{
|
|
674
|
+
{
|
|
675
|
+
backgroundColor: theme.colors.background,
|
|
676
|
+
shadowColor: theme.colors.shadow,
|
|
677
|
+
zIndex: surfaceZIndex,
|
|
678
|
+
},
|
|
566
679
|
panelGeometry,
|
|
567
680
|
panelTransition,
|
|
568
681
|
panelStyle,
|
|
@@ -570,17 +683,53 @@ function SheetSurface({
|
|
|
570
683
|
]}
|
|
571
684
|
pointerEvents="auto"
|
|
572
685
|
>
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
686
|
+
{header ? (
|
|
687
|
+
// Nav-header mode on a side drawer: a static titled bar (the drawer
|
|
688
|
+
// body does not own a Dialog scroll offset to drive a collapse) over
|
|
689
|
+
// the content, which is inset below the bar.
|
|
690
|
+
<View style={{ flex: 1, minHeight: 0 }}>
|
|
691
|
+
<DialogNavHeader
|
|
692
|
+
controller={headerController}
|
|
693
|
+
header={header}
|
|
694
|
+
onDismiss={onDismiss}
|
|
695
|
+
collapse={false}
|
|
696
|
+
/>
|
|
697
|
+
<ScrollView
|
|
698
|
+
style={{ flexShrink: 1 }}
|
|
699
|
+
contentContainerStyle={{ paddingTop: DIALOG_NAV_BAR_HEIGHT, paddingBottom: contentPadding }}
|
|
700
|
+
showsVerticalScrollIndicator={false}
|
|
701
|
+
scrollEventThrottle={16}
|
|
702
|
+
scrollEnabled={scrollable !== false}
|
|
703
|
+
onScroll={(e) => {
|
|
704
|
+
headerController.scrollY.value = e.nativeEvent.contentOffset.y;
|
|
705
|
+
}}
|
|
706
|
+
>
|
|
707
|
+
<DialogHeaderProvider controller={headerController}>
|
|
708
|
+
<DialogBody
|
|
709
|
+
titleId={titleId}
|
|
710
|
+
descriptionId={descriptionId}
|
|
711
|
+
title={title}
|
|
712
|
+
description={description}
|
|
713
|
+
actions={actions}
|
|
714
|
+
>
|
|
715
|
+
{children}
|
|
716
|
+
</DialogBody>
|
|
717
|
+
</DialogHeaderProvider>
|
|
718
|
+
</ScrollView>
|
|
719
|
+
</View>
|
|
720
|
+
) : (
|
|
721
|
+
<View style={{ padding: contentPadding }}>
|
|
722
|
+
<DialogBody
|
|
723
|
+
titleId={titleId}
|
|
724
|
+
descriptionId={descriptionId}
|
|
725
|
+
title={title}
|
|
726
|
+
description={description}
|
|
727
|
+
actions={actions}
|
|
728
|
+
>
|
|
729
|
+
{children}
|
|
730
|
+
</DialogBody>
|
|
731
|
+
</View>
|
|
732
|
+
)}
|
|
584
733
|
</View>
|
|
585
734
|
</View>
|
|
586
735
|
);
|
|
@@ -659,13 +808,15 @@ export function AutoMountedDialog({
|
|
|
659
808
|
}
|
|
660
809
|
|
|
661
810
|
const sheetStyles = {
|
|
811
|
+
// `zIndex` for the root (backdrop) and panel (surface) is applied inline from
|
|
812
|
+
// the per-layer `createOverlayZIndex(layer)` in `SheetSurface`, so a side
|
|
813
|
+
// dialog stacked on top of another (surface stack) paints above it.
|
|
662
814
|
root: {
|
|
663
815
|
position: 'fixed' as 'absolute',
|
|
664
816
|
top: 0,
|
|
665
817
|
left: 0,
|
|
666
818
|
right: 0,
|
|
667
819
|
bottom: 0,
|
|
668
|
-
zIndex: dialogZIndex.backdrop,
|
|
669
820
|
} as ViewStyle,
|
|
670
821
|
backdrop: {
|
|
671
822
|
position: 'absolute',
|
|
@@ -681,7 +832,6 @@ const sheetStyles = {
|
|
|
681
832
|
shadowOpacity: 0.18,
|
|
682
833
|
shadowRadius: 24,
|
|
683
834
|
shadowOffset: { width: 0, height: 8 },
|
|
684
|
-
zIndex: dialogZIndex.surface,
|
|
685
835
|
} as ViewStyle,
|
|
686
836
|
};
|
|
687
837
|
|