@oxyhq/bloom 0.46.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 (160) hide show
  1. package/lib/commonjs/bottom-sheet/BottomSheetBase.js +36 -12
  2. package/lib/commonjs/bottom-sheet/BottomSheetBase.js.map +1 -1
  3. package/lib/commonjs/combobox/Combobox.js +3 -3
  4. package/lib/commonjs/combobox/Combobox.js.map +1 -1
  5. package/lib/commonjs/command/Command.js +3 -3
  6. package/lib/commonjs/command/Command.js.map +1 -1
  7. package/lib/commonjs/dialog/Dialog.js +103 -8
  8. package/lib/commonjs/dialog/Dialog.js.map +1 -1
  9. package/lib/commonjs/dialog/Dialog.web.js +189 -12
  10. package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
  11. package/lib/commonjs/dialog/DialogBottomSheet.js +111 -31
  12. package/lib/commonjs/dialog/DialogBottomSheet.js.map +1 -1
  13. package/lib/commonjs/dialog/DialogHeader.js +371 -0
  14. package/lib/commonjs/dialog/DialogHeader.js.map +1 -0
  15. package/lib/commonjs/dialog/DialogMorph.js +458 -0
  16. package/lib/commonjs/dialog/DialogMorph.js.map +1 -0
  17. package/lib/commonjs/dialog/index.js +14 -0
  18. package/lib/commonjs/dialog/index.js.map +1 -1
  19. package/lib/commonjs/dialog/index.web.js +14 -0
  20. package/lib/commonjs/dialog/index.web.js.map +1 -1
  21. package/lib/commonjs/index.js +11 -4
  22. package/lib/commonjs/index.js.map +1 -1
  23. package/lib/commonjs/index.web.js +11 -4
  24. package/lib/commonjs/index.web.js.map +1 -1
  25. package/lib/commonjs/react-native-className.d.js +6 -0
  26. package/lib/commonjs/react-native-className.d.js.map +1 -0
  27. package/lib/commonjs/{search-input → search}/index.js +5 -3
  28. package/lib/commonjs/search/index.js.map +1 -0
  29. package/lib/commonjs/settings-list/SettingsList.js +6 -2
  30. package/lib/commonjs/settings-list/SettingsList.js.map +1 -1
  31. package/lib/commonjs/surfaces/SurfaceHost.js +23 -9
  32. package/lib/commonjs/surfaces/SurfaceHost.js.map +1 -1
  33. package/lib/commonjs/text-field/index.js +22 -6
  34. package/lib/commonjs/text-field/index.js.map +1 -1
  35. package/lib/module/bottom-sheet/BottomSheetBase.js +36 -12
  36. package/lib/module/bottom-sheet/BottomSheetBase.js.map +1 -1
  37. package/lib/module/combobox/Combobox.js +3 -3
  38. package/lib/module/combobox/Combobox.js.map +1 -1
  39. package/lib/module/command/Command.js +3 -3
  40. package/lib/module/command/Command.js.map +1 -1
  41. package/lib/module/dialog/Dialog.js +104 -9
  42. package/lib/module/dialog/Dialog.js.map +1 -1
  43. package/lib/module/dialog/Dialog.web.js +192 -16
  44. package/lib/module/dialog/Dialog.web.js.map +1 -1
  45. package/lib/module/dialog/DialogBottomSheet.js +113 -33
  46. package/lib/module/dialog/DialogBottomSheet.js.map +1 -1
  47. package/lib/module/dialog/DialogHeader.js +362 -0
  48. package/lib/module/dialog/DialogHeader.js.map +1 -0
  49. package/lib/module/dialog/DialogMorph.js +451 -0
  50. package/lib/module/dialog/DialogMorph.js.map +1 -0
  51. package/lib/module/dialog/index.js +2 -0
  52. package/lib/module/dialog/index.js.map +1 -1
  53. package/lib/module/dialog/index.web.js +2 -0
  54. package/lib/module/dialog/index.web.js.map +1 -1
  55. package/lib/module/index.js +2 -2
  56. package/lib/module/index.js.map +1 -1
  57. package/lib/module/index.web.js +2 -2
  58. package/lib/module/index.web.js.map +1 -1
  59. package/lib/module/react-native-className.d.js +20 -0
  60. package/lib/module/react-native-className.d.js.map +1 -0
  61. package/lib/module/{search-input → search}/index.js +6 -2
  62. package/lib/module/search/index.js.map +1 -0
  63. package/lib/module/settings-list/SettingsList.js +6 -2
  64. package/lib/module/settings-list/SettingsList.js.map +1 -1
  65. package/lib/module/surfaces/SurfaceHost.js +23 -9
  66. package/lib/module/surfaces/SurfaceHost.js.map +1 -1
  67. package/lib/module/text-field/index.js +22 -6
  68. package/lib/module/text-field/index.js.map +1 -1
  69. package/lib/typescript/commonjs/bottom-sheet/BottomSheetBase.d.ts +31 -1
  70. package/lib/typescript/commonjs/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
  71. package/lib/typescript/commonjs/combobox/Combobox.d.ts +1 -1
  72. package/lib/typescript/commonjs/command/Command.d.ts +1 -1
  73. package/lib/typescript/commonjs/dialog/Dialog.d.ts.map +1 -1
  74. package/lib/typescript/commonjs/dialog/Dialog.web.d.ts.map +1 -1
  75. package/lib/typescript/commonjs/dialog/DialogBottomSheet.d.ts +2 -2
  76. package/lib/typescript/commonjs/dialog/DialogBottomSheet.d.ts.map +1 -1
  77. package/lib/typescript/commonjs/dialog/DialogHeader.d.ts +86 -0
  78. package/lib/typescript/commonjs/dialog/DialogHeader.d.ts.map +1 -0
  79. package/lib/typescript/commonjs/dialog/DialogMorph.d.ts +150 -0
  80. package/lib/typescript/commonjs/dialog/DialogMorph.d.ts.map +1 -0
  81. package/lib/typescript/commonjs/dialog/index.d.ts +4 -1
  82. package/lib/typescript/commonjs/dialog/index.d.ts.map +1 -1
  83. package/lib/typescript/commonjs/dialog/index.web.d.ts +4 -1
  84. package/lib/typescript/commonjs/dialog/index.web.d.ts.map +1 -1
  85. package/lib/typescript/commonjs/dialog/types.d.ts +105 -0
  86. package/lib/typescript/commonjs/dialog/types.d.ts.map +1 -1
  87. package/lib/typescript/commonjs/icons/common.d.ts +1 -0
  88. package/lib/typescript/commonjs/icons/common.d.ts.map +1 -1
  89. package/lib/typescript/commonjs/index.d.ts +3 -3
  90. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  91. package/lib/typescript/commonjs/index.web.d.ts +3 -3
  92. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  93. package/lib/typescript/commonjs/input-group/types.d.ts +1 -1
  94. package/lib/typescript/commonjs/{search-input → search}/index.d.ts +1 -1
  95. package/lib/typescript/commonjs/search/index.d.ts.map +1 -0
  96. package/lib/typescript/commonjs/surfaces/SurfaceHost.d.ts.map +1 -1
  97. package/lib/typescript/commonjs/surfaces/types.d.ts +26 -1
  98. package/lib/typescript/commonjs/surfaces/types.d.ts.map +1 -1
  99. package/lib/typescript/commonjs/text-field/index.d.ts +2 -1
  100. package/lib/typescript/commonjs/text-field/index.d.ts.map +1 -1
  101. package/lib/typescript/module/bottom-sheet/BottomSheetBase.d.ts +31 -1
  102. package/lib/typescript/module/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
  103. package/lib/typescript/module/combobox/Combobox.d.ts +1 -1
  104. package/lib/typescript/module/command/Command.d.ts +1 -1
  105. package/lib/typescript/module/dialog/Dialog.d.ts.map +1 -1
  106. package/lib/typescript/module/dialog/Dialog.web.d.ts.map +1 -1
  107. package/lib/typescript/module/dialog/DialogBottomSheet.d.ts +2 -2
  108. package/lib/typescript/module/dialog/DialogBottomSheet.d.ts.map +1 -1
  109. package/lib/typescript/module/dialog/DialogHeader.d.ts +86 -0
  110. package/lib/typescript/module/dialog/DialogHeader.d.ts.map +1 -0
  111. package/lib/typescript/module/dialog/DialogMorph.d.ts +150 -0
  112. package/lib/typescript/module/dialog/DialogMorph.d.ts.map +1 -0
  113. package/lib/typescript/module/dialog/index.d.ts +4 -1
  114. package/lib/typescript/module/dialog/index.d.ts.map +1 -1
  115. package/lib/typescript/module/dialog/index.web.d.ts +4 -1
  116. package/lib/typescript/module/dialog/index.web.d.ts.map +1 -1
  117. package/lib/typescript/module/dialog/types.d.ts +105 -0
  118. package/lib/typescript/module/dialog/types.d.ts.map +1 -1
  119. package/lib/typescript/module/icons/common.d.ts +1 -0
  120. package/lib/typescript/module/icons/common.d.ts.map +1 -1
  121. package/lib/typescript/module/index.d.ts +3 -3
  122. package/lib/typescript/module/index.d.ts.map +1 -1
  123. package/lib/typescript/module/index.web.d.ts +3 -3
  124. package/lib/typescript/module/index.web.d.ts.map +1 -1
  125. package/lib/typescript/module/input-group/types.d.ts +1 -1
  126. package/lib/typescript/module/{search-input → search}/index.d.ts +1 -1
  127. package/lib/typescript/module/search/index.d.ts.map +1 -0
  128. package/lib/typescript/module/surfaces/SurfaceHost.d.ts.map +1 -1
  129. package/lib/typescript/module/surfaces/types.d.ts +26 -1
  130. package/lib/typescript/module/surfaces/types.d.ts.map +1 -1
  131. package/lib/typescript/module/text-field/index.d.ts +2 -1
  132. package/lib/typescript/module/text-field/index.d.ts.map +1 -1
  133. package/package.json +8 -8
  134. package/src/__tests__/BottomSheet.test.tsx +47 -0
  135. package/src/__tests__/DialogBottomSheet.test.tsx +30 -5
  136. package/src/__tests__/DialogMorph.test.tsx +230 -0
  137. package/src/bottom-sheet/BottomSheetBase.tsx +72 -11
  138. package/src/combobox/Combobox.tsx +3 -3
  139. package/src/command/Command.tsx +3 -3
  140. package/src/dialog/Dialog.tsx +111 -14
  141. package/src/dialog/Dialog.web.tsx +186 -25
  142. package/src/dialog/DialogBottomSheet.tsx +122 -30
  143. package/src/dialog/DialogHeader.tsx +429 -0
  144. package/src/dialog/DialogMorph.tsx +576 -0
  145. package/src/dialog/index.ts +4 -0
  146. package/src/dialog/index.web.ts +4 -0
  147. package/src/dialog/types.ts +106 -0
  148. package/src/index.ts +3 -1
  149. package/src/index.web.ts +3 -1
  150. package/src/input-group/types.ts +1 -1
  151. package/src/react-native-className.d.ts +38 -0
  152. package/src/{search-input → search}/index.tsx +8 -5
  153. package/src/settings-list/SettingsList.tsx +6 -2
  154. package/src/surfaces/SurfaceHost.tsx +17 -1
  155. package/src/surfaces/types.ts +26 -1
  156. package/src/text-field/index.tsx +24 -5
  157. package/lib/commonjs/search-input/index.js.map +0 -1
  158. package/lib/module/search-input/index.js.map +0 -1
  159. package/lib/typescript/commonjs/search-input/index.d.ts.map +0 -1
  160. package/lib/typescript/module/search-input/index.d.ts.map +0 -1
