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
@@ -0,0 +1,52 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
+ /**
3
+ * WordPress dependencies.
4
+ */
5
+ import { useReducedMotion } from '@wordpress/compose';
6
+ import { useEffect, useState } from '@wordpress/element';
7
+
8
+ // Covers an animationend that never arrives.
9
+ var EXIT_FALLBACK_MS = 400;
10
+ function useExitAnimation(isOpen, node) {
11
+ var _useState = useState(false),
12
+ _useState2 = _slicedToArray(_useState, 2),
13
+ isExiting = _useState2[0],
14
+ setIsExiting = _useState2[1];
15
+ var _useState3 = useState(isOpen),
16
+ _useState4 = _slicedToArray(_useState3, 2),
17
+ wasOpen = _useState4[0],
18
+ setWasOpen = _useState4[1];
19
+
20
+ // The stylesheet zeroes the animation here, so animationend never fires and the
21
+ // fallback would hold a visible panel for 400ms.
22
+ var prefersReducedMotion = useReducedMotion();
23
+
24
+ // During render: a commit later the panel would leave the DOM and remount.
25
+ if (wasOpen !== isOpen) {
26
+ setWasOpen(isOpen);
27
+ setIsExiting(!isOpen && !prefersReducedMotion);
28
+ }
29
+ useEffect(function () {
30
+ if (!isExiting) {
31
+ return;
32
+ }
33
+ // Keyed on the name: animationend bubbles from anything else animating.
34
+ var finish = function finish(event) {
35
+ if (event !== null && event !== void 0 && event.animationName && 'newspack-drawer-slide-out' !== event.animationName) {
36
+ return;
37
+ }
38
+ setIsExiting(false);
39
+ };
40
+ var timer = setTimeout(finish, EXIT_FALLBACK_MS);
41
+ node === null || node === void 0 || node.addEventListener('animationend', finish);
42
+ return function () {
43
+ clearTimeout(timer);
44
+ node === null || node === void 0 || node.removeEventListener('animationend', finish);
45
+ };
46
+ }, [isExiting, node]);
47
+ return {
48
+ isRendered: isOpen || isExiting,
49
+ isExiting: isExiting
50
+ };
51
+ }
52
+ export default useExitAnimation;
@@ -29,6 +29,9 @@ import { jsx as _jsx } from "react/jsx-runtime";
29
29
  *
30
30
  * The `confirmDialog` element must be rendered somewhere in the component's
31
31
  * JSX for the dialog to appear.
32
+ *
33
+ * `cancelConfirm()` withdraws a pending request without invoking its callback,
34
+ * for when whatever raised the prompt goes away on its own.
32
35
  */
33
36
  function useConfirmDialog(options) {
34
37
  var message = options.message,
@@ -53,12 +56,20 @@ function useConfirmDialog(options) {
53
56
  callback();
54
57
  }
55
58
  }, []);
