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

react-ui-org / react-ui / 12767513876

14 Jan 2025 12:21PM UTC coverage: 82.899% (-9.1%) from 91.979%
12767513876

Pull #544

github

web-flow
Merge 61bcff4ab into 684d5abff
Pull Request #544: Re-implement `Modal` component using HTMLDialogElement (#461)

743 of 897 branches covered (82.83%)

Branch coverage included in aggregate %.

4 of 53 new or added lines in 7 files covered. (7.55%)

35 existing lines in 5 files now uncovered.

658 of 793 relevant lines covered (82.98%)

63.78 hits per line

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

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

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

43
export const Modal = ({
4✔
44
  allowCloseOnBackdropClick,
45
  allowCloseOnEscapeKey,
46
  allowPrimaryActionOnEnterKey,
47
  autoFocus,
48
  children,
49
  closeButtonRef,
50
  portalId,
51
  position,
52
  preventScrollUnderneath,
53
  primaryButtonRef,
54
  size,
55
  ...restProps
56
}) => {
NEW
57
  const dialogRef = useRef();
×
58

NEW
59
  useEffect(() => {
×
NEW
60
    dialogRef.current.showModal();
×
61
  }, []);
62

NEW
63
  useModalFocus(autoFocus, dialogRef, primaryButtonRef);
×
UNCOV
64
  useModalScrollPrevention(preventScrollUnderneath);
×
65

NEW
66
  const onCancel = useCallback(
×
NEW
67
    (e) => dialogOnCancelHandler(e, closeButtonRef),
×
68
    [closeButtonRef],
69
  );
NEW
70
  const onClick = useCallback(
×
NEW
71
    (e) => dialogOnClickHandler(e, closeButtonRef, dialogRef, allowCloseOnBackdropClick),
×
72
    [allowCloseOnBackdropClick, closeButtonRef, dialogRef],
73
  );
NEW
74
  const onClose = useCallback(
×
NEW
75
    (e) => dialogOnCloseHandler(e, closeButtonRef),
×
76
    [closeButtonRef],
77
  );
NEW
78
  const onKeyDown = useCallback(
×
NEW
79
    (e) => dialogOnKeyDownHandler(
×
80
      e,
81
      closeButtonRef,
82
      primaryButtonRef,
83
      allowCloseOnEscapeKey,
84
      allowPrimaryActionOnEnterKey,
85
    ),
86
    [
87
      allowCloseOnEscapeKey,
88
      allowPrimaryActionOnEnterKey,
89
      closeButtonRef,
90
      primaryButtonRef,
91
    ],
92
  );
NEW
93
  const events = {
×
94
    onCancel,
95
    onClick,
96
    onClose,
97
    onKeyDown,
98
  };
99

UNCOV
100
  if (portalId === null) {
×
UNCOV
101
    return preRender(
×
102
      children,
103
      dialogRef,
104
      position,
105
      size,
106
      events,
107
      restProps,
108
    );
109
  }
110

UNCOV
111
  return createPortal(
×
112
    preRender(
113
      children,
114
      dialogRef,
115
      position,
116
      size,
117
      events,
118
      restProps,
119
    ),
120
    document.getElementById(portalId),
121
  );
122
};
123

124
Modal.defaultProps = {
4✔
125
  allowCloseOnBackdropClick: true,
126
  allowCloseOnEscapeKey: true,
127
  allowPrimaryActionOnEnterKey: true,
128
  autoFocus: true,
129
  children: null,
130
  closeButtonRef: null,
131
  portalId: null,
132
  position: 'center',
133
  preventScrollUnderneath: window.document.body,
134
  primaryButtonRef: null,
135
  size: 'medium',
136
};
137

138
Modal.propTypes = {
4✔
139
  /**
140
   * If `true`, the `Modal` can be closed by clicking on the backdrop.
141
   */
142
  allowCloseOnBackdropClick: PropTypes.bool,
143
  /**
144
   * If `true`, the `Modal` can be closed by pressing the Escape key.
145
   */
146
  allowCloseOnEscapeKey: PropTypes.bool,
147
  /**
148
   * If `true`, the `Modal` can be submitted by pressing the Enter key.
149
   */
150
  allowPrimaryActionOnEnterKey: PropTypes.bool,
151
  /**
152
   * If `true`, focus the first input element in the `Modal`, or primary button (referenced by the `primaryButtonRef`
153
   * prop), or other focusable element when the `Modal` is opened. If there are none or `autoFocus` is set to `false`,
154
   * focus the Modal itself.
155
   */
156
  autoFocus: PropTypes.bool,
157
  /**
158
   * Nested elements. Supported types are:
159
   *
160
   * * `ModalHeader`
161
   * * `ModalBody`
162
   * * `ModalFooter`
163
   *
164
   * At least `ModalBody` is required.
165
   */
166
  children: PropTypes.node,
167
  /**
168
   * Reference to close button element. It is used to close modal when Escape key is pressed
169
   * or the backdrop is clicked.
170
   */
171
  closeButtonRef: PropTypes.shape({
172
    // eslint-disable-next-line react/forbid-prop-types
173
    current: PropTypes.any,
174
  }),
175
  /**
176
   * If set, modal is rendered in the React Portal with that ID.
177
   */
178
  portalId: PropTypes.string,
179
  /**
180
   * Vertical position of the modal inside browser window.
181
   */
182
  position: PropTypes.oneOf(['top', 'center']),
183
  /**
184
   * Mode in which Modal prevents scroll of elements bellow:
185
   * * `off` - Modal does not prevent any scroll
186
   * * [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement) - Modal prevents scroll on this HTML element
187
   * * object
188
   *   * `reset` - method called on Modal's unmount to reset scroll prevention
189
   *   * `start` - method called on Modal's mount to custom scroll prevention
190
   */
191
  preventScrollUnderneath: PropTypes.oneOfType([
192
    PropTypes.oneOf([
193
      HTMLElement,
194
      'off',
195
    ]),
196
    PropTypes.shape({
197
      reset: PropTypes.func,
198
      start: PropTypes.func,
199
    }),
200
  ]),
201
  /**
202
   * Reference to primary button element. It is used to submit modal when Enter key is pressed and as fallback
203
   * when `autoFocus` functionality does not find any input element to be focused.
204
   */
205
  primaryButtonRef: PropTypes.shape({
206
    // eslint-disable-next-line react/forbid-prop-types
207
    current: PropTypes.any,
208
  }),
209
  /**
210
   * Size of the modal.
211
   */
212
  size: PropTypes.oneOf(['small', 'medium', 'large', 'fullscreen', 'auto']),
213
};
214

215
export const ModalWithGlobalProps = withGlobalProps(Modal, 'Modal');
4✔
216

217
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