@@ -1,9 +1,60 @@
1
+ import type { ReactNode } from 'react';
1
2
  import type { GestureResponderEvent, StyleProp, ViewStyle } from 'react-native';
2
3
 
3
4
  import type { ResponsiveDialogPlacement } from './placement';
4
5
 
5
6
  export type { DialogPlacement, ResponsiveDialogPlacement } from './placement';
6
7
 
8
+ /**
9
+ * Config for the Dialog's OWN navigation header — a DISTINCT surface mode from
10
+ * the declarative `title`/`description`/`actions` centered-heading path. When
11
+ * present it turns the Dialog into a scrolling page with a sticky, gradient top
12
+ * bar (left + right slots) and a large collapsing title that lives in the
13
+ * Dialog's own scroll content; as the large title scrolls under the bar a small
14
+ * title cross-fades into the bar center. Screens render NO header of their own.
15
+ *
16
+ * A surface seeds this from its registry config; a mounted screen may override
17
+ * or augment any field at runtime via `useDialogHeader` (a dynamic title, a Save
18
+ * action on the right, etc.).
19
+ */
20
+ export interface DialogHeaderConfig {
21
+ /** Collapsing title — rendered large in-content and small in the nav bar. */
22
+ title?: string;
23
+ /**
24
+ * A branded node rendered centered in the nav bar INSTEAD of the collapsing
25
+ * text title — e.g. a product wordmark. Always visible (it never collapses),
26
+ * and it suppresses the large in-content title/subtitle entirely, so the
27
+ * surface starts flush under the bar. `title`/`subtitle` are ignored while
28
+ * this is set. Must be referentially stable (memoize it) so the header does
29
+ * not thrash.
30
+ */
31
+ titleContent?: ReactNode;
32
+ /** Supporting copy under the large title (and, optionally, in the bar). */
33
+ subtitle?: string;
34
+ /**
35
+ * Whether to render the large collapsing title in the scroll content.
36
+ * Defaults to `true`. When `false`, only the small nav-bar title shows (always
37
+ * visible, no collapse) and the content starts flush under the bar.
38
+ */
39
+ largeTitle?: boolean;
40
+ /** Custom left slot node. Overrides the default back button. */
41
+ left?: ReactNode;
42
+ /** Custom right slot node. Overrides the default close button. */
43
+ right?: ReactNode;
44
+ /**
45
+ * When set, the default left slot renders a back button that calls this. A
46
+ * custom `left` node still wins. Set by the surface host when the surface can
47
+ * navigate back.
48
+ */
49
+ onBack?: () => void;
50
+ /**
51
+ * Whether the default right slot renders a close button (dismiss). Defaults to
52
+ * `true`. A custom `right` node still wins. `false` suppresses the close
53
+ * affordance entirely (e.g. a blocking surface).
54
+ */
55
+ showClose?: boolean;
56
+ }
57
+
7
58
  /**
8
59
  * Side-sheet inset (px) from the root overlay's edges. Lets a host place a
9
60
  * `left`/`right` placement "inside its own shell" — e.g. offset from the top
@@ -134,6 +185,40 @@ export type DialogProps = React.PropsWithChildren<{
134
185
  inset?: DialogInset;
135
186
  /** Whether to render the drag handle in bottom-sheet mode. Defaults to `true`. */
