@oxyhq/bloom 0.14.0 → 0.16.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/alert-dialog/AlertDialog.js +35 -27
- package/lib/commonjs/alert-dialog/AlertDialog.js.map +1 -1
- package/lib/commonjs/alert-dialog/index.js +1 -1
- package/lib/commonjs/alert-dialog/index.js.map +1 -1
- package/lib/commonjs/alert-dialog/index.web.js +1 -1
- package/lib/commonjs/alert-dialog/index.web.js.map +1 -1
- package/lib/commonjs/command/Command.js +27 -16
- package/lib/commonjs/command/Command.js.map +1 -1
- package/lib/commonjs/command/index.js +1 -1
- package/lib/commonjs/command/index.js.map +1 -1
- package/lib/commonjs/command/index.web.js +1 -1
- package/lib/commonjs/command/index.web.js.map +1 -1
- package/lib/commonjs/dialog/Dialog.js +336 -43
- package/lib/commonjs/dialog/Dialog.js.map +1 -1
- package/lib/commonjs/dialog/Dialog.web.js +420 -66
- package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
- package/lib/commonjs/dialog/index.js +2 -9
- package/lib/commonjs/dialog/index.js.map +1 -1
- package/lib/commonjs/dialog/index.web.js +2 -15
- package/lib/commonjs/dialog/index.web.js.map +1 -1
- package/lib/commonjs/dialog/placement.js +104 -0
- package/lib/commonjs/dialog/placement.js.map +1 -0
- package/lib/commonjs/index.js +7 -29
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +13 -35
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/module/alert-dialog/AlertDialog.js +35 -27
- package/lib/module/alert-dialog/AlertDialog.js.map +1 -1
- package/lib/module/alert-dialog/index.js +2 -2
- package/lib/module/alert-dialog/index.js.map +1 -1
- package/lib/module/alert-dialog/index.web.js +2 -2
- package/lib/module/alert-dialog/index.web.js.map +1 -1
- package/lib/module/command/Command.js +27 -16
- package/lib/module/command/Command.js.map +1 -1
- package/lib/module/command/index.js +2 -2
- package/lib/module/command/index.js.map +1 -1
- package/lib/module/command/index.web.js +2 -2
- package/lib/module/command/index.web.js.map +1 -1
- package/lib/module/dialog/Dialog.js +335 -46
- package/lib/module/dialog/Dialog.js.map +1 -1
- package/lib/module/dialog/Dialog.web.js +418 -68
- package/lib/module/dialog/Dialog.web.js.map +1 -1
- package/lib/module/dialog/index.js +1 -2
- package/lib/module/dialog/index.js.map +1 -1
- package/lib/module/dialog/index.web.js +1 -2
- package/lib/module/dialog/index.web.js.map +1 -1
- package/lib/module/dialog/placement.js +100 -0
- package/lib/module/dialog/placement.js.map +1 -0
- package/lib/module/index.js +1 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +1 -3
- package/lib/module/index.web.js.map +1 -1
- package/lib/typescript/commonjs/alert-dialog/AlertDialog.d.ts +13 -12
- package/lib/typescript/commonjs/alert-dialog/AlertDialog.d.ts.map +1 -1
- package/lib/typescript/commonjs/alert-dialog/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/alert-dialog/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/command/Command.d.ts +8 -8
- package/lib/typescript/commonjs/command/Command.d.ts.map +1 -1
- package/lib/typescript/commonjs/command/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/command/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/Dialog.d.ts +17 -15
- package/lib/typescript/commonjs/dialog/Dialog.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/Dialog.web.d.ts +18 -5
- package/lib/typescript/commonjs/dialog/Dialog.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/index.d.ts +2 -4
- package/lib/typescript/commonjs/dialog/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/index.web.d.ts +2 -4
- package/lib/typescript/commonjs/dialog/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/placement.d.ts +71 -0
- package/lib/typescript/commonjs/dialog/placement.d.ts.map +1 -0
- package/lib/typescript/commonjs/dialog/types.d.ts +66 -3
- package/lib/typescript/commonjs/dialog/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +2 -4
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +2 -4
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/module/alert-dialog/AlertDialog.d.ts +13 -12
- package/lib/typescript/module/alert-dialog/AlertDialog.d.ts.map +1 -1
- package/lib/typescript/module/alert-dialog/index.d.ts.map +1 -1
- package/lib/typescript/module/alert-dialog/index.web.d.ts.map +1 -1
- package/lib/typescript/module/command/Command.d.ts +8 -8
- package/lib/typescript/module/command/Command.d.ts.map +1 -1
- package/lib/typescript/module/command/index.d.ts.map +1 -1
- package/lib/typescript/module/command/index.web.d.ts.map +1 -1
- package/lib/typescript/module/dialog/Dialog.d.ts +17 -15
- package/lib/typescript/module/dialog/Dialog.d.ts.map +1 -1
- package/lib/typescript/module/dialog/Dialog.web.d.ts +18 -5
- package/lib/typescript/module/dialog/Dialog.web.d.ts.map +1 -1
- package/lib/typescript/module/dialog/index.d.ts +2 -4
- package/lib/typescript/module/dialog/index.d.ts.map +1 -1
- package/lib/typescript/module/dialog/index.web.d.ts +2 -4
- package/lib/typescript/module/dialog/index.web.d.ts.map +1 -1
- package/lib/typescript/module/dialog/placement.d.ts +71 -0
- package/lib/typescript/module/dialog/placement.d.ts.map +1 -0
- package/lib/typescript/module/dialog/types.d.ts +66 -3
- package/lib/typescript/module/dialog/types.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +2 -4
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +2 -4
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/package.json +1 -18
- package/src/__tests__/AlertDialog.test.tsx +122 -0
- package/src/__tests__/Command.test.tsx +107 -0
- package/src/__tests__/DialogPlacement.test.tsx +144 -0
- package/src/alert-dialog/AlertDialog.tsx +46 -36
- package/src/alert-dialog/index.ts +2 -2
- package/src/alert-dialog/index.web.ts +2 -2
- package/src/command/Command.tsx +30 -19
- package/src/command/index.ts +2 -2
- package/src/command/index.web.ts +2 -2
- package/src/dialog/Dialog.tsx +406 -50
- package/src/dialog/Dialog.web.tsx +496 -78
- package/src/dialog/index.ts +4 -3
- package/src/dialog/index.web.ts +4 -7
- package/src/dialog/placement.ts +113 -0
- package/src/dialog/types.ts +69 -3
- package/src/index.ts +4 -11
- package/src/index.web.ts +4 -11
- package/lib/commonjs/dialog/CenteredDialog.js +0 -250
- package/lib/commonjs/dialog/CenteredDialog.js.map +0 -1
- package/lib/commonjs/dialog/CenteredDialog.web.js +0 -328
- package/lib/commonjs/dialog/CenteredDialog.web.js.map +0 -1
- package/lib/commonjs/dialog/centered-dialog-tokens.js +0 -62
- package/lib/commonjs/dialog/centered-dialog-tokens.js.map +0 -1
- package/lib/commonjs/dialog/centered-dialog-types.js +0 -6
- package/lib/commonjs/dialog/centered-dialog-types.js.map +0 -1
- package/lib/commonjs/responsive-sheet/ResponsiveSheet.js +0 -300
- package/lib/commonjs/responsive-sheet/ResponsiveSheet.js.map +0 -1
- package/lib/commonjs/responsive-sheet/ResponsiveSheet.native.js +0 -226
- package/lib/commonjs/responsive-sheet/ResponsiveSheet.native.js.map +0 -1
- package/lib/commonjs/responsive-sheet/const.js +0 -60
- package/lib/commonjs/responsive-sheet/const.js.map +0 -1
- package/lib/commonjs/responsive-sheet/index.js +0 -19
- package/lib/commonjs/responsive-sheet/index.js.map +0 -1
- package/lib/commonjs/responsive-sheet/index.web.js +0 -19
- package/lib/commonjs/responsive-sheet/index.web.js.map +0 -1
- package/lib/commonjs/responsive-sheet/types.js +0 -6
- package/lib/commonjs/responsive-sheet/types.js.map +0 -1
- package/lib/module/dialog/CenteredDialog.js +0 -244
- package/lib/module/dialog/CenteredDialog.js.map +0 -1
- package/lib/module/dialog/CenteredDialog.web.js +0 -322
- package/lib/module/dialog/CenteredDialog.web.js.map +0 -1
- package/lib/module/dialog/centered-dialog-tokens.js +0 -58
- package/lib/module/dialog/centered-dialog-tokens.js.map +0 -1
- package/lib/module/dialog/centered-dialog-types.js +0 -4
- package/lib/module/dialog/centered-dialog-types.js.map +0 -1
- package/lib/module/responsive-sheet/ResponsiveSheet.js +0 -293
- package/lib/module/responsive-sheet/ResponsiveSheet.js.map +0 -1
- package/lib/module/responsive-sheet/ResponsiveSheet.native.js +0 -219
- package/lib/module/responsive-sheet/ResponsiveSheet.native.js.map +0 -1
- package/lib/module/responsive-sheet/const.js +0 -56
- package/lib/module/responsive-sheet/const.js.map +0 -1
- package/lib/module/responsive-sheet/index.js +0 -4
- package/lib/module/responsive-sheet/index.js.map +0 -1
- package/lib/module/responsive-sheet/index.web.js +0 -14
- package/lib/module/responsive-sheet/index.web.js.map +0 -1
- package/lib/module/responsive-sheet/types.js +0 -4
- package/lib/module/responsive-sheet/types.js.map +0 -1
- package/lib/typescript/commonjs/dialog/CenteredDialog.d.ts +0 -20
- package/lib/typescript/commonjs/dialog/CenteredDialog.d.ts.map +0 -1
- package/lib/typescript/commonjs/dialog/CenteredDialog.web.d.ts +0 -29
- package/lib/typescript/commonjs/dialog/CenteredDialog.web.d.ts.map +0 -1
- package/lib/typescript/commonjs/dialog/centered-dialog-tokens.d.ts +0 -47
- package/lib/typescript/commonjs/dialog/centered-dialog-tokens.d.ts.map +0 -1
- package/lib/typescript/commonjs/dialog/centered-dialog-types.d.ts +0 -70
- package/lib/typescript/commonjs/dialog/centered-dialog-types.d.ts.map +0 -1
- package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.d.ts +0 -26
- package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.d.ts.map +0 -1
- package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.native.d.ts +0 -20
- package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.native.d.ts.map +0 -1
- package/lib/typescript/commonjs/responsive-sheet/const.d.ts +0 -43
- package/lib/typescript/commonjs/responsive-sheet/const.d.ts.map +0 -1
- package/lib/typescript/commonjs/responsive-sheet/index.d.ts +0 -3
- package/lib/typescript/commonjs/responsive-sheet/index.d.ts.map +0 -1
- package/lib/typescript/commonjs/responsive-sheet/index.web.d.ts +0 -3
- package/lib/typescript/commonjs/responsive-sheet/index.web.d.ts.map +0 -1
- package/lib/typescript/commonjs/responsive-sheet/types.d.ts +0 -55
- package/lib/typescript/commonjs/responsive-sheet/types.d.ts.map +0 -1
- package/lib/typescript/module/dialog/CenteredDialog.d.ts +0 -20
- package/lib/typescript/module/dialog/CenteredDialog.d.ts.map +0 -1
- package/lib/typescript/module/dialog/CenteredDialog.web.d.ts +0 -29
- package/lib/typescript/module/dialog/CenteredDialog.web.d.ts.map +0 -1
- package/lib/typescript/module/dialog/centered-dialog-tokens.d.ts +0 -47
- package/lib/typescript/module/dialog/centered-dialog-tokens.d.ts.map +0 -1
- package/lib/typescript/module/dialog/centered-dialog-types.d.ts +0 -70
- package/lib/typescript/module/dialog/centered-dialog-types.d.ts.map +0 -1
- package/lib/typescript/module/responsive-sheet/ResponsiveSheet.d.ts +0 -26
- package/lib/typescript/module/responsive-sheet/ResponsiveSheet.d.ts.map +0 -1
- package/lib/typescript/module/responsive-sheet/ResponsiveSheet.native.d.ts +0 -20
- package/lib/typescript/module/responsive-sheet/ResponsiveSheet.native.d.ts.map +0 -1
- package/lib/typescript/module/responsive-sheet/const.d.ts +0 -43
- package/lib/typescript/module/responsive-sheet/const.d.ts.map +0 -1
- package/lib/typescript/module/responsive-sheet/index.d.ts +0 -3
- package/lib/typescript/module/responsive-sheet/index.d.ts.map +0 -1
- package/lib/typescript/module/responsive-sheet/index.web.d.ts +0 -3
- package/lib/typescript/module/responsive-sheet/index.web.d.ts.map +0 -1
- package/lib/typescript/module/responsive-sheet/types.d.ts +0 -55
- package/lib/typescript/module/responsive-sheet/types.d.ts.map +0 -1
- package/src/__tests__/CenteredDialog.test.tsx +0 -108
- package/src/__tests__/ResponsiveSheet.test.tsx +0 -104
- package/src/dialog/CenteredDialog.stories.tsx +0 -125
- package/src/dialog/CenteredDialog.tsx +0 -303
- package/src/dialog/CenteredDialog.web.tsx +0 -372
- package/src/dialog/centered-dialog-tokens.ts +0 -68
- package/src/dialog/centered-dialog-types.ts +0 -70
- package/src/responsive-sheet/ResponsiveSheet.native.tsx +0 -257
- package/src/responsive-sheet/ResponsiveSheet.stories.tsx +0 -54
- package/src/responsive-sheet/ResponsiveSheet.tsx +0 -327
- package/src/responsive-sheet/const.ts +0 -53
- package/src/responsive-sheet/index.ts +0 -6
- package/src/responsive-sheet/index.web.ts +0 -16
- package/src/responsive-sheet/types.ts +0 -57
package/src/dialog/Dialog.tsx
CHANGED
|
@@ -5,63 +5,118 @@ import React, {
|
|
|
5
5
|
useImperativeHandle,
|
|
6
6
|
useMemo,
|
|
7
7
|
useRef,
|
|
8
|
+
useState,
|
|
8
9
|
} from 'react';
|
|
9
10
|
import {
|
|
11
|
+
Pressable,
|
|
12
|
+
StyleSheet,
|
|
10
13
|
Text,
|
|
11
14
|
TouchableOpacity,
|
|
15
|
+
useWindowDimensions,
|
|
12
16
|
View,
|
|
13
17
|
type GestureResponderEvent,
|
|
18
|
+
type StyleProp,
|
|
19
|
+
type ViewStyle,
|
|
14
20
|
} from 'react-native';
|
|
21
|
+
import Animated, {
|
|
22
|
+
cancelAnimation,
|
|
23
|
+
Easing,
|
|
24
|
+
runOnJS,
|
|
25
|
+
useAnimatedStyle,
|
|
26
|
+
useSharedValue,
|
|
27
|
+
withTiming,
|
|
28
|
+
} from 'react-native-reanimated';
|
|
15
29
|
|
|
16
30
|
import { BottomSheet, type BottomSheetRef } from '../bottom-sheet';
|
|
31
|
+
import { Z_INDEX } from '../styles/z-index';
|
|
17
32
|
import type { ThemeColors } from '../theme/types';
|
|
18
33
|
import { useTheme } from '../theme/use-theme';
|
|
19
34
|
import { Context, useDialogContext, useDialogControl } from './context';
|
|
35
|
+
import {
|
|
36
|
+
ANIMATION_DURATION,
|
|
37
|
+
DEFAULT_MAX_HEIGHT_RATIO,
|
|
38
|
+
DEFAULT_SIDE_WIDTH,
|
|
39
|
+
DIALOG_SHEET_BACKDROP_TESTID,
|
|
40
|
+
PANEL_RADIUS,
|
|
41
|
+
SHEET_BACKDROP_OPACITY,
|
|
42
|
+
SIDE_SHEET_MIN_GUTTER,
|
|
43
|
+
useResolvedPlacement,
|
|
44
|
+
} from './placement';
|
|
20
45
|
import type {
|
|
21
46
|
DialogAction,
|
|
22
47
|
DialogActionColor,
|
|
23
48
|
DialogControlProps,
|
|
49
|
+
DialogInset,
|
|
24
50
|
DialogProps,
|
|
25
51
|
} from './types';
|
|
26
52
|
|
|
53
|
+
export { DIALOG_SHEET_BACKDROP_TESTID };
|
|
54
|
+
|
|
27
55
|
/**
|
|
28
56
|
* Native variant of `<Dialog>`.
|
|
29
57
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* gracefully degrades from sheet (phone) to centered card (tablet, via the
|
|
33
|
-
* 500px max-width cap).
|
|
58
|
+
* Open/close is driven by EITHER the imperative `control` (legacy) OR the
|
|
59
|
+
* controlled `open` prop. When `open` is provided it wins.
|
|
34
60
|
*
|
|
35
|
-
*
|
|
61
|
+
* `placement` selects the surface:
|
|
36
62
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
63
|
+
* - `'center'` (default) / `'bottom'` — bloom's `BottomSheet` (`detached`
|
|
64
|
+
* for center: a floating, content-hugging card that degrades from sheet to
|
|
65
|
+
* centered card via the 500px cap; flush for bottom: a full-width sheet
|
|
66
|
+
* with a drag handle). Default behavior is unchanged.
|
|
67
|
+
* - `'left'` / `'right'` — an anchored side-sheet/drawer driven by
|
|
68
|
+
* reanimated. Exit animations are safe on native.
|
|
43
69
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
70
|
+
* The component accepts three rendering modes simultaneously:
|
|
71
|
+
*
|
|
72
|
+
* 1. Declarative — `title`, `description`, `actions`.
|
|
73
|
+
* 2. Custom children — caller passes JSX, bloom renders the chrome.
|
|
74
|
+
* 3. Pure children — no `title`/`description`/`actions`.
|
|
47
75
|
*/
|
|
48
76
|
export function Dialog({
|
|
49
77
|
control,
|
|
78
|
+
open: controlledOpen,
|
|
50
79
|
onClose,
|
|
51
80
|
testID,
|
|
52
81
|
title,
|
|
53
82
|
description,
|
|
54
83
|
actions,
|
|
84
|
+
placement,
|
|
85
|
+
width = DEFAULT_SIDE_WIDTH,
|
|
86
|
+
maxHeightRatio = DEFAULT_MAX_HEIGHT_RATIO,
|
|
87
|
+
inset,
|
|
88
|
+
showHandle = true,
|
|
89
|
+
dismissOnBackdrop = true,
|
|
55
90
|
style,
|
|
91
|
+
panelStyle,
|
|
92
|
+
panelClassName,
|
|
93
|
+
containerStyle,
|
|
94
|
+
containerClassName,
|
|
56
95
|
label,
|
|
57
96
|
children,
|
|
58
97
|
}: DialogProps) {
|
|
98
|
+
const isControlled = controlledOpen !== undefined;
|
|
99
|
+
const resolvedPlacement = useResolvedPlacement(placement);
|
|
100
|
+
const isSide = resolvedPlacement === 'left' || resolvedPlacement === 'right';
|
|
101
|
+
|
|
102
|
+
// Imperative open state for the side placement (the BottomSheet path owns its
|
|
103
|
+
// own visibility via its ref instead).
|
|
104
|
+
const [sideOpen, setSideOpen] = useState(false);
|
|
105
|
+
|
|
59
106
|
const theme = useTheme();
|
|
60
107
|
const ref = useRef<BottomSheetRef>(null);
|
|
61
108
|
const closeCallbacks = useRef<(() => void)[]>([]);
|
|
62
109
|
const titleId = useId();
|
|
63
110
|
const descriptionId = useId();
|
|
64
111
|
|
|
112
|
+
// Read the latest controlled flag / `onClose` inside stable callbacks
|
|
113
|
+
// without re-binding them (the context + imperative handle depend on `close`
|
|
114
|
+
// staying referentially stable).
|
|
115
|
+
const isControlledRef = useRef(isControlled);
|
|
116
|
+
isControlledRef.current = isControlled;
|
|
117
|
+
const onCloseRef = useRef(onClose);
|
|
118
|
+
onCloseRef.current = onClose;
|
|
119
|
+
|
|
65
120
|
// Drain queued close callbacks atomically — capturing the list and
|
|
66
121
|
// resetting it before invocation ensures a callback that synchronously
|
|
67
122
|
// re-opens the dialog (and queues fresh callbacks) does not see the old
|
|
@@ -81,23 +136,51 @@ export function Dialog({
|
|
|
81
136
|
}, []);
|
|
82
137
|
|
|
83
138
|
const open = useCallback(() => {
|
|
139
|
+
setSideOpen(true);
|
|
84
140
|
ref.current?.present();
|
|
85
141
|
}, []);
|
|
86
142
|
|
|
143
|
+
// A dismissal request (backdrop, pan-to-close, action button). In controlled
|
|
144
|
+
// mode it asks the host to close via `onClose` (the host flips `open`); in
|
|
145
|
+
// imperative mode it dismisses the surface directly. The optional callback
|
|
146
|
+
// runs once the dialog has finished closing.
|
|
87
147
|
const close = useCallback<DialogControlProps['close']>((cb) => {
|
|
88
148
|
if (typeof cb === 'function') {
|
|
89
149
|
closeCallbacks.current.push(cb);
|
|
90
150
|
}
|
|
151
|
+
if (isControlledRef.current) {
|
|
152
|
+
onCloseRef.current?.();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
setSideOpen(false);
|
|
91
156
|
ref.current?.dismiss();
|
|
92
157
|
}, []);
|
|
93
158
|
|
|
159
|
+
// Fired when the underlying surface has finished closing. Drains queued
|
|
160
|
+
// callbacks, then fires `onClose` ONLY in imperative mode — controlled mode
|
|
161
|
+
// already requested the close through `onClose` (the host then flipped
|
|
162
|
+
// `open`), so firing it again here would double-call it.
|
|
94
163
|
const handleDismiss = useCallback(() => {
|
|
95
164
|
callQueuedCallbacks();
|
|
96
|
-
|
|
97
|
-
}, [callQueuedCallbacks
|
|
165
|
+
if (!isControlledRef.current) onCloseRef.current?.();
|
|
166
|
+
}, [callQueuedCallbacks]);
|
|
167
|
+
|
|
168
|
+
// In controlled mode, mirror the `open` prop onto the underlying surface so
|
|
169
|
+
// both modes share one lifecycle. The BottomSheet path presents/dismisses by
|
|
170
|
+
// ref; the side path flips `sideOpen`.
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (!isControlled) return;
|
|
173
|
+
if (controlledOpen) {
|
|
174
|
+
setSideOpen(true);
|
|
175
|
+
ref.current?.present();
|
|
176
|
+
} else {
|
|
177
|
+
setSideOpen(false);
|
|
178
|
+
ref.current?.dismiss();
|
|
179
|
+
}
|
|
180
|
+
}, [isControlled, controlledOpen]);
|
|
98
181
|
|
|
99
182
|
useImperativeHandle(
|
|
100
|
-
control
|
|
183
|
+
control?.ref,
|
|
101
184
|
() => ({ open, close }),
|
|
102
185
|
[open, close],
|
|
103
186
|
);
|
|
@@ -119,16 +202,59 @@ export function Dialog({
|
|
|
119
202
|
[theme.colors.background],
|
|
120
203
|
);
|
|
121
204
|
|
|
205
|
+
const bodyNode = (
|
|
206
|
+
<DialogBody
|
|
207
|
+
titleId={titleId}
|
|
208
|
+
descriptionId={descriptionId}
|
|
209
|
+
title={title}
|
|
210
|
+
description={description}
|
|
211
|
+
actions={actions}
|
|
212
|
+
>
|
|
213
|
+
{children}
|
|
214
|
+
</DialogBody>
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
if (isSide) {
|
|
218
|
+
return (
|
|
219
|
+
<Context.Provider value={context}>
|
|
220
|
+
<SideSheet
|
|
221
|
+
open={sideOpen}
|
|
222
|
+
onDismiss={handleDismiss}
|
|
223
|
+
side={resolvedPlacement}
|
|
224
|
+
width={width}
|
|
225
|
+
inset={inset}
|
|
226
|
+
dismissOnBackdrop={dismissOnBackdrop}
|
|
227
|
+
testID={testID}
|
|
228
|
+
label={label}
|
|
229
|
+
title={title}
|
|
230
|
+
description={description}
|
|
231
|
+
titleId={titleId}
|
|
232
|
+
descriptionId={descriptionId}
|
|
233
|
+
panelStyle={panelStyle}
|
|
234
|
+
panelClassName={panelClassName}
|
|
235
|
+
containerStyle={containerStyle}
|
|
236
|
+
containerClassName={containerClassName}
|
|
237
|
+
style={style}
|
|
238
|
+
>
|
|
239
|
+
{bodyNode}
|
|
240
|
+
</SideSheet>
|
|
241
|
+
</Context.Provider>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const isBottom = resolvedPlacement === 'bottom';
|
|
246
|
+
|
|
122
247
|
return (
|
|
123
248
|
<BottomSheet
|
|
124
249
|
ref={ref}
|
|
125
250
|
onDismiss={handleDismiss}
|
|
126
251
|
enablePanDownToClose
|
|
127
|
-
detached
|
|
252
|
+
detached={!isBottom}
|
|
253
|
+
showHandle={isBottom ? showHandle : true}
|
|
128
254
|
// Stronger dim when a Dialog is stacked over another sheet so the
|
|
129
255
|
// underlying sheet's handle/content doesn't bleed through.
|
|
130
256
|
backdropOpacity={0.7}
|
|
131
|
-
style={sheetStyle}
|
|
257
|
+
style={isBottom ? bottomSheetStyle(theme.colors.background, maxHeightRatio) : sheetStyle}
|
|
132
258
|
>
|
|
133
259
|
<Context.Provider value={context}>
|
|
134
260
|
<View
|
|
@@ -136,6 +262,7 @@ export function Dialog({
|
|
|
136
262
|
accessibilityLabel={label}
|
|
137
263
|
aria-labelledby={title ? titleId : undefined}
|
|
138
264
|
aria-describedby={description ? descriptionId : undefined}
|
|
265
|
+
{...(panelClassName ? ({ className: panelClassName } as Record<string, string>) : {})}
|
|
139
266
|
style={[
|
|
140
267
|
// Detached BottomSheet already adds `marginBottom: insets.bottom + 16`
|
|
141
268
|
// to the sheet container — the floating card sits ABOVE the
|
|
@@ -143,45 +270,252 @@ export function Dialog({
|
|
|
143
270
|
{ paddingTop: 20, paddingHorizontal: 20, paddingBottom: 20 },
|
|
144
271
|
{ backgroundColor: theme.colors.background },
|
|
145
272
|
style,
|
|
273
|
+
panelStyle,
|
|
146
274
|
]}
|
|
147
275
|
>
|
|
148
|
-
{
|
|
149
|
-
<Text
|
|
150
|
-
nativeID={titleId}
|
|
151
|
-
style={{
|
|
152
|
-
fontSize: 22,
|
|
153
|
-
fontWeight: '600',
|
|
154
|
-
color: theme.colors.text,
|
|
155
|
-
paddingBottom: description ? 4 : 16,
|
|
156
|
-
lineHeight: 30,
|
|
157
|
-
}}
|
|
158
|
-
>
|
|
159
|
-
{title}
|
|
160
|
-
</Text>
|
|
161
|
-
) : null}
|
|
162
|
-
{description ? (
|
|
163
|
-
<Text
|
|
164
|
-
nativeID={descriptionId}
|
|
165
|
-
style={{
|
|
166
|
-
fontSize: 16,
|
|
167
|
-
color: theme.colors.textSecondary,
|
|
168
|
-
paddingBottom: 16,
|
|
169
|
-
lineHeight: 22,
|
|
170
|
-
}}
|
|
171
|
-
>
|
|
172
|
-
{description}
|
|
173
|
-
</Text>
|
|
174
|
-
) : null}
|
|
175
|
-
{children}
|
|
176
|
-
{actions && actions.length > 0 ? (
|
|
177
|
-
<ActionRow actions={actions} />
|
|
178
|
-
) : null}
|
|
276
|
+
{bodyNode}
|
|
179
277
|
</View>
|
|
180
278
|
</Context.Provider>
|
|
181
279
|
</BottomSheet>
|
|
182
280
|
);
|
|
183
281
|
}
|
|
184
282
|
|
|
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
|
+
/**
|
|
349
|
+
* Reanimated side-sheet/drawer for the `left`/`right` placements (native).
|
|
350
|
+
*
|
|
351
|
+
* A shared `progress` value times 0 -> 1 on open and 1 -> 0 on close, mapped to
|
|
352
|
+
* the panel translateX and the backdrop opacity. The node stays mounted through
|
|
353
|
+
* the close timing and unmounts from the animation completion callback. Native
|
|
354
|
+
* exit animations are safe (the web `removeChild` crash does not apply).
|
|
355
|
+
*/
|
|
356
|
+
function SideSheet({
|
|
357
|
+
open,
|
|
358
|
+
onDismiss,
|
|
359
|
+
side,
|
|
360
|
+
width,
|
|
361
|
+
inset,
|
|
362
|
+
dismissOnBackdrop,
|
|
363
|
+
testID,
|
|
364
|
+
label,
|
|
365
|
+
title,
|
|
366
|
+
description,
|
|
367
|
+
titleId,
|
|
368
|
+
descriptionId,
|
|
369
|
+
panelStyle,
|
|
370
|
+
panelClassName,
|
|
371
|
+
containerStyle,
|
|
372
|
+
containerClassName,
|
|
373
|
+
style,
|
|
374
|
+
children,
|
|
375
|
+
}: {
|
|
376
|
+
open: boolean;
|
|
377
|
+
onDismiss: () => void;
|
|
378
|
+
side: 'left' | 'right';
|
|
379
|
+
width: number;
|
|
380
|
+
inset?: DialogInset;
|
|
381
|
+
dismissOnBackdrop: boolean;
|
|
382
|
+
testID?: string;
|
|
383
|
+
label?: string;
|
|
384
|
+
title?: string;
|
|
385
|
+
description?: string;
|
|
386
|
+
titleId: string;
|
|
387
|
+
descriptionId: string;
|
|
388
|
+
panelStyle?: StyleProp<ViewStyle>;
|
|
389
|
+
panelClassName?: string;
|
|
390
|
+
containerStyle?: StyleProp<ViewStyle>;
|
|
391
|
+
containerClassName?: string;
|
|
392
|
+
style?: StyleProp<ViewStyle>;
|
|
393
|
+
children: React.ReactNode;
|
|
394
|
+
}) {
|
|
395
|
+
const theme = useTheme();
|
|
396
|
+
const { width: viewportWidth } = useWindowDimensions();
|
|
397
|
+
|
|
398
|
+
const [mounted, setMounted] = useState(open);
|
|
399
|
+
const progress = useSharedValue(open ? 1 : 0);
|
|
400
|
+
|
|
401
|
+
const insetTop = inset?.top ?? 0;
|
|
402
|
+
const insetBottom = inset?.bottom ?? 0;
|
|
403
|
+
const insetLeft = inset?.left ?? 0;
|
|
404
|
+
const insetRight = inset?.right ?? 0;
|
|
405
|
+
const anchorInset = side === 'left' ? insetLeft : insetRight;
|
|
406
|
+
const oppositeInset = side === 'left' ? insetRight : insetLeft;
|
|
407
|
+
|
|
408
|
+
const sideWidth = useMemo(() => {
|
|
409
|
+
const available = viewportWidth - anchorInset - oppositeInset - SIDE_SHEET_MIN_GUTTER;
|
|
410
|
+
return Math.max(0, Math.min(width, available));
|
|
411
|
+
}, [viewportWidth, anchorInset, oppositeInset, width]);
|
|
412
|
+
|
|
413
|
+
// Travel distance for the hidden state: the panel's measured width, with the
|
|
414
|
+
// geometry target as the pre-measurement fallback.
|
|
415
|
+
const travel = useSharedValue(sideWidth);
|
|
416
|
+
useEffect(() => {
|
|
417
|
+
travel.value = sideWidth;
|
|
418
|
+
}, [sideWidth, travel]);
|
|
419
|
+
|
|
420
|
+
const finishClose = useCallback(() => {
|
|
421
|
+
setMounted(false);
|
|
422
|
+
onDismiss();
|
|
423
|
+
}, [onDismiss]);
|
|
424
|
+
|
|
425
|
+
useEffect(() => {
|
|
426
|
+
cancelAnimation(progress);
|
|
427
|
+
const timing = { duration: ANIMATION_DURATION, easing: Easing.out(Easing.cubic) };
|
|
428
|
+
if (open) {
|
|
429
|
+
setMounted(true);
|
|
430
|
+
progress.value = withTiming(1, timing);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
if (!mounted) return;
|
|
434
|
+
progress.value = withTiming(0, timing, (finished) => {
|
|
435
|
+
if (finished) runOnJS(finishClose)();
|
|
436
|
+
});
|
|
437
|
+
}, [open, mounted, progress, finishClose]);
|
|
438
|
+
|
|
439
|
+
const handleBackdropPress = useCallback(() => {
|
|
440
|
+
if (dismissOnBackdrop) onDismiss();
|
|
441
|
+
}, [dismissOnBackdrop, onDismiss]);
|
|
442
|
+
|
|
443
|
+
const backdropAnimatedStyle = useAnimatedStyle(() => ({
|
|
444
|
+
opacity: progress.value * SHEET_BACKDROP_OPACITY,
|
|
445
|
+
}));
|
|
446
|
+
|
|
447
|
+
const hiddenSign = side === 'left' ? -1 : 1;
|
|
448
|
+
const panelAnimatedStyle = useAnimatedStyle(() => {
|
|
449
|
+
const hidden = 1 - progress.value;
|
|
450
|
+
return {
|
|
451
|
+
opacity: progress.value,
|
|
452
|
+
transform: [{ translateX: hidden * travel.value * hiddenSign }],
|
|
453
|
+
};
|
|
454
|
+
}, [hiddenSign]);
|
|
455
|
+
|
|
456
|
+
const measurePanel = useCallback(
|
|
457
|
+
(e: { nativeEvent: { layout: { width: number } } }) => {
|
|
458
|
+
const measured = e.nativeEvent.layout.width;
|
|
459
|
+
if (measured > 0) travel.value = measured;
|
|
460
|
+
},
|
|
461
|
+
[travel],
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
const panelGeometry = useMemo<ViewStyle>(
|
|
465
|
+
() =>
|
|
466
|
+
({
|
|
467
|
+
top: insetTop,
|
|
468
|
+
bottom: insetBottom,
|
|
469
|
+
[side]: anchorInset,
|
|
470
|
+
width: sideWidth,
|
|
471
|
+
borderRadius: PANEL_RADIUS,
|
|
472
|
+
}) as ViewStyle,
|
|
473
|
+
[insetTop, insetBottom, side, anchorInset, sideWidth],
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
if (!mounted) return null;
|
|
477
|
+
|
|
478
|
+
return (
|
|
479
|
+
<View
|
|
480
|
+
style={[sideStyles.root, containerStyle]}
|
|
481
|
+
{...(containerClassName ? ({ className: containerClassName } as Record<string, string>) : {})}
|
|
482
|
+
pointerEvents="box-none"
|
|
483
|
+
>
|
|
484
|
+
<Animated.View style={[sideStyles.backdrop, backdropAnimatedStyle]} pointerEvents="auto">
|
|
485
|
+
<Pressable
|
|
486
|
+
testID={testID ? `${testID}-backdrop` : DIALOG_SHEET_BACKDROP_TESTID}
|
|
487
|
+
accessibilityRole="button"
|
|
488
|
+
accessibilityLabel={label ? `Dismiss ${label}` : 'Dismiss dialog'}
|
|
489
|
+
onPress={handleBackdropPress}
|
|
490
|
+
disabled={!dismissOnBackdrop}
|
|
491
|
+
style={StyleSheet.absoluteFill}
|
|
492
|
+
/>
|
|
493
|
+
</Animated.View>
|
|
494
|
+
|
|
495
|
+
<Animated.View
|
|
496
|
+
accessibilityViewIsModal
|
|
497
|
+
accessibilityLabel={label}
|
|
498
|
+
aria-labelledby={title ? titleId : undefined}
|
|
499
|
+
aria-describedby={description ? descriptionId : undefined}
|
|
500
|
+
testID={testID}
|
|
501
|
+
onLayout={measurePanel}
|
|
502
|
+
{...(panelClassName ? ({ className: panelClassName } as Record<string, string>) : {})}
|
|
503
|
+
style={[
|
|
504
|
+
sideStyles.panel,
|
|
505
|
+
{ backgroundColor: theme.colors.background, shadowColor: theme.colors.shadow },
|
|
506
|
+
panelGeometry,
|
|
507
|
+
panelAnimatedStyle,
|
|
508
|
+
panelStyle,
|
|
509
|
+
style,
|
|
510
|
+
]}
|
|
511
|
+
pointerEvents="auto"
|
|
512
|
+
>
|
|
513
|
+
<View style={sideStyles.body}>{children}</View>
|
|
514
|
+
</Animated.View>
|
|
515
|
+
</View>
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
|
|
185
519
|
function ActionRow({ actions }: { actions: DialogAction[] }) {
|
|
186
520
|
return (
|
|
187
521
|
<View style={{ width: '100%', gap: 8, justifyContent: 'flex-end' }}>
|
|
@@ -265,6 +599,28 @@ function getActionPalette(
|
|
|
265
599
|
}
|
|
266
600
|
}
|
|
267
601
|
|
|
602
|
+
const sideStyles = StyleSheet.create({
|
|
603
|
+
root: {
|
|
604
|
+
...StyleSheet.absoluteFill,
|
|
605
|
+
zIndex: Z_INDEX.fullscreen,
|
|
606
|
+
},
|
|
607
|
+
backdrop: {
|
|
608
|
+
...StyleSheet.absoluteFill,
|
|
609
|
+
backgroundColor: '#000',
|
|
610
|
+
},
|
|
611
|
+
panel: {
|
|
612
|
+
position: 'absolute',
|
|
613
|
+
overflow: 'hidden',
|
|
614
|
+
shadowOpacity: 0.18,
|
|
615
|
+
shadowRadius: 24,
|
|
616
|
+
shadowOffset: { width: 0, height: 8 },
|
|
617
|
+
elevation: 12,
|
|
618
|
+
},
|
|
619
|
+
body: {
|
|
620
|
+
padding: 20,
|
|
621
|
+
},
|
|
622
|
+
});
|
|
623
|
+
|
|
268
624
|
/**
|
|
269
625
|
* Helper used by the imperative `alert()` API. Mounts a `<Dialog>` against
|
|
270
626
|
* a fresh control and presents it immediately. `onResolve` is invoked
|