@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
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React, {
|
|
2
2
|
createContext,
|
|
3
3
|
useCallback,
|
|
4
|
-
useContext,
|
|
5
4
|
useEffect,
|
|
6
5
|
useId,
|
|
7
6
|
useImperativeHandle,
|
|
@@ -13,8 +12,10 @@ import {
|
|
|
13
12
|
Pressable,
|
|
14
13
|
Text,
|
|
15
14
|
TouchableOpacity,
|
|
15
|
+
useWindowDimensions,
|
|
16
16
|
View,
|
|
17
17
|
type GestureResponderEvent,
|
|
18
|
+
type StyleProp,
|
|
18
19
|
type ViewStyle,
|
|
19
20
|
} from 'react-native';
|
|
20
21
|
import { RemoveScrollBar } from 'react-remove-scroll-bar';
|
|
@@ -24,14 +25,32 @@ import { createOverlayZIndex } from '../styles/z-index';
|
|
|
24
25
|
import type { ThemeColors } from '../theme/types';
|
|
25
26
|
import { useTheme } from '../theme/use-theme';
|
|
26
27
|
import { Context, useDialogContext, useDialogControl } from './context';
|
|
28
|
+
import {
|
|
29
|
+
ANIMATION_DURATION,
|
|
30
|
+
CENTER_FADE_OUT_DURATION,
|
|
31
|
+
DEFAULT_CENTER_MAX_WIDTH,
|
|
32
|
+
DEFAULT_MAX_HEIGHT_RATIO,
|
|
33
|
+
DEFAULT_SIDE_WIDTH,
|
|
34
|
+
DIALOG_SHEET_BACKDROP_TESTID,
|
|
35
|
+
EASE_OUT,
|
|
36
|
+
HANDLE_HEIGHT,
|
|
37
|
+
HANDLE_RADIUS,
|
|
38
|
+
HANDLE_WIDTH,
|
|
39
|
+
PANEL_RADIUS,
|
|
40
|
+
SHEET_BACKDROP_OPACITY,
|
|
41
|
+
SIDE_SHEET_MIN_GUTTER,
|
|
42
|
+
useResolvedPlacement,
|
|
43
|
+
type DialogPlacement,
|
|
44
|
+
} from './placement';
|
|
27
45
|
import type {
|
|
28
46
|
DialogAction,
|
|
29
47
|
DialogActionColor,
|
|
30
48
|
DialogControlProps,
|
|
49
|
+
DialogInset,
|
|
31
50
|
DialogProps,
|
|
32
51
|
} from './types';
|
|
33
52
|
|
|
34
|
-
const FADE_OUT_DURATION =
|
|
53
|
+
const FADE_OUT_DURATION = CENTER_FADE_OUT_DURATION;
|
|
35
54
|
const dialogZIndex = createOverlayZIndex();
|
|
36
55
|
|
|
37
56
|
const stopPropagation = (e: { stopPropagation: () => void }) => e.stopPropagation();
|
|
@@ -41,42 +60,101 @@ const ClosingContext = createContext(false);
|
|
|
41
60
|
/**
|
|
42
61
|
* Web variant of `<Dialog>`.
|
|
43
62
|
*
|
|
44
|
-
* A centered modal card rendered into the bloom Portal at the end of
|
|
45
|
-
* `document.body
|
|
46
|
-
*
|
|
47
|
-
* agnostic.
|
|
63
|
+
* A centered modal card (default) rendered into the bloom Portal at the end of
|
|
64
|
+
* `document.body`, or — when `placement` is `left`/`right`/`bottom` — an
|
|
65
|
+
* anchored side-sheet or bottom-sheet. Same prop API as native, so call sites
|
|
66
|
+
* are platform agnostic.
|
|
67
|
+
*
|
|
68
|
+
* Open/close is driven by EITHER the imperative `control` (legacy) OR the
|
|
69
|
+
* controlled `open` prop. When `open` is provided it wins.
|
|
48
70
|
*
|
|
49
71
|
* Accessibility: the panel has `role="dialog"` and the `title`/`description`
|
|
50
72
|
* props (when provided) become the `aria-labelledby` / `aria-describedby`
|
|
51
73
|
* targets. Pressing the backdrop dismisses. Pressing `Escape` dismisses.
|
|
52
74
|
* Focus is locked inside the dialog while it's open.
|
|
75
|
+
*
|
|
76
|
+
* Web motion never uses reanimated `exiting` (which throws `removeChild` on
|
|
77
|
+
* concurrent React unmounts): the centered card uses CSS keyframes and the
|
|
78
|
+
* side/bottom modes use self-contained CSS transitions on a
|
|
79
|
+
* mounted-through-exit node.
|
|
53
80
|
*/
|
|
54
81
|
export function Dialog({
|
|
55
82
|
control,
|
|
83
|
+
open: controlledOpen,
|
|
56
84
|
onClose,
|
|
57
85
|
testID,
|
|
58
86
|
title,
|
|
59
87
|
description,
|
|
60
88
|
actions,
|
|
89
|
+
placement,
|
|
90
|
+
width = DEFAULT_SIDE_WIDTH,
|
|
91
|
+
maxWidth = DEFAULT_CENTER_MAX_WIDTH,
|
|
92
|
+
maxHeightRatio = DEFAULT_MAX_HEIGHT_RATIO,
|
|
93
|
+
inset,
|
|
94
|
+
showHandle = true,
|
|
95
|
+
dismissOnBackdrop = true,
|
|
61
96
|
style,
|
|
97
|
+
panelStyle,
|
|
98
|
+
panelClassName,
|
|
99
|
+
containerStyle,
|
|
100
|
+
containerClassName,
|
|
62
101
|
label,
|
|
63
102
|
children,
|
|
64
103
|
}: DialogProps) {
|
|
104
|
+
// Controlled mode is opt-in: when `open` is a boolean the host owns the
|
|
105
|
+
// visible state; otherwise the legacy imperative `control` path drives it.
|
|
106
|
+
const isControlled = controlledOpen !== undefined;
|
|
107
|
+
const resolvedPlacement = useResolvedPlacement(placement);
|
|
108
|
+
|
|
65
109
|
const [isOpen, setIsOpen] = useState(false);
|
|
66
110
|
const [isClosing, setIsClosing] = useState(false);
|
|
67
111
|
const closeCallbacksRef = useRef<(() => void)[]>([]);
|
|
112
|
+
// Read the latest controlled flag inside stable callbacks without re-binding.
|
|
113
|
+
const isControlledRef = useRef(isControlled);
|
|
114
|
+
isControlledRef.current = isControlled;
|
|
68
115
|
|
|
69
116
|
const open = useCallback(() => {
|
|
70
117
|
setIsClosing(false);
|
|
71
118
|
setIsOpen(true);
|
|
72
119
|
}, []);
|
|
73
120
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
121
|
+
// A dismissal request (backdrop, Escape, action button). In controlled mode
|
|
122
|
+
// it asks the host to close via `onClose` (the host flips `open`); in
|
|
123
|
+
// imperative mode it starts the exit animation directly. Either way an
|
|
124
|
+
// optional callback runs after the dialog has finished closing.
|
|
125
|
+
const close = useCallback<DialogControlProps['close']>(
|
|
126
|
+
(cb) => {
|
|
127
|
+
if (typeof cb === 'function') {
|
|
128
|
+
closeCallbacksRef.current.push(cb);
|
|
129
|
+
}
|
|
130
|
+
if (isControlledRef.current) {
|
|
131
|
+
onCloseRef.current?.();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
setIsClosing(true);
|
|
135
|
+
},
|
|
136
|
+
[],
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// `onClose` mirrored into a ref so `close` stays referentially stable (the
|
|
140
|
+
// dialog context + imperative handle depend on it).
|
|
141
|
+
const onCloseRef = useRef(onClose);
|
|
142
|
+
onCloseRef.current = onClose;
|
|
143
|
+
|
|
144
|
+
// In controlled mode, mirror the `open` prop into the internal open/closing
|
|
145
|
+
// state machine so both modes share the same exit-animation lifecycle.
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
if (!isControlled) return;
|
|
148
|
+
if (controlledOpen) {
|
|
149
|
+
setIsClosing(false);
|
|
150
|
+
setIsOpen(true);
|
|
151
|
+
} else {
|
|
152
|
+
setIsClosing(true);
|
|
77
153
|
}
|
|
78
|
-
|
|
79
|
-
|
|
154
|
+
}, [isControlled, controlledOpen]);
|
|
155
|
+
|
|
156
|
+
const exitDuration =
|
|
157
|
+
resolvedPlacement === 'center' ? FADE_OUT_DURATION : ANIMATION_DURATION;
|
|
80
158
|
|
|
81
159
|
useEffect(() => {
|
|
82
160
|
if (!isClosing) return;
|
|
@@ -95,17 +173,23 @@ export function Dialog({
|
|
|
95
173
|
}
|
|
96
174
|
}
|
|
97
175
|
}
|
|
98
|
-
onClose
|
|
99
|
-
|
|
176
|
+
// Imperative mode fires `onClose` when the exit settles. Controlled mode
|
|
177
|
+
// already requested the close through `onClose` (the host then flipped
|
|
178
|
+
// `open`), so it must NOT fire it a second time here.
|
|
179
|
+
if (!isControlledRef.current) onCloseRef.current?.();
|
|
180
|
+
}, exitDuration);
|
|
100
181
|
|
|
101
182
|
return () => clearTimeout(timer);
|
|
102
|
-
}, [isClosing,
|
|
183
|
+
}, [isClosing, exitDuration]);
|
|
103
184
|
|
|
104
185
|
// Escape-to-close while open. The listener is intentionally scoped to the
|
|
105
186
|
// open lifetime so stacked dialogs don't fight for the keydown — the
|
|
106
|
-
// top-most one wins via document-level event order.
|
|
187
|
+
// top-most one wins via document-level event order. Escape honors
|
|
188
|
+
// `dismissOnBackdrop`: a blocking dialog (e.g. an unanswered confirm) is not
|
|
189
|
+
// dismissible by Escape, matching the backdrop's behavior. The default
|
|
190
|
+
// (`dismissOnBackdrop` true) is unchanged — Escape still closes.
|
|
107
191
|
useEffect(() => {
|
|
108
|
-
if (!isOpen || typeof document === 'undefined') return;
|
|
192
|
+
if (!isOpen || !dismissOnBackdrop || typeof document === 'undefined') return;
|
|
109
193
|
const handler = (e: KeyboardEvent) => {
|
|
110
194
|
if (e.key === 'Escape') {
|
|
111
195
|
e.stopPropagation();
|
|
@@ -114,10 +198,10 @@ export function Dialog({
|
|
|
114
198
|
};
|
|
115
199
|
document.addEventListener('keydown', handler);
|
|
116
200
|
return () => document.removeEventListener('keydown', handler);
|
|
117
|
-
}, [close, isOpen]);
|
|
201
|
+
}, [close, dismissOnBackdrop, isOpen]);
|
|
118
202
|
|
|
119
203
|
useImperativeHandle(
|
|
120
|
-
control
|
|
204
|
+
control?.ref,
|
|
121
205
|
() => ({ open, close }),
|
|
122
206
|
[open, close],
|
|
123
207
|
);
|
|
@@ -129,48 +213,142 @@ export function Dialog({
|
|
|
129
213
|
|
|
130
214
|
if (!isOpen) return null;
|
|
131
215
|
|
|
216
|
+
if (resolvedPlacement === 'center') {
|
|
217
|
+
return (
|
|
218
|
+
<Portal>
|
|
219
|
+
<Context.Provider value={context}>
|
|
220
|
+
<ClosingContext.Provider value={isClosing}>
|
|
221
|
+
<RemoveScrollBar />
|
|
222
|
+
<Pressable
|
|
223
|
+
onPress={dismissOnBackdrop ? () => close() : undefined}
|
|
224
|
+
disabled={!dismissOnBackdrop}
|
|
225
|
+
// `pointerEvents: 'auto'` opts back in from the Portal root's
|
|
226
|
+
// `pointer-events: none`, which is set so the idle portal
|
|
227
|
+
// doesn't intercept clicks on the underlying app.
|
|
228
|
+
style={{
|
|
229
|
+
position: 'fixed' as 'absolute',
|
|
230
|
+
top: 0,
|
|
231
|
+
left: 0,
|
|
232
|
+
right: 0,
|
|
233
|
+
bottom: 0,
|
|
234
|
+
zIndex: dialogZIndex.backdrop,
|
|
235
|
+
alignItems: 'center',
|
|
236
|
+
justifyContent: 'center',
|
|
237
|
+
paddingHorizontal: 20,
|
|
238
|
+
pointerEvents: 'auto',
|
|
239
|
+
}}
|
|
240
|
+
>
|
|
241
|
+
<DialogBackdrop isClosing={isClosing} />
|
|
242
|
+
<DialogPanel
|
|
243
|
+
testID={testID}
|
|
244
|
+
label={label}
|
|
245
|
+
title={title}
|
|
246
|
+
description={description}
|
|
247
|
+
actions={actions}
|
|
248
|
+
style={style}
|
|
249
|
+
maxWidth={maxWidth}
|
|
250
|
+
isClosing={isClosing}
|
|
251
|
+
>
|
|
252
|
+
{children}
|
|
253
|
+
</DialogPanel>
|
|
254
|
+
</Pressable>
|
|
255
|
+
</ClosingContext.Provider>
|
|
256
|
+
</Context.Provider>
|
|
257
|
+
</Portal>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
132
261
|
return (
|
|
133
262
|
<Portal>
|
|
134
263
|
<Context.Provider value={context}>
|
|
135
264
|
<ClosingContext.Provider value={isClosing}>
|
|
136
265
|
<RemoveScrollBar />
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
266
|
+
<SheetSurface
|
|
267
|
+
testID={testID}
|
|
268
|
+
label={label}
|
|
269
|
+
title={title}
|
|
270
|
+
description={description}
|
|
271
|
+
actions={actions}
|
|
272
|
+
placement={resolvedPlacement}
|
|
273
|
+
shown={!isClosing}
|
|
274
|
+
width={width}
|
|
275
|
+
maxHeightRatio={maxHeightRatio}
|
|
276
|
+
inset={inset}
|
|
277
|
+
showHandle={showHandle}
|
|
278
|
+
dismissOnBackdrop={dismissOnBackdrop}
|
|
279
|
+
onDismiss={close}
|
|
280
|
+
panelStyle={panelStyle}
|
|
281
|
+
panelClassName={panelClassName}
|
|
282
|
+
containerStyle={containerStyle}
|
|
283
|
+
containerClassName={containerClassName}
|
|
284
|
+
style={style}
|
|
154
285
|
>
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
testID={testID}
|
|
158
|
-
label={label}
|
|
159
|
-
title={title}
|
|
160
|
-
description={description}
|
|
161
|
-
actions={actions}
|
|
162
|
-
style={style}
|
|
163
|
-
isClosing={isClosing}
|
|
164
|
-
>
|
|
165
|
-
{children}
|
|
166
|
-
</DialogPanel>
|
|
167
|
-
</Pressable>
|
|
286
|
+
{children}
|
|
287
|
+
</SheetSurface>
|
|
168
288
|
</ClosingContext.Provider>
|
|
169
289
|
</Context.Provider>
|
|
170
290
|
</Portal>
|
|
171
291
|
);
|
|
172
292
|
}
|
|
173
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Renders the dialog's body: optional declarative title + description, any
|
|
296
|
+
* `children`, then the action row. Shared by the centered panel and the
|
|
297
|
+
* side/bottom sheet so all placements present identical content. The
|
|
298
|
+
* `titleId`/`descriptionId` are wired by the caller onto the `role="dialog"`
|
|
299
|
+
* element for `aria-labelledby` / `aria-describedby`.
|
|
300
|
+
*/
|
|
301
|
+
function DialogBody({
|
|
302
|
+
titleId,
|
|
303
|
+
descriptionId,
|
|
304
|
+
title,
|
|
305
|
+
description,
|
|
306
|
+
actions,
|
|
307
|
+
children,
|
|
308
|
+
}: {
|
|
309
|
+
titleId: string;
|
|
310
|
+
descriptionId: string;
|
|
311
|
+
title?: string;
|
|
312
|
+
description?: string;
|
|
313
|
+
actions?: DialogAction[];
|
|
314
|
+
children?: React.ReactNode;
|
|
315
|
+
}) {
|
|
316
|
+
const theme = useTheme();
|
|
317
|
+
return (
|
|
318
|
+
<>
|
|
319
|
+
{title ? (
|
|
320
|
+
<Text
|
|
321
|
+
nativeID={titleId}
|
|
322
|
+
style={{
|
|
323
|
+
fontSize: 22,
|
|
324
|
+
fontWeight: '600',
|
|
325
|
+
color: theme.colors.text,
|
|
326
|
+
paddingBottom: description ? 4 : 16,
|
|
327
|
+
lineHeight: 30,
|
|
328
|
+
}}
|
|
329
|
+
>
|
|
330
|
+
{title}
|
|
331
|
+
</Text>
|
|
332
|
+
) : null}
|
|
333
|
+
{description ? (
|
|
334
|
+
<Text
|
|
335
|
+
nativeID={descriptionId}
|
|
336
|
+
style={{
|
|
337
|
+
fontSize: 16,
|
|
338
|
+
color: theme.colors.textSecondary,
|
|
339
|
+
paddingBottom: 16,
|
|
340
|
+
lineHeight: 22,
|
|
341
|
+
}}
|
|
342
|
+
>
|
|
343
|
+
{description}
|
|
344
|
+
</Text>
|
|
345
|
+
) : null}
|
|
346
|
+
{children}
|
|
347
|
+
{actions && actions.length > 0 ? <ActionRow actions={actions} /> : null}
|
|
348
|
+
</>
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
174
352
|
function DialogPanel({
|
|
175
353
|
testID,
|
|
176
354
|
label,
|
|
@@ -178,6 +356,7 @@ function DialogPanel({
|
|
|
178
356
|
description,
|
|
179
357
|
actions,
|
|
180
358
|
style,
|
|
359
|
+
maxWidth,
|
|
181
360
|
isClosing,
|
|
182
361
|
children,
|
|
183
362
|
}: {
|
|
@@ -187,6 +366,7 @@ function DialogPanel({
|
|
|
187
366
|
description?: string;
|
|
188
367
|
actions?: DialogAction[];
|
|
189
368
|
style?: DialogProps['style'];
|
|
369
|
+
maxWidth: number;
|
|
190
370
|
isClosing: boolean;
|
|
191
371
|
children?: React.ReactNode;
|
|
192
372
|
}) {
|
|
@@ -209,7 +389,7 @@ function DialogPanel({
|
|
|
209
389
|
position: 'relative',
|
|
210
390
|
borderRadius: 20,
|
|
211
391
|
width: '100%',
|
|
212
|
-
maxWidth
|
|
392
|
+
maxWidth,
|
|
213
393
|
backgroundColor: theme.colors.background,
|
|
214
394
|
borderWidth: 1,
|
|
215
395
|
borderColor: theme.isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)',
|
|
@@ -226,39 +406,232 @@ function DialogPanel({
|
|
|
226
406
|
style,
|
|
227
407
|
]}
|
|
228
408
|
>
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}}
|
|
239
|
-
>
|
|
240
|
-
{title}
|
|
241
|
-
</Text>
|
|
242
|
-
) : null}
|
|
243
|
-
{description ? (
|
|
244
|
-
<Text
|
|
245
|
-
nativeID={descriptionId}
|
|
246
|
-
style={{
|
|
247
|
-
fontSize: 16,
|
|
248
|
-
color: theme.colors.textSecondary,
|
|
249
|
-
paddingBottom: 16,
|
|
250
|
-
lineHeight: 22,
|
|
251
|
-
}}
|
|
252
|
-
>
|
|
253
|
-
{description}
|
|
254
|
-
</Text>
|
|
255
|
-
) : null}
|
|
256
|
-
{children}
|
|
257
|
-
{actions && actions.length > 0 ? <ActionRow actions={actions} /> : null}
|
|
409
|
+
<DialogBody
|
|
410
|
+
titleId={titleId}
|
|
411
|
+
descriptionId={descriptionId}
|
|
412
|
+
title={title}
|
|
413
|
+
description={description}
|
|
414
|
+
actions={actions}
|
|
415
|
+
>
|
|
416
|
+
{children}
|
|
417
|
+
</DialogBody>
|
|
258
418
|
</View>
|
|
259
419
|
);
|
|
260
420
|
}
|
|
261
421
|
|
|
422
|
+
export { DIALOG_SHEET_BACKDROP_TESTID };
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Side-sheet / bottom-sheet surface for the `left`/`right`/`bottom`
|
|
426
|
+
* placements. Pure CSS transitions: the
|
|
427
|
+
* node stays mounted through the exit while a `shown` flag drives the
|
|
428
|
+
* transform/opacity, so both directions animate without reanimated `exiting`.
|
|
429
|
+
*/
|
|
430
|
+
function SheetSurface({
|
|
431
|
+
testID,
|
|
432
|
+
label,
|
|
433
|
+
title,
|
|
434
|
+
description,
|
|
435
|
+
actions,
|
|
436
|
+
placement,
|
|
437
|
+
shown,
|
|
438
|
+
width,
|
|
439
|
+
maxHeightRatio,
|
|
440
|
+
inset,
|
|
441
|
+
showHandle,
|
|
442
|
+
dismissOnBackdrop,
|
|
443
|
+
onDismiss,
|
|
444
|
+
panelStyle,
|
|
445
|
+
panelClassName,
|
|
446
|
+
containerStyle,
|
|
447
|
+
containerClassName,
|
|
448
|
+
style,
|
|
449
|
+
children,
|
|
450
|
+
}: {
|
|
451
|
+
testID?: string;
|
|
452
|
+
label?: string;
|
|
453
|
+
title?: string;
|
|
454
|
+
description?: string;
|
|
455
|
+
actions?: DialogAction[];
|
|
456
|
+
placement: Exclude<DialogPlacement, 'center'>;
|
|
457
|
+
shown: boolean;
|
|
458
|
+
width: number;
|
|
459
|
+
maxHeightRatio: number;
|
|
460
|
+
inset?: DialogInset;
|
|
461
|
+
showHandle: boolean;
|
|
462
|
+
dismissOnBackdrop: boolean;
|
|
463
|
+
onDismiss: () => void;
|
|
464
|
+
panelStyle?: StyleProp<ViewStyle>;
|
|
465
|
+
panelClassName?: string;
|
|
466
|
+
containerStyle?: StyleProp<ViewStyle>;
|
|
467
|
+
containerClassName?: string;
|
|
468
|
+
style?: StyleProp<ViewStyle>;
|
|
469
|
+
children?: React.ReactNode;
|
|
470
|
+
}) {
|
|
471
|
+
const theme = useTheme();
|
|
472
|
+
const titleId = useId();
|
|
473
|
+
const descriptionId = useId();
|
|
474
|
+
const { width: viewportWidth, height: viewportHeight } = useWindowDimensions();
|
|
475
|
+
|
|
476
|
+
// Defer the entry transition by a frame so the start state paints before the
|
|
477
|
+
// browser animates to `shown`. `entered` is false on the first committed
|
|
478
|
+
// frame, then flips true so the CSS transition runs.
|
|
479
|
+
const frameRef = useRef<FrameToken | null>(null);
|
|
480
|
+
const [entered, setEntered] = useState(false);
|
|
481
|
+
useEffect(() => {
|
|
482
|
+
frameRef.current = scheduleFrame(() => {
|
|
483
|
+
frameRef.current = scheduleFrame(() => setEntered(true));
|
|
484
|
+
});
|
|
485
|
+
return () => {
|
|
486
|
+
if (frameRef.current !== null) cancelFrame(frameRef.current);
|
|
487
|
+
frameRef.current = null;
|
|
488
|
+
};
|
|
489
|
+
}, []);
|
|
490
|
+
|
|
491
|
+
const visible = shown && entered;
|
|
492
|
+
const isBottom = placement === 'bottom';
|
|
493
|
+
|
|
494
|
+
const panelTransition = useMemo<ViewStyle>(
|
|
495
|
+
() =>
|
|
496
|
+
({
|
|
497
|
+
transitionProperty: 'transform, opacity',
|
|
498
|
+
transitionDuration: `${ANIMATION_DURATION}ms`,
|
|
499
|
+
transitionTimingFunction: EASE_OUT,
|
|
500
|
+
}) as ViewStyle,
|
|
501
|
+
[],
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
const backdropTransition = useMemo<ViewStyle>(
|
|
505
|
+
() =>
|
|
506
|
+
({
|
|
507
|
+
transitionProperty: 'opacity',
|
|
508
|
+
transitionDuration: `${ANIMATION_DURATION}ms`,
|
|
509
|
+
transitionTimingFunction: EASE_OUT,
|
|
510
|
+
}) as ViewStyle,
|
|
511
|
+
[],
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
const panelGeometry = useMemo<ViewStyle>(() => {
|
|
515
|
+
if (isBottom) {
|
|
516
|
+
return {
|
|
517
|
+
left: 0,
|
|
518
|
+
right: 0,
|
|
519
|
+
bottom: 0,
|
|
520
|
+
maxHeight: Math.round(viewportHeight * maxHeightRatio),
|
|
521
|
+
borderTopLeftRadius: PANEL_RADIUS,
|
|
522
|
+
borderTopRightRadius: PANEL_RADIUS,
|
|
523
|
+
transform: [{ translateY: visible ? 0 : '100%' }],
|
|
524
|
+
opacity: visible ? 1 : 0,
|
|
525
|
+
} as ViewStyle;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const insetTop = inset?.top ?? 0;
|
|
529
|
+
const insetBottom = inset?.bottom ?? 0;
|
|
530
|
+
const insetLeft = inset?.left ?? 0;
|
|
531
|
+
const insetRight = inset?.right ?? 0;
|
|
532
|
+
const anchorInset = placement === 'left' ? insetLeft : insetRight;
|
|
533
|
+
const oppositeInset = placement === 'left' ? insetRight : insetLeft;
|
|
534
|
+
const available = viewportWidth - anchorInset - oppositeInset - SIDE_SHEET_MIN_GUTTER;
|
|
535
|
+
const cappedWidth = Math.max(0, Math.min(width, available));
|
|
536
|
+
const hiddenSign = placement === 'left' ? '-100%' : '100%';
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
top: insetTop,
|
|
540
|
+
bottom: insetBottom,
|
|
541
|
+
[placement]: anchorInset,
|
|
542
|
+
width: cappedWidth,
|
|
543
|
+
borderRadius: PANEL_RADIUS,
|
|
544
|
+
transform: [{ translateX: visible ? 0 : hiddenSign }],
|
|
545
|
+
opacity: visible ? 1 : 0,
|
|
546
|
+
} as ViewStyle;
|
|
547
|
+
}, [isBottom, visible, placement, width, inset, viewportWidth, viewportHeight, maxHeightRatio]);
|
|
548
|
+
|
|
549
|
+
const handleBackdropPress = useCallback(() => {
|
|
550
|
+
if (dismissOnBackdrop) onDismiss();
|
|
551
|
+
}, [dismissOnBackdrop, onDismiss]);
|
|
552
|
+
|
|
553
|
+
return (
|
|
554
|
+
<View
|
|
555
|
+
style={[sheetStyles.root, containerStyle]}
|
|
556
|
+
{...(containerClassName ? ({ className: containerClassName } as Record<string, string>) : {})}
|
|
557
|
+
pointerEvents="box-none"
|
|
558
|
+
>
|
|
559
|
+
<Pressable
|
|
560
|
+
testID={testID ? `${testID}-backdrop` : DIALOG_SHEET_BACKDROP_TESTID}
|
|
561
|
+
accessibilityRole="button"
|
|
562
|
+
accessibilityLabel={label ? `Dismiss ${label}` : 'Dismiss dialog'}
|
|
563
|
+
onPress={handleBackdropPress}
|
|
564
|
+
disabled={!dismissOnBackdrop}
|
|
565
|
+
style={[
|
|
566
|
+
sheetStyles.backdrop,
|
|
567
|
+
backdropTransition,
|
|
568
|
+
{ opacity: visible ? SHEET_BACKDROP_OPACITY : 0 },
|
|
569
|
+
]}
|
|
570
|
+
/>
|
|
571
|
+
|
|
572
|
+
<View
|
|
573
|
+
role="dialog"
|
|
574
|
+
aria-label={label}
|
|
575
|
+
aria-labelledby={title ? titleId : undefined}
|
|
576
|
+
aria-describedby={description ? descriptionId : undefined}
|
|
577
|
+
accessibilityViewIsModal
|
|
578
|
+
testID={testID}
|
|
579
|
+
onStartShouldSetResponder={() => true}
|
|
580
|
+
onResponderRelease={stopPropagation}
|
|
581
|
+
{...({ onClick: stopPropagation } as Record<string, unknown>)}
|
|
582
|
+
{...(panelClassName ? ({ className: panelClassName } as Record<string, string>) : {})}
|
|
583
|
+
style={[
|
|
584
|
+
sheetStyles.panel,
|
|
585
|
+
{ backgroundColor: theme.colors.background, shadowColor: theme.colors.shadow },
|
|
586
|
+
panelGeometry,
|
|
587
|
+
panelTransition,
|
|
588
|
+
panelStyle,
|
|
589
|
+
style,
|
|
590
|
+
]}
|
|
591
|
+
pointerEvents="auto"
|
|
592
|
+
>
|
|
593
|
+
{isBottom && showHandle ? (
|
|
594
|
+
<View style={sheetStyles.handleRow} pointerEvents="none">
|
|
595
|
+
<View style={[sheetStyles.handle, { backgroundColor: theme.colors.border }]} />
|
|
596
|
+
</View>
|
|
597
|
+
) : null}
|
|
598
|
+
<View style={sheetStyles.body}>
|
|
599
|
+
<DialogBody
|
|
600
|
+
titleId={titleId}
|
|
601
|
+
descriptionId={descriptionId}
|
|
602
|
+
title={title}
|
|
603
|
+
description={description}
|
|
604
|
+
actions={actions}
|
|
605
|
+
>
|
|
606
|
+
{children}
|
|
607
|
+
</DialogBody>
|
|
608
|
+
</View>
|
|
609
|
+
</View>
|
|
610
|
+
</View>
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Opaque token returned by `scheduleFrame`. `requestAnimationFrame` returns a
|
|
616
|
+
* number; the `setTimeout` fallback returns a timer handle.
|
|
617
|
+
*/
|
|
618
|
+
type FrameToken = { raf: number } | { timer: ReturnType<typeof setTimeout> };
|
|
619
|
+
|
|
620
|
+
function scheduleFrame(cb: () => void): FrameToken {
|
|
621
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
622
|
+
return { raf: requestAnimationFrame(cb) };
|
|
623
|
+
}
|
|
624
|
+
return { timer: setTimeout(cb, 0) };
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function cancelFrame(token: FrameToken): void {
|
|
628
|
+
if ('raf' in token) {
|
|
629
|
+
if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(token.raf);
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
clearTimeout(token.timer);
|
|
633
|
+
}
|
|
634
|
+
|
|
262
635
|
function ActionRow({ actions }: { actions: DialogAction[] }) {
|
|
263
636
|
return (
|
|
264
637
|
<View style={{ width: '100%', gap: 8, justifyContent: 'flex-end' }}>
|
|
@@ -392,6 +765,48 @@ export function AutoMountedDialog({
|
|
|
392
765
|
);
|
|
393
766
|
}
|
|
394
767
|
|
|
768
|
+
const sheetStyles = {
|
|
769
|
+
root: {
|
|
770
|
+
position: 'fixed' as 'absolute',
|
|
771
|
+
top: 0,
|
|
772
|
+
left: 0,
|
|
773
|
+
right: 0,
|
|
774
|
+
bottom: 0,
|
|
775
|
+
zIndex: dialogZIndex.backdrop,
|
|
776
|
+
} as ViewStyle,
|
|
777
|
+
backdrop: {
|
|
778
|
+
position: 'absolute',
|
|
779
|
+
top: 0,
|
|
780
|
+
left: 0,
|
|
781
|
+
right: 0,
|
|
782
|
+
bottom: 0,
|
|
783
|
+
backgroundColor: '#000',
|
|
784
|
+
} as ViewStyle,
|
|
785
|
+
panel: {
|
|
786
|
+
position: 'absolute',
|
|
787
|
+
overflow: 'hidden',
|
|
788
|
+
shadowOpacity: 0.18,
|
|
789
|
+
shadowRadius: 24,
|
|
790
|
+
shadowOffset: { width: 0, height: 8 },
|
|
791
|
+
zIndex: dialogZIndex.surface,
|
|
792
|
+
} as ViewStyle,
|
|
793
|
+
handleRow: {
|
|
794
|
+
width: '100%',
|
|
795
|
+
alignItems: 'center',
|
|
796
|
+
paddingTop: 8,
|
|
797
|
+
paddingBottom: 4,
|
|
798
|
+
} as ViewStyle,
|
|
799
|
+
handle: {
|
|
800
|
+
width: HANDLE_WIDTH,
|
|
801
|
+
height: HANDLE_HEIGHT,
|
|
802
|
+
borderRadius: HANDLE_RADIUS,
|
|
803
|
+
opacity: 0.5,
|
|
804
|
+
} as ViewStyle,
|
|
805
|
+
body: {
|
|
806
|
+
padding: 20,
|
|
807
|
+
} as ViewStyle,
|
|
808
|
+
};
|
|
809
|
+
|
|
395
810
|
/**
|
|
396
811
|
* CSS keyframes required for web dialog animations.
|
|
397
812
|
* Consumers should inject this string into a <style> tag or their global CSS:
|
|
@@ -402,6 +817,9 @@ export function AutoMountedDialog({
|
|
|
402
817
|
* @keyframes bloomDialogZoomFadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
|
|
403
818
|
* @keyframes bloomDialogZoomFadeOut { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.95); } }
|
|
404
819
|
* ```
|
|
820
|
+
*
|
|
821
|
+
* The `left`/`right`/`bottom` placements do NOT depend on this — they animate
|
|
822
|
+
* via self-contained inline CSS transitions and need no keyframe injection.
|
|
405
823
|
*/
|
|
406
824
|
export const BLOOM_DIALOG_CSS = `
|
|
407
825
|
@keyframes bloomDialogFadeIn { from { opacity: 0; } to { opacity: 1; } }
|