@sproutsocial/seeds-react-modal 2.3.0 → 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.
@@ -8,34 +8,34 @@ $ tsup --dts
8
8
  CLI Cleaning output folder
9
9
  CJS Build start
10
10
  ESM Build start
11
- CJS dist/index.js 38.17 KB
12
- CJS dist/v1/index.js 9.86 KB
13
- CJS dist/v2/index.js 30.16 KB
14
- CJS dist/index.js.map 69.27 KB
15
- CJS dist/v1/index.js.map 13.03 KB
16
- CJS dist/v2/index.js.map 55.88 KB
17
- CJS ⚡️ Build success in 201ms
18
11
  ESM dist/esm/index.js 583.00 B
19
12
  ESM dist/esm/v1/index.js 165.00 B
20
- ESM dist/esm/v2/index.js 596.00 B
13
+ ESM dist/esm/v2/index.js 746.00 B
21
14
  ESM dist/esm/chunk-IYDY4OPB.js 7.12 KB
22
- ESM dist/esm/chunk-4F3JQDQZ.js 25.32 KB
15
+ ESM dist/esm/chunk-TQ44T5IM.js 26.85 KB
23
16
  ESM dist/esm/index.js.map 1.05 KB
24
17
  ESM dist/esm/v1/index.js.map 71.00 B
25
18
  ESM dist/esm/v2/index.js.map 71.00 B
26
19
  ESM dist/esm/chunk-IYDY4OPB.js.map 12.85 KB
27
- ESM dist/esm/chunk-4F3JQDQZ.js.map 54.99 KB
28
- ESM ⚡️ Build success in 205ms
20
+ ESM dist/esm/chunk-TQ44T5IM.js.map 63.46 KB
21
+ ESM ⚡️ Build success in 280ms
22
+ CJS dist/index.js 39.00 KB
23
+ CJS dist/v1/index.js 9.86 KB
24
+ CJS dist/v2/index.js 31.92 KB
25
+ CJS dist/index.js.map 77.26 KB
26
+ CJS dist/v1/index.js.map 13.03 KB
27
+ CJS dist/v2/index.js.map 64.54 KB
28
+ CJS ⚡️ Build success in 284ms
29
29
  DTS Build start
30
- DTS ⚡️ Build success in 16886ms
30
+ DTS ⚡️ Build success in 19209ms
31
31
  DTS dist/index.d.ts 975.00 B
32
32
  DTS dist/v1/index.d.ts 413.00 B
33
- DTS dist/v2/index.d.ts 1021.00 B
33
+ DTS dist/v2/index.d.ts 3.72 KB
34
34
  DTS dist/Modal-ki8oiGbC.d.ts 2.52 KB
35
- DTS dist/ModalAction-B9lvk1rm.d.ts 16.80 KB
35
+ DTS dist/ModalAction-BHG3Zbd9.d.ts 20.46 KB
36
36
  DTS dist/index.d.mts 978.00 B
37
37
  DTS dist/v1/index.d.mts 415.00 B
38
- DTS dist/v2/index.d.mts 1022.00 B
38
+ DTS dist/v2/index.d.mts 3.72 KB
39
39
  DTS dist/Modal-ki8oiGbC.d.mts 2.52 KB
40
- DTS dist/ModalAction-B9lvk1rm.d.mts 16.80 KB
41
- Done in 19.02s.
40
+ DTS dist/ModalAction-BHG3Zbd9.d.mts 20.46 KB
41
+ Done in 22.38s.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
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
+
3
13
  ## 2.3.0
4
14
 
5
15
  ### Minor 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
 
@@ -333,6 +333,121 @@ type TypeModalProps = TypeModalPropsWithTitle | TypeModalPropsWithSubtitleOnly |
333
333
  * </Modal>
334
334
  */
335
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
+ };
336
451
 
337
452
  interface HeaderProps {
338
453
  draggable?: boolean;
@@ -446,4 +561,4 @@ declare const ModalRail: React.FC<TypeModalRailProps>;
446
561
 
447
562
  declare const ModalAction: React.FC<TypeModalActionProps>;
448
563
 
449
- 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
 
@@ -333,6 +333,121 @@ type TypeModalProps = TypeModalPropsWithTitle | TypeModalPropsWithSubtitleOnly |
333
333
  * </Modal>
334
334
  */
335
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
+ };
336
451
 
