@oxyhq/bloom 0.14.0 → 0.15.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.
Files changed (144) hide show
  1. package/lib/commonjs/dialog/CenteredDialog.js +2 -0
  2. package/lib/commonjs/dialog/CenteredDialog.js.map +1 -1
  3. package/lib/commonjs/dialog/CenteredDialog.web.js +2 -0
  4. package/lib/commonjs/dialog/CenteredDialog.web.js.map +1 -1
  5. package/lib/commonjs/dialog/Dialog.js +337 -43
  6. package/lib/commonjs/dialog/Dialog.js.map +1 -1
  7. package/lib/commonjs/dialog/Dialog.web.js +414 -63
  8. package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
  9. package/lib/commonjs/dialog/index.js +6 -0
  10. package/lib/commonjs/dialog/index.js.map +1 -1
  11. package/lib/commonjs/dialog/index.web.js +6 -0
  12. package/lib/commonjs/dialog/index.web.js.map +1 -1
  13. package/lib/commonjs/dialog/placement.js +104 -0
  14. package/lib/commonjs/dialog/placement.js.map +1 -0
  15. package/lib/commonjs/index.js +7 -0
  16. package/lib/commonjs/index.js.map +1 -1
  17. package/lib/commonjs/index.web.js +7 -0
  18. package/lib/commonjs/index.web.js.map +1 -1
  19. package/lib/commonjs/responsive-sheet/ResponsiveSheet.js +63 -266
  20. package/lib/commonjs/responsive-sheet/ResponsiveSheet.js.map +1 -1
  21. package/lib/commonjs/responsive-sheet/ResponsiveSheet.web.js +84 -0
  22. package/lib/commonjs/responsive-sheet/ResponsiveSheet.web.js.map +1 -0
  23. package/lib/commonjs/responsive-sheet/index.web.js +3 -3
  24. package/lib/commonjs/responsive-sheet/index.web.js.map +1 -1
  25. package/lib/module/dialog/CenteredDialog.js +2 -0
  26. package/lib/module/dialog/CenteredDialog.js.map +1 -1
  27. package/lib/module/dialog/CenteredDialog.web.js +2 -0
  28. package/lib/module/dialog/CenteredDialog.web.js.map +1 -1
  29. package/lib/module/dialog/Dialog.js +336 -46
  30. package/lib/module/dialog/Dialog.js.map +1 -1
  31. package/lib/module/dialog/Dialog.web.js +412 -65
  32. package/lib/module/dialog/Dialog.web.js.map +1 -1
  33. package/lib/module/dialog/index.js +1 -1
  34. package/lib/module/dialog/index.js.map +1 -1
  35. package/lib/module/dialog/index.web.js +1 -1
  36. package/lib/module/dialog/index.web.js.map +1 -1
  37. package/lib/module/dialog/placement.js +100 -0
  38. package/lib/module/dialog/placement.js.map +1 -0
  39. package/lib/module/index.js +1 -1
  40. package/lib/module/index.js.map +1 -1
  41. package/lib/module/index.web.js +1 -1
  42. package/lib/module/index.web.js.map +1 -1
  43. package/lib/module/responsive-sheet/ResponsiveSheet.js +63 -266
  44. package/lib/module/responsive-sheet/ResponsiveSheet.js.map +1 -1
  45. package/lib/module/responsive-sheet/ResponsiveSheet.web.js +77 -0
  46. package/lib/module/responsive-sheet/ResponsiveSheet.web.js.map +1 -0
  47. package/lib/module/responsive-sheet/index.web.js +7 -9
  48. package/lib/module/responsive-sheet/index.web.js.map +1 -1
  49. package/lib/typescript/commonjs/dialog/CenteredDialog.d.ts +2 -0
  50. package/lib/typescript/commonjs/dialog/CenteredDialog.d.ts.map +1 -1
  51. package/lib/typescript/commonjs/dialog/CenteredDialog.web.d.ts +2 -0
  52. package/lib/typescript/commonjs/dialog/CenteredDialog.web.d.ts.map +1 -1
  53. package/lib/typescript/commonjs/dialog/Dialog.d.ts +18 -15
  54. package/lib/typescript/commonjs/dialog/Dialog.d.ts.map +1 -1
  55. package/lib/typescript/commonjs/dialog/Dialog.web.d.ts +18 -5
  56. package/lib/typescript/commonjs/dialog/Dialog.web.d.ts.map +1 -1
  57. package/lib/typescript/commonjs/dialog/centered-dialog-types.d.ts +4 -0
  58. package/lib/typescript/commonjs/dialog/centered-dialog-types.d.ts.map +1 -1
  59. package/lib/typescript/commonjs/dialog/index.d.ts +2 -2
  60. package/lib/typescript/commonjs/dialog/index.d.ts.map +1 -1
  61. package/lib/typescript/commonjs/dialog/index.web.d.ts +2 -2
  62. package/lib/typescript/commonjs/dialog/index.web.d.ts.map +1 -1
  63. package/lib/typescript/commonjs/dialog/placement.d.ts +71 -0
  64. package/lib/typescript/commonjs/dialog/placement.d.ts.map +1 -0
  65. package/lib/typescript/commonjs/dialog/types.d.ts +66 -3
  66. package/lib/typescript/commonjs/dialog/types.d.ts.map +1 -1
  67. package/lib/typescript/commonjs/index.d.ts +2 -2
  68. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  69. package/lib/typescript/commonjs/index.web.d.ts +2 -2
  70. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  71. package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.d.ts +15 -19
  72. package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.d.ts.map +1 -1
  73. package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.web.d.ts +16 -0
  74. package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.web.d.ts.map +1 -0
  75. package/lib/typescript/commonjs/responsive-sheet/index.web.d.ts +1 -1
  76. package/lib/typescript/commonjs/responsive-sheet/index.web.d.ts.map +1 -1
  77. package/lib/typescript/commonjs/responsive-sheet/types.d.ts +5 -2
  78. package/lib/typescript/commonjs/responsive-sheet/types.d.ts.map +1 -1
  79. package/lib/typescript/module/dialog/CenteredDialog.d.ts +2 -0
  80. package/lib/typescript/module/dialog/CenteredDialog.d.ts.map +1 -1
  81. package/lib/typescript/module/dialog/CenteredDialog.web.d.ts +2 -0
  82. package/lib/typescript/module/dialog/CenteredDialog.web.d.ts.map +1 -1
  83. package/lib/typescript/module/dialog/Dialog.d.ts +18 -15
  84. package/lib/typescript/module/dialog/Dialog.d.ts.map +1 -1
  85. package/lib/typescript/module/dialog/Dialog.web.d.ts +18 -5
  86. package/lib/typescript/module/dialog/Dialog.web.d.ts.map +1 -1
  87. package/lib/typescript/module/dialog/centered-dialog-types.d.ts +4 -0
  88. package/lib/typescript/module/dialog/centered-dialog-types.d.ts.map +1 -1
  89. package/lib/typescript/module/dialog/index.d.ts +2 -2
  90. package/lib/typescript/module/dialog/index.d.ts.map +1 -1
  91. package/lib/typescript/module/dialog/index.web.d.ts +2 -2
  92. package/lib/typescript/module/dialog/index.web.d.ts.map +1 -1
  93. package/lib/typescript/module/dialog/placement.d.ts +71 -0
  94. package/lib/typescript/module/dialog/placement.d.ts.map +1 -0
  95. package/lib/typescript/module/dialog/types.d.ts +66 -3
  96. package/lib/typescript/module/dialog/types.d.ts.map +1 -1
  97. package/lib/typescript/module/index.d.ts +2 -2
  98. package/lib/typescript/module/index.d.ts.map +1 -1
  99. package/lib/typescript/module/index.web.d.ts +2 -2
  100. package/lib/typescript/module/index.web.d.ts.map +1 -1
  101. package/lib/typescript/module/responsive-sheet/ResponsiveSheet.d.ts +15 -19
  102. package/lib/typescript/module/responsive-sheet/ResponsiveSheet.d.ts.map +1 -1
  103. package/lib/typescript/module/responsive-sheet/ResponsiveSheet.web.d.ts +16 -0
  104. package/lib/typescript/module/responsive-sheet/ResponsiveSheet.web.d.ts.map +1 -0
  105. package/lib/typescript/module/responsive-sheet/index.web.d.ts +1 -1
  106. package/lib/typescript/module/responsive-sheet/index.web.d.ts.map +1 -1
  107. package/lib/typescript/module/responsive-sheet/types.d.ts +5 -2
  108. package/lib/typescript/module/responsive-sheet/types.d.ts.map +1 -1
  109. package/package.json +1 -1
  110. package/src/__tests__/DialogPlacement.test.tsx +144 -0
  111. package/src/__tests__/ResponsiveSheet.test.tsx +36 -52
  112. package/src/dialog/CenteredDialog.tsx +2 -0
  113. package/src/dialog/CenteredDialog.web.tsx +2 -0
  114. package/src/dialog/Dialog.tsx +407 -50
  115. package/src/dialog/Dialog.web.tsx +490 -75
  116. package/src/dialog/centered-dialog-types.ts +4 -0
  117. package/src/dialog/index.ts +4 -1
  118. package/src/dialog/index.web.ts +4 -1
  119. package/src/dialog/placement.ts +113 -0
  120. package/src/dialog/types.ts +69 -3
  121. package/src/index.ts +4 -0
  122. package/src/index.web.ts +4 -0
  123. package/src/responsive-sheet/ResponsiveSheet.tsx +58 -297
  124. package/src/responsive-sheet/ResponsiveSheet.web.tsx +76 -0
  125. package/src/responsive-sheet/index.web.ts +7 -9
  126. package/src/responsive-sheet/types.ts +5 -2
  127. package/lib/commonjs/responsive-sheet/ResponsiveSheet.native.js +0 -226
  128. package/lib/commonjs/responsive-sheet/ResponsiveSheet.native.js.map +0 -1
  129. package/lib/commonjs/responsive-sheet/const.js +0 -60
  130. package/lib/commonjs/responsive-sheet/const.js.map +0 -1
  131. package/lib/module/responsive-sheet/ResponsiveSheet.native.js +0 -219
  132. package/lib/module/responsive-sheet/ResponsiveSheet.native.js.map +0 -1
  133. package/lib/module/responsive-sheet/const.js +0 -56
  134. package/lib/module/responsive-sheet/const.js.map +0 -1
  135. package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.native.d.ts +0 -20
  136. package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.native.d.ts.map +0 -1
  137. package/lib/typescript/commonjs/responsive-sheet/const.d.ts +0 -43
  138. package/lib/typescript/commonjs/responsive-sheet/const.d.ts.map +0 -1
  139. package/lib/typescript/module/responsive-sheet/ResponsiveSheet.native.d.ts +0 -20
  140. package/lib/typescript/module/responsive-sheet/ResponsiveSheet.native.d.ts.map +0 -1
  141. package/lib/typescript/module/responsive-sheet/const.d.ts +0 -43
  142. package/lib/typescript/module/responsive-sheet/const.d.ts.map +0 -1
  143. package/src/responsive-sheet/ResponsiveSheet.native.tsx +0 -257
  144. package/src/responsive-sheet/const.ts +0 -53
