@instructure/ui-modal 10.3.1-snapshot-9 → 10.4.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
@@ -3,9 +3,12 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
- ## [10.3.1-snapshot-9](https://github.com/instructure/instructure-ui/compare/v10.3.0...v10.3.1-snapshot-9) (2024-10-11)
6
+ # [10.4.0](https://github.com/instructure/instructure-ui/compare/v10.3.0...v10.4.0) (2024-10-16)
7
7
 
8
- **Note:** Version bump only for package @instructure/ui-modal
8
+
9
+ ### Features
10
+
11
+ * **ui-modal:** modify modal to support less strict children ([40f8ca2](https://github.com/instructure/instructure-ui/commit/40f8ca24e80ceb41e8c5d05d1f9d5e8f77113370))
9
12
 
10
13
 
11
14
 
@@ -1,6 +1,6 @@
1
1
  import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
2
2
  const _excluded = ["label"];
3
- var _Modal, _Modal2, _Modal$Body, _Modal3, _Modal4, _Modal5, _Modal$Body2, _Modal$Body3, _Modal$Body4, _Modal$Body5, _Modal$Body6, _Modal$Body7, _Modal6, _Modal7, _Modal8, _input, _Modal$Header, _Modal$Body8, _Modal$Footer;
3
+ var _Modal, _Modal2, _Modal$Body, _Modal3, _Modal4, _View, _Modal5, _Modal$Body2, _Modal$Body3, _Modal$Body4, _Modal$Body5, _Modal$Body6, _Modal$Body7, _Modal6, _Modal7, _input, _Modal$Header, _Modal$Body8, _Modal$Footer;
4
4
  /*
5
5
  * The MIT License (MIT)
6
6
  *
@@ -31,6 +31,7 @@ import { vi } from 'vitest';
31
31
  import userEvent from '@testing-library/user-event';
32
32
  import '@testing-library/jest-dom';
33
33
  import { Modal } from '../index';
34
+ import { View } from '@instructure/ui-view';
34
35
  describe('<Modal />', () => {
35
36
  let consoleWarningMock;
36
37
  let consoleErrorMock;
@@ -146,13 +147,26 @@ describe('<Modal />', () => {
146
147
  const modalBody = await findByText(bodyText);
147
148
  expect(modalBody).toBeInTheDocument();
148
149
  });
150
+ it('should handle custom children', async () => {
151
+ const bodyText = 'Modal-body-text';
152
+ const _render7 = render(/*#__PURE__*/React.createElement(Modal, {
153
+ open: true,
154
+ label: "Modal Dialog",
155
+ shouldReturnFocus: false
156
+ }, _View || (_View = /*#__PURE__*/React.createElement(View, null, "This is a custom child")), /*#__PURE__*/React.createElement(Modal.Body, null, bodyText))),
157
+ findByText = _render7.findByText;
158
+ const modalBody = await findByText(bodyText);
159
+ const customChild = await findByText('This is a custom child');
160
+ expect(modalBody).toBeInTheDocument();
161
+ expect(customChild).toBeInTheDocument();
162
+ });
149
163
  it('should apply the aria attributes', async () => {
150
- const _render7 = render(_Modal5 || (_Modal5 = /*#__PURE__*/React.createElement(Modal, {
164
+ const _render8 = render(_Modal5 || (_Modal5 = /*#__PURE__*/React.createElement(Modal, {
151
165
  open: true,
152
166
  label: "Modal Dialog",
153
167
  shouldReturnFocus: false
154
168
  }, /*#__PURE__*/React.createElement(Modal.Body, null, "Foo Bar Baz")))),
155
- findByRole = _render7.findByRole;
169
+ findByRole = _render8.findByRole;
156
170
  const dialog = await findByRole('dialog');
157
171
  expect(dialog).toBeInTheDocument();
158
172
  expect(dialog).toHaveAttribute('aria-label', 'Modal Dialog');
@@ -161,7 +175,7 @@ describe('<Modal />', () => {
161
175
  const onEnter = vi.fn();
162
176
  const onEntering = vi.fn();
163
177
  const onEntered = vi.fn();
164
- const _render8 = render(/*#__PURE__*/React.createElement(Modal, {
178
+ const _render9 = render(/*#__PURE__*/React.createElement(Modal, {
165
179
  open: true,
166
180
  onEnter: onEnter,
167
181
  onEntering: onEntering,
@@ -170,7 +184,7 @@ describe('<Modal />', () => {
170
184
  label: "Modal Dialog",
171
185
  shouldReturnFocus: false
172
186
  }, _Modal$Body2 || (_Modal$Body2 = /*#__PURE__*/React.createElement(Modal.Body, null, "Foo Bar Baz")))),
173
- findByRole = _render8.findByRole;
187
+ findByRole = _render9.findByRole;
174
188
  const dialog = await findByRole('dialog');
175
189
  expect(dialog).toBeInTheDocument();
176
190
  await waitFor(() => {
@@ -181,13 +195,13 @@ describe('<Modal />', () => {
181
195
  });
182
196
  it('should support onOpen prop', async () => {
183
197
  const onOpen = vi.fn();
184
- const _render9 = render(/*#__PURE__*/React.createElement(Modal, {
198
+ const _render10 = render(/*#__PURE__*/React.createElement(Modal, {
185
199
  open: true,
186
200
  onOpen: onOpen,
187
201
  label: "Modal Dialog",
188
202
  shouldReturnFocus: false
189
203
  }, _Modal$Body3 || (_Modal$Body3 = /*#__PURE__*/React.createElement(Modal.Body, null, "Foo Bar Baz")))),
190
- findByRole = _render9.findByRole;
204
+ findByRole = _render10.findByRole;
191
205
  const dialog = await findByRole('dialog');
192
206
  expect(dialog).toBeInTheDocument();
193
207
  await waitFor(() => {
@@ -196,14 +210,14 @@ describe('<Modal />', () => {
196
210
  });
197
211
  it('should support onClose prop', async () => {
198
212
  const onClose = vi.fn();
199
- const _render10 = render(/*#__PURE__*/React.createElement(Modal, {
213
+ const _render11 = render(/*#__PURE__*/React.createElement(Modal, {
200
214
  open: true,
201
215
  onClose: onClose,
202
216
  label: "Modal Dialog",
203
217
  shouldReturnFocus: false
204
218
  }, _Modal$Body4 || (_Modal$Body4 = /*#__PURE__*/React.createElement(Modal.Body, null, "Foo Bar Baz")))),
205
- findByRole = _render10.findByRole,
206
- rerender = _render10.rerender;
219
+ findByRole = _render11.findByRole,
220
+ rerender = _render11.rerender;
207
221
  const dialog = await findByRole('dialog');
208
222
  expect(dialog).toBeInTheDocument();
209
223
  rerender(/*#__PURE__*/React.createElement(Modal, {
@@ -218,13 +232,13 @@ describe('<Modal />', () => {
218
232
  });
219
233
  it('should dismiss when overlay clicked by default', async () => {
220
234
  const onDismiss = vi.fn();
221
- const _render11 = render(/*#__PURE__*/React.createElement(Modal, {
235
+ const _render12 = render(/*#__PURE__*/React.createElement(Modal, {
222
236
  open: true,
223
237
  onDismiss: onDismiss,
224
238
  label: "Modal Dialog",
225
239
  shouldReturnFocus: false
226
240
  }, _Modal$Body6 || (_Modal$Body6 = /*#__PURE__*/React.createElement(Modal.Body, null, "Modal Text")))),
227
- findByText = _render11.findByText;
241
+ findByText = _render12.findByText;
228
242
  const modalBody = await findByText('Modal Text');
229
243
  expect(modalBody).toBeInTheDocument();
230
244
  await waitFor(() => {
@@ -235,7 +249,7 @@ describe('<Modal />', () => {
235
249
  it('should NOT dismiss when overlay clicked with shouldCloseOnDocumentClick=false', async () => {
236
250
  const onDismiss = vi.fn();
237
251
  const onClickOuter = vi.fn();
238
- const _render12 = render(/*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
252
+ const _render13 = render(/*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
239
253
  "data-testid": "outer-element",
240
254
  onClick: onClickOuter
241
255
  }, "for dismiss"), /*#__PURE__*/React.createElement(Modal, {
@@ -245,8 +259,8 @@ describe('<Modal />', () => {
245
259
  shouldReturnFocus: false,
246
260
  shouldCloseOnDocumentClick: false
247
261
  }, _Modal$Body7 || (_Modal$Body7 = /*#__PURE__*/React.createElement(Modal.Body, null, "Foo Bar Baz ", /*#__PURE__*/React.createElement("button", null, "click me")))))),
248
- findByRole = _render12.findByRole,
249
- getByTestId = _render12.getByTestId;
262
+ findByRole = _render13.findByRole,
263
+ getByTestId = _render13.getByTestId;
250
264
  const dialog = await findByRole('dialog');
251
265
  expect(dialog).toBeInTheDocument();
252
266
  userEvent.click(getByTestId('outer-element'));
@@ -257,38 +271,26 @@ describe('<Modal />', () => {
257
271
  });
258
272
  });
259
273
  it('should render children', async () => {
260
- const _render13 = render(_Modal6 || (_Modal6 = /*#__PURE__*/React.createElement(Modal, {
274
+ const _render14 = render(_Modal6 || (_Modal6 = /*#__PURE__*/React.createElement(Modal, {
261
275
  open: true,
262
276
  label: "Modal Dialog",
263
277
  shouldReturnFocus: false
264
278
  }, /*#__PURE__*/React.createElement(Modal.Body, null, /*#__PURE__*/React.createElement("button", null, "Cancel"))))),
265
- findByText = _render13.findByText;
279
+ findByText = _render14.findByText;
266
280
  const cancelButton = await findByText('Cancel');
267
281
  expect(cancelButton).toBeInTheDocument();
268
282
  });
269
283
  describe('children validation', () => {
270
284
  it('should pass validation when children are valid', async () => {
271
- const _render14 = render(_Modal7 || (_Modal7 = /*#__PURE__*/React.createElement(Modal, {
285
+ const _render15 = render(_Modal7 || (_Modal7 = /*#__PURE__*/React.createElement(Modal, {
272
286
  open: true,
273
287
  label: "Modal Dialog",
274
288
  shouldReturnFocus: false
275
289
  }, /*#__PURE__*/React.createElement(Modal.Header, null, "Hello World"), /*#__PURE__*/React.createElement(Modal.Body, null, "Foo Bar Baz"), /*#__PURE__*/React.createElement(Modal.Footer, null, /*#__PURE__*/React.createElement("button", null, "Cancel"))))),
276
- findByRole = _render14.findByRole;
277
- const dialog = await findByRole('dialog');
278
- expect(dialog).toBeInTheDocument();
279
- expect(consoleErrorMock).not.toHaveBeenCalled();
280
- });
281
- it('should not pass validation when children are invalid', async () => {
282
- const _render15 = render(_Modal8 || (_Modal8 = /*#__PURE__*/React.createElement(Modal, {
283
- open: true,
284
- label: "Modal Dialog",
285
- shouldReturnFocus: false
286
- }, /*#__PURE__*/React.createElement(Modal.Body, null, "Foo Bar Baz"), /*#__PURE__*/React.createElement(Modal.Footer, null, /*#__PURE__*/React.createElement("button", null, "Cancel")), /*#__PURE__*/React.createElement(Modal.Header, null, "Hello World")))),
287
290
  findByRole = _render15.findByRole;
288
291
  const dialog = await findByRole('dialog');
289
- const expectedErrorMessage = 'Expected children of Modal in one of the following formats:';
290
292
  expect(dialog).toBeInTheDocument();
291
- expect(consoleErrorMock).toHaveBeenCalledWith(expect.any(String), expect.any(String), expect.stringContaining(expectedErrorMessage), expect.any(String));
293
+ expect(consoleErrorMock).not.toHaveBeenCalled();
292
294
  });
293
295
  it('should pass inverse variant to children when set', async () => {
294
296
  let headerRef = null;
package/es/Modal/index.js CHANGED
@@ -26,8 +26,8 @@ var _dec, _dec2, _class, _Modal;
26
26
  */
27
27
 
28
28
  /** @jsx jsx */
29
- import React, { Children, Component } from 'react';
30
- import { passthroughProps, safeCloneElement, matchComponentTypes } from '@instructure/ui-react-utils';
29
+ import { Children, Component, isValidElement } from 'react';
30
+ import { passthroughProps, safeCloneElement } from '@instructure/ui-react-utils';
31
31
  import { createChainedFunction } from '@instructure/ui-utils';
32
32
  import { testable } from '@instructure/ui-testable';
33
33
  import { Transition } from '@instructure/ui-motion';
@@ -114,15 +114,14 @@ let Modal = (_dec = withStyle(generateStyle, generateComponentTheme), _dec2 = te
114
114
  return Children.map(children, child => {
115
115
  if (!child) return; // ignore null, falsy children
116
116
 
117
- if (matchComponentTypes(child, [ModalBody])) {
117
+ if (/*#__PURE__*/isValidElement(child)) {
118
+ var _child$props;
118
119
  return safeCloneElement(child, {
119
120
  variant: variant,
120
- overflow: child.props.overflow || overflow
121
+ overflow: (child === null || child === void 0 ? void 0 : (_child$props = child.props) === null || _child$props === void 0 ? void 0 : _child$props.overflow) || overflow
121
122
  });
122
123
  } else {
123
- return safeCloneElement(child, {
124
- variant: variant
125
- });
124
+ return child;
126
125
  }
127
126
  });
128
127
  }
package/es/Modal/props.js CHANGED
@@ -23,14 +23,11 @@
23
23
  */
24
24
 
25
25
  import PropTypes from 'prop-types';
26
- import { element, Children as ChildrenPropTypes } from '@instructure/ui-prop-types';
26
+ import { element } from '@instructure/ui-prop-types';
27
27
  import { transitionTypePropType } from '@instructure/ui-motion';
28
- import { ModalHeader } from './ModalHeader';
29
- import { ModalBody } from './ModalBody';
30
- import { ModalFooter } from './ModalFooter';
31
28
  const propTypes = {
32
29
  label: PropTypes.string.isRequired,
33
- children: ChildrenPropTypes.enforceOrder([ModalHeader, ModalBody, ModalFooter], [ModalHeader, ModalBody], [ModalBody, ModalFooter], [ModalBody]),
30
+ children: PropTypes.node,
34
31
  as: PropTypes.elementType,
35
32
  size: PropTypes.oneOf(['auto', 'small', 'medium', 'large', 'fullscreen']),
36
33
  variant: PropTypes.oneOf(['default', 'inverse']),
@@ -8,8 +8,9 @@ var _vitest = require("vitest");
8
8
  var _userEvent = _interopRequireDefault(require("@testing-library/user-event"));
9
9
  require("@testing-library/jest-dom");
10
10
  var _index = require("../index");
11
+ var _View2 = require("@instructure/ui-view/lib/View");
11
12
  const _excluded = ["label"];
12
- var _Modal, _Modal2, _Modal$Body, _Modal3, _Modal4, _Modal5, _Modal$Body2, _Modal$Body3, _Modal$Body4, _Modal$Body5, _Modal$Body6, _Modal$Body7, _Modal6, _Modal7, _Modal8, _input, _Modal$Header, _Modal$Body8, _Modal$Footer;
13
+ var _Modal, _Modal2, _Modal$Body, _Modal3, _Modal4, _View, _Modal5, _Modal$Body2, _Modal$Body3, _Modal$Body4, _Modal$Body5, _Modal$Body6, _Modal$Body7, _Modal6, _Modal7, _input, _Modal$Header, _Modal$Body8, _Modal$Footer;
13
14
  /*
14
15
  * The MIT License (MIT)
15
16
  *
@@ -148,13 +149,26 @@ describe('<Modal />', () => {
148
149
  const modalBody = await findByText(bodyText);
149
150
  expect(modalBody).toBeInTheDocument();
150
151
  });
152
+ it('should handle custom children', async () => {
153
+ const bodyText = 'Modal-body-text';
154
+ const _render7 = (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_index.Modal, {
155
+ open: true,
156
+ label: "Modal Dialog",
157
+ shouldReturnFocus: false
158
+ }, _View || (_View = /*#__PURE__*/_react.default.createElement(_View2.View, null, "This is a custom child")), /*#__PURE__*/_react.default.createElement(_index.Modal.Body, null, bodyText))),
159
+ findByText = _render7.findByText;
160
+ const modalBody = await findByText(bodyText);
161
+ const customChild = await findByText('This is a custom child');
162
+ expect(modalBody).toBeInTheDocument();
163
+ expect(customChild).toBeInTheDocument();
164
+ });
151
165
  it('should apply the aria attributes', async () => {
152
- const _render7 = (0, _react2.render)(_Modal5 || (_Modal5 = /*#__PURE__*/_react.default.createElement(_index.Modal, {
166
+ const _render8 = (0, _react2.render)(_Modal5 || (_Modal5 = /*#__PURE__*/_react.default.createElement(_index.Modal, {
153
167
  open: true,
154
168
  label: "Modal Dialog",
155
169
  shouldReturnFocus: false
156
170
  }, /*#__PURE__*/_react.default.createElement(_index.Modal.Body, null, "Foo Bar Baz")))),
157
- findByRole = _render7.findByRole;
171
+ findByRole = _render8.findByRole;
158
172
  const dialog = await findByRole('dialog');
159
173
  expect(dialog).toBeInTheDocument();
160
174
  expect(dialog).toHaveAttribute('aria-label', 'Modal Dialog');
@@ -163,7 +177,7 @@ describe('<Modal />', () => {
163
177
  const onEnter = _vitest.vi.fn();
164
178
  const onEntering = _vitest.vi.fn();
165
179
  const onEntered = _vitest.vi.fn();
166
- const _render8 = (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_index.Modal, {
180
+ const _render9 = (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_index.Modal, {
167
181
  open: true,
168
182
  onEnter: onEnter,
169
183
  onEntering: onEntering,
@@ -172,7 +186,7 @@ describe('<Modal />', () => {
172
186
  label: "Modal Dialog",
173
187
  shouldReturnFocus: false
174
188
  }, _Modal$Body2 || (_Modal$Body2 = /*#__PURE__*/_react.default.createElement(_index.Modal.Body, null, "Foo Bar Baz")))),
175
- findByRole = _render8.findByRole;
189
+ findByRole = _render9.findByRole;
176
190
  const dialog = await findByRole('dialog');
177
191
  expect(dialog).toBeInTheDocument();
178
192
  await (0, _react2.waitFor)(() => {
@@ -183,13 +197,13 @@ describe('<Modal />', () => {
183
197
  });
184
198
  it('should support onOpen prop', async () => {
185
199
  const onOpen = _vitest.vi.fn();
186
- const _render9 = (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_index.Modal, {
200
+ const _render10 = (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_index.Modal, {
187
201
  open: true,
188
202
  onOpen: onOpen,
189
203
  label: "Modal Dialog",
190
204
  shouldReturnFocus: false
191
205
  }, _Modal$Body3 || (_Modal$Body3 = /*#__PURE__*/_react.default.createElement(_index.Modal.Body, null, "Foo Bar Baz")))),
192
- findByRole = _render9.findByRole;
206
+ findByRole = _render10.findByRole;
193
207
  const dialog = await findByRole('dialog');
194
208
  expect(dialog).toBeInTheDocument();
195
209
  await (0, _react2.waitFor)(() => {
@@ -198,14 +212,14 @@ describe('<Modal />', () => {
198
212
  });
199
213
  it('should support onClose prop', async () => {
200
214
  const onClose = _vitest.vi.fn();
201
- const _render10 = (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_index.Modal, {
215
+ const _render11 = (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_index.Modal, {
202
216
  open: true,
203
217
  onClose: onClose,
204
218
  label: "Modal Dialog",
205
219
  shouldReturnFocus: false
206
220
  }, _Modal$Body4 || (_Modal$Body4 = /*#__PURE__*/_react.default.createElement(_index.Modal.Body, null, "Foo Bar Baz")))),
207
- findByRole = _render10.findByRole,
208
- rerender = _render10.rerender;
221
+ findByRole = _render11.findByRole,
222
+ rerender = _render11.rerender;
209
223
  const dialog = await findByRole('dialog');
210
224
  expect(dialog).toBeInTheDocument();
211
225
  rerender(/*#__PURE__*/_react.default.createElement(_index.Modal, {
@@ -220,13 +234,13 @@ describe('<Modal />', () => {
220
234
  });
221
235
  it('should dismiss when overlay clicked by default', async () => {
222
236
  const onDismiss = _vitest.vi.fn();
223
- const _render11 = (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_index.Modal, {
237
+ const _render12 = (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_index.Modal, {
224
238
  open: true,
225
239
  onDismiss: onDismiss,
226
240
  label: "Modal Dialog",
227
241
  shouldReturnFocus: false
228
242
  }, _Modal$Body6 || (_Modal$Body6 = /*#__PURE__*/_react.default.createElement(_index.Modal.Body, null, "Modal Text")))),
229
- findByText = _render11.findByText;
243
+ findByText = _render12.findByText;
230
244
  const modalBody = await findByText('Modal Text');
231
245
  expect(modalBody).toBeInTheDocument();
232
246
  await (0, _react2.waitFor)(() => {
@@ -237,7 +251,7 @@ describe('<Modal />', () => {
237
251
  it('should NOT dismiss when overlay clicked with shouldCloseOnDocumentClick=false', async () => {
238
252
  const onDismiss = _vitest.vi.fn();
239
253
  const onClickOuter = _vitest.vi.fn();
240
- const _render12 = (0, _react2.render)(/*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement("button", {
254
+ const _render13 = (0, _react2.render)(/*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement("button", {
241
255
  "data-testid": "outer-element",
242
256
  onClick: onClickOuter
243
257
  }, "for dismiss"), /*#__PURE__*/_react.default.createElement(_index.Modal, {
@@ -247,8 +261,8 @@ describe('<Modal />', () => {
247
261
  shouldReturnFocus: false,
248
262
  shouldCloseOnDocumentClick: false
249
263
  }, _Modal$Body7 || (_Modal$Body7 = /*#__PURE__*/_react.default.createElement(_index.Modal.Body, null, "Foo Bar Baz ", /*#__PURE__*/_react.default.createElement("button", null, "click me")))))),
250
- findByRole = _render12.findByRole,
251
- getByTestId = _render12.getByTestId;
264
+ findByRole = _render13.findByRole,
265
+ getByTestId = _render13.getByTestId;
252
266
  const dialog = await findByRole('dialog');
253
267
  expect(dialog).toBeInTheDocument();
254
268
  _userEvent.default.click(getByTestId('outer-element'));
@@ -259,38 +273,26 @@ describe('<Modal />', () => {
259
273
  });
260
274
  });
261
275
  it('should render children', async () => {
262
- const _render13 = (0, _react2.render)(_Modal6 || (_Modal6 = /*#__PURE__*/_react.default.createElement(_index.Modal, {
276
+ const _render14 = (0, _react2.render)(_Modal6 || (_Modal6 = /*#__PURE__*/_react.default.createElement(_index.Modal, {
263
277
  open: true,
264
278
  label: "Modal Dialog",
265
279
  shouldReturnFocus: false
266
280
  }, /*#__PURE__*/_react.default.createElement(_index.Modal.Body, null, /*#__PURE__*/_react.default.createElement("button", null, "Cancel"))))),
267
- findByText = _render13.findByText;
281
+ findByText = _render14.findByText;
268
282
  const cancelButton = await findByText('Cancel');
269
283
  expect(cancelButton).toBeInTheDocument();
270
284
  });
271
285
  describe('children validation', () => {
272
286
  it('should pass validation when children are valid', async () => {
273
- const _render14 = (0, _react2.render)(_Modal7 || (_Modal7 = /*#__PURE__*/_react.default.createElement(_index.Modal, {
287
+ const _render15 = (0, _react2.render)(_Modal7 || (_Modal7 = /*#__PURE__*/_react.default.createElement(_index.Modal, {
274
288
  open: true,
275
289
  label: "Modal Dialog",
276
290
  shouldReturnFocus: false
277
291
  }, /*#__PURE__*/_react.default.createElement(_index.Modal.Header, null, "Hello World"), /*#__PURE__*/_react.default.createElement(_index.Modal.Body, null, "Foo Bar Baz"), /*#__PURE__*/_react.default.createElement(_index.Modal.Footer, null, /*#__PURE__*/_react.default.createElement("button", null, "Cancel"))))),
278
- findByRole = _render14.findByRole;
279
- const dialog = await findByRole('dialog');
280
- expect(dialog).toBeInTheDocument();
281
- expect(consoleErrorMock).not.toHaveBeenCalled();
282
- });
283
- it('should not pass validation when children are invalid', async () => {
284
- const _render15 = (0, _react2.render)(_Modal8 || (_Modal8 = /*#__PURE__*/_react.default.createElement(_index.Modal, {
285
- open: true,
286
- label: "Modal Dialog",
287
- shouldReturnFocus: false
288
- }, /*#__PURE__*/_react.default.createElement(_index.Modal.Body, null, "Foo Bar Baz"), /*#__PURE__*/_react.default.createElement(_index.Modal.Footer, null, /*#__PURE__*/_react.default.createElement("button", null, "Cancel")), /*#__PURE__*/_react.default.createElement(_index.Modal.Header, null, "Hello World")))),
289
292
  findByRole = _render15.findByRole;
290
293
  const dialog = await findByRole('dialog');
291
- const expectedErrorMessage = 'Expected children of Modal in one of the following formats:';
292
294
  expect(dialog).toBeInTheDocument();
293
- expect(consoleErrorMock).toHaveBeenCalledWith(expect.any(String), expect.any(String), expect.stringContaining(expectedErrorMessage), expect.any(String));
295
+ expect(consoleErrorMock).not.toHaveBeenCalled();
294
296
  });
295
297
  it('should pass inverse variant to children when set', async () => {
296
298
  let headerRef = null;
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
 
3
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
4
- var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
5
4
  Object.defineProperty(exports, "__esModule", {
6
5
  value: true
7
6
  });
@@ -26,10 +25,9 @@ Object.defineProperty(exports, "ModalHeader", {
26
25
  });
27
26
  exports.default = void 0;
28
27
  var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
29
- var _react = _interopRequireWildcard(require("react"));
28
+ var _react = require("react");
30
29
  var _passthroughProps = require("@instructure/ui-react-utils/lib/passthroughProps.js");
31
30
  var _safeCloneElement = require("@instructure/ui-react-utils/lib/safeCloneElement.js");
32
- var _matchComponentTypes = require("@instructure/ui-react-utils/lib/matchComponentTypes.js");
33
31
  var _createChainedFunction = require("@instructure/ui-utils/lib/createChainedFunction.js");
34
32
  var _testable = require("@instructure/ui-testable/lib/testable.js");
35
33
  var _Transition = require("@instructure/ui-motion/lib/Transition");
@@ -142,15 +140,14 @@ let Modal = exports.Modal = (_dec = (0, _emotion.withStyle)(_styles.default, _th
142
140
  return _react.Children.map(children, child => {
143
141
  if (!child) return; // ignore null, falsy children
144
142
 
145
- if ((0, _matchComponentTypes.matchComponentTypes)(child, [_ModalBody.ModalBody])) {
143
+ if (/*#__PURE__*/(0, _react.isValidElement)(child)) {
144
+ var _child$props;
146
145
  return (0, _safeCloneElement.safeCloneElement)(child, {
147
146
  variant: variant,
148
- overflow: child.props.overflow || overflow
147
+ overflow: (child === null || child === void 0 ? void 0 : (_child$props = child.props) === null || _child$props === void 0 ? void 0 : _child$props.overflow) || overflow
149
148
  });
150
149
  } else {
151
- return (0, _safeCloneElement.safeCloneElement)(child, {
152
- variant: variant
153
- });
150
+ return child;
154
151
  }
155
152
  });
156
153
  }
@@ -7,11 +7,7 @@ Object.defineProperty(exports, "__esModule", {
7
7
  exports.propTypes = exports.allowedProps = void 0;
8
8
  var _propTypes = _interopRequireDefault(require("prop-types"));
9
9
  var _element = require("@instructure/ui-prop-types/lib/element.js");
10
- var _Children = require("@instructure/ui-prop-types/lib/Children.js");
11
10
  var _uiMotion = require("@instructure/ui-motion");
12
- var _ModalHeader = require("./ModalHeader");
13
- var _ModalBody = require("./ModalBody");
14
- var _ModalFooter = require("./ModalFooter");
15
11
  /*
16
12
  * The MIT License (MIT)
17
13
  *
@@ -38,7 +34,7 @@ var _ModalFooter = require("./ModalFooter");
38
34
 
39
35
  const propTypes = exports.propTypes = {
40
36
  label: _propTypes.default.string.isRequired,
41
- children: _Children.Children.enforceOrder([_ModalHeader.ModalHeader, _ModalBody.ModalBody, _ModalFooter.ModalFooter], [_ModalHeader.ModalHeader, _ModalBody.ModalBody], [_ModalBody.ModalBody, _ModalFooter.ModalFooter], [_ModalBody.ModalBody]),
37
+ children: _propTypes.default.node,
42
38
  as: _propTypes.default.elementType,
43
39
  size: _propTypes.default.oneOf(['auto', 'small', 'medium', 'large', 'fullscreen']),
44
40
  variant: _propTypes.default.oneOf(['default', 'inverse']),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-modal",
3
- "version": "10.3.1-snapshot-9",
3
+ "version": "10.4.0",
4
4
  "description": "A component for displaying content in a dialog overlay",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./es/index.js",
@@ -24,30 +24,30 @@
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
26
  "@babel/runtime": "^7.25.6",
27
- "@instructure/console": "10.3.1-snapshot-9",
28
- "@instructure/emotion": "10.3.1-snapshot-9",
29
- "@instructure/shared-types": "10.3.1-snapshot-9",
30
- "@instructure/ui-buttons": "10.3.1-snapshot-9",
31
- "@instructure/ui-dialog": "10.3.1-snapshot-9",
32
- "@instructure/ui-dom-utils": "10.3.1-snapshot-9",
33
- "@instructure/ui-motion": "10.3.1-snapshot-9",
34
- "@instructure/ui-overlays": "10.3.1-snapshot-9",
35
- "@instructure/ui-portal": "10.3.1-snapshot-9",
36
- "@instructure/ui-prop-types": "10.3.1-snapshot-9",
37
- "@instructure/ui-react-utils": "10.3.1-snapshot-9",
38
- "@instructure/ui-testable": "10.3.1-snapshot-9",
39
- "@instructure/ui-utils": "10.3.1-snapshot-9",
40
- "@instructure/ui-view": "10.3.1-snapshot-9",
27
+ "@instructure/console": "10.4.0",
28
+ "@instructure/emotion": "10.4.0",
29
+ "@instructure/shared-types": "10.4.0",
30
+ "@instructure/ui-buttons": "10.4.0",
31
+ "@instructure/ui-dialog": "10.4.0",
32
+ "@instructure/ui-dom-utils": "10.4.0",
33
+ "@instructure/ui-motion": "10.4.0",
34
+ "@instructure/ui-overlays": "10.4.0",
35
+ "@instructure/ui-portal": "10.4.0",
36
+ "@instructure/ui-prop-types": "10.4.0",
37
+ "@instructure/ui-react-utils": "10.4.0",
38
+ "@instructure/ui-testable": "10.4.0",
39
+ "@instructure/ui-utils": "10.4.0",
40
+ "@instructure/ui-view": "10.4.0",
41
41
  "prop-types": "^15.8.1"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "react": ">=16.8 <=18"
45
45
  },
46
46
  "devDependencies": {
47
- "@instructure/ui-babel-preset": "10.3.1-snapshot-9",
48
- "@instructure/ui-color-utils": "10.3.1-snapshot-9",
49
- "@instructure/ui-position": "10.3.1-snapshot-9",
50
- "@instructure/ui-themes": "10.3.1-snapshot-9",
47
+ "@instructure/ui-babel-preset": "10.4.0",
48
+ "@instructure/ui-color-utils": "10.4.0",
49
+ "@instructure/ui-position": "10.4.0",
50
+ "@instructure/ui-themes": "10.4.0",
51
51
  "@testing-library/jest-dom": "^6.4.6",
52
52
  "@testing-library/react": "^16.0.1",
53
53
  "@testing-library/user-event": "^14.5.2",
@@ -1051,6 +1051,100 @@ On smaller viewports (like mobile devices or scaled-up UI), we don't want to los
1051
1051
  render(<Example />)
1052
1052
  ```
1053
1053
 
1054
+ ### Using custom children
1055
+
1056
+ Occasionally, you might find it useful to incorporate custom components within a `Modal`, such as a higher-order component for `Modal.Header` or `Modal.Body` or not using built in child components at all. Although this approach is typically not advised, it can sometimes aid in code splitting or achieving more streamlined code, especially for more intricate and sizable `Modal`s.
1057
+
1058
+ Below example demonstrates how to use a higher-order component for `Modal.Body`. `Modal` consists of a `Modal.Header`, a custom `WrappedModalBody` component, and a `View` component. Properties `variant` and `overflow` are passed down to child components. While the original `Modal.Header`, `Modal.Body` and `Modal.Footer` components use these properties, please note that these might cause unpredictable side effects for custom components.
1059
+
1060
+ ```js
1061
+ ---
1062
+ type: example
1063
+ ---
1064
+
1065
+ class Example extends React.Component {
1066
+ constructor (props) {
1067
+ super(props)
1068
+
1069
+ this.state = {
1070
+ open: false
1071
+ }
1072
+ }
1073
+
1074
+ handleButtonClick = () => {
1075
+ this.setState(function (state) {
1076
+ return { open: !state.open }
1077
+ })
1078
+ };
1079
+
1080
+ renderCloseButton () {
1081
+ return (
1082
+ <CloseButton
1083
+ color="primary-inverse"
1084
+ placement="end"
1085
+ offset="small"
1086
+ onClick={this.handleButtonClick}
1087
+ screenReaderLabel="Close"
1088
+ />
1089
+ )
1090
+ }
1091
+
1092
+ render () {
1093
+ return (
1094
+ <div style={{ padding: '0 0 11rem 0', margin: '0 auto' }}>
1095
+ <Button onClick={this.handleButtonClick}>
1096
+ {this.state.open ? 'Close' : 'Open'} the Modal
1097
+ </Button>
1098
+ <Modal
1099
+ as="form"
1100
+ open={this.state.open}
1101
+ onDismiss={() => { this.setState({ open: false }) }}
1102
+ size="large"
1103
+ label="Modal Dialog: Hello World"
1104
+ shouldCloseOnDocumentClick
1105
+ variant='inverse'
1106
+ overflow='scroll'
1107
+ >
1108
+ <Modal.Header>
1109
+ {this.renderCloseButton()}
1110
+ <Heading>This is a Modal with a Modal.Body wrapped in to a HOC</Heading>
1111
+ </Modal.Header>
1112
+ <WrappedModalBody>
1113
+ <Heading level='h3'>WrappedModalBody inherits the variant and overflow properties automatically</Heading>
1114
+ <Text lineHeight="double">{lorem.paragraphs(5)}</Text>
1115
+ </WrappedModalBody>
1116
+ <View
1117
+ as="div"
1118
+ margin="small"
1119
+ padding="large"
1120
+ background="primary">
1121
+ <Heading level='h3'>This View child does not inherit the variant and overflow properties</Heading>
1122
+ <Text>{lorem.paragraphs(5)}</Text>
1123
+ </View>
1124
+ </Modal>
1125
+ </div>
1126
+ )
1127
+ }
1128
+ }
1129
+
1130
+ const withLogger = (WrappedComponent) => {
1131
+ class WithLogger extends React.Component {
1132
+ componentDidMount() {
1133
+ console.log('WrappedModelBody mounted');
1134
+ }
1135
+ render() {
1136
+ return <WrappedComponent {...this.props} />;
1137
+ }
1138
+ }
1139
+
1140
+ return WithLogger;
1141
+ }
1142
+
1143
+ const WrappedModalBody = withLogger(Modal.Body)
1144
+
1145
+ render(<Example />)
1146
+ ```
1147
+
1054
1148
  ### Guidelines
1055
1149
 
1056
1150
  ```js