@trackunit/react-modal 1.7.13 → 1.7.18
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 +68 -9
- package/index.esm.js +68 -9
- package/package.json +7 -7
- package/src/modal/Modal.d.ts +6 -7
- package/src/modal/Modal.stories.d.ts +0 -2
- package/src/modal/ModalBody/ModalBody.d.ts +21 -0
- package/src/modal/ModalBody/ModalBody.stories.d.ts +8 -0
- package/src/modal/ModalBody/ModalBody.variants.d.ts +1 -0
- package/src/modal/ModalFooter/ModalFooter.d.ts +65 -0
- package/src/modal/ModalFooter/ModalFooter.stories.d.ts +14 -0
- package/src/modal/ModalFooter/ModalFooter.variants.d.ts +1 -0
- package/src/modal/ModalHeader/ModalHeader.d.ts +30 -0
- package/src/modal/ModalHeader/ModalHeader.stories.d.ts +10 -0
- package/src/modal/ModalHeader/ModalHeader.variants.d.ts +3 -0
- package/src/modal/useModalFooterBorder.d.ts +29 -0
package/index.cjs.js
CHANGED
|
@@ -5,8 +5,8 @@ var i18nLibraryTranslation = require('@trackunit/i18n-library-translation');
|
|
|
5
5
|
var react$1 = require('@floating-ui/react');
|
|
6
6
|
var reactComponents = require('@trackunit/react-components');
|
|
7
7
|
var uiDesignTokens = require('@trackunit/ui-design-tokens');
|
|
8
|
-
var cssClassVarianceUtilities = require('@trackunit/css-class-variance-utilities');
|
|
9
8
|
var react = require('react');
|
|
9
|
+
var cssClassVarianceUtilities = require('@trackunit/css-class-variance-utilities');
|
|
10
10
|
var reactCoreHooks = require('@trackunit/react-core-hooks');
|
|
11
11
|
|
|
12
12
|
var defaultTranslations = {
|
|
@@ -104,6 +104,64 @@ const ModalBackdrop = ({ children, onClick }) => {
|
|
|
104
104
|
}, ref: ref, children: children }));
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Observes the modal body within the given root element and toggles a top border on the footer
|
|
109
|
+
* when the body becomes vertically scrollable.
|
|
110
|
+
*
|
|
111
|
+
* Behavior:
|
|
112
|
+
* - Locates elements via stable data-attributes (by default: [data-modal-body], [data-modal-footer]).
|
|
113
|
+
* - Recomputes on resize, DOM mutations within the body, scroll events, and image load/error events.
|
|
114
|
+
* - Cleans up all observers/listeners on unmount or dependency change.
|
|
115
|
+
*
|
|
116
|
+
* Edge cases:
|
|
117
|
+
* - If either body or footer is missing, the hook does nothing.
|
|
118
|
+
* - No DOM mutations occur when `enabled` is false.
|
|
119
|
+
*
|
|
120
|
+
* @param rootRef Root element that contains both the modal body and footer.
|
|
121
|
+
* @param options Optional configuration.
|
|
122
|
+
* @param options.borderClass CSS class toggled on the footer when the body is scrollable. Defaults to "border-t".
|
|
123
|
+
* @param options.enabled Whether the hook is active. Defaults to true.
|
|
124
|
+
* @param options.bodySelector CSS selector to locate the body element. Defaults to "[data-modal-body]".
|
|
125
|
+
* @param options.footerSelector CSS selector to locate the footer element. Defaults to "[data-modal-footer]".
|
|
126
|
+
*/
|
|
127
|
+
const useModalFooterBorder = (rootRef, { borderClass = "border-t", enabled = true, bodySelector = "[data-modal-body]", footerSelector = "[data-modal-footer]", } = {}) => {
|
|
128
|
+
react.useLayoutEffect(() => {
|
|
129
|
+
if (!enabled)
|
|
130
|
+
return;
|
|
131
|
+
const root = rootRef.current;
|
|
132
|
+
if (!root)
|
|
133
|
+
return;
|
|
134
|
+
const bodyEl = root.querySelector(bodySelector);
|
|
135
|
+
const footerEl = root.querySelector(footerSelector);
|
|
136
|
+
if (!bodyEl || !footerEl)
|
|
137
|
+
return;
|
|
138
|
+
const update = () => {
|
|
139
|
+
const hasScrollbar = bodyEl.scrollHeight > bodyEl.clientHeight;
|
|
140
|
+
footerEl.classList.toggle(borderClass, hasScrollbar);
|
|
141
|
+
};
|
|
142
|
+
update();
|
|
143
|
+
const resizeObserver = new ResizeObserver(update);
|
|
144
|
+
resizeObserver.observe(bodyEl);
|
|
145
|
+
const mutationObserver = new MutationObserver(update);
|
|
146
|
+
mutationObserver.observe(bodyEl, { childList: true, subtree: true, characterData: true });
|
|
147
|
+
bodyEl.addEventListener("scroll", update);
|
|
148
|
+
const images = Array.from(bodyEl.querySelectorAll("img"));
|
|
149
|
+
images.forEach(img => {
|
|
150
|
+
if (!img.complete) {
|
|
151
|
+
const once = () => update();
|
|
152
|
+
img.addEventListener("load", once, { once: true });
|
|
153
|
+
img.addEventListener("error", once, { once: true });
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
return () => {
|
|
157
|
+
resizeObserver.disconnect();
|
|
158
|
+
mutationObserver.disconnect();
|
|
159
|
+
bodyEl.removeEventListener("scroll", update);
|
|
160
|
+
footerEl.classList.remove(borderClass);
|
|
161
|
+
};
|
|
162
|
+
}, [rootRef, borderClass, enabled, bodySelector, footerSelector]);
|
|
163
|
+
};
|
|
164
|
+
|
|
107
165
|
/**
|
|
108
166
|
* - Modals are used to present critical information or request user input needed to complete a user's workflow.
|
|
109
167
|
* - Modals interrupt a user's workflow by design.
|
|
@@ -121,20 +179,21 @@ const ModalBackdrop = ({ children, onClick }) => {
|
|
|
121
179
|
* };
|
|
122
180
|
*
|
|
123
181
|
* const childComponent = ({...modalProps}: UseModalReturnValue) => {
|
|
124
|
-
* return (
|
|
125
|
-
*
|
|
126
|
-
* <
|
|
127
|
-
* <CardBody>
|
|
182
|
+
* return (* <Modal {...modalProps}>
|
|
183
|
+
* <ModalHeader onClose={modalProps.close} heading="My Modal" />
|
|
184
|
+
* <ModalBody>
|
|
128
185
|
* <p>This is a modal</p>
|
|
129
|
-
* </
|
|
130
|
-
*
|
|
131
|
-
*
|
|
186
|
+
* </ModalBody>
|
|
187
|
+
* <ModalFooter onCancel={modalProps.close} primaryLabel="Cancel" />
|
|
188
|
+
* </Modal> *);
|
|
132
189
|
* };
|
|
133
190
|
* ```
|
|
134
191
|
*/
|
|
135
192
|
const Modal = ({ children, isOpen, role = "dialog", dataTestId, className, containerClassName, onBackdropClick, floatingUi, ref, }) => {
|
|
136
193
|
const { rootElement, refs, floatingStyles, context, getFloatingProps } = floatingUi;
|
|
137
|
-
|
|
194
|
+
const cardRef = react.useRef(null);
|
|
195
|
+
useModalFooterBorder(cardRef, { enabled: isOpen, borderClass: "border-t" });
|
|
196
|
+
return (jsxRuntime.jsx(reactComponents.Portal, { root: rootElement, children: isOpen ? (jsxRuntime.jsx(react$1.FloatingFocusManager, { context: context, children: jsxRuntime.jsx("div", { ref: refs.setFloating, style: { ...floatingStyles, zIndex: uiDesignTokens.zIndex.overlay }, ...getFloatingProps(), children: jsxRuntime.jsx(ModalBackdrop, { onClick: onBackdropClick, children: jsxRuntime.jsx("div", { "aria-modal": true, className: cvaModalContainer({ className: containerClassName }), ref: ref, role: role, children: jsxRuntime.jsx(reactComponents.Card, { className: cvaModalCard({ className }), dataTestId: dataTestId, ref: cardRef, children: children }) }) }) }) })) : null }));
|
|
138
197
|
};
|
|
139
198
|
Modal.displayName = "Modal";
|
|
140
199
|
|
package/index.esm.js
CHANGED
|
@@ -3,8 +3,8 @@ import { registerTranslations } from '@trackunit/i18n-library-translation';
|
|
|
3
3
|
import { FloatingFocusManager, useFloating, autoUpdate, shift, useDismiss, useInteractions } from '@floating-ui/react';
|
|
4
4
|
import { Portal, Card } from '@trackunit/react-components';
|
|
5
5
|
import { zIndex } from '@trackunit/ui-design-tokens';
|
|
6
|
+
import { useRef, useLayoutEffect, useState, useEffect, useCallback, useMemo } from 'react';
|
|
6
7
|
import { cvaMerge } from '@trackunit/css-class-variance-utilities';
|
|
7
|
-
import { useRef, useState, useEffect, useCallback, useMemo } from 'react';
|
|
8
8
|
import { useModalDialogContext } from '@trackunit/react-core-hooks';
|
|
9
9
|
|
|
10
10
|
var defaultTranslations = {
|
|
@@ -102,6 +102,64 @@ const ModalBackdrop = ({ children, onClick }) => {
|
|
|
102
102
|
}, ref: ref, children: children }));
|
|
103
103
|
};
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Observes the modal body within the given root element and toggles a top border on the footer
|
|
107
|
+
* when the body becomes vertically scrollable.
|
|
108
|
+
*
|
|
109
|
+
* Behavior:
|
|
110
|
+
* - Locates elements via stable data-attributes (by default: [data-modal-body], [data-modal-footer]).
|
|
111
|
+
* - Recomputes on resize, DOM mutations within the body, scroll events, and image load/error events.
|
|
112
|
+
* - Cleans up all observers/listeners on unmount or dependency change.
|
|
113
|
+
*
|
|
114
|
+
* Edge cases:
|
|
115
|
+
* - If either body or footer is missing, the hook does nothing.
|
|
116
|
+
* - No DOM mutations occur when `enabled` is false.
|
|
117
|
+
*
|
|
118
|
+
* @param rootRef Root element that contains both the modal body and footer.
|
|
119
|
+
* @param options Optional configuration.
|
|
120
|
+
* @param options.borderClass CSS class toggled on the footer when the body is scrollable. Defaults to "border-t".
|
|
121
|
+
* @param options.enabled Whether the hook is active. Defaults to true.
|
|
122
|
+
* @param options.bodySelector CSS selector to locate the body element. Defaults to "[data-modal-body]".
|
|
123
|
+
* @param options.footerSelector CSS selector to locate the footer element. Defaults to "[data-modal-footer]".
|
|
124
|
+
*/
|
|
125
|
+
const useModalFooterBorder = (rootRef, { borderClass = "border-t", enabled = true, bodySelector = "[data-modal-body]", footerSelector = "[data-modal-footer]", } = {}) => {
|
|
126
|
+
useLayoutEffect(() => {
|
|
127
|
+
if (!enabled)
|
|
128
|
+
return;
|
|
129
|
+
const root = rootRef.current;
|
|
130
|
+
if (!root)
|
|
131
|
+
return;
|
|
132
|
+
const bodyEl = root.querySelector(bodySelector);
|
|
133
|
+
const footerEl = root.querySelector(footerSelector);
|
|
134
|
+
if (!bodyEl || !footerEl)
|
|
135
|
+
return;
|
|
136
|
+
const update = () => {
|
|
137
|
+
const hasScrollbar = bodyEl.scrollHeight > bodyEl.clientHeight;
|
|
138
|
+
footerEl.classList.toggle(borderClass, hasScrollbar);
|
|
139
|
+
};
|
|
140
|
+
update();
|
|
141
|
+
const resizeObserver = new ResizeObserver(update);
|
|
142
|
+
resizeObserver.observe(bodyEl);
|
|
143
|
+
const mutationObserver = new MutationObserver(update);
|
|
144
|
+
mutationObserver.observe(bodyEl, { childList: true, subtree: true, characterData: true });
|
|
145
|
+
bodyEl.addEventListener("scroll", update);
|
|
146
|
+
const images = Array.from(bodyEl.querySelectorAll("img"));
|
|
147
|
+
images.forEach(img => {
|
|
148
|
+
if (!img.complete) {
|
|
149
|
+
const once = () => update();
|
|
150
|
+
img.addEventListener("load", once, { once: true });
|
|
151
|
+
img.addEventListener("error", once, { once: true });
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return () => {
|
|
155
|
+
resizeObserver.disconnect();
|
|
156
|
+
mutationObserver.disconnect();
|
|
157
|
+
bodyEl.removeEventListener("scroll", update);
|
|
158
|
+
footerEl.classList.remove(borderClass);
|
|
159
|
+
};
|
|
160
|
+
}, [rootRef, borderClass, enabled, bodySelector, footerSelector]);
|
|
161
|
+
};
|
|
162
|
+
|
|
105
163
|
/**
|
|
106
164
|
* - Modals are used to present critical information or request user input needed to complete a user's workflow.
|
|
107
165
|
* - Modals interrupt a user's workflow by design.
|
|
@@ -119,20 +177,21 @@ const ModalBackdrop = ({ children, onClick }) => {
|
|
|
119
177
|
* };
|
|
120
178
|
*
|
|
121
179
|
* const childComponent = ({...modalProps}: UseModalReturnValue) => {
|
|
122
|
-
* return (
|
|
123
|
-
*
|
|
124
|
-
* <
|
|
125
|
-
* <CardBody>
|
|
180
|
+
* return (* <Modal {...modalProps}>
|
|
181
|
+
* <ModalHeader onClose={modalProps.close} heading="My Modal" />
|
|
182
|
+
* <ModalBody>
|
|
126
183
|
* <p>This is a modal</p>
|
|
127
|
-
* </
|
|
128
|
-
*
|
|
129
|
-
*
|
|
184
|
+
* </ModalBody>
|
|
185
|
+
* <ModalFooter onCancel={modalProps.close} primaryLabel="Cancel" />
|
|
186
|
+
* </Modal> *);
|
|
130
187
|
* };
|
|
131
188
|
* ```
|
|
132
189
|
*/
|
|
133
190
|
const Modal = ({ children, isOpen, role = "dialog", dataTestId, className, containerClassName, onBackdropClick, floatingUi, ref, }) => {
|
|
134
191
|
const { rootElement, refs, floatingStyles, context, getFloatingProps } = floatingUi;
|
|
135
|
-
|
|
192
|
+
const cardRef = useRef(null);
|
|
193
|
+
useModalFooterBorder(cardRef, { enabled: isOpen, borderClass: "border-t" });
|
|
194
|
+
return (jsx(Portal, { root: rootElement, children: isOpen ? (jsx(FloatingFocusManager, { context: context, children: jsx("div", { ref: refs.setFloating, style: { ...floatingStyles, zIndex: zIndex.overlay }, ...getFloatingProps(), children: jsx(ModalBackdrop, { onClick: onBackdropClick, children: jsx("div", { "aria-modal": true, className: cvaModalContainer({ className: containerClassName }), ref: ref, role: role, children: jsx(Card, { className: cvaModalCard({ className }), dataTestId: dataTestId, ref: cardRef, children: children }) }) }) }) })) : null }));
|
|
136
195
|
};
|
|
137
196
|
Modal.displayName = "Modal";
|
|
138
197
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-modal",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.18",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"react": "19.0.0",
|
|
11
|
-
"@trackunit/react-components": "1.8.
|
|
12
|
-
"@trackunit/react-core-hooks": "1.6.
|
|
13
|
-
"@trackunit/css-class-variance-utilities": "1.6.
|
|
14
|
-
"@trackunit/i18n-library-translation": "1.6.
|
|
15
|
-
"@trackunit/react-test-setup": "1.3.
|
|
11
|
+
"@trackunit/react-components": "1.8.35",
|
|
12
|
+
"@trackunit/react-core-hooks": "1.6.62",
|
|
13
|
+
"@trackunit/css-class-variance-utilities": "1.6.58",
|
|
14
|
+
"@trackunit/i18n-library-translation": "1.6.63",
|
|
15
|
+
"@trackunit/react-test-setup": "1.3.58",
|
|
16
16
|
"@floating-ui/react": "^0.26.25",
|
|
17
17
|
"@floating-ui/react-dom": "2.1.2",
|
|
18
|
-
"@trackunit/ui-design-tokens": "1.6.
|
|
18
|
+
"@trackunit/ui-design-tokens": "1.6.60"
|
|
19
19
|
},
|
|
20
20
|
"module": "./index.esm.js",
|
|
21
21
|
"main": "./index.cjs.js",
|
package/src/modal/Modal.d.ts
CHANGED
|
@@ -35,14 +35,13 @@ export type ModalProps<TOnCloseFnArgs extends BaseUseModalActionArgs = [], TOnOp
|
|
|
35
35
|
* };
|
|
36
36
|
*
|
|
37
37
|
* const childComponent = ({...modalProps}: UseModalReturnValue) => {
|
|
38
|
-
* return (
|
|
39
|
-
*
|
|
40
|
-
* <
|
|
41
|
-
* <CardBody>
|
|
38
|
+
* return (* <Modal {...modalProps}>
|
|
39
|
+
* <ModalHeader onClose={modalProps.close} heading="My Modal" />
|
|
40
|
+
* <ModalBody>
|
|
42
41
|
* <p>This is a modal</p>
|
|
43
|
-
* </
|
|
44
|
-
*
|
|
45
|
-
*
|
|
42
|
+
* </ModalBody>
|
|
43
|
+
* <ModalFooter onCancel={modalProps.close} primaryLabel="Cancel" />
|
|
44
|
+
* </Modal> *);
|
|
46
45
|
* };
|
|
47
46
|
* ```
|
|
48
47
|
*/
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { Meta, StoryObj } from "@storybook/react-webpack5";
|
|
2
2
|
import { Modal } from "./Modal";
|
|
3
|
-
import { UseModalReturnValue } from "./useModal";
|
|
4
3
|
type Story = StoryObj<typeof Modal>;
|
|
5
4
|
declare const meta: Meta<typeof Modal>;
|
|
6
5
|
export default meta;
|
|
7
6
|
export declare const packageName: () => import("react/jsx-runtime").JSX.Element;
|
|
8
|
-
export declare const OverviewTemplate: ({ ...modalProps }: UseModalReturnValue) => import("react/jsx-runtime").JSX.Element;
|
|
9
7
|
export declare const Default: Story;
|
|
10
8
|
export declare const IncorrectUsageErrorModal: (closeOnOutsideClick: true) => import("react/jsx-runtime").JSX.Element;
|
|
11
9
|
export declare const CorrectUsagePrimaryAction: (closeOnOutsideClick: true) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { CommonProps } from "@trackunit/react-components";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
export interface ModalBodyProps extends CommonProps {
|
|
4
|
+
/**
|
|
5
|
+
* Content to render inside the modal body. Accepts any React nodes.
|
|
6
|
+
* Typically text, lists, forms or other components.
|
|
7
|
+
* When content overflows, the container becomes scrollable.
|
|
8
|
+
*/
|
|
9
|
+
children: ReactNode | null;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Modal body container.
|
|
13
|
+
*
|
|
14
|
+
* Renders children inside a scrollable flex container.
|
|
15
|
+
*
|
|
16
|
+
* @param {ModalBodyProps} props Component props.
|
|
17
|
+
* @param {ReactNode | null} props.children Content to render inside the modal body.
|
|
18
|
+
* @param {string} [props.dataTestId] Optional test id for the container.
|
|
19
|
+
* @returns {ReactElement} Modal body wrapper element.
|
|
20
|
+
*/
|
|
21
|
+
export declare const ModalBody: import("react").ForwardRefExoticComponent<ModalBodyProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react-webpack5";
|
|
2
|
+
import { ModalBody } from "./ModalBody";
|
|
3
|
+
type Story = StoryObj<typeof ModalBody>;
|
|
4
|
+
declare const meta: Meta<typeof ModalBody>;
|
|
5
|
+
export default meta;
|
|
6
|
+
export declare const packageName: () => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export declare const Default: Story;
|
|
8
|
+
export declare const InContextWithinModal: Story;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const cvaModalBodyContainer: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { CommonProps } from "@trackunit/react-components";
|
|
2
|
+
export interface ModalFooterProps extends CommonProps {
|
|
3
|
+
/**
|
|
4
|
+
* Click handler for the Cancel button.
|
|
5
|
+
*/
|
|
6
|
+
onCancel: () => void;
|
|
7
|
+
/**
|
|
8
|
+
* Text for the Primary action button.
|
|
9
|
+
* When omitted, the Primary button is not rendered.
|
|
10
|
+
*/
|
|
11
|
+
primaryLabel?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Visual variant for the Primary button.
|
|
14
|
+
* Defaults to "primary".
|
|
15
|
+
*/
|
|
16
|
+
primaryVariant?: "primary" | "primary-danger";
|
|
17
|
+
/**
|
|
18
|
+
* Click handler for the Primary action button.
|
|
19
|
+
* The Primary button is rendered only if both primaryLabel and primaryAction are provided.
|
|
20
|
+
*/
|
|
21
|
+
primaryAction?: () => void;
|
|
22
|
+
/**
|
|
23
|
+
* Loading state for the Primary button.
|
|
24
|
+
* Defaults to false.
|
|
25
|
+
*/
|
|
26
|
+
primaryLoading?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Text for the Secondary action button.
|
|
29
|
+
* The Secondary button is rendered only if both secondaryLabel and secondaryAction are provided.
|
|
30
|
+
*/
|
|
31
|
+
secondaryLabel?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Click handler for the Secondary action button.
|
|
34
|
+
*/
|
|
35
|
+
secondaryAction?: () => void;
|
|
36
|
+
/**
|
|
37
|
+
* Visual variant for the Secondary button.
|
|
38
|
+
* Defaults to "secondary".
|
|
39
|
+
*/
|
|
40
|
+
secondaryVariant?: "secondary" | "secondary-danger";
|
|
41
|
+
/**
|
|
42
|
+
* Loading state for the Secondary button.
|
|
43
|
+
* Defaults to false.
|
|
44
|
+
*/
|
|
45
|
+
secondaryLoading?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Text for the Cancel button.
|
|
48
|
+
*/
|
|
49
|
+
cancelLabel: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Modal footer with action buttons.
|
|
53
|
+
*
|
|
54
|
+
* @param props Component props.
|
|
55
|
+
* @param props.cancelLabel Text for the Cancel button.
|
|
56
|
+
* @param props.onCancel Optional handler invoked when the Cancel button is clicked (alias to onClose, if provided).
|
|
57
|
+
* @param props.secondaryLabel Text for the optional Secondary action button.
|
|
58
|
+
* @param props.secondaryAction Click handler for the optional Secondary action button.
|
|
59
|
+
* @param props.primaryLabel Text for the Primary action button.
|
|
60
|
+
* @param props.primaryAction Click handler for the Primary action button.
|
|
61
|
+
* @param props.primaryVariant Visual variant for the Primary button ("primary" | "primary-danger").
|
|
62
|
+
* @param props.primaryLoading Optional loading state for the Primary button.
|
|
63
|
+
* @returns {ReactElement} Modal footer container element.
|
|
64
|
+
*/
|
|
65
|
+
export declare const ModalFooter: import("react").ForwardRefExoticComponent<ModalFooterProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react-webpack5";
|
|
2
|
+
import { ModalFooter } from "./ModalFooter";
|
|
3
|
+
type Story = StoryObj<typeof ModalFooter>;
|
|
4
|
+
declare const meta: Meta<typeof ModalFooter>;
|
|
5
|
+
export default meta;
|
|
6
|
+
export declare const packageName: () => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export declare const Default: Story;
|
|
8
|
+
export declare const WithSecondaryAction: Story;
|
|
9
|
+
export declare const PrimaryDanger: Story;
|
|
10
|
+
export declare const LoadingPrimary: Story;
|
|
11
|
+
export declare const OnlyCancel: Story;
|
|
12
|
+
export declare const SecondaryDanger: Story;
|
|
13
|
+
export declare const LoadingSecondary: Story;
|
|
14
|
+
export declare const InContextWithinModal: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const cvaModalFooterContainer: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { CommonProps } from "@trackunit/react-components";
|
|
2
|
+
interface ModalHeaderProps extends CommonProps {
|
|
3
|
+
/**
|
|
4
|
+
* Main title displayed in the modal header.
|
|
5
|
+
*/
|
|
6
|
+
heading: string;
|
|
7
|
+
/**
|
|
8
|
+
* Optional subtitle displayed below the main title.
|
|
9
|
+
* Not rendered when undefined.
|
|
10
|
+
*/
|
|
11
|
+
subHeading?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Click handler for the close button.
|
|
14
|
+
*/
|
|
15
|
+
onClose: () => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Modal header section.
|
|
19
|
+
* Displays a main heading, optional subheading, and a close button.
|
|
20
|
+
|
|
21
|
+
* @param {ModalHeaderProps} props Component props.
|
|
22
|
+
* @param {string} [props.heading] Main heading text.
|
|
23
|
+
* @param {string} [props.subHeading] Optional subheading content.
|
|
24
|
+
* @param {() => void} props.onClose Close button click handler.
|
|
25
|
+
* @param {string} [props.dataTestId] Optional test id for the container.
|
|
26
|
+
* @param {string} [props.className] Optional additional class name(s).
|
|
27
|
+
* @returns {ReactElement} The modal header element.
|
|
28
|
+
*/
|
|
29
|
+
export declare const ModalHeader: import("react").ForwardRefExoticComponent<ModalHeaderProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react-webpack5";
|
|
2
|
+
import { ModalHeader } from "./ModalHeader";
|
|
3
|
+
type Story = StoryObj<typeof ModalHeader>;
|
|
4
|
+
declare const meta: Meta<typeof ModalHeader>;
|
|
5
|
+
export default meta;
|
|
6
|
+
export declare const packageName: () => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export declare const Default: Story;
|
|
8
|
+
export declare const WithSubHeading: Story;
|
|
9
|
+
export declare const LongHeadings: Story;
|
|
10
|
+
export declare const InContextWithinModal: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const cvaContainer: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
|
|
2
|
+
export declare const cvaHeadingContainer: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
|
|
3
|
+
export declare const cvaIconContainer: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { RefObject } from "react";
|
|
2
|
+
type Options = {
|
|
3
|
+
borderClass?: string;
|
|
4
|
+
enabled?: boolean;
|
|
5
|
+
bodySelector?: string;
|
|
6
|
+
footerSelector?: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Observes the modal body within the given root element and toggles a top border on the footer
|
|
10
|
+
* when the body becomes vertically scrollable.
|
|
11
|
+
*
|
|
12
|
+
* Behavior:
|
|
13
|
+
* - Locates elements via stable data-attributes (by default: [data-modal-body], [data-modal-footer]).
|
|
14
|
+
* - Recomputes on resize, DOM mutations within the body, scroll events, and image load/error events.
|
|
15
|
+
* - Cleans up all observers/listeners on unmount or dependency change.
|
|
16
|
+
*
|
|
17
|
+
* Edge cases:
|
|
18
|
+
* - If either body or footer is missing, the hook does nothing.
|
|
19
|
+
* - No DOM mutations occur when `enabled` is false.
|
|
20
|
+
*
|
|
21
|
+
* @param rootRef Root element that contains both the modal body and footer.
|
|
22
|
+
* @param options Optional configuration.
|
|
23
|
+
* @param options.borderClass CSS class toggled on the footer when the body is scrollable. Defaults to "border-t".
|
|
24
|
+
* @param options.enabled Whether the hook is active. Defaults to true.
|
|
25
|
+
* @param options.bodySelector CSS selector to locate the body element. Defaults to "[data-modal-body]".
|
|
26
|
+
* @param options.footerSelector CSS selector to locate the footer element. Defaults to "[data-modal-footer]".
|
|
27
|
+
*/
|
|
28
|
+
export declare const useModalFooterBorder: (rootRef: RefObject<HTMLElement | null>, { borderClass, enabled, bodySelector, footerSelector, }?: Options) => void;
|
|
29
|
+
export {};
|