@@ -1,327 +1,88 @@
1
- import React, {
2
- useCallback,
3
- useEffect,
4
- useMemo,
5
- useRef,
6
- useState,
7
- } from 'react';
8
- import {
9
- Pressable,
10
- StyleSheet,
11
- useWindowDimensions,
12
- View,
13
- type ViewStyle,
14
- } from 'react-native';
1
+ import React from 'react';
15
2
 
16
- import { Z_INDEX } from '../styles/z-index';
17
- import { useTheme } from '../theme/use-theme';
3
+ import { Dialog } from '../dialog';
18
4
  import {
19
- ANIMATION_DURATION,
20
- BACKDROP_OPACITY,
21
- DEFAULT_BREAKPOINT,
22
- DEFAULT_LABEL,
23
- DEFAULT_MAX_HEIGHT_RATIO,
24
- DEFAULT_SIDE,
25
- DEFAULT_WIDTH,
26
- EASE_OUT,
27
- HANDLE_HEIGHT,
28
- HANDLE_RADIUS,
29
- HANDLE_WIDTH,
30
- PANEL_RADIUS,
31
- SIDE_SHEET_MIN_GUTTER,
32
- } from './const';
5
+ BREAKPOINT_LG,
6
+ BREAKPOINT_MD,
7
+ BREAKPOINT_SM,
8
+ BREAKPOINT_XL,
9
+ DIALOG_SHEET_BACKDROP_TESTID,
10
+ type ResponsiveDialogPlacement,
11
+ } from '../dialog/placement';
33
12
  import type { ResponsiveSheetProps } from './types';
