newspack-components 4.5.0 → 4.6.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.
Files changed (138) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/DEVELOPMENT.md +1 -1
  3. package/dist/cjs/accordion/index.js +43 -26
  4. package/dist/cjs/accordion/index.test.js +80 -0
  5. package/dist/cjs/accordion/style.scss +25 -21
  6. package/dist/cjs/action-card/style.scss +0 -22
  7. package/dist/cjs/breadcrumbs/index.js +80 -0
  8. package/dist/cjs/breadcrumbs/index.test.js +109 -0
  9. package/dist/cjs/breadcrumbs/style.scss +41 -0
  10. package/dist/cjs/card/core-card.js +8 -4
  11. package/dist/cjs/card/index.js +1 -1
  12. package/dist/cjs/card/style-core.scss +14 -0
  13. package/dist/cjs/card/style.scss +0 -9
  14. package/dist/cjs/card-feature/index.js +25 -13
  15. package/dist/cjs/card-settings-group/index.js +3 -0
  16. package/dist/cjs/confirm-dialog/index.js +34 -8
  17. package/dist/cjs/confirm-dialog/index.test.js +173 -0
  18. package/dist/cjs/divider/style.scss +4 -2
  19. package/dist/cjs/grid/style.scss +5 -0
  20. package/dist/cjs/handoff/index.js +8 -5
  21. package/dist/cjs/index.js +34 -1
  22. package/dist/cjs/integration-icon/index.js +40 -0
  23. package/dist/cjs/integration-icon/style.scss +42 -0
  24. package/dist/cjs/integration-icons/index.js +21 -2
  25. package/dist/cjs/modal/style.scss +4 -0
  26. package/dist/cjs/page/index.js +121 -0
  27. package/dist/cjs/page/index.test.js +69 -0
  28. package/dist/cjs/page/style.scss +86 -0
  29. package/dist/cjs/route-match.js +59 -0
  30. package/dist/cjs/route-match.test.js +77 -0
  31. package/dist/cjs/section-header/index.js +8 -1
  32. package/dist/cjs/section-header/style.scss +11 -11
  33. package/dist/cjs/select-control/GroupedSelectControl.js +1 -1
  34. package/dist/cjs/select-control/style.scss +4 -0
  35. package/dist/cjs/style.scss +1 -4
  36. package/dist/cjs/tabbed-navigation/index.js +188 -40
  37. package/dist/cjs/tabbed-navigation/index.test.js +299 -0
  38. package/dist/cjs/tabbed-navigation/style.scss +77 -57
  39. package/dist/cjs/with-wizard/index.js +34 -45
  40. package/dist/cjs/with-wizard-screen/index.js +82 -41
  41. package/dist/cjs/with-wizard-screen/index.test.js +50 -0
  42. package/dist/cjs/with-wizard-screen/style.scss +15 -118
  43. package/dist/cjs/wizard/breadcrumbs-select.js +70 -0
  44. package/dist/cjs/wizard/breadcrumbs-select.test.js +200 -0
  45. package/dist/cjs/wizard/components/WizardError.js +8 -4
  46. package/dist/cjs/wizard/index.js +162 -141
  47. package/dist/esm/accordion/index.js +42 -26
  48. package/dist/esm/accordion/index.test.js +79 -0
  49. package/dist/esm/accordion/style.scss +25 -21
  50. package/dist/esm/action-card/style.scss +0 -22
  51. package/dist/esm/breadcrumbs/index.js +75 -0
  52. package/dist/esm/breadcrumbs/index.test.js +105 -0
  53. package/dist/esm/breadcrumbs/style.scss +41 -0
  54. package/dist/esm/card/core-card.js +8 -4
  55. package/dist/esm/card/index.js +1 -1
  56. package/dist/esm/card/style-core.scss +14 -0
  57. package/dist/esm/card/style.scss +0 -9
  58. package/dist/esm/card-feature/index.js +25 -13
  59. package/dist/esm/card-settings-group/index.js +3 -0
  60. package/dist/esm/confirm-dialog/index.js +34 -8
  61. package/dist/esm/confirm-dialog/index.test.js +173 -0
  62. package/dist/esm/divider/style.scss +4 -2
  63. package/dist/esm/grid/style.scss +5 -0
  64. package/dist/esm/handoff/index.js +7 -5
  65. package/dist/esm/index.js +5 -1
  66. package/dist/esm/integration-icon/index.js +34 -0
  67. package/dist/esm/integration-icon/style.scss +42 -0
  68. package/dist/esm/integration-icons/index.js +22 -1
  69. package/dist/esm/modal/style.scss +4 -0
  70. package/dist/esm/page/index.js +113 -0
  71. package/dist/esm/page/index.test.js +65 -0
  72. package/dist/esm/page/style.scss +86 -0
  73. package/dist/esm/route-match.js +51 -0
  74. package/dist/esm/route-match.test.js +74 -0
  75. package/dist/esm/section-header/index.js +8 -1
  76. package/dist/esm/section-header/style.scss +11 -11
  77. package/dist/esm/select-control/GroupedSelectControl.js +1 -1
  78. package/dist/esm/select-control/style.scss +4 -0
  79. package/dist/esm/style.scss +1 -4
  80. package/dist/esm/tabbed-navigation/index.js +192 -38
  81. package/dist/esm/tabbed-navigation/index.test.js +296 -0
  82. package/dist/esm/tabbed-navigation/style.scss +77 -57
  83. package/dist/esm/with-wizard/index.js +33 -45
  84. package/dist/esm/with-wizard-screen/index.js +78 -41
  85. package/dist/esm/with-wizard-screen/index.test.js +46 -0
  86. package/dist/esm/with-wizard-screen/style.scss +15 -118
  87. package/dist/esm/wizard/breadcrumbs-select.js +63 -0
  88. package/dist/esm/wizard/breadcrumbs-select.test.js +197 -0
  89. package/dist/esm/wizard/components/WizardError.js +7 -5
  90. package/dist/esm/wizard/index.js +165 -144
  91. package/package.json +10 -2
  92. package/src/accordion/index.js +27 -15
  93. package/src/accordion/index.test.js +62 -0
  94. package/src/accordion/style.scss +25 -21
  95. package/src/action-card/style.scss +0 -22
  96. package/src/breadcrumbs/index.js +63 -0
  97. package/src/breadcrumbs/index.test.js +59 -0
  98. package/src/breadcrumbs/style.scss +41 -0
  99. package/src/card/core-card.js +11 -6
  100. package/src/card/index.js +0 -1
  101. package/src/card/style-core.scss +14 -0
  102. package/src/card/style.scss +0 -9
  103. package/src/card-feature/README.md +3 -3
  104. package/src/card-feature/index.tsx +37 -21
  105. package/src/card-settings-group/index.tsx +4 -0
  106. package/src/card-sortable-list/index.tsx +1 -1
  107. package/src/confirm-dialog/index.test.js +142 -0
  108. package/src/confirm-dialog/index.tsx +34 -7
  109. package/src/divider/style.scss +4 -2
  110. package/src/grid/style.scss +5 -0
  111. package/src/handoff/index.js +4 -3
  112. package/src/index.js +5 -1
  113. package/src/integration-icon/index.tsx +35 -0
  114. package/src/integration-icon/style.scss +42 -0
  115. package/src/integration-icons/index.js +23 -0
  116. package/src/modal/style.scss +4 -0
  117. package/src/page/index.js +89 -0
  118. package/src/page/index.test.js +43 -0
  119. package/src/page/style.scss +86 -0
  120. package/src/route-match.js +47 -0
  121. package/src/route-match.test.js +50 -0
  122. package/src/section-header/index.js +7 -3
  123. package/src/section-header/style.scss +11 -11
  124. package/src/select-control/GroupedSelectControl.js +1 -1
  125. package/src/select-control/style.scss +4 -0
  126. package/src/style.scss +1 -4
  127. package/src/tabbed-navigation/index.js +181 -34
  128. package/src/tabbed-navigation/index.test.js +228 -0
  129. package/src/tabbed-navigation/style.scss +77 -57
  130. package/src/with-wizard/index.js +18 -39
  131. package/src/with-wizard-screen/index.js +70 -35
  132. package/src/with-wizard-screen/index.test.js +36 -0
  133. package/src/with-wizard-screen/style.scss +15 -118
  134. package/src/wizard/breadcrumbs-select.js +51 -0
  135. package/src/wizard/breadcrumbs-select.test.js +124 -0
  136. package/src/wizard/components/WizardError.js +4 -3
  137. package/src/wizard/index.js +160 -146
  138. package/tsconfig.json +7 -0
