newspack-components 4.6.3 → 4.7.0-alpha.2

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.
Files changed (148) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/colors/colors.module.scss +13 -7
  3. package/dist/cjs/autocomplete-tokenfield/style.scss +4 -2
  4. package/dist/cjs/breadcrumbs/format-count.js +33 -0
  5. package/dist/cjs/breadcrumbs/index.js +78 -20
  6. package/dist/cjs/breadcrumbs/index.test.js +110 -0
  7. package/dist/cjs/breadcrumbs/style.scss +6 -0
  8. package/dist/cjs/button/index.js +41 -17
  9. package/dist/cjs/button/index.test.js +273 -0
  10. package/dist/cjs/card/core-card.js +9 -4
  11. package/dist/cjs/card/core-card.test.js +29 -0
  12. package/dist/cjs/card/index.js +4 -2
  13. package/dist/cjs/card/style-core.scss +59 -0
  14. package/dist/cjs/card-feature/index.js +7 -1
  15. package/dist/cjs/card-feature/index.test.js +92 -0
  16. package/dist/cjs/color-picker/index.js +1 -1
  17. package/dist/cjs/confirm-dialog/index.js +26 -9
  18. package/dist/cjs/confirm-dialog/index.test.js +176 -0
  19. package/dist/cjs/dataviews/index.js +11 -2
  20. package/dist/cjs/divider/index.js +13 -0
  21. package/dist/cjs/drawer/action.js +74 -0
  22. package/dist/cjs/drawer/body.js +79 -0
  23. package/dist/cjs/drawer/close-icon.js +45 -0
  24. package/dist/cjs/drawer/content.js +72 -0
  25. package/dist/cjs/drawer/context.js +23 -0
  26. package/dist/cjs/drawer/context.test.js +43 -0
  27. package/dist/cjs/drawer/divider.js +24 -0
  28. package/dist/cjs/drawer/footer.js +26 -0
  29. package/dist/cjs/drawer/header.js +26 -0
  30. package/dist/cjs/drawer/index.js +42 -0
  31. package/dist/cjs/drawer/index.test.js +1287 -0
  32. package/dist/cjs/drawer/root.js +350 -0
  33. package/dist/cjs/drawer/slots.test.js +422 -0
  34. package/dist/cjs/drawer/style.scss +205 -0
  35. package/dist/cjs/drawer/title.js +54 -0
  36. package/dist/cjs/drawer/types.js +5 -0
  37. package/dist/cjs/drawer/use-exit-animation.js +59 -0
  38. package/dist/cjs/hooks/use-confirm-dialog.js +15 -3
  39. package/dist/cjs/hooks/use-unsaved-changes-dialog.js +19 -4
  40. package/dist/cjs/index.js +28 -1
  41. package/dist/cjs/page/index.js +1 -1
  42. package/dist/cjs/page-control/index.js +82 -0
  43. package/dist/cjs/plugin-settings/SettingsSection.js +7 -1
  44. package/dist/cjs/section-header/index.js +5 -2
  45. package/dist/cjs/section-header/style.scss +25 -12
  46. package/dist/cjs/table-card/index.js +67 -0
  47. package/dist/cjs/table-card/index.test.js +153 -0
  48. package/dist/cjs/with-wizard-screen/style.scss +8 -0
  49. package/dist/cjs/wizard/index.js +102 -38
  50. package/dist/esm/autocomplete-tokenfield/style.scss +4 -2
  51. package/dist/esm/breadcrumbs/format-count.js +27 -0
  52. package/dist/esm/breadcrumbs/index.js +81 -20
  53. package/dist/esm/breadcrumbs/index.test.js +110 -0
  54. package/dist/esm/breadcrumbs/style.scss +6 -0
  55. package/dist/esm/button/index.js +42 -18
  56. package/dist/esm/button/index.test.js +273 -0
  57. package/dist/esm/card/core-card.js +9 -4
  58. package/dist/esm/card/core-card.test.js +29 -0
  59. package/dist/esm/card/index.js +4 -2
  60. package/dist/esm/card/style-core.scss +59 -0
  61. package/dist/esm/card-feature/index.js +8 -2
  62. package/dist/esm/card-feature/index.test.js +96 -0
  63. package/dist/esm/color-picker/index.js +1 -1
  64. package/dist/esm/confirm-dialog/index.js +26 -9
  65. package/dist/esm/confirm-dialog/index.test.js +176 -0
  66. package/dist/esm/dataviews/index.js +11 -2
  67. package/dist/esm/divider/index.js +14 -0
  68. package/dist/esm/drawer/action.js +68 -0
  69. package/dist/esm/drawer/body.js +70 -0
  70. package/dist/esm/drawer/close-icon.js +37 -0
  71. package/dist/esm/drawer/content.js +63 -0
  72. package/dist/esm/drawer/context.js +17 -0
  73. package/dist/esm/drawer/context.test.js +40 -0
  74. package/dist/esm/drawer/divider.js +16 -0
  75. package/dist/esm/drawer/footer.js +18 -0
  76. package/dist/esm/drawer/header.js +18 -0
  77. package/dist/esm/drawer/index.js +35 -0
  78. package/dist/esm/drawer/index.test.js +1285 -0
  79. package/dist/esm/drawer/root.js +342 -0
  80. package/dist/esm/drawer/slots.test.js +422 -0
  81. package/dist/esm/drawer/style.scss +205 -0
  82. package/dist/esm/drawer/title.js +46 -0
  83. package/dist/esm/drawer/types.js +1 -0
  84. package/dist/esm/drawer/use-exit-animation.js +52 -0
  85. package/dist/esm/hooks/use-confirm-dialog.js +15 -3
  86. package/dist/esm/hooks/use-unsaved-changes-dialog.js +20 -5
  87. package/dist/esm/index.js +4 -0
  88. package/dist/esm/page/index.js +1 -1
  89. package/dist/esm/page-control/index.js +82 -0
  90. package/dist/esm/plugin-settings/SettingsSection.js +8 -2
  91. package/dist/esm/section-header/index.js +5 -2
  92. package/dist/esm/section-header/style.scss +25 -12
  93. package/dist/esm/table-card/index.js +60 -0
  94. package/dist/esm/table-card/index.test.js +150 -0
  95. package/dist/esm/with-wizard-screen/style.scss +8 -0
  96. package/dist/esm/wizard/index.js +104 -38
  97. package/package.json +2 -2
  98. package/src/autocomplete-tokenfield/style.scss +4 -2
  99. package/src/breadcrumbs/format-count.js +27 -0
  100. package/src/breadcrumbs/index.js +68 -8
  101. package/src/breadcrumbs/index.test.js +57 -0
  102. package/src/breadcrumbs/style.scss +6 -0
  103. package/src/button/index.test.js +143 -0
  104. package/src/button/index.tsx +32 -7
  105. package/src/card/core-card.js +15 -2
  106. package/src/card/core-card.test.js +19 -0
  107. package/src/card/index.js +3 -0
  108. package/src/card/style-core.scss +59 -0
  109. package/src/card-feature/README.md +4 -0
  110. package/src/card-feature/index.test.js +65 -0
  111. package/src/card-feature/index.tsx +13 -2
  112. package/src/color-picker/index.js +1 -1
  113. package/src/confirm-dialog/README.md +3 -2
  114. package/src/confirm-dialog/index.test.js +146 -0
  115. package/src/confirm-dialog/index.tsx +23 -9
  116. package/src/dataviews/index.tsx +11 -2
  117. package/src/divider/index.js +13 -0
  118. package/src/drawer/README.md +454 -0
  119. package/src/drawer/action.tsx +67 -0
  120. package/src/drawer/body.tsx +60 -0
  121. package/src/drawer/close-icon.tsx +37 -0
  122. package/src/drawer/content.tsx +54 -0
  123. package/src/drawer/context.test.js +31 -0
  124. package/src/drawer/context.ts +25 -0
  125. package/src/drawer/divider.tsx +13 -0
  126. package/src/drawer/footer.tsx +15 -0
  127. package/src/drawer/header.tsx +15 -0
  128. package/src/drawer/index.test.js +1012 -0
  129. package/src/drawer/index.tsx +33 -0
  130. package/src/drawer/root.tsx +328 -0
  131. package/src/drawer/slots.test.js +314 -0
  132. package/src/drawer/style.scss +205 -0
  133. package/src/drawer/title.tsx +40 -0
  134. package/src/drawer/types.ts +79 -0
  135. package/src/drawer/use-exit-animation.ts +46 -0
  136. package/src/hooks/use-confirm-dialog.tsx +14 -3
  137. package/src/hooks/use-unsaved-changes-dialog.tsx +17 -5
  138. package/src/index.js +4 -0
  139. package/src/page/index.js +1 -1
  140. package/src/page-control/index.js +63 -0
  141. package/src/plugin-settings/SettingsSection.js +8 -2
  142. package/src/section-header/index.js +5 -2
  143. package/src/section-header/style.scss +25 -12
  144. package/src/table-card/README.md +100 -0
  145. package/src/table-card/index.test.js +112 -0
  146. package/src/table-card/index.tsx +56 -0
  147. package/src/with-wizard-screen/style.scss +8 -0
  148. package/src/wizard/index.js +95 -34