136
187
  showHandle?: boolean;
188
+ /**
189
+ * Turns on the Dialog's OWN navigation header (see {@link DialogHeaderConfig}).
190
+ * When present the Dialog renders a sticky gradient nav bar + a large
191
+ * collapsing title over its own scroll content, and its content is inset below
192
+ * the bar — a DISTINCT mode from the declarative
193
+ * `title`/`description`/`actions` centered heading (a confirm/alert dialog
194
+ * passes NO `header` and is unchanged). Screens render no header of their own;
195
+ * the title/slots come from this config, refined at runtime by a mounted
196
+ * screen's `useDialogHeader` contribution.
197
+ */
198
+ header?: DialogHeaderConfig;
199
+ /**
200
+ * Open-INTENT seed for a freshly-mounted placement branch, distinct from the
201
+ * controlled `open` prop. It ONLY seeds a branch's initial visible state on
202
+ * mount — it never switches the dialog into controlled close semantics, so
203
+ * the imperative `control.close()` still owns the exit animation and fires
204
+ * `onClose` post-exit (no synchronous-`onClose` entry leak). Set by the
205
+ * surface stack so a responsive `Dialog` that swaps its inner component across
206
+ * a breakpoint (centered card ↔ bottom sheet on resize) re-mounts the new
207
+ * branch ALREADY OPEN instead of blank. Ignored when the controlled `open`
208
+ * prop is provided (that path seeds itself).
209
+ */
210
+ startOpen?: boolean;
211
+ /**
212
+ * Whether the dialog wraps its content in an internal scroll container.
213
+ * Defaults to `true`. Set `false` when the content owns its own scrolling
214
+ * primitive (a `FlatList` / `SectionList` / any VirtualizedList, or its own
215
+ * `ScrollView`): the surface then renders children WITHOUT a ScrollView wrap —
216
+ * a bounded, overflow-clipped card on the centered/side placements and a
217
+ * non-scrollable `BottomSheet` body on the bottom placement — so the child
218
+ * gets a bounded height and owns its scroll, and no "VirtualizedList inside a
219
+ * plain ScrollView" warning is emitted.
220
+ */
221
+ scrollable?: boolean;
137
222
  /**
138
223
  * Inner padding (px) of the dialog content container, applied uniformly across
139
224
  * every placement (centered panel, side-sheet body, bottom-sheet body).
@@ -148,6 +233,27 @@ export type DialogProps = React.PropsWithChildren<{
148
233
  contentPadding?: number;
149
234
  /** Whether tapping the backdrop dismisses the dialog. Defaults to `true`. */
