@sproutsocial/seeds-react-modal 2.2.5 → 2.4.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/src/v2/Modal.tsx CHANGED
@@ -14,6 +14,14 @@ import {
14
14
  import type { TypeModalProps } from "./ModalTypes";
15
15
  import { getOverlayVariants, useIsMobile } from "./MotionConfig";
16
16
 
17
+ // Type aliases for Dialog.Content event handlers
18
+ type InteractOutsideHandler = NonNullable<
19
+ React.ComponentPropsWithoutRef<typeof Dialog.Content>["onInteractOutside"]
20
+ >;
21
+ type EscapeKeyDownHandler = NonNullable<
22
+ React.ComponentPropsWithoutRef<typeof Dialog.Content>["onEscapeKeyDown"]
23
+ >;
24
+
17
25
  /**
18
26
  * Accessible modal dialog component built on Radix UI Dialog primitives.
19
27
  *
@@ -61,6 +69,16 @@ const Modal = (props: TypeModalProps) => {
61
69
  closeButtonAriaLabel = "Close",
62
70
  closeButtonProps,
63
71
  zIndex = 6,
72
+ // Extract Dialog.Content event handlers
73
+ onOpenAutoFocus,
74
+ onCloseAutoFocus,
75
+ onEscapeKeyDown,
76
+ onPointerDownOutside,
77
+ onFocusOutside,
78
+ onInteractOutside,
79
+ // Extract convenience boolean props
80
+ disableOutsideClickClose = false,
81
+ disableEscapeKeyClose = false,
64
82
  ...rest
65
83
  } = props;
66
84
 
@@ -104,6 +122,27 @@ const Modal = (props: TypeModalProps) => {
104
122
  ? DraggableModalContent
105
123
  : StaticModalContent;
106
124
 
125
+ // Wrap event handlers to support convenience boolean props
126
+ const wrappedOnInteractOutside = React.useCallback<InteractOutsideHandler>(
127
+ (e) => {
128
+ if (disableOutsideClickClose) {
129
+ e.preventDefault();
130
+ }
131
+ onInteractOutside?.(e);
132
+ },
133
+ [disableOutsideClickClose, onInteractOutside]
134
+ );
135
+
136
+ const wrappedOnEscapeKeyDown = React.useCallback<EscapeKeyDownHandler>(
137
+ (e) => {
138
+ if (disableEscapeKeyClose) {
139
+ e.preventDefault();
140
+ }
141
+ onEscapeKeyDown?.(e);
142
+ },
143
+ [disableEscapeKeyClose, onEscapeKeyDown]
144
+ );
145
+
107
146
  return (
108
147
  <Dialog.Root
109
148
  open={open}
@@ -130,6 +169,7 @@ const Modal = (props: TypeModalProps) => {
130
169
  <StyledOverlay
131
170
  data-slot="modal-overlay"
132
171
  data-qa-modal-overlay
172
+ data-testid="modal-overlay"
133
173
  allowInteraction={draggable}
134
174
  />
135
175
  </StyledMotionOverlay>
@@ -141,6 +181,14 @@ const Modal = (props: TypeModalProps) => {
141
181
  draggable={draggable}
142
182
  zIndex={zIndex}
143
183
  rest={rest}
184
+ dialogContentProps={{
185
+ onOpenAutoFocus,
186
+ onCloseAutoFocus,
187
+ onEscapeKeyDown: wrappedOnEscapeKeyDown,
188
+ onPointerDownOutside,
189
+ onFocusOutside,
190
+ onInteractOutside: wrappedOnInteractOutside,
191
+ }}
144
192
  >
145
193
  {/* Floating actions rail - always show a close by default */}
146
194
  <ModalRail>
@@ -178,4 +226,145 @@ const Modal = (props: TypeModalProps) => {
178
226
  );
179
227
  };
180
228
 
