@navikt/ds-react 4.12.1 → 5.0.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/_docs.json +101 -127
- package/cjs/date/datepicker/DatePicker.js +1 -1
- package/cjs/date/hooks/useEscape.js +6 -1
- package/cjs/modal/Modal.js +85 -46
- package/cjs/modal/{ModalContent.js → ModalBody.js} +3 -3
- package/cjs/modal/ModalContext.js +8 -0
- package/cjs/modal/ModalFooter.js +46 -0
- package/cjs/modal/ModalHeader.js +56 -0
- package/cjs/modal/ModalUtils.js +40 -0
- package/cjs/modal/dialog-polyfill.js +833 -0
- package/cjs/popover/Popover.js +5 -2
- package/cjs/table/DataCell.js +1 -3
- package/cjs/table/ExpandableRow.js +2 -1
- package/cjs/table/HeaderCell.js +1 -4
- package/cjs/table/Table.js +1 -1
- package/esm/date/datepicker/DatePicker.d.ts +2 -2
- package/esm/date/datepicker/DatePicker.js +1 -1
- package/esm/date/datepicker/DatePicker.js.map +1 -1
- package/esm/date/hooks/useEscape.d.ts +2 -1
- package/esm/date/hooks/useEscape.js +6 -1
- package/esm/date/hooks/useEscape.js.map +1 -1
- package/esm/modal/Modal.d.ts +76 -51
- package/esm/modal/Modal.js +87 -48
- package/esm/modal/Modal.js.map +1 -1
- package/esm/modal/ModalBody.d.ts +6 -0
- package/esm/modal/{ModalContent.js → ModalBody.js} +4 -4
- package/esm/modal/ModalBody.js.map +1 -0
- package/esm/modal/ModalContext.d.ts +6 -0
- package/esm/modal/ModalContext.js +3 -0
- package/esm/modal/ModalContext.js.map +1 -0
- package/esm/modal/ModalFooter.d.ts +6 -0
- package/esm/modal/ModalFooter.js +19 -0
- package/esm/modal/ModalFooter.js.map +1 -0
- package/esm/modal/ModalHeader.d.ts +11 -0
- package/esm/modal/ModalHeader.js +29 -0
- package/esm/modal/ModalHeader.js.map +1 -0
- package/esm/modal/ModalUtils.d.ts +4 -0
- package/esm/modal/ModalUtils.js +33 -0
- package/esm/modal/ModalUtils.js.map +1 -0
- package/esm/modal/dialog-polyfill.d.ts +5 -0
- package/esm/modal/dialog-polyfill.js +832 -0
- package/esm/modal/dialog-polyfill.js.map +1 -0
- package/esm/modal/index.d.ts +3 -1
- package/esm/popover/Popover.js +6 -3
- package/esm/popover/Popover.js.map +1 -1
- package/esm/provider/Provider.d.ts +1 -6
- package/esm/provider/Provider.js.map +1 -1
- package/esm/table/DataCell.js +2 -4
- package/esm/table/DataCell.js.map +1 -1
- package/esm/table/ExpandableRow.js +2 -1
- package/esm/table/ExpandableRow.js.map +1 -1
- package/esm/table/HeaderCell.js +1 -4
- package/esm/table/HeaderCell.js.map +1 -1
- package/esm/table/Table.d.ts +2 -3
- package/esm/table/Table.js +1 -1
- package/esm/table/Table.js.map +1 -1
- package/package.json +3 -5
- package/src/date/datepicker/DatePicker.tsx +3 -3
- package/src/date/hooks/useEscape.tsx +8 -3
- package/src/modal/Modal.tsx +171 -121
- package/src/modal/ModalBody.tsx +14 -0
- package/src/modal/ModalContext.ts +6 -0
- package/src/modal/ModalFooter.tsx +14 -0
- package/src/modal/ModalHeader.tsx +42 -0
- package/src/modal/ModalUtils.ts +37 -0
- package/src/modal/dialog-polyfill.ts +980 -0
- package/src/modal/index.ts +3 -1
- package/src/modal/modal.stories.tsx +142 -59
- package/src/popover/Popover.tsx +6 -2
- package/src/provider/Provider.tsx +1 -6
- package/src/table/DataCell.tsx +1 -5
- package/src/table/ExpandableRow.tsx +2 -1
- package/src/table/HeaderCell.tsx +1 -5
- package/src/table/Table.tsx +3 -4
- package/src/table/stories/table-expandable.stories.tsx +37 -1
- package/src/table/stories/table.stories.tsx +4 -1
- package/esm/modal/ModalContent.d.ts +0 -10
- package/esm/modal/ModalContent.js.map +0 -1
- package/src/modal/ModalContent.tsx +0 -26
package/src/modal/Modal.tsx
CHANGED
|
@@ -1,76 +1,91 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useContext,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
} from "react";
|
|
2
8
|
import cl from "clsx";
|
|
3
|
-
import
|
|
9
|
+
import dialogPolyfill from "./dialog-polyfill";
|
|
10
|
+
import { Detail, Heading, mergeRefs, useId } from "..";
|
|
11
|
+
import ModalBody from "./ModalBody";
|
|
12
|
+
import ModalHeader from "./ModalHeader";
|
|
13
|
+
import ModalFooter from "./ModalFooter";
|
|
14
|
+
import { getCloseHandler, useBodyScrollLock } from "./ModalUtils";
|
|
15
|
+
import { ModalContext } from "./ModalContext";
|
|
4
16
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { XMarkIcon } from "@navikt/aksel-icons";
|
|
17
|
+
const needPolyfill =
|
|
18
|
+
typeof window !== "undefined" && window.HTMLDialogElement === undefined;
|
|
8
19
|
|
|
9
|
-
export interface ModalProps
|
|
20
|
+
export interface ModalProps
|
|
21
|
+
extends React.DialogHTMLAttributes<HTMLDialogElement> {
|
|
10
22
|
/**
|
|
11
|
-
* Modal
|
|
12
|
-
|
|
13
|
-
children: React.ReactNode;
|
|
14
|
-
/**
|
|
15
|
-
* Open state for modal
|
|
23
|
+
* Content for the header. Alteratively you can use <Modal.Header> instead for more control,
|
|
24
|
+
* but then you have to set `aria-label` or `aria-labelledby` on the modal manually.
|
|
16
25
|
*/
|
|
17
|
-
|
|
26
|
+
header?: {
|
|
27
|
+
label?: string;
|
|
28
|
+
icon?: React.ReactNode;
|
|
29
|
+
heading: string;
|
|
30
|
+
/**
|
|
31
|
+
* Heading size
|
|
32
|
+
* @default "medium"
|
|
33
|
+
* */
|
|
34
|
+
size?: "medium" | "small";
|
|
35
|
+
/**
|
|
36
|
+
* Removes close-button (X) when false
|
|
37
|
+
* @default true
|
|
38
|
+
*/
|
|
39
|
+
closeButton?: boolean;
|
|
40
|
+
};
|
|
18
41
|
/**
|
|
19
|
-
*
|
|
42
|
+
* Modal content
|
|
20
43
|
*/
|
|
21
|
-
|
|
44
|
+
children: React.ReactNode;
|
|
22
45
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
46
|
+
* Whether the modal should be visible or not.
|
|
47
|
+
* Remember to use the `onClose` callback to keep your local state in sync.
|
|
48
|
+
* You can also use `ref.current.openModal()` and `ref.current.close()`.
|
|
25
49
|
*/
|
|
26
|
-
|
|
50
|
+
open?: boolean;
|
|
27
51
|
/**
|
|
28
|
-
*
|
|
52
|
+
* Called when the modal has been closed
|
|
29
53
|
*/
|
|
30
|
-
|
|
54
|
+
onClose?: React.ReactEventHandler<HTMLDialogElement>;
|
|
31
55
|
/**
|
|
32
|
-
*
|
|
56
|
+
* Called when the user wants to close the modal (clicked the close button or pressed Esc).
|
|
57
|
+
* @returns Whether to close the modal
|
|
33
58
|
*/
|
|
34
|
-
|
|
59
|
+
onBeforeClose?: () => boolean | void;
|
|
35
60
|
/**
|
|
36
|
-
*
|
|
37
|
-
* @default true
|
|
61
|
+
* Called when the user presses the Esc key, unless `onBeforeClose()` returns `false`.
|
|
38
62
|
*/
|
|
39
|
-
|
|
63
|
+
onCancel?: React.ReactEventHandler<HTMLDialogElement>;
|
|
40
64
|
/**
|
|
41
|
-
*
|
|
42
|
-
*/
|
|
43
|
-
|
|
65
|
+
* @default fit-content (up to 700px)
|
|
66
|
+
* */
|
|
67
|
+
width?: "medium" | "small" | number | string;
|
|
44
68
|
/**
|
|
45
|
-
*
|
|
69
|
+
* User defined classname for modal
|
|
46
70
|
*/
|
|
47
|
-
|
|
71
|
+
className?: string;
|
|
48
72
|
/**
|
|
49
|
-
*
|
|
73
|
+
* Sets aria-labelledby on modal.
|
|
74
|
+
* No need to set this manually if the `header` prop is used. A reference to `header.heading` will be created automatically.
|
|
75
|
+
* @warning If not using `header`, you should set either `aria-labelledby` or `aria-label`.
|
|
50
76
|
*/
|
|
51
|
-
parentSelector?(): HTMLElement;
|
|
52
77
|
"aria-labelledby"?: string;
|
|
53
|
-
"aria-describedby"?: string;
|
|
54
|
-
"aria-modal"?: boolean;
|
|
55
|
-
/**
|
|
56
|
-
* Sets aria-label on modal
|
|
57
|
-
* @warning This should be set if not using 'aria-labelledby' or 'aria-describedby'
|
|
58
|
-
*/
|
|
59
|
-
"aria-label"?: string;
|
|
60
78
|
}
|
|
61
79
|
|
|
62
80
|
interface ModalComponent
|
|
63
|
-
extends
|
|
64
|
-
React.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
extends React.ForwardRefExoticComponent<
|
|
82
|
+
ModalProps & React.RefAttributes<HTMLDialogElement>
|
|
83
|
+
> {
|
|
84
|
+
Header: typeof ModalHeader;
|
|
85
|
+
Body: typeof ModalBody;
|
|
86
|
+
Footer: typeof ModalFooter;
|
|
68
87
|
}
|
|
69
88
|
|
|
70
|
-
type ModalLifecycle = {
|
|
71
|
-
setAppElement: (element: any) => void;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
89
|
/**
|
|
75
90
|
* A component that displays a modal dialog.
|
|
76
91
|
*
|
|
@@ -78,112 +93,147 @@ type ModalLifecycle = {
|
|
|
78
93
|
* @see 🏷️ {@link ModalProps}
|
|
79
94
|
*
|
|
80
95
|
* @example
|
|
96
|
+
* State change with `useRef`
|
|
97
|
+
* ```jsx
|
|
98
|
+
* const ref = useRef<HTMLDialogElement>(null);
|
|
99
|
+
* <Button onClick={() => ref.current?.showModal()}>Open modal</Button>
|
|
100
|
+
* <Modal
|
|
101
|
+
* ref={ref}
|
|
102
|
+
* header={{
|
|
103
|
+
* label: "Optional label",
|
|
104
|
+
* icon: <FileIcon aria-hidden />,
|
|
105
|
+
* heading: "My heading",
|
|
106
|
+
* }}
|
|
107
|
+
* >
|
|
108
|
+
* <Modal.Body>
|
|
109
|
+
* <BodyLong>Hello world</BodyLong>
|
|
110
|
+
* </Modal.Body>
|
|
111
|
+
* <Modal.Footer>
|
|
112
|
+
* <Button>Save</Button>
|
|
113
|
+
* <Button type="button" variant="tertiary" onClick={() => ref.current?.close()}>Close</Button>
|
|
114
|
+
* </Modal.Footer>
|
|
115
|
+
* </Modal>
|
|
116
|
+
* ```
|
|
117
|
+
* @example
|
|
118
|
+
* State change with `useState`
|
|
81
119
|
* ```jsx
|
|
82
120
|
* const [open, setOpen] = useState(false);
|
|
83
|
-
*
|
|
84
121
|
* <Modal
|
|
85
122
|
* open={open}
|
|
86
|
-
*
|
|
87
|
-
* onClose={() => setOpen((x) => !x)}
|
|
123
|
+
* onClose={() => setOpen(false)}
|
|
88
124
|
* aria-labelledby="modal-heading"
|
|
89
125
|
* >
|
|
90
|
-
* <Modal.
|
|
91
|
-
* <Heading
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* <BodyLong
|
|
95
|
-
*
|
|
96
|
-
* </BodyLong>
|
|
97
|
-
* </Modal.Content>
|
|
126
|
+
* <Modal.Header>
|
|
127
|
+
* <Heading level="1" size="large" id="modal-heading">My heading</Heading>
|
|
128
|
+
* </Modal.Header>
|
|
129
|
+
* <Modal.Body>
|
|
130
|
+
* <BodyLong>Hello world</BodyLong>
|
|
131
|
+
* </Modal.Body>
|
|
98
132
|
* </Modal>
|
|
99
133
|
* ```
|
|
100
134
|
*/
|
|
101
|
-
export const Modal = forwardRef<
|
|
135
|
+
export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
|
|
102
136
|
(
|
|
103
137
|
{
|
|
138
|
+
header,
|
|
104
139
|
children,
|
|
105
140
|
open,
|
|
106
|
-
|
|
141
|
+
onBeforeClose,
|
|
142
|
+
onCancel,
|
|
143
|
+
width,
|
|
107
144
|
className,
|
|
108
|
-
|
|
109
|
-
shouldCloseOnOverlayClick = true,
|
|
110
|
-
shouldCloseOnEsc = true,
|
|
111
|
-
closeButton = true,
|
|
112
|
-
"aria-describedby": ariaDescribedBy,
|
|
113
|
-
"aria-labelledby": ariaLabelledBy,
|
|
114
|
-
"aria-modal": ariaModal,
|
|
115
|
-
"aria-label": contentLabel,
|
|
145
|
+
"aria-labelledby": ariaLabelledby,
|
|
116
146
|
style,
|
|
117
|
-
parentSelector,
|
|
118
147
|
...rest
|
|
119
|
-
},
|
|
148
|
+
}: ModalProps,
|
|
120
149
|
ref
|
|
121
150
|
) => {
|
|
122
|
-
const modalRef = useRef<
|
|
151
|
+
const modalRef = useRef<HTMLDialogElement>(null);
|
|
123
152
|
const mergedRef = useMemo(() => mergeRefs([modalRef, ref]), [ref]);
|
|
124
|
-
const
|
|
125
|
-
const rootElement = useProvider()?.rootElement;
|
|
126
|
-
const appElement = useProvider()?.appElement;
|
|
153
|
+
const ariaLabelId = useId();
|
|
127
154
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
155
|
+
if (useContext(ModalContext)) {
|
|
156
|
+
console.error("Modals should not be nested");
|
|
157
|
+
}
|
|
131
158
|
|
|
132
|
-
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
} else if (buttonRef.current) {
|
|
136
|
-
buttonRef.current.focus();
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
if (needPolyfill && modalRef.current) {
|
|
161
|
+
dialogPolyfill.registerDialog(modalRef.current);
|
|
137
162
|
}
|
|
138
|
-
};
|
|
163
|
+
}, [modalRef]);
|
|
139
164
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
// We need to have this in a useEffect so that the content renders before the modal is displayed,
|
|
167
|
+
// and in case `open` is true initially.
|
|
168
|
+
if (modalRef.current && open !== undefined) {
|
|
169
|
+
if (open && !modalRef.current.open) {
|
|
170
|
+
modalRef.current.showModal();
|
|
171
|
+
} else if (!open && modalRef.current.open) {
|
|
172
|
+
modalRef.current.close();
|
|
173
|
+
}
|
|
143
174
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
175
|
+
}, [modalRef, open]);
|
|
176
|
+
|
|
177
|
+
useBodyScrollLock(modalRef, "navds-modal__document-body");
|
|
178
|
+
|
|
179
|
+
const isWidthPreset =
|
|
180
|
+
typeof width === "string" && ["small", "medium"].includes(width);
|
|
148
181
|
|
|
149
182
|
return (
|
|
150
|
-
<
|
|
151
|
-
{...rest}
|
|
152
|
-
parentSelector={getParentSelector()}
|
|
153
|
-
style={style}
|
|
154
|
-
isOpen={open}
|
|
183
|
+
<dialog
|
|
155
184
|
ref={mergedRef}
|
|
156
|
-
className={cl("navds-modal", className
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
modal: ariaModal,
|
|
185
|
+
className={cl("navds-modal", className, {
|
|
186
|
+
"navds-modal--polyfilled": needPolyfill,
|
|
187
|
+
"navds-modal--autowidth": !width,
|
|
188
|
+
[`navds-modal--${width}`]: isWidthPreset,
|
|
189
|
+
})}
|
|
190
|
+
style={{
|
|
191
|
+
...style,
|
|
192
|
+
...(!isWidthPreset ? { width } : {}),
|
|
165
193
|
}}
|
|
166
|
-
|
|
194
|
+
onCancel={(event) => {
|
|
195
|
+
// FYI: onCancel fires when you press Esc
|
|
196
|
+
if (onBeforeClose && onBeforeClose() === false) {
|
|
197
|
+
event.preventDefault();
|
|
198
|
+
} else if (onCancel) onCancel(event);
|
|
199
|
+
}}
|
|
200
|
+
aria-labelledby={
|
|
201
|
+
!ariaLabelledby && !rest["aria-label"] && header
|
|
202
|
+
? ariaLabelId
|
|
203
|
+
: ariaLabelledby
|
|
204
|
+
}
|
|
205
|
+
{...rest}
|
|
167
206
|
>
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
207
|
+
<ModalContext.Provider
|
|
208
|
+
value={{
|
|
209
|
+
closeHandler: getCloseHandler(modalRef, header, onBeforeClose),
|
|
210
|
+
}}
|
|
211
|
+
>
|
|
212
|
+
{header && (
|
|
213
|
+
<ModalHeader>
|
|
214
|
+
{header.label && (
|
|
215
|
+
<Detail className="navds-modal__label">{header.label}</Detail>
|
|
216
|
+
)}
|
|
217
|
+
<Heading
|
|
218
|
+
size={header.size ?? "medium"}
|
|
219
|
+
level="1"
|
|
220
|
+
id={ariaLabelId}
|
|
221
|
+
>
|
|
222
|
+
<span className="navds-modal__header-icon">{header.icon}</span>
|
|
223
|
+
{header.heading}
|
|
224
|
+
</Heading>
|
|
225
|
+
</ModalHeader>
|
|
226
|
+
)}
|
|
227
|
+
|
|
228
|
+
{children}
|
|
229
|
+
</ModalContext.Provider>
|
|
230
|
+
</dialog>
|
|
182
231
|
);
|
|
183
232
|
}
|
|
184
233
|
) as ModalComponent;
|
|
185
234
|
|
|
186
|
-
Modal.
|
|
187
|
-
Modal.
|
|
235
|
+
Modal.Header = ModalHeader;
|
|
236
|
+
Modal.Body = ModalBody;
|
|
237
|
+
Modal.Footer = ModalFooter;
|
|
188
238
|
|
|
189
239
|
export default Modal;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React, { forwardRef } from "react";
|
|
2
|
+
import cl from "clsx";
|
|
3
|
+
|
|
4
|
+
export interface ModalBodyProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const ModalBody = forwardRef<HTMLDivElement, ModalBodyProps>(
|
|
9
|
+
({ className, ...rest }, ref) => (
|
|
10
|
+
<div {...rest} ref={ref} className={cl("navds-modal__body", className)} />
|
|
11
|
+
)
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
export default ModalBody;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React, { forwardRef } from "react";
|
|
2
|
+
import cl from "clsx";
|
|
3
|
+
|
|
4
|
+
export interface ModalFooterProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const ModalFooter = forwardRef<HTMLDivElement, ModalFooterProps>(
|
|
9
|
+
({ className, ...rest }, ref) => (
|
|
10
|
+
<div {...rest} ref={ref} className={cl("navds-modal__footer", className)} />
|
|
11
|
+
)
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
export default ModalFooter;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React, { forwardRef, useContext } from "react";
|
|
2
|
+
import cl from "clsx";
|
|
3
|
+
import { XMarkIcon } from "@navikt/aksel-icons";
|
|
4
|
+
import { Button } from "../button";
|
|
5
|
+
import { ModalContext } from "./ModalContext";
|
|
6
|
+
|
|
7
|
+
export interface ModalHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
children?: React.ReactNode;
|
|
9
|
+
/**
|
|
10
|
+
* Removes close-button (X) when false
|
|
11
|
+
* @default true
|
|
12
|
+
*/
|
|
13
|
+
closeButton?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ModalHeader = forwardRef<HTMLDivElement, ModalHeaderProps>(
|
|
17
|
+
({ children, className, closeButton = true, ...rest }, ref) => {
|
|
18
|
+
const context = useContext(ModalContext);
|
|
19
|
+
if (context === null) {
|
|
20
|
+
console.error("<Modal.Header> has to be used within a <Modal>");
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div {...rest} ref={ref} className={cl("navds-modal__header", className)}>
|
|
26
|
+
{context.closeHandler && closeButton && (
|
|
27
|
+
<Button
|
|
28
|
+
type="button"
|
|
29
|
+
className="navds-modal__button"
|
|
30
|
+
size="small"
|
|
31
|
+
variant="tertiary-neutral"
|
|
32
|
+
onClick={context.closeHandler}
|
|
33
|
+
icon={<XMarkIcon title="Lukk modalvindu" />}
|
|
34
|
+
/>
|
|
35
|
+
)}
|
|
36
|
+
{children}
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
export default ModalHeader;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ModalProps } from "./Modal";
|
|
3
|
+
|
|
4
|
+
export function getCloseHandler(
|
|
5
|
+
modalRef: React.RefObject<HTMLDialogElement>,
|
|
6
|
+
header: ModalProps["header"],
|
|
7
|
+
onBeforeClose: ModalProps["onBeforeClose"]
|
|
8
|
+
) {
|
|
9
|
+
if (header && header.closeButton === false) return undefined;
|
|
10
|
+
if (onBeforeClose) {
|
|
11
|
+
return () => onBeforeClose() !== false && modalRef.current?.close();
|
|
12
|
+
}
|
|
13
|
+
return () => modalRef.current?.close();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useBodyScrollLock(
|
|
17
|
+
modalRef: React.RefObject<HTMLDialogElement>,
|
|
18
|
+
bodyClass: string
|
|
19
|
+
) {
|
|
20
|
+
React.useEffect(() => {
|
|
21
|
+
if (!modalRef.current) return;
|
|
22
|
+
if (modalRef.current.open) document.body.classList.add(bodyClass); // In case `open` is true initially
|
|
23
|
+
|
|
24
|
+
const observer = new MutationObserver(() => {
|
|
25
|
+
if (modalRef.current?.open) document.body.classList.add(bodyClass);
|
|
26
|
+
else document.body.classList.remove(bodyClass);
|
|
27
|
+
});
|
|
28
|
+
observer.observe(modalRef.current, {
|
|
29
|
+
attributes: true,
|
|
30
|
+
attributeFilter: ["open"],
|
|
31
|
+
});
|
|
32
|
+
return () => {
|
|
33
|
+
observer.disconnect();
|
|
34
|
+
document.body.classList.remove(bodyClass); // In case modal is unmounted before it's closed
|
|
35
|
+
};
|
|
36
|
+
}, [modalRef, bodyClass]);
|
|
37
|
+
}
|