59
+ var cancelConfirm = useCallback(function () {
60
+ return setPendingAction(null);
61
+ }, []);
56
62
  var confirmDialog = /*#__PURE__*/_jsx(ConfirmDialog, _objectSpread(_objectSpread({}, dialogProps), {}, {
57
63
  when: when,
58
64
  isOpen: !!pendingAction,
59
65
  onConfirm: function onConfirm() {
60
- pendingAction === null || pendingAction === void 0 || pendingAction();
61
- setPendingAction(null);
66
+ // Cleared either way: a throwing callback would otherwise leave the
67
+ // dialog permanently unraisable for this instance.
68
+ try {
69
+ pendingAction === null || pendingAction === void 0 || pendingAction();
70
+ } finally {
71
+ setPendingAction(null);
72
+ }
62
73
  },
63
74
  onCancel: function onCancel() {
64
75
  return setPendingAction(null);
@@ -67,7 +78,8 @@ function useConfirmDialog(options) {
67
78
  }));
68
79
  return {
69
80
  confirmDialog: confirmDialog,
70
- requestConfirm: requestConfirm
81
+ requestConfirm: requestConfirm,
82
+ cancelConfirm: cancelConfirm
71
83
  };
72
84
  }
73
85
  export default useConfirmDialog;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * WordPress dependencies.
3
3
  */
4
- import { useEffect, useRef } from '@wordpress/element';
4
+ import { useCallback, useEffect, useRef } from '@wordpress/element';
5
5
  import { __ } from '@wordpress/i18n';
6
6
 
7
7
  /**
@@ -34,7 +34,8 @@ function isSameOriginNavigation(link) {
34
34
  * messaging, intercepts same-origin link clicks so the dialog fires instead
35
35
  * of a silent navigation, and adds a `beforeunload` listener as the last-resort
36
36
  * guard for refresh / tab-close (browser-native, cannot be styled). The
37
- * returned `confirmDialog` element must be rendered in JSX.
37
+ * returned `confirmDialog` element must be rendered in JSX, and `requestConfirm`
38
+ * and `cancelConfirm` behave as `useConfirmDialog` documents them.
38
39
  *
39
40
  * Single-consumer constraint: the click handler is attached at the document
40
41
  * level in capture phase. Two simultaneously-active instances will both fire
@@ -45,11 +46,14 @@ function useUnsavedChangesDialog(_ref) {
45
46
  var _useConfirmDialog = useConfirmDialog({
46
47
  when: when,
47
48
  message: __('You have unsaved changes that will be lost. Discard changes?', 'newspack-plugin'),
48
- confirmButtonText: __('Discard changes', 'newspack-plugin'),
49
+ confirmButtonText: __('Discard Changes', 'newspack-plugin'),
50
+ // Hidden, but still the dialog's accessible name.
51
+ title: __('Unsaved changes', 'newspack-plugin'),
49
52
  hideTitle: true
50
53
  }),
51
54
  confirmDialog = _useConfirmDialog.confirmDialog,
52
- requestConfirm = _useConfirmDialog.requestConfirm;
55
+ requestConfirm = _useConfirmDialog.requestConfirm,
56
+ cancelConfirm = _useConfirmDialog.cancelConfirm;
53
57
 
54
58
  // Tracks navigation the user has already approved via our custom dialog so
55
59
  // the beforeunload guard doesn't fire a second native prompt on top of it.
@@ -128,9 +132,20 @@ function useUnsavedChangesDialog(_ref) {
128
132
  return window.removeEventListener('beforeunload', handler);
129
133
  };
130
134
  }, [when]);
135
+
136
+ // Escape hatch for an action that navigates on its own — a handoff that
137
+ // POSTs and then assigns `window.location` — once the dialog has already
138
+ // been confirmed. Left armed, the beforeunload guard would draw a second,
139
+ // native prompt on top of it. Call it immediately before navigating, so a
140
+ // failed action leaves the guard in place.
141
+ var allowNextUnload = useCallback(function () {
142
+ isNavigatingRef.current = true;
143
+ }, []);
131
144
  return {
132
145
  confirmDialog: confirmDialog,
133
- requestConfirm: requestConfirm
146
+ requestConfirm: requestConfirm,
147
+ cancelConfirm: cancelConfirm,
148
+ allowNextUnload: allowNextUnload
134
149
  };
135
150
  }
136
151
  export default useUnsavedChangesDialog;
package/dist/esm/index.js CHANGED
@@ -19,6 +19,7 @@ export { default as ConfirmDialog } from './confirm-dialog';
19
19
  export { default as CustomSelectControl } from './custom-select-control';
20
20
  export { default as DataViews } from './dataviews';
21
21
  export { default as Divider } from './divider';
22
+ export { default as Drawer } from './drawer';
22
23
  export { default as FormTokenField } from './form-token-field';
23
24
  export { default as Footer } from './footer';
24
25
  export { default as Handoff } from './handoff';
@@ -31,6 +32,7 @@ export { default as Modal } from './modal';
31
32
  export { default as NewspackIcon } from './newspack-icon';
32
33
  export { default as Notice } from './notice';
33
34
  export { default as Page } from './page';
35
+ export { default as PageControl } from './page-control';
34
36
  export { default as PluginInstaller } from './plugin-installer';
35
37
  export { default as PluginSettings } from './plugin-settings';
36
38
  export { default as PluginToggle } from './plugin-toggle';
@@ -44,10 +46,12 @@ export { default as StepsList } from './steps-list';
44
46
  export { default as StepsListItem } from './steps-list-item';
45
47
  export { default as StyleCard } from './style-card';
46
48
  export { default as TabbedNavigation } from './tabbed-navigation';
49
+ export { default as TableCard } from './table-card';
47
50
  export { default as TextControl } from './text-control';
48
51
  export { default as Waiting } from './waiting';
49
52
  export { default as WebPreview } from './web-preview';
50
53
  export { default as Wizard } from './wizard';
54
+ export { WizardBanner } from './wizard';
51
55
  export { default as withWizard } from './with-wizard';
52
56
  export { default as withWizardScreen } from './with-wizard-screen';
53
57
  export { default as Router } from './proxied-imports/router';
@@ -39,7 +39,7 @@ import './style.scss';
39
39
  * inside its active tab panel — or, when no visible tab
40
40
  * owns the route, as a sibling of the panels.
41
41
  * @param {string} [props.className]
42
- * @param {*} props.children
42
+ * @param {*} [props.children]
43
43
  * @return {JSX.Element} Page component.
44
44
  */
45
45
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
@@ -0,0 +1,82 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
+ 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; }
4
+ 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; }
5
+ /**
6
+ * Page Control.
7
+ *
8
+ * A single-page selector backed by an autocomplete search, for settings that point at a
9
+ * page without enumerating every page the site has.
10
+ */
11
+
12
+ /**
13
+ * WordPress dependencies
14
+ */
15
+ import { useEffect, useState } from '@wordpress/element';
16
+ import { decodeEntities } from '@wordpress/html-entities';
17
+ import { __ } from '@wordpress/i18n';
18
+
19
+ /**
20
+ * Internal dependencies
21
+ */
22
+ import { AutocompleteWithSuggestions } from '../';
23
+
24
+ /**
25
+ * Render a single-page selector.
26
+ *
27
+ * @param {Object} props Component props.
28
+ * @param {string} props.label Field label.
29
+ * @param {string} props.help Help text.
30
+ * @param {boolean} props.disabled Whether the field is disabled (e.g. while saving).
31
+ * @param {Object} props.selected The saved page as `{ label, value }`, if any.
32
+ * @param {Function} props.onChange Called with the selected page ID, or '' when cleared.
33
+ */
34
+ import { jsx as _jsx } from "react/jsx-runtime";
35
+ var PageControl = function PageControl(_ref) {
36
+ var label = _ref.label,
37
+ help = _ref.help,
38
+ disabled = _ref.disabled,
39
+ selected = _ref.selected,
40
+ _onChange = _ref.onChange;
41
+ // Titles arrive HTML-encoded from the server, while the autocomplete decodes the
42
+ // suggestions it fetches. Decode here so the saved page and the search results below
43
+ // it render the same way.
44
+ var normalize = function normalize(item) {
45
+ return item ? _objectSpread(_objectSpread({}, item), {}, {
46
+ label: decodeEntities(item.label)
47
+ }) : null;
48
+ };
49
+ var _useState = useState(normalize(selected)),
50
+ _useState2 = _slicedToArray(_useState, 2),
51
+ selectedItem = _useState2[0],
52
+ setSelectedItem = _useState2[1];
53
+ // Settings are fetched after this control first renders, so adopt the saved page
54
+ // whenever it arrives or changes rather than only on mount.
55
+ useEffect(function () {
56
+ setSelectedItem(normalize(selected));
57
+ }, [selected === null || selected === void 0 ? void 0 : selected.value, selected === null || selected === void 0 ? void 0 : selected.label]);
58
+ return /*#__PURE__*/_jsx("fieldset", {
59
+ disabled: disabled,
60
+ className: "newspack-page-control",
61
+ children: /*#__PURE__*/_jsx(AutocompleteWithSuggestions, {
62
+ label: label,
63
+ help: help,
64
+ postTypes: [{
65
+ slug: 'page',
66
+ label: __('Page', 'newspack-plugin')
67
+ }],
68
+ postTypeLabel: __('page', 'newspack-plugin'),
69
+ postTypeLabelPlural: __('pages', 'newspack-plugin'),
70
+ selectedItems: selectedItem ? [selectedItem] : [],
71
+ onChange: function onChange(items) {
72
+ if (disabled) {
73
+ return;
74
+ }
75
+ var item = items && items.length ? items[items.length - 1] : null;
76
+ setSelectedItem(normalize(item));
77
+ _onChange(item ? String(item.value) : '');
78
+ }
79
+ })
80
+ });
81
+ };
82
+ export default PageControl;
@@ -17,13 +17,17 @@ import { CheckboxControl } from '@wordpress/components';
17
17
  /**
18
18
  * Internal dependencies
19
19
  */
