@xsolla/xui-modal 0.158.0 → 0.160.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  type ModalVariant,
24
24
  type WorkAreaProps,
25
25
  type WorkAreaSize,
26
- } from '@xsolla/xui-modal';
26
+ } from "@xsolla/xui-modal";
27
27
  ```
28
28
 
29
29
  `useModalId` is re-exported from `@xsolla/xui-core` and is consumed by portaled descendants (e.g. `Select`, `Dropdown`) so click-outside detection ignores content that logically belongs to the modal.
@@ -31,9 +31,9 @@ import {
31
31
  ## Quick start
32
32
 
33
33
  ```tsx
34
- import * as React from 'react';
35
- import { Modal, ModalProvider, useModal } from '@xsolla/xui-modal';
36
- import { Button } from '@xsolla/xui-button';
34
+ import * as React from "react";
35
+ import { Modal, ModalProvider, useModal } from "@xsolla/xui-modal";
36
+ import { Button } from "@xsolla/xui-button";
37
37
 
38
38
  function Trigger() {
39
39
  const [open, close] = useModal(() => (
@@ -60,56 +60,57 @@ export default function QuickStart() {
60
60
 
61
61
  The dialog component with header, body, and footer zones. Accepts a `ref` forwarded to the inner `WorkArea`. Unknown props are spread onto the root `Box`. `size` is a typed `ModalProps` field but is never consumed by `Modal`'s internal layout; it passes through via `...rest` to the root `Box` with no built-in styling effect.
62
62
 
63
- | Prop | Type | Default | Description |
64
- | --- | --- | --- | --- |
65
- | `children` | `ReactNode` | — | **Required.** Modal body content. |
66
- | `type` | `"popup" \| "bottom-sheet" \| "full-screen"` | `"popup"` | Presentation type. Drives positioning, border-radius, and shadow. |
67
- | `align` | `"left" \| "center"` | `"left"` | Content alignment within the body. |
68
- | `onClose` | `() => void` | | Close callback. When provided, renders a close (X) button and enables Escape and click-outside dismissal. |
69
- | `onBack` | `() => void` | — | Back callback. Renders a chevron-left button on the left of the header. |
70
- | `header` | `ReactNode` | — | Custom header replaces the default back/close row. |
71
- | `footer` | `ReactNode` | — | Footer content rendered below children. |
72
- | `openContent` | `boolean` | `false` | Removes inner padding so children fill edge-to-edge. |
73
- | `closeOutside` | `boolean` | `true` | Whether clicking outside closes the modal (requires `onClose`). |
74
- | `maxWidth` | `string \| number` | `680` | Forced to `100%` for `bottom-sheet` and `full-screen`. |
75
- | `minHeight` | `string \| number` | | Minimum height of the modal. |
76
- | `styled` | `CSSProperties` | — | Inline overrides on the modal container. |
77
- | `title` | `string` | — | Visually hidden accessible title drives `aria-labelledby`. For a visible heading, render it inside `children` or pass a custom `header`. |
78
- | `aria-label` | `string` | — | Alternative accessible name when `title` is not used. |
79
- | `aria-describedby` | `string` | — | ID of an element describing modal content. |
80
- | `initialFocusRef` | `RefObject<HTMLElement>` | — | Element to focus on open. Falls back to the close button, then the first focusable child. |
63
+ | Prop | Type | Default | Description |
64
+ | ------------------ | -------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
65
+ | `testID` | `string` | — | Test ID for testing frameworks. On web this renders as `data-testid`; on React Native it renders as `testID`. |
66
+ | `children` | `ReactNode` | | **Required.** Modal body content. |
67
+ | `type` | `"popup" \| "bottom-sheet" \| "full-screen"` | `"popup"` | Presentation type. Drives positioning, border-radius, and shadow. |
68
+ | `align` | `"left" \| "center"` | `"left"` | Content alignment within the body. |
69
+ | `onClose` | `() => void` | — | Close callback. When provided, renders a close (X) button and enables Escape and click-outside dismissal. |
70
+ | `onBack` | `() => void` | — | Back callback. Renders a chevron-left button on the left of the header. |
71
+ | `header` | `ReactNode` | | Custom header replaces the default back/close row. |
72
+ | `footer` | `ReactNode` | | Footer content rendered below children. |
73
+ | `openContent` | `boolean` | `false` | Removes inner padding so children fill edge-to-edge. |
74
+ | `closeOutside` | `boolean` | `true` | Whether clicking outside closes the modal (requires `onClose`). |
75
+ | `maxWidth` | `string \| number` | `680` | Forced to `100%` for `bottom-sheet` and `full-screen`. |
76
+ | `minHeight` | `string \| number` | — | Minimum height of the modal. |
77
+ | `styled` | `CSSProperties` | — | Inline overrides on the modal container. |
78
+ | `title` | `string` | — | Visually hidden accessible title drives `aria-labelledby`. For a visible heading, render it inside `children` or pass a custom `header`. |
79
+ | `aria-label` | `string` | — | Alternative accessible name when `title` is not used. |
80
+ | `aria-describedby` | `string` | — | ID of an element describing modal content. |
81
+ | `initialFocusRef` | `RefObject<HTMLElement>` | — | Element to focus on open. Falls back to the close button, then the first focusable child. |
81
82
 
82
83
  Inherits `ThemeOverrideProps` (`themeMode`, `themeProductContext`).
83
84
 
84
85
  ### Modal types
85
86
 
86
- | Type | Positioning | Border radius | Shadow | Max width |
87
- | --- | --- | --- | --- | --- |
88
- | `popup` (default) | Centred in the overlay | All corners | Yes | `680px` (or custom) |
89
- | `bottom-sheet` | Anchored to the bottom | Top corners only | Yes | `100%` (forced) |
90
- | `full-screen` | Fills the viewport | `0` | None | `100%` (forced) |
87
+ | Type | Positioning | Border radius | Shadow | Max width |
88
+ | ----------------- | ---------------------- | ---------------- | ------ | ------------------- |
89
+ | `popup` (default) | Centred in the overlay | All corners | Yes | `680px` (or custom) |
90
+ | `bottom-sheet` | Anchored to the bottom | Top corners only | Yes | `100%` (forced) |
91
+ | `full-screen` | Fills the viewport | `0` | None | `100%` (forced) |
91
92
 
92
93
  ### `<ModalProvider>`
93
94
 
94
95
  Wraps your app, manages the open-modal stack, and renders `ModalRoot`.
95
96
 
96
- | Prop | Type | Description |
97
- | --- | --- | --- |
97
+ | Prop | Type | Description |
98
+ | ---------- | ----------- | ---------------------------------- |
98
99
  | `children` | `ReactNode` | **Required.** Application content. |
99
100
 
100
101
  ### `<WorkArea>`
101
102
 
102
103
  Internal chrome used by `Modal`. Can be rendered standalone for custom layouts.
103
104
 
104
- | Prop | Type | Default | Description |
105
- | --- | --- | --- | --- |
106
- | `children` | `ReactNode` | — | **Required.** Content. |
107
- | `type` | `"popup" \| "bottom-sheet" \| "full-screen"` | `"popup"` | Affects border-radius and shadow. |
108
- | `align` | `"left" \| "center"` | — | Content alignment. |
109
- | `indent` | `"sm" \| "md" \| "lg"` | — | Padding hint passed alongside the work-area sizing. |
110
- | `stretched` | `boolean` | `false` | Stretch to full height. |
111
- | `openContent` | `boolean` | `false` | Edge-to-edge mode. |
112
- | `fetching` | `boolean` | `false` | Renders a loading placeholder when `true`. |
105
+ | Prop | Type | Default | Description |
106
+ | ------------- | -------------------------------------------- | --------- | --------------------------------------------------- |
107
+ | `children` | `ReactNode` | — | **Required.** Content. |
108
+ | `type` | `"popup" \| "bottom-sheet" \| "full-screen"` | `"popup"` | Affects border-radius and shadow. |
109
+ | `align` | `"left" \| "center"` | — | Content alignment. |
110
+ | `indent` | `"sm" \| "md" \| "lg"` | — | Padding hint passed alongside the work-area sizing. |
111
+ | `stretched` | `boolean` | `false` | Stretch to full height. |
112
+ | `openContent` | `boolean` | `false` | Edge-to-edge mode. |
113
+ | `fetching` | `boolean` | `false` | Renders a loading placeholder when `true`. |
113
114
 
114
115
  Inherits `ThemeOverrideProps` (`themeMode`, `themeProductContext`).
115
116
 
@@ -133,26 +134,26 @@ The raw React context. Most apps consume `useModal`, but advanced cases can call
133
134
 
134
135
  ### Exported types
135
136
 
136
- | Type | Members |
137
- | --- | --- |
138
- | `ModalProps` | Props for `<Modal>`. |
139
- | `ModalSize` | `"sm" \| "md" \| "lg"` |
140
- | `ModalVariant` | `"popup" \| "bottom-sheet" \| "full-screen"` |
141
- | `ModalType` | `(props: any) => ReactNode` — the render function signature. |
142
- | `ModalContextType` | `{ onOpenModal, onCloseModal }` |
143
- | `ModalProviderProps` | Props for `<ModalProvider>`. |
144
- | `ModalRootProps` | Props for the internal root. |
145
- | `WorkAreaProps` | Props for `<WorkArea>`. |
146
- | `WorkAreaSize` | `"sm" \| "md" \| "lg"` |
137
+ | Type | Members |
138
+ | -------------------- | ------------------------------------------------------------ |
139
+ | `ModalProps` | Props for `<Modal>`. |
140
+ | `ModalSize` | `"sm" \| "md" \| "lg"` |
141
+ | `ModalVariant` | `"popup" \| "bottom-sheet" \| "full-screen"` |
142
+ | `ModalType` | `(props: any) => ReactNode` — the render function signature. |
143
+ | `ModalContextType` | `{ onOpenModal, onCloseModal }` |
144
+ | `ModalProviderProps` | Props for `<ModalProvider>`. |
145
+ | `ModalRootProps` | Props for the internal root. |
146
+ | `WorkAreaProps` | Props for `<WorkArea>`. |
147
+ | `WorkAreaSize` | `"sm" \| "md" \| "lg"` |
147
148
 
148
149
  ## Examples
149
150
 
150
151
  ### Modal types
151
152
 
152
153
  ```tsx
153
- import * as React from 'react';
154
- import { Modal, ModalProvider, useModal } from '@xsolla/xui-modal';
155
- import { Button } from '@xsolla/xui-button';
154
+ import * as React from "react";
155
+ import { Modal, ModalProvider, useModal } from "@xsolla/xui-modal";
156
+ import { Button } from "@xsolla/xui-button";
156
157
 
157
158
  function Demo() {
158
159
  const [openPopup, closePopup] = useModal(() => (
@@ -171,7 +172,7 @@ function Demo() {
171
172
  </Modal>
172
173
  ));
173
174
  return (
174
- <div style={{ display: 'flex', gap: 12 }}>
175
+ <div style={{ display: "flex", gap: 12 }}>
175
176
  <Button onPress={openPopup}>Popup</Button>
176
177
  <Button onPress={openSheet}>Bottom sheet</Button>
177
178
  <Button onPress={openFull}>Full screen</Button>
@@ -191,9 +192,9 @@ export default function Example() {
191
192
  ### Confirmation dialog
192
193
 
193
194
  ```tsx
194
- import * as React from 'react';
195
- import { Modal, ModalProvider, useModal } from '@xsolla/xui-modal';
196
- import { Button, ButtonGroup } from '@xsolla/xui-button';
195
+ import * as React from "react";
196
+ import { Modal, ModalProvider, useModal } from "@xsolla/xui-modal";
197
+ import { Button, ButtonGroup } from "@xsolla/xui-button";
197
198
 
198
199
  function ConfirmTrigger() {
199
200
  const [confirmed, setConfirmed] = React.useState(false);
@@ -205,8 +206,18 @@ function ConfirmTrigger() {
205
206
  title="Delete item?"
206
207
  footer={
207
208
  <ButtonGroup orientation="horizontal" size="xl">
208
- <Button variant="secondary" tone="mono" onPress={close}>Cancel</Button>
209
- <Button tone="alert" onPress={() => { setConfirmed(true); close(); }}>Delete</Button>
209
+ <Button variant="secondary" tone="mono" onPress={close}>
210
+ Cancel
211
+ </Button>
212
+ <Button
213
+ tone="alert"
214
+ onPress={() => {
215
+ setConfirmed(true);
216
+ close();
217
+ }}
218
+ >
219
+ Delete
220
+ </Button>
210
221
  </ButtonGroup>
211
222
  }
212
223
  >
@@ -216,7 +227,9 @@ function ConfirmTrigger() {
216
227
 
217
228
  return (
218
229
  <div>
219
- <Button tone="alert" onPress={open}>Delete</Button>
230
+ <Button tone="alert" onPress={open}>
231
+ Delete
232
+ </Button>
220
233
  {confirmed && <p>Deleted.</p>}
221
234
  </div>
222
235
  );
@@ -234,15 +247,18 @@ export default function ConfirmDialog() {
234
247
  ### Multi-step modal
235
248
 
236
249
  ```tsx
237
- import * as React from 'react';
238
- import { Modal, ModalProvider, useModal } from '@xsolla/xui-modal';
239
- import { Button } from '@xsolla/xui-button';
250
+ import * as React from "react";
251
+ import { Modal, ModalProvider, useModal } from "@xsolla/xui-modal";
252
+ import { Button } from "@xsolla/xui-button";
240
253
 
241
254
  function MultiStep() {
242
255
  const [step, setStep] = React.useState(1);
243
256
  const [open, close] = useModal(() => (
244
257
  <Modal
245
- onClose={() => { setStep(1); close(); }}
258
+ onClose={() => {
259
+ setStep(1);
260
+ close();
261
+ }}
246
262
  onBack={step > 1 ? () => setStep((s) => s - 1) : undefined}
247
263
  title={`Step ${step} of 3`}
248
264
  >
@@ -250,7 +266,14 @@ function MultiStep() {
250
266
  {step < 3 ? (
251
267
  <Button onPress={() => setStep((s) => s + 1)}>Next</Button>
252
268
  ) : (
253
- <Button onPress={() => { setStep(1); close(); }}>Finish</Button>
269
+ <Button
270
+ onPress={() => {
271
+ setStep(1);
272
+ close();
273
+ }}
274
+ >
275
+ Finish
276
+ </Button>
254
277
  )}
255
278
  </Modal>
256
279
  ));
@@ -269,14 +292,18 @@ export default function MultiStepExample() {
269
292
  ### Edge-to-edge content
270
293
 
271
294
  ```tsx
272
- import * as React from 'react';
273
- import { Modal, ModalProvider, useModal } from '@xsolla/xui-modal';
274
- import { Button } from '@xsolla/xui-button';
295
+ import * as React from "react";
296
+ import { Modal, ModalProvider, useModal } from "@xsolla/xui-modal";
297
+ import { Button } from "@xsolla/xui-button";
275
298
 
276
299
  function HeroTrigger() {
277
300
  const [open, close] = useModal(() => (
278
301
  <Modal openContent onClose={close} title="Gallery">
279
- <img src="/hero-image.jpg" alt="Hero" style={{ width: '100%', display: 'block' }} />
302
+ <img
303
+ src="/hero-image.jpg"
304
+ alt="Hero"
305
+ style={{ width: "100%", display: "block" }}
306
+ />
280
307
  </Modal>
281
308
  ));
282
309
  return <Button onPress={open}>Open hero</Button>;
@@ -293,23 +320,23 @@ export default function EdgeToEdge() {
293
320
 
294
321
  ## Platform Support
295
322
 
296
- | Feature | Web | React Native |
297
- | --- | --- | --- |
298
- | Provider | `ModalProvider` (with `ModalRoot` portal) | `ModalProvider` (root is a no-op stub) |
299
- | Hook | `useModal()` → `[open, close]` | Same signature; provider stack does not render |
300
- | Overlay | Fixed-positioned backdrop | N/A |
301
- | Focus trap | Keyboard Tab trapping | N/A |
302
- | Escape key | Closes modal | N/A |
303
- | Click outside | Dismisses when `closeOutside={true}` | N/A |
323
+ | Feature | Web | React Native |
324
+ | ------------- | ----------------------------------------- | ---------------------------------------------- |
325
+ | Provider | `ModalProvider` (with `ModalRoot` portal) | `ModalProvider` (root is a no-op stub) |
326
+ | Hook | `useModal()` → `[open, close]` | Same signature; provider stack does not render |
327
+ | Overlay | Fixed-positioned backdrop | N/A |
328
+ | Focus trap | Keyboard Tab trapping | N/A |
329
+ | Escape key | Closes modal | N/A |
330
+ | Click outside | Dismisses when `closeOutside={true}` | N/A |
304
331
 
305
332
  ## Keyboard Interaction
306
333
 
307
- | Key | Action |
308
- | --- | --- |
309
- | `Escape` | Closes the modal (when `onClose` is provided). |
310
- | `Tab` | Moves focus to the next focusable element (trapped). |
334
+ | Key | Action |
335
+ | ------------- | -------------------------------------------------------- |
336
+ | `Escape` | Closes the modal (when `onClose` is provided). |
337
+ | `Tab` | Moves focus to the next focusable element (trapped). |
311
338
  | `Shift + Tab` | Moves focus to the previous focusable element (trapped). |
312
- | `Enter` | Activates the focused button. |
339
+ | `Enter` | Activates the focused button. |
313
340
 
314
341
  ## Accessibility
315
342
 
@@ -33,6 +33,8 @@ interface ModalProps extends ThemeOverrideProps {
33
33
  "aria-describedby"?: string;
34
34
  /** Ref to element that should receive focus when modal opens */
35
35
  initialFocusRef?: RefObject<HTMLElement>;
36
+ /** Test ID for testing frameworks */
37
+ testID?: string;
36
38
  }
37
39
  type ModalType = (props: any) => ReactNode;
38
40
  interface ModalContextType {
@@ -41,9 +43,13 @@ interface ModalContextType {
41
43
  }
42
44
  interface ModalProviderProps {
43
45
  children: ReactNode;
46
+ /** Test ID for testing frameworks */
47
+ testID?: string;
44
48
  }
45
49
  interface ModalRootProps {
46
50
  modals: Record<string, ModalType>;
51
+ /** Test ID for testing frameworks */
52
+ testID?: string;
47
53
  }
48
54
  type WorkAreaSize = "sm" | "md" | "lg";
49
55
  interface WorkAreaProps extends ThemeOverrideProps {
@@ -56,11 +62,13 @@ interface WorkAreaProps extends ThemeOverrideProps {
56
62
  type?: ModalVariant;
57
63
  /** Content alignment */
58
64
  align?: "left" | "center";
65
+ /** Test ID for testing frameworks */
66
+ testID?: string;
59
67
  }
60
68
 
61
69
  declare const Modal: react.ForwardRefExoticComponent<ModalProps & react.RefAttributes<any>>;
62
70
 
63
- declare const ModalProvider: ({ children }: ModalProviderProps) => react_jsx_runtime.JSX.Element;
71
+ declare const ModalProvider: ({ children, testID }: ModalProviderProps) => react_jsx_runtime.JSX.Element;
64
72
 
65
73
  declare const useModal: (modal: ModalType) => [() => void, () => void];
66
74
 
package/native/index.d.ts CHANGED
@@ -33,6 +33,8 @@ interface ModalProps extends ThemeOverrideProps {
33
33
  "aria-describedby"?: string;
34
34
  /** Ref to element that should receive focus when modal opens */
35
35
  initialFocusRef?: RefObject<HTMLElement>;
36
+ /** Test ID for testing frameworks */
37
+ testID?: string;
36
38
  }
37
39
  type ModalType = (props: any) => ReactNode;
38
40
  interface ModalContextType {
@@ -41,9 +43,13 @@ interface ModalContextType {
41
43
  }
42
44
  interface ModalProviderProps {
43
45
  children: ReactNode;
46
+ /** Test ID for testing frameworks */
47
+ testID?: string;
44
48
  }
45
49
  interface ModalRootProps {
46
50
  modals: Record<string, ModalType>;
51
+ /** Test ID for testing frameworks */
52
+ testID?: string;
47
53
  }
48
54
  type WorkAreaSize = "sm" | "md" | "lg";
49
55
  interface WorkAreaProps extends ThemeOverrideProps {
@@ -56,11 +62,13 @@ interface WorkAreaProps extends ThemeOverrideProps {
56
62
  type?: ModalVariant;
57
63
  /** Content alignment */
58
64
  align?: "left" | "center";
65
+ /** Test ID for testing frameworks */
66
+ testID?: string;
59
67
  }
60
68
 
61
69
  declare const Modal: react.ForwardRefExoticComponent<ModalProps & react.RefAttributes<any>>;
62
70
 
63
- declare const ModalProvider: ({ children }: ModalProviderProps) => react_jsx_runtime.JSX.Element;
71
+ declare const ModalProvider: ({ children, testID }: ModalProviderProps) => react_jsx_runtime.JSX.Element;
64
72
 
65
73
  declare const useModal: (modal: ModalType) => [() => void, () => void];
66
74
 
package/native/index.js CHANGED
@@ -232,6 +232,7 @@ var WorkArea = (0, import_react.forwardRef)(
232
232
  fetching = false,
233
233
  type = "popup",
234
234
  align,
235
+ testID,
235
236
  themeMode,
236
237
  themeProductContext
237
238
  }, ref) => {
@@ -241,6 +242,7 @@ var WorkArea = (0, import_react.forwardRef)(
241
242
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
242
243
  Box,
243
244
  {
245
+ testID,
244
246
  ref,
245
247
  backgroundColor: theme.colors.background.primary,
246
248
  width: "100%",
@@ -309,6 +311,7 @@ var Modal = (0, import_react2.forwardRef)(
309
311
  "aria-label": ariaLabel,
310
312
  "aria-describedby": ariaDescribedBy,
311
313
  initialFocusRef,
314
+ testID,
312
315
  themeMode,
313
316
  themeProductContext,
314
317
  ...rest
@@ -395,6 +398,7 @@ var Modal = (0, import_react2.forwardRef)(
395
398
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
396
399
  Box,
397
400
  {
401
+ testID,
398
402
  ref: modalRef,
399
403
  role: "dialog",
400
404
  "aria-modal": "true",
@@ -453,14 +457,14 @@ var Modal = (0, import_react2.forwardRef)(
453
457
  style: { flexShrink: 0 },
454
458
  children: header ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
455
459
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box, { style: { minWidth: 36 }, children: onBack && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
456
- import_xui_button.FlexButton,
460
+ import_xui_button.IconButton,
457
461
  {
458
462
  variant: "secondary",
463
+ tone: "mono",
459
464
  size: sizing.headerButtonSize,
460
- background: true,
465
+ icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_xui_icons_base.ChevronLeft, {}),
461
466
  onPress: onBack,
462
- "aria-label": "Go back",
463
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_xui_icons_base.ChevronLeft, {})
467
+ "aria-label": "Go back"
464
468
  }
465
469
  ) }),
466
470
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -470,14 +474,14 @@ var Modal = (0, import_react2.forwardRef)(
470
474
  style: { minWidth: 36 },
471
475
  alignItems: "flex-end",
472
476
  children: onClose && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
473
- import_xui_button.FlexButton,
477
+ import_xui_button.IconButton,
474
478
  {
475
479
  variant: "secondary",
480
+ tone: "mono",
476
481
  size: sizing.headerButtonSize,
477
- background: true,
482
+ icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_xui_icons_base.Remove, {}),
478
483
  onPress: onClose,
479
- "aria-label": "Close modal",
480
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_xui_icons_base.Remove, {})
484
+ "aria-label": "Close modal"
481
485
  }
482
486
  )
483
487
  }
@@ -491,7 +495,9 @@ var Modal = (0, import_react2.forwardRef)(
491
495
  flexGrow: 1,
492
496
  width: "100%",
493
497
  padding: openContent ? 0 : sizing.contentPadding,
494
- style: { paddingTop: hasHeader ? 0 : void 0 },
498
+ style: {
499
+ paddingTop: hasHeader && !openContent ? sizing.headerGap : void 0
500
+ },
495
501
  children
496
502
  }
497
503
  ),
@@ -531,14 +537,16 @@ var ModalContext = (0, import_react3.createContext)({
531
537
 
532
538
  // src/ModalRoot.native.tsx
533
539
  var import_react4 = require("react");
534
- var ModalRoot = (0, import_react4.memo)(({ modals }) => {
535
- return null;
536
- });
540
+ var ModalRoot = (0, import_react4.memo)(
541
+ ({ modals: _modals, testID: _testID }) => {
542
+ return null;
543
+ }
544
+ );
537
545
  ModalRoot.displayName = "ModalRoot";
538
546
 
539
547
  // src/ModalProvider.tsx
540
548
  var import_jsx_runtime4 = require("react/jsx-runtime");
541
- var ModalProvider = ({ children }) => {
549
+ var ModalProvider = ({ children, testID }) => {
542
550
  const [modals, setModals] = (0, import_react5.useState)({});
543
551
  const onOpenModal = (0, import_react5.useCallback)(
544
552
  (key, modal) => setModals((m) => ({ ...m, [key]: modal })),
@@ -559,7 +567,7 @@ var ModalProvider = ({ children }) => {
559
567
  );
560
568
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(ModalContext.Provider, { value: contextValue, children: [
561
569
  children,
562
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ModalRoot, { modals })
570
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ModalRoot, { modals, testID })
563
571
  ] });
564
572
  };
565
573