@@ -12,19 +12,20 @@ import classnames from 'classnames';
12
12
  */
13
13
  import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components';
14
14
  import { useDispatch, useSelect } from '@wordpress/data';
15
- import { useEffect, useState, forwardRef } from '@wordpress/element';
15
+ import { cloneElement, isValidElement, useEffect, useState, forwardRef } from '@wordpress/element';
16
16
  import { __ } from '@wordpress/i18n';
17
17
  import { category, chevronLeft, moreVertical } from '@wordpress/icons';
18
18
 
19
19
  /**
20
20
  * Internal dependencies
21
21
  */
22
- import { Footer, Notice, Button, NewspackIcon, TabbedNavigation, PluginInstaller, SectionHeader, HandoffMessage } from '../';
22
+ import { Footer, Notice, Button, TabbedNavigation, PluginInstaller, SectionHeader, HandoffMessage, Page } from '../';
23
+ import { activeBreadcrumbs, appendSectionName } from './breadcrumbs-select';
23
24
  import Router from '../proxied-imports/router';
24
25
  import registerStore, { WIZARD_STORE_NAMESPACE } from './store';
25
26
  import WizardSnackbar from './components/WizardSnackbar';
26
27
  import WizardError from './components/WizardError';
27
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
28
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
28
29
  registerStore();