150
235
  dismissOnBackdrop?: boolean;
236
+ /**
237
+ * Whether the panel MORPHS across an in-place content swap. Defaults to
238
+ * `true`.
239
+ *
240
+ * When the content inside an OPEN dialog is replaced (a nav-within drill-in,
241
+ * as opposed to presenting a new surface on top), the panel animates from the
242
+ * outgoing content's size to the incoming one's while the incoming content
243
+ * fades in, instead of hard-cutting. The swap is signalled by the content
244
+ * itself through `useDialogFrame` — a dialog whose content never declares a
245
+ * frame is unaffected either way.
246
+ *
247
+ * The centered card morphs its height (about its centre) and its `maxWidth`;
248
+ * the bottom sheet morphs its height from its anchored edge; side sheets have
249
+ * fixed geometry and never morph. Morphing also requires the Dialog to own the
250
+ * scroll container: a `scrollable={false}` surface owns its own size, so its
251
+ * content only cross-fades.
252
+ *
253
+ * Set `false` for content that must never be resized into — e.g. a full-bleed
254
+ * picker that always fills the surface.
255
+ */
256
+ morph?: boolean;
151
257
  /**
152
258
  * Style overrides applied to the inner content container on native (the
153
259
  * floating bottom-sheet card) and to the modal panel on web.
package/src/index.ts CHANGED
@@ -59,6 +59,7 @@ export {
59
59
  alert,
60
60
  useDialogContext,
61
61
  useDialogControl,
62
+ useDialogHeader,
62
63
  } from './dialog';
63
64
  export type {
64
65
  AlertButton,
@@ -67,6 +68,7 @@ export type {
67
68
  DialogActionColor,
68
69
  DialogContextProps,
69
70
  DialogControlProps,
71
+ DialogHeaderConfig,
70
72
  DialogInset,
71
73
  DialogPlacement,
72
74
  DialogProps,
@@ -144,7 +146,7 @@ export type { AnimatedCheckProps, AnimatedCheckRef } from './animated-check';
144
146
  // Form components
145
147
  export * from './text-field';
146
148
  export * from './segmented-control';
147
- export { SearchInput } from './search-input';
149
+ export { Search } from './search';
148
150
  export { Label } from './label';
149
151
  export type { LabelProps } from './label';
150
152
  export { Field } from './field';
package/src/index.web.ts CHANGED
@@ -64,6 +64,7 @@ export {
64
64
  alert,
65
65
  useDialogContext,
66
66
  useDialogControl,
67
+ useDialogHeader,
67
68
  } from './dialog/index.web';
68
69
  export type {
69
70
  AlertButton,
@@ -72,6 +73,7 @@ export type {
72
73
  DialogActionColor,
73
74
  DialogContextProps,
74
75
  DialogControlProps,
76
+ DialogHeaderConfig,
75
77
  DialogInset,
76
78
  DialogPlacement,
77
79
  DialogProps,
@@ -149,7 +151,7 @@ export type { AnimatedCheckProps, AnimatedCheckRef } from './animated-check';
149
151
  // Form components
150
152
  export * from './text-field';
151
153
  export * from './segmented-control';
152
- export { SearchInput } from './search-input';
154
+ export { Search } from './search';
153
155
  export { Label } from './label';
154
156
  export type { LabelProps } from './label';
155
157
  export { Field } from './field';
@@ -21,7 +21,7 @@ export interface InputGroupAddonProps {
21
21
  export interface InputGroupProps {
22
22
  /**
23
23
  * Group children. Place `InputGroup.Addon` elements before/after the input
24
- * (a Bloom `TextFieldInput`, a `SearchInput`, or any control). The middle
24
+ * (a Bloom `TextFieldInput`, a `Search`, or any control). The middle
25
25
  * child stretches to fill.
26
26
  */