337
452
  interface HeaderProps {
338
453
  draggable?: boolean;
@@ -446,4 +561,4 @@ declare const ModalRail: React.FC<TypeModalRailProps>;
446
561
 
447
562
  declare const ModalAction: React.FC<TypeModalActionProps>;
448
563
 
449
- 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,5 +1,5 @@
1
1
  // src/v2/Modal.tsx
2
- import * as React11 from "react";
2
+ import * as React12 from "react";
3
3
  import * as Dialog6 from "@radix-ui/react-dialog";
4
4
  import { AnimatePresence } from "motion/react";
5
5
 
@@ -705,6 +705,34 @@ var ModalAction = ({
705
705
  };
706
706
  ModalAction.displayName = "ModalAction";
707
707
 
708
+ // src/v2/components/ModalExternalTrigger.tsx
709
+ import * as React10 from "react";
710
+ import { Button } from "@sproutsocial/seeds-react-button";
711
+ import { jsx as jsx9 } from "react/jsx-runtime";
712
+ var ModalExternalTrigger = React10.forwardRef(({ isOpen, onTrigger, modalId, onClick, ...buttonProps }, ref) => {
713
+ const handleClick = React10.useCallback(
714
+ (e) => {
715
+ onClick?.(e);
716
+ if (!e.defaultPrevented) {
717
+ onTrigger();
718
+ }
719
+ },
720
+ [onClick, onTrigger]
721
+ );
722
+ return /* @__PURE__ */ jsx9(
723
+ Button,
724
+ {
725
+ ref,
726
+ onClick: handleClick,
727
+ "aria-haspopup": "dialog",
728
+ "aria-expanded": isOpen,
729
+ "aria-controls": modalId,
730
+ ...buttonProps
731
+ }
732
+ );
733
+ });
734
+ ModalExternalTrigger.displayName = "ModalExternalTrigger";
735
+
708
736
  // src/v2/components/ModalOverlay.tsx
709
737
  import "react";
710
738
  import styled7 from "styled-components";
@@ -743,7 +771,7 @@ var StyledOverlay = styled7.div.withConfig({
743
771
  StyledOverlay.displayName = "ModalOverlay";
744
772
 
745
773
  // src/v2/Modal.tsx
746
- import { Fragment, jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
774
+ import { Fragment, jsx as jsx10, jsxs as jsxs3 } from "react/jsx-runtime";
747
775
  var Modal = (props) => {
748
776
  const {
749
777
  children,
@@ -774,15 +802,15 @@ var Modal = (props) => {
774
802
  disableEscapeKeyClose = false,
775
803
  ...rest
776
804
  } = props;
777
- const [isOpen, setIsOpen] = React11.useState(defaultOpen ?? false);
778
- const handleOpenChange = React11.useCallback(
805
+ const [isOpen, setIsOpen] = React12.useState(defaultOpen ?? false);
806
+ const handleOpenChange = React12.useCallback(
779
807
  (newOpen) => {
780
808
  setIsOpen(newOpen);
781
809
  onOpenChange?.(newOpen);
782
810
  },
783
811
  [onOpenChange]
784
812
  );
785
- const dataAttributes = React11.useMemo(() => {
813
+ const dataAttributes = React12.useMemo(() => {
786
814
  const attrs = {};
787
815
  Object.entries(data).forEach(([key, value]) => {
788
816
  attrs[`data-${key}`] = String(value);
@@ -797,7 +825,7 @@ var Modal = (props) => {
797
825
  const isMobile = useIsMobile();
798
826
  const overlayVariants = getOverlayVariants(isMobile);
799
827
  const ModalContentComponent = draggable ? DraggableModalContent : StaticModalContent;
800
- const wrappedOnInteractOutside = React11.useCallback(
828
+ const wrappedOnInteractOutside = React12.useCallback(
801
829
  (e) => {
802
830
  if (disableOutsideClickClose) {
803
831
  e.preventDefault();
@@ -806,7 +834,7 @@ var Modal = (props) => {
806
834
  },
807
835
  [disableOutsideClickClose, onInteractOutside]
808
836
  );
809
- const wrappedOnEscapeKeyDown = React11.useCallback(
837
+ const wrappedOnEscapeKeyDown = React12.useCallback(
810
838
  (e) => {
811
839
  if (disableEscapeKeyClose) {
812
840
  e.preventDefault();
@@ -823,9 +851,9 @@ var Modal = (props) => {
823
851
  onOpenChange: handleOpenChange,
824
852
  modal: !draggable,
825
853
  children: [
826
- modalTrigger && /* @__PURE__ */ jsx9(Dialog6.Trigger, { asChild: true, children: modalTrigger }),
827
- /* @__PURE__ */ jsx9(Dialog6.Portal, { forceMount: true, children: /* @__PURE__ */ jsx9(AnimatePresence, { mode: "wait", children: (open ?? isOpen) && /* @__PURE__ */ jsxs3(Fragment, { children: [
828
- showOverlay && /* @__PURE__ */ jsx9(Dialog6.Overlay, { asChild: true, children: /* @__PURE__ */ jsx9(
854
+ modalTrigger && /* @__PURE__ */ jsx10(Dialog6.Trigger, { asChild: true, children: modalTrigger }),
855
+ /* @__PURE__ */ jsx10(Dialog6.Portal, { forceMount: true, children: /* @__PURE__ */ jsx10(AnimatePresence, { mode: "wait", children: (open ?? isOpen) && /* @__PURE__ */ jsxs3(Fragment, { children: [
856
+ showOverlay && /* @__PURE__ */ jsx10(Dialog6.Overlay, { asChild: true, children: /* @__PURE__ */ jsx10(
829
857
  StyledMotionOverlay,
830
858
  {
831
859
  $zIndex: zIndex,
@@ -833,7 +861,7 @@ var Modal = (props) => {
833
861
  initial: "initial",
834
862
  animate: "animate",
835
863
  exit: "exit",
836
- children: /* @__PURE__ */ jsx9(
864
+ children: /* @__PURE__ */ jsx10(
837
865
  StyledOverlay,
838
866
  {
839
867
  "data-slot": "modal-overlay",
@@ -862,7 +890,7 @@ var Modal = (props) => {
862
890
  },
863
891
  children: [
864
892
  /* @__PURE__ */ jsxs3(ModalRail, { children: [
865
- /* @__PURE__ */ jsx9(
893
+ /* @__PURE__ */ jsx10(
866
894
  ModalAction,
867
895
  {
868
896
  actionType: "close",
@@ -871,10 +899,10 @@ var Modal = (props) => {
871
899
  "aria-label": closeButtonProps?.["aria-label"] ?? closeButtonAriaLabel
872
900
  }
873
901
  ),
874
- actions?.map((action, idx) => /* @__PURE__ */ jsx9(ModalAction, { ...action }, idx))
902
+ actions?.map((action, idx) => /* @__PURE__ */ jsx10(ModalAction, { ...action }, idx))
875
903
  ] }),
876
- shouldRenderHeader && /* @__PURE__ */ jsx9(ModalHeader, { title, subtitle }),
877
- description && /* @__PURE__ */ jsx9(ModalDescription, { children: description }),
904
+ shouldRenderHeader && /* @__PURE__ */ jsx10(ModalHeader, { title, subtitle }),
905
+ description && /* @__PURE__ */ jsx10(ModalDescription, { children: description }),
878
906
  children
879
907
  ]
880
908
  }
@@ -884,6 +912,32 @@ var Modal = (props) => {
884
912
  }
885
913
  );
886
914
  };
915
+ function useModalTriggerProps(isOpen, modalId) {
916
+ return React12.useMemo(
917
+ () => ({
918
+ "aria-haspopup": "dialog",
919
+ "aria-expanded": isOpen,
920
+ ...modalId ? { "aria-controls": modalId } : {}
921
+ }),
922
+ [isOpen, modalId]
923
+ );
924
+ }
925
+ function useModalExternalTrigger(modalId) {
926
+ const triggerRef = React12.useRef(null);
927
+ const triggerProps = React12.useCallback(
928
+ (isOpen) => ({
929
+ "aria-haspopup": "dialog",
930
+ "aria-expanded": isOpen,
931
+ ...modalId ? { "aria-controls": modalId } : {}
932
+ }),
933
+ [modalId]
934
+ );
935
+ const onCloseAutoFocus = React12.useCallback((e) => {
936
+ e.preventDefault();
937
+ triggerRef.current?.focus();
938
+ }, []);
939
+ return { triggerRef, triggerProps, onCloseAutoFocus };
940
+ }
887
941
  var Modal_default = Modal;
888
942
 
889
943
  export {
@@ -900,6 +954,9 @@ export {
900
954
  ModalDescription,
901
955
  ModalRail,
902
956
  ModalAction,
957
+ ModalExternalTrigger,
958
+ useModalTriggerProps,
959
+ useModalExternalTrigger,
903
960
  Modal_default
904
961
  };
905
- //# sourceMappingURL=chunk-4F3JQDQZ.js.map
962
+ //# sourceMappingURL=chunk-TQ44T5IM.js.map