@oxyhq/bloom 0.16.0 → 0.16.2
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/dialog/Dialog.js +45 -157
- package/lib/commonjs/dialog/Dialog.js.map +1 -1
- package/lib/commonjs/dialog/Dialog.web.js +64 -198
- package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
- package/lib/commonjs/dialog/DialogBottomSheet.js +216 -0
- package/lib/commonjs/dialog/DialogBottomSheet.js.map +1 -0
- package/lib/commonjs/dialog/DialogContent.js +155 -0
- package/lib/commonjs/dialog/DialogContent.js.map +1 -0
- package/lib/commonjs/dialog/placement.js +12 -1
- package/lib/commonjs/dialog/placement.js.map +1 -1
- package/lib/module/dialog/Dialog.js +48 -160
- package/lib/module/dialog/Dialog.js.map +1 -1
- package/lib/module/dialog/Dialog.web.js +66 -200
- package/lib/module/dialog/Dialog.web.js.map +1 -1
- package/lib/module/dialog/DialogBottomSheet.js +211 -0
- package/lib/module/dialog/DialogBottomSheet.js.map +1 -0
- package/lib/module/dialog/DialogContent.js +148 -0
- package/lib/module/dialog/DialogContent.js.map +1 -0
- package/lib/module/dialog/placement.js +11 -0
- package/lib/module/dialog/placement.js.map +1 -1
- package/lib/typescript/commonjs/dialog/Dialog.d.ts +1 -1
- package/lib/typescript/commonjs/dialog/Dialog.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/Dialog.web.d.ts +11 -8
- package/lib/typescript/commonjs/dialog/Dialog.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/DialogBottomSheet.d.ts +29 -0
- package/lib/typescript/commonjs/dialog/DialogBottomSheet.d.ts.map +1 -0
- package/lib/typescript/commonjs/dialog/DialogContent.d.ts +32 -0
- package/lib/typescript/commonjs/dialog/DialogContent.d.ts.map +1 -0
- package/lib/typescript/commonjs/dialog/placement.d.ts +10 -0
- package/lib/typescript/commonjs/dialog/placement.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/types.d.ts +12 -0
- package/lib/typescript/commonjs/dialog/types.d.ts.map +1 -1
- package/lib/typescript/module/dialog/Dialog.d.ts +1 -1
- package/lib/typescript/module/dialog/Dialog.d.ts.map +1 -1
- package/lib/typescript/module/dialog/Dialog.web.d.ts +11 -8
- package/lib/typescript/module/dialog/Dialog.web.d.ts.map +1 -1
- package/lib/typescript/module/dialog/DialogBottomSheet.d.ts +29 -0
- package/lib/typescript/module/dialog/DialogBottomSheet.d.ts.map +1 -0
- package/lib/typescript/module/dialog/DialogContent.d.ts +32 -0
- package/lib/typescript/module/dialog/DialogContent.d.ts.map +1 -0
- package/lib/typescript/module/dialog/placement.d.ts +10 -0
- package/lib/typescript/module/dialog/placement.d.ts.map +1 -1
- package/lib/typescript/module/dialog/types.d.ts +12 -0
- package/lib/typescript/module/dialog/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/BottomSheetGesture.test.tsx +135 -0
- package/src/__tests__/DialogBottomSheet.test.tsx +259 -0
- package/src/dialog/Dialog.tsx +41 -171
- package/src/dialog/Dialog.web.tsx +56 -211
- package/src/dialog/DialogBottomSheet.tsx +250 -0
- package/src/dialog/DialogContent.tsx +157 -0
- package/src/dialog/placement.ts +11 -0
- package/src/dialog/types.ts +12 -0
package/src/dialog/Dialog.tsx
CHANGED
|
@@ -10,11 +10,8 @@ import React, {
|
|
|
10
10
|
import {
|
|
11
11
|
Pressable,
|
|
12
12
|
StyleSheet,
|
|
13
|
-
Text,
|
|
14
|
-
TouchableOpacity,
|
|
15
13
|
useWindowDimensions,
|
|
16
14
|
View,
|
|
17
|
-
type GestureResponderEvent,
|
|
18
15
|
type StyleProp,
|
|
19
16
|
type ViewStyle,
|
|
20
17
|
} from 'react-native';
|
|
@@ -29,12 +26,13 @@ import Animated, {
|
|
|
29
26
|
|
|
30
27
|
import { BottomSheet, type BottomSheetRef } from '../bottom-sheet';
|
|
31
28
|
import { Z_INDEX } from '../styles/z-index';
|
|
32
|
-
import type { ThemeColors } from '../theme/types';
|
|
33
29
|
import { useTheme } from '../theme/use-theme';
|
|
34
|
-
import { Context,
|
|
30
|
+
import { Context, useDialogControl } from './context';
|
|
31
|
+
import { DialogBody } from './DialogContent';
|
|
32
|
+
import { DialogBottomSheet } from './DialogBottomSheet';
|
|
35
33
|
import {
|
|
36
34
|
ANIMATION_DURATION,
|
|
37
|
-
|
|
35
|
+
DEFAULT_DIALOG_CONTENT_PADDING,
|
|
38
36
|
DEFAULT_SIDE_WIDTH,
|
|
39
37
|
DIALOG_SHEET_BACKDROP_TESTID,
|
|
40
38
|
PANEL_RADIUS,
|
|
@@ -44,7 +42,6 @@ import {
|
|
|
44
42
|
} from './placement';
|
|
45
43
|
import type {
|
|
46
44
|
DialogAction,
|
|
47
|
-
DialogActionColor,
|
|
48
45
|
DialogControlProps,
|
|
49
46
|
DialogInset,
|
|
50
47
|
DialogProps,
|
|
@@ -74,6 +71,31 @@ export { DIALOG_SHEET_BACKDROP_TESTID };
|
|
|
74
71
|
* 3. Pure children — no `title`/`description`/`actions`.
|
|
75
72
|
*/
|
|
76
73
|
export function Dialog({
|
|
74
|
+
placement,
|
|
75
|
+
...rest
|
|
76
|
+
}: DialogProps) {
|
|
77
|
+
const resolvedPlacement = useResolvedPlacement(placement);
|
|
78
|
+
|
|
79
|
+
// `bottom` routes to the shared cross-platform `BottomSheet` surface — one
|
|
80
|
+
// code path with drag-to-dismiss on web and native. `center` (default) and
|
|
81
|
+
// the side-sheets keep their existing native lifecycle below. Each branch is
|
|
82
|
+
// its own component so the dispatcher only ever calls `useResolvedPlacement`,
|
|
83
|
+
// keeping the rules-of-hooks contract intact across a responsive placement
|
|
84
|
+
// change.
|
|
85
|
+
if (resolvedPlacement === 'bottom') {
|
|
86
|
+
return <DialogBottomSheet {...rest} />;
|
|
87
|
+
}
|
|
88
|
+
return <CenteredOrSideDialog {...rest} placement={resolvedPlacement} />;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Native `center` and `left`/`right` placements. `center` uses bloom's
|
|
93
|
+
* `BottomSheet` in `detached` mode (a floating, content-hugging card that
|
|
94
|
+
* degrades from sheet to centered card via the 500px cap); the side placements
|
|
95
|
+
* use a reanimated drawer. These share one open/close lifecycle. The `bottom`
|
|
96
|
+
* placement is handled separately by `DialogBottomSheet`.
|
|
97
|
+
*/
|
|
98
|
+
function CenteredOrSideDialog({
|
|
77
99
|
control,
|
|
78
100
|
open: controlledOpen,
|
|
79
101
|
onClose,
|
|
@@ -83,10 +105,9 @@ export function Dialog({
|
|
|
83
105
|
actions,
|
|
84
106
|
placement,
|
|
85
107
|
width = DEFAULT_SIDE_WIDTH,
|
|
86
|
-
maxHeightRatio = DEFAULT_MAX_HEIGHT_RATIO,
|
|
87
108
|
inset,
|
|
88
|
-
showHandle = true,
|
|
89
109
|
dismissOnBackdrop = true,
|
|
110
|
+
contentPadding = DEFAULT_DIALOG_CONTENT_PADDING,
|
|
90
111
|
style,
|
|
91
112
|
panelStyle,
|
|
92
113
|
panelClassName,
|
|
@@ -94,10 +115,9 @@ export function Dialog({
|
|
|
94
115
|
containerClassName,
|
|
95
116
|
label,
|
|
96
117
|
children,
|
|
97
|
-
}: DialogProps) {
|
|
118
|
+
}: Omit<DialogProps, 'placement'> & { placement: 'center' | 'left' | 'right' }) {
|
|
98
119
|
const isControlled = controlledOpen !== undefined;
|
|
99
|
-
const
|
|
100
|
-
const isSide = resolvedPlacement === 'left' || resolvedPlacement === 'right';
|
|
120
|
+
const isSide = placement === 'left' || placement === 'right';
|
|
101
121
|
|
|
102
122
|
// Imperative open state for the side placement (the BottomSheet path owns its
|
|
103
123
|
// own visibility via its ref instead).
|
|
@@ -220,10 +240,11 @@ export function Dialog({
|
|
|
220
240
|
<SideSheet
|
|
221
241
|
open={sideOpen}
|
|
222
242
|
onDismiss={handleDismiss}
|
|
223
|
-
side={
|
|
243
|
+
side={placement}
|
|
224
244
|
width={width}
|
|
225
245
|
inset={inset}
|
|
226
246
|
dismissOnBackdrop={dismissOnBackdrop}
|
|
247
|
+
contentPadding={contentPadding}
|
|
227
248
|
testID={testID}
|
|
228
249
|
label={label}
|
|
229
250
|
title={title}
|
|
@@ -242,19 +263,17 @@ export function Dialog({
|
|
|
242
263
|
);
|
|
243
264
|
}
|
|
244
265
|
|
|
245
|
-
const isBottom = resolvedPlacement === 'bottom';
|
|
246
|
-
|
|
247
266
|
return (
|
|
248
267
|
<BottomSheet
|
|
249
268
|
ref={ref}
|
|
250
269
|
onDismiss={handleDismiss}
|
|
251
270
|
enablePanDownToClose
|
|
252
|
-
detached
|
|
253
|
-
showHandle
|
|
271
|
+
detached
|
|
272
|
+
showHandle
|
|
254
273
|
// Stronger dim when a Dialog is stacked over another sheet so the
|
|
255
274
|
// underlying sheet's handle/content doesn't bleed through.
|
|
256
275
|
backdropOpacity={0.7}
|
|
257
|
-
style={
|
|
276
|
+
style={sheetStyle}
|
|
258
277
|
>
|
|
259
278
|
<Context.Provider value={context}>
|
|
260
279
|
<View
|
|
@@ -267,7 +286,7 @@ export function Dialog({
|
|
|
267
286
|
// Detached BottomSheet already adds `marginBottom: insets.bottom + 16`
|
|
268
287
|
// to the sheet container — the floating card sits ABOVE the
|
|
269
288
|
// system gesture bar, so we don't add `insets.bottom` here.
|
|
270
|
-
{
|
|
289
|
+
{ padding: contentPadding },
|
|
271
290
|
{ backgroundColor: theme.colors.background },
|
|
272
291
|
style,
|
|
273
292
|
panelStyle,
|
|
@@ -280,71 +299,6 @@ export function Dialog({
|
|
|
280
299
|
);
|
|
281
300
|
}
|
|
282
301
|
|
|
283
|
-
function bottomSheetStyle(background: string, maxHeightRatio: number): ViewStyle {
|
|
284
|
-
return {
|
|
285
|
-
backgroundColor: background,
|
|
286
|
-
borderTopLeftRadius: PANEL_RADIUS + 4,
|
|
287
|
-
borderTopRightRadius: PANEL_RADIUS + 4,
|
|
288
|
-
maxHeight: `${Math.round(maxHeightRatio * 100)}%`,
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Renders the dialog's body: optional declarative title + description, any
|
|
294
|
-
* `children`, then the action row. Shared by all placements. The
|
|
295
|
-
* `titleId`/`descriptionId` are wired by the caller for accessibility.
|
|
296
|
-
*/
|
|
297
|
-
function DialogBody({
|
|
298
|
-
titleId,
|
|
299
|
-
descriptionId,
|
|
300
|
-
title,
|
|
301
|
-
description,
|
|
302
|
-
actions,
|
|
303
|
-
children,
|
|
304
|
-
}: {
|
|
305
|
-
titleId: string;
|
|
306
|
-
descriptionId: string;
|
|
307
|
-
title?: string;
|
|
308
|
-
description?: string;
|
|
309
|
-
actions?: DialogAction[];
|
|
310
|
-
children?: React.ReactNode;
|
|
311
|
-
}) {
|
|
312
|
-
const theme = useTheme();
|
|
313
|
-
return (
|
|
314
|
-
<>
|
|
315
|
-
{title ? (
|
|
316
|
-
<Text
|
|
317
|
-
nativeID={titleId}
|
|
318
|
-
style={{
|
|
319
|
-
fontSize: 22,
|
|
320
|
-
fontWeight: '600',
|
|
321
|
-
color: theme.colors.text,
|
|
322
|
-
paddingBottom: description ? 4 : 16,
|
|
323
|
-
lineHeight: 30,
|
|
324
|
-
}}
|
|
325
|
-
>
|
|
326
|
-
{title}
|
|
327
|
-
</Text>
|
|
328
|
-
) : null}
|
|
329
|
-
{description ? (
|
|
330
|
-
<Text
|
|
331
|
-
nativeID={descriptionId}
|
|
332
|
-
style={{
|
|
333
|
-
fontSize: 16,
|
|
334
|
-
color: theme.colors.textSecondary,
|
|
335
|
-
paddingBottom: 16,
|
|
336
|
-
lineHeight: 22,
|
|
337
|
-
}}
|
|
338
|
-
>
|
|
339
|
-
{description}
|
|
340
|
-
</Text>
|
|
341
|
-
) : null}
|
|
342
|
-
{children}
|
|
343
|
-
{actions && actions.length > 0 ? <ActionRow actions={actions} /> : null}
|
|
344
|
-
</>
|
|
345
|
-
);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
302
|
/**
|
|
349
303
|
* Reanimated side-sheet/drawer for the `left`/`right` placements (native).
|
|
350
304
|
*
|
|
@@ -360,6 +314,7 @@ function SideSheet({
|
|
|
360
314
|
width,
|
|
361
315
|
inset,
|
|
362
316
|
dismissOnBackdrop,
|
|
317
|
+
contentPadding,
|
|
363
318
|
testID,
|
|
364
319
|
label,
|
|
365
320
|
title,
|
|
@@ -379,6 +334,7 @@ function SideSheet({
|
|
|
379
334
|
width: number;
|
|
380
335
|
inset?: DialogInset;
|
|
381
336
|
dismissOnBackdrop: boolean;
|
|
337
|
+
contentPadding: number;
|
|
382
338
|
testID?: string;
|
|
383
339
|
label?: string;
|
|
384
340
|
title?: string;
|
|
@@ -510,95 +466,12 @@ function SideSheet({
|
|
|
510
466
|
]}
|
|
511
467
|
pointerEvents="auto"
|
|
512
468
|
>
|
|
513
|
-
<View style={
|
|
469
|
+
<View style={{ padding: contentPadding }}>{children}</View>
|
|
514
470
|
</Animated.View>
|
|
515
471
|
</View>
|
|
516
472
|
);
|
|
517
473
|
}
|
|
518
474
|
|
|
519
|
-
function ActionRow({ actions }: { actions: DialogAction[] }) {
|
|
520
|
-
return (
|
|
521
|
-
<View style={{ width: '100%', gap: 8, justifyContent: 'flex-end' }}>
|
|
522
|
-
{actions.map((action, idx) => (
|
|
523
|
-
<ActionButton
|
|
524
|
-
key={`${action.label}-${idx}`}
|
|
525
|
-
action={action}
|
|
526
|
-
/>
|
|
527
|
-
))}
|
|
528
|
-
</View>
|
|
529
|
-
);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
function ActionButton({ action }: { action: DialogAction }) {
|
|
533
|
-
const { close } = useDialogContext();
|
|
534
|
-
const theme = useTheme();
|
|
535
|
-
const color: DialogActionColor = action.color ?? 'default';
|
|
536
|
-
const shouldCloseOnPress = action.shouldCloseOnPress ?? true;
|
|
537
|
-
|
|
538
|
-
const { background, foreground } = getActionPalette(color, theme.colors);
|
|
539
|
-
|
|
540
|
-
const handlePress = useCallback(
|
|
541
|
-
(e: GestureResponderEvent) => {
|
|
542
|
-
const onPress = action.onPress;
|
|
543
|
-
if (color === 'cancel') {
|
|
544
|
-
// Cancel always dismisses; consumer's onPress (rare) runs after.
|
|
545
|
-
close(onPress ? () => onPress(e) : undefined);
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
if (shouldCloseOnPress) {
|
|
549
|
-
close(onPress ? () => onPress(e) : undefined);
|
|
550
|
-
} else {
|
|
551
|
-
onPress?.(e);
|
|
552
|
-
}
|
|
553
|
-
},
|
|
554
|
-
[action.onPress, close, color, shouldCloseOnPress],
|
|
555
|
-
);
|
|
556
|
-
|
|
557
|
-
return (
|
|
558
|
-
<TouchableOpacity
|
|
559
|
-
style={{
|
|
560
|
-
borderRadius: 9999,
|
|
561
|
-
alignItems: 'center',
|
|
562
|
-
justifyContent: 'center',
|
|
563
|
-
backgroundColor: background,
|
|
564
|
-
opacity: action.disabled ? 0.5 : 1,
|
|
565
|
-
paddingVertical: 12,
|
|
566
|
-
paddingHorizontal: 24,
|
|
567
|
-
}}
|
|
568
|
-
onPress={handlePress}
|
|
569
|
-
disabled={action.disabled}
|
|
570
|
-
activeOpacity={0.7}
|
|
571
|
-
testID={action.testID}
|
|
572
|
-
>
|
|
573
|
-
<Text style={{ fontSize: 16, fontWeight: '500', color: foreground }}>
|
|
574
|
-
{action.label}
|
|
575
|
-
</Text>
|
|
576
|
-
</TouchableOpacity>
|
|
577
|
-
);
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
function getActionPalette(
|
|
581
|
-
color: DialogActionColor,
|
|
582
|
-
colors: ThemeColors,
|
|
583
|
-
): { background: string; foreground: string } {
|
|
584
|
-
switch (color) {
|
|
585
|
-
case 'destructive':
|
|
586
|
-
return {
|
|
587
|
-
background: colors.negative,
|
|
588
|
-
foreground: colors.negativeForeground,
|
|
589
|
-
};
|
|
590
|
-
case 'cancel':
|
|
591
|
-
return { background: colors.contrast50, foreground: colors.text };
|
|
592
|
-
case 'default':
|
|
593
|
-
return { background: colors.primary, foreground: colors.primaryForeground };
|
|
594
|
-
/* c8 ignore next 2 -- TS exhaustiveness check guards this branch */
|
|
595
|
-
default: {
|
|
596
|
-
const _exhaustive: never = color;
|
|
597
|
-
return { background: colors.primary, foreground: colors.primaryForeground };
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
475
|
const sideStyles = StyleSheet.create({
|
|
603
476
|
root: {
|
|
604
477
|
...StyleSheet.absoluteFill,
|
|
@@ -616,9 +489,6 @@ const sideStyles = StyleSheet.create({
|
|
|
616
489
|
shadowOffset: { width: 0, height: 8 },
|
|
617
490
|
elevation: 12,
|
|
618
491
|
},
|
|
619
|
-
body: {
|
|
620
|
-
padding: 20,
|
|
621
|
-
},
|
|
622
492
|
});
|
|
623
493
|
|
|
624
494
|
/**
|