@khanacademy/wonder-blocks-modal 4.0.39 → 4.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @khanacademy/wonder-blocks-modal
2
2
 
3
+ ## 4.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1b21747a: Added theming support to ModalDialog, removed MediaLayout in favor of media queries
8
+
3
9
  ## 4.0.39
4
10
 
5
11
  ### Patch Changes
@@ -32,7 +32,7 @@ type Props = {
32
32
  */
33
33
  testId?: string;
34
34
  /**
35
- * The ID of the content labelling this dialog, if applicable.
35
+ * The ID of the title labelling this dialog, if applicable.
36
36
  */
37
37
  "aria-labelledby"?: string;
38
38
  /**
@@ -40,21 +40,5 @@ type Props = {
40
40
  */
41
41
  "aria-describedby"?: string;
42
42
  };
43
- type DefaultProps = {
44
- role: Props["role"];
45
- };
46
- /**
47
- * `ModalDialog` is a component that contains these elements:
48
- * - The visual dialog element itself (`<div role="dialog"/>`)
49
- * - The custom contents below and/or above the Dialog itself (e.g. decorative graphics).
50
- *
51
- * **Accessibility notes:**
52
- * - By default (e.g. using `OnePaneDialog`), `aria-labelledby` is populated automatically using the dialog title `id`.
53
- * - If there is a custom Dialog implementation (e.g. `TwoPaneDialog`), the dialog element doesn’t have to have
54
- * the `aria-labelledby` attribute however this is recommended. It should match the `id` of the dialog title.
55
- */
56
- export default class ModalDialog extends React.Component<Props> {
57
- static defaultProps: DefaultProps;
58
- render(): React.ReactNode;
59
- }
60
- export {};
43
+ declare const ModalDialog: React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLDivElement>>;
44
+ export default ModalDialog;
package/dist/es/index.js CHANGED
@@ -1,98 +1,130 @@
1
1
  import * as React from 'react';
2
- import { StyleSheet } from 'aphrodite';
3
- import { MediaLayoutContext, MediaLayout, MEDIA_MODAL_SPEC } from '@khanacademy/wonder-blocks-layout';
4
2
  import { View, IDProvider } from '@khanacademy/wonder-blocks-core';
5
- import Spacing from '@khanacademy/wonder-blocks-spacing';
3
+ import { tokens, createThemeContext, ThemeSwitcherContext, useScopedTheme, useStyles } from '@khanacademy/wonder-blocks-theming';
4
+ import { StyleSheet } from 'aphrodite';
6
5
  import Color from '@khanacademy/wonder-blocks-color';
6
+ import Spacing from '@khanacademy/wonder-blocks-spacing';
7
+ import { MediaLayout } from '@khanacademy/wonder-blocks-layout';
7
8
  import { HeadingMedium, LabelSmall } from '@khanacademy/wonder-blocks-typography';
8
9
  import * as ReactDOM from 'react-dom';
9
10
  import { withActionScheduler } from '@khanacademy/wonder-blocks-timing';
10
11
  import xIcon from '@phosphor-icons/core/regular/x.svg';
11
12
  import IconButton from '@khanacademy/wonder-blocks-icon-button';
12
13
 
13
- class ModalDialog extends React.Component {
14
- render() {
15
- const {
16
- above,
17
- below,
18
- role,
19
- style,
20
- children,
21
- testId,
22
- "aria-labelledby": ariaLabelledBy,
23
- "aria-describedby": ariaDescribedBy
24
- } = this.props;
25
- const contextValue = {
26
- ssrSize: "large",
27
- mediaSpec: MEDIA_MODAL_SPEC
28
- };
29
- return React.createElement(MediaLayoutContext.Provider, {
30
- value: contextValue
31
- }, React.createElement(MediaLayout, {
32
- styleSheets: styleSheets$3
33
- }, ({
34
- styles
35
- }) => React.createElement(View, {
36
- style: [styles.wrapper, style]
37
- }, below && React.createElement(View, {
38
- style: styles.below
39
- }, below), React.createElement(View, {
40
- role: role,
41
- "aria-modal": "true",
42
- "aria-labelledby": ariaLabelledBy,
43
- "aria-describedby": ariaDescribedBy,
44
- style: styles.dialog,
45
- testId: testId
46
- }, children), above && React.createElement(View, {
47
- style: styles.above
48
- }, above))));
49
- }
14
+ function _extends() {
15
+ _extends = Object.assign ? Object.assign.bind() : function (target) {
16
+ for (var i = 1; i < arguments.length; i++) {
17
+ var source = arguments[i];
18
+ for (var key in source) {
19
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
20
+ target[key] = source[key];
21
+ }
22
+ }
23
+ }
24
+ return target;
25
+ };
26
+ return _extends.apply(this, arguments);
50
27
  }
