@trackunit/react-modal 1.10.1 → 1.10.2
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/index.cjs.js +82 -40
- package/index.esm.js +83 -41
- package/package.json +7 -6
- package/src/modal/Modal.d.ts +10 -10
- package/src/modal/Modal.stories.d.ts +41 -37
- package/src/modal/Modal.variants.d.ts +6 -0
- package/src/modal/modalSizeUsage.d.ts +11 -0
- package/src/modal/useModal.d.ts +10 -3
package/index.cjs.js
CHANGED
|
@@ -56,26 +56,73 @@ const cvaModalContainer = cssClassVarianceUtilities.cvaMerge([
|
|
|
56
56
|
"w-full",
|
|
57
57
|
"flex",
|
|
58
58
|
"fixed",
|
|
59
|
-
"
|
|
60
|
-
"left-0",
|
|
59
|
+
"inset-0",
|
|
61
60
|
"min-h-[100dvh]",
|
|
62
61
|
"min-w-[100dvw]",
|
|
63
62
|
"overflow-auto",
|
|
64
63
|
"items-center",
|
|
65
64
|
]);
|
|
65
|
+
const modalSizeConfigs = {
|
|
66
|
+
small: {
|
|
67
|
+
// Small modal — confirmations / short messages
|
|
68
|
+
minWidth: "320px",
|
|
69
|
+
viewportWidthPercent: "40dvw",
|
|
70
|
+
paddingOffset: "1rem",
|
|
71
|
+
breakpoint: "480px",
|
|
72
|
+
transitionRate: 0.15,
|
|
73
|
+
maxWidth: "520px",
|
|
74
|
+
maxHeight: "48dvh",
|
|
75
|
+
},
|
|
76
|
+
medium: {
|
|
77
|
+
// Medium modal — simple forms and single-step flows
|
|
78
|
+
minWidth: "320px",
|
|
79
|
+
viewportWidthPercent: "64dvw",
|
|
80
|
+
paddingOffset: "1rem",
|
|
81
|
+
breakpoint: "480px",
|
|
82
|
+
transitionRate: 0.15,
|
|
83
|
+
maxWidth: "700px",
|
|
84
|
+
maxHeight: "60dvh",
|
|
85
|
+
},
|
|
86
|
+
large: {
|
|
87
|
+
// Large modal — tables, complex layouts, multi-column flows
|
|
88
|
+
minWidth: "320px",
|
|
89
|
+
viewportWidthPercent: "90dvw",
|
|
90
|
+
paddingOffset: "1rem",
|
|
91
|
+
breakpoint: "480px",
|
|
92
|
+
transitionRate: 0.15,
|
|
93
|
+
maxWidth: "1400px",
|
|
94
|
+
maxHeight: "min(80dvh, 1000px)",
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Returns the CSS properties for the modal card based on the size.
|
|
99
|
+
*/
|
|
100
|
+
const getModalCardCSSVariables = (size) => {
|
|
101
|
+
const config = modalSizeConfigs[size];
|
|
102
|
+
// eslint-disable-next-line local-rules/no-typescript-assertion
|
|
103
|
+
return {
|
|
104
|
+
"--modal-min-width": config.minWidth,
|
|
105
|
+
"--modal-viewport-width-percent": config.viewportWidthPercent,
|
|
106
|
+
"--modal-padding-offset": config.paddingOffset,
|
|
107
|
+
"--modal-breakpoint": config.breakpoint,
|
|
108
|
+
"--modal-transition-rate": config.transitionRate,
|
|
109
|
+
"--modal-max-width": config.maxWidth,
|
|
110
|
+
"--modal-max-height": config.maxHeight,
|
|
111
|
+
};
|
|
112
|
+
};
|
|
66
113
|
const cvaModalCard = cssClassVarianceUtilities.cvaMerge([
|
|
67
114
|
"m-auto",
|
|
68
115
|
"shadow-2xl",
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"max-
|
|
116
|
+
"overflow-y-auto",
|
|
117
|
+
"w-[clamp(var(--modal-min-width),calc(var(--modal-viewport-width-percent)-var(--modal-padding-offset)-((100dvw-var(--modal-breakpoint))*var(--modal-transition-rate))),var(--modal-max-width))]",
|
|
118
|
+
"max-h-[var(--modal-max-height)]",
|
|
119
|
+
"@container", // This is used to target the container size for all children
|
|
72
120
|
]);
|
|
73
121
|
const cvaModalBackdrop = cssClassVarianceUtilities.cvaMerge([
|
|
74
122
|
"justify-center",
|
|
75
123
|
"items-center",
|
|
76
124
|
"fixed",
|
|
77
|
-
"
|
|
78
|
-
"left-0",
|
|
125
|
+
"inset-0",
|
|
79
126
|
"min-h-[100dvh]",
|
|
80
127
|
"min-w-[100dvw]",
|
|
81
128
|
"w-full",
|
|
@@ -236,11 +283,14 @@ const useModalFooterBorder = (rootRef, { footerClass = "border-t", enabled = tru
|
|
|
236
283
|
* };
|
|
237
284
|
* ```
|
|
238
285
|
*/
|
|
239
|
-
const Modal = ({ children, isOpen, role = "dialog", "data-testid": dataTestId, className,
|
|
240
|
-
|
|
286
|
+
const Modal = ({ children, isOpen, role = "dialog", "data-testid": dataTestId, className, size, onBackdropClick, floatingUi, ref, }) => {
|
|
287
|
+
// For dialogs/modals, Floating UI recommends not using floatingStyles since the modal
|
|
288
|
+
// is viewport-centered via CSS, not positioned relative to a reference element.
|
|
289
|
+
// See: https://floating-ui.com/docs/dialog
|
|
290
|
+
const { rootElement, refs, context, getFloatingProps } = floatingUi;
|
|
241
291
|
const cardRef = react.useRef(null);
|
|
242
292
|
useModalFooterBorder(cardRef, { enabled: isOpen, footerClass: "border-t pt-4" });
|
|
243
|
-
return (jsxRuntime.jsx(reactComponents.Portal, { root: rootElement, children: isOpen ? (jsxRuntime.jsx(react$1.FloatingFocusManager, { context: context, children: jsxRuntime.jsx("div", { ref: refs.setFloating, style: {
|
|
293
|
+
return (jsxRuntime.jsx(reactComponents.Portal, { root: rootElement, children: isOpen ? (jsxRuntime.jsx(react$1.FloatingFocusManager, { context: context, children: jsxRuntime.jsx("div", { ref: refs.setFloating, style: { zIndex: uiDesignTokens.zIndex.overlay }, ...getFloatingProps(), children: jsxRuntime.jsx(ModalBackdrop, { onClick: onBackdropClick, children: jsxRuntime.jsx("div", { "aria-modal": true, className: cvaModalContainer(), ref: ref, role: role, children: jsxRuntime.jsx(reactComponents.Card, { className: cvaModalCard({ className }), "data-testid": dataTestId, ref: cardRef, style: getModalCardCSSVariables(size), children: children }) }) }) }) })) : null }));
|
|
244
294
|
};
|
|
245
295
|
Modal.displayName = "Modal";
|
|
246
296
|
|
|
@@ -338,30 +388,32 @@ const ModalHeader = react.forwardRef(({ heading, subHeading, onClose, "data-test
|
|
|
338
388
|
*
|
|
339
389
|
* - `useModal` should be used with the `Modal` component.
|
|
340
390
|
* - const modal = useModal();
|
|
341
|
-
* - <Modal {...modal} />
|
|
391
|
+
* - <Modal {...modal} size="medium" />
|
|
342
392
|
*/
|
|
343
|
-
const useModal = (props
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
isOpen: typeof props.isOpen === "boolean" ? props.isOpen : isOpen,
|
|
349
|
-
});
|
|
350
|
-
};
|
|
351
|
-
const useModalInner = ({ isOpen, setIsOpen, onOpenChange, rootElement, closeOnOutsideClick = true, closeOnEsc = true, onClose, onOpen, ref: customRef, }) => {
|
|
393
|
+
const useModal = (props) => {
|
|
394
|
+
const { isOpen: controlledIsOpen, onOpenChange, rootElement, closeOnOutsideClick = true, closeOnEsc = true, onClose, onOpen, ref: customRef, role, } = props ?? {};
|
|
395
|
+
const [internalIsOpen, setIsOpen] = react.useState(Boolean(controlledIsOpen));
|
|
396
|
+
const isOpen = typeof controlledIsOpen === "boolean" ? controlledIsOpen : internalIsOpen;
|
|
397
|
+
const { blockScroll, restoreScroll } = reactComponents.useScrollBlock();
|
|
352
398
|
const ref = react.useRef(null);
|
|
353
399
|
const modalDialogContext = react.useContext(reactCoreContextsApi.ModalDialogContext);
|
|
354
400
|
if (!modalDialogContext) {
|
|
355
401
|
throw new Error("useModal must be used within the ModalDialogContextProvider");
|
|
356
402
|
}
|
|
357
403
|
const { openModal, closeModal } = modalDialogContext;
|
|
358
|
-
const { refs,
|
|
404
|
+
const { refs, context } = react$1.useFloating({
|
|
359
405
|
open: isOpen,
|
|
360
|
-
onOpenChange: (
|
|
406
|
+
onOpenChange: (newIsOpen, event, openChangeReason) => {
|
|
407
|
+
if (newIsOpen) {
|
|
408
|
+
blockScroll();
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
restoreScroll();
|
|
412
|
+
}
|
|
361
413
|
if (onOpenChange) {
|
|
362
|
-
return onOpenChange(
|
|
414
|
+
return onOpenChange(newIsOpen, event, openChangeReason);
|
|
363
415
|
}
|
|
364
|
-
setIsOpen(
|
|
416
|
+
setIsOpen(newIsOpen);
|
|
365
417
|
},
|
|
366
418
|
whileElementsMounted: react$1.autoUpdate,
|
|
367
419
|
middleware: [react$1.shift()],
|
|
@@ -386,8 +438,9 @@ const useModalInner = ({ isOpen, setIsOpen, onOpenChange, rootElement, closeOnOu
|
|
|
386
438
|
}, [isOpen, openModal, closeModal]);
|
|
387
439
|
const open = react.useCallback((...args) => {
|
|
388
440
|
onOpen?.(...args);
|
|
441
|
+
blockScroll();
|
|
389
442
|
setIsOpen(true);
|
|
390
|
-
}, [onOpen,
|
|
443
|
+
}, [onOpen, blockScroll]);
|
|
391
444
|
const close = react.useCallback((...args) => {
|
|
392
445
|
for (const arg of args) {
|
|
393
446
|
if (!arg || typeof arg !== "object") {
|
|
@@ -403,8 +456,9 @@ const useModalInner = ({ isOpen, setIsOpen, onOpenChange, rootElement, closeOnOu
|
|
|
403
456
|
}
|
|
404
457
|
}
|
|
405
458
|
onClose?.(...args);
|
|
459
|
+
restoreScroll();
|
|
406
460
|
setIsOpen(false);
|
|
407
|
-
}, [onClose,
|
|
461
|
+
}, [onClose, restoreScroll]);
|
|
408
462
|
const toggle = react.useCallback((...args) => {
|
|
409
463
|
if (isOpen) {
|
|
410
464
|
return close(...args);
|
|
@@ -423,23 +477,11 @@ const useModalInner = ({ isOpen, setIsOpen, onOpenChange, rootElement, closeOnOu
|
|
|
423
477
|
floatingUi: {
|
|
424
478
|
refs,
|
|
425
479
|
rootElement,
|
|
426
|
-
floatingStyles,
|
|
427
480
|
context,
|
|
428
481
|
getFloatingProps,
|
|
429
482
|
},
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
closeOnOutsideClick,
|
|
433
|
-
context,
|
|
434
|
-
customRef,
|
|
435
|
-
floatingStyles,
|
|
436
|
-
getFloatingProps,
|
|
437
|
-
isOpen,
|
|
438
|
-
open,
|
|
439
|
-
refs,
|
|
440
|
-
rootElement,
|
|
441
|
-
toggle,
|
|
442
|
-
]);
|
|
483
|
+
role,
|
|
484
|
+
}), [isOpen, closeOnOutsideClick, toggle, open, close, customRef, refs, rootElement, context, getFloatingProps, role]);
|
|
443
485
|
};
|
|
444
486
|
|
|
445
487
|
/*
|
package/index.esm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import { registerTranslations } from '@trackunit/i18n-library-translation';
|
|
3
3
|
import { FloatingFocusManager, useFloating, autoUpdate, shift, useDismiss, useInteractions } from '@floating-ui/react';
|
|
4
|
-
import { Portal, Card, Button, Heading, Text, IconButton, Icon } from '@trackunit/react-components';
|
|
4
|
+
import { Portal, Card, Button, Heading, Text, IconButton, Icon, useScrollBlock } from '@trackunit/react-components';
|
|
5
5
|
import { zIndex } from '@trackunit/ui-design-tokens';
|
|
6
6
|
import { useRef, useLayoutEffect, forwardRef, useState, useContext, useEffect, useCallback, useMemo } from 'react';
|
|
7
7
|
import { cvaMerge } from '@trackunit/css-class-variance-utilities';
|
|
@@ -54,26 +54,73 @@ const cvaModalContainer = cvaMerge([
|
|
|
54
54
|
"w-full",
|
|
55
55
|
"flex",
|
|
56
56
|
"fixed",
|
|
57
|
-
"
|
|
58
|
-
"left-0",
|
|
57
|
+
"inset-0",
|
|
59
58
|
"min-h-[100dvh]",
|
|
60
59
|
"min-w-[100dvw]",
|
|
61
60
|
"overflow-auto",
|
|
62
61
|
"items-center",
|
|
63
62
|
]);
|
|
63
|
+
const modalSizeConfigs = {
|
|
64
|
+
small: {
|
|
65
|
+
// Small modal — confirmations / short messages
|
|
66
|
+
minWidth: "320px",
|
|
67
|
+
viewportWidthPercent: "40dvw",
|
|
68
|
+
paddingOffset: "1rem",
|
|
69
|
+
breakpoint: "480px",
|
|
70
|
+
transitionRate: 0.15,
|
|
71
|
+
maxWidth: "520px",
|
|
72
|
+
maxHeight: "48dvh",
|
|
73
|
+
},
|
|
74
|
+
medium: {
|
|
75
|
+
// Medium modal — simple forms and single-step flows
|
|
76
|
+
minWidth: "320px",
|
|
77
|
+
viewportWidthPercent: "64dvw",
|
|
78
|
+
paddingOffset: "1rem",
|
|
79
|
+
breakpoint: "480px",
|
|
80
|
+
transitionRate: 0.15,
|
|
81
|
+
maxWidth: "700px",
|
|
82
|
+
maxHeight: "60dvh",
|
|
83
|
+
},
|
|
84
|
+
large: {
|
|
85
|
+
// Large modal — tables, complex layouts, multi-column flows
|
|
86
|
+
minWidth: "320px",
|
|
87
|
+
viewportWidthPercent: "90dvw",
|
|
88
|
+
paddingOffset: "1rem",
|
|
89
|
+
breakpoint: "480px",
|
|
90
|
+
transitionRate: 0.15,
|
|
91
|
+
maxWidth: "1400px",
|
|
92
|
+
maxHeight: "min(80dvh, 1000px)",
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Returns the CSS properties for the modal card based on the size.
|
|
97
|
+
*/
|
|
98
|
+
const getModalCardCSSVariables = (size) => {
|
|
99
|
+
const config = modalSizeConfigs[size];
|
|
100
|
+
// eslint-disable-next-line local-rules/no-typescript-assertion
|
|
101
|
+
return {
|
|
102
|
+
"--modal-min-width": config.minWidth,
|
|
103
|
+
"--modal-viewport-width-percent": config.viewportWidthPercent,
|
|
104
|
+
"--modal-padding-offset": config.paddingOffset,
|
|
105
|
+
"--modal-breakpoint": config.breakpoint,
|
|
106
|
+
"--modal-transition-rate": config.transitionRate,
|
|
107
|
+
"--modal-max-width": config.maxWidth,
|
|
108
|
+
"--modal-max-height": config.maxHeight,
|
|
109
|
+
};
|
|
110
|
+
};
|
|
64
111
|
const cvaModalCard = cvaMerge([
|
|
65
112
|
"m-auto",
|
|
66
113
|
"shadow-2xl",
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"max-
|
|
114
|
+
"overflow-y-auto",
|
|
115
|
+
"w-[clamp(var(--modal-min-width),calc(var(--modal-viewport-width-percent)-var(--modal-padding-offset)-((100dvw-var(--modal-breakpoint))*var(--modal-transition-rate))),var(--modal-max-width))]",
|
|
116
|
+
"max-h-[var(--modal-max-height)]",
|
|
117
|
+
"@container", // This is used to target the container size for all children
|
|
70
118
|
]);
|
|
71
119
|
const cvaModalBackdrop = cvaMerge([
|
|
72
120
|
"justify-center",
|
|
73
121
|
"items-center",
|
|
74
122
|
"fixed",
|
|
75
|
-
"
|
|
76
|
-
"left-0",
|
|
123
|
+
"inset-0",
|
|
77
124
|
"min-h-[100dvh]",
|
|
78
125
|
"min-w-[100dvw]",
|
|
79
126
|
"w-full",
|
|
@@ -234,11 +281,14 @@ const useModalFooterBorder = (rootRef, { footerClass = "border-t", enabled = tru
|
|
|
234
281
|
* };
|
|
235
282
|
* ```
|
|
236
283
|
*/
|
|
237
|
-
const Modal = ({ children, isOpen, role = "dialog", "data-testid": dataTestId, className,
|
|
238
|
-
|
|
284
|
+
const Modal = ({ children, isOpen, role = "dialog", "data-testid": dataTestId, className, size, onBackdropClick, floatingUi, ref, }) => {
|
|
285
|
+
// For dialogs/modals, Floating UI recommends not using floatingStyles since the modal
|
|
286
|
+
// is viewport-centered via CSS, not positioned relative to a reference element.
|
|
287
|
+
// See: https://floating-ui.com/docs/dialog
|
|
288
|
+
const { rootElement, refs, context, getFloatingProps } = floatingUi;
|
|
239
289
|
const cardRef = useRef(null);
|
|
240
290
|
useModalFooterBorder(cardRef, { enabled: isOpen, footerClass: "border-t pt-4" });
|
|
241
|
-
return (jsx(Portal, { root: rootElement, children: isOpen ? (jsx(FloatingFocusManager, { context: context, children: jsx("div", { ref: refs.setFloating, style: {
|
|
291
|
+
return (jsx(Portal, { root: rootElement, children: isOpen ? (jsx(FloatingFocusManager, { context: context, children: jsx("div", { ref: refs.setFloating, style: { zIndex: zIndex.overlay }, ...getFloatingProps(), children: jsx(ModalBackdrop, { onClick: onBackdropClick, children: jsx("div", { "aria-modal": true, className: cvaModalContainer(), ref: ref, role: role, children: jsx(Card, { className: cvaModalCard({ className }), "data-testid": dataTestId, ref: cardRef, style: getModalCardCSSVariables(size), children: children }) }) }) }) })) : null }));
|
|
242
292
|
};
|
|
243
293
|
Modal.displayName = "Modal";
|
|
244
294
|
|
|
@@ -336,30 +386,32 @@ const ModalHeader = forwardRef(({ heading, subHeading, onClose, "data-testid": d
|
|
|
336
386
|
*
|
|
337
387
|
* - `useModal` should be used with the `Modal` component.
|
|
338
388
|
* - const modal = useModal();
|
|
339
|
-
* - <Modal {...modal} />
|
|
389
|
+
* - <Modal {...modal} size="medium" />
|
|
340
390
|
*/
|
|
341
|
-
const useModal = (props
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
isOpen: typeof props.isOpen === "boolean" ? props.isOpen : isOpen,
|
|
347
|
-
});
|
|
348
|
-
};
|
|
349
|
-
const useModalInner = ({ isOpen, setIsOpen, onOpenChange, rootElement, closeOnOutsideClick = true, closeOnEsc = true, onClose, onOpen, ref: customRef, }) => {
|
|
391
|
+
const useModal = (props) => {
|
|
392
|
+
const { isOpen: controlledIsOpen, onOpenChange, rootElement, closeOnOutsideClick = true, closeOnEsc = true, onClose, onOpen, ref: customRef, role, } = props ?? {};
|
|
393
|
+
const [internalIsOpen, setIsOpen] = useState(Boolean(controlledIsOpen));
|
|
394
|
+
const isOpen = typeof controlledIsOpen === "boolean" ? controlledIsOpen : internalIsOpen;
|
|
395
|
+
const { blockScroll, restoreScroll } = useScrollBlock();
|
|
350
396
|
const ref = useRef(null);
|
|
351
397
|
const modalDialogContext = useContext(ModalDialogContext);
|
|
352
398
|
if (!modalDialogContext) {
|
|
353
399
|
throw new Error("useModal must be used within the ModalDialogContextProvider");
|
|
354
400
|
}
|
|
355
401
|
const { openModal, closeModal } = modalDialogContext;
|
|
356
|
-
const { refs,
|
|
402
|
+
const { refs, context } = useFloating({
|
|
357
403
|
open: isOpen,
|
|
358
|
-
onOpenChange: (
|
|
404
|
+
onOpenChange: (newIsOpen, event, openChangeReason) => {
|
|
405
|
+
if (newIsOpen) {
|
|
406
|
+
blockScroll();
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
restoreScroll();
|
|
410
|
+
}
|
|
359
411
|
if (onOpenChange) {
|
|
360
|
-
return onOpenChange(
|
|
412
|
+
return onOpenChange(newIsOpen, event, openChangeReason);
|
|
361
413
|
}
|
|
362
|
-
setIsOpen(
|
|
414
|
+
setIsOpen(newIsOpen);
|
|
363
415
|
},
|
|
364
416
|
whileElementsMounted: autoUpdate,
|
|
365
417
|
middleware: [shift()],
|
|
@@ -384,8 +436,9 @@ const useModalInner = ({ isOpen, setIsOpen, onOpenChange, rootElement, closeOnOu
|
|
|
384
436
|
}, [isOpen, openModal, closeModal]);
|
|
385
437
|
const open = useCallback((...args) => {
|
|
386
438
|
onOpen?.(...args);
|
|
439
|
+
blockScroll();
|
|
387
440
|
setIsOpen(true);
|
|
388
|
-
}, [onOpen,
|
|
441
|
+
}, [onOpen, blockScroll]);
|
|
389
442
|
const close = useCallback((...args) => {
|
|
390
443
|
for (const arg of args) {
|
|
391
444
|
if (!arg || typeof arg !== "object") {
|
|
@@ -401,8 +454,9 @@ const useModalInner = ({ isOpen, setIsOpen, onOpenChange, rootElement, closeOnOu
|
|
|
401
454
|
}
|
|
402
455
|
}
|
|
403
456
|
onClose?.(...args);
|
|
457
|
+
restoreScroll();
|
|
404
458
|
setIsOpen(false);
|
|
405
|
-
}, [onClose,
|
|
459
|
+
}, [onClose, restoreScroll]);
|
|
406
460
|
const toggle = useCallback((...args) => {
|
|
407
461
|
if (isOpen) {
|
|
408
462
|
return close(...args);
|
|
@@ -421,23 +475,11 @@ const useModalInner = ({ isOpen, setIsOpen, onOpenChange, rootElement, closeOnOu
|
|
|
421
475
|
floatingUi: {
|
|
422
476
|
refs,
|
|
423
477
|
rootElement,
|
|
424
|
-
floatingStyles,
|
|
425
478
|
context,
|
|
426
479
|
getFloatingProps,
|
|
427
480
|
},
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
closeOnOutsideClick,
|
|
431
|
-
context,
|
|
432
|
-
customRef,
|
|
433
|
-
floatingStyles,
|
|
434
|
-
getFloatingProps,
|
|
435
|
-
isOpen,
|
|
436
|
-
open,
|
|
437
|
-
refs,
|
|
438
|
-
rootElement,
|
|
439
|
-
toggle,
|
|
440
|
-
]);
|
|
481
|
+
role,
|
|
482
|
+
}), [isOpen, closeOnOutsideClick, toggle, open, close, customRef, refs, rootElement, context, getFloatingProps, role]);
|
|
441
483
|
};
|
|
442
484
|
|
|
443
485
|
/*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-modal",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.2",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"react": "19.0.0",
|
|
11
|
-
"@trackunit/react-components": "1.12.
|
|
12
|
-
"@trackunit/css-class-variance-utilities": "1.9.
|
|
13
|
-
"@trackunit/i18n-library-translation": "1.9.
|
|
11
|
+
"@trackunit/react-components": "1.12.2",
|
|
12
|
+
"@trackunit/css-class-variance-utilities": "1.9.2",
|
|
13
|
+
"@trackunit/i18n-library-translation": "1.9.2",
|
|
14
14
|
"@floating-ui/react": "^0.26.25",
|
|
15
15
|
"@floating-ui/react-dom": "2.1.2",
|
|
16
|
-
"@trackunit/ui-design-tokens": "1.9.
|
|
17
|
-
"@trackunit/react-core-contexts-api": "1.10.
|
|
16
|
+
"@trackunit/ui-design-tokens": "1.9.2",
|
|
17
|
+
"@trackunit/react-core-contexts-api": "1.10.2",
|
|
18
|
+
"@trackunit/shared-utils": "1.11.2"
|
|
18
19
|
},
|
|
19
20
|
"module": "./index.esm.js",
|
|
20
21
|
"main": "./index.cjs.js",
|
package/src/modal/Modal.d.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Size } from "@trackunit/react-components";
|
|
2
|
+
import { PropsWithChildren, ReactElement } from "react";
|
|
2
3
|
import { BaseUseModalActionArgs, UseModalReturnValue } from "./useModal";
|
|
3
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Modal props extend the return type of useModal and add presentational-only props.
|
|
6
|
+
*/
|
|
7
|
+
export type ModalProps<TOnCloseFnArgs extends BaseUseModalActionArgs = [], TOnOpenFnArgs extends BaseUseModalActionArgs = []> = PropsWithChildren<UseModalReturnValue<TOnCloseFnArgs, TOnOpenFnArgs> & {
|
|
4
8
|
/**
|
|
5
|
-
* The
|
|
9
|
+
* The size of the modal card.
|
|
6
10
|
*/
|
|
7
|
-
|
|
11
|
+
size: Size;
|
|
8
12
|
/**
|
|
9
13
|
* The classes for the modal.
|
|
10
14
|
*/
|
|
11
15
|
className?: string;
|
|
12
|
-
/**
|
|
13
|
-
* The classes for the container of the modal.
|
|
14
|
-
*/
|
|
15
|
-
containerClassName?: string;
|
|
16
16
|
/**
|
|
17
17
|
* The test ID applied to the modal.
|
|
18
18
|
*/
|
|
19
19
|
"data-testid"?: string;
|
|
20
|
-
}
|
|
20
|
+
}>;
|
|
21
21
|
/**
|
|
22
22
|
* - Modals are used to present critical information or request user input needed to complete a user's workflow.
|
|
23
23
|
* - Modals interrupt a user's workflow by design.
|
|
@@ -46,6 +46,6 @@ export type ModalProps<TOnCloseFnArgs extends BaseUseModalActionArgs = [], TOnOp
|
|
|
46
46
|
* ```
|
|
47
47
|
*/
|
|
48
48
|
export declare const Modal: {
|
|
49
|
-
<TOnCloseFnArgs extends BaseUseModalActionArgs = [], TOnOpenFnArgs extends BaseUseModalActionArgs = []>({ children, isOpen, role, "data-testid": dataTestId, className,
|
|
49
|
+
<TOnCloseFnArgs extends BaseUseModalActionArgs = [], TOnOpenFnArgs extends BaseUseModalActionArgs = []>({ children, isOpen, role, "data-testid": dataTestId, className, size, onBackdropClick, floatingUi, ref, }: ModalProps<TOnCloseFnArgs, TOnOpenFnArgs>): ReactElement;
|
|
50
50
|
displayName: string;
|
|
51
51
|
};
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
+
import { StoryObjWithOptionalArgs } from "@trackunit/shared-utils";
|
|
1
2
|
import { ReactElement } from "react";
|
|
3
|
+
type Story = StoryObjWithOptionalArgs<typeof meta, "ref" | "isOpen" | "close" | "open" | "toggle" | "onBackdropClick" | "floatingUi" | "children" | "closeOnOutsideClick">;
|
|
2
4
|
declare const meta: {
|
|
3
5
|
title: string;
|
|
4
6
|
component: {
|
|
5
|
-
<TOnCloseFnArgs extends import("./useModal").BaseUseModalActionArgs = [], TOnOpenFnArgs extends import("./useModal").BaseUseModalActionArgs = []>({ children, isOpen, role, "data-testid": dataTestId, className,
|
|
7
|
+
<TOnCloseFnArgs extends import("./useModal").BaseUseModalActionArgs = [], TOnOpenFnArgs extends import("./useModal").BaseUseModalActionArgs = []>({ children, isOpen, role, "data-testid": dataTestId, className, size, onBackdropClick, floatingUi, ref, }: import("./Modal").ModalProps<TOnCloseFnArgs, TOnOpenFnArgs>): ReactElement;
|
|
6
8
|
displayName: string;
|
|
7
9
|
};
|
|
8
10
|
tags: string[];
|
|
9
11
|
parameters: {
|
|
10
12
|
docs: {
|
|
13
|
+
description: {
|
|
14
|
+
component: string;
|
|
15
|
+
};
|
|
11
16
|
source: {
|
|
12
17
|
type: string;
|
|
13
18
|
excludeDecorators: boolean;
|
|
@@ -16,44 +21,56 @@ declare const meta: {
|
|
|
16
21
|
};
|
|
17
22
|
argTypes: {
|
|
18
23
|
floatingUi: {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
closeOnOutsideClick: {
|
|
24
|
+
control: false;
|
|
25
|
+
description: string;
|
|
24
26
|
table: {
|
|
25
27
|
category: string;
|
|
26
|
-
defaultValue: {
|
|
27
|
-
summary: string;
|
|
28
|
-
};
|
|
29
28
|
};
|
|
30
29
|
};
|
|
31
30
|
ref: {
|
|
31
|
+
control: false;
|
|
32
|
+
description: string;
|
|
32
33
|
table: {
|
|
33
34
|
category: string;
|
|
34
35
|
};
|
|
35
36
|
};
|
|
36
37
|
onBackdropClick: {
|
|
38
|
+
control: false;
|
|
39
|
+
description: string;
|
|
37
40
|
table: {
|
|
38
41
|
category: string;
|
|
39
42
|
};
|
|
40
43
|
};
|
|
41
44
|
open: {
|
|
45
|
+
control: false;
|
|
46
|
+
description: string;
|
|
42
47
|
table: {
|
|
43
48
|
category: string;
|
|
44
49
|
};
|
|
45
50
|
};
|
|
46
51
|
close: {
|
|
52
|
+
control: false;
|
|
53
|
+
description: string;
|
|
47
54
|
table: {
|
|
48
55
|
category: string;
|
|
49
56
|
};
|
|
50
57
|
};
|
|
51
58
|
toggle: {
|
|
59
|
+
control: false;
|
|
60
|
+
description: string;
|
|
52
61
|
table: {
|
|
53
62
|
category: string;
|
|
54
63
|
};
|
|
55
64
|
};
|
|
56
65
|
isOpen: {
|
|
66
|
+
control: false;
|
|
67
|
+
description: string;
|
|
68
|
+
table: {
|
|
69
|
+
category: string;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
closeOnOutsideClick: {
|
|
73
|
+
control: false;
|
|
57
74
|
description: string;
|
|
58
75
|
table: {
|
|
59
76
|
category: string;
|
|
@@ -62,35 +79,22 @@ declare const meta: {
|
|
|
62
79
|
};
|
|
63
80
|
};
|
|
64
81
|
};
|
|
82
|
+
size: {
|
|
83
|
+
control: {
|
|
84
|
+
type: "select";
|
|
85
|
+
};
|
|
86
|
+
options: string[];
|
|
87
|
+
description: string;
|
|
88
|
+
table: {
|
|
89
|
+
defaultValue: {
|
|
90
|
+
summary: string;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
};
|
|
65
94
|
};
|
|
66
95
|
};
|
|
67
96
|
export default meta;
|
|
68
97
|
export declare const PackageName: () => ReactElement;
|
|
69
|
-
export declare const Default:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
};
|
|
73
|
-
export declare const IncorrectUsageErrorModal: {
|
|
74
|
-
render: () => import("react/jsx-runtime").JSX.Element;
|
|
75
|
-
args: {};
|
|
76
|
-
};
|
|
77
|
-
export declare const CorrectUsagePrimaryAction: {
|
|
78
|
-
render: () => import("react/jsx-runtime").JSX.Element;
|
|
79
|
-
args: {};
|
|
80
|
-
};
|
|
81
|
-
export declare const IncorrectUsageLongText: {
|
|
82
|
-
render: () => import("react/jsx-runtime").JSX.Element;
|
|
83
|
-
args: {};
|
|
84
|
-
};
|
|
85
|
-
export declare const CorrectUsageImportantAction: {
|
|
86
|
-
render: () => import("react/jsx-runtime").JSX.Element;
|
|
87
|
-
args: {};
|
|
88
|
-
};
|
|
89
|
-
export declare const CorrectAccessibility: {
|
|
90
|
-
render: () => import("react/jsx-runtime").JSX.Element;
|
|
91
|
-
args: {};
|
|
92
|
-
};
|
|
93
|
-
export declare const CorrectStateManagement: {
|
|
94
|
-
render: () => import("react/jsx-runtime").JSX.Element;
|
|
95
|
-
args: {};
|
|
96
|
-
};
|
|
98
|
+
export declare const Default: Story;
|
|
99
|
+
export declare const WithScrollableContent: Story;
|
|
100
|
+
export declare const MultiColumnLayout: Story;
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import { Size } from "@trackunit/react-components";
|
|
2
|
+
import { CSSProperties } from "react";
|
|
1
3
|
export declare const cvaModalContainer: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
|
|
4
|
+
/**
|
|
5
|
+
* Returns the CSS properties for the modal card based on the size.
|
|
6
|
+
*/
|
|
7
|
+
export declare const getModalCardCSSVariables: (size: Size) => CSSProperties;
|
|
2
8
|
export declare const cvaModalCard: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
|
|
3
9
|
export declare const cvaModalBackdrop: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface SizeSection {
|
|
2
|
+
label: string;
|
|
3
|
+
items: Array<string>;
|
|
4
|
+
}
|
|
5
|
+
interface SizeInfo {
|
|
6
|
+
description: string;
|
|
7
|
+
specs: Array<string>;
|
|
8
|
+
sections: Array<SizeSection>;
|
|
9
|
+
}
|
|
10
|
+
export declare const MODAL_SIZE_INFO: Record<"small" | "medium" | "large", SizeInfo>;
|
|
11
|
+
export {};
|
package/src/modal/useModal.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { UseFloatingOptions, UseFloatingReturn } from "@floating-ui/react";
|
|
2
2
|
import { UseFloatingReturn as UseFloatingReturn_Dom } from "@floating-ui/react-dom";
|
|
3
3
|
import { UseInteractionsReturn } from "@floating-ui/react/dist/floating-ui.react";
|
|
4
|
-
import { MouseEvent, Ref } from "react";
|
|
4
|
+
import { AriaRole, MouseEvent, Ref } from "react";
|
|
5
5
|
type BaseUseModalActionArg = string | number | boolean | null | undefined | [] | object | MouseEvent;
|
|
6
6
|
export type BaseUseModalActionArgs = {} & Array<BaseUseModalActionArg>;
|
|
7
7
|
type OnCloseFn<TOnCloseFnArgs extends BaseUseModalActionArgs> = (...args: TOnCloseFnArgs) => void;
|
|
@@ -15,10 +15,13 @@ export type UseModalProps<TOnCloseFnArgs extends BaseUseModalActionArgs = [], TO
|
|
|
15
15
|
closeOnEsc?: boolean;
|
|
16
16
|
closeOnOutsideClick?: boolean;
|
|
17
17
|
ref?: Ref<HTMLDivElement>;
|
|
18
|
+
/**
|
|
19
|
+
* The aria role for the modal.
|
|
20
|
+
*/
|
|
21
|
+
role?: AriaRole;
|
|
18
22
|
};
|
|
19
23
|
type FloatingUiProps = {
|
|
20
24
|
refs: UseFloatingReturn_Dom["refs"];
|
|
21
|
-
floatingStyles: UseFloatingReturn_Dom["floatingStyles"];
|
|
22
25
|
rootElement?: HTMLElement;
|
|
23
26
|
context: UseFloatingReturn["context"];
|
|
24
27
|
getFloatingProps: UseInteractionsReturn["getFloatingProps"];
|
|
@@ -56,13 +59,17 @@ export type UseModalReturnValue<TOnCloseFnArgs extends BaseUseModalActionArgs =
|
|
|
56
59
|
* The floating UI properties for the modal.
|
|
57
60
|
*/
|
|
58
61
|
floatingUi: FloatingUiProps;
|
|
62
|
+
/**
|
|
63
|
+
* The aria role for the modal.
|
|
64
|
+
*/
|
|
65
|
+
role?: AriaRole;
|
|
59
66
|
};
|
|
60
67
|
/**
|
|
61
68
|
* A hook to handle the state and configuration of Modal components.
|
|
62
69
|
*
|
|
63
70
|
* - `useModal` should be used with the `Modal` component.
|
|
64
71
|
* - const modal = useModal();
|
|
65
|
-
* - <Modal {...modal} />
|
|
72
|
+
* - <Modal {...modal} size="medium" />
|
|
66
73
|
*/
|
|
67
74
|
export declare const useModal: <TOnCloseFnArgs extends BaseUseModalActionArgs = [], TOnOpenFnArgs extends BaseUseModalActionArgs = []>(props?: UseModalProps<TOnCloseFnArgs, TOnOpenFnArgs>) => UseModalReturnValue<TOnCloseFnArgs, TOnOpenFnArgs>;
|
|
68
75
|
export {};
|