27
27
  children: React.ReactNode;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * `className` support for React Native core components in Bloom.
3
+ *
4
+ * Bloom styles some primitives with NativeWind/`react-native-css` utility
5
+ * classes (`className="..."`). react-native-css applies them at runtime — on web
6
+ * as real CSS classes emitted by the consumer's Tailwind pipeline (`@source`-
7
+ * scanning Bloom's lib), on native as compiled styles. This ambient augmentation
8
+ * makes `className` type-valid on the RN primitives we set it on.
9
+ *
10
+ * Heritage-free (a plain interface-merge `declare module`, no `extends` / import
11
+ * of RN's own module graph) so it never drags a second `@types/react` copy into
12
+ * the RN 0.85 / React 19 tree — the same hazard the `mergeRefs` note calls out.
13
+ */
14
+ // `export {}` makes this file a MODULE, so the `declare module` below MERGES
15
+ // with react-native's real types (an augmentation) instead of replacing the
16
+ // module and hiding its exports.
17
+ export {};
18
+
19
+ declare module 'react-native' {
20
+ interface ViewProps {
21
+ className?: string;
22
+ }
23
+ interface TextProps {
24
+ className?: string;
25
+ }
26
+ interface ImageProps {
27
+ className?: string;
28
+ }
29
+ interface PressableProps {
30
+ className?: string;
31
+ }
32
+ interface ScrollViewProps {
33
+ className?: string;
34
+ }
35
+ interface TextInputProps {
36
+ className?: string;
37
+ }
38
+ }
@@ -8,7 +8,10 @@ import { TextField, TextFieldIcon, TextFieldInput, type TextFieldInputProps } fr
8
8
  import { MagnifyingGlass_Stroke2_Corner0_Rounded as MagnifyingGlassIcon } from '../icons/MagnifyingGlass';
9
9
  import { TimesLarge_Stroke2_Corner0_Rounded as X } from '../icons/Times';
10
10
 
11
- type SearchInputProps = Omit<TextFieldInputProps, 'label'> & {
11
+ /** Fully-rounded (pill) corner radius clamps to half the field height. */
12
+ const PILL_RADIUS = 999;
13
+
14
+ type SearchProps = Omit<TextFieldInputProps, 'label'> & {
12
15
  label?: TextFieldInputProps['label'];
13
16
  /**
14
17
  * Called when the user presses the clear (X) button.
@@ -16,14 +19,14 @@ type SearchInputProps = Omit<TextFieldInputProps, 'label'> & {
16
19
  onClearText?: () => void;
17
20
  };
18
21
 
19
- export const SearchInput = forwardRef<TextInput, SearchInputProps>(
20
- function SearchInput({ value, label = 'Search', onClearText, ...rest }, ref) {
22
+ export const Search = forwardRef<TextInput, SearchProps>(
23
+ function Search({ value, label = 'Search', onClearText, ...rest }, ref) {
21
24
  const theme = useTheme();
22
25
  const showClear = value != null && value.length > 0;
23
26
 
24
27
  return (
25
28
  <View style={[a.w_full, a.relative]}>
26
- <TextField>
29
+ <TextField radius={PILL_RADIUS}>
27
30
  <TextFieldIcon icon={MagnifyingGlassIcon} />
28
31
  <TextFieldInput
29
32
  inputRef={ref}
@@ -70,4 +73,4 @@ export const SearchInput = forwardRef<TextInput, SearchInputProps>(
70
73
  );
71
74
  },
72
75
  );
73
- SearchInput.displayName = 'SearchInput';
76
+ Search.displayName = 'Search';
@@ -207,7 +207,7 @@ const styles = StyleSheet.create({
207
207
  itemContainer: {
208
208
  flexDirection: 'row',
209
209
  alignItems: 'center',
210
- paddingHorizontal: 16,
210
+ paddingHorizontal: 12,
211
211
  paddingVertical: 10,
212
212
  minHeight: 44,
213
213
  gap: 12,
@@ -243,7 +243,11 @@ const styles = StyleSheet.create({
243
243
  marginBottom: 16,
244
244
  },
245
245
  groupCard: {
246
- marginHorizontal: 16,
246
+ // No horizontal self-inset: a `SettingsListGroup` fills its parent's content
247
+ // width, so the single horizontal inset comes from the ONE content padding
248
+ // the screen/Dialog already applies (the same `px-screen-margin` that pads
249
+ // the title/search). Keeping a self-margin here double-inset grouped rows on
250
+ // every screen that also applies that padding.
247
251
  borderRadius: 16,
248
252
  overflow: 'hidden',
249
253
  },
@@ -38,6 +38,9 @@ function placementFor(p: SurfacePresentation): Partial<DialogProps> {
38
38
  showHandle: p.showHandle,
39
39
  dismissOnBackdrop: p.dismissOnBackdrop,
40
40
  contentPadding: p.contentPadding,
41
+ header: p.header,
42
+ scrollable: p.scrollable,
43
+ morph: p.morph,
41
44
  style: p.style,
42
45
  panelStyle: p.panelStyle,
43
46
  panelClassName: p.panelClassName,
@@ -112,7 +115,20 @@ export function createSurfaceHost(Dialog: DialogComponent) {
112
115
  );
113
116
 
114
117
  return (
115
- <Dialog control={control} onClose={handleClose} layer={index} {...placement}>
118
+ // `startOpen` is the surface's open-INTENT (true while not closing). It
119
+ // ONLY seeds a freshly-mounted Dialog branch's visible state — the
120
+ // imperative `control` still owns open/close — so when a responsive Dialog
121
+ // swaps its inner component on resize (centered card ↔ bottom sheet) the
122
+ // new branch mounts STILL OPEN instead of dismissing the surface. A real
123
+ // dismiss (backdrop/Escape/back/programmatic) still resolves + splices
124
+ // exactly once via `control.close()` → `handleClose`, post-exit.
125
+ <Dialog
126
+ control={control}
127
+ onClose={handleClose}
128
+ layer={index}
129
+ startOpen={status !== 'closing'}
130
+ {...placement}
131
+ >
116
132
  <SurfaceContext.Provider value={controls}>
117
133
  {entry.render(controls)}
118
134
  </SurfaceContext.Provider>
@@ -1,7 +1,7 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { StyleProp, ViewStyle } from 'react-native';
3
3
 
4
- import type { DialogInset, ResponsiveDialogPlacement } from '../dialog/types';
4
+ import type { DialogHeaderConfig, DialogInset, ResponsiveDialogPlacement } from '../dialog/types';
5
5
 
6
6
  /**
7
7
  * How a surface renders — the placement + the `Dialog` sizing/chrome options a
@@ -39,6 +39,31 @@ export interface SurfacePresentation {
39
39
  dismissOnBackdrop?: boolean;
40
40
  /** Inner padding (px) of the surface content container. Forwarded to `Dialog`. */
41
41
  contentPadding?: number;
42
+ /**
43
+ * Turns on the Dialog's OWN navigation header for this surface (sticky gradient
44
+ * nav bar + large collapsing title over the surface's scroll content). Seeds
45
+ * the header content; a mounted screen refines it at runtime via
46
+ * `useDialogHeader`. Omit for a plain surface (a confirm/alert dialog). Forwarded
47
+ * to `Dialog`.
48
+ */
49
+ header?: DialogHeaderConfig;
50
+ /**
51
+ * Whether the surface wraps its content in an internal scroll container.
52
+ * Defaults to `true`. Set `false` when the surface's content owns its own
53
+ * scrolling primitive (a `FlatList` / `SectionList` / VirtualizedList, or its
54
+ * own `ScrollView`) so the host does not nest it in a ScrollView. Forwarded
55
+ * to `Dialog`.
56
+ */
57
+ scrollable?: boolean;
58
+ /**
59
+ * Whether the surface MORPHS its panel when its content is swapped in place —
60
+ * navigation WITHIN this surface, as opposed to presenting a new surface on
61
+ * top. Defaults to `true`. The content signals the swap through
62
+ * `useDialogFrame`; a surface whose content never declares a frame is
63
+ * unaffected. Set `false` for content that must never be resized into (a
64
+ * full-bleed picker). Forwarded to `Dialog`.
65
+ */
66
+ morph?: boolean;
42
67
  /** Style overrides applied to the inner content container. */
43
68
  style?: StyleProp<ViewStyle>;
44
69
  /** Style overrides for the side/bottom placement panel surface. */
@@ -30,6 +30,8 @@ import { Text } from '../typography';
30
30
  const Context = createContext<{
31
31
  inputRef: React.RefObject<TextInput | null> | null;
32
32
  isInvalid: boolean;
33
+ /** Corner radius of the input chrome. A large value (e.g. 999) reads as a pill. */
34
+ radius: number;
33
35
  hovered: boolean;
34
36
  onHoverIn: () => void;
35
37
  onHoverOut: () => void;
@@ -39,6 +41,7 @@ const Context = createContext<{
39
41
  }>({
40
42
  inputRef: null,
41
43
  isInvalid: false,
44
+ radius: 10,
42
45
  hovered: false,
43
46
  onHoverIn: () => {},
44
47
  onHoverOut: () => {},
@@ -48,11 +51,24 @@ const Context = createContext<{
48
51
  });
49
52
  Context.displayName = 'TextFieldContext';
50
53
 
54
+ /**
55
+ * RN Web paints the browser's default focus outline on the underlying `<input>`.
56
+ * That rectangle follows the input's own box — NOT the TextField chrome — so on a
57
+ * pill (large radius) it pokes out of the corners and reads as a stray border on
58
+ * focus. Strip it so ONLY the TextField's own focus chrome (the animated border)
59
+ * signals focus. `outlineStyle`/`outlineWidth` are RN-Web style properties absent
60
+ * from RN's core types; `web()` guarantees this object is never applied on native.
61
+ */
62
+ const WEB_INPUT_OUTLINE_RESET: TextStyle | undefined =
63
+ Platform.OS === 'web'
64
+ ? ({ outlineWidth: 0, outlineStyle: 'none' } as unknown as TextStyle)
65
+ : undefined;
66
+
51
67
  export type TextFieldProps = React.PropsWithChildren<
52
- { isInvalid?: boolean } & ViewStyleProp
68
+ { isInvalid?: boolean; radius?: number } & ViewStyleProp
53
69
  >;
54
70
 
55
- export function TextField({ children, isInvalid = false, style }: TextFieldProps) {
71
+ export function TextField({ children, isInvalid = false, radius = 10, style }: TextFieldProps) {
56
72
  const inputRef = useRef<TextInput>(null);
57
73
  const {
58
74
  state: hovered,
@@ -75,8 +91,9 @@ export function TextField({ children, isInvalid = false, style }: TextFieldProps
75
91
  onFocus,
76
92
  onBlur,
77
93
  isInvalid,
94
+ radius,
78
95
  }),
79
- [inputRef, hovered, onHoverIn, onHoverOut, focused, onFocus, onBlur, isInvalid],
96
+ [inputRef, hovered, onHoverIn, onHoverOut, focused, onFocus, onBlur, isInvalid, radius],
80
97
  );
81
98
 
82
99
  return (
@@ -240,6 +257,7 @@ export function TextFieldInput({
240
257
  marginTop: 2,
241
258
  marginBottom: 2,
242
259
  }),
260
+ WEB_INPUT_OUTLINE_RESET,
243
261
  style,
244
262
  ]) as TextStyle;
245
263
 
@@ -272,7 +290,7 @@ export function TextFieldInput({
272
290
  a.z_10,
273
291
  a.absolute,
274
292
  a.inset_0,
275
- { borderRadius: 10 },
293
+ { borderRadius: ctx.radius },
276
294
  { backgroundColor: theme.colors.contrast50 },
277
295
  { borderColor: 'transparent', borderWidth: 2 },
278
296
  ctx.hovered ? chromeHover : {},
@@ -393,6 +411,7 @@ function FloatingLabelInput({
393
411
  },
394
412
  android({ paddingTop: 24, paddingBottom: 6 }),
395
413
  web({ paddingTop: 25, paddingBottom: 7 }),
414
+ WEB_INPUT_OUTLINE_RESET,
396
415
  style,
397
416
  ]) as TextStyle;
398
417
 
@@ -444,7 +463,7 @@ function FloatingLabelInput({
444
463
  a.z_10,
445
464
  a.absolute,
446
465
  a.inset_0,
447
- { borderRadius: 10 },
466
+ { borderRadius: ctx.radius },
448
467
  { backgroundColor: theme.colors.contrast50 },
449
468
  { borderColor: 'transparent', borderWidth: 2 },
450
469
  ctx.hovered ? chromeHover : {},
@@ -1 +0,0 @@
1
- {"version":3,"names":["_react","require","_reactNative","_useTheme","_index","_button","_index2","_MagnifyingGlass","_Times","_jsxRuntime","SearchInput","exports","forwardRef","value","label","onClearText","rest","ref","theme","useTheme","showClear","length","jsxs","View","style","a","w_full","relative","children","TextField","jsx","TextFieldIcon","icon","MagnifyingGlassIcon","TextFieldInput","inputRef","placeholder","returnKeyType","keyboardAppearance","mode","selectTextOnFocus","Platform","OS","autoFocus","accessibilityRole","autoCorrect","autoComplete","autoCapitalize","paddingRight","undefined","absolute","z_20","my_auto","inset_0","justify_center","pr_sm","left","Button","testID","onPress","accessibilityLabel","hitSlop","top","bottom","right","variant","size","TimesLarge_Stroke2_Corner0_Rounded","fill","colors","textSecondary","padding","minHeight","displayName"],"sourceRoot":"../../../src","sources":["search-input/index.tsx"],"mappings":"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","ignoreList":[]}
@@ -1 +0,0 @@
1
- {"version":3,"names":["forwardRef","Platform","View","useTheme","atoms","a","Button","TextField","TextFieldIcon","TextFieldInput","MagnifyingGlass_Stroke2_Corner0_Rounded","MagnifyingGlassIcon","TimesLarge_Stroke2_Corner0_Rounded","X","jsx","_jsx","jsxs","_jsxs","SearchInput","value","label","onClearText","rest","ref","theme","showClear","length","style","w_full","relative","children","icon","inputRef","placeholder","returnKeyType","keyboardAppearance","mode","selectTextOnFocus","OS","autoFocus","accessibilityRole","autoCorrect","autoComplete","autoCapitalize","paddingRight","undefined","absolute","z_20","my_auto","inset_0","justify_center","pr_sm","left","testID","onPress","accessibilityLabel","hitSlop","top","bottom","right","variant","size","fill","colors","textSecondary","padding","minHeight","displayName"],"sourceRoot":"../../../src","sources":["search-input/index.tsx"],"mappings":"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","ignoreList":[]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/search-input/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,SAAS,EAAQ,MAAM,cAAc,CAAC;AAK9D,OAAO,EAA4C,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAYnG,eAAO,MAAM,WAAW;YAPd,mBAAmB,CAAC,OAAO,CAAC;IACpC;;OAEG;kBACW,MAAM,IAAI;6CAwDzB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/search-input/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,SAAS,EAAQ,MAAM,cAAc,CAAC;AAK9D,OAAO,EAA4C,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAYnG,eAAO,MAAM,WAAW;YAPd,mBAAmB,CAAC,OAAO,CAAC;IACpC;;OAEG;kBACW,MAAM,IAAI;6CAwDzB,CAAC"}