229
+ /**
230
+ * Hook for adding proper ARIA attributes to external modal triggers.
231
+ *
232
+ * ⚠️ **NOT RECOMMENDED** - Prefer using modalTrigger prop or ModalTrigger component.
233
+ * Use this hook ONLY as a last resort when architectural constraints prevent keeping
234
+ * the trigger inside the Modal component tree.
235
+ *
236
+ * **Important Limitations:**
237
+ * - This hook only provides ARIA attributes (aria-haspopup, aria-expanded)
238
+ * - Focus restoration is NOT automatic - you must manually handle it with refs
239
+ * - Radix UI cannot track external triggers for proper accessibility
240
+ *
241
+ * **Why modalTrigger prop is better:**
242
+ * - Automatic ARIA attributes
243
+ * - Automatic focus restoration
244
+ * - Better touch device support
245
+ * - Follows WAI-ARIA Dialog best practices
246
+ *
247
+ * @param isOpen - Current open state of the modal
248
+ * @param modalId - Optional ID of the modal element for aria-controls
249
+ * @returns Object with ARIA attributes to spread onto trigger element
250
+ *
251
+ * @example
252
+ * ```tsx
253
+ * // Manual focus restoration required
254
+ * const [isOpen, setIsOpen] = useState(false);
255
+ * const triggerRef = useRef<HTMLButtonElement>(null);
256
+ * const triggerProps = useModalTriggerProps(isOpen);
257
+ *
258
+ * return (
259
+ * <>
260
+ * <Button
261
+ * ref={triggerRef}
262
+ * {...triggerProps}
263
+ * onClick={() => setIsOpen(true)}
264
+ * >
265
+ * Open Modal
266
+ * </Button>
267
+ * <Modal
268
+ * open={isOpen}
269
+ * onOpenChange={setIsOpen}
270
+ * onCloseAutoFocus={(e) => {
271
+ * e.preventDefault();
272
+ * triggerRef.current?.focus();
273
+ * }}
274
+ * >
275
+ * <ModalBody>Content</ModalBody>
276
+ * </Modal>
277
+ * </>
278
+ * );
279
+ * ```
280
+ */
281
+ export function useModalTriggerProps(
282
+ isOpen: boolean,
283
+ modalId?: string
284
+ ): {
285
+ "aria-haspopup": "dialog";
286
+ "aria-expanded": boolean;
287
+ "aria-controls"?: string;
288
+ } {
289
+ return React.useMemo(
290
+ () => ({
291
+ "aria-haspopup": "dialog" as const,
292
+ "aria-expanded": isOpen,
293
+ ...(modalId ? { "aria-controls": modalId } : {}),
294
+ }),
295
+ [isOpen, modalId]
296
+ );
297
+ }
298
+
299
+ /**
300
+ * Hook for managing external modal triggers with automatic focus restoration.
301
+ *
302
+ * ⚠️ **NOT RECOMMENDED** - Prefer using modalTrigger prop or ModalTrigger component.
303
+ * Use this hook ONLY as a last resort when architectural constraints prevent keeping
304
+ * the trigger inside the Modal component tree.
305
+ *
306
+ * This hook improves upon useModalTriggerProps by managing the trigger ref internally
307
+ * and providing the onCloseAutoFocus callback, eliminating the need for manual
308
+ * focus restoration boilerplate.
309
+ *
310
+ * **Improvements over useModalTriggerProps:**
311
+ * - ✅ No manual ref creation
312
+ * - ✅ Automatic focus restoration via onCloseAutoFocus callback
313
+ * - ✅ Automatic ARIA attributes
314
+ *
315
+ * **Why modalTrigger prop is still better:**
316
+ * - Better touch device support
317
+ * - Follows WAI-ARIA Dialog best practices
318
+ * - Less boilerplate overall
319
+ *
320
+ * @param modalId - Optional ID of the modal element for aria-controls
321
+ * @returns Object with triggerRef, ARIA props, and onCloseAutoFocus callback
322
+ *
323
+ * @example
324
+ * ```tsx
325
+ * const [isOpen, setIsOpen] = useState(false);
326
+ * const { triggerRef, triggerProps, onCloseAutoFocus } = useModalExternalTrigger();
327
+ *
328
+ * return (
329
+ * <>
330
+ * <Button
331
+ * ref={triggerRef}
332
+ * {...triggerProps(isOpen)}
333
+ * onClick={() => setIsOpen(true)}
334
+ * >
335
+ * Open Modal
336
+ * </Button>
337
+ * <Modal
338
+ * open={isOpen}
339
+ * onOpenChange={setIsOpen}
340
+ * onCloseAutoFocus={onCloseAutoFocus}
341
+ * >
342
+ * <ModalBody>Content</ModalBody>
343
+ * </Modal>
344
+ * </>
345
+ * );
346
+ * ```
347
+ */
348
+ export function useModalExternalTrigger<
349
+ T extends HTMLElement = HTMLButtonElement
350
+ >(modalId?: string) {
351
+ const triggerRef = React.useRef<T>(null);
352
+
353
+ const triggerProps = React.useCallback(
354
+ (isOpen: boolean) => ({
355
+ "aria-haspopup": "dialog" as const,
356
+ "aria-expanded": isOpen,
357
+ ...(modalId ? { "aria-controls": modalId } : {}),
358
+ }),
359
+ [modalId]
360
+ );
361
+
362
+ const onCloseAutoFocus = React.useCallback((e: Event) => {
363
+ e.preventDefault();
364
+ triggerRef.current?.focus();
365
+ }, []);
366
+
367
+ return { triggerRef, triggerProps, onCloseAutoFocus };
368
+ }
369
+
181
370
  export default Modal;