20
- import { ActionCard, Button, Grid, Notice, SelectControl, TextControl } from '../';
20
+ import { ActionCard, Button, Grid, Notice, PageControl, SelectControl, TextControl } from '../';
21
21
  import './style.scss';
22
22
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
23
23
  var isSelectControl = function isSelectControl(setting) {
24
24
  return Array.isArray(setting.options) && setting.options.length;
25
25
  };
26
26
  var getControlComponent = function getControlComponent(setting) {
27
+ // An explicit `control` overrides the control implied by the setting's data type.
28
+ if ('page' === setting.control) {
29
+ return PageControl;
30
+ }
27
31
  if (isSelectControl(setting)) {
28
32
  return SelectControl;
29
33
  }
@@ -84,6 +88,8 @@ var SettingsSection = function SettingsSection(props) {
84
88
  };
85
89
  })) || null,
86
90
  value: setting.value,
91
+ // Only page controls consume `selected`; keep it off every other control's props.
92
+ selected: 'page' === setting.control ? setting.selected || null : null,
87
93
  multiple: isSelectControl(setting) && setting.multiple ? true : null,
88
94
  checked: setting.type === 'boolean' ? !!setting.value : null,
89
95
  onChange: function onChange(value) {
@@ -122,7 +128,7 @@ var SettingsSection = function SettingsSection(props) {
122
128
  onUpdate();
123
129
  setSaveDisabled(true);
124
130
  },
125
- children: __('Save Settings', 'newspack')
131
+ children: __('Save Settings', 'newspack-plugin')
126
132
  })),
