newspack-components 4.8.0 → 4.9.0-alpha.1

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 (103) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/DEVELOPMENT.md +18 -17
  3. package/dist/cjs/action-card/index.js +85 -18
  4. package/dist/cjs/action-card/index.test.js +192 -5
  5. package/dist/cjs/action-card/style.scss +3 -9
  6. package/dist/cjs/card-feature/index.js +28 -3
  7. package/dist/cjs/card-feature/index.test.js +100 -45
  8. package/dist/cjs/card-feature/style.scss +1 -5
  9. package/dist/cjs/debug-badge/index.js +44 -0
  10. package/dist/cjs/debug-badge/index.test.js +50 -0
  11. package/dist/cjs/debug-badge/style.scss +26 -0
  12. package/dist/cjs/empty-state/index.test.js +37 -24
  13. package/dist/cjs/empty-state/root.js +2 -1
  14. package/dist/cjs/footer/index.js +52 -16
  15. package/dist/cjs/footer/index.test.js +208 -0
  16. package/dist/cjs/footer/style.scss +17 -1
  17. package/dist/cjs/index.js +7 -0
  18. package/dist/cjs/integration-icon/index.js +4 -4
  19. package/dist/cjs/integration-icon/style.scss +18 -2
  20. package/dist/cjs/integration-icons/active-campaign.js +4 -7
  21. package/dist/cjs/integration-icons/beehiiv.js +19 -0
  22. package/dist/cjs/integration-icons/gravity-forms.js +19 -0
  23. package/dist/cjs/integration-icons/index.js +26 -6
  24. package/dist/cjs/integration-icons/salesforce.js +5 -3
  25. package/dist/cjs/notice/index.js +1 -4
  26. package/dist/cjs/notice/style.scss +0 -19
  27. package/dist/cjs/plugin-toggle/index.js +1 -1
  28. package/dist/cjs/with-wizard-screen/index.js +1 -3
  29. package/dist/cjs/with-wizard-screen/index.test.js +0 -3
  30. package/dist/cjs/wizard/breadcrumbs-select.js +17 -4
  31. package/dist/cjs/wizard/breadcrumbs-select.test.js +17 -0
  32. package/dist/cjs/wizard/index.js +50 -13
  33. package/dist/cjs/wizard/index.test.js +304 -5
  34. package/dist/cjs/wizard/store/index.js +2 -0
  35. package/dist/esm/action-card/index.js +86 -21
  36. package/dist/esm/action-card/index.test.js +192 -1
  37. package/dist/esm/action-card/style.scss +3 -9
  38. package/dist/esm/card-feature/index.js +29 -4
  39. package/dist/esm/card-feature/index.test.js +100 -45
  40. package/dist/esm/card-feature/style.scss +1 -5
  41. package/dist/esm/debug-badge/index.js +39 -0
  42. package/dist/esm/debug-badge/index.test.js +46 -0
  43. package/dist/esm/debug-badge/style.scss +26 -0
  44. package/dist/esm/empty-state/index.test.js +37 -24
  45. package/dist/esm/empty-state/root.js +2 -1
  46. package/dist/esm/footer/index.js +52 -17
  47. package/dist/esm/footer/index.test.js +210 -0
  48. package/dist/esm/footer/style.scss +17 -1
  49. package/dist/esm/index.js +1 -0
  50. package/dist/esm/integration-icon/index.js +5 -5
  51. package/dist/esm/integration-icon/style.scss +18 -2
  52. package/dist/esm/integration-icons/active-campaign.js +5 -8
  53. package/dist/esm/integration-icons/beehiiv.js +13 -0
  54. package/dist/esm/integration-icons/gravity-forms.js +13 -0
  55. package/dist/esm/integration-icons/index.js +15 -4
  56. package/dist/esm/integration-icons/salesforce.js +5 -3
  57. package/dist/esm/notice/index.js +2 -5
  58. package/dist/esm/notice/style.scss +0 -19
  59. package/dist/esm/plugin-toggle/index.js +1 -1
  60. package/dist/esm/with-wizard-screen/index.js +2 -4
  61. package/dist/esm/with-wizard-screen/index.test.js +0 -3
  62. package/dist/esm/wizard/breadcrumbs-select.js +16 -3
  63. package/dist/esm/wizard/breadcrumbs-select.test.js +18 -1
  64. package/dist/esm/wizard/index.js +53 -16
  65. package/dist/esm/wizard/index.test.js +304 -5
  66. package/dist/esm/wizard/store/index.js +2 -0
  67. package/package.json +1 -1
  68. package/src/action-card/action-card.d.ts +3 -2
  69. package/src/action-card/index.js +81 -8
  70. package/src/action-card/index.test.js +159 -0
  71. package/src/action-card/style.scss +3 -9
  72. package/src/card-feature/README.md +10 -11
  73. package/src/card-feature/index.test.js +45 -15
  74. package/src/card-feature/index.tsx +43 -24
  75. package/src/card-feature/style.scss +1 -5
  76. package/src/debug-badge/index.js +36 -0
  77. package/src/debug-badge/index.test.js +39 -0
  78. package/src/debug-badge/style.scss +26 -0
  79. package/src/empty-state/README.md +11 -10
  80. package/src/empty-state/index.test.js +10 -0
  81. package/src/empty-state/root.tsx +2 -1
  82. package/src/empty-state/types.ts +1 -1
  83. package/src/footer/index.js +47 -2
  84. package/src/footer/index.test.js +114 -0
  85. package/src/footer/style.scss +17 -1
  86. package/src/index.js +1 -0
  87. package/src/integration-icon/index.tsx +5 -5
  88. package/src/integration-icon/style.scss +18 -2
  89. package/src/integration-icons/active-campaign.js +1 -5
  90. package/src/integration-icons/beehiiv.js +10 -0
  91. package/src/integration-icons/gravity-forms.js +10 -0
  92. package/src/integration-icons/index.js +14 -3
  93. package/src/integration-icons/salesforce.js +3 -3
  94. package/src/notice/index.js +2 -17
  95. package/src/notice/style.scss +0 -19
  96. package/src/plugin-toggle/index.js +1 -1
  97. package/src/with-wizard-screen/index.js +2 -2
  98. package/src/with-wizard-screen/index.test.js +0 -2
  99. package/src/wizard/breadcrumbs-select.js +12 -4
  100. package/src/wizard/breadcrumbs-select.test.js +15 -1
  101. package/src/wizard/index.js +45 -14
  102. package/src/wizard/index.test.js +169 -3
  103. package/src/wizard/store/index.js +2 -0
