• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

react-ui-org / react-ui / 14417056483

11 Apr 2025 08:25PM UTC coverage: 90.674% (-1.3%) from 91.956%
14417056483

push

github

adamkudrna
Bump version to v0.59.0

802 of 890 branches covered (90.11%)

Branch coverage included in aggregate %.

744 of 815 relevant lines covered (91.29%)

74.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.86
/src/components/Modal/Modal.jsx
1
import PropTypes from 'prop-types';
2
import React, {
3
  useCallback,
4
  useEffect,
5
  useImperativeHandle,
6
  useRef,
7
} from 'react';
8
import { createPortal } from 'react-dom';
9
import { classNames } from '../../helpers/classNames';
10
import { transferProps } from '../../helpers/transferProps';
11
import { withGlobalProps } from '../../providers/globalProps';
12
import { getRootColorClassName } from '../_helpers/getRootColorClassName';
13
import { dialogOnCancelHandler } from './_helpers/dialogOnCancelHandler';
14
import { dialogOnClickHandler } from './_helpers/dialogOnClickHandler';
15
import { dialogOnCloseHandler } from './_helpers/dialogOnCloseHandler';
16
import { dialogOnKeyDownHandler } from './_helpers/dialogOnKeyDownHandler';
17
import { getPositionClassName } from './_helpers/getPositionClassName';
18
import { getSizeClassName } from './_helpers/getSizeClassName';
19
import { useModalFocus } from './_hooks/useModalFocus';
20
import { useModalScrollPrevention } from './_hooks/useModalScrollPrevention';
21
import styles from './Modal.module.scss';
22

23
const preRender = (
4✔
24
  children,
25
  color,
26
  dialogRef,
27
  position,
28
  size,
29
  events,
30
  restProps,
31
) => (
32
  <dialog
62✔
33
    {...transferProps(restProps)}
34
    {...transferProps(events)}
35
    className={classNames(
36
      styles.root,
37
      color && getRootColorClassName(color, styles),
37✔
38
      getSizeClassName(size, styles),
39
      getPositionClassName(position, styles),
40
    )}
41
    ref={dialogRef}
42
  >
43
    {children}
44
  </dialog>
45
);
46

47
export const Modal = ({
4✔
48
  allowCloseOnBackdropClick,
49
  allowCloseOnEscapeKey,
50
  allowPrimaryActionOnEnterKey,
51
  autoFocus,
52
  children,
53
  closeButtonRef,
54
  color,
55
  dialogRef,
56
  portalId,
57
  position,
58
  preventScrollUnderneath,
59
  primaryButtonRef,
60
  size,
61
  ...restProps
62
}) => {
63
  const internalDialogRef = useRef();
62✔
64

65
  useEffect(() => {
62✔
66
    internalDialogRef.current.showModal();
62✔
67
  }, []);
68

69
  // We need to have a reference to the dialog element to be able to call its methods,
70
  // but at the same time we want to expose this reference to the parent component for
71
  // case someone wants to call dialog methods from outside the component.
72
  useImperativeHandle(dialogRef, () => internalDialogRef.current);
62✔
73

74
  useModalFocus(autoFocus, internalDialogRef, primaryButtonRef);
62✔
75
  useModalScrollPrevention(preventScrollUnderneath);
62✔
76

77
  const onCancel = useCallback(
62✔
78
    (e) => dialogOnCancelHandler(e, closeButtonRef, restProps.onCancel),
×
79
    [closeButtonRef, restProps.onCancel],
80
  );
81
  const onClick = useCallback(
62✔
82
    (e) => dialogOnClickHandler(e, closeButtonRef, internalDialogRef, allowCloseOnBackdropClick),
6✔
83
    [allowCloseOnBackdropClick, closeButtonRef, internalDialogRef],
84
  );
85
  const onClose = useCallback(
62✔
86
    (e) => dialogOnCloseHandler(e, closeButtonRef, restProps.onClose),
×
87
    [closeButtonRef, restProps.onClose],
88
  );
89
  const onKeyDown = useCallback(
62✔
90
    (e) => dialogOnKeyDownHandler(
6✔
91
      e,
92
      closeButtonRef,
93
      primaryButtonRef,
94
      allowCloseOnEscapeKey,
95
      allowPrimaryActionOnEnterKey,
96
    ),
97
    [
98
      allowCloseOnEscapeKey,
99
      allowPrimaryActionOnEnterKey,
100
      closeButtonRef,
101
      primaryButtonRef,
102
    ],
103
  );
104
  const events = {
62✔
105
    onCancel,
106
    onClick,
107
    onClose,
108
    onKeyDown,
109
  };
110

111
  if (portalId === null) {
62✔
112
    return preRender(
60✔
113
      children,
114
      color,
115
      internalDialogRef,
116
      position,
117
      size,
118
      events,
119
      restProps,
120
    );
121
  }
122

123
  return createPortal(
2✔
124
    preRender(
125
      children,
126
      color,
127
      internalDialogRef,
128
      position,
129
      size,
130
      events,
131
      restProps,
132
    ),
133
    document.getElementById(portalId),
134
  );
135
};
136

