@oxyhq/bloom 0.48.0 → 0.49.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 (88) hide show
  1. package/lib/commonjs/bottom-sheet/BottomSheetBase.js +3 -1
  2. package/lib/commonjs/bottom-sheet/BottomSheetBase.js.map +1 -1
  3. package/lib/commonjs/dialog/Dialog.js +30 -4
  4. package/lib/commonjs/dialog/Dialog.js.map +1 -1
  5. package/lib/commonjs/dialog/Dialog.web.js +48 -14
  6. package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
  7. package/lib/commonjs/dialog/DialogBottomSheet.js +30 -5
  8. package/lib/commonjs/dialog/DialogBottomSheet.js.map +1 -1
  9. package/lib/commonjs/dialog/DialogHeader.js +12 -6
  10. package/lib/commonjs/dialog/DialogHeader.js.map +1 -1
  11. package/lib/commonjs/dialog/DialogMorph.js +458 -0
  12. package/lib/commonjs/dialog/DialogMorph.js.map +1 -0
  13. package/lib/commonjs/dialog/index.js +7 -0
  14. package/lib/commonjs/dialog/index.js.map +1 -1
  15. package/lib/commonjs/dialog/index.web.js +7 -0
  16. package/lib/commonjs/dialog/index.web.js.map +1 -1
  17. package/lib/commonjs/settings-list/SettingsList.js +1 -1
  18. package/lib/commonjs/surfaces/SurfaceHost.js +1 -0
  19. package/lib/commonjs/surfaces/SurfaceHost.js.map +1 -1
  20. package/lib/module/bottom-sheet/BottomSheetBase.js +3 -1
  21. package/lib/module/bottom-sheet/BottomSheetBase.js.map +1 -1
  22. package/lib/module/dialog/Dialog.js +30 -4
  23. package/lib/module/dialog/Dialog.js.map +1 -1
  24. package/lib/module/dialog/Dialog.web.js +48 -15
  25. package/lib/module/dialog/Dialog.web.js.map +1 -1
  26. package/lib/module/dialog/DialogBottomSheet.js +31 -6
  27. package/lib/module/dialog/DialogBottomSheet.js.map +1 -1
  28. package/lib/module/dialog/DialogHeader.js +12 -6
  29. package/lib/module/dialog/DialogHeader.js.map +1 -1
  30. package/lib/module/dialog/DialogMorph.js +451 -0
  31. package/lib/module/dialog/DialogMorph.js.map +1 -0
  32. package/lib/module/dialog/index.js +1 -0
  33. package/lib/module/dialog/index.js.map +1 -1
  34. package/lib/module/dialog/index.web.js +1 -0
  35. package/lib/module/dialog/index.web.js.map +1 -1
  36. package/lib/module/settings-list/SettingsList.js +1 -1
  37. package/lib/module/surfaces/SurfaceHost.js +1 -0
  38. package/lib/module/surfaces/SurfaceHost.js.map +1 -1
  39. package/lib/typescript/commonjs/bottom-sheet/BottomSheetBase.d.ts +8 -1
  40. package/lib/typescript/commonjs/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
  41. package/lib/typescript/commonjs/dialog/Dialog.d.ts.map +1 -1
  42. package/lib/typescript/commonjs/dialog/Dialog.web.d.ts.map +1 -1
  43. package/lib/typescript/commonjs/dialog/DialogBottomSheet.d.ts +2 -2
  44. package/lib/typescript/commonjs/dialog/DialogBottomSheet.d.ts.map +1 -1
  45. package/lib/typescript/commonjs/dialog/DialogHeader.d.ts.map +1 -1
  46. package/lib/typescript/commonjs/dialog/DialogMorph.d.ts +150 -0
  47. package/lib/typescript/commonjs/dialog/DialogMorph.d.ts.map +1 -0
  48. package/lib/typescript/commonjs/dialog/index.d.ts +2 -0
  49. package/lib/typescript/commonjs/dialog/index.d.ts.map +1 -1
  50. package/lib/typescript/commonjs/dialog/index.web.d.ts +2 -0
  51. package/lib/typescript/commonjs/dialog/index.web.d.ts.map +1 -1
  52. package/lib/typescript/commonjs/dialog/types.d.ts +30 -0
  53. package/lib/typescript/commonjs/dialog/types.d.ts.map +1 -1
  54. package/lib/typescript/commonjs/surfaces/SurfaceHost.d.ts.map +1 -1
  55. package/lib/typescript/commonjs/surfaces/types.d.ts +9 -0
  56. package/lib/typescript/commonjs/surfaces/types.d.ts.map +1 -1
  57. package/lib/typescript/module/bottom-sheet/BottomSheetBase.d.ts +8 -1
  58. package/lib/typescript/module/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
  59. package/lib/typescript/module/dialog/Dialog.d.ts.map +1 -1
  60. package/lib/typescript/module/dialog/Dialog.web.d.ts.map +1 -1
  61. package/lib/typescript/module/dialog/DialogBottomSheet.d.ts +2 -2
  62. package/lib/typescript/module/dialog/DialogBottomSheet.d.ts.map +1 -1
  63. package/lib/typescript/module/dialog/DialogHeader.d.ts.map +1 -1
  64. package/lib/typescript/module/dialog/DialogMorph.d.ts +150 -0
  65. package/lib/typescript/module/dialog/DialogMorph.d.ts.map +1 -0
  66. package/lib/typescript/module/dialog/index.d.ts +2 -0
  67. package/lib/typescript/module/dialog/index.d.ts.map +1 -1
  68. package/lib/typescript/module/dialog/index.web.d.ts +2 -0
  69. package/lib/typescript/module/dialog/index.web.d.ts.map +1 -1
  70. package/lib/typescript/module/dialog/types.d.ts +30 -0
  71. package/lib/typescript/module/dialog/types.d.ts.map +1 -1
  72. package/lib/typescript/module/surfaces/SurfaceHost.d.ts.map +1 -1
  73. package/lib/typescript/module/surfaces/types.d.ts +9 -0
  74. package/lib/typescript/module/surfaces/types.d.ts.map +1 -1
  75. package/package.json +1 -1
  76. package/src/__tests__/DialogMorph.test.tsx +230 -0
  77. package/src/bottom-sheet/BottomSheetBase.tsx +13 -1
  78. package/src/dialog/Dialog.tsx +38 -11
  79. package/src/dialog/Dialog.web.tsx +40 -10
  80. package/src/dialog/DialogBottomSheet.tsx +35 -5
  81. package/src/dialog/DialogHeader.tsx +14 -5
  82. package/src/dialog/DialogMorph.tsx +576 -0
  83. package/src/dialog/index.ts +2 -0
  84. package/src/dialog/index.web.ts +2 -0
  85. package/src/dialog/types.ts +30 -0
  86. package/src/settings-list/SettingsList.tsx +1 -1
  87. package/src/surfaces/SurfaceHost.tsx +1 -0
  88. package/src/surfaces/types.ts +9 -0