34
13
 
35
- /** Stable testID for the dimmed backdrop dismiss button. */
36
- export const RESPONSIVE_SHEET_BACKDROP_TESTID = 'bloom-responsive-sheet-backdrop';
37
-
38
14
  /**
39
- * Default / web variant of `<ResponsiveSheet>`.
15
+ * @deprecated Use `<Dialog placement={{ base: 'bottom', md: 'left' }}>` instead.
40
16
  *
41
- * This file is BOTH the package's default entry (`react-native` export
42
- * condition resolves it for downstream `tsc`, and non-Metro web bundlers pick
43
- * it via the `browser` condition through `index.web`) AND the canonical web
44
- * implementation. It MUST NOT import `react-native-reanimated` reanimated has
45
- * no web build and its worklets Babel plugin is native-only, so a static import
46
- * here would break every web bundler and downstream type-check. The native
47
- * fork (`ResponsiveSheet.native.tsx`, selected only by Metro) drives the same
48
- * motion with reanimated.
17
+ * `ResponsiveSheet` is now a thin wrapper that configures the canonical
18
+ * `<Dialog>` an anchored side-sheet on wide screens collapsing to a
19
+ * bottom-sheet below the breakpoint. It holds ZERO motion/layout logic of its
20
+ * own; it maps its props onto a responsive `Dialog` `placement`. The
21
+ * `@oxyhq/bloom/responsive-sheet` subpath is retained for the migration window.
49
22
  *
50
- * Motion is pure CSS transitions: react-native-web emits `transition-*` style
51
- * props to the DOM, and the panel/backdrop toggle their transform/opacity by a
52
- * `shown` flag. Presence is handled with a `mounted` + `shown` pair so both the
53
- * open AND close directions animate while the node stays mounted through the
54
- * exit. We deliberately do NOT use reanimated `exiting` on web — concurrent
55
- * React unmounts there throw `Failed to execute 'removeChild' on 'Node'`, the
56
- * exact bug this component exists to avoid.
23
+ * This file is BOTH the package default entry (resolved by downstream `tsc`
24
+ * and non-Metro web bundlers via the `browser` condition through `index.web`)
25
+ * AND a clean delegation, so it carries no platform-specific imports.
57
26
  */