@@ -65,6 +65,34 @@ describe('ConfirmDialog navigation blocking', function () {
65
65
  expect(replaceSpy).toHaveBeenCalledTimes(1);
66
66
  expect(history.location.pathname).toBe('/');
67
67
  });
68
+
69
+ // v5 keeps a single prompt slot, so an unprompted re-sync would be caught by
70
+ // whichever dialog on the screen did install a blocker.
71
+ it('cancels an unprompted dialog without tripping another blocker', function () {
72
+ var historyRef = {
73
+ current: null
74
+ };
75
+ render(/*#__PURE__*/_jsxs(MemoryRouter, {
76
+ children: [/*#__PURE__*/_jsx(HistoryGrabber, {
77
+ historyRef: historyRef
78
+ }), /*#__PURE__*/_jsx(ConfirmDialog, {
79
+ when: true,
80
+ confirmButtonText: "Leave",
81
+ cancelButtonText: "Stay",
82
+ children: "Wizard guard"
83
+ }), /*#__PURE__*/_jsx(ConfirmDialog, {
84
+ isOpen: true,
85
+ confirmButtonText: "Discard changes",
86
+ cancelButtonText: "Keep editing",
87
+ children: "Unsaved changes"
88
+ })]
89
+ }));
90
+ var replaceSpy = jest.spyOn(historyRef.current, 'replace');
91
+ cancelNavigation();
92
+ expect(dialog()).not.toBeInTheDocument();
93
+ expect(screen.queryByText('Wizard guard')).not.toBeInTheDocument();
94
+ expect(replaceSpy).not.toHaveBeenCalled();
95
+ });
68
96
  it('replays a blocked push on confirm: re-sync replace first, then push, and re-arms the blocker', function () {
69
97
  var history = renderWithHistory();
70
98
  var replaceSpy = jest.spyOn(history, 'replace');
@@ -170,4 +198,152 @@ describe('ConfirmDialog navigation blocking', function () {
170
198
  expect(history.location.pathname).toBe('/');
171
199
  expect(dialog()).toBeInTheDocument();
172
200
  });
201
+ });
202
+
203
+ // `useHistory` returns undefined outside a wizard, where `newspack-multibranded-site`
204
+ // runs react-router v6 against this package's v5.
205
+ describe('ConfirmDialog without a router', function () {
206
+ var renderUnrouted = function renderUnrouted() {
207
+ var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
208
+ return render(/*#__PURE__*/_jsx(ConfirmDialog, _objectSpread(_objectSpread({
209
+ isOpen: true,
210
+ confirmButtonText: "Discard changes",
211
+ cancelButtonText: "Keep editing"
212
+ }, props), {}, {
213
+ children: "Unsaved changes"
214
+ })));
215
+ };
216
+ it('cancels with no history to re-sync', function () {
217
+ var onCancel = jest.fn();
218
+ renderUnrouted({
219
+ onCancel: onCancel
220
+ });
221
+ expect(dialog()).toBeInTheDocument();
222
+ cancelNavigation();
223
+ expect(onCancel).toHaveBeenCalled();
224
+ expect(dialog()).not.toBeInTheDocument();
225
+ });
226
+ it('confirms with no history to re-sync', function () {
227
+ var onConfirm = jest.fn();
228
+ renderUnrouted({
229
+ onConfirm: onConfirm
230
+ });
231
+ confirmNavigation();
232
+ expect(onConfirm).toHaveBeenCalled();
233
+ expect(dialog()).not.toBeInTheDocument();
234
+ });
235
+ it('installs no blocker when asked to block without a router', function () {
236
+ expect(function () {
237
+ return renderUnrouted({
238
+ when: true
239
+ });
240
+ }).not.toThrow();
241
+ expect(dialog()).toBeInTheDocument();
242
+ });
243
+ });
244
+ describe('ConfirmDialog controlled by isOpen', function () {
245
+ var tree = function tree(isOpen) {
246
+ return /*#__PURE__*/_jsx(ConfirmDialog, {
247
+ isOpen: isOpen,
248
+ confirmButtonText: "Discard changes",
249
+ cancelButtonText: "Keep editing",
250
+ children: "Unsaved changes"
251
+ });
252
+ };
253
+ it('closes when isOpen goes false', function () {
254
+ var _render = render(tree(true)),
255
+ rerender = _render.rerender;
256
+ expect(dialog()).toBeInTheDocument();
257
+ rerender(tree(false));
258
+ expect(dialog()).not.toBeInTheDocument();
259
+ });
260
+ it('opens again afterwards', function () {
261
+ var _render2 = render(tree(true)),
262
+ rerender = _render2.rerender;
263
+ rerender(tree(false));
264
+ rerender(tree(true));
265
+ expect(dialog()).toBeInTheDocument();
266
+ });
267
+ it('leaves a blocked navigation prompt up', function () {
268
+ var historyRef = {
269
+ current: null
270
+ };
271
+ render(/*#__PURE__*/_jsxs(MemoryRouter, {
272
+ children: [/*#__PURE__*/_jsx(HistoryGrabber, {
273
+ historyRef: historyRef
274
+ }), /*#__PURE__*/_jsx(ConfirmDialog, {
275
+ when: true,
276
+ isOpen: false,
277
+ confirmButtonText: "Discard changes",
278
+ cancelButtonText: "Keep editing",
279
+ children: "Unsaved changes"
280
+ })]
281
+ }));
282
+ act(function () {
283
+ return historyRef.current.push('/next');
284
+ });
285
+ expect(dialog()).toBeInTheDocument();
286
+ });
287
+ it('withdraws a blocked navigation prompt when isOpen goes false', function () {
288
+ var historyRef = {
289
+ current: null
290
+ };
291
+ var withOpen = function withOpen(isOpen) {
292
+ return /*#__PURE__*/_jsxs(MemoryRouter, {
293
+ children: [/*#__PURE__*/_jsx(HistoryGrabber, {
294
+ historyRef: historyRef
295
+ }), /*#__PURE__*/_jsx(ConfirmDialog, {
296
+ when: true,
297
+ isOpen: isOpen,
298
+ confirmButtonText: "Discard changes",
299
+ cancelButtonText: "Keep editing",
300
+ children: "Unsaved changes"
301
+ })]
302
+ });
303
+ };
304
+ var _render3 = render(withOpen(true)),
305
+ rerender = _render3.rerender;
306
+ act(function () {
307
+ return historyRef.current.push('/next');
308
+ });
309
+ expect(dialog()).toBeInTheDocument();
310
+ rerender(withOpen(false));
311
+ expect(dialog()).not.toBeInTheDocument();
312
+ expect(historyRef.current.location.pathname).toBe('/');
313
+
314
+ // Confirming the next prompt must not replay the abandoned destination.
315
+ rerender(withOpen(true));
316
+ confirmNavigation();
317
+ expect(historyRef.current.location.pathname).toBe('/');
318
+ });
319
+ });
320
+ describe('ConfirmDialog named by a hidden title', function () {
321
+ it('takes its accessible name from the title while hiding the header', function () {
322
+ render(/*#__PURE__*/_jsx(ConfirmDialog, {
323
+ isOpen: true,
324
+ hideTitle: true,
325
+ title: "Unsaved changes",
326
+ confirmButtonText: "Discard changes",
327
+ children: "You have unsaved changes that will be lost. Discard changes?"
328
+ }));
329
+ var frame = screen.getByRole('dialog', {
330
+ name: 'Unsaved changes'
331
+ });
332
+ expect(frame).toHaveClass('newspack-modal--hide-title');
333
+ expect(frame.querySelector('.components-modal__header')).toContainElement(screen.getByRole('heading', {
334
+ name: 'Unsaved changes'
335
+ }));
336
+ });
337
+ it('has no accessible name without a title', function () {
338
+ render(/*#__PURE__*/_jsx(ConfirmDialog, {
339
+ isOpen: true,
340
+ hideTitle: true,
341
+ confirmButtonText: "Discard changes",
342
+ children: "You have unsaved changes that will be lost. Discard changes?"
343
+ }));
344
+ expect(screen.queryByRole('dialog', {
345
+ name: 'Unsaved changes'
346
+ })).not.toBeInTheDocument();
347
+ expect(screen.getByRole('dialog')).not.toHaveAttribute('aria-labelledby');
348
+ });
173
349
  });