@@ -145,6 +145,29 @@ export type TypeModalActionProps =
145
145
  // MODAL PROPS - Main Modal component types
146
146
  // =============================================================================
147
147
 
148
+ /**
149
+ * Event handler props from Radix UI Dialog.Content.
150
+ * These control modal interaction behavior like closing on outside clicks or escape key.
151
+ */
152
+ type DialogContentEventHandlers = Pick<
153
+ React.ComponentPropsWithoutRef<typeof Dialog.Content>,
154
+ | "onOpenAutoFocus"
155
+ | "onCloseAutoFocus"
156
+ | "onEscapeKeyDown"
157
+ | "onPointerDownOutside"
158
+ | "onFocusOutside"
159
+ | "onInteractOutside"
160
+ >;
161
+
162
+ /**
163
+ * Dialog.Content event handlers excluding onInteractOutside.
164
+ * Used for draggable modals where onInteractOutside conflicts with the draggable UX.
165
+ */
166
+ type DialogContentEventHandlersWithoutInteractOutside = Omit<
167
+ DialogContentEventHandlers,
168
+ "onInteractOutside"
169
+ >;
170
+
148
171
  /**
149
172
  * Base common props shared by all modal variants (without close button props).
150
173
  */
@@ -182,6 +205,18 @@ type TypeModalCommonPropsBase = TypeContainerProps &
182
205
 
183
206
  /** Controls the z-index CSS property (defaults to 6 to match Modal v1) */
184
207
  zIndex?: number;
208
+
209
+ /**
210
+ * Prevents the modal from closing when clicking outside.
211
+ * The onInteractOutside handler will still be called if provided.
212
+ */
213
+ disableOutsideClickClose?: boolean;
214
+
215
+ /**
216
+ * Prevents the modal from closing when pressing the Escape key.
217
+ * The onEscapeKeyDown handler will still be called if provided.
218
+ */
219
+ disableEscapeKeyClose?: boolean;
185
220
  };
186
221
 