58
27
  export function ResponsiveSheet({
59
28
  open,
60
29
  onClose,
61
- side = DEFAULT_SIDE,
62
- breakpoint = DEFAULT_BREAKPOINT,
63
- width = DEFAULT_WIDTH,
64
- maxHeightRatio = DEFAULT_MAX_HEIGHT_RATIO,
30
+ side = 'left',
31
+ breakpoint = BREAKPOINT_MD,
32
+ width,
33
+ maxHeightRatio,
65
34
  inset,
66
- showHandle = true,
67
- dismissOnBackdrop = true,
35
+ showHandle,
36
+ dismissOnBackdrop,
68
37
  panelStyle,
69
38
  panelClassName,
70
39
  containerStyle,
71
40
  containerClassName,
72
- label = DEFAULT_LABEL,
41
+ label,
73
42
  children,
74
43
  }: ResponsiveSheetProps) {
75
- const theme = useTheme();
76
- const { width: viewportWidth, height: viewportHeight } = useWindowDimensions();
77
-
78
- // Presence: `mounted` keeps the node in the tree through the exit transition;
79
- // `shown` drives the enter/exit transform & opacity. On open we mount first,
80
- // then flip `shown` on the next frame so the browser registers the start
81
- // state and animates to the end. On close we flip `shown` off, then unmount
82
- // after the transition duration.
83
- const [mounted, setMounted] = useState(open);
84
- const [shown, setShown] = useState(false);
85
- const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
86
- const frameRef = useRef<FrameToken | null>(null);
87
-
88
- useEffect(() => {
89
- if (open) {
90
- if (closeTimerRef.current) {
91
- clearTimeout(closeTimerRef.current);
92
- closeTimerRef.current = null;
93
- }
94
- setMounted(true);
95
- // Defer to the next frame so the entry start state paints before we
96
- // transition to the shown state. Two frames guarantees the start
97
- // transform has committed before the toggle (a single frame can be
98
- // coalesced with the mount paint in some browsers).
99
- frameRef.current = scheduleFrame(() => {
100
- frameRef.current = scheduleFrame(() => setShown(true));
101
- });
102
- return () => {
103
- if (frameRef.current !== null) {
104
- cancelFrame(frameRef.current);
105
- frameRef.current = null;
106
- }
107
- };
108
- }
109
-
110
- // Closing: only run the exit if something is currently mounted.
111
- setShown(false);
112
- if (!mounted) return;
113
- closeTimerRef.current = setTimeout(() => {
114
- setMounted(false);
115
- closeTimerRef.current = null;
116
- }, ANIMATION_DURATION);
117
- return () => {
118
- if (closeTimerRef.current) {
119
- clearTimeout(closeTimerRef.current);
120
- closeTimerRef.current = null;
121
- }
122
- };
123
- }, [open, mounted]);
124
-
125
- // Escape-to-close while open (web only; document is undefined elsewhere).
126
- useEffect(() => {
127
- if (!open || typeof document === 'undefined') return;
128
- const handler = (e: KeyboardEvent) => {
129
- if (e.key === 'Escape') {
130
- e.stopPropagation();
131
- onClose();
132
- }
133
- };
134
- document.addEventListener('keydown', handler);
135
- return () => document.removeEventListener('keydown', handler);
136
- }, [open, onClose]);
137
-
138
- const handleBackdropPress = useCallback(() => {
139
- if (dismissOnBackdrop) onClose();
140
- }, [dismissOnBackdrop, onClose]);
141
-
142
- const isBottom = viewportWidth < breakpoint;
143
- const panelBackground = theme.colors.background;
144
-
145
- const panelTransition = useMemo<ViewStyle>(
146
- () =>
147
- ({
148
- transitionProperty: 'transform, opacity',
149
- transitionDuration: `${ANIMATION_DURATION}ms`,
150
- transitionTimingFunction: EASE_OUT,
151
- }) as ViewStyle,
152
- [],
153
- );
154
-
155
- const backdropTransition = useMemo<ViewStyle>(
156
- () =>
157
- ({
158
- transitionProperty: 'opacity',
159
- transitionDuration: `${ANIMATION_DURATION}ms`,
160
- transitionTimingFunction: EASE_OUT,
161
- }) as ViewStyle,
162
- [],
163
- );
164
-
165
- const panelGeometry = useMemo<ViewStyle>(() => {
166
- if (isBottom) {
167
- return {
168
- left: 0,
169
- right: 0,
170
- bottom: 0,
171
- maxHeight: Math.round(viewportHeight * maxHeightRatio),
172
- borderTopLeftRadius: PANEL_RADIUS,
173
- borderTopRightRadius: PANEL_RADIUS,
174
- transform: [{ translateY: shown ? 0 : '100%' }],
175
- opacity: shown ? 1 : 0,
176
- } as ViewStyle;
177
- }
178
-
179
- // Side-sheet: full height minus top/bottom inset, anchored to `side`,
180
- // offset by the matching horizontal inset. Width is capped so the panel
181
- // always leaves a gutter on the opposite edge.
182
- const insetTop = inset?.top ?? 0;
183
- const insetBottom = inset?.bottom ?? 0;
184
- const insetLeft = inset?.left ?? 0;
185
- const insetRight = inset?.right ?? 0;
186
- const anchorInset = side === 'left' ? insetLeft : insetRight;
187
- const oppositeInset = side === 'left' ? insetRight : insetLeft;
188
- const available = viewportWidth - anchorInset - oppositeInset - SIDE_SHEET_MIN_GUTTER;
189
- const cappedWidth = Math.max(0, Math.min(width, available));
190
- const hiddenSign = side === 'left' ? '-100%' : '100%';
191
-
192
- return {
193
- top: insetTop,
194
- bottom: insetBottom,
195
- [side]: anchorInset,
196
- width: cappedWidth,
197
- borderRadius: PANEL_RADIUS,
198
- transform: [{ translateX: shown ? 0 : hiddenSign }],
199
- opacity: shown ? 1 : 0,
200
- } as ViewStyle;
201
- }, [isBottom, shown, side, width, inset, viewportWidth, viewportHeight, maxHeightRatio]);
202
-
203
- if (!mounted) return null;
204
-
205
44
  return (
206
- <View
207
- style={[styles.root, containerStyle]}
208
- // NativeWind reads `className` at runtime; RN core does not type it on
209
- // View, so spread it conditionally (matching the Button/Fab pattern).
210
- {...(containerClassName ? ({ className: containerClassName } as Record<string, string>) : {})}
211
- pointerEvents="box-none"
45
+ <Dialog
46
+ open={open}
47
+ onClose={onClose}
48
+ placement={placementForSide(side, breakpoint)}
49
+ width={width}
50
+ maxHeightRatio={maxHeightRatio}
51
+ inset={inset}
52
+ showHandle={showHandle}
53
+ dismissOnBackdrop={dismissOnBackdrop}
54
+ panelStyle={panelStyle}
55
+ panelClassName={panelClassName}
56
+ containerStyle={containerStyle}
57
+ containerClassName={containerClassName}
58
+ label={label}
212
59
  >
213
- <Pressable
214
- testID={RESPONSIVE_SHEET_BACKDROP_TESTID}
215
- accessibilityRole="button"
216
- accessibilityLabel={`Dismiss ${label}`}
217
- onPress={handleBackdropPress}
218
- disabled={!dismissOnBackdrop}
219
- style={[
220
- styles.backdrop,
221
- backdropTransition,
222
- { opacity: shown ? BACKDROP_OPACITY : 0 },
223
- isWeb ? webBackdropBlur : null,
224
- ]}
225
- />
226
-
227
- <View
228
- accessibilityViewIsModal
229
- aria-label={label}
230
- {...(panelClassName ? ({ className: panelClassName } as Record<string, string>) : {})}
231
- style={[
232
- styles.panel,
233
- { backgroundColor: panelBackground, shadowColor: theme.colors.shadow },
234
- panelGeometry,
235
- panelTransition,
236
- panelStyle,
237
- ]}
238
- pointerEvents="auto"
239
- >
240
- {isBottom && showHandle ? (
241
- <View style={styles.handleRow} pointerEvents="none">
242
- <View style={[styles.handle, { backgroundColor: theme.colors.border }]} />
243
- </View>
244
- ) : null}
245
- {children}
246
- </View>
247
- </View>
60
+ {children}
61
+ </Dialog>
248
62
  );
249
63
  }