@@ -9,8 +9,9 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
9
9
  /**
10
10
  * WordPress dependencies
11
11
  */
12
- import { Draggable, ExternalLink, ToggleControl } from '@wordpress/components';
13
- import { useEffect, useState } from '@wordpress/element';
12
+ import { Draggable, ExternalLink, Notice, ToggleControl } from '@wordpress/components';
13
+ import { RawHTML, useEffect, useState } from '@wordpress/element';
14
+ import { decodeEntities } from '@wordpress/html-entities';
14
15
  import { __ } from '@wordpress/i18n';
15
16
  import { Icon, check, chevronDown, chevronUp, dragHandle } from '@wordpress/icons';
16
17
  import { Badge } from '@wordpress/ui';
@@ -18,20 +19,90 @@ import { Badge } from '@wordpress/ui';
18
19
  /**
19
20
  * Internal dependencies
20
21
  */
21
- import { Button, Card, Grid, Handoff, Notice, Waiting } from '../';
22
+ import { Button, Card, Grid, Handoff, Waiting } from '../';
22
23
  import './style.scss';
23
24
 
24
25
  /**
25
26
  * External dependencies
26
27
  */
27
28
  import classnames from 'classnames';
29
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
30
+ var NOTIFICATION_LEVELS = ['error', 'info', 'success', 'warning'];
31
+
32
+ /**
33
+ * Collect a notification's text, including text nested inside React elements.
34
+ *
35
+ * Segments join with a space so that adjacent elements do not weld together: by the
36
+ * time speak() sees this string the markup that separated them is already gone.
37
+ *
38
+ * @param {*} value Notification content.
39
+ * @return {string} The collected text.
40
+ */
41
+ var _getNotificationText = function getNotificationText(value) {
42
+ var _value$props;
43
+ if (typeof value === 'string') {
44
+ return value;
45
+ }
46
+ if (typeof value === 'number') {
47
+ return String(value);
48
+ }
49
+ if (value instanceof Error) {
50
+ return value.message;
51
+ }
52
+ if (Array.isArray(value)) {
53
+ return value.map(_getNotificationText).join(' ');
54
+ }
55
+ return (value === null || value === void 0 || (_value$props = value.props) === null || _value$props === void 0 ? void 0 : _value$props.children) === undefined ? '' : _getNotificationText(value.props.children);
56
+ };
57
+
58
+ /**
59
+ * Derive a plain-text announcement from a notification.
60
+ *
61
+ * Notice defaults spokenMessage to its children and runs renderToString over them
62
+ * during render, which corrupts the hook dispatcher when those children are
63
+ * components. A notification can be any element, so the announcement is always
64
+ * built from its text instead of letting that default apply. Tags in an HTML
65
+ * notification stay in, since speak() strips them itself. Entities are decoded so
66
+ * the announcement matches the text on screen, which getNotificationContent decodes
67
+ * for the same reason.
68
+ *
69
+ * @param {*} notification Notification content.
70
+ * @return {string} Message to announce.
71
+ */
72
+ var getSpokenNotification = function getSpokenNotification(notification) {
73
+ return decodeEntities(_getNotificationText(notification)).replace(/\s+/g, ' ').trim();
74
+ };
75
+
76
+ /**
77
+ * Decode a notification's own text for display.
78
+ *
79
+ * React does not decode entities in text children, and notification text is often
80
+ * server-sourced, so a message carrying `&#8217;` would otherwise show the entity
81
+ * and announce the character. The HTML branch is left alone: there the browser's
82
+ * parser decodes as it renders.
83
+ *
84
+ * @param {*} value Notification content.
85
+ * @return {*} The content, with its own strings decoded.
86
+ */
87
+ var _getNotificationContent = function getNotificationContent(value) {
88
+ if (value instanceof Error) {
89
+ return decodeEntities(value.message);
90
+ }
91
+ if (typeof value === 'string') {
92
+ return decodeEntities(value);
93
+ }
94
+ if (Array.isArray(value)) {
95
+ return value.map(_getNotificationContent);
96
+ }
97
+ return value;
98
+ };
28
99
 