187
222
  /**
@@ -244,18 +279,30 @@ type TypeModalCommonProps =
244
279
  /**
245
280
  * Base props with draggable and showOverlay relationship enforced.
246
281
  *
247
- * When draggable is true, showOverlay must be false because the overlay
248
- * would block interaction with content behind the modal.
282
+ * When draggable is true:
283
+ * - showOverlay must be false (overlay would block interaction with content behind the modal)
284
+ * - onInteractOutside is not allowed (conflicts with draggable UX which needs to keep modal open)
285
+ * - disableOutsideClickClose is not allowed (since onInteractOutside is not supported)
286
+ *
287
+ * When draggable is false (or undefined):
288
+ * - All Dialog.Content event handlers are available including onInteractOutside
289
+ * - disableOutsideClickClose can be used
249
290
  */
250
291
  type TypeModalBaseProps =
251
- | (TypeModalCommonProps & {
252
- draggable: true;
253
- showOverlay?: false;
254
- })
255
- | (TypeModalCommonProps & {
256
- draggable?: false;
257
- showOverlay?: boolean;
258
- });
292
+ | (TypeModalCommonProps &
293
+ DialogContentEventHandlersWithoutInteractOutside & {
294
+ draggable: true;
295
+ showOverlay?: false;
296
+ /** onInteractOutside is not supported for draggable modals */
297
+ onInteractOutside?: never;
298
+ /** disableOutsideClickClose is not supported for draggable modals */
299
+ disableOutsideClickClose?: never;
300
+ })
301
+ | (TypeModalCommonProps &
302
+ DialogContentEventHandlers & {
303
+ draggable?: false;
304
+ showOverlay?: boolean;
305
+ });
259
306
 