250
64
 
251
- // This module is the web/default impl, but it is also the fallback resolved by
252
- // a downstream consumer's `tsc` for the native target, so guard the web-only
253
- // `backdrop-blur` style behind a runtime check rather than assuming web.
254
- const isWeb = typeof document !== 'undefined';
255
-
256
65
  /**
257
- * Opaque token returned by `scheduleFrame`. `requestAnimationFrame` returns a
258
- * number; the `setTimeout` fallback returns a timer handle — both are kept so
259
- * `cancelFrame` can route to the matching canceller.
66
+ * Build the `Dialog` `placement` map for a `ResponsiveSheet`: a bottom-sheet
67
+ * below `breakpoint`, the anchored `side` at and above it. The `breakpoint`
68
+ * (px) is bucketed to the nearest Tailwind key so the side-sheet engages at the
69
+ * matching width.
260
70
  */
261
- type FrameToken = { raf: number } | { timer: ReturnType<typeof setTimeout> };
71
+ function placementForSide(
72
+ side: 'left' | 'right',
73
+ breakpoint: number,
74
+ ): ResponsiveDialogPlacement {
75
+ if (breakpoint <= BREAKPOINT_SM) return { base: 'bottom', sm: side };
76
+ if (breakpoint <= BREAKPOINT_MD) return { base: 'bottom', md: side };
77
+ if (breakpoint <= BREAKPOINT_LG) return { base: 'bottom', lg: side };
78
+ if (breakpoint <= BREAKPOINT_XL) return { base: 'bottom', xl: side };
79
+ return { base: 'bottom', xl: side };
80
+ }
262
81
 