@@ -1 +1 @@
1
- {"version":3,"file":"SurfaceHost.d.ts","sourceRoot":"","sources":["../../../../src/surfaces/SurfaceHost.tsx"],"names":[],"mappings":"AAAA,OAAO,KAKN,MAAM,OAAO,CAAC;AAIf,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAYnD,KAAK,eAAe,GAAG,KAAK,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;AA+BxD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,eAAe;;;;;;uBAqGhB;YAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;SAAE;;;EAcrE"}
1
+ {"version":3,"file":"SurfaceHost.d.ts","sourceRoot":"","sources":["../../../../src/surfaces/SurfaceHost.tsx"],"names":[],"mappings":"AAAA,OAAO,KAKN,MAAM,OAAO,CAAC;AAIf,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAYnD,KAAK,eAAe,GAAG,KAAK,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;AAgCxD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,eAAe;;;;;;uBAqGhB;YAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;SAAE;;;EAcrE"}
@@ -53,6 +53,15 @@ export interface SurfacePresentation {
53
53
  * to `Dialog`.
54
54
  */
55
55
  scrollable?: boolean;
56
+ /**
57
+ * Whether the surface MORPHS its panel when its content is swapped in place —
58
+ * navigation WITHIN this surface, as opposed to presenting a new surface on
59
+ * top. Defaults to `true`. The content signals the swap through
60
+ * `useDialogFrame`; a surface whose content never declares a frame is
61
+ * unaffected. Set `false` for content that must never be resized into (a
62
+ * full-bleed picker). Forwarded to `Dialog`.
63
+ */
64
+ morph?: boolean;
56
65
  /** Style overrides applied to the inner content container. */
57
66
  style?: StyleProp<ViewStyle>;
58
67
  /** Style overrides for the side/bottom placement panel surface. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/surfaces/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/surfaces/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/bloom",
3
- "version": "0.48.0",
3
+ "version": "0.49.0",
4
4
  "description": "Bloom UI — Oxy ecosystem component library for React Native + Expo + Web",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -0,0 +1,230 @@
1
+ import React from 'react';
2
+ import { act, render } from '@testing-library/react-native';
3
+ import type { LayoutChangeEvent, ViewStyle } from 'react-native';
4
+
5
+ import {
6
+ DialogMorphContent,
7
+ useDialogFrame,
8
+ useDialogMorph,
9
+ } from '../dialog/DialogMorph';
10
+
11
+ /**
12
+ * Size morphing across an in-place content swap.
13
+ *
14
+ * These assert the STATE MACHINE — when the panel height is taken over and when
15
+ * it is handed back to natural sizing. The reanimated mock settles `withTiming`
16
+ * synchronously, so the frame-by-frame interpolation is NOT observable here; it
17
+ * is verified in a real browser, the only place reanimated's web mapper ticks.
18
+ */
19
+
20
+ const layout = (height: number): LayoutChangeEvent =>
21
+ ({
22
+ nativeEvent: { layout: { x: 0, y: 0, width: 320, height } },
23
+ }) as LayoutChangeEvent;
24
+
25
+ interface HarnessProps {
26
+ morphEnabled?: boolean;
27
+ measurable?: boolean;
28
+ frameKey: string;
29
+ frameMorph?: boolean;
30
+ onState: (state: {
31
+ panelStyle: ViewStyle;
32
+ onPanelLayout: (e: LayoutChangeEvent) => void;
33
+ onContentLayout: (e: LayoutChangeEvent) => void;
34
+ }) => void;
35
+ }
36
+
37
+ /** Drives one surface: measurement in, panel style + a frame-declaring child out. */
38
+ function Harness({
39
+ morphEnabled = true,
40
+ measurable = true,
41
+ frameKey,
42
+ frameMorph,
43
+ onState,
44
+ }: HarnessProps) {
45
+ const morph = useDialogMorph({ enabled: morphEnabled, measurable, maxHeight: 800 });
46
+ onState({
47
+ panelStyle: morph.panelStyle as ViewStyle,
48
+ onPanelLayout: morph.onPanelLayout,
49
+ onContentLayout: morph.onContentLayout,
50
+ });
51
+ return (
52
+ <DialogMorphContent morph={morph}>
53
+ <Frame frameKey={frameKey} frameMorph={frameMorph} />
54
+ </DialogMorphContent>
55
+ );
56
+ }
57
+
58
+ function Frame({ frameKey, frameMorph }: { frameKey: string; frameMorph?: boolean }) {
59
+ useDialogFrame({ key: frameKey, morph: frameMorph });
60
+ return null;
61
+ }
62
+
63
+ /** Mount the harness and return a handle for driving layout + swapping frames. */
64
+ function mountHarness(props: Omit<Partial<HarnessProps>, 'onState' | 'frameKey'> = {}) {
65
+ let state: Parameters<HarnessProps['onState']>[0] | null = null;
66
+ const onState: HarnessProps['onState'] = (next) => {
67
+ state = next;
68
+ };
69
+ const element = (frameKey: string, extra: Omit<Partial<HarnessProps>, 'onState'> = {}) => (
70
+ <Harness frameKey={frameKey} onState={onState} {...props} {...extra} />
71
+ );
72
+ const { rerender, unmount } = render(element('a'));
73
+ const read = () => {
74
+ if (!state) throw new Error('harness did not render');
75
+ return state;
76
+ };
77
+ return {
78
+ /** The panel height the surface currently resolves to. */
79
+ panelHeight: () => read().panelStyle.height,
80
+ /** Report the panel's and then the content's laid-out heights. */
81
+ measure(panelHeight: number, contentHeight: number) {
82
+ act(() => {
83
+ read().onPanelLayout(layout(panelHeight));
84
+ read().onContentLayout(layout(contentHeight));
85
+ });
86
+ },
87
+ /**
88
+ * Report only the content's natural height (the incoming frame settling).
89
+ * Followed by a re-render: the mock's `useAnimatedStyle` only re-evaluates
90
+ * on render, whereas the real one re-runs on the shared-value write.
91
+ */
92
+ measureContent(contentHeight: number, frameKey = 'b') {
93
+ act(() => read().onContentLayout(layout(contentHeight)));
94
+ rerender(element(frameKey));
95
+ },
96
+ /** Declare a frame, then re-render so the resulting panel style is observable. */
97
+ swapTo(frameKey: string, extra: Omit<Partial<HarnessProps>, 'onState'> = {}) {
98
+ rerender(element(frameKey, extra));
99
+ rerender(element(frameKey, extra));
100
+ },
101
+ unmount,
102
+ };
103
+ }
104
+
105
+ describe('Dialog size morphing', () => {
106
+ it('leaves the panel naturally sized until a frame actually changes', () => {
107
+ const h = mountHarness();
108
+ expect(h.panelHeight()).toBe('auto');
109
+
110
+ h.measure(300, 298);
111
+ expect(h.panelHeight()).toBe('auto');
112
+
113
+ // Re-declaring the SAME frame is not a swap.
114
+ h.swapTo('a');
115
+ expect(h.panelHeight()).toBe('auto');
116
+ h.unmount();
117
+ });
118
+
119
+ it('pins the panel at the outgoing size, then releases onto the incoming one', () => {
120
+ const h = mountHarness();
121
+ h.measure(300, 298);
122
+
123
+ h.swapTo('b');
124
+ // Held at the size the outgoing frame was painted at — no jump while the
125
+ // incoming content is still being measured.
126
+ expect(h.panelHeight()).toBe(300);
127
+
128
+ // The incoming frame reports 500 of natural height; the panel carries the
129
+ // 2px of chrome calibrated from the outgoing frame, then hands the height
130
+ // back so later content changes size the panel normally again.
131
+ h.measureContent(500);
132
+ expect(h.panelHeight()).toBe('auto');
133
+ h.unmount();
134
+ });
135
+
136
+ it('releases the pin even when the incoming frame is taller than the bound', () => {
137
+ const h = mountHarness();
138
+ h.measure(300, 300);
139
+ h.swapTo('b');
140
+ expect(h.panelHeight()).toBe(300);
141
+
142
+ h.measureContent(5000);
143
+ expect(h.panelHeight()).toBe('auto');
144
+ h.unmount();
145
+ });
146
+
147
+ it('releases without reshaping when the incoming frame fills the space it is given', () => {
148
+ const h = mountHarness();
149
+ h.measure(300, 300);
150
+ h.swapTo('b');
151
+ expect(h.panelHeight()).toBe(300);
152
+
153
+ // A child that flexes to its container measures ~0 inside a content-sized
154
+ // wrapper; collapsing the panel onto that would empty the surface.
155
+ h.measureContent(0);
156
+ expect(h.panelHeight()).toBe('auto');
157
+ h.unmount();
158
+ });
159
+
160
+ it('never pins when nothing has been measured yet', () => {
161
+ const h = mountHarness();
162
+ h.swapTo('b');
163
+ expect(h.panelHeight()).toBe('auto');
164
+ h.unmount();
165
+ });
166
+
167
+ it('honours the per-frame morph opt-out', () => {
168
+ const h = mountHarness();
169
+ h.measure(300, 300);
170
+ h.swapTo('b', { frameMorph: false });
171
+ expect(h.panelHeight()).toBe('auto');
172
+ h.unmount();
173
+ });
174
+
175
+ it('honours the surface-level morph opt-out', () => {
176
+ const h = mountHarness({ morphEnabled: false });
177
+ h.measure(300, 300);
178
+ h.swapTo('b');
179
+ expect(h.panelHeight()).toBe('auto');
180
+ h.unmount();
181
+ });
182
+
183
+ it('does not pin a surface whose content owns its own size', () => {
184
+ const h = mountHarness({ measurable: false });
185
+ h.measure(300, 300);
186
+ h.swapTo('b');
187
+ expect(h.panelHeight()).toBe('auto');
188
+ h.unmount();
189
+ });
190
+
191
+ it('drives maxWidth only on a placement that can vary it', () => {
192
+ let fixedWidth: ViewStyle | null = null;
193
+ let varyingWidth: ViewStyle | null = null;
194
+ render(
195
+ <WidthHarness
196
+ onStyle={(s) => {
197
+ fixedWidth = s;
198
+ }}
199
+ />,
200
+ );
201
+ render(
202
+ <WidthHarness
203
+ maxWidth={480}
204
+ onStyle={(s) => {
205
+ varyingWidth = s;
206
+ }}
207
+ />,
208
+ );
209
+ expect((fixedWidth as unknown as ViewStyle).maxWidth).toBeUndefined();
210
+ expect((varyingWidth as unknown as ViewStyle).maxWidth).toBe(480);
211
+ });
212
+ });
213
+
214
+ function WidthHarness({
215
+ maxWidth,
216
+ onStyle,
217
+ }: {
218
+ maxWidth?: number;
219
+ onStyle: (style: ViewStyle) => void;
220
+ }) {
221
+ const morph = useDialogMorph({ enabled: true, measurable: true, maxHeight: 800, maxWidth });
222
+ onStyle(morph.panelStyle as ViewStyle);
223
+ return null;
224
+ }
225
+
226
+ describe('useDialogFrame outside a morph-capable surface', () => {
227
+ it('is a no-op (a side sheet, or a plain confirm dialog)', () => {
228
+ expect(() => render(<Frame frameKey="only" />)).not.toThrow();
229
+ });
230
+ });
@@ -6,6 +6,7 @@ import {
6
6
  Pressable,
7
7
  Dimensions,
8
8
  Platform,
9
+ type LayoutChangeEvent,
9
10
  type ViewStyle,
10
11
  type StyleProp,
11
12
  } from 'react-native';
@@ -151,6 +152,13 @@ export interface BottomSheetProps {
151
152
  * Dialog to float its sticky nav header over the sheet's scroll content.
152
153
  */
153
154
  headerOverlay?: React.ReactNode;
155
+ /**
156
+ * Layout callback for the SHEET CARD — the same box `style` targets. Lets a
157
+ * host observe the card's rendered size (the Dialog measures it to morph the
158
+ * sheet between two content frames). Fires on every size change, including
159
+ * while a host animates the card's height.
160
+ */
161
+ onLayout?: (event: LayoutChangeEvent) => void;
154
162
  }
155
163
 
156
164
  /**
@@ -209,6 +217,7 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
209
217
  handleComponent,
210
218
  scrollY: externalScrollY,
211
219
  headerOverlay,
220
+ onLayout,
212
221
  } = props;
213
222
 
214
223
  const insets = useSafeAreaInsets();
@@ -780,7 +789,10 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
780
789
  </Animated.View>
781
790
 
782
791
  <GestureDetector gesture={panGesture}>
783
- <Animated.View style={[dynamicStyles.sheet, sheetMarginStyle, sheetStyle, sheetHeightStyle, style]}>
792
+ <Animated.View
793
+ onLayout={onLayout}
794
+ style={[dynamicStyles.sheet, sheetMarginStyle, sheetStyle, sheetHeightStyle, style]}
795
+ >
784
796
  {backgroundComponent?.({ style: styles.background })}
785
797
 
786
798
  {handleSlot}
@@ -38,6 +38,7 @@ import {
38
38
  DialogNavHeader,
39
39
  useDialogHeaderController,
40
40
  } from './DialogHeader';
41
+ import { DialogMorphContent, useDialogMorph } from './DialogMorph';
41
42
  import {
42
43
  ANIMATION_DURATION,
43
44
  DEFAULT_DIALOG_CONTENT_PADDING,
@@ -113,6 +114,7 @@ function CenteredOrSideDialog({
113
114
  actions,
114
115
  header,
115
116
  scrollable,
117
+ morph,
116
118
  placement,
117
119
  layer,
118
120
  width = DEFAULT_SIDE_WIDTH,
@@ -131,6 +133,19 @@ function CenteredOrSideDialog({
131
133
  const isSide = placement === 'left' || placement === 'right';
132
134
  const headerController = useDialogHeaderController();
133
135
  const scrollableResolved = scrollable ?? true;
136
+ const { height: viewportHeight } = useWindowDimensions();
137
+
138
+ // Size morphing across an in-place content swap. The detached centered card
139
+ // is content-hugging, so animating its height reshapes it about its own
140
+ // centre; the side placements are anchored top-to-bottom at a fixed width and
141
+ // have no size to morph, so they never pin (`measurable: false`). The card's
142
+ // own `maxHeight` (inside `BottomSheet`) does the exact capping — the viewport
143
+ // is just the bound the reshape may not exceed.
144
+ const morphState = useDialogMorph({
145
+ enabled: morph !== false && !isSide,
146
+ measurable: scrollableResolved && !isSide,
147
+ maxHeight: viewportHeight,
148
+ });
134
149
 
135
150
  // Imperative open state for the side placement (the BottomSheet path owns its
136
151
  // own visibility via its ref instead).
@@ -223,16 +238,21 @@ function CenteredOrSideDialog({
223
238
  [close],
224
239
  );
225
240
 
226
- const sheetStyle = useMemo(
227
- () => ({
228
- maxWidth: 500,
229
- backgroundColor: theme.colors.background,
230
- // All four corners rounded — bloom's BottomSheet defaults to top-only
231
- // radius in flush mode, but we use `detached` so the whole card is
232
- // floating and rounded uniformly.
233
- borderRadius: 20,
234
- }),
235
- [theme.colors.background],
241
+ const sheetStyle = useMemo<StyleProp<ViewStyle>>(
242
+ () => [
243
+ {
244
+ maxWidth: 500,
245
+ backgroundColor: theme.colors.background,
246
+ // All four corners rounded bloom's BottomSheet defaults to top-only
247
+ // radius in flush mode, but we use `detached` so the whole card is
248
+ // floating and rounded uniformly.
249
+ borderRadius: 20,
250
+ },
251
+ // Drives the card's `height` only while a morph is in flight; at rest it
252
+ // resolves to `height: 'auto'` — the card's normal content sizing.
253
+ morphState.panelStyle,
254
+ ],
255
+ [theme.colors.background, morphState.panelStyle],
236
256
  );
237
257
 
238
258
  const bodyNode = (
@@ -322,6 +342,8 @@ function CenteredOrSideDialog({
322
342
  // Stronger dim when a Dialog is stacked over another sheet so the
323
343
  // underlying sheet's handle/content doesn't bleed through.
324
344
  backdropOpacity={0.7}
345
+ // Measures the floating card — the size a morph starts from.
346
+ onLayout={morphState.onPanelLayout}
325
347
  style={sheetStyle}
326
348
  >
327
349
  <Context.Provider value={context}>
@@ -342,7 +364,12 @@ function CenteredOrSideDialog({
342
364
  panelStyle,
343
365
  ]}
344
366
  >
345
- {header ? headerBody : bodyNode}
367
+ {/* The morph layer is its own node INSIDE the class-name'd container:
368
+ a className'd component's `onLayout` never fires on web, and this
369
+ one has to measure the incoming frame. */}
370
+ <DialogMorphContent morph={morphState}>
371
+ {header ? headerBody : bodyNode}
372
+ </DialogMorphContent>
346
373
  </View>