29
30
 
30
31
  /**
@@ -63,6 +64,49 @@ var ResetHeaderData = function ResetHeaderData() {
63
64
  return null;
64
65
  };
65
66
 
67
+ /**
68
+ * Wizard header + content region. Rendered inside the wizard's HashRouter so it
69
+ * can read the current route and derive the active-tab breadcrumb.
70
+ */
71
+ var WizardHeaderRegion = function WizardHeaderRegion(_ref) {
72
+ var hideHeader = _ref.hideHeader,
73
+ headerText = _ref.headerText,
74
+ sections = _ref.sections,
75
+ sectionName = _ref.sectionName,
76
+ subTitle = _ref.subTitle,
77
+ actions = _ref.actions,
78
+ tabbedNavigation = _ref.tabbedNavigation,
79
+ children = _ref.children;
80
+ var _useLocation = useLocation(),
81
+ pathname = _useLocation.pathname;
82
+ if (hideHeader) {
83
+ // Without the Page shell the tabs still own the content: it renders
84
+ // inside the active tab's panel.
85
+ if (isValidElement(tabbedNavigation)) {
86
+ return cloneElement(tabbedNavigation, {
87
+ content: children
88
+ });
89
+ }
90
+ return children;
91
+ }
92
+ var breadcrumbItems = activeBreadcrumbs(sections, pathname);
93
+ if (!breadcrumbItems.length && headerText) {
94
+ breadcrumbItems = [{
95
+ label: headerText
96
+ }];
97
+ }
98
+ // Append any render-time leaf crumb(s) the section supplied via
99
+ // headerData.sectionName (deduped against the current trailing label).
100
+ breadcrumbItems = appendSectionName(breadcrumbItems, sectionName);
101
+ return /*#__PURE__*/_jsx(Page, {
102
+ breadcrumbItems: breadcrumbItems,
103
+ subTitle: subTitle,
104
+ actions: actions,
105
+ tabbedNavigation: tabbedNavigation,
106
+ children: children
107
+ });
108
+ };
109
+
66
110
  /**
67
111
  * @typedef {Object} WizardProps
68
112
  * @property {string} headerText The header text.
@@ -84,23 +128,23 @@ var ResetHeaderData = function ResetHeaderData() {
84
128
  * @param {WizardProps} props
85
129
  * @return {JSX.Element} Wizard component
86
130
  */