263
82
  /**
264
- * Schedule a callback for the next paint. Uses `requestAnimationFrame` in the
265
- * browser; degrades to a 0ms timeout when rAF is unavailable (SSR/hydration,
266
- * non-DOM test envs) so the enter transition's `shown` flip still fires.
83
+ * @deprecated Stable testID for the dimmed backdrop dismiss button now
84
+ * provided by the underlying `<Dialog>`'s side/bottom backdrop.
267
85
  */
268
- function scheduleFrame(cb: () => void): FrameToken {
269
- if (typeof requestAnimationFrame === 'function') {
270
- return { raf: requestAnimationFrame(cb) };
271
- }
272
- return { timer: setTimeout(cb, 0) };
273
- }
274
-
275
- function cancelFrame(token: FrameToken): void {
276
- if ('raf' in token) {
277
- if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(token.raf);
278
- return;
279
- }
280
- clearTimeout(token.timer);
281
- }
282
-
283
- // `backdrop-filter` is web-only; react-native-web passes it through to the DOM.
284
- const webBackdropBlur = { backdropFilter: 'blur(2px)' } as unknown as ViewStyle;
285
-
286
- const styles = StyleSheet.create({
287
- root: {
288
- // `position: fixed` pins the overlay to the viewport on web; react-native
289
- // does not type `'fixed'`, so cast to the RN-accepted `'absolute'`. On
290
- // native the `.native` fork uses `StyleSheet.absoluteFill` instead.
291
- position: 'fixed' as 'absolute',
292
- top: 0,
293
- left: 0,
294
- right: 0,
295
- bottom: 0,
296
- zIndex: Z_INDEX.fullscreen,
297
- },
298
- backdrop: {
299
- position: 'absolute',
300
- top: 0,
301
- left: 0,
302
- right: 0,
303
- bottom: 0,
304
- backgroundColor: '#000',
305
- },
306
- panel: {
307
- position: 'absolute',
308
- overflow: 'hidden',
309
- shadowOpacity: 0.18,
310
- shadowRadius: 24,
311
- shadowOffset: { width: 0, height: 8 },
312
- },
313
- handleRow: {
314
- width: '100%',
315
- alignItems: 'center',
316
- paddingTop: 8,
317
- paddingBottom: 4,
318
- },
319
- handle: {
320
- width: HANDLE_WIDTH,
321
- height: HANDLE_HEIGHT,
322
- borderRadius: HANDLE_RADIUS,
323
- opacity: 0.5,
324
- },
325
- });
86
+ export const RESPONSIVE_SHEET_BACKDROP_TESTID = DIALOG_SHEET_BACKDROP_TESTID;
326
87
 
