@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/.turbo/turbo-build.log +19 -19
- package/CHANGELOG.md +41 -0
- package/dist/{ModalAction-gIgCE73I.d.mts → ModalAction-BHG3Zbd9.d.mts} +151 -6
- package/dist/{ModalAction-gIgCE73I.d.ts → ModalAction-BHG3Zbd9.d.ts} +151 -6
- package/dist/esm/{chunk-UP2XQN57.js → chunk-TQ44T5IM.js} +114 -17
- package/dist/esm/chunk-TQ44T5IM.js.map +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/v2/index.js +9 -3
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +87 -19
- package/dist/index.js.map +1 -1
- package/dist/v2/index.d.mts +77 -3
- package/dist/v2/index.d.ts +77 -3
- package/dist/v2/index.js +121 -21
- package/dist/v2/index.js.map +1 -1
- package/package.json +7 -7
- package/src/__tests__/v2/Modal.test.tsx +972 -0
- package/src/v2/Modal.tsx +189 -0
- package/src/v2/ModalTypes.ts +57 -10
- package/src/v2/ModalV2.stories.tsx +278 -3
- package/src/v2/components/ModalContent.tsx +13 -1
- package/src/v2/components/ModalExternalTrigger.tsx +104 -0
- package/src/v2/components/index.ts +1 -0
- package/src/v2/index.ts +7 -1
- package/dist/esm/chunk-UP2XQN57.js.map +0 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -8,34 +8,34 @@ $ tsup --dts
|
|
|
8
8
|
[34mCLI[39m Cleaning output folder
|
|
9
9
|
[34mCJS[39m Build start
|
|
10
10
|
[34mESM[39m Build start
|
|
11
|
-
[32mCJS[39m [1mdist/index.js [22m[32m37.00 KB[39m
|
|
12
|
-
[32mCJS[39m [1mdist/v1/index.js [22m[32m9.86 KB[39m
|
|
13
|
-
[32mCJS[39m [1mdist/v2/index.js [22m[32m28.98 KB[39m
|
|
14
|
-
[32mCJS[39m [1mdist/index.js.map [22m[32m66.80 KB[39m
|
|
15
|
-
[32mCJS[39m [1mdist/v1/index.js.map [22m[32m13.03 KB[39m
|
|
16
|
-
[32mCJS[39m [1mdist/v2/index.js.map [22m[32m53.42 KB[39m
|
|
17
|
-
[32mCJS[39m ⚡️ Build success in 212ms
|
|
18
|
-
[32mESM[39m [1mdist/esm/v1/index.js [22m[32m165.00 B[39m
|
|
19
|
-
[32mESM[39m [1mdist/esm/v2/index.js [22m[32m596.00 B[39m
|
|
20
11
|
[32mESM[39m [1mdist/esm/index.js [22m[32m583.00 B[39m
|
|
12
|
+
[32mESM[39m [1mdist/esm/v1/index.js [22m[32m165.00 B[39m
|
|
13
|
+
[32mESM[39m [1mdist/esm/v2/index.js [22m[32m746.00 B[39m
|
|
21
14
|
[32mESM[39m [1mdist/esm/chunk-IYDY4OPB.js [22m[32m7.12 KB[39m
|
|
22
|
-
[32mESM[39m [1mdist/esm/chunk-
|
|
15
|
+
[32mESM[39m [1mdist/esm/chunk-TQ44T5IM.js [22m[32m26.85 KB[39m
|
|
16
|
+
[32mESM[39m [1mdist/esm/index.js.map [22m[32m1.05 KB[39m
|
|
23
17
|
[32mESM[39m [1mdist/esm/v1/index.js.map [22m[32m71.00 B[39m
|
|
24
18
|
[32mESM[39m [1mdist/esm/v2/index.js.map [22m[32m71.00 B[39m
|
|
25
|
-
[32mESM[39m [1mdist/esm/index.js.map [22m[32m1.05 KB[39m
|
|
26
19
|
[32mESM[39m [1mdist/esm/chunk-IYDY4OPB.js.map [22m[32m12.85 KB[39m
|
|
27
|
-
[32mESM[39m [1mdist/esm/chunk-
|
|
28
|
-
[32mESM[39m ⚡️ Build success in
|
|
20
|
+
[32mESM[39m [1mdist/esm/chunk-TQ44T5IM.js.map [22m[32m63.46 KB[39m
|
|
21
|
+
[32mESM[39m ⚡️ Build success in 280ms
|
|
22
|
+
[32mCJS[39m [1mdist/index.js [22m[32m39.00 KB[39m
|
|
23
|
+
[32mCJS[39m [1mdist/v1/index.js [22m[32m9.86 KB[39m
|
|
24
|
+
[32mCJS[39m [1mdist/v2/index.js [22m[32m31.92 KB[39m
|
|
25
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m77.26 KB[39m
|
|
26
|
+
[32mCJS[39m [1mdist/v1/index.js.map [22m[32m13.03 KB[39m
|
|
27
|
+
[32mCJS[39m [1mdist/v2/index.js.map [22m[32m64.54 KB[39m
|
|
28
|
+
[32mCJS[39m ⚡️ Build success in 284ms
|
|
29
29
|
[34mDTS[39m Build start
|
|
30
|
-
[32mDTS[39m ⚡️ Build success in
|
|
30
|
+
[32mDTS[39m ⚡️ Build success in 19209ms
|
|
31
31
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m975.00 B[39m
|
|
32
32
|
[32mDTS[39m [1mdist/v1/index.d.ts [22m[32m413.00 B[39m
|
|
33
|
-
[32mDTS[39m [1mdist/v2/index.d.ts [22m[
|
|
33
|
+
[32mDTS[39m [1mdist/v2/index.d.ts [22m[32m3.72 KB[39m
|
|
34
34
|
[32mDTS[39m [1mdist/Modal-ki8oiGbC.d.ts [22m[32m2.52 KB[39m
|
|
35
|
-
[32mDTS[39m [1mdist/ModalAction-
|
|
35
|
+
[32mDTS[39m [1mdist/ModalAction-BHG3Zbd9.d.ts [22m[32m20.46 KB[39m
|
|
36
36
|
[32mDTS[39m [1mdist/index.d.mts [22m[32m978.00 B[39m
|
|
37
37
|
[32mDTS[39m [1mdist/v1/index.d.mts [22m[32m415.00 B[39m
|
|
38
|
-
[32mDTS[39m [1mdist/v2/index.d.mts [22m[
|
|
38
|
+
[32mDTS[39m [1mdist/v2/index.d.mts [22m[32m3.72 KB[39m
|
|
39
39
|
[32mDTS[39m [1mdist/Modal-ki8oiGbC.d.mts [22m[32m2.52 KB[39m
|
|
40
|
-
[32mDTS[39m [1mdist/ModalAction-
|
|
41
|
-
Done in
|
|
40
|
+
[32mDTS[39m [1mdist/ModalAction-BHG3Zbd9.d.mts [22m[32m20.46 KB[39m
|
|
41
|
+
Done in 22.38s.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# @sproutsocial/seeds-react-modal
|
|
2
2
|
|
|
3
|
+
## 2.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 4a7f66e: Add external trigger helpers for Modal V2:
|
|
8
|
+
|
|
9
|
+
- Add `useModalExternalTrigger` hook that manages refs and provides ARIA attributes and focus restoration callback for external triggers
|
|
10
|
+
- Add `ModalExternalTrigger` component (Button wrapper with automatic ARIA attributes for external triggers)
|
|
11
|
+
- Update Modal V2 documentation with external trigger patterns and composition proposal
|
|
12
|
+
|
|
13
|
+
## 2.3.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- c76c353: Add modal interaction control to Modal v2 with both convenience props and Radix UI event handlers.
|
|
18
|
+
|
|
19
|
+
Convenience Boolean Props (recommended for simple cases):
|
|
20
|
+
|
|
21
|
+
- `disableOutsideClickClose`: Prevents modal from closing when clicking outside. Custom `onInteractOutside` handlers still fire for side effects.
|
|
22
|
+
- `disableEscapeKeyClose`: Prevents modal from closing when pressing Escape key. Custom `onEscapeKeyDown` handlers still fire for side effects.
|
|
23
|
+
|
|
24
|
+
Radix UI Dialog.Content Event Handlers (for granular control):
|
|
25
|
+
|
|
26
|
+
- `onInteractOutside`: Control behavior when user interacts outside the modal
|
|
27
|
+
- `onEscapeKeyDown`: Control behavior when Escape key is pressed
|
|
28
|
+
- `onPointerDownOutside`: Handle pointer down events outside the modal
|
|
29
|
+
- `onFocusOutside`: Handle focus moving outside the modal
|
|
30
|
+
- `onOpenAutoFocus`: Handle focus behavior when modal opens
|
|
31
|
+
- `onCloseAutoFocus`: Handle focus behavior when modal closes
|
|
32
|
+
|
|
33
|
+
Boolean props can be combined with event handlers - the boolean prevents closing while the handler adds custom side effects like analytics or warnings.
|
|
34
|
+
|
|
35
|
+
Draggable Modal Restrictions: `disableOutsideClickClose` and `onInteractOutside` are not supported for draggable modals (TypeScript will error) as they conflict with the draggable UX. All other props including `disableEscapeKeyClose` work with draggable modals. This is enforced through TypeScript discriminated union types.
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- @sproutsocial/seeds-react-theme@3.3.2
|
|
40
|
+
- @sproutsocial/seeds-react-box@1.1.10
|
|
41
|
+
- @sproutsocial/seeds-react-icon@2.1.1
|
|
42
|
+
- @sproutsocial/seeds-react-button@1.3.12
|
|
43
|
+
|
|
3
44
|
## 2.2.5
|
|
4
45
|
|
|
5
46
|
### Patch Changes
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as React from 'react';
|
|
2
3
|
import * as styled_components from 'styled-components';
|
|
3
4
|
import * as _sproutsocial_seeds_react_box from '@sproutsocial/seeds-react-box';
|
|
4
5
|
import { TypeContainerProps, TypeBoxProps } from '@sproutsocial/seeds-react-box';
|
|
5
|
-
import * as React from 'react';
|
|
6
6
|
import * as Dialog from '@radix-ui/react-dialog';
|
|
7
7
|
import { TypeIconName } from '@sproutsocial/seeds-react-icon';
|
|
8
8
|
|
|
@@ -111,6 +111,16 @@ type TypeModalActionProps = ({
|
|
|
111
111
|
/** Click handler for the button */
|
|
112
112
|
onClick?: () => void;
|
|
113
113
|
} & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onClick">);
|
|
114
|
+
/**
|
|
115
|
+
* Event handler props from Radix UI Dialog.Content.
|
|
116
|
+
* These control modal interaction behavior like closing on outside clicks or escape key.
|
|
117
|
+
*/
|
|
118
|
+
type DialogContentEventHandlers = Pick<React.ComponentPropsWithoutRef<typeof Dialog.Content>, "onOpenAutoFocus" | "onCloseAutoFocus" | "onEscapeKeyDown" | "onPointerDownOutside" | "onFocusOutside" | "onInteractOutside">;
|
|
119
|
+
/**
|
|
120
|
+
* Dialog.Content event handlers excluding onInteractOutside.
|
|
121
|
+
* Used for draggable modals where onInteractOutside conflicts with the draggable UX.
|
|
122
|
+
*/
|
|
123
|
+
type DialogContentEventHandlersWithoutInteractOutside = Omit<DialogContentEventHandlers, "onInteractOutside">;
|
|
114
124
|
/**
|
|
115
125
|
* Base common props shared by all modal variants (without close button props).
|
|
116
126
|
*/
|
|
@@ -139,6 +149,16 @@ type TypeModalCommonPropsBase = TypeContainerProps & Omit<React.ComponentPropsWi
|
|
|
139
149
|
actions?: TypeModalActionProps[];
|
|
140
150
|
/** Controls the z-index CSS property (defaults to 6 to match Modal v1) */
|
|
141
151
|
zIndex?: number;
|
|
152
|
+
/**
|
|
153
|
+
* Prevents the modal from closing when clicking outside.
|
|
154
|
+
* The onInteractOutside handler will still be called if provided.
|
|
155
|
+
*/
|
|
156
|
+
disableOutsideClickClose?: boolean;
|
|
157
|
+
/**
|
|
158
|
+
* Prevents the modal from closing when pressing the Escape key.
|
|
159
|
+
* The onEscapeKeyDown handler will still be called if provided.
|
|
160
|
+
*/
|
|
161
|
+
disableEscapeKeyClose?: boolean;
|
|
142
162
|
};
|
|
143
163
|
/**
|
|
144
164
|
* Common props with close button accessibility enforcement.
|
|
@@ -194,13 +214,23 @@ type TypeModalCommonProps = (TypeModalCommonPropsBase & {
|
|
|
194
214
|
/**
|
|
195
215
|
* Base props with draggable and showOverlay relationship enforced.
|
|
196
216
|
*
|
|
197
|
-
* When draggable is true
|
|
198
|
-
* would block interaction with content behind the modal
|
|
217
|
+
* When draggable is true:
|
|
218
|
+
* - showOverlay must be false (overlay would block interaction with content behind the modal)
|
|
219
|
+
* - onInteractOutside is not allowed (conflicts with draggable UX which needs to keep modal open)
|
|
220
|
+
* - disableOutsideClickClose is not allowed (since onInteractOutside is not supported)
|
|
221
|
+
*
|
|
222
|
+
* When draggable is false (or undefined):
|
|
223
|
+
* - All Dialog.Content event handlers are available including onInteractOutside
|
|
224
|
+
* - disableOutsideClickClose can be used
|
|
199
225
|
*/
|
|
200
|
-
type TypeModalBaseProps = (TypeModalCommonProps & {
|
|
226
|
+
type TypeModalBaseProps = (TypeModalCommonProps & DialogContentEventHandlersWithoutInteractOutside & {
|
|
201
227
|
draggable: true;
|
|
202
228
|
showOverlay?: false;
|
|
203
|
-
|
|
229
|
+
/** onInteractOutside is not supported for draggable modals */
|
|
230
|
+
onInteractOutside?: never;
|
|
231
|
+
/** disableOutsideClickClose is not supported for draggable modals */
|
|
232
|
+
disableOutsideClickClose?: never;
|
|
233
|
+
}) | (TypeModalCommonProps & DialogContentEventHandlers & {
|
|
204
234
|
draggable?: false;
|
|
205
235
|
showOverlay?: boolean;
|
|
206
236
|
});
|
|
@@ -303,6 +333,121 @@ type TypeModalProps = TypeModalPropsWithTitle | TypeModalPropsWithSubtitleOnly |
|
|
|
303
333
|
* </Modal>
|
|
304
334
|
*/
|
|
305
335
|
declare const Modal: (props: TypeModalProps) => react_jsx_runtime.JSX.Element;
|
|
336
|
+
/**
|
|
337
|
+
* Hook for adding proper ARIA attributes to external modal triggers.
|
|
338
|
+
*
|
|
339
|
+
* ⚠️ **NOT RECOMMENDED** - Prefer using modalTrigger prop or ModalTrigger component.
|
|
340
|
+
* Use this hook ONLY as a last resort when architectural constraints prevent keeping
|
|
341
|
+
* the trigger inside the Modal component tree.
|
|
342
|
+
*
|
|
343
|
+
* **Important Limitations:**
|
|
344
|
+
* - This hook only provides ARIA attributes (aria-haspopup, aria-expanded)
|
|
345
|
+
* - Focus restoration is NOT automatic - you must manually handle it with refs
|
|
346
|
+
* - Radix UI cannot track external triggers for proper accessibility
|
|
347
|
+
*
|
|
348
|
+
* **Why modalTrigger prop is better:**
|
|
349
|
+
* - Automatic ARIA attributes
|
|
350
|
+
* - Automatic focus restoration
|
|
351
|
+
* - Better touch device support
|
|
352
|
+
* - Follows WAI-ARIA Dialog best practices
|
|
353
|
+
*
|
|
354
|
+
* @param isOpen - Current open state of the modal
|
|
355
|
+
* @param modalId - Optional ID of the modal element for aria-controls
|
|
356
|
+
* @returns Object with ARIA attributes to spread onto trigger element
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* ```tsx
|
|
360
|
+
* // Manual focus restoration required
|
|
361
|
+
* const [isOpen, setIsOpen] = useState(false);
|
|
362
|
+
* const triggerRef = useRef<HTMLButtonElement>(null);
|
|
363
|
+
* const triggerProps = useModalTriggerProps(isOpen);
|
|
364
|
+
*
|
|
365
|
+
* return (
|
|
366
|
+
* <>
|
|
367
|
+
* <Button
|
|
368
|
+
* ref={triggerRef}
|
|
369
|
+
* {...triggerProps}
|
|
370
|
+
* onClick={() => setIsOpen(true)}
|
|
371
|
+
* >
|
|
372
|
+
* Open Modal
|
|
373
|
+
* </Button>
|
|
374
|
+
* <Modal
|
|
375
|
+
* open={isOpen}
|
|
376
|
+
* onOpenChange={setIsOpen}
|
|
377
|
+
* onCloseAutoFocus={(e) => {
|
|
378
|
+
* e.preventDefault();
|
|
379
|
+
* triggerRef.current?.focus();
|
|
380
|
+
* }}
|
|
381
|
+
* >
|
|
382
|
+
* <ModalBody>Content</ModalBody>
|
|
383
|
+
* </Modal>
|
|
384
|
+
* </>
|
|
385
|
+
* );
|
|
386
|
+
* ```
|
|
387
|
+
*/
|
|
388
|
+
declare function useModalTriggerProps(isOpen: boolean, modalId?: string): {
|
|
389
|
+
"aria-haspopup": "dialog";
|
|
390
|
+
"aria-expanded": boolean;
|
|
391
|
+
"aria-controls"?: string;
|
|
392
|
+
};
|
|
393
|
+
/**
|
|
394
|
+
* Hook for managing external modal triggers with automatic focus restoration.
|
|
395
|
+
*
|
|
396
|
+
* ⚠️ **NOT RECOMMENDED** - Prefer using modalTrigger prop or ModalTrigger component.
|
|
397
|
+
* Use this hook ONLY as a last resort when architectural constraints prevent keeping
|
|
398
|
+
* the trigger inside the Modal component tree.
|
|
399
|
+
*
|
|
400
|
+
* This hook improves upon useModalTriggerProps by managing the trigger ref internally
|
|
401
|
+
* and providing the onCloseAutoFocus callback, eliminating the need for manual
|
|
402
|
+
* focus restoration boilerplate.
|
|
403
|
+
*
|
|
404
|
+
* **Improvements over useModalTriggerProps:**
|
|
405
|
+
* - ✅ No manual ref creation
|
|
406
|
+
* - ✅ Automatic focus restoration via onCloseAutoFocus callback
|
|
407
|
+
* - ✅ Automatic ARIA attributes
|
|
408
|
+
*
|
|
409
|
+
* **Why modalTrigger prop is still better:**
|
|
410
|
+
* - Better touch device support
|
|
411
|
+
* - Follows WAI-ARIA Dialog best practices
|
|
412
|
+
* - Less boilerplate overall
|
|
413
|
+
*
|
|
414
|
+
* @param modalId - Optional ID of the modal element for aria-controls
|
|
415
|
+
* @returns Object with triggerRef, ARIA props, and onCloseAutoFocus callback
|
|
416
|
+
*
|
|
417
|
+
* @example
|
|
418
|
+
* ```tsx
|
|
419
|
+
* const [isOpen, setIsOpen] = useState(false);
|
|
420
|
+
* const { triggerRef, triggerProps, onCloseAutoFocus } = useModalExternalTrigger();
|
|
421
|
+
*
|
|
422
|
+
* return (
|
|
423
|
+
* <>
|
|
424
|
+
* <Button
|
|
425
|
+
* ref={triggerRef}
|
|
426
|
+
* {...triggerProps(isOpen)}
|
|
427
|
+
* onClick={() => setIsOpen(true)}
|
|
428
|
+
* >
|
|
429
|
+
* Open Modal
|
|
430
|
+
* </Button>
|
|
431
|
+
* <Modal
|
|
432
|
+
* open={isOpen}
|
|
433
|
+
* onOpenChange={setIsOpen}
|
|
434
|
+
* onCloseAutoFocus={onCloseAutoFocus}
|
|
435
|
+
* >
|
|
436
|
+
* <ModalBody>Content</ModalBody>
|
|
437
|
+
* </Modal>
|
|
438
|
+
* </>
|
|
439
|
+
* );
|
|
440
|
+
* ```
|
|
441
|
+
*/
|
|
442
|
+
declare function useModalExternalTrigger<T extends HTMLElement = HTMLButtonElement>(modalId?: string): {
|
|
443
|
+
triggerRef: React.RefObject<T>;
|
|
444
|
+
triggerProps: (isOpen: boolean) => {
|
|
445
|
+
"aria-controls"?: string | undefined;
|
|
446
|
+
"aria-haspopup": "dialog";
|
|
447
|
+
"aria-expanded": boolean;
|
|
448
|
+
};
|
|
449
|
+
onCloseAutoFocus: (e: Event) => void;
|
|
450
|
+
};
|
|
306
451
|
|
|
307
452
|
interface HeaderProps {
|
|
308
453
|
draggable?: boolean;
|
|
@@ -416,4 +561,4 @@ declare const ModalRail: React.FC<TypeModalRailProps>;
|
|
|
416
561
|
|
|
417
562
|
declare const ModalAction: React.FC<TypeModalActionProps>;
|
|
418
563
|
|
|
419
|
-
export { Modal as M, type TypeModalProps as T, ModalDescription as a, ModalHeader as b, ModalFooter as c, ModalBody as d, ModalCloseWrapper as e, ModalRail as f, ModalAction as g, ModalCustomHeader as h, ModalCustomFooter as i, type TypeModalHeaderProps as j, type TypeModalFooterProps as k, type TypeModalBodyProps as l, type TypeModalDescriptionProps as m, type TypeModalRailProps as n, type TypeModalActionProps as o, type ModalCloseWrapperProps as p };
|
|
564
|
+
export { Modal as M, type TypeModalProps as T, ModalDescription as a, ModalHeader as b, ModalFooter as c, ModalBody as d, ModalCloseWrapper as e, ModalRail as f, ModalAction as g, ModalCustomHeader as h, ModalCustomFooter as i, type TypeModalHeaderProps as j, type TypeModalFooterProps as k, type TypeModalBodyProps as l, type TypeModalDescriptionProps as m, type TypeModalRailProps as n, type TypeModalActionProps as o, type ModalCloseWrapperProps as p, useModalExternalTrigger as q, useModalTriggerProps as u };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as React from 'react';
|
|
2
3
|
import * as styled_components from 'styled-components';
|
|
3
4
|
import * as _sproutsocial_seeds_react_box from '@sproutsocial/seeds-react-box';
|
|
4
5
|
import { TypeContainerProps, TypeBoxProps } from '@sproutsocial/seeds-react-box';
|
|
5
|
-
import * as React from 'react';
|
|
6
6
|
import * as Dialog from '@radix-ui/react-dialog';
|
|
7
7
|
import { TypeIconName } from '@sproutsocial/seeds-react-icon';
|
|
8
8
|
|
|
@@ -111,6 +111,16 @@ type TypeModalActionProps = ({
|
|
|
111
111
|
/** Click handler for the button */
|
|
112
112
|
onClick?: () => void;
|
|
113
113
|
} & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onClick">);
|
|
114
|
+
/**
|
|
115
|
+
* Event handler props from Radix UI Dialog.Content.
|
|
116
|
+
* These control modal interaction behavior like closing on outside clicks or escape key.
|
|
117
|
+
*/
|
|
118
|
+
type DialogContentEventHandlers = Pick<React.ComponentPropsWithoutRef<typeof Dialog.Content>, "onOpenAutoFocus" | "onCloseAutoFocus" | "onEscapeKeyDown" | "onPointerDownOutside" | "onFocusOutside" | "onInteractOutside">;
|
|
119
|
+
/**
|
|
120
|
+
* Dialog.Content event handlers excluding onInteractOutside.
|
|
121
|
+
* Used for draggable modals where onInteractOutside conflicts with the draggable UX.
|
|
122
|
+
*/
|
|
123
|
+
type DialogContentEventHandlersWithoutInteractOutside = Omit<DialogContentEventHandlers, "onInteractOutside">;
|
|
114
124
|
/**
|
|
115
125
|
* Base common props shared by all modal variants (without close button props).
|
|
116
126
|
*/
|
|
@@ -139,6 +149,16 @@ type TypeModalCommonPropsBase = TypeContainerProps & Omit<React.ComponentPropsWi
|
|
|
139
149
|
actions?: TypeModalActionProps[];
|
|
140
150
|
/** Controls the z-index CSS property (defaults to 6 to match Modal v1) */
|
|
141
151
|
zIndex?: number;
|
|
152
|
+
/**
|
|
153
|
+
* Prevents the modal from closing when clicking outside.
|
|
154
|
+
* The onInteractOutside handler will still be called if provided.
|
|
155
|
+
*/
|
|
156
|
+
disableOutsideClickClose?: boolean;
|
|
157
|
+
/**
|
|
158
|
+
* Prevents the modal from closing when pressing the Escape key.
|
|
159
|
+
* The onEscapeKeyDown handler will still be called if provided.
|
|
160
|
+
*/
|
|
161
|
+
disableEscapeKeyClose?: boolean;
|
|
142
162
|
};
|
|
143
163
|
/**
|
|
144
164
|
* Common props with close button accessibility enforcement.
|
|
@@ -194,13 +214,23 @@ type TypeModalCommonProps = (TypeModalCommonPropsBase & {
|
|
|
194
214
|
/**
|
|
195
215
|
* Base props with draggable and showOverlay relationship enforced.
|
|
196
216
|
*
|
|
197
|
-
* When draggable is true
|
|
198
|
-
* would block interaction with content behind the modal
|
|
217
|
+
* When draggable is true:
|
|
218
|
+
* - showOverlay must be false (overlay would block interaction with content behind the modal)
|
|
219
|
+
* - onInteractOutside is not allowed (conflicts with draggable UX which needs to keep modal open)
|
|
220
|
+
* - disableOutsideClickClose is not allowed (since onInteractOutside is not supported)
|
|
221
|
+
*
|
|
222
|
+
* When draggable is false (or undefined):
|
|
223
|
+
* - All Dialog.Content event handlers are available including onInteractOutside
|
|
224
|
+
* - disableOutsideClickClose can be used
|
|
199
225
|
*/
|
|
200
|
-
type TypeModalBaseProps = (TypeModalCommonProps & {
|
|
226
|
+
type TypeModalBaseProps = (TypeModalCommonProps & DialogContentEventHandlersWithoutInteractOutside & {
|
|
201
227
|
draggable: true;
|
|
202
228
|
showOverlay?: false;
|
|
203
|
-
|
|
229
|
+
/** onInteractOutside is not supported for draggable modals */
|
|
230
|
+
onInteractOutside?: never;
|
|
231
|
+
/** disableOutsideClickClose is not supported for draggable modals */
|
|
232
|
+
disableOutsideClickClose?: never;
|
|
233
|
+
}) | (TypeModalCommonProps & DialogContentEventHandlers & {
|
|
204
234
|
draggable?: false;
|
|
205
235
|
showOverlay?: boolean;
|
|
206
236
|
});
|
|
@@ -303,6 +333,121 @@ type TypeModalProps = TypeModalPropsWithTitle | TypeModalPropsWithSubtitleOnly |
|
|
|
303
333
|
* </Modal>
|
|
304
334
|
*/
|
|
305
335
|
declare const Modal: (props: TypeModalProps) => react_jsx_runtime.JSX.Element;
|
|
336
|
+
/**
|
|
337
|
+
* Hook for adding proper ARIA attributes to external modal triggers.
|
|
338
|
+
*
|
|
339
|
+
* ⚠️ **NOT RECOMMENDED** - Prefer using modalTrigger prop or ModalTrigger component.
|
|
340
|
+
* Use this hook ONLY as a last resort when architectural constraints prevent keeping
|
|
341
|
+
* the trigger inside the Modal component tree.
|
|
342
|
+
*
|
|
343
|
+
* **Important Limitations:**
|
|
344
|
+
* - This hook only provides ARIA attributes (aria-haspopup, aria-expanded)
|
|
345
|
+
* - Focus restoration is NOT automatic - you must manually handle it with refs
|
|
346
|
+
* - Radix UI cannot track external triggers for proper accessibility
|
|
347
|
+
*
|
|
348
|
+
* **Why modalTrigger prop is better:**
|
|
349
|
+
* - Automatic ARIA attributes
|
|
350
|
+
* - Automatic focus restoration
|
|
351
|
+
* - Better touch device support
|
|
352
|
+
* - Follows WAI-ARIA Dialog best practices
|
|
353
|
+
*
|
|
354
|
+
* @param isOpen - Current open state of the modal
|
|
355
|
+
* @param modalId - Optional ID of the modal element for aria-controls
|
|
356
|
+
* @returns Object with ARIA attributes to spread onto trigger element
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* ```tsx
|
|
360
|
+
* // Manual focus restoration required
|
|
361
|
+
* const [isOpen, setIsOpen] = useState(false);
|
|
362
|
+
* const triggerRef = useRef<HTMLButtonElement>(null);
|
|
363
|
+
* const triggerProps = useModalTriggerProps(isOpen);
|
|
364
|
+
*
|
|
365
|
+
* return (
|
|
366
|
+
* <>
|
|
367
|
+
* <Button
|
|
368
|
+
* ref={triggerRef}
|
|
369
|
+
* {...triggerProps}
|
|
370
|
+
* onClick={() => setIsOpen(true)}
|
|
371
|
+
* >
|
|
372
|
+
* Open Modal
|
|
373
|
+
* </Button>
|
|
374
|
+
* <Modal
|
|
375
|
+
* open={isOpen}
|
|
376
|
+
* onOpenChange={setIsOpen}
|
|
377
|
+
* onCloseAutoFocus={(e) => {
|
|
378
|
+
* e.preventDefault();
|
|
379
|
+
* triggerRef.current?.focus();
|
|
380
|
+
* }}
|
|
381
|
+
* >
|
|
382
|
+
* <ModalBody>Content</ModalBody>
|
|
383
|
+
* </Modal>
|
|
384
|
+
* </>
|
|
385
|
+
* );
|
|
386
|
+
* ```
|
|
387
|
+
*/
|
|
388
|
+
declare function useModalTriggerProps(isOpen: boolean, modalId?: string): {
|
|
389
|
+
"aria-haspopup": "dialog";
|
|
390
|
+
"aria-expanded": boolean;
|
|
391
|
+
"aria-controls"?: string;
|
|
392
|
+
};
|
|
393
|
+
/**
|
|
394
|
+
* Hook for managing external modal triggers with automatic focus restoration.
|
|
395
|
+
*
|
|
396
|
+
* ⚠️ **NOT RECOMMENDED** - Prefer using modalTrigger prop or ModalTrigger component.
|
|
397
|
+
* Use this hook ONLY as a last resort when architectural constraints prevent keeping
|
|
398
|
+
* the trigger inside the Modal component tree.
|
|
399
|
+
*
|
|
400
|
+
* This hook improves upon useModalTriggerProps by managing the trigger ref internally
|
|
401
|
+
* and providing the onCloseAutoFocus callback, eliminating the need for manual
|
|
402
|
+
* focus restoration boilerplate.
|
|
403
|
+
*
|
|
404
|
+
* **Improvements over useModalTriggerProps:**
|
|
405
|
+
* - ✅ No manual ref creation
|
|
406
|
+
* - ✅ Automatic focus restoration via onCloseAutoFocus callback
|
|
407
|
+
* - ✅ Automatic ARIA attributes
|
|
408
|
+
*
|
|
409
|
+
* **Why modalTrigger prop is still better:**
|
|
410
|
+
* - Better touch device support
|
|
411
|
+
* - Follows WAI-ARIA Dialog best practices
|
|
412
|
+
* - Less boilerplate overall
|
|
413
|
+
*
|
|
414
|
+
* @param modalId - Optional ID of the modal element for aria-controls
|
|
415
|
+
* @returns Object with triggerRef, ARIA props, and onCloseAutoFocus callback
|
|
416
|
+
*
|
|
417
|
+
* @example
|
|
418
|
+
* ```tsx
|
|
419
|
+
* const [isOpen, setIsOpen] = useState(false);
|
|
420
|
+
* const { triggerRef, triggerProps, onCloseAutoFocus } = useModalExternalTrigger();
|
|
421
|
+
*
|
|
422
|
+
* return (
|
|
423
|
+
* <>
|
|
424
|
+
* <Button
|
|
425
|
+
* ref={triggerRef}
|
|
426
|
+
* {...triggerProps(isOpen)}
|
|
427
|
+
* onClick={() => setIsOpen(true)}
|
|
428
|
+
* >
|
|
429
|
+
* Open Modal
|
|
430
|
+
* </Button>
|
|
431
|
+
* <Modal
|
|
432
|
+
* open={isOpen}
|
|
433
|
+
* onOpenChange={setIsOpen}
|
|
434
|
+
* onCloseAutoFocus={onCloseAutoFocus}
|
|
435
|
+
* >
|
|
436
|
+
* <ModalBody>Content</ModalBody>
|
|
437
|
+
* </Modal>
|
|
438
|
+
* </>
|
|
439
|
+
* );
|
|
440
|
+
* ```
|
|
441
|
+
*/
|
|
442
|
+
declare function useModalExternalTrigger<T extends HTMLElement = HTMLButtonElement>(modalId?: string): {
|
|
443
|
+
triggerRef: React.RefObject<T>;
|
|
444
|
+
triggerProps: (isOpen: boolean) => {
|
|
445
|
+
"aria-controls"?: string | undefined;
|
|
446
|
+
"aria-haspopup": "dialog";
|
|
447
|
+
"aria-expanded": boolean;
|
|
448
|
+
};
|
|
449
|
+
onCloseAutoFocus: (e: Event) => void;
|
|
450
|
+
};
|
|
306
451
|
|
|
307
452
|
interface HeaderProps {
|
|
308
453
|
draggable?: boolean;
|
|
@@ -416,4 +561,4 @@ declare const ModalRail: React.FC<TypeModalRailProps>;
|
|
|
416
561
|
|
|
417
562
|
declare const ModalAction: React.FC<TypeModalActionProps>;
|
|
418
563
|
|
|
419
|
-
export { Modal as M, type TypeModalProps as T, ModalDescription as a, ModalHeader as b, ModalFooter as c, ModalBody as d, ModalCloseWrapper as e, ModalRail as f, ModalAction as g, ModalCustomHeader as h, ModalCustomFooter as i, type TypeModalHeaderProps as j, type TypeModalFooterProps as k, type TypeModalBodyProps as l, type TypeModalDescriptionProps as m, type TypeModalRailProps as n, type TypeModalActionProps as o, type ModalCloseWrapperProps as p };
|
|
564
|
+
export { Modal as M, type TypeModalProps as T, ModalDescription as a, ModalHeader as b, ModalFooter as c, ModalBody as d, ModalCloseWrapper as e, ModalRail as f, ModalAction as g, ModalCustomHeader as h, ModalCustomFooter as i, type TypeModalHeaderProps as j, type TypeModalFooterProps as k, type TypeModalBodyProps as l, type TypeModalDescriptionProps as m, type TypeModalRailProps as n, type TypeModalActionProps as o, type ModalCloseWrapperProps as p, useModalExternalTrigger as q, useModalTriggerProps as u };
|