51
- ModalDialog.defaultProps = {
52
- role: "dialog"
53
- };
54
- const styleSheets$3 = {
55
- all: StyleSheet.create({
56
- wrapper: {
57
- display: "flex",
58
- flexDirection: "row",
59
- alignItems: "stretch",
60
- width: "100%",
61
- height: "100%",
62
- position: "relative"
63
- },
28
+
29
+ const theme = {
30
+ border: {
31
+ radius: tokens.border.radius.medium_4
32
+ },
33
+ spacing: {
64
34
  dialog: {
65
- width: "100%",
66
- height: "100%",
67
- borderRadius: 4,
68
- overflow: "hidden"
69
- },
70
- above: {
71
- pointerEvents: "none",
72
- position: "absolute",
73
- top: 0,
74
- left: 0,
75
- bottom: 0,
76
- right: 0,
77
- zIndex: 1
78
- },
79
- below: {
80
- pointerEvents: "none",
81
- position: "absolute",
82
- top: 0,
83
- left: 0,
84
- bottom: 0,
85
- right: 0,
86
- zIndex: -1
35
+ small: tokens.spacing.medium_16
87
36
  }
88
- }),
89
- small: StyleSheet.create({
90
- wrapper: {
91
- padding: Spacing.medium_16,
37
+ }
38
+ };
39
+
40
+ const themes = {
41
+ default: theme
42
+ };
43
+ const ModalDialogThemeContext = createThemeContext(theme);
44
+ function ThemeModalDialog(props) {
45
+ const currentTheme = React.useContext(ThemeSwitcherContext);
46
+ const theme$1 = themes[currentTheme] || theme;
47
+ return React.createElement(ModalDialogThemeContext.Provider, {
48
+ value: theme$1
49
+ }, props.children);
50
+ }
51
+
52
+ const ModalDialogCore = React.forwardRef(function ModalDialogCore(props, ref) {
53
+ const {
54
+ above,
55
+ below,
56
+ role = "dialog",
57
+ style,
58
+ children,
59
+ testId,
60
+ "aria-labelledby": ariaLabelledBy,
61
+ "aria-describedby": ariaDescribedBy
62
+ } = props;
63
+ const {
64
+ theme
65
+ } = useScopedTheme(ModalDialogThemeContext);
66
+ const styles = useStyles(themedStylesFn, theme);
67
+ return React.createElement(View, {
68
+ style: [styles.wrapper, style]
69
+ }, below && React.createElement(View, {
70
+ style: styles.below
71
+ }, below), React.createElement(View, {
72
+ role: role,
73
+ "aria-modal": "true",
74
+ "aria-labelledby": ariaLabelledBy,
75
+ "aria-describedby": ariaDescribedBy,
76
+ ref: ref,
77
+ style: styles.dialog,
78
+ testId: testId
79
+ }, children), above && React.createElement(View, {
80
+ style: styles.above
81
+ }, above));
82
+ });
83
+ const ModalDialog = React.forwardRef(function ModalDialog(props, ref) {
84
+ return React.createElement(ThemeModalDialog, null, React.createElement(ModalDialogCore, _extends({}, props, {
85
+ ref: ref
86
+ })));
87
+ });
88
+ const small = "@media (max-width: 767px)";
89
+ const themedStylesFn = theme => ({
90
+ wrapper: {
91
+ display: "flex",
92
+ flexDirection: "row",
93
+ alignItems: "stretch",
94
+ width: "100%",
95
+ height: "100%",
96
+ position: "relative",
97
+ [small]: {
98
+ padding: theme.spacing.dialog.small,
92
99
  flexDirection: "column"
93
100
  }
94
- })
95
- };
101
+ },
102
+ dialog: {
103
+ width: "100%",
104
+ height: "100%",
105
+ borderRadius: theme.border.radius,
106
+ overflow: "hidden"
107
+ },
108
+ above: {
109
+ pointerEvents: "none",
110
+ position: "absolute",
111
+ top: 0,
112
+ left: 0,
113
+ bottom: 0,
114
+ right: 0,
115
+ zIndex: 1
116
+ },
117
+ below: {
118
+ pointerEvents: "none",
119
+ position: "absolute",
120
+ top: 0,
121
+ left: 0,
122
+ bottom: 0,
123
+ right: 0,
124
+ zIndex: -1
125
+ }
126
+ });
127
+ ModalDialog.displayName = "ModalDialog";
96
128
 
97
129
  class ModalFooter extends React.Component {
98
130
  static isClassOf(instance) {
@@ -260,21 +292,6 @@ class FocusTrap extends React.Component {
260
292
  }
261
293
  }
262
294
 
263
- function _extends() {
264
- _extends = Object.assign ? Object.assign.bind() : function (target) {
265
- for (var i = 1; i < arguments.length; i++) {
266
- var source = arguments[i];
267
- for (var key in source) {
268
- if (Object.prototype.hasOwnProperty.call(source, key)) {
269
- target[key] = source[key];
270
- }
271
- }
272
- }
273
- return target;
274
- };
275
- return _extends.apply(this, arguments);
276
- }
277
-
278
295
  const ModalLauncherPortalAttributeName = "data-modal-launcher-portal";
279
296
 
280
297
  const FOCUSABLE_ELEMENTS = 'a[href], details, input, textarea, select, button:not([aria-label^="Close"])';
package/dist/index.js CHANGED
@@ -3,11 +3,12 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var React = require('react');
6
- var aphrodite = require('aphrodite');
7
- var wonderBlocksLayout = require('@khanacademy/wonder-blocks-layout');
8
6
  var wonderBlocksCore = require('@khanacademy/wonder-blocks-core');
9
- var Spacing = require('@khanacademy/wonder-blocks-spacing');
7
+ var wonderBlocksTheming = require('@khanacademy/wonder-blocks-theming');
8
+ var aphrodite = require('aphrodite');
10
9
  var Color = require('@khanacademy/wonder-blocks-color');
10
+ var Spacing = require('@khanacademy/wonder-blocks-spacing');
11
+ var wonderBlocksLayout = require('@khanacademy/wonder-blocks-layout');
11
12
  var wonderBlocksTypography = require('@khanacademy/wonder-blocks-typography');
12
13
  var ReactDOM = require('react-dom');
13
14
  var wonderBlocksTiming = require('@khanacademy/wonder-blocks-timing');
@@ -17,113 +18,144 @@ var IconButton = require('@khanacademy/wonder-blocks-icon-button');
17
18
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
18
19
 
19
20
  function _interopNamespace(e) {
20
- if (e && e.__esModule) return e;
21
- var n = Object.create(null);
22
- if (e) {
23
- Object.keys(e).forEach(function (k) {
24
- if (k !== 'default') {
25
- var d = Object.getOwnPropertyDescriptor(e, k);
26
- Object.defineProperty(n, k, d.get ? d : {
27
- enumerable: true,
28
- get: function () { return e[k]; }
29
- });
30
- }
21
+ if (e && e.__esModule) return e;
22
+ var n = Object.create(null);
23
+ if (e) {
24
+ Object.keys(e).forEach(function (k) {
25
+ if (k !== 'default') {
26
+ var d = Object.getOwnPropertyDescriptor(e, k);
27
+ Object.defineProperty(n, k, d.get ? d : {
28
+ enumerable: true,
29
+ get: function () { return e[k]; }
31
30
  });
32
- }
33
- n["default"] = e;
34
- return Object.freeze(n);
31
+ }
32
+ });
33
+ }
34
+ n["default"] = e;
35
+ return Object.freeze(n);
35
36
  }
36
37
 
37
38
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
38
- var Spacing__default = /*#__PURE__*/_interopDefaultLegacy(Spacing);
39
39
  var Color__default = /*#__PURE__*/_interopDefaultLegacy(Color);
40
+ var Spacing__default = /*#__PURE__*/_interopDefaultLegacy(Spacing);
40
41
  var ReactDOM__namespace = /*#__PURE__*/_interopNamespace(ReactDOM);
41
42
  var xIcon__default = /*#__PURE__*/_interopDefaultLegacy(xIcon);
42
43
  var IconButton__default = /*#__PURE__*/_interopDefaultLegacy(IconButton);
43
44
 
44
- class ModalDialog extends React__namespace.Component {
45
- render() {
46
- const {
47
- above,
48
- below,
49
- role,
50
- style,
51
- children,
52
- testId,
53
- "aria-labelledby": ariaLabelledBy,
54
- "aria-describedby": ariaDescribedBy
55
- } = this.props;
56
- const contextValue = {
57
- ssrSize: "large",
58
- mediaSpec: wonderBlocksLayout.MEDIA_MODAL_SPEC
59
- };
60
- return React__namespace.createElement(wonderBlocksLayout.MediaLayoutContext.Provider, {
61
- value: contextValue
62
- }, React__namespace.createElement(wonderBlocksLayout.MediaLayout, {
63
- styleSheets: styleSheets$3
64
- }, ({
65
- styles
66
- }) => React__namespace.createElement(wonderBlocksCore.View, {
67
- style: [styles.wrapper, style]
68
- }, below && React__namespace.createElement(wonderBlocksCore.View, {
69
- style: styles.below
70
- }, below), React__namespace.createElement(wonderBlocksCore.View, {
71
- role: role,
72
- "aria-modal": "true",
73
- "aria-labelledby": ariaLabelledBy,
74
- "aria-describedby": ariaDescribedBy,
75
- style: styles.dialog,
76
- testId: testId
77
- }, children), above && React__namespace.createElement(wonderBlocksCore.View, {
78
- style: styles.above
79
- }, above))));
80
- }
45
+ function _extends() {
46
+ _extends = Object.assign ? Object.assign.bind() : function (target) {
47
+ for (var i = 1; i < arguments.length; i++) {
48
+ var source = arguments[i];
49
+ for (var key in source) {
50
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
51
+ target[key] = source[key];
52
+ }
53
+ }
54
+ }
55
+ return target;
56
+ };
57
+ return _extends.apply(this, arguments);
81
58
  }
82
- ModalDialog.defaultProps = {
83
- role: "dialog"
84
- };
85
- const styleSheets$3 = {
86
- all: aphrodite.StyleSheet.create({
87
- wrapper: {
88
- display: "flex",
89
- flexDirection: "row",
90
- alignItems: "stretch",
91
- width: "100%",
92
- height: "100%",
93
- position: "relative"
94
- },
59
+
60
+ const theme = {
61
+ border: {
62
+ radius: wonderBlocksTheming.tokens.border.radius.medium_4
63
+ },
64
+ spacing: {
95
65
  dialog: {
96
- width: "100%",
97
- height: "100%",
98
- borderRadius: 4,
99
- overflow: "hidden"
100
- },
101
- above: {
102
- pointerEvents: "none",
103
- position: "absolute",
104
- top: 0,
105
- left: 0,
106
- bottom: 0,
107
- right: 0,
108
- zIndex: 1
109
- },
110
- below: {
111
- pointerEvents: "none",
112
- position: "absolute",
113
- top: 0,
114
- left: 0,
115
- bottom: 0,
116
- right: 0,
117
- zIndex: -1
66
+ small: wonderBlocksTheming.tokens.spacing.medium_16
118
67
  }
119
- }),
120
- small: aphrodite.StyleSheet.create({
121
- wrapper: {
122
- padding: Spacing__default["default"].medium_16,
68
+ }
69
+ };
70
+
71
+ const themes = {
72
+ default: theme
73
+ };
74
+ const ModalDialogThemeContext = wonderBlocksTheming.createThemeContext(theme);
75
+ function ThemeModalDialog(props) {
76
+ const currentTheme = React__namespace.useContext(wonderBlocksTheming.ThemeSwitcherContext);
77
+ const theme$1 = themes[currentTheme] || theme;
78
+ return React__namespace.createElement(ModalDialogThemeContext.Provider, {
79
+ value: theme$1
80
+ }, props.children);
81
+ }
82
+
83
+ const ModalDialogCore = React__namespace.forwardRef(function ModalDialogCore(props, ref) {
84
+ const {
85
+ above,
86
+ below,
87
+ role = "dialog",
88
+ style,
89
+ children,
90
+ testId,
91
+ "aria-labelledby": ariaLabelledBy,
92
+ "aria-describedby": ariaDescribedBy
93
+ } = props;
94
+ const {
95
+ theme
96
+ } = wonderBlocksTheming.useScopedTheme(ModalDialogThemeContext);
97
+ const styles = wonderBlocksTheming.useStyles(themedStylesFn, theme);
98
+ return React__namespace.createElement(wonderBlocksCore.View, {
99
+ style: [styles.wrapper, style]
100
+ }, below && React__namespace.createElement(wonderBlocksCore.View, {
101
+ style: styles.below
102
+ }, below), React__namespace.createElement(wonderBlocksCore.View, {
103
+ role: role,
104
+ "aria-modal": "true",
105
+ "aria-labelledby": ariaLabelledBy,
106
+ "aria-describedby": ariaDescribedBy,
107
+ ref: ref,
108
+ style: styles.dialog,
109
+ testId: testId
110
+ }, children), above && React__namespace.createElement(wonderBlocksCore.View, {
111
+ style: styles.above
112
+ }, above));
113
+ });
114
+ const ModalDialog = React__namespace.forwardRef(function ModalDialog(props, ref) {
115
+ return React__namespace.createElement(ThemeModalDialog, null, React__namespace.createElement(ModalDialogCore, _extends({}, props, {
116
+ ref: ref
117
+ })));
118
+ });
119
+ const small = "@media (max-width: 767px)";
120
+ const themedStylesFn = theme => ({
121
+ wrapper: {
122
+ display: "flex",
123
+ flexDirection: "row",
124
+ alignItems: "stretch",
125
+ width: "100%",
126
+ height: "100%",
127
+ position: "relative",
128
+ [small]: {
129
+ padding: theme.spacing.dialog.small,
123
130
  flexDirection: "column"
124
131
  }
125
- })
126
- };
132
+ },
133
+ dialog: {
134
+ width: "100%",
135
+ height: "100%",
136
+ borderRadius: theme.border.radius,
137
+ overflow: "hidden"
138
+ },
139
+ above: {
140
+ pointerEvents: "none",
141
+ position: "absolute",
142
+ top: 0,
143
+ left: 0,
144
+ bottom: 0,
145
+ right: 0,
146
+ zIndex: 1
147
+ },
148
+ below: {
149
+ pointerEvents: "none",
150
+ position: "absolute",
151
+ top: 0,
152
+ left: 0,
153
+ bottom: 0,
154
+ right: 0,
155
+ zIndex: -1
156
+ }
157
+ });
158
+ ModalDialog.displayName = "ModalDialog";
127
159
 
128
160
  class ModalFooter extends React__namespace.Component {
129
161
  static isClassOf(instance) {
@@ -291,21 +323,6 @@ class FocusTrap extends React__namespace.Component {
291
323
  }
292
324
  }
293
325
 
294
- function _extends() {
295
- _extends = Object.assign ? Object.assign.bind() : function (target) {
296
- for (var i = 1; i < arguments.length; i++) {
297
- var source = arguments[i];
298
- for (var key in source) {
299
- if (Object.prototype.hasOwnProperty.call(source, key)) {
300
- target[key] = source[key];
301
- }
302
- }
303
- }
304
- return target;
305
- };
306
- return _extends.apply(this, arguments);
307
- }
308
-
309
326
  const ModalLauncherPortalAttributeName = "data-modal-launcher-portal";
310
327
 
311
328
  const FOCUSABLE_ELEMENTS = 'a[href], details, input, textarea, select, button:not([aria-label^="Close"])';
@@ -0,0 +1,11 @@
1
+ declare const theme: {
2
+ border: {
3
+ radius: number;
4
+ };
5
+ spacing: {
6
+ dialog: {
7
+ small: 16;
8
+ };
9
+ };
10
+ };
11
+ export default theme;
@@ -0,0 +1,26 @@
1
+ import * as React from "react";
2
+ import defaultTheme from "./default";
3
+ type Props = {
4
+ children: React.ReactNode;
5
+ };
6
+ export type ModalDialogThemeContract = typeof defaultTheme;
7
+ /**
8
+ * The context that provides the theme to the ModalDialog component.
9
+ * This is generally consumed via the `useScopedTheme` hook.
10
+ */
11
+ export declare const ModalDialogThemeContext: React.Context<{
12
+ border: {
13
+ radius: number;
14
+ };
15
+ spacing: {
16
+ dialog: {
17
+ small: 16;
18
+ };
19
+ };
20
+ }>;
21
+ /**
22
+ * ThemeModalDialog is a component that provides a theme to the <ModalDialog/>
23
+ * component.
24
+ */
25
+ export default function ThemeModalDialog(props: Props): JSX.Element;
26
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-modal",
3
- "version": "4.0.39",
3
+ "version": "4.1.0",
4
4
  "design": "v2",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -22,6 +22,7 @@
22
22
  "@khanacademy/wonder-blocks-icon-button": "^5.1.7",
23
23
  "@khanacademy/wonder-blocks-layout": "^2.0.25",
24
24
  "@khanacademy/wonder-blocks-spacing": "^4.0.1",
25
+ "@khanacademy/wonder-blocks-theming": "^1.2.1",
25
26
  "@khanacademy/wonder-blocks-timing": "^4.0.2",
26
27
  "@khanacademy/wonder-blocks-typography": "^2.1.10"
27
28
  },
@@ -0,0 +1,78 @@
1
+ import * as React from "react";
2
+ import {render, screen} from "@testing-library/react";
3
+
4
+ import ModalDialog from "../modal-dialog";
5
+
6
+ describe("ModalDialog", () => {
7
+ it("should render its contents", () => {
8
+ // Arrange
9
+
10
+ // Act
11
+ render(
12
+ <ModalDialog>
13
+ <h1>A modal</h1>
14
+ <p>The contents</p>
15
+ </ModalDialog>,
16
+ );
17
+
18
+ // Assert
19
+ expect(screen.getByRole("dialog")).toBeInTheDocument();
20
+ });
21
+
22
+ it("should announce the dialog if aria-labelledby is set", () => {
23
+ // Arrange
24
+
25
+ // Act
26
+ render(
27
+ <ModalDialog aria-labelledby="dialog-title">
28
+ <h1 id="dialog-title">A modal</h1>
29
+ <p>The contents</p>
30
+ </ModalDialog>,
31
+ );
32
+
33
+ // Assert
34
+ expect(
35
+ screen.getByRole("dialog", {name: "A modal"}),
36
+ ).toBeInTheDocument();
37
+ });
38
+
39
+ it("should announce the dialog's contents if aria-describedby is set", () => {
40
+ // Arrange
41
+
42
+ // Act
43
+ render(
44
+ <ModalDialog aria-describedby="dialog-body">
45
+ <>
46
+ <h1>A modal</h1>
47
+ <p id="dialog-body">The contents</p>
48
+ </>
49
+ </ModalDialog>,
50
+ );
51
+
52
+ // Assert
53
+ expect(
54
+ screen.getByRole("dialog", {description: "The contents"}),
55
+ ).toBeInTheDocument();
56
+ });
57
+
58
+ it("adds testId to the dialog element", () => {
59
+ // Arrange
60
+
61
+ // Act
62
+ render(<ModalDialog testId="test-id">A modal</ModalDialog>);
63
+
64
+ // Assert
65
+ expect(screen.getByTestId("test-id")).toBeInTheDocument();
66
+ });
67
+
68
+ it("forwards the ref to the dialog element", () => {
69
+ // Arrange
70
+ const ref: React.RefObject<HTMLDivElement> = React.createRef();
71
+
72
+ // Act
73
+ render(<ModalDialog ref={ref}>A modal</ModalDialog>);
74
+
75
+ // Assert
76
+ expect(ref.current).toBeInstanceOf(HTMLDivElement);
77
+ });
78
+ });