260
307
  /**
261
308
  * Modal props with title provided.
@@ -4,6 +4,13 @@ import { Box } from "@sproutsocial/seeds-react-box";
4
4
  import { Button } from "@sproutsocial/seeds-react-button";
5
5
  import Text from "@sproutsocial/seeds-react-text";
6
6
  import { FormField } from "@sproutsocial/seeds-react-form-field";
7
+ import {
8
+ MenuContent,
9
+ MenuItem,
10
+ MenuToggleButton,
11
+ SingleSelectMenu,
12
+ type TypeSingleSelectMenuProps,
13
+ } from "@sproutsocial/seeds-react-menu";
7
14
  import {
8
15
  Modal,
9
16
  ModalHeader,
@@ -11,6 +18,8 @@ import {
11
18
  ModalBody,
12
19
  ModalCloseWrapper,
13
20
  ModalCustomFooter,
21
+ ModalExternalTrigger,
22
+ useModalExternalTrigger,
14
23
  } from ".";
15
24
 
16
25
  const meta: Meta<typeof Modal> = {
@@ -83,9 +92,11 @@ export const Default: Story = {
83
92
  >
84
93
  <ModalHeader title="Modal Title" subtitle="This is a subtitle" />
85
94
  <ModalBody>
86
- This modal uses uncontrolled state - no need to manage open/close
87
- state! The modalTrigger prop and floating close button handle
88
- everything.
95
+ <Text>
96
+ This modal uses uncontrolled state - no need to manage open/close
97
+ state! The modalTrigger prop and floating close button handle
98
+ everything.
99
+ </Text>
89
100
  </ModalBody>
90
101
  <ModalFooter
91
102
  cancelButton={<Button>Cancel</Button>}
@@ -876,3 +887,267 @@ export const CustomFooterOverride: Story = {
876
887
  );
877
888
  },
878
889
  };
890
+
891
+ export const SingleSelectMenuInModal: Story = {
892
+ render: () => {
893
+ const [selectedItem, setSelectedItem] =
894
+ useState<TypeSingleSelectMenuProps["selectedItem"]>(null);
895
+
896
+ return (
897
+ <>
898
+ <Modal
899
+ modalTrigger={<Button appearance="primary">Open Modal</Button>}
900
+ showOverlay={true}
901
+ aria-label="Demo Modal"
902
+ closeButtonProps={{ "aria-label": "Close" }}
903
+ >
904
+ <ModalHeader title="Select an Option" />
905
+ <ModalBody>
906
+ <Box p={400}>
907
+ <SingleSelectMenu
908
+ popoutProps={{ appendToBody: false }}
909
+ selectedItem={selectedItem}
910
+ onSelectedItemChange={({ selectedItem: item }) =>
911
+ setSelectedItem(item)
912
+ }
913
+ menuToggleElement={
914
+ <MenuToggleButton>
915
+ {selectedItem?.id ?? "Select..."}
916
+ </MenuToggleButton>
917
+ }
918
+ >
919
+ <MenuContent>
920
+ <MenuItem id="option-1">Option 1</MenuItem>
921
+ <MenuItem id="option-2">Option 2</MenuItem>
922
+ <MenuItem id="option-3">Option 3</MenuItem>
923
+ </MenuContent>
924
+ </SingleSelectMenu>
925
+ </Box>
926
+ </ModalBody>
927
+ </Modal>
928
+ </>
929
+ );
930
+ },
931
+ };
932
+
933
+ /**
934
+ * Example using the useModalExternalTrigger hook for external triggers.
935
+ *
936
+ * ⚠️ NOT RECOMMENDED - This is a last resort pattern. Prefer using modalTrigger prop.
937
+ *
938
+ * The hook provides:
939
+ * - triggerRef: Ref to attach to your trigger element
940
+ * - triggerProps: Function that returns ARIA props (pass isOpen state)
941
+ * - onCloseAutoFocus: Callback for Modal to restore focus
942
+ *
943
+ * This eliminates manual ref creation and focus restoration boilerplate.
944
+ */
945
+ export const ExternalTriggerWithHook: Story = {
946
+ render: () => {
947
+ const [isOpen, setIsOpen] = useState(false);
948
+ const { triggerRef, triggerProps, onCloseAutoFocus } =
949
+ useModalExternalTrigger();
950
+
951
+ return (
952
+ <Box>
953
+ <Text mb={400} fontWeight="semibold">
954
+ ⚠️ LAST RESORT: useModalExternalTrigger Hook
955
+ </Text>
956
+ <Text mb={400}>
957
+ Only use when the trigger absolutely cannot be near the Modal (e.g.,
958
+ trigger in header, modal at bottom of app tree).
959
+ </Text>
960
+
961
+ <Text mb={400} fontWeight="semibold">
962
+ Benefits:
963
+ </Text>
964
+ <Box as="ul" pl={400} mb={400}>
965
+ <li>
966
+ <Text>
967
+ Works with any trigger element (Button, custom components, etc.)
968
+ </Text>
969
+ </li>
970
+ <li>
971
+ <Text>
972
+ Automatic focus restoration via onCloseAutoFocus callback
973
+ </Text>
974
+ </li>
975
+ <li>
976
+ <Text>No manual ref creation needed</Text>
977
+ </li>
978
+ </Box>
979
+
980
+ <Button
981
+ ref={triggerRef}
982
+ {...triggerProps(isOpen)}
983
+ appearance="primary"
984
+ onClick={() => setIsOpen(true)}
985
+ >
986
+ Open Modal (External Trigger)
987
+ </Button>
988
+
989
+ <Modal
990
+ open={isOpen}
991
+ onOpenChange={setIsOpen}
992
+ onCloseAutoFocus={onCloseAutoFocus}
993
+ aria-label="External Trigger Hook Modal"
994
+ closeButtonAriaLabel="Close Modal"
995
+ >
996
+ <ModalHeader
997
+ title="External Trigger with Hook"
998
+ subtitle="Using useModalExternalTrigger"
999
+ />
1000
+ <ModalBody>
1001
+ <Text mb={400}>
1002
+ The useModalExternalTrigger hook simplifies external trigger setup
1003
+ by:
1004
+ </Text>
1005
+ <Box as="ul" pl={400}>
1006
+ <li>
1007
+ <Text>Managing the trigger ref internally</Text>
1008
+ </li>
1009
+ <li>
1010
+ <Text>Providing ARIA props via triggerProps function</Text>
1011
+ </li>
1012
+ <li>
1013
+ <Text>
1014
+ Handling focus restoration via onCloseAutoFocus callback
1015
+ </Text>
1016
+ </li>
1017
+ </Box>
1018
+ <Text mt={400}>
1019
+ This eliminates the boilerplate of manual ref management and
1020
+ custom onCloseAutoFocus implementation.
1021
+ </Text>
1022
+ </ModalBody>
1023
+ <ModalFooter
1024
+ cancelButton={
1025
+ <ModalCloseWrapper>
1026
+ <Button>Cancel</Button>
1027
+ </ModalCloseWrapper>
1028
+ }
1029
+ primaryButton={
1030
+ <ModalCloseWrapper>
1031
+ <Button appearance="primary">Confirm</Button>
1032
+ </ModalCloseWrapper>
1033
+ }
1034
+ />
1035
+ </Modal>
1036
+ </Box>
1037
+ );
1038
+ },
1039
+ };
1040
+
1041
+ /**
1042
+ * Example using the ModalExternalTrigger component for external triggers.
1043
+ *
1044
+ * ⚠️ NOT RECOMMENDED - This is a last resort pattern. Prefer using modalTrigger prop.
1045
+ *
1046
+ * ModalExternalTrigger is a Button variant with built-in ARIA attributes.
1047
+ * Use it when you need an external trigger that is specifically a Button.
1048
+ *
1049
+ * Note: Focus restoration still requires manual onCloseAutoFocus implementation.
1050
+ */
1051
+ export const ExternalTriggerComponent: Story = {
1052
+ render: () => {
1053
+ const [isOpen, setIsOpen] = useState(false);
1054
+ const triggerRef = React.useRef<HTMLButtonElement>(null);
1055
+
1056
+ return (
1057
+ <Box>
1058
+ <Text mb={400} fontWeight="semibold">
1059
+ ⚠️ LAST RESORT: ModalExternalTrigger Component
1060
+ </Text>
1061
+ <Text mb={400}>
1062
+ Only use when the trigger absolutely cannot be near the Modal AND
1063
+ you're using a Seeds Button specifically.
1064
+ </Text>
1065
+
1066
+ <Text mb={400} fontWeight="semibold">
1067
+ Benefits:
1068
+ </Text>
1069
+ <Box as="ul" pl={400} mb={400}>
1070
+ <li>
1071
+ <Text>
1072
+ Automatic ARIA attributes (aria-haspopup, aria-expanded, etc.)
1073
+ </Text>
1074
+ </li>
1075
+ <li>
1076
+ <Text>
1077
+ All Button props supported (appearance, size, disabled, etc.)
1078
+ </Text>
1079
+ </li>
1080
+ <li>
1081
+ <Text>Cleaner than hook for Button-only triggers</Text>
1082
+ </li>
1083
+ </Box>
1084
+
1085
+ <Text mb={400} fontWeight="semibold">
1086
+ Limitation:
1087
+ </Text>
1088
+ <Text mb={400}>
1089
+ Focus restoration still requires manual onCloseAutoFocus handling.
1090
+ </Text>
1091
+
1092
+ <ModalExternalTrigger
1093
+ ref={triggerRef}
1094
+ isOpen={isOpen}
1095
+ onTrigger={() => setIsOpen(true)}
1096
+ appearance="primary"
1097
+ size="default"
1098
+ >
1099
+ Open Modal (External Trigger)
1100
+ </ModalExternalTrigger>
1101
+
1102
+ <Modal
1103
+ open={isOpen}
1104
+ onOpenChange={setIsOpen}
1105
+ onCloseAutoFocus={(e) => {
1106
+ e.preventDefault();
1107
+ triggerRef.current?.focus();
1108
+ }}
1109
+ aria-label="External Trigger Component Modal"
1110
+ closeButtonAriaLabel="Close Modal"
1111
+ >
1112
+ <ModalHeader
1113
+ title="External Trigger Component"
1114
+ subtitle="Using ModalExternalTrigger"
1115
+ />
1116
+ <ModalBody>
1117
+ <Text mb={400}>
1118
+ ModalExternalTrigger extends Seeds Button with automatic ARIA
1119
+ attributes:
1120
+ </Text>
1121
+ <Box as="ul" pl={400}>
1122
+ <li>
1123
+ <Text>aria-haspopup="dialog"</Text>
1124
+ </li>
1125
+ <li>
1126
+ <Text>aria-expanded based on isOpen prop</Text>
1127
+ </li>
1128
+ <li>
1129
+ <Text>aria-controls (if modalId provided)</Text>
1130
+ </li>
1131
+ </Box>
1132
+ <Text mt={400}>
1133
+ Supports all Button props (appearance, size, disabled, etc.).
1134
+ Focus restoration requires manual onCloseAutoFocus handling.
1135
+ </Text>
1136
+ </ModalBody>
1137
+ <ModalFooter
1138
+ cancelButton={
1139
+ <ModalCloseWrapper>
1140
+ <Button>Cancel</Button>
1141
+ </ModalCloseWrapper>
1142
+ }
1143
+ primaryButton={
1144
+ <ModalCloseWrapper>
1145
+ <Button appearance="primary">Confirm</Button>
1146
+ </ModalCloseWrapper>
1147
+ }
1148
+ />
1149
+ </Modal>
1150
+ </Box>
1151
+ );
1152
+ },
1153
+ };
@@ -119,6 +119,15 @@ interface ModalContentProps {
119
119
  draggable?: boolean;
120
120
  zIndex?: number;
121
121
  rest: any;
122
+ dialogContentProps?: Pick<
123
+ React.ComponentPropsWithoutRef<typeof Dialog.Content>,
124
+ | "onOpenAutoFocus"
125
+ | "onCloseAutoFocus"
126
+ | "onEscapeKeyDown"
127
+ | "onPointerDownOutside"
128
+ | "onFocusOutside"
129
+ | "onInteractOutside"
130
+ >;
122
131
  }