87
- var Wizard = function Wizard(_ref, ref) {
88
- var _ref$sections = _ref.sections,
89
- sections = _ref$sections === void 0 ? [] : _ref$sections,
90
- headerText = _ref.headerText,
91
- apiSlug = _ref.apiSlug,
92
- _ref$sharedProps = _ref.sharedProps,
93
- sharedProps = _ref$sharedProps === void 0 ? {} : _ref$sharedProps,
94
- subHeaderText = _ref.subHeaderText,
95
- hasSimpleFooter = _ref.hasSimpleFooter,
96
- className = _ref.className,
97
- renderAboveSections = _ref.renderAboveSections,
98
- _ref$requiredPlugins = _ref.requiredPlugins,
99
- requiredPlugins = _ref$requiredPlugins === void 0 ? [] : _ref$requiredPlugins,
100
- _ref$isInitialFetchTr = _ref.isInitialFetchTriggered,
101
- isInitialFetchTriggered = _ref$isInitialFetchTr === void 0 ? true : _ref$isInitialFetchTr,
102
- _ref$fixedHeader = _ref.fixedHeader,
103
- fixedHeader = _ref$fixedHeader === void 0 ? false : _ref$fixedHeader;
131
+ var Wizard = function Wizard(_ref2, ref) {
132
+ var _ref2$sections = _ref2.sections,
133
+ sections = _ref2$sections === void 0 ? [] : _ref2$sections,
134
+ headerText = _ref2.headerText,
135
+ apiSlug = _ref2.apiSlug,
136
+ _ref2$sharedProps = _ref2.sharedProps,
137
+ sharedProps = _ref2$sharedProps === void 0 ? {} : _ref2$sharedProps,
138
+ subHeaderText = _ref2.subHeaderText,
139
+ hasSimpleFooter = _ref2.hasSimpleFooter,
140
+ className = _ref2.className,
141
+ renderAboveSections = _ref2.renderAboveSections,
142
+ _ref2$requiredPlugins = _ref2.requiredPlugins,
143
+ requiredPlugins = _ref2$requiredPlugins === void 0 ? [] : _ref2$requiredPlugins,
144
+ _ref2$isInitialFetchT = _ref2.isInitialFetchTriggered,
145
+ isInitialFetchTriggered = _ref2$isInitialFetchT === void 0 ? true : _ref2$isInitialFetchT,
146
+ _ref2$hideHeader = _ref2.hideHeader,
147
+ hideHeader = _ref2$hideHeader === void 0 ? false : _ref2$hideHeader;
104
148
  var isLoading = useSelect(function (select) {
105
149
  return select(WIZARD_STORE_NAMESPACE).isLoading();
106
150
  });
@@ -148,8 +192,8 @@ var Wizard = function Wizard(_ref, ref) {
148
192
  render: function render() {
149
193
  return /*#__PURE__*/_jsx(PluginInstaller, {
150
194
  plugins: requiredPlugins,
151
- onStatus: function onStatus(_ref2) {
152
- var complete = _ref2.complete;
195
+ onStatus: function onStatus(_ref3) {
196
+ var complete = _ref3.complete;
153
197
  return setPluginRequirementsSatisfied(complete);
154
198
  }
155
199
  });
@@ -160,137 +204,114 @@ var Wizard = function Wizard(_ref, ref) {
160
204
  // When plugins are required but not yet satisfied, `displayedSections` is replaced with
161
205
  // the PluginInstaller. Use it for routing so the installer actually mounts and runs.
162
206
  var routedSections = pluginRequirementsSatisfied ? sections : displayedSections;
163
- var urlWithoutHash = window.location.href.split('#')[0];
207
+ var tabbedNavigation = displayedSections.length > 1 && /*#__PURE__*/_jsx(TabbedNavigation, {
208
+ items: displayedSections,
209
+ children: /*#__PURE__*/_jsx(WizardError, {})
210
+ });
211
+ var content = /*#__PURE__*/_jsxs(_Fragment, {
212
+ children: [/*#__PURE__*/_jsx(HandoffMessage, {}), sections.length > 1 && /*#__PURE__*/_jsx(ResetHeaderData, {}), /*#__PURE__*/_jsx("div", {
213
+ className: "newspack-wizard__main",
214
+ children: /*#__PURE__*/_jsxs(Switch, {
215
+ children: [routedSections.map(function (section, index) {
216
+ var _section$exact;
217
+ var SectionComponent = section.render;
218
+ var sectionProps = section.props || {};
219
+ return /*#__PURE__*/_jsx(Route, {
220
+ exact: (_section$exact = section.exact) !== null && _section$exact !== void 0 ? _section$exact : false,
221
+ path: section.path,
222
+ render: function render(routerProps) {
223
+ return /*#__PURE__*/_jsxs("div", {
224
+ className: classnames('newspack-wizard__content', className, {
225
+ 'newspack-wizard__content--full-width': section.fullWidth
226
+ }),
227
+ children: ['function' === typeof renderAboveSections ? renderAboveSections() : null, (sectionTitle || section.title) && /*#__PURE__*/_jsx(SectionHeader, {
228
+ className: "newspack-wizard__section-header",
229
+ backNav: backNav || section.backNav,
230
+ title: sectionTitle || section.title,
231
+ description: sectionDescription || section.description,
232
+ badges: badges || section.badges,
233
+ menu: sectionMenu || section.menu,
234
+ primaryAction: sectionPrimaryAction || section.primaryAction,
235
+ secondaryAction: sectionSecondaryAction || section.secondaryAction,
236
+ heading: 2,
237
+ noMargin: true
238
+ }), /*#__PURE__*/_jsx(SectionComponent, _objectSpread(_objectSpread(_objectSpread({}, routerProps), sectionProps), sharedProps))]
239
+ });
240
+ }
241
+ }, index);
242
+ }), /*#__PURE__*/_jsx(Redirect, {
243
+ to: displayedSections[0].path
244
+ })]
245
+ })
246
+ })]
247
+ });
248
+ var headerActions = (actions === null || actions === void 0 ? void 0 : actions.length) > 0 ? /*#__PURE__*/_jsxs(_Fragment, {
249
+ children: [mainActions.map(function (action, index) {
250
+ return /*#__PURE__*/_jsx(Button, {
251
+ className: "newspack-wizard__actions__main",
252
+ href: action.href,
253
+ icon: resolveIcon(action.icon),
254
+ variant: action.type,
255
+ onClick: action.action,
256
+ disabled: action.disabled || false,
257
+ isDestructive: action.destructive || false,
258
+ children: action.label
259
+ }, index);
260
+ }), /*#__PURE__*/_jsx(DropdownMenu, {
261
+ className: (moreActions === null || moreActions === void 0 ? void 0 : moreActions.length) === 0 ? 'newspack-wizard__actions__more--primary-only' : '',
262
+ icon: moreVertical,
263
+ label: __('More', 'newspack-plugin'),
264
+ popoverProps: {
265
+ className: 'newspack-wizard__actions__more'
266
+ },
267
+ children: function children() {
268
+ return (
269
+ // Split actions into groups whenever an action opts in via `separator: true`.
270
+ // Consecutive MenuGroups render the WordPress-standard divider between them.
271
+ actions.reduce(function (groups, action) {
272
+ if (action.separator || groups.length === 0) {
273
+ groups.push([]);
274
+ }
275
+ groups[groups.length - 1].push(action);
276
+ return groups;
277
+ }, []).map(function (group, groupIndex) {
278
+ return /*#__PURE__*/_jsx(MenuGroup, {
279
+ children: group.map(function (action, index) {
280
+ return /*#__PURE__*/_jsx(MenuItem, {
281
+ className: action.type === 'primary' || action.type === 'secondary' ? 'newspack-wizard__actions__more__main' : 'newspack-wizard__actions__more__more',
282
+ icon: action.icon,
283
+ href: action.href,
284
+ onClick: action.action,
285
+ disabled: action.disabled || false,
286
+ isDestructive: action.destructive || false,
287
+ children: action.label
288
+ }, index);
289
+ })
290
+ }, groupIndex);
291
+ })
292
+ );
293
+ }
294
+ })]
295
+ }) : undefined;
164
296
  return /*#__PURE__*/_jsxs("div", {
165
297
  ref: ref,
166
298
  children: [/*#__PURE__*/_jsxs("div", {
167
299
  className: classnames(isLoading ? 'newspack-wizard__is-loading' : 'newspack-wizard__is-loaded', {
168
- 'newspack-wizard__is-loading-quiet': isQuietLoading,
169
- 'newspack-wizard__fixed-header': fixedHeader
300
+ 'newspack-wizard__is-loading-quiet': isQuietLoading
170
301
  }),
171
302
  children: [/*#__PURE__*/_jsxs(HashRouter, {
172
303
  hashType: "slash",
173
304
  children: [newspack_aux_data.is_debug_mode && /*#__PURE__*/_jsx(Notice, {
174
305
  debugMode: true
175
- }), /*#__PURE__*/_jsxs("div", {
176
- className: "newspack-wizard__header",
177
- children: [/*#__PURE__*/_jsx("div", {
178
- className: "newspack-wizard__header__inner",
179
- children: /*#__PURE__*/_jsxs("div", {
180
- className: "newspack-wizard__title",
181
- children: [newspack_urls.dashboard !== urlWithoutHash ? /*#__PURE__*/_jsx(Button, {
182
- isLink: true,
183
- href: newspack_urls.dashboard,
184
- label: __('Return to Dashboard', 'newspack-plugin'),
185
- showTooltip: true,
186
- icon: category,
187
- iconSize: 36,
188
- children: /*#__PURE__*/_jsx(NewspackIcon, {
189
- size: 36
190
- })
191
- }) : /*#__PURE__*/_jsx(NewspackIcon, {
192
- size: 36
193
- }), /*#__PURE__*/_jsxs("div", {
194
- children: [headerText && /*#__PURE__*/_jsxs("h2", {
195
- className: "newspack-wizard__header__title",
196
- children: [headerText, sectionName && /*#__PURE__*/_jsxs("span", {
197
- className: "newspack-wizard__header__section",
198
- children: [/*#__PURE__*/_jsx("span", {
199
- className: "newspack-wizard__header__section__separator",
200
- children: " / "
201
- }), " ", sectionName]
202
- })]
203
- }), subHeaderText && /*#__PURE__*/_jsx("span", {
204
- children: subHeaderText
205
- })]
206
- })]
207
- })
208
- }), (actions === null || actions === void 0 ? void 0 : actions.length) > 0 && /*#__PURE__*/_jsxs("div", {
209
- className: "newspack-wizard__header__actions",
210
- children: [mainActions.map(function (action, index) {
211
- return /*#__PURE__*/_jsx(Button, {
212
- className: "newspack-wizard__header__actions__main",
213
- href: action.href,
214
- icon: resolveIcon(action.icon),
215
- variant: action.type,
216
- onClick: action.action,
217
- disabled: action.disabled || false,
218
- isDestructive: action.destructive || false,
219
- children: action.label
220
- }, index);
221
- }), /*#__PURE__*/_jsx(DropdownMenu, {
222
- className: (moreActions === null || moreActions === void 0 ? void 0 : moreActions.length) === 0 ? 'newspack-wizard__header__actions__more--primary-only' : '',
223
- icon: moreVertical,
224
- label: __('More', 'newspack-plugin'),
225
- popoverProps: {
226
- className: 'newspack-wizard__header__actions__more'
227
- },
228
- children: function children() {
229
- return (
230
- // Split actions into groups whenever an action opts in via `separator: true`.
231
- // Consecutive MenuGroups render the WordPress-standard divider between them.
232
- actions.reduce(function (groups, action) {
233
- if (action.separator || groups.length === 0) {
234
- groups.push([]);
235
- }
236
- groups[groups.length - 1].push(action);
237
- return groups;
238
- }, []).map(function (group, groupIndex) {
239
- return /*#__PURE__*/_jsx(MenuGroup, {
240
- children: group.map(function (action, index) {
241
- return /*#__PURE__*/_jsx(MenuItem, {
242
- className: action.type === 'primary' || action.type === 'secondary' ? 'newspack-wizard__header__actions__more__main' : 'newspack-wizard__header__actions__more__more',
243
- icon: action.icon,
244
- href: action.href,
245
- onClick: action.action,
246
- disabled: action.disabled || false,
247
- isDestructive: action.destructive || false,
248
- children: action.label
249
- }, index);
250
- })
251
- }, groupIndex);
252
- })
253
- );
254
- }
255
- })]
256
- })]
257
- }), displayedSections.length > 1 && /*#__PURE__*/_jsx(TabbedNavigation, {
258
- items: displayedSections,
259
- children: /*#__PURE__*/_jsx(WizardError, {})
260
- }), /*#__PURE__*/_jsx(HandoffMessage, {}), sections.length > 1 && /*#__PURE__*/_jsx(ResetHeaderData, {}), /*#__PURE__*/_jsx("div", {
261
- className: "newspack-wizard__main",
262
- children: /*#__PURE__*/_jsxs(Switch, {
263
- children: [routedSections.map(function (section, index) {
264
- var _section$exact;
265
- var SectionComponent = section.render;
266
- var sectionProps = section.props || {};
267
- return /*#__PURE__*/_jsx(Route, {
268
- exact: (_section$exact = section.exact) !== null && _section$exact !== void 0 ? _section$exact : false,
269
- path: section.path,
270
- render: function render(routerProps) {
271
- return /*#__PURE__*/_jsxs("div", {
272
- className: classnames('newspack-wizard__content', className, {
273
- 'newspack-wizard__content--full-width': section.fullWidth
274
- }),
275
- children: ['function' === typeof renderAboveSections ? renderAboveSections() : null, (sectionTitle || section.title) && /*#__PURE__*/_jsx(SectionHeader, {
276
- className: "newspack-wizard__section-header",
277
- backNav: backNav || section.backNav,
278
- title: sectionTitle || section.title,
279
- description: sectionDescription || section.description,
280
- badges: badges || section.badges,
281
- menu: sectionMenu || section.menu,
282
- primaryAction: sectionPrimaryAction || section.primaryAction,
283
- secondaryAction: sectionSecondaryAction || section.secondaryAction,
284
- heading: 1,
285
- noMargin: true
286
- }), /*#__PURE__*/_jsx(SectionComponent, _objectSpread(_objectSpread(_objectSpread({}, routerProps), sectionProps), sharedProps))]
287
- });
288
- }
289
- }, index);
290
- }), /*#__PURE__*/_jsx(Redirect, {
291
- to: displayedSections[0].path
292
- })]
293
- })
306
+ }), /*#__PURE__*/_jsx(WizardHeaderRegion, {
307
+ hideHeader: hideHeader,
308
+ headerText: headerText,
309
+ sections: routedSections,
310
+ sectionName: sectionName,
311
+ subTitle: subHeaderText,
312
+ actions: headerActions,
313
+ tabbedNavigation: tabbedNavigation,
314
+ children: content
294
315
  })]
295
316
  }), (notices === null || notices === void 0 ? void 0 : notices.length) > 0 && /*#__PURE__*/_jsx("div", {
296
317
  className: "newspack-wizard__snackbar-list",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newspack-components",
3
- "version": "4.5.0",
3
+ "version": "4.6.0",
4
4
  "description": "Newspack design system components",
5
5
  "author": "Automattic",
6
6
  "license": "GPL-2.0-or-later",
@@ -23,9 +23,14 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
+ "@wordpress/admin-ui": "2.4.0",
27
+ "@wordpress/style-runtime": "^0.5.0",
28
+ "@wordpress/theme": "^0.16.0",
29
+ "@wordpress/ui": "^0.16.0",
26
30
  "classnames": "^2.3.1",
27
31
  "lodash": "^4.17.21",
28
32
  "moment-range": "^3.1.1",
33
+ "newspack-icons": "1.1.0",
29
34
  "qs": "^6.15.2",
30
35
  "react-router-dom": "^5.3.4",
31
36
  "@babel/runtime": "^7.29.7",
@@ -53,6 +58,7 @@
53
58
  "@babel/preset-env": "^7.29.7",
54
59
  "@babel/preset-react": "^7.29.7",
55
60
  "@babel/preset-typescript": "^7.29.7",
61
+ "newspack-scripts": "5.10.0",
56
62
  "recursive-copy": "^2.0.0"
57
63
  },
58
64
  "babel": {
@@ -67,8 +73,10 @@
67
73
  "compile:cjs": "babel src --config-file ./babel.config.cjs.js --extensions='.js,.jsx,.ts,.tsx' --out-dir dist/cjs",
68
74
  "compile:esm": "babel src --extensions='.js,.jsx,.ts,.tsx' --out-dir dist/esm",
69
75
  "compile": "npm run compile:cjs && npm run compile:esm",
76
+ "test": "newspack-scripts test",
70
77
  "prepublishOnly": "npm run clean && npm run compile && node copy-styles.js",
71
78
  "postpublish": "npm run clean",
72
- "build": "npm run clean && npm run compile && node copy-styles.js"
79
+ "build": "npm run clean && npm run compile && node copy-styles.js",
80
+ "typescript:check": "newspack-scripts typescript-check"
73
81
  }
74
82
  }
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { useState } from '@wordpress/element';
5
- import { Icon, chevronRight } from '@wordpress/icons';
4
+ import { __experimentalVStack as VStack, PanelBody } from '@wordpress/components'; // eslint-disable-line @wordpress/no-unsafe-wp-apis
5
+ import { Children, Fragment, cloneElement } from '@wordpress/element';
6
6
 
7
7
  /**
8
8
  * External dependencies
@@ -12,22 +12,34 @@ import classNames from 'classnames';
12
12
  /**
13
13
  * Internal dependencies
14
14
  */
15
+ import Divider from '../divider';
15
16
  import './style.scss';
16
17
 
17
- const Accordion = ( { children, title, defaultOpen = false } ) => {
18
- const [ isOpen, setIsOpen ] = useState( defaultOpen );
18
+ export const AccordionPanel = ( { children, className, title, defaultOpen = false } ) => (
19
+ <PanelBody className={ className } title={ title } initialOpen={ defaultOpen }>
20
+ { children }
21
+ </PanelBody>
22
+ );
23
+
24
+ const Accordion = ( { children, className, hideSingleTitle = false, spacing = 6 } ) => {
25
+ const panels = Children.toArray( children );
26
+ // With nothing to collapse against, a lone panel can render open and untitled.
27
+ if ( hideSingleTitle && panels.length === 1 ) {
28
+ return (
29
+ <div className={ classNames( 'newspack-accordion', className ) }>
30
+ { cloneElement( panels[ 0 ], { defaultOpen: true, title: undefined } ) }
31
+ </div>
32
+ );
33
+ }
19
34
  return (
20
- <details
21
- className={ classNames( 'newspack-accordion', { 'newspack-accordion--is-open': isOpen } ) }
22
- open={ isOpen }
23
- onToggle={ e => setIsOpen( e.currentTarget.open ) }
24
- >
25
- <summary>
26
- { title }
27
- <Icon className="newspack-accordion__icon" icon={ chevronRight } size={ 24 } />
28
- </summary>
29
- <div className="newspack-accordion__content">{ children }</div>
30
- </details>
35
+ <VStack className={ classNames( 'newspack-accordion', className ) } spacing={ spacing }>
36
+ { panels.map( ( panel, index ) => (
37
+ <Fragment key={ panel.key }>
38
+ { panel }
39
+ { index < panels.length - 1 && <Divider variant="secondary" marginBottom={ 0 } marginTop={ 0 } /> }
40
+ </Fragment>
41
+ ) ) }
42
+ </VStack>
31
43
  );
32
44
  };
33
45
 
@@ -0,0 +1,62 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { render, screen } from '@testing-library/react';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import Accordion, { AccordionPanel } from './index';
10
+
11
+ const renderPanels = ( count, props = {} ) =>
12
+ render(
13
+ <Accordion { ...props }>
14
+ { Array.from( { length: count }, ( _, i ) => (
15
+ <AccordionPanel key={ i } title={ `Panel ${ i }` }>
16
+ content
17
+ </AccordionPanel>
18
+ ) ) }
19
+ </Accordion>
20
+ );
21
+
22
+ const dividers = container => container.querySelectorAll( '.newspack-divider' );
23
+
24
+ describe( 'Accordion dividers', () => {
25
+ it( 'renders no divider for a single panel', () => {
26
+ const { container } = renderPanels( 1 );
27
+ expect( container.querySelectorAll( '.components-panel__body' ) ).toHaveLength( 1 );
28
+ expect( dividers( container ) ).toHaveLength( 0 );
29
+ } );
30
+
31
+ it( 'renders a divider between panels but not after the last', () => {
32
+ const { container } = renderPanels( 3 );
33
+ expect( container.querySelectorAll( '.components-panel__body' ) ).toHaveLength( 3 );
34
+ expect( dividers( container ) ).toHaveLength( 2 );
35
+ expect( container.querySelector( '.newspack-accordion' ).lastElementChild ).not.toHaveClass( 'newspack-divider' );
36
+ } );
37
+
38
+ it( 'renders secondary dividers', () => {
39
+ const { container } = renderPanels( 2 );
40
+ expect( dividers( container )[ 0 ] ).toHaveClass( 'newspack-divider--variant-secondary' );
41
+ } );
42
+ } );
43
+
44
+ describe( 'Accordion hideSingleTitle', () => {
45
+ it( 'keeps the title on a lone panel by default', () => {
46
+ renderPanels( 1 );
47
+ expect( screen.getByRole( 'button', { name: 'Panel 0' } ) ).toBeInTheDocument();
48
+ } );
49
+
50
+ it( 'drops the title and opens a lone panel when set', () => {
51
+ const { container } = renderPanels( 1, { hideSingleTitle: true } );
52
+ expect( screen.queryByRole( 'button', { name: 'Panel 0' } ) ).not.toBeInTheDocument();
53
+ expect( container.querySelector( '.components-panel__body' ) ).toHaveClass( 'is-opened' );
54
+ expect( screen.getByText( 'content' ) ).toBeInTheDocument();
55
+ } );
56
+
57
+ it( 'leaves titles alone when there is more than one panel', () => {
58
+ renderPanels( 2, { hideSingleTitle: true } );
59
+ expect( screen.getByRole( 'button', { name: 'Panel 0' } ) ).toBeInTheDocument();
60
+ expect( screen.getByRole( 'button', { name: 'Panel 1' } ) ).toBeInTheDocument();
61
+ } );
62
+ } );
@@ -1,31 +1,35 @@
1
- @use "~@wordpress/base-styles/colors" as wp-colors;
1
+ @use "~@wordpress/base-styles/mixins" as wp-mixins;
2
+ @use "~@wordpress/base-styles/variables" as wp-vars;
2
3
 
4
+ // Sit flush with the surrounding column: core insets panels horizontally, which
5
+ // would misalign them against the section they belong to.
3
6
  .newspack-accordion {
4
- border: 1px solid wp-colors.$gray-300;
7
+ .components-panel__body {
8
+ border: 0;
5
9
 
6
- &__icon {
7
- transition: transform 200ms ease-out;
8
- transform: rotate(90deg);
10
+ > .components-panel__body-title:hover {
11
+ background: none;
12
+ }
9
13
  }
10
14
 
11
- &--is-open {
12
- .newspack-accordion {
13
- &__icon {
14
- transform: rotate(-90deg);
15
- }
15
+ .components-panel__body.is-opened {
16
+ padding: 0;
17
+
18
+ > .components-panel__body-title {
19
+ margin: 0 0 wp-vars.$grid-unit-30;
16
20
  }
17
21
  }
18
- summary {
19
- padding: 16px 32px;
20
- display: flex;
21
- align-items: center;
22
- justify-content: space-between;
23
- cursor: pointer;
24
- font-size: 14px;
25
- font-weight: bold;
26
- }
27
22
 
28
- &__content {
29
- padding: 16px 32px;
23
+ .components-panel__body-toggle.components-button {
24
+ @include wp-mixins.heading-large();
25
+ padding: 0;
26
+
27
+ .components-panel__arrow {
28
+ right: 0;
29
+ }
30
+
31
+ &:focus:not(:focus-visible) {
32
+ box-shadow: none;
33
+ }
30
34
  }
31
35
  }
@@ -77,28 +77,6 @@
77
77
  padding: 0 4px;
78
78
  }
79
79
 
80
- &.mv0 {
81
- border-left-color: wp-colors.$gray-300;
82
- border-radius: 0;
83
- border-right-color: wp-colors.$gray-300;
84
-
85
- &:first-of-type {
86
- border-top-color: wp-colors.$gray-300;
87
- border-top-left-radius: 3px;
88
- border-top-right-radius: 3px;
89
- }
90
-
91
- &:last-of-type {
92
- border-bottom-color: wp-colors.$gray-300;
93
- border-bottom-left-radius: 3px;
94
- border-bottom-right-radius: 3px;
95
- }
96
-
97
- & + & {
98
- margin-top: -1px;
99
- }
100
- }
101
-
102
80
  &.newspack-card__is-disabled,
103
81
  &.newspack-card__is-primary,
104
82
  &.newspack-card__is-secondary,