29
100
  /**
30
101
  * ActionCard component
102
+ *
31
103
  * @param {ActionCardProps} props Component props.
32
104
  * @return {JSX.Element} ActionCard component.
33
105
  */
34
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
35
106
  var ActionCard = function ActionCard(_ref) {
36
107
  var badges = _ref.badges,
37
108
  className = _ref.className,
@@ -125,6 +196,7 @@ var ActionCard = function ActionCard(_ref) {
125
196
  }
126
197
  }, [collapse]);
127
198
  var hasChildren = notification || children;
199
+ var notificationContent = _getNotificationContent(notification);
128
200
  var classes = classnames('newspack-action-card', simple && 'newspack-card--is-clickable', hasGreyHeader && 'newspack-card--has-grey-header', hasWhiteHeader && 'newspack-card--has-white-header', hasChildren && 'newspack-card--has-children', indent && 'newspack-card--indent', isSmall && 'is-small', isMedium && 'is-medium', checkbox && 'has-checkbox', expandable && 'is-expandable', draggable && 'is-draggable', actionContent && 'has-action-content', className);
129
201
  var backgroundImageStyles = function backgroundImageStyles(url) {
130
202
  return url ? {
@@ -248,24 +320,17 @@ var ActionCard = function ActionCard(_ref) {
248
320
  width: 24
249
321
  })
250
322
  })]