327
88
  export default ResponsiveSheet;
@@ -0,0 +1,76 @@
1
+ import React from 'react';
2
+
3
+ import { Dialog } from '../dialog/index.web';
4
+ import {
5
+ BREAKPOINT_LG,
6
+ BREAKPOINT_MD,
7
+ BREAKPOINT_SM,
8
+ BREAKPOINT_XL,
9
+ DIALOG_SHEET_BACKDROP_TESTID,
10
+ type ResponsiveDialogPlacement,
11
+ } from '../dialog/placement';
12
+ import type { ResponsiveSheetProps } from './types';
13
+
14
+ /**
15
+ * @deprecated Use `<Dialog placement={{ base: 'bottom', md: 'left' }}>` instead.
16
+ *
17
+ * Web fork of the deprecated `ResponsiveSheet` wrapper. Identical delegation to
18
+ * the default fork, but imports the web `Dialog` explicitly so non-Metro web
19
+ * bundlers never follow the native (reanimated) `Dialog` sibling.
20
+ */
21
+ export function ResponsiveSheet({
22
+ open,
23
+ onClose,
24
+ side = 'left',
25
+ breakpoint = BREAKPOINT_MD,
26
+ width,
27
+ maxHeightRatio,
28
+ inset,
29
+ showHandle,
30
+ dismissOnBackdrop,
31
+ panelStyle,
32
+ panelClassName,
33
+ containerStyle,
34
+ containerClassName,
35
+ label,
36
+ children,
37
+ }: ResponsiveSheetProps) {
38
+ return (
39
+ <Dialog
40
+ open={open}
41
+ onClose={onClose}
42
+ placement={placementForSide(side, breakpoint)}
43
+ width={width}
44
+ maxHeightRatio={maxHeightRatio}
45
+ inset={inset}
46
+ showHandle={showHandle}
47
+ dismissOnBackdrop={dismissOnBackdrop}
48
+ panelStyle={panelStyle}
49
+ panelClassName={panelClassName}
50
+ containerStyle={containerStyle}
51
+ containerClassName={containerClassName}
52
+ label={label}
53
+ >
54
+ {children}
55
+ </Dialog>
56
+ );
57
+ }
58
+
59
+ function placementForSide(
60
+ side: 'left' | 'right',
61
+ breakpoint: number,
62
+ ): ResponsiveDialogPlacement {
63
+ if (breakpoint <= BREAKPOINT_SM) return { base: 'bottom', sm: side };
64
+ if (breakpoint <= BREAKPOINT_MD) return { base: 'bottom', md: side };
65
+ if (breakpoint <= BREAKPOINT_LG) return { base: 'bottom', lg: side };
66
+ if (breakpoint <= BREAKPOINT_XL) return { base: 'bottom', xl: side };
67
+ return { base: 'bottom', xl: side };
68
+ }
69
+
70
+ /**
71
+ * @deprecated Stable testID for the dimmed backdrop dismiss button — now
72
+ * provided by the underlying `<Dialog>`'s side/bottom backdrop.
73
+ */
74
+ export const RESPONSIVE_SHEET_BACKDROP_TESTID = DIALOG_SHEET_BACKDROP_TESTID;
75
+
76
+ export default ResponsiveSheet;
@@ -1,14 +1,12 @@
1
1
  // Web variant of the `./responsive-sheet` barrel.