137
Modal.defaultProps = {
4✔
138
  allowCloseOnBackdropClick: true,
139
  allowCloseOnEscapeKey: true,
140
  allowPrimaryActionOnEnterKey: true,
141
  autoFocus: true,
142
  children: null,
143
  closeButtonRef: null,
144
  color: undefined,
145
  dialogRef: null,
146
  portalId: null,
147
  position: 'center',
148
  preventScrollUnderneath: window.document.body,
149
  primaryButtonRef: null,
150
  size: 'medium',
151
};
152

153
Modal.propTypes = {
4✔
154
  /**
155
   * If `true`, the `Modal` can be closed by clicking on the backdrop.
156
   */
157
  allowCloseOnBackdropClick: PropTypes.bool,
158
  /**
159
   * If `true`, the `Modal` can be closed by pressing the Escape key.
160
   */
161
  allowCloseOnEscapeKey: PropTypes.bool,
162
  /**
163
   * If `true`, the `Modal` can be submitted by pressing the Enter key.
164
   */
165
  allowPrimaryActionOnEnterKey: PropTypes.bool,
166
  /**
167
   * If `true`, focus the first input element in the `Modal`, or primary button (referenced by the `primaryButtonRef`
168
   * prop), or other focusable element when the `Modal` is opened. If there are none or `autoFocus` is set to `false`,
169
   * focus the Modal itself.
170
   */
171
  autoFocus: PropTypes.bool,
172
  /**
173
   * Nested elements. Supported types are:
174
   *
175
   * * `ModalHeader`
176
   * * `ModalBody`
177
   * * `ModalFooter`
178
   *
179
   * At least `ModalBody` is required.
180
   */
181
  children: PropTypes.node,
182
  /**
183
   * Reference to close button element. It is used to close modal when Escape key is pressed
184
   * or the backdrop is clicked.
185
   */
186
  closeButtonRef: PropTypes.shape({
187
    // eslint-disable-next-line react/forbid-prop-types
188
    current: PropTypes.any,
189
  }),
190
  /**
191
   * Color to clarify importance and meaning of the modal. Implements
192
   * [Feedback color collection](/docs/foundation/collections#colors).
193
   */
194
  color: PropTypes.oneOf(['success', 'warning', 'danger', 'help', 'info', 'note']),
195
  /**
196
   * Reference to dialog element
197
   */
198
  dialogRef: PropTypes.shape({
199
    // eslint-disable-next-line react/forbid-prop-types
200
    current: PropTypes.any,
201
  }),
202
  /**
203
   * If set, modal is rendered in the React Portal with that ID.
204
   */
205
  portalId: PropTypes.string,
206
  /**
207
   * Vertical position of the modal inside browser window.
208
   */
209
  position: PropTypes.oneOf(['top', 'center']),
210
  /**
211
   * Mode in which Modal prevents scroll of elements bellow:
212
   * * `off` - Modal does not prevent any scroll
213
   * * [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement) - Modal prevents scroll on this HTML element
214
   * * object
215
   *   * `reset` - method called on Modal's unmount to reset scroll prevention
216
   *   * `start` - method called on Modal's mount to custom scroll prevention
217
   */
218
  preventScrollUnderneath: PropTypes.oneOfType([
219
    PropTypes.oneOf([
220
      HTMLElement,
221
      'off',
222
    ]),
223
    PropTypes.shape({
224
      reset: PropTypes.func,
225
      start: PropTypes.func,
226
    }),
227
  ]),
228
  /**
229
   * Reference to primary button element. It is used to submit modal when Enter key is pressed and as fallback
230
   * when `autoFocus` functionality does not find any input element to be focused.
231
   */
232
  primaryButtonRef: PropTypes.shape({
233
    // eslint-disable-next-line react/forbid-prop-types
234
    current: PropTypes.any,
235
  }),
236
  /**
237
   * Size of the modal.
238
   */
239
  size: PropTypes.oneOf(['small', 'medium', 'large', 'fullscreen', 'auto']),
240
};
241

242
export const ModalWithGlobalProps = withGlobalProps(Modal, 'Modal');
4✔
243

244
export default ModalWithGlobalProps;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc