newspack-components 4.7.0 → 4.8.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 (251) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/DEVELOPMENT.md +42 -21
  3. package/dist/cjs/_mixins.scss +13 -0
  4. package/dist/cjs/_variables.scss +12 -0
  5. package/dist/cjs/action-card/index.js +18 -10
  6. package/dist/cjs/action-card/index.test.js +91 -0
  7. package/dist/cjs/action-card/style.scss +2 -40
  8. package/dist/cjs/button/index.js +8 -3
  9. package/dist/cjs/button/index.test.js +61 -5
  10. package/dist/cjs/card/core-card.js +0 -2
  11. package/dist/cjs/card/style-core.scss +7 -5
  12. package/dist/cjs/card-feature/index.js +75 -61
  13. package/dist/cjs/card-feature/index.test.js +396 -54
  14. package/dist/cjs/card-feature/style.scss +20 -31
  15. package/dist/cjs/card-form/index.js +5 -5
  16. package/dist/cjs/card-sortable-list/index.js +5 -3
  17. package/dist/cjs/card-sortable-list/index.test.js +50 -0
  18. package/dist/cjs/collapsible-group/context.js +19 -0
  19. package/dist/cjs/collapsible-group/index.js +24 -0
  20. package/dist/cjs/collapsible-group/index.test.js +215 -0
  21. package/dist/cjs/collapsible-group/item.js +61 -0
  22. package/dist/cjs/collapsible-group/root.js +62 -0
  23. package/dist/cjs/collapsible-group/style.scss +47 -0
  24. package/dist/cjs/collapsible-group/types.js +5 -0
  25. package/dist/cjs/empty-state/actions.js +45 -0
  26. package/dist/cjs/empty-state/context.js +41 -0
  27. package/dist/cjs/empty-state/header.js +66 -0
  28. package/dist/cjs/empty-state/index.js +31 -0
  29. package/dist/cjs/empty-state/index.test.js +303 -0
  30. package/dist/cjs/empty-state/root.js +56 -0
  31. package/dist/cjs/empty-state/style.scss +51 -0
  32. package/dist/cjs/empty-state/types.js +5 -0
  33. package/dist/cjs/index.js +34 -20
  34. package/dist/cjs/info-button/index.js +59 -43
  35. package/dist/cjs/info-button/index.test.js +199 -0
  36. package/dist/cjs/info-button/style.scss +41 -9
  37. package/dist/cjs/modal/style.scss +12 -12
  38. package/dist/cjs/newspack-icon/style.scss +4 -4
  39. package/dist/cjs/notice/style.scss +3 -3
  40. package/dist/cjs/page/style.scss +16 -16
  41. package/dist/cjs/plugin-installer/index.js +5 -9
  42. package/dist/cjs/plugin-installer/style.scss +3 -14
  43. package/dist/cjs/section-header/index.js +31 -18
  44. package/dist/cjs/section-header/index.test.js +40 -0
  45. package/dist/cjs/section-header/style.scss +22 -6
  46. package/dist/cjs/settings/DateRangeSetting.js +309 -0
  47. package/dist/cjs/settings/SettingsSection.js +19 -5
  48. package/dist/cjs/settings/SettingsSection.test.js +49 -0
  49. package/dist/cjs/settings/index.js +3 -1
  50. package/dist/cjs/settings/style.scss +40 -7
  51. package/dist/cjs/stat-card/body.js +41 -0
  52. package/dist/cjs/stat-card/constants.js +11 -0
  53. package/dist/cjs/stat-card/context.js +54 -0
  54. package/dist/cjs/stat-card/delta.js +91 -0
  55. package/dist/cjs/stat-card/footer.js +72 -0
  56. package/dist/cjs/stat-card/index.js +47 -0
  57. package/dist/cjs/stat-card/index.test.js +938 -0
  58. package/dist/cjs/stat-card/label.js +66 -0
  59. package/dist/cjs/stat-card/root.js +69 -0
  60. package/dist/cjs/stat-card/secondary.js +38 -0
  61. package/dist/cjs/stat-card/style.scss +89 -0
  62. package/dist/cjs/stat-card/types.js +5 -0
  63. package/dist/cjs/stat-card/value.js +78 -0
  64. package/dist/cjs/status-indicator/index.js +66 -0
  65. package/dist/cjs/status-indicator/index.test.js +101 -0
  66. package/dist/cjs/status-indicator/statuses.js +39 -0
  67. package/dist/cjs/status-indicator/style.scss +9 -0
  68. package/dist/cjs/status-indicator/types.js +5 -0
  69. package/dist/cjs/style.scss +9 -9
  70. package/dist/cjs/tabbed-navigation/style.scss +6 -4
  71. package/dist/cjs/types.js +5 -0
  72. package/dist/cjs/web-preview/style.scss +11 -11
  73. package/dist/cjs/with-wizard/style.scss +10 -10
  74. package/dist/cjs/with-wizard-screen/style.scss +9 -9
  75. package/dist/cjs/wizard/index.js +33 -4
  76. package/dist/cjs/wizard/store/index.js +23 -1
  77. package/dist/esm/_mixins.scss +13 -0
  78. package/dist/esm/_variables.scss +12 -0
  79. package/dist/esm/action-card/index.js +18 -10
  80. package/dist/esm/action-card/index.test.js +89 -0
  81. package/dist/esm/action-card/style.scss +2 -40
  82. package/dist/esm/button/index.js +9 -4
  83. package/dist/esm/button/index.test.js +63 -5
  84. package/dist/esm/card/core-card.js +0 -2
  85. package/dist/esm/card/style-core.scss +7 -5
  86. package/dist/esm/card-feature/index.js +78 -62
  87. package/dist/esm/card-feature/index.test.js +397 -55
  88. package/dist/esm/card-feature/style.scss +20 -31
  89. package/dist/esm/card-form/index.js +5 -5
  90. package/dist/esm/card-sortable-list/index.js +6 -4
  91. package/dist/esm/card-sortable-list/index.test.js +46 -0
  92. package/dist/esm/collapsible-group/context.js +13 -0
  93. package/dist/esm/collapsible-group/index.js +17 -0
  94. package/dist/esm/collapsible-group/index.test.js +215 -0
  95. package/dist/esm/collapsible-group/item.js +53 -0
  96. package/dist/esm/collapsible-group/root.js +54 -0
  97. package/dist/esm/collapsible-group/style.scss +47 -0
  98. package/dist/esm/collapsible-group/types.js +1 -0
  99. package/dist/esm/empty-state/actions.js +37 -0
  100. package/dist/esm/empty-state/context.js +35 -0
  101. package/dist/esm/empty-state/header.js +58 -0
  102. package/dist/esm/empty-state/index.js +23 -0
  103. package/dist/esm/empty-state/index.test.js +299 -0
  104. package/dist/esm/empty-state/root.js +56 -0
  105. package/dist/esm/empty-state/style.scss +51 -0
  106. package/dist/esm/empty-state/types.js +1 -0
  107. package/dist/esm/index.js +4 -2
  108. package/dist/esm/info-button/index.js +54 -43
  109. package/dist/esm/info-button/index.test.js +195 -0
  110. package/dist/esm/info-button/style.scss +41 -9
  111. package/dist/esm/modal/style.scss +12 -12
  112. package/dist/esm/newspack-icon/style.scss +4 -4
  113. package/dist/esm/notice/style.scss +3 -3
  114. package/dist/esm/page/style.scss +16 -16
  115. package/dist/esm/plugin-installer/index.js +5 -9
  116. package/dist/esm/plugin-installer/style.scss +3 -14
  117. package/dist/esm/section-header/index.js +27 -14
  118. package/dist/esm/section-header/index.test.js +36 -0
  119. package/dist/esm/section-header/style.scss +22 -6
  120. package/dist/esm/settings/DateRangeSetting.js +304 -0
  121. package/dist/esm/settings/SettingsSection.js +16 -3
  122. package/dist/esm/settings/SettingsSection.test.js +45 -0
  123. package/dist/esm/settings/index.js +3 -1
  124. package/dist/esm/settings/style.scss +40 -7
  125. package/dist/esm/stat-card/body.js +36 -0
  126. package/dist/esm/stat-card/constants.js +5 -0
  127. package/dist/esm/stat-card/context.js +48 -0
  128. package/dist/esm/stat-card/delta.js +86 -0
  129. package/dist/esm/stat-card/footer.js +67 -0
  130. package/dist/esm/stat-card/index.js +33 -0
  131. package/dist/esm/stat-card/index.test.js +939 -0
  132. package/dist/esm/stat-card/label.js +61 -0
  133. package/dist/esm/stat-card/root.js +64 -0
  134. package/dist/esm/stat-card/secondary.js +33 -0
  135. package/dist/esm/stat-card/style.scss +89 -0
  136. package/dist/esm/stat-card/types.js +1 -0
  137. package/dist/esm/stat-card/value.js +73 -0
  138. package/dist/esm/status-indicator/index.js +52 -0
  139. package/dist/esm/status-indicator/index.test.js +96 -0
  140. package/dist/esm/status-indicator/statuses.js +33 -0
  141. package/dist/esm/status-indicator/style.scss +9 -0
  142. package/dist/esm/status-indicator/types.js +1 -0
  143. package/dist/esm/style.scss +9 -9
  144. package/dist/esm/tabbed-navigation/style.scss +6 -4
  145. package/dist/esm/types.js +1 -0
  146. package/dist/esm/web-preview/style.scss +11 -11
  147. package/dist/esm/with-wizard/style.scss +10 -10
  148. package/dist/esm/with-wizard-screen/style.scss +9 -9
  149. package/dist/esm/wizard/index.js +38 -7
  150. package/dist/esm/wizard/store/index.js +23 -1
  151. package/package.json +1 -1
  152. package/src/_mixins.scss +13 -0
  153. package/src/_variables.scss +12 -0
  154. package/src/action-card/action-card.d.ts +3 -2
  155. package/src/action-card/index.js +12 -12
  156. package/src/action-card/index.test.js +56 -0
  157. package/src/action-card/style.scss +2 -40
  158. package/src/button/index.test.js +52 -5
  159. package/src/button/index.tsx +9 -4
  160. package/src/card/core-card.js +0 -2
  161. package/src/card/style-core.scss +7 -5
  162. package/src/card-feature/README.md +68 -27
  163. package/src/card-feature/index.test.js +242 -27
  164. package/src/card-feature/index.tsx +77 -67
  165. package/src/card-feature/style.scss +20 -31
  166. package/src/card-form/README.md +10 -10
  167. package/src/card-form/index.tsx +4 -8
  168. package/src/card-sortable-list/README.md +4 -5
  169. package/src/card-sortable-list/index.test.js +29 -0
  170. package/src/card-sortable-list/index.tsx +7 -4
  171. package/src/collapsible-group/README.md +50 -0
  172. package/src/collapsible-group/context.ts +13 -0
  173. package/src/collapsible-group/index.test.js +168 -0
  174. package/src/collapsible-group/index.tsx +17 -0
  175. package/src/collapsible-group/item.tsx +42 -0
  176. package/src/collapsible-group/root.tsx +42 -0
  177. package/src/collapsible-group/style.scss +47 -0
  178. package/src/collapsible-group/types.ts +22 -0
  179. package/src/drawer/README.md +2 -2
  180. package/src/empty-state/README.md +165 -0
  181. package/src/empty-state/actions.tsx +37 -0
  182. package/src/empty-state/context.ts +40 -0
  183. package/src/empty-state/header.tsx +48 -0
  184. package/src/empty-state/index.test.js +247 -0
  185. package/src/empty-state/index.tsx +22 -0
  186. package/src/empty-state/root.tsx +39 -0
  187. package/src/empty-state/style.scss +51 -0
  188. package/src/empty-state/types.ts +33 -0
  189. package/src/index.js +4 -2
  190. package/src/info-button/README.md +115 -0
  191. package/src/info-button/index.test.js +163 -0
  192. package/src/info-button/index.tsx +64 -0
  193. package/src/info-button/style.scss +41 -9
  194. package/src/modal/style.scss +12 -12
  195. package/src/newspack-icon/style.scss +4 -4
  196. package/src/notice/style.scss +3 -3
  197. package/src/page/style.scss +16 -16
  198. package/src/plugin-installer/index.js +3 -9
  199. package/src/plugin-installer/style.scss +3 -14
  200. package/src/section-header/index.js +27 -14
  201. package/src/section-header/index.test.js +28 -0
  202. package/src/section-header/style.scss +22 -6
  203. package/src/settings/DateRangeSetting.js +266 -0
  204. package/src/settings/SettingsSection.js +24 -4
  205. package/src/settings/SettingsSection.test.js +45 -0
  206. package/src/settings/index.js +2 -0
  207. package/src/settings/style.scss +40 -7
  208. package/src/stat-card/README.md +434 -0
  209. package/src/stat-card/body.tsx +28 -0
  210. package/src/stat-card/constants.ts +5 -0
  211. package/src/stat-card/context.ts +53 -0
  212. package/src/stat-card/delta.tsx +88 -0
  213. package/src/stat-card/footer.tsx +68 -0
  214. package/src/stat-card/index.test.js +818 -0
  215. package/src/stat-card/index.tsx +48 -0
  216. package/src/stat-card/label.tsx +61 -0
  217. package/src/stat-card/root.tsx +40 -0
  218. package/src/stat-card/secondary.tsx +27 -0
  219. package/src/stat-card/style.scss +89 -0
  220. package/src/stat-card/types.ts +84 -0
  221. package/src/stat-card/value.tsx +72 -0
  222. package/src/status-indicator/README.md +123 -0
  223. package/src/status-indicator/index.test.js +70 -0
  224. package/src/status-indicator/index.tsx +39 -0
  225. package/src/status-indicator/statuses.ts +35 -0
  226. package/src/status-indicator/style.scss +9 -0
  227. package/src/status-indicator/types.ts +28 -0
  228. package/src/style.scss +9 -9
  229. package/src/tabbed-navigation/style.scss +6 -4
  230. package/src/types.ts +25 -0
  231. package/src/web-preview/style.scss +11 -11
  232. package/src/with-wizard/style.scss +10 -10
  233. package/src/with-wizard-screen/style.scss +9 -9
  234. package/src/wizard/index.js +49 -7
  235. package/src/wizard/store/index.js +26 -1
  236. package/dist/cjs/accordion/index.js +0 -72
  237. package/dist/cjs/accordion/index.test.js +0 -80
  238. package/dist/cjs/accordion/style.scss +0 -35
  239. package/dist/cjs/badge/index.js +0 -32
  240. package/dist/cjs/badge/style.scss +0 -36
  241. package/dist/esm/accordion/index.js +0 -63
  242. package/dist/esm/accordion/index.test.js +0 -79
  243. package/dist/esm/accordion/style.scss +0 -35
  244. package/dist/esm/badge/index.js +0 -24
  245. package/dist/esm/badge/style.scss +0 -36
  246. package/src/accordion/index.js +0 -46
  247. package/src/accordion/index.test.js +0 -62
  248. package/src/accordion/style.scss +0 -35
  249. package/src/badge/index.tsx +0 -26
  250. package/src/badge/style.scss +0 -36
  251. package/src/info-button/index.js +0 -38
@@ -3,18 +3,18 @@
3
3
  */
4
4
 
5
5
  @use "~@wordpress/base-styles/colors" as wp-colors;
6
- @use "../../../colors/colors.module" as colors;
6
+ @use "../../../colors/colors.module" as np-colors;
7
7
 
8
8
  .newspack-wizard {
9
9
  // Color
10
- --wp-admin-theme-color: #{colors.$primary-600};
11
- --wp-admin-theme-color--rgb: #{colors.$primary-600--rgb};
12
- --wp-admin-theme-color-darker-10: #{colors.$primary-700};
13
- --wp-admin-theme-color-darker-10--rgb: #{colors.$primary-700--rgb};
14
- --wp-admin-theme-color-darker-20: #{colors.$primary-800};
15
- --wp-admin-theme-color-darker-20--rgb: #{colors.$primary-800--rgb};
16
- --wp-admin-theme-color-lighter-10: #{colors.$primary-000};
17
- --wp-admin-theme-color-lighter-10--rgb: #{colors.$primary-000--rgb};
10
+ --wp-admin-theme-color: #{np-colors.$primary-600};
11
+ --wp-admin-theme-color--rgb: #{np-colors.$primary-600--rgb};
12
+ --wp-admin-theme-color-darker-10: #{np-colors.$primary-700};
13
+ --wp-admin-theme-color-darker-10--rgb: #{np-colors.$primary-700--rgb};
14
+ --wp-admin-theme-color-darker-20: #{np-colors.$primary-800};
15
+ --wp-admin-theme-color-darker-20--rgb: #{np-colors.$primary-800--rgb};
16
+ --wp-admin-theme-color-lighter-10: #{np-colors.$primary-000};
17
+ --wp-admin-theme-color-lighter-10--rgb: #{np-colors.$primary-000--rgb};
18
18
 
19
19
  color: wp-colors.$gray-900;
20
20
 
@@ -11,16 +11,18 @@ import classnames from 'classnames';
11
11
  * WordPress dependencies.
12
12
  */
13
13
  // Notice is aliased: `Notice` below is Newspack's own, which this file also uses.
14
- import { DropdownMenu, MenuGroup, MenuItem, Notice as CoreNotice, SlotFillProvider, createSlotFill } from '@wordpress/components';
14
+ import { DropdownMenu, MenuGroup, MenuItem, Notice as CoreNotice, SlotFillProvider, createSlotFill,
15
+ // eslint-disable-next-line @wordpress/no-unsafe-wp-apis
16
+ __experimentalVStack as VStack } from '@wordpress/components';
15
17
  import { useDispatch, useSelect } from '@wordpress/data';
16
- import { cloneElement, createInterpolateElement, isValidElement, useRef, useEffect, useState, forwardRef } from '@wordpress/element';
18
+ import { cloneElement, createInterpolateElement, isValidElement, useEffect, useRef, useState, forwardRef } from '@wordpress/element';
17
19
  import { __ } from '@wordpress/i18n';
18
20
  import { category, chevronLeft, moreVertical } from '@wordpress/icons';
19
21
 
20
22
  /**
21
23
  * Internal dependencies
22
24
  */
23
- import { Footer, Notice, Button, TabbedNavigation, PluginInstaller, SectionHeader, HandoffMessage, Page } from '../';
25
+ import { Footer, Notice, Button, TabbedNavigation, PluginInstaller, SectionHeader, HandoffMessage, Page, Waiting } from '../';
24
26
  import { activeBreadcrumbs, appendSectionName } from './breadcrumbs-select';
25
27
  import Router from '../proxied-imports/router';
26
28
  import registerStore, { WIZARD_STORE_NAMESPACE } from './store';
@@ -204,6 +206,7 @@ var Wizard = function Wizard(_ref2, ref) {
204
206
  sectionDescription = headerData.sectionDescription,
205
207
  sectionMenu = headerData.sectionMenu,
206
208
  sectionName = headerData.sectionName,
209
+ sectionSize = headerData.sectionSize,
207
210
  sectionTitle = headerData.sectionTitle,
208
211
  sectionPrimaryAction = headerData.sectionPrimaryAction,
209
212
  sectionSecondaryAction = headerData.sectionSecondaryAction;
@@ -226,6 +229,14 @@ var Wizard = function Wizard(_ref2, ref) {
226
229
  _useState2 = _slicedToArray(_useState, 2),
227
230
  pluginRequirementsSatisfied = _useState2[0],
228
231
  setPluginRequirementsSatisfied = _useState2[1];
232
+ // Whether the requirements check has reported back. Until it has, the check
233
+ // shows the same fetching treatment as the rest of the wizard rather than
234
+ // the installer's own spinner and the swapped header; the installer only
235
+ // surfaces once a plugin is actually found missing.
236
+ var _useState3 = useState(requiredPlugins.length === 0),
237
+ _useState4 = _slicedToArray(_useState3, 2),
238
+ pluginRequirementsKnown = _useState4[0],
239
+ setPluginRequirementsKnown = _useState4[1];
229
240
 
230
241
  // The data fetch above runs once per mount, while the required plugins are
231
242
  // still missing — endpoints that need them answer empty or error outright,
@@ -240,6 +251,7 @@ var Wizard = function Wizard(_ref2, ref) {
240
251
  // Leave the installer first: whatever happens to the refetch, the user
241
252
  // should not be stranded on a required-plugins screen for a plugin they
242
253
  // just installed.
254
+ setPluginRequirementsKnown(true);
243
255
  setPluginRequirementsSatisfied(complete);
244
256
  if (!complete) {
245
257
  requirementsWereUnmet.current = true;
@@ -251,13 +263,31 @@ var Wizard = function Wizard(_ref2, ref) {
251
263
  }
252
264
  };
253
265
  if (!pluginRequirementsSatisfied) {
254
- headerText = requiredPlugins.length > 1 ? __('Required plugins', 'newspack-plugin') : __('Required plugin', 'newspack-plugin');
266
+ if (pluginRequirementsKnown) {
267
+ headerText = requiredPlugins.length > 1 ? __('Required plugins', 'newspack-plugin') : __('Required plugin', 'newspack-plugin');
268
+ }
255
269
  displayedSections = [{
256
270
  path: '/',
257
271
  render: function render() {
258
- return /*#__PURE__*/_jsx(PluginInstaller, {
259
- plugins: requiredPlugins,
260
- onStatus: onPluginStatus
272
+ return /*#__PURE__*/_jsxs(_Fragment, {
273
+ children: [!pluginRequirementsKnown && /*#__PURE__*/_jsx("div", {
274
+ className: "newspack-wizard__loader",
275
+ children: /*#__PURE__*/_jsxs(VStack, {
276
+ alignment: "center",
277
+ spacing: 2,
278
+ children: [/*#__PURE__*/_jsx(Waiting, {
279
+ noMargin: true
280
+ }), /*#__PURE__*/_jsx("strong", {
281
+ children: __('Fetching…', 'newspack-plugin')
282
+ })]
283
+ })
284
+ }), /*#__PURE__*/_jsx("div", {
285
+ hidden: !pluginRequirementsKnown,
286
+ children: /*#__PURE__*/_jsx(PluginInstaller, {
287
+ plugins: requiredPlugins,
288
+ onStatus: onPluginStatus
289
+ })
290
+ })]
261
291
  });
262
292
  }
263
293
  }];
@@ -321,6 +351,7 @@ var Wizard = function Wizard(_ref2, ref) {
321
351
  primaryAction: sectionPrimaryAction || section.primaryAction,
322
352
  secondaryAction: sectionSecondaryAction || section.secondaryAction,
323
353
  heading: 2,
354
+ size: sectionSize || section.size,
324
355
  noMargin: true
325
356
  }), /*#__PURE__*/_jsx(SectionComponent, _objectSpread(_objectSpread(_objectSpread({}, routerProps), sectionProps), sharedProps))]
326
357
  });
@@ -257,6 +257,28 @@ var store = createReduxStore(WIZARD_STORE_NAMESPACE, {
257
257
  })
258
258
  }
259
259
  });
260
+
261
+ /**
262
+ * Register the wizard store, unless it already exists.
263
+ *
264
+ * This package is bundled separately into each consuming plugin, so more than one
265
+ * bundle can call this on the same page (e.g. the block editor loads assets from
266
+ * both newspack-blocks and newspack-manager). @wordpress/data keeps a single
267
+ * registry on `wp.data`, so the second call is a no-op that logs
268
+ * `Store "newspack/wizards" is already registered.` — guard it instead.
269
+ *
270
+ * The first bundle to register wins, and it is not necessarily the newest one:
271
+ * consumers rebuild on their own schedules, so an older copy of this package can
272
+ * get there first and leave newer selectors missing. That is worth knowing about
273
+ * while developing, so keep the breadcrumb outside production builds.
274
+ */
260
275
  export default (function () {
261
- return register(store);
276
+ if (select(WIZARD_STORE_NAMESPACE)) {
277
+ if ('production' !== process.env.NODE_ENV) {
278
+ // eslint-disable-next-line no-console
279
+ console.warn("Wizard store: \"".concat(WIZARD_STORE_NAMESPACE, "\" is already registered, so this copy of newspack-components was ignored. Two bundles of the package are on this page and the first one to load wins."));
280
+ }
281
+ return;
282
+ }
283
+ register(store);
262
284
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newspack-components",
3
- "version": "4.7.0",
3
+ "version": "4.8.0",
4
4
  "description": "Newspack design system components",
5
5
  "author": "Automattic",
6
6
  "license": "GPL-2.0-or-later",
@@ -0,0 +1,13 @@
1
+ @use "../../colors/colors.module" as colors;
2
+ @use "~@wordpress/base-styles/colors" as wp-colors;
3
+ @use "~@wordpress/base-styles/variables" as wp-vars;
4
+
5
+ @mixin text-muted {
6
+ /* stylelint-disable-next-line plugin-wpds/no-unknown-ds-tokens -- the plugin's token list lags the prebuilt design-tokens.css names. */
7
+ color: var( --wpds-color-foreground-content-neutral-weak, #{wp-colors.$gray-700} );
8
+ }
9
+
10
+ @mixin wpds-focus-ring {
11
+ /* stylelint-disable-next-line plugin-wpds/no-unknown-ds-tokens -- the plugin's token list lags the prebuilt design-tokens.css names. */
12
+ outline: var( --wpds-border-width-focus, var( --wp-admin-border-width-focus, #{wp-vars.$border-width-focus-fallback} ) ) solid var( --wpds-color-stroke-focus, var( --wp-admin-theme-color, #{colors.$primary-600} ) );
13
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * SCSS Variables.
3
+ *
4
+ * The steps this package needs above the @wordpress/base-styles scale. Please
5
+ * reach for a base-styles variable first, and only add here when there is no
6
+ * counterpart and you're pretty sure the value will be reused in many places.
7
+ * It's purely for UI. Don't add component-specific values, which belong in that
8
+ * component's own stylesheet.
9
+ */
10
+
11
+ $font-size-3x-large: 48px;
12
+ $font-line-height-3x-large: 1.125;
@@ -1,3 +1,5 @@
1
+ import type { CardBadge } from '../types';
2
+
1
3
  export interface ActionCardProps {
2
4
  id?: string | number;
3
5
  title?: string | React.ReactNode;
@@ -5,8 +7,7 @@ export interface ActionCardProps {
5
7
  href?: string;
6
8
  description?: string | React.ReactNode;
7
9
  actionText?: React.ReactNode | string | null;
8
- badge?: string;
9
- badgeLevel?: 'success' | 'info' | 'warning' | 'error';
10
+ badges?: CardBadge[];
10
11
  className?: string;
11
12
  indent?: string;
12
13
  notification?: string | Error | null;
@@ -9,6 +9,7 @@ import { Draggable, ExternalLink, ToggleControl } from '@wordpress/components';
9
9
  import { useEffect, useState } from '@wordpress/element';
10
10
  import { __ } from '@wordpress/i18n';
11
11
  import { Icon, check, chevronDown, chevronUp, dragHandle } from '@wordpress/icons';
12
+ import { Badge } from '@wordpress/ui';
12
13
 
13
14
  /**
14
15
  * Internal dependencies
@@ -28,8 +29,7 @@ import classnames from 'classnames';
28
29
  * @return {JSX.Element} ActionCard component.
29
30
  */
30
31
  const ActionCard = ( {
31
- badge,
32
- badgeLevel,
32
+ badges,
33
33
  className,
34
34
  checkbox,
35
35
  children,
@@ -78,6 +78,11 @@ const ActionCard = ( {
78
78
  dragWrapperRef,
79
79
  onDragCallback,
80
80
  } ) => {
81
+ // A badge with no label paints an empty pill, since the library Badge styles its
82
+ // wrapper rather than its text. Callers derive labels from data that can be absent
83
+ // (a gateway with no connection status, an unmapped placement), so drop those here
84
+ // rather than asking every caller to guard its own array.
85
+ const visibleBadges = ( badges || [] ).filter( badge => badge?.label );
81
86
  const [ expanded, setExpanded ] = useState( Boolean( isExpanded ) );
82
87
  const [ dragging, setDragging ] = useState( false );
83
88
  const [ targetIndex, setTargetIndex ] = useState( null );
@@ -124,7 +129,6 @@ const ActionCard = ( {
124
129
  const togglePositionClass = togglePosition === 'trailing' ? 'is-toggle-trailing' : 'is-toggle-leading';
125
130
  const hasInternalLink = href && href.indexOf( 'http' ) !== 0;
126
131
  const isDisplayingSecondaryAction = secondaryActionText && onSecondaryActionClick;
127
- const badges = ! Array.isArray( badge ) && badge ? [ badge ] : badge;
128
132
  const HeadingTag = `h${ heading }`;
129
133
 
130
134
  const cardContent = (
@@ -166,15 +170,11 @@ const ActionCard = ( {
166
170
  ) }
167
171
  { ! titleLink && ! expandable && title }
168
172
  </span>
169
- { badges?.length &&
170
- badges.map( ( badgeText, i ) => (
171
- <span
172
- key={ `badge-${ i }` }
173
- className={ `newspack-action-card__badge newspack-action-card__badge-level-${ badgeLevel }` }
174
- >
175
- { badgeText }
176
- </span>
177
- ) ) }
173
+ { visibleBadges.map( ( { label, intent }, i ) => (
174
+ <Badge key={ `badge-${ i }` } intent={ intent ?? 'none' }>
175
+ { label }
176
+ </Badge>
177
+ ) ) }
178
178
  </HeadingTag>
179
179
  { description && (
180
180
  <p>
@@ -0,0 +1,56 @@
1
+ /**
2
+ * External dependencies.
3
+ */
4
+ import { render, screen } from '@testing-library/react';
5
+
6
+ /**
7
+ * Internal dependencies.
8
+ */
9
+ import ActionCard from './index';
10
+
11
+ /**
12
+ * The library Badge styles its wrapper rather than its text, so a badge with no label
13
+ * paints a bare coloured pill. Callers build the `badges` array from data that can be
14
+ * absent — a payment gateway reports no connection status until it is enabled, a prompt
15
+ * can sit on a placement with no entry in the placement map — and an array literal is
16
+ * always non-empty, so the component has to drop those itself.
17
+ */
18
+ describe( 'ActionCard badges', () => {
19
+ const badgeText = () => screen.queryAllByText( ( _, node ) => /__badge/.test( node?.className || '' ) );
20
+
21
+ it( 'renders a badge that has a label', () => {
22
+ render( <ActionCard title="Stripe" badges={ [ { label: 'Connected', intent: 'stable' } ] } /> );
23
+
24
+ expect( screen.getByText( 'Connected' ) ).toBeInTheDocument();
25
+ } );
26
+
27
+ it( 'renders no badge when the label is null', () => {
28
+ const { container } = render( <ActionCard title="Stripe" badges={ [ { label: null, intent: 'informational' } ] } /> );
29
+
30
+ expect( container.querySelectorAll( '[class*="__badge"]' ) ).toHaveLength( 0 );
31
+ } );
32
+
33
+ it( 'renders no badge when the label is undefined or empty', () => {
34
+ const { container } = render( <ActionCard title="Stripe" badges={ [ { label: undefined }, { label: '' } ] } /> );
35
+
36
+ expect( container.querySelectorAll( '[class*="__badge"]' ) ).toHaveLength( 0 );
37
+ } );
38
+
39
+ it( 'keeps the labelled badges and drops only the empty ones', () => {
40
+ render( <ActionCard title="Plans" badges={ [ { label: 'Premium' }, { label: null }, { label: 'Archived', intent: 'draft' } ] } /> );
41
+
42
+ expect( screen.getByText( 'Premium' ) ).toBeInTheDocument();
43
+ expect( screen.getByText( 'Archived' ) ).toBeInTheDocument();
44
+ expect( badgeText() ).toHaveLength( 2 );
45
+ } );
46
+
47
+ it( 'renders nothing for an absent or empty badges prop', () => {
48
+ const { container: withoutProp } = render( <ActionCard title="Stripe" /> );
49
+ expect( withoutProp.querySelectorAll( '[class*="__badge"]' ) ).toHaveLength( 0 );
50
+
51
+ const { container: withEmptyArray } = render( <ActionCard title="Stripe" badges={ [] } /> );
52
+ expect( withEmptyArray.querySelectorAll( '[class*="__badge"]' ) ).toHaveLength( 0 );
53
+ // An empty array used to fall through as a literal `0` in the heading.
54
+ expect( withEmptyArray.textContent ).not.toContain( '0' );
55
+ } );
56
+ } );
@@ -4,6 +4,7 @@
4
4
 
5
5
  @use "sass:color";
6
6
  @use "~@wordpress/base-styles/colors" as wp-colors;
7
+ @use "~@wordpress/base-styles/variables" as wp-vars;
7
8
 
8
9
  .newspack-card.newspack-action-card {
9
10
  padding: 0;
@@ -196,11 +197,7 @@
196
197
  align-items: center;
197
198
  display: flex;
198
199
  flex-wrap: wrap;
199
- margin-right: -8px;
200
-
201
- .newspack-action-card__title {
202
- margin-right: 8px;
203
- }
200
+ gap: wp-vars.$grid-unit-10;
204
201
 
205
202
  a {
206
203
  color: inherit;
@@ -372,41 +369,6 @@
372
369
  margin: 0;
373
370
  }
374
371
 
375
- /* Badge */
376
- .newspack-action-card__badge {
377
- background: wp-colors.$gray-100;
378
- border-radius: 2px;
379
- color: wp-colors.$gray-900;
380
- display: block;
381
- flex: 0 0 auto;
382
- font-size: 12px;
383
- font-weight: 400;
384
- line-height: 1.333;
385
- margin: 0 8px 0 0;
386
- padding: 2px 4px;
387
-
388
- &.newspack-action-card__badge-level-warning {
389
- background-color: color-mix(in srgb, #fff 90%, wp-colors.$alert-yellow);
390
- color: color-mix(in srgb, #000 50%, wp-colors.$alert-yellow);
391
- }
392
- &.newspack-action-card__badge-level-error {
393
- background-color: color-mix(in srgb, #fff 90%, wp-colors.$alert-red);
394
- color: color-mix(in srgb, #000 50%, wp-colors.$alert-red);
395
- }
396
- &.newspack-action-card__badge-level-success {
397
- background-color: color-mix(in srgb, #fff 90%, wp-colors.$alert-green);
398
- color: color-mix(in srgb, #000 50%, wp-colors.$alert-green);
399
- }
400
- &.newspack-action-card__badge-level-brand {
401
- background-color: color-mix(in srgb, #fff 90%, var(--wp-admin-theme-color));
402
- color: color-mix(in srgb, #000 50%, var(--wp-admin-theme-color));
403
- }
404
-
405
- + .newspack-action-card__badge {
406
- margin-left: -4px;
407
- }
408
- }
409
-
410
372
  /* Description Card */
411
373
  &.newspack-description-card {
412
374
  font-style: italic;
@@ -4,6 +4,11 @@
4
4
  import { act, render, screen } from '@testing-library/react';
5
5
  import { MemoryRouter, useHistory } from 'react-router-dom';
6
6
 
7
+ /**
8
+ * WordPress dependencies.
9
+ */
10
+ import { createRef } from '@wordpress/element';
11
+
7
12
  /**
8
13
  * Internal dependencies.
9
14
  */
@@ -20,6 +25,16 @@ const clickButton = async () => {
20
25
  } );
21
26
  };
22
27
 
28
+ // React formats its warnings with `%s` placeholders and passes the values as
29
+ // further arguments, so assert against the whole call rather than one argument.
30
+ const messagesFrom = spy => spy.mock.calls.flat().join( ' ' );
31
+
32
+ // A failing assertion skips any `mockRestore()` left after it, which would leave
33
+ // the console swallowed for every later test in the file.
34
+ afterEach( () => {
35
+ jest.restoreAllMocks();
36
+ } );
37
+
23
38
  describe( 'Button href with onClick', () => {
24
39
  it( 'awaits the onClick, then pushes the route', async () => {
25
40
  const historyRef = { current: null };
@@ -113,7 +128,6 @@ describe( 'Button href with onClick outside a router', () => {
113
128
  expect( window.location.href ).toBe( '' );
114
129
  } );
115
130
  expect( warn ).toHaveBeenCalledWith( expect.stringContaining( 'javascript:' ) );
116
- warn.mockRestore();
117
131
  } );
118
132
  } );
119
133
 
@@ -121,19 +135,18 @@ describe( 'Button with a javascript: href alone', () => {
121
135
  const link = () => screen.getByText( 'Save' ).closest( 'a' );
122
136
 
123
137
  it( 'renders no link', () => {
124
- const warn = jest.spyOn( console, 'warn' ).mockImplementation( () => {} );
138
+ // Silenced only; the assertion below is about the markup, not the warning.
139
+ jest.spyOn( console, 'warn' ).mockImplementation( () => {} );
125
140
  render( <Button href="JavaScript:alert(1)">Save</Button> );
126
141
 
127
142
  expect( link() ).toBeNull();
128
- warn.mockRestore();
129
143
  } );
130
144
 
131
145
  it( 'sees through interleaved control characters', () => {
132
- const warn = jest.spyOn( console, 'warn' ).mockImplementation( () => {} );
146
+ jest.spyOn( console, 'warn' ).mockImplementation( () => {} );
133
147
  render( <Button href={ 'java\nscript:alert(1)' }>Save</Button> );
134
148
 
135
149
  expect( link() ).toBeNull();
136
- warn.mockRestore();
137
150
  } );
138
151
 
139
152
  it( 'leaves an ordinary href alone', () => {
@@ -141,3 +154,37 @@ describe( 'Button with a javascript: href alone', () => {
141
154
  expect( link() ).toHaveAttribute( 'href', '/wp-admin/admin.php?page=next' );
142
155
  } );
143
156
  } );
157
+
158
+ describe( 'Button loading prop', () => {
159
+ const button = () => screen.getByRole( 'button', { name: 'Save' } );
160
+
161
+ it( 'renders the core busy state without putting `loading` on the DOM node', () => {
162
+ const error = jest.spyOn( console, 'error' ).mockImplementation( () => {} );
163
+ const { rerender } = render( <Button>Save</Button> );
164
+
165
+ expect( button() ).not.toHaveClass( 'is-busy' );
166
+
167
+ rerender( <Button loading>Save</Button> );
168
+
169
+ expect( button() ).toHaveClass( 'is-busy' );
170
+ // React drops an unknown boolean-valued attribute instead of writing it, so
171
+ // the warning rather than the absent attribute is what a revert trips. It is
172
+ // also deduped per property name for the life of the module registry, which
173
+ // is why this names the message instead of asserting no errors at all.
174
+ expect( messagesFrom( error ) ).not.toContain( 'non-boolean attribute' );
175
+ } );
176
+ } );
177
+
178
+ // core's Button wraps itself in a Tooltip when it is given a `label`, and the
179
+ // Tooltip needs a ref on its child. Two call sites do that with this Button:
180
+ // the experimental-tools back button and the audience segment-group heading.
181
+ describe( 'Button ref', () => {
182
+ it( 'forwards the ref to the rendered button', () => {
183
+ const error = jest.spyOn( console, 'error' ).mockImplementation( () => {} );
184
+ const ref = createRef();
185
+ render( <Button ref={ ref }>Save</Button> );
186
+
187
+ expect( ref.current ).toBe( screen.getByRole( 'button', { name: 'Save' } ) );
188
+ expect( messagesFrom( error ) ).not.toContain( 'Function components cannot be given refs' );
189
+ } );
190
+ } );
@@ -6,7 +6,7 @@
6
6
  * WordPress dependencies.
7
7
  */
8
8
  import { Button as BaseComponent } from '@wordpress/components';
9
- import { useEffect, useState } from '@wordpress/element';
9
+ import { forwardRef, useEffect, useState } from '@wordpress/element';
10
10
 
11
11
  /**
12
12
  * Internal dependencies
@@ -32,7 +32,7 @@ const isJavascriptHref = ( href?: string ) =>
32
32
  .toLowerCase()
33
33
  .startsWith( 'javascript:' );
34
34
 
35
- const Button = ( { href, loading = undefined, onClick, ...otherProps }: Props ) => {
35
+ const Button = forwardRef< HTMLElement, Props >( ( { href, loading = undefined, onClick, ...otherProps }: Props, ref ) => {
36
36
  const history = useHistory();
37
37
  const [ isAwaitingOnClick, setIsAwaitingOnClick ] = useState( false );
38
38
 
@@ -67,8 +67,13 @@ const Button = ( { href, loading = undefined, onClick, ...otherProps }: Props )
67
67
  if ( isAwaitingOnClick ) {
68
68
  otherProps.disabled = true;
69
69
  }
70
+ // `loading` is this component's own prop name; @wordpress/components' Button
71
+ // expresses the same state as `isBusy`. Forwarding `loading` verbatim would
72
+ // land it on the DOM node, which React rejects as a non-boolean attribute.
70
73
  // @ts-expect-error - @wordpress/components' Button can only have either href or onClick, not both.
71
- return <BaseComponent loading={ loading ? true : undefined } { ...otherProps } />;
72
- };
74
+ return <BaseComponent ref={ ref } isBusy={ loading ? true : undefined } { ...otherProps } />;
75
+ } );
76
+
77
+ Button.displayName = 'Button';
73
78
 
74
79
  export default Button;
@@ -152,7 +152,6 @@ const CoreCard = ( {
152
152
  <ToggleControl
153
153
  className="newspack-card--core__action"
154
154
  label={ otherProps.title }
155
- hideLabelFromVision
156
155
  checked={ isActive }
157
156
  onChange={ onToggle }
158
157
  />
@@ -165,7 +164,6 @@ const CoreCard = ( {
165
164
  <ToggleControl
166
165
  className="newspack-card--core__action"
167
166
  label={ otherProps.title }
168
- hideLabelFromVision
169
167
  checked={ isActive }
170
168
  onChange={ onToggle }
171
169
  />
@@ -4,7 +4,6 @@
4
4
 
5
5
  @use "~@wordpress/base-styles/colors" as wp-colors;
6
6
  @use "~@wordpress/base-styles/variables" as wp-vars;
7
- @use "../../../colors/colors.module" as colors;
8
7
 
9
8
  .newspack-card--core,
10
9
  .newspack-wizard .newspack-card--core {
@@ -45,7 +44,13 @@
45
44
  }
46
45
  .newspack-card--core {
47
46
  &__header-content {
48
- * {
47
+ h1,
48
+ h2,
49
+ h3,
50
+ h4,
51
+ h5,
52
+ h6,
53
+ p {
49
54
  line-height: wp-vars.$font-line-height-small;
50
55
  }
51
56
  }
@@ -210,9 +215,6 @@
210
215
  text-decoration: underline;
211
216
  }
212
217
  }
213
- .newspack-badge {
214
- font-weight: 400;
215
- }
216
218
  }
217
219
  }
218
220
  &__is-selectable {