251
- }), notification && /*#__PURE__*/_jsxs("div", {
323
+ }), notification && NOTIFICATION_LEVELS.includes(notificationLevel) && /*#__PURE__*/_jsx("div", {
252
324
  className: "newspack-action-card__notification newspack-action-card__region-children",
253
- children: ['error' === notificationLevel && /*#__PURE__*/_jsx(Notice, {
254
- noticeText: notification,
255
- isError: true,
256
- rawHTML: notificationHTML
257
- }), 'info' === notificationLevel && /*#__PURE__*/_jsx(Notice, {
258
- noticeText: notification,
259
- rawHTML: notificationHTML
260
- }), 'success' === notificationLevel && /*#__PURE__*/_jsx(Notice, {
261
- noticeText: notification,
262
- isSuccess: true,
263
- rawHTML: notificationHTML
264
- }), 'warning' === notificationLevel && /*#__PURE__*/_jsx(Notice, {
265
- noticeText: notification,
266
- isWarning: true,
267
- rawHTML: notificationHTML
268
- })]
325
+ children: /*#__PURE__*/_jsx(Notice, {
326
+ status: notificationLevel,
327
+ isDismissible: false,
328
+ spokenMessage: getSpokenNotification(notification),
329
+ children: notificationHTML && typeof notification === 'string' ? /*#__PURE__*/_jsx(RawHTML, {
330
+ className: "newspack-action-card__notification-html",
331
+ children: notification
332
+ }) : notificationContent
333
+ })
269
334
  }), children && (expandable && expanded || !expandable) ? /*#__PURE__*/_jsx("div", {
270
335
  className: "newspack-action-card__region-children",
271
336
  children: children
@@ -1,12 +1,28 @@
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; }
1
5
  /**
2
6
  * External dependencies.
3
7
  */
4
8
  import { render, screen } from '@testing-library/react';
5
9
 
10
+ /**
11
+ * WordPress dependencies.
12
+ */
13
+ import { speak } from '@wordpress/a11y';
14
+ import { useState } from '@wordpress/element';
15
+
6
16
  /**
7
17
  * Internal dependencies.
8
18
  */
9
19
  import ActionCard from './index';
20
+ import { jsx as _jsx } from "react/jsx-runtime";
21
+ jest.mock('@wordpress/a11y', function () {
22
+ return _objectSpread(_objectSpread({}, jest.requireActual('@wordpress/a11y')), {}, {
23
+ speak: jest.fn()
24
+ });
25
+ });
10
26
 
11
27
  /**
12
28
  * The library Badge styles its wrapper rather than its text, so a badge with no label
@@ -15,7 +31,6 @@ import ActionCard from './index';
15
31
  * can sit on a placement with no entry in the placement map — and an array literal is
16
32
  * always non-empty, so the component has to drop those itself.
17
33
  */
18
- import { jsx as _jsx } from "react/jsx-runtime";
19
34
  describe('ActionCard badges', function () {
20
35
  var badgeText = function badgeText() {
21
36
  return screen.queryAllByText(function (_, node) {
@@ -86,4 +101,180 @@ describe('ActionCard badges', function () {
86
101
  // An empty array used to fall through as a literal `0` in the heading.
87
102
  expect(withEmptyArray.textContent).not.toContain('0');
88
103
  });
104
+ });
105
+
106
+ /**
107
+ * ActionCard derives the notice's spoken message rather than letting core default it to
108
+ * the children. Core runs `renderToString` over that default during render, which corrupts
109
+ * the hook dispatcher when the children are components — so the derivation is what keeps a
110
+ * notification carrying a `<Button>` from crashing the card on a later re-render.
111
+ */
112
+ describe('ActionCard notifications', function () {
113
+ beforeEach(function () {
114
+ speak.mockClear();
115
+ });
116
+ var spoken = function spoken() {
117
+ return speak.mock.calls.map(function (call) {
118
+ return call[0];
119
+ });
120
+ };
121
+ it('renders a notice for a recognised level', function () {
122
+ var _render5 = render(/*#__PURE__*/_jsx(ActionCard, {
123
+ title: "Ad Manager",
124
+ notification: "Plugin cannot be installed",
125
+ notificationLevel: "error"
126
+ })),
127
+ container = _render5.container;
128
+ expect(container.querySelector('.components-notice.is-error')).toBeInTheDocument();
129
+ });
130
+ it('renders no notice for an unrecognised or absent level', function () {
131
+ var _render6 = render(/*#__PURE__*/_jsx(ActionCard, {
132
+ title: "Ad Manager",
133
+ notification: "Something happened",
134
+ notificationLevel: "critical"
135
+ })),
136
+ unknown = _render6.container;
137
+ expect(unknown.querySelector('.components-notice')).not.toBeInTheDocument();
138
+ var _render7 = render(/*#__PURE__*/_jsx(ActionCard, {
139
+ title: "Ad Manager",
140
+ notification: "Something happened"
141
+ })),
142
+ absent = _render7.container;
143
+ expect(absent.querySelector('.components-notice')).not.toBeInTheDocument();
144
+ });
145
+ it('announces the text inside an element-only notification', function () {
146
+ render(/*#__PURE__*/_jsx(ActionCard, {
147
+ title: "Ad Manager",
148
+ notificationLevel: "error",
149
+ notification: [/*#__PURE__*/_jsx("button", {
150
+ children: "Click here to connect your account."
151
+ }, "connect")]
152
+ }));
153
+ expect(spoken()).toContain('Click here to connect your account.');
154
+ });
155
+ it('announces strings and element text together', function () {
156
+ render(/*#__PURE__*/_jsx(ActionCard, {
157
+ title: "Ad Manager",
158
+ notificationLevel: "success",
159
+ notification: ['Created custom targeting keys: ',
160
+ /*#__PURE__*/
161
+ // eslint-disable-next-line react/jsx-indent
162
+ _jsx("a", {
163
+ href: "https://example.org",
164
+ children: "Visit your GAM dashboard"
165
+ }, "dashboard")]
166
+ }));
167
+ expect(spoken()).toContain('Created custom targeting keys: Visit your GAM dashboard');
168
+ });
169
+ it('announces an Error and renders its message rather than throwing', function () {
170
+ render(/*#__PURE__*/_jsx(ActionCard, {
171
+ title: "Ad Manager",
172
+ notificationLevel: "error",
173
+ notification: new Error('Network unreachable')
174
+ }));
175
+ expect(screen.getByText('Network unreachable')).toBeInTheDocument();
176
+ expect(spoken()).toContain('Network unreachable');
177
+ });
178
+ it('renders HTML notifications in their own wrapper and hands speak() the tags to strip', function () {
179
+ var _render8 = render(/*#__PURE__*/_jsx(ActionCard, {
180
+ title: "Ad Manager",
181
+ notificationLevel: "error",
182
+ notification: '<p>Install failed.</p><p>Try again.</p>',
183
+ notificationHTML: true
184
+ })),
185
+ container = _render8.container;
186
+ expect(container.querySelectorAll('.newspack-action-card__notification-html > p')).toHaveLength(2);
187
+ expect(spoken()[0]).toContain('<p>Install failed.</p>');
188
+ });
189
+ it('decodes entities in both the announcement and the visible text', function () {
190
+ render(/*#__PURE__*/_jsx(ActionCard, {
191
+ title: "Ad Manager",
192
+ notificationLevel: "error",
193
+ notification: 'Plugin &#8220;Foo&#8221; could not be activated'
194
+ }));
195
+ expect(screen.getByText('Plugin “Foo” could not be activated')).toBeInTheDocument();
196
+ expect(spoken()[0]).toContain('“Foo”');
197
+ });
198
+ it('decodes entities inside an array of notification parts', function () {
199
+ render(/*#__PURE__*/_jsx(ActionCard, {
200
+ title: "Ad Manager",
201
+ notificationLevel: "error",
202
+ notification: ['Could not reach the publisher&#8217;s network.', ' ']
203
+ }));
204
+ expect(screen.getByText(/Could not reach the publisher’s network\./)).toBeInTheDocument();
205
+ expect(spoken()[0]).toContain('publisher’s');
206
+ });
207
+ it('leaves entity decoding to the browser on the HTML branch', function () {
208
+ var _render9 = render(/*#__PURE__*/_jsx(ActionCard, {
209
+ title: "Ad Manager",
210
+ notificationLevel: "error",
211
+ notification: 'Get it from <a href="https://example.org">the plugin&#8217;s site</a>',
212
+ notificationHTML: true
213
+ })),
214
+ container = _render9.container;
215
+ expect(container.querySelector('.newspack-action-card__notification-html a').textContent).toBe('the plugin’s site');
216
+ expect(spoken()[0]).toContain('plugin’s site');
217
+ });
218
+ it('renders a non-string notification as children even when notificationHTML is set', function () {
219
+ var _render0 = render(/*#__PURE__*/_jsx(ActionCard, {
220
+ title: "Ad Manager",
221
+ notificationLevel: "error",
222
+ notification: /*#__PURE__*/_jsx("span", {
223
+ children: "Network unreachable"
224
+ }),
225
+ notificationHTML: true
226
+ })),
227
+ container = _render0.container;
228
+ expect(screen.getByText('Network unreachable')).toBeInTheDocument();
229
+ expect(container.querySelector('.newspack-action-card__notification-html')).not.toBeInTheDocument();
230
+ });
231
+ it('keeps adjacent element texts apart in the announcement', function () {
232
+ render(/*#__PURE__*/_jsx(ActionCard, {
233
+ title: "Ad Manager",
234
+ notificationLevel: "error",
235
+ notification: [
236
+ /*#__PURE__*/
237
+ // eslint-disable-next-line react/jsx-indent
238
+ _jsx("a", {
239
+ href: "https://example.org",
240
+ children: "Visit your GAM dashboard"
241
+ }, "dashboard"), /*#__PURE__*/_jsx("button", {
242
+ type: "button",
243
+ children: "Click here to connect your account."
244
+ }, "connect")]
245
+ }));
246
+ expect(spoken()[0]).toBe('Visit your GAM dashboard Click here to connect your account.');
247
+ });
248
+ it('survives a re-render that changes how many hooked components the notification holds', function () {
249
+ var HookedAction = function HookedAction(_ref) {
250
+ var children = _ref.children;
251
+ var _useState = useState(children),
252
+ _useState2 = _slicedToArray(_useState, 1),
253
+ label = _useState2[0];
254
+ return /*#__PURE__*/_jsx("button", {
255
+ type: "button",
256
+ children: label
257
+ });
258
+ };
259
+ var card = function card(notification) {
260
+ return /*#__PURE__*/_jsx(ActionCard, {
261
+ title: "Ad Manager",
262
+ notificationLevel: "error",
263
+ notification: notification
264
+ });
265
+ };
266
+ var _render1 = render(card([/*#__PURE__*/_jsx(HookedAction, {
267
+ children: "Retry"
268
+ }, "retry"), /*#__PURE__*/_jsx(HookedAction, {
269
+ children: "Documentation"
270
+ }, "docs")])),
271
+ rerender = _render1.rerender,
272
+ container = _render1.container;
273
+ expect(function () {
274
+ return rerender(card([/*#__PURE__*/_jsx(HookedAction, {
275
+ children: "Retry"
276
+ }, "retry")]));
277
+ }).not.toThrow();
278
+ expect(container.querySelector('.components-notice.is-error')).toBeInTheDocument();
279
+ });
89
280
  });
@@ -339,18 +339,12 @@
339
339
 
340
340
  /* Notifications */
341
341
  .newspack-action-card__notification {
342
- .newspack-notice {
342
+ .components-notice {
343
343
  margin: 0;
344
-
345
- &__heading {
346
- margin-top: 0;
347
- }
348
344
  }
349
345
 
350
- &.newspack-action-card__region-children {
351
- .newspack-notice {
352
- margin-top: 32px;
353
- }
346
+ .newspack-action-card__notification-html > * {
347
+ margin: 0;
354
348
  }
355
349
  }
356
350
 
@@ -7,7 +7,7 @@ import classnames from 'classnames';
7
7
  * WordPress dependencies
8
8
  */
9
9
  import { __, _x, sprintf } from '@wordpress/i18n';
10
- import { createElement, isValidElement } from '@wordpress/element';
10
+ import { createElement, isValidElement, useCallback, useEffect, useRef } from '@wordpress/element';
11
11
  import { useInstanceId } from '@wordpress/compose';
12
12
  import { DropdownMenu } from '@wordpress/components';
13
13
  import { moreVertical } from '@wordpress/icons';
@@ -72,6 +72,30 @@ var _CardFeature = function CardFeature(_ref) {
72
72
  // translators: %1$s: the button's visible action label, e.g. "Enable". %2$s: the feature's name. The visible label must stay first (WCAG 2.5.3).
73
73
  _x('%1$s %2$s', 'accessible button name: visible action label, then feature name', 'newspack-plugin'), buttonLabel, title);
74
74
  var showMoreControls = enabled && !!(moreControls !== null && moreControls !== void 0 && moreControls.length) && (!requirements || requirementsActionable);
75
+ // A Configure button with nothing behind it would click to nowhere.
76
+ var showPrimaryButton = !isConfigureState || !!onConfigure;
77
+
78
+ // Enabling a feature with nothing to configure removes the button that was
79
+ // just used, so its focus goes to the More menu instead of the page. React
80
+ // detaches a ref before removing the node, which is the last point the
81
+ // button can still be read as focused.
82
+ var actionsRef = useRef(null);
83
+ var primaryButtonNode = useRef(null);
84
+ var primaryButtonLeftWithFocus = useRef(false);
85
+ var primaryButtonRef = useCallback(function (node) {
86
+ if (!node && primaryButtonNode.current) {
87
+ primaryButtonLeftWithFocus.current = primaryButtonNode.current === primaryButtonNode.current.ownerDocument.activeElement;
88
+ }
89
+ primaryButtonNode.current = node;
90
+ }, []);
91
+ useEffect(function () {
92
+ var _actionsRef$current;
93
+ if (showPrimaryButton || !primaryButtonLeftWithFocus.current) {
94
+ return;
95
+ }
96
+ primaryButtonLeftWithFocus.current = false;
97
+ (_actionsRef$current = actionsRef.current) === null || _actionsRef$current === void 0 || (_actionsRef$current = _actionsRef$current.querySelector('.components-dropdown-menu__toggle')) === null || _actionsRef$current === void 0 || _actionsRef$current.focus();
98
+ }, [showPrimaryButton]);
75
99
  var handleButtonClick = function handleButtonClick() {
76
100
  if (isConfigureState) {
77
101
  onConfigure === null || onConfigure === void 0 || onConfigure();
@@ -81,8 +105,7 @@ var _CardFeature = function CardFeature(_ref) {
81
105
  };
82
106
  var iconDescriptor = icon && !isValidElement(icon) ? icon : null;
83
107
  var iconClasses = iconDescriptor ? classnames('newspack-card-feature__icon', {
84
- 'newspack-card-feature__icon--radius-small': !!iconDescriptor.backgroundColor && iconDescriptor.radius !== 'full',
85
- 'newspack-card-feature__icon--radius-full': iconDescriptor.radius === 'full'
108
+ 'newspack-card-feature__icon--has-background': !!iconDescriptor.backgroundColor
86
109
  }) : undefined;
87
110
  var renderedIcon = null;
88
111
  if (isValidElement(icon)) {
@@ -129,10 +152,12 @@ var _CardFeature = function CardFeature(_ref) {
129
152
  gap: "sm",
130
153
  wrap: "wrap",
131
154
  children: [/*#__PURE__*/_jsxs(Stack, {
155
+ ref: actionsRef,
132
156
  direction: "row",
133
157
  align: "center",
134
158
  gap: "sm",
135
- children: [/*#__PURE__*/_jsx(Button, {
159
+ children: [showPrimaryButton && /*#__PURE__*/_jsx(Button, {
160
+ ref: primaryButtonRef,
136
161
  variant: isConfigureState ? 'tertiary' : 'secondary',
137
162
  accessibleWhenDisabled: true,
138
163
  "aria-describedby": describedById,