347
374
  </Context.Provider>
348
375
  </BottomSheet>
@@ -16,6 +16,7 @@ import {
16
16
  type StyleProp,
17
17
  type ViewStyle,
18
18
  } from 'react-native';
19
+ import Animated from 'react-native-reanimated';
19
20
  import { RemoveScrollBar } from 'react-remove-scroll-bar';
20
21
 
21
22
  import { Portal } from '../portal/index.web';
@@ -31,11 +32,13 @@ import {
31
32
  DialogNavHeader,
32
33
  useDialogHeaderController,
33
34
  } from './DialogHeader';
35
+ import { DialogMorphContent, useDialogMorph } from './DialogMorph';
34
36
  import {
35
37
  ANIMATION_DURATION,
36
38
  CENTER_FADE_OUT_DURATION,
37
39
  DEFAULT_CENTER_MAX_WIDTH,
38
40
  DEFAULT_DIALOG_CONTENT_PADDING,
41
+ DEFAULT_MAX_HEIGHT_RATIO,
39
42
  DEFAULT_SIDE_WIDTH,
40
43
  DIALOG_SHEET_BACKDROP_TESTID,
41
44
  EASE_OUT,
@@ -149,6 +152,7 @@ function CenterOrSideDialog({
149
152
  contentPadding = DEFAULT_DIALOG_CONTENT_PADDING,
150
153
  maxHeightRatio,
151
154
  scrollable,
155
+ morph,
152
156
  style,
153
157
  panelStyle,
154
158
  panelClassName,
@@ -323,6 +327,7 @@ function CenterOrSideDialog({
323
327
  contentPadding={contentPadding}
324
328
  maxHeightRatio={maxHeightRatio}
325
329
  scrollable={scrollable}
330
+ morph={morph}
326
331
  surfaceZIndex={dialogZIndex.surface}
327
332
  isClosing={isClosing}
328
333
  >
@@ -383,6 +388,7 @@ function DialogPanel({
383
388
  contentPadding,
384
389
  maxHeightRatio,
385
390
  scrollable,
391
+ morph,
386
392
  surfaceZIndex,
387
393
  isClosing,
388
394
  children,
@@ -398,6 +404,7 @@ function DialogPanel({
398
404
  contentPadding: number;
399
405
  maxHeightRatio?: number;
400
406
  scrollable?: boolean;
407
+ morph?: boolean;
401
408
  surfaceZIndex: number;
402
409
  isClosing: boolean;
403
410
  children?: React.ReactNode;
@@ -407,6 +414,19 @@ function DialogPanel({
407
414
  const titleId = useId();
408
415
  const descriptionId = useId();
409
416
  const headerController = useDialogHeaderController();
417
+ const { height: viewportHeight } = useWindowDimensions();
418
+ const heightRatio = maxHeightRatio ?? DEFAULT_MAX_HEIGHT_RATIO;
419
+
420
+ // Size morphing across an in-place content swap. The centered card is the one
421
+ // placement whose width can vary too, so `maxWidth` is handed over as well.
422
+ // The card's own `maxHeight` (the ratio percentage below) does the exact
423
+ // capping — the viewport is just the bound the reshape may not exceed.
424
+ const morphState = useDialogMorph({
425
+ enabled: morph !== false,
426
+ measurable: scrollable !== false,
427
+ maxHeight: viewportHeight,
428
+ maxWidth,
429
+ });
410
430
 
411
431
  const body = (
412
432
  <DialogBody
@@ -421,12 +441,13 @@ function DialogPanel({
421
441
  );
422
442
 
423
443
  return (
424
- <View
444
+ <Animated.View
425
445
  role="dialog"
426
446
  aria-label={label}
427
447
  aria-labelledby={title ? titleId : undefined}
428
448
  aria-describedby={description ? descriptionId : undefined}
429
449
  testID={testID}
450
+ onLayout={morphState.onPanelLayout}
430
451
  onStartShouldSetResponder={() => true}
431
452
  onResponderRelease={stopPropagation}
432
453
  {...({ onClick: stopPropagation } as Record<string, unknown>)}
@@ -441,7 +462,7 @@ function DialogPanel({
441
462
  // capped at `maxHeightRatio` of the viewport and scrolls inside the
442
463
  // rounded card (via the ScrollView below) — consumers never add their
443
464
  // own height cap / ScrollView. `overflow: hidden` clips to the radius.
444
- maxHeight: `${Math.round((maxHeightRatio ?? 0.9) * 100)}%`,
465
+ maxHeight: `${Math.round(heightRatio * 100)}%`,
445
466
  overflow: 'hidden',
446
467
  backgroundColor: theme.colors.background,
447
468
  borderWidth: 1,
@@ -455,6 +476,10 @@ function DialogPanel({
455
476
  isClosing
456
477
  ? ({ animation: `bloomDialogZoomFadeOut ease-in ${FADE_OUT_DURATION}ms forwards` } as ViewStyle)
457
478
  : ({ animation: 'bloomDialogZoomFadeIn cubic-bezier(0.16, 1, 0.3, 1) 0.3s' } as ViewStyle),
479
+ // Drives `height` (and `maxWidth`) only while a morph is in flight; at
480
+ // rest it resolves to `height: 'auto'` — the natural sizing above. Placed
481
+ // before `style` so a consumer's explicit size still wins.
482
+ morphState.panelStyle,
458
483
  style,
459
484
  ]}
460
485
  >
@@ -471,10 +496,10 @@ function DialogPanel({
471
496
  collapse={scrollable !== false}
472
497
  />
473
498
  {scrollable === false ? (
474
- <View style={{ flex: 1, minHeight: 0 }}>
499
+ <DialogMorphContent morph={morphState} style={{ flex: 1, minHeight: 0 }}>
475
500
  <View style={{ height: DIALOG_NAV_BAR_HEIGHT }} />
476
501
  <DialogHeaderProvider controller={headerController}>{body}</DialogHeaderProvider>
477
- </View>
502
+ </DialogMorphContent>
478
503
  ) : (
479
504
  <ScrollView
480
505
  style={{ flexShrink: 1 }}
@@ -485,8 +510,10 @@ function DialogPanel({
485
510
  headerController.scrollY.value = e.nativeEvent.contentOffset.y;
486
511
  }}
487
512
  >
488
- <DialogLargeTitle controller={headerController} header={header} />
489
- <DialogHeaderProvider controller={headerController}>{body}</DialogHeaderProvider>
513
+ <DialogMorphContent morph={morphState}>
514
+ <DialogLargeTitle controller={headerController} header={header} />
515
+ <DialogHeaderProvider controller={headerController}>{body}</DialogHeaderProvider>
516
+ </DialogMorphContent>
490
517
  </ScrollView>
491
518
  )}
492
519
  </>
@@ -496,19 +523,22 @@ function DialogPanel({
496
523
  // `maxHeight` + `overflow: hidden` cap it and `flex: 1` + `minHeight: 0`
497
524
  // hand the child a bounded height to scroll into — with NO wrapping
498
525
  // ScrollView, so a VirtualizedList never nests inside one.
499
- <View style={{ flex: 1, minHeight: 0, padding: contentPadding }}>
526
+ <DialogMorphContent
527
+ morph={morphState}
528
+ style={{ flex: 1, minHeight: 0, padding: contentPadding }}
529
+ >
500
530
  {body}
501
- </View>
531
+ </DialogMorphContent>
502
532
  ) : (
503
533
  <ScrollView
504
534
  style={{ flexShrink: 1 }}
505
535
  contentContainerStyle={{ padding: contentPadding }}
506
536
  showsVerticalScrollIndicator={false}
507
537
  >
508
- {body}
538
+ <DialogMorphContent morph={morphState}>{body}</DialogMorphContent>
509
539
  </ScrollView>
510
540
  )}
511
- </View>
541
+ </Animated.View>
512
542
  );
513
543
  }
514
544
 
@@ -6,7 +6,12 @@ import React, {
6
6
  useMemo,
7
7
  useRef,
8
8
  } from 'react';
9
- import { View, type StyleProp, type ViewStyle } from 'react-native';
9
+ import {
10
+ useWindowDimensions,
11
+ View,
12
+ type StyleProp,
13
+ type ViewStyle,
14
+ } from 'react-native';
10
15
 
11
16
  import { BottomSheet, type BottomSheetRef } from '../bottom-sheet';
12
17
  import { useTheme } from '../theme/use-theme';
@@ -19,6 +24,7 @@ import {
19
24
  DialogNavHeader,
20
25
  useDialogHeaderController,
21
26
  } from './DialogHeader';
27
+ import { DialogMorphContent, useDialogMorph } from './DialogMorph';
22
28
  import {
23
29
  DEFAULT_DIALOG_CONTENT_PADDING,
24
30
  DEFAULT_MAX_HEIGHT_RATIO,
@@ -62,6 +68,7 @@ export function DialogBottomSheet({
62
68
  dismissOnBackdrop = true,
63
69
  contentPadding = DEFAULT_DIALOG_CONTENT_PADDING,
64
70
  scrollable,
71
+ morph,
65
72
  style,
66
73
  panelStyle,
67
74
  panelClassName,
@@ -72,6 +79,7 @@ export function DialogBottomSheet({
72
79
  }: DialogBottomSheetProps) {
73
80
  const isControlled = controlledOpen !== undefined;
74
81
  const theme = useTheme();
82
+ const { height: viewportHeight } = useWindowDimensions();
75
83
  const ref = useRef<BottomSheetRef>(null);
76
84
  const closeCallbacks = useRef<(() => void)[]>([]);
77
85
  const titleId = useId();
@@ -155,6 +163,19 @@ export function DialogBottomSheet({
155
163
  [dismissOnBackdrop],
156
164
  );
157
165
 
166
+ const scrollableResolved = scrollable ?? true;
167
+
168
+ // Size morphing across an in-place content swap. The sheet is anchored at the
169
+ // bottom, so animating its height makes it grow/shrink from that edge; its
170
+ // width is fixed by the placement. The sheet's own `maxHeight` (the ratio
171
+ // percentage + the safe-area clamp inside `BottomSheet`) does the exact
172
+ // capping — the viewport is just the bound the reshape may not exceed.
173
+ const morphState = useDialogMorph({
174
+ enabled: morph !== false,
175
+ measurable: scrollableResolved,
176
+ maxHeight: viewportHeight,
177
+ });
178
+
158
179
  // `panelStyle` paints the sheet surface (e.g. a brand background). It composes
159
180
  // AFTER the BottomSheet's internal background so a host palette wins; the
160
181
  // shape (radius, max height) is supplied here and remains overridable.
@@ -168,8 +189,11 @@ export function DialogBottomSheet({
168
189
  maxHeight: maxHeightPercent,
169
190
  },
170
191
  panelStyle,
192
+ // Drives the card's `height` only while a morph is in flight; at rest it
193
+ // resolves to `height: 'auto'` — the sheet's normal content sizing.
194
+ morphState.panelStyle,
171
195
  ];
172
- }, [theme.colors.background, maxHeightRatio, panelStyle]);
196
+ }, [theme.colors.background, maxHeightRatio, panelStyle, morphState.panelStyle]);
173
197
 
174
198
  // Does this dialog render bloom's declarative chrome (title / description /
175
199
  // actions)? This no longer gates the SCROLL decision (that is now the explicit
@@ -183,8 +207,6 @@ export function DialogBottomSheet({
183
207
  description !== undefined ||
184
208
  (actions !== undefined && actions.length > 0);
185
209
 
186
- const scrollableResolved = scrollable ?? true;
187
-
188
210
  const dialogBody = (
189
211
  <DialogBody
190
212
  titleId={titleId}
@@ -264,6 +286,8 @@ export function DialogBottomSheet({
264
286
  // Stronger dim than a lone sheet so an underlying sheet's handle/content
265
287
  // doesn't bleed through when a Dialog is stacked over one.
266
288
  backdropOpacity={SHEET_BACKDROP_OPACITY + 0.3}
289
+ // Measures the sheet card — the size a morph starts from.
290
+ onLayout={morphState.onPanelLayout}
267
291
  style={sheetStyle}
268
292
  >
269
293
  <Context.Provider value={context}>
@@ -285,7 +309,12 @@ export function DialogBottomSheet({
285
309
  panelStyle,
286
310
  ]}
287
311
  >
288
- {header ? headerBody : dialogBody}
312
+ {/* The morph layer is its own node INSIDE the class-name'd container:
313
+ a className'd component's `onLayout` never fires on web, and this
314
+ one has to measure the incoming frame. */}
315
+ <DialogMorphContent morph={morphState}>
316
+ {header ? headerBody : dialogBody}
317
+ </DialogMorphContent>
289
318
  </View>
290
319
  </Context.Provider>
291
320
  </BottomSheet>
@@ -313,6 +342,7 @@ export type DialogBottomSheetProps = Pick<
313
342
  | 'dismissOnBackdrop'
314
343
  | 'contentPadding'
315
344
  | 'scrollable'
345
+ | 'morph'
316
346
  | 'style'
317
347
  | 'panelStyle'
318
348
  | 'panelClassName'