@@ -24,11 +24,20 @@ import classnames from 'classnames';
24
24
  */
25
25
  import './style.scss';
26
26
  import { jsx as _jsx } from "react/jsx-runtime";
27
- export default function DataViews(_ref) {
27
+ function DataViews(_ref) {
28
28
  var className = _ref.className,
29
29
  props = _objectWithoutProperties(_ref, _excluded);
30
30
  return /*#__PURE__*/_jsx("div", {
31
31
  className: classnames('newspack-dataviews', className),
32
32
  children: /*#__PURE__*/_jsx(BaseDataViews, _objectSpread({}, props))
33
33
  });
34
- }
34
+ }
35
+
36
+ // The implementation is typed against the `unknown` instantiation because
37
+ // BaseDataViews' conditionally-optional `getItemId` prop cannot be related
38
+ // to a JSX spread while `Item` is an unresolved type parameter. The export
39
+ // re-types it with the generic signature so call sites get item-aware
40
+ // typing for `data`, `fields`, `actions`, `getItemId`, etc. The assertion
41
+ // is verified by the compiler: the generic signature is assignable to the
42
+ // implementation's concrete one.
43
+ export default DataViews;
@@ -16,6 +16,20 @@ import './style.scss';
16
16
  * External dependencies
17
17
  */