127
133
  children: (active || active === null) && /*#__PURE__*/_jsxs(Fragment, {
128
134
  children: [(error === null || error === void 0 ? void 0 : error.message) && /*#__PURE__*/_jsx(Notice, {
@@ -36,6 +36,7 @@ import classnames from 'classnames';
36
36
  * @property {boolean} [isWhite=false] - Indicates if the header should use a white theme.
37
37
  * @property {boolean} [noMargin=false] - Indicates if the header should have no margin.
38
38
  * @property {boolean} [pageHeader=false] - Indicates if the header is used as a page header.
39
+ * @property {string} [size='default'] - Size variant, either 'default' or 'small'. Scales the title, and the icon with it, independently of `pageHeader`.
39
40
  * @property {string} title - The title of the section.
40
41
  * @property {?string} [id=null] - Optional ID for the header element.
41
42
  * @property {?string|Function|*} [children=null] - Optional children to display in the header.
@@ -67,6 +68,8 @@ var SectionHeader = function SectionHeader(_ref) {
67
68
  noMargin = _ref$noMargin === void 0 ? false : _ref$noMargin,
68
69
  _ref$pageHeader = _ref.pageHeader,
69
70
  pageHeader = _ref$pageHeader === void 0 ? false : _ref$pageHeader,
71
+ _ref$size = _ref.size,
72
+ size = _ref$size === void 0 ? 'default' : _ref$size,
70
73
  title = _ref.title,
71
74
  _ref$id = _ref.id,
72
75
  id = _ref$id === void 0 ? null : _ref$id,
@@ -93,7 +96,7 @@ var SectionHeader = function SectionHeader(_ref) {
93
96
  }, 250);
94
97
  }
95
98
  }, []);
96
- var classes = classnames('newspack-section-header', centered && 'newspack-section-header--is-centered', isWhite && 'newspack-section-header--is-white', noMargin && 'newspack-section-header--no-margin', pageHeader && 'newspack-section-header--page-header');
99
+ var classes = classnames('newspack-section-header', centered && 'newspack-section-header--is-centered', isWhite && 'newspack-section-header--is-white', noMargin && 'newspack-section-header--no-margin', pageHeader && 'newspack-section-header--page-header', size === 'small' && 'newspack-section-header--small');
97
100
 
98
101
  // The breadcrumb `Page` owns the single page `<h1>`, so a `pageHeader` section
99
102
  // is a secondary heading: its level follows `heading` (default 2). `pageHeader`
@@ -156,7 +159,7 @@ var SectionHeader = function SectionHeader(_ref) {
156
159
  className: "newspack-section-header__icon",
157
160
  children: /*#__PURE__*/_jsx(Icon, {
158
161
  icon: icon,
159
- size: 48
162
+ size: size === 'small' ? 24 : 48
160
163
  })
161
164
  }), backNav ? /*#__PURE__*/_jsxs(HStack, {
162
165
  alignment: "left",
@@ -95,6 +95,31 @@
95
95
  @include wp-mixins.heading-x-large();
96
96
  }
97
97
 
98
+ &#{&}--small {
99
+ .newspack-section-header__title {
100
+ @include wp-mixins.heading-large();
101
+ }
102
+ }
103
+
104
+ &__icon {
105
+ background-color: var(--wp-admin-theme-color-lighter-10);
106
+ border-radius: 100%;
107
+ display: grid;
108
+ height: 80px;
109
+ margin-bottom: 8px !important;
110
+ width: 80px;
111
+ place-items: center;
112
+ svg {
113
+ fill: var(--wp-admin-theme-color);
114
+ }
115
+ }
116
+
117
+ &#{&}--small .newspack-section-header__icon {
118
+ height: wp-vars.$grid-unit-50;
119
+ margin-bottom: wp-vars.$grid-unit-05 !important;
120
+ width: wp-vars.$grid-unit-50;
121
+ }
122
+
98
123
  &__title-container {
99
124
  align-items: center;
100
125
  display: flex;
@@ -154,17 +179,5 @@
154
179
  > p {
155
180
  text-align: center;
156
181
  }
157
- .newspack-section-header__icon {
158
- background-color: var(--wp-admin-theme-color-lighter-10);
159
- border-radius: 100%;
160
- display: grid;
161
- height: 80px;
162
- margin-bottom: 8px !important;
163
- width: 80px;
164
- place-items: center;
165
- svg {
166
- fill: var(--wp-admin-theme-color);
167
- }
168
- }
169
182
  }