2
2
  //
3
- // The default barrel (`./index.ts`) re-exports from `./ResponsiveSheet`, which
4
- // is itself the reanimated-free web/CSS implementation Metro additionally
5
- // resolves `./ResponsiveSheet.native` for native targets. Web bundlers select
6
- // THIS file via the `"browser"` export condition in `package.json`'s
7
- // `exports['./responsive-sheet']`, pointing them explicitly at the CSS
8
- // implementation so a non-Metro bundler never follows the `.native` sibling
9
- // (which statically imports `react-native-reanimated`, a native-only module).
10
- // Types are platform-agnostic, so they come straight from `./types`.
11
- export { ResponsiveSheet, RESPONSIVE_SHEET_BACKDROP_TESTID } from './ResponsiveSheet';
3
+ // `ResponsiveSheet` is now a deprecated thin wrapper over `<Dialog>`. The
4
+ // default barrel (`./index.ts`) imports the default `Dialog`; THIS web barrel
5
+ // re-exports from `./ResponsiveSheet.web`, which imports the web `Dialog`
6
+ // explicitly so a non-Metro bundler never follows the native (reanimated)
7
+ // `Dialog` sibling. Web bundlers select this file via the `"browser"` export
8
+ // condition in `package.json`'s `exports['./responsive-sheet']`.
9
+ export { ResponsiveSheet, RESPONSIVE_SHEET_BACKDROP_TESTID } from './ResponsiveSheet.web';
12
10
  export type {
13
11
  ResponsiveSheetProps,
14
12
  ResponsiveSheetSide,
@@ -17,11 +17,14 @@ export interface ResponsiveSheetInset {
17
17
  }
18
18
 
19
19
  /**
20
+ * @deprecated Use `<Dialog placement={{ base: 'bottom', md: 'left' }}>` from
21
+ * `@oxyhq/bloom/dialog` instead. `ResponsiveSheet` is now a thin wrapper over
22
+ * `<Dialog>` retained for the migration window.
23
+ *
20
24
  * A responsive overlay sheet: an anchored side-sheet on wide screens and a
21
25
  * bottom-sheet (with drag handle) on small screens, switching by viewport
22
26
  * width. It is a plain controlled component — the host drives `open`/`onClose`
23
- * from its own state. It renders in place (no portal) so the host can scope it
24
- * inside a themed/positioned subtree.
27
+ * from its own state.
25
28
  */
26
29
  export interface ResponsiveSheetProps {
27
30
  /** Whether the sheet is open. Controlled by the host. */