18
18
  import classNames from 'classnames';
19
+
20
+ /**
21
+ * Divider component.
22
+ *
23
+ * Every prop beyond the documented ones is forwarded to the `hr` element.
24
+ *
25
+ * @param {import('react').ComponentPropsWithoutRef<'hr'> & {
26
+ * alignment?: string,
27
+ * marginBottom?: number|string,
28
+ * marginTop?: number|string,
29
+ * variant?: string,
30
+ * }} props - Component props: the documented options plus any `hr` attribute.
31
+ * @return {JSX.Element} Divider component.
32
+ */
19
33
  import { jsx as _jsx } from "react/jsx-runtime";
20
34
  var Divider = function Divider(_ref) {
21
35
  var _ref$alignment = _ref.alignment,
@@ -0,0 +1,68 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
3
+ var _excluded = ["ariaLabel", "closes", "onClick", "href", "children"];
4
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
5
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
6
+ /**
7
+ * WordPress dependencies.
8
+ */
9
+ import { useContext, useEffect } from '@wordpress/element';
10
+
11
+ /**
12
+ * Internal dependencies.
13
+ */
14
+ import Button from '../button';
15
+ import { DrawerContext } from './context';
16
+ import { jsx as _jsx } from "react/jsx-runtime";
17
+ var Action = function Action(_ref) {
18
+ var ariaLabel = _ref.ariaLabel,
19
+ _ref$closes = _ref.closes,
20
+ closes = _ref$closes === void 0 ? false : _ref$closes,
21
+ onClick = _ref.onClick,
22
+ href = _ref.href,
23
+ children = _ref.children,
24
+ buttonProps = _objectWithoutProperties(_ref, _excluded);
25
+ // Not useDrawerContext: only `closes` requires the context.
26
+ var context = useContext(DrawerContext);
27
+
28
+ // `href=""` is still an anchor; a null from a JS caller means no link at all.
29
+ var hasHref = 'string' === typeof href;
30
+ useEffect(function () {
31
+ if ('production' === process.env.NODE_ENV || !hasHref) {
32
+ return;
33
+ }
34
+ if (onClick || closes) {
35
+ // eslint-disable-next-line no-console
36
+ console.warn('Drawer: an action with `href` cannot also take `onClick` or `closes`, and they are ignored. ' + 'A link that ran the close funnel would navigate away while the unsaved-changes confirmation was still opening.');
37
+ }
38
+ }, [hasHref, onClick, closes]);
39
+ useEffect(function () {
40
+ if ('production' === process.env.NODE_ENV) {
41
+ return;
42
+ }
43
+ if (typeof children === 'string' && ariaLabel && !ariaLabel.toLowerCase().includes(children.toLowerCase())) {
44
+ // eslint-disable-next-line no-console
45
+ console.warn("Drawer: action ariaLabel \"".concat(ariaLabel, "\" does not contain its visible label \"").concat(children, "\". ") + 'Voice control matches spoken commands against the accessible name (WCAG 2.5.3, Label in Name), ' + 'so ariaLabel must extend the visible text rather than replace it.');
46
+ }
47
+ }, [ariaLabel, children]);
48
+ var handleClick = function handleClick(event) {
49
+ onClick === null || onClick === void 0 || onClick(event);
50
+ if (closes) {
51
+ if (!context) {
52
+ throw new Error('Drawer subcomponents must be rendered inside Drawer.Root.');
53
+ }
54
+ context.requestClose();
55
+ }
56
+ };
57
+
58
+ // Button swallows `href` whenever an onClick is present, so a link supplies none.
59
+ var handlesClick = !hasHref && (!!onClick || closes);
60
+ return /*#__PURE__*/_jsx(Button, _objectSpread(_objectSpread({
61
+ "aria-label": ariaLabel,
62
+ href: hasHref ? href : undefined,
63
+ onClick: handlesClick ? handleClick : undefined
64
+ }, buttonProps), {}, {
65
+ children: children
66
+ }));
67
+ };
68
+ export default Action;
@@ -0,0 +1,70 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
+ /**
3
+ * WordPress dependencies.
4
+ */
5
+ import { __experimentalVStack as VStack } from '@wordpress/components'; // eslint-disable-line @wordpress/no-unsafe-wp-apis
6
+ import { useEffect, useState } from '@wordpress/element';
7
+ import { __ } from '@wordpress/i18n';
8
+
9
+ /**
10
+ * Internal dependencies.
11
+ */
12
+ import { jsx as _jsx } from "react/jsx-runtime";
13
+ // Core tab-stops its own container once it overflows; the drawer scrolls here.
14
+ var Body = function Body(_ref) {
15
+ var children = _ref.children;
16
+ var _useState = useState(null),
17
+ _useState2 = _slicedToArray(_useState, 2),
18
+ node = _useState2[0],
19
+ setNode = _useState2[1];
20
+ var _useState3 = useState(false),
21
+ _useState4 = _slicedToArray(_useState3, 2),
22
+ isScrollable = _useState4[0],
23
+ setIsScrollable = _useState4[1];
24
+ useEffect(function () {
25
+ if (!node) {
26
+ return;
27
+ }
28
+ var measure = function measure() {
29
+ return setIsScrollable(node.scrollHeight > node.clientHeight);
30
+ };
31
+ measure();
32
+ if (!window.ResizeObserver) {
33
+ return;
34
+ }
35
+ var resize = new ResizeObserver(measure);
36
+ var observeChildren = function observeChildren() {
37
+ resize.disconnect();
38
+ resize.observe(node);
39
+ // Border box: a section's padding rides on an inline custom property.
40
+ Array.from(node.children).forEach(function (child) {
41
+ return resize.observe(child, {
42
+ box: 'border-box'
43
+ });
44
+ });
45
+ measure();
46
+ };
47
+ observeChildren();
48
+ var mutation = new MutationObserver(observeChildren);
49
+ mutation.observe(node, {
50
+ childList: true
51
+ });
52
+ return function () {
53
+ resize.disconnect();
54
+ mutation.disconnect();
55
+ };
56
+ }, [node]);
57
+ return /*#__PURE__*/_jsx(VStack, {
58
+ ref: setNode,
59
+ className: "newspack-drawer__body",
60
+ spacing: 0,
61
+ justify: "flex-start"
62
+ // A bare div is role=generic, where ARIA prohibits an author name.
63
+ ,
64
+ role: isScrollable ? 'group' : undefined,
65
+ tabIndex: isScrollable ? 0 : undefined,
66
+ "aria-label": isScrollable ? __('Scrollable section', 'newspack-plugin') : undefined,
67
+ children: children
68
+ });
69
+ };
70
+ export default Body;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * WordPress dependencies.
3
+ */
4
+ import { close } from '@wordpress/icons';
5
+ import { __, sprintf } from '@wordpress/i18n';
6
+
7
+ /**
8
+ * External dependencies.
9
+ */
10
+ import classnames from 'classnames';
11
+
12
+ /**
13
+ * Internal dependencies.
14
+ */
15
+ import Button from '../button';
16
+ import { useDrawerContext } from './context';
17
+ import { jsx as _jsx } from "react/jsx-runtime";
18
+ var CloseIcon = function CloseIcon(_ref) {
19
+ var label = _ref.label,
20
+ _ref$icon = _ref.icon,
21
+ icon = _ref$icon === void 0 ? close : _ref$icon,
22
+ className = _ref.className;
23
+ var _useDrawerContext = useDrawerContext(),
24
+ requestClose = _useDrawerContext.requestClose,
25
+ title = _useDrawerContext.title;
26
+ var defaultLabel = title !== null && title !== void 0 && title.text ?
27
+ // translators: %s: the drawer's title.
28
+ sprintf(__('Close %s', 'newspack-plugin'), title.text) : __('Close', 'newspack-plugin');
29
+ return /*#__PURE__*/_jsx(Button, {
30
+ className: classnames('newspack-drawer__dismiss', className),
31
+ icon: icon,
32
+ size: "small",
33
+ label: label || defaultLabel,
34
+ onClick: requestClose
35
+ });
36
+ };
37
+ export default CloseIcon;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * WordPress dependencies.
3
+ */
4
+ import { __experimentalVStack as VStack } from '@wordpress/components'; // eslint-disable-line @wordpress/no-unsafe-wp-apis
5
+ import { Children, isValidElement } from '@wordpress/element';
6
+
7
+ /**
8
+ * External dependencies.
9
+ */
10
+ import classnames from 'classnames';
11
+
12
+ /**
13
+ * Internal dependencies.
14
+ */
15
+ import { jsx as _jsx } from "react/jsx-runtime";
16
+ // VStack drops non-elements, a lone string aside. A run shares one wrapper so
17
+ // `Edited by { name }` is one row, not three.
18
+ var asRows = function asRows(children) {
19
+ if ('string' === typeof children) {
20
+ return children;
21
+ }
22
+ var rows = [];
23
+ var text = [];
24
+ var flushText = function flushText() {
25
+ if (text.some(function (part) {
26
+ return '' !== String(part).trim();
27
+ })) {
28
+ rows.push(/*#__PURE__*/_jsx("div", {
29
+ children: text
30
+ }, "text-".concat(rows.length)));
31
+ }
32
+ text = [];
33
+ };
34
+ Children.toArray(children).forEach(function (child) {
35
+ if (isValidElement(child)) {
36
+ flushText();
37
+ rows.push(child);
38
+ } else {
39
+ text.push(child);
40
+ }
41
+ });
42
+ flushText();
43
+ return rows;
44
+ };
45
+ var Content = function Content(_ref) {
46
+ var _ref$padding = _ref.padding,
47
+ padding = _ref$padding === void 0 ? 6 : _ref$padding,
48
+ _ref$gap = _ref.gap,
49
+ gap = _ref$gap === void 0 ? 4 : _ref$gap,
50
+ className = _ref.className,
51
+ children = _ref.children;
52
+ return /*#__PURE__*/_jsx(VStack, {
53
+ className: classnames('newspack-drawer__content', className),
54
+ spacing: gap
55
+ // A custom property, not inline padding, so the stylesheet's seam rules win.
56
+ ,
57
+ style: {
58
+ '--newspack-drawer-content-padding': "".concat(padding * 4, "px")
59
+ },
60
+ children: asRows(children)
61
+ });
62
+ };
63
+ export default Content;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * WordPress dependencies.
3
+ */
4
+ import { createContext, useContext } from '@wordpress/element';
5
+
6
+ /**
7
+ * Internal dependencies.
8
+ */
9
+
10
+ export var DrawerContext = createContext(null);
11
+ export var useDrawerContext = function useDrawerContext() {
12
+ var context = useContext(DrawerContext);
13
+ if (!context) {
14
+ throw new Error('Drawer subcomponents must be rendered inside Drawer.Root.');
15
+ }
16
+ return context;
17
+ };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Internal dependencies.
3
+ */
4
+ import { DrawerContext, useDrawerContext } from './context';
5
+
6
+ /**
7
+ * External dependencies.
8
+ */
9
+ import { render, screen } from '@testing-library/react';
10
+ import { jsx as _jsx } from "react/jsx-runtime";
11
+ var Probe = function Probe() {
12
+ var _useDrawerContext = useDrawerContext(),
13
+ title = _useDrawerContext.title;
14
+ return /*#__PURE__*/_jsx("span", {
15
+ children: title ? title.text : 'no title'
16
+ });
17
+ };
18
+ describe('useDrawerContext', function () {
19
+ it('returns the provided value', function () {
20
+ render(/*#__PURE__*/_jsx(DrawerContext.Provider, {
21
+ value: {
22
+ requestClose: function requestClose() {},
23
+ title: {
24
+ id: 't1',
25
+ text: 'Hello'
26
+ },
27
+ setTitle: function setTitle() {}
28
+ },
29
+ children: /*#__PURE__*/_jsx(Probe, {})
30
+ }));
31
+ expect(screen.getByText('Hello')).toBeInTheDocument();
32
+ });
33
+ it('throws outside Drawer.Root', function () {
34
+ var consoleError = jest.spyOn(console, 'error').mockImplementation(function () {});
35
+ expect(function () {
36
+ return render(/*#__PURE__*/_jsx(Probe, {}));
37
+ }).toThrow('Drawer subcomponents must be rendered inside Drawer.Root.');
38
+ consoleError.mockRestore();
39
+ });
40
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * External dependencies.
3
+ */
4
+ import classnames from 'classnames';
5
+
6
+ /**
7
+ * Internal dependencies.
8
+ */
9
+ import { jsx as _jsx } from "react/jsx-runtime";
10
+ var Divider = function Divider(_ref) {
11
+ var className = _ref.className;
12
+ return /*#__PURE__*/_jsx("hr", {
13
+ className: classnames('newspack-drawer__divider', className)
14
+ });
15
+ };
16
+ export default Divider;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * External dependencies.
3
+ */
4
+ import classnames from 'classnames';
5
+
6
+ /**
7
+ * Internal dependencies.
8
+ */
9
+ import { jsx as _jsx } from "react/jsx-runtime";
10
+ var Footer = function Footer(_ref) {
11
+ var className = _ref.className,
12
+ children = _ref.children;
13
+ return /*#__PURE__*/_jsx("div", {
14
+ className: classnames('newspack-drawer__footer', className),
15
+ children: children
16
+ });
17
+ };
18
+ export default Footer;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * External dependencies.
3
+ */
4
+ import classnames from 'classnames';
5
+
6
+ /**
7
+ * Internal dependencies.
8
+ */
9
+ import { jsx as _jsx } from "react/jsx-runtime";
10
+ var Header = function Header(_ref) {
11
+ var className = _ref.className,
12
+ children = _ref.children;
13
+ return /*#__PURE__*/_jsx("div", {
14
+ className: classnames('newspack-drawer__header', className),
15
+ children: children
16
+ });
17
+ };
18
+ export default Header;
@@ -0,0 +1,35 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
+ /**
3
+ * Drawer
4
+ */
5
+
6
+ /**
7
+ * Internal dependencies.
8
+ */
9
+ import Action from './action';
10
+ import CloseIcon from './close-icon';
11
+ import Content from './content';
12
+ import Footer from './footer';
13
+ import Header from './header';
14
+ import Root from './root';
15
+ import Divider from './divider';
16
+ import Title from './title';
17
+
18
+ // A namespace object, not this package's usual flat exports. See the README.
19
+ export var Drawer = {
20
+ Root: Root,
21
+ Header: Header,
22
+ Title: Title,
23
+ CloseIcon: CloseIcon,
24
+ Content: Content,
25
+ Divider: Divider,
26
+ Footer: Footer,
27
+ Action: Action
28
+ };
29
+ Object.entries(Drawer).forEach(function (_ref) {
30
+ var _ref2 = _slicedToArray(_ref, 2),
31
+ name = _ref2[0],
32
+ part = _ref2[1];
33
+ part.displayName = "Drawer.".concat(name);
34
+ });
35
+ export default Drawer;