170
183
  }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * TableCard
3
+ *
4
+ * The DataViews-in-a-card composition from @wordpress/ui. Which card edges the
5
+ * table touches follows upstream first/last-child rules, so it changes with the
6
+ * slots present — the README spells the combinations out.
7
+ */
8
+
9
+ /**
10
+ * WordPress dependencies
11
+ */
12
+ import { Card, Stack } from '@wordpress/ui';
13
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
+ /** `0` is a renderable slot value; the falsy leftovers of `cond && <X />` are not. */
15
+ var isRenderable = function isRenderable(node) {
16
+ return node !== undefined && node !== null && node !== '' && typeof node !== 'boolean';
17
+ };
18
+ export default function TableCard(_ref) {
19
+ var title = _ref.title,
20
+ titleId = _ref.titleId,
21
+ _ref$heading = _ref.heading,
22
+ heading = _ref$heading === void 0 ? 3 : _ref$heading,
23
+ actions = _ref.actions,
24
+ before = _ref.before,
25
+ after = _ref.after,
26
+ className = _ref.className,
27
+ children = _ref.children;
28
+ var TitleTag = "h".concat(heading);
29
+ var hasTitle = isRenderable(title);
30
+ var hasActions = isRenderable(actions);
31
+ return /*#__PURE__*/_jsxs(Card.Root, {
32
+ className: className,
33
+ children: [(hasTitle || hasActions) && /*#__PURE__*/_jsxs(Card.Header, {
34
+ render: hasActions ? /*#__PURE__*/_jsx(Stack, {
35
+ direction: "row",
36
+ justify: hasTitle ? 'space-between' : 'end',
37
+ align: "center"
38
+ }) : undefined,
39
+ children: [hasTitle && /*#__PURE__*/_jsx(Card.Title, {
40
+ render: /*#__PURE__*/_jsx(TitleTag, {
41
+ id: titleId
42
+ }),
43
+ children: title
44
+ }), hasActions && /*#__PURE__*/_jsx(Stack, {
45
+ direction: "row",
46
+ gap: "sm",
47
+ align: "center",
48
+ children: actions
49
+ })]
50
+ }), /*#__PURE__*/_jsxs(Card.Content, {
51
+ render: /*#__PURE__*/_jsx(Stack, {
52
+ direction: "column",
53
+ gap: "lg"
54
+ }),
55
+ children: [before, /*#__PURE__*/_jsx(Card.FullBleed, {
56
+ children: children
57
+ }), after]
58
+ })]
59
+ });
60
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { render, screen } from '@testing-library/react';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import TableCard from '.';
10
+
11
+ // @wordpress/ui injects no CSS under Jest, so the layout this component exists
12
+ // for (full bleed, edge bumps, the content gap) is invisible here; slot order
13
+ // and heading semantics are the assertable contract.
14
+ import { jsx as _jsx } from "react/jsx-runtime";
15
+ describe('TableCard', function () {
16
+ it('renders the table children inside the card', function () {
17
+ render(/*#__PURE__*/_jsx(TableCard, {
18
+ children: /*#__PURE__*/_jsx("table", {
19
+ "aria-label": "Prices"
20
+ })
21
+ }));
22
+ expect(screen.getByRole('table', {
23
+ name: 'Prices'
24
+ })).toBeInTheDocument();
25
+ });
26
+ it('renders the title as a real heading carrying the given id', function () {
27
+ var _render = render(/*#__PURE__*/_jsx(TableCard, {
28
+ title: "Price schedule",
29
+ titleId: "tc-title",
30
+ children: /*#__PURE__*/_jsx("table", {
31
+ "aria-label": "Prices"
32
+ })
33
+ })),
34
+ rerender = _render.rerender;
35
+ expect(screen.getByRole('heading', {
36
+ level: 3,
37
+ name: 'Price schedule'
38
+ })).toHaveAttribute('id', 'tc-title');
39
+ rerender(/*#__PURE__*/_jsx(TableCard, {
40
+ title: "Price schedule",
41
+ heading: 2,
42
+ children: /*#__PURE__*/_jsx("table", {
43
+ "aria-label": "Prices"
44
+ })
45
+ }));
46
+ expect(screen.getByRole('heading', {
47
+ level: 2,
48
+ name: 'Price schedule'
49
+ })).toBeInTheDocument();
50
+ rerender(/*#__PURE__*/_jsx(TableCard, {
51
+ children: /*#__PURE__*/_jsx("table", {
52
+ "aria-label": "Prices"
53
+ })
54
+ }));
55
+ expect(screen.queryByRole('heading')).not.toBeInTheDocument();
56
+ });
57
+ it('treats 0 as a renderable title and the empty string as none', function () {
58
+ var _render2 = render(/*#__PURE__*/_jsx(TableCard, {
59
+ title: 0,
60
+ children: /*#__PURE__*/_jsx("table", {
61
+ "aria-label": "Prices"
62
+ })
63
+ })),
64
+ rerender = _render2.rerender;
65
+ expect(screen.getByRole('heading', {
66
+ level: 3,
67
+ name: '0'
68
+ })).toBeInTheDocument();
69
+ rerender(/*#__PURE__*/_jsx(TableCard, {
70
+ title: "",
71
+ children: /*#__PURE__*/_jsx("table", {
72
+ "aria-label": "Prices"
73
+ })
74
+ }));
75
+ expect(screen.queryByRole('heading')).not.toBeInTheDocument();
76
+ });
77
+ it('renders header actions alongside the title', function () {
78
+ render(/*#__PURE__*/_jsx(TableCard, {
79
+ title: "Price Schedule",
80
+ actions: /*#__PURE__*/_jsx("button", {
81
+ children: "Add Price"
82
+ }),
83
+ children: /*#__PURE__*/_jsx("table", {
84
+ "aria-label": "Prices"
85
+ })
86
+ }));
87
+ var heading = screen.getByRole('heading', {
88
+ level: 3,
89
+ name: 'Price Schedule'
90
+ });
91
+ var action = screen.getByRole('button', {
92
+ name: 'Add Price'
93
+ });
94
+ expect(heading.parentElement).toContainElement(action);
95
+ });
96
+ it('renders a header for actions alone, and none for boolean slot leftovers', function () {
97
+ var _render3 = render(/*#__PURE__*/_jsx(TableCard, {
98
+ actions: /*#__PURE__*/_jsx("button", {
99
+ children: "Add Price"
100
+ }),
101
+ children: /*#__PURE__*/_jsx("table", {
102
+ "aria-label": "Prices"
103
+ })
104
+ })),
105
+ rerender = _render3.rerender;
106
+ expect(screen.getByRole('button', {
107
+ name: 'Add Price'
108
+ })).toBeInTheDocument();
109
+ expect(screen.queryByRole('heading')).not.toBeInTheDocument();
110
+ rerender(/*#__PURE__*/_jsx(TableCard, {
111
+ title: false,
112
+ actions: false,
113
+ children: /*#__PURE__*/_jsx("table", {
114
+ "aria-label": "Prices"
115
+ })
116
+ }));
117
+ expect(screen.queryByRole('heading')).not.toBeInTheDocument();
118
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
119
+ });
120
+ it('orders before, table, after within the card body', function () {
121
+ render(/*#__PURE__*/_jsx(TableCard, {
122
+ before: /*#__PURE__*/_jsx("p", {
123
+ children: "Stats row"
124
+ }),
125
+ after: /*#__PURE__*/_jsx("button", {
126
+ children: "See More"
127
+ }),
128
+ children: /*#__PURE__*/_jsx("table", {
129
+ "aria-label": "Prices"
130
+ })
131
+ }));
132
+ var before = screen.getByText('Stats row');
133
+ var after = screen.getByRole('button', {
134
+ name: 'See More'
135
+ });
136
+ var table = screen.getByRole('table', {
137
+ name: 'Prices'
138
+ });
139
+ // Walk the table up to the card body's direct child, so extra upstream
140
+ // wrappers cannot silently repoint the comparison.
141
+ var body = before.parentElement;
142
+ var tableWrap = table;
143
+ while (tableWrap.parentElement !== body) {
144
+ tableWrap = tableWrap.parentElement;
145
+ }
146
+ var order = Array.from(body.children);
147
+ expect(order.indexOf(before)).toBeLessThan(order.indexOf(tableWrap));
148
+ expect(order.indexOf(tableWrap)).toBeLessThan(order.indexOf(after));
149
+ });
150
+ });
@@ -18,6 +18,14 @@
18
18
 
19
19
  color: wp-colors.$gray-900;
20
20
 
21
+ // Full-width dividers span 100vw, which overspills the admin content area
22
+ // (admin menu offset + scrollbar) and triggers horizontal scrolling; clip
23
+ // (not hidden) so no scroll container is created. Scoped off __content,
24
+ // which can be a constrained centered column.
25
+ &:not(.newspack-wizard__content) {
26
+ overflow-x: clip;
27
+ }
28
+
21
29
  // Headings
22
30
  h1,
23
31
  h2,