123
132
 
124
133
  /**
@@ -131,13 +140,14 @@ export const StaticModalContent: React.FC<ModalContentProps> = ({
131
140
  dataAttributes,
132
141
  zIndex,
133
142
  rest,
143
+ dialogContentProps,
134
144
  }) => {
135
145
  const isMobile = useIsMobile();
136
146
  const contentVariants = getContentVariants(isMobile, false);
137
147
 
138
148
  return (
139
149
  <DragContext.Provider value={null}>
140
- <Dialog.Content asChild aria-label={label}>
150
+ <Dialog.Content asChild aria-label={label} {...dialogContentProps}>
141
151
  <StyledMotionWrapper
142
152
  $isMobile={isMobile}
143
153
  $zIndex={zIndex}
@@ -170,6 +180,7 @@ export const DraggableModalContent: React.FC<ModalContentProps> = ({
170
180
  dataAttributes,
171
181
  zIndex,
172
182
  rest,
183
+ dialogContentProps,
173
184
  }) => {
174
185
  const [position, setPosition] = React.useState({ x: 0, y: 0 });
175
186
  const [isDragging, setIsDragging] = React.useState(false);
@@ -274,6 +285,7 @@ export const DraggableModalContent: React.FC<ModalContentProps> = ({
274
285
  <Dialog.Content
275
286
  asChild
276
287
  aria-label={label}
288
+ {...dialogContentProps}
277
289
  onInteractOutside={handleInteractOutside}
278
290
  >
279
291
  <StyledMotionWrapper