newspack-components 4.8.1-alpha.1 → 4.9.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 (45) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/dist/cjs/card-feature/index.js +28 -3
  3. package/dist/cjs/card-feature/index.test.js +100 -45
  4. package/dist/cjs/card-feature/style.scss +1 -5
  5. package/dist/cjs/integration-icon/index.js +4 -4
  6. package/dist/cjs/integration-icon/style.scss +18 -2
  7. package/dist/cjs/integration-icons/active-campaign.js +4 -7
  8. package/dist/cjs/integration-icons/beehiiv.js +19 -0
  9. package/dist/cjs/integration-icons/gravity-forms.js +19 -0
  10. package/dist/cjs/integration-icons/index.js +26 -6
  11. package/dist/cjs/integration-icons/salesforce.js +5 -3
  12. package/dist/cjs/wizard/breadcrumbs-select.js +17 -4
  13. package/dist/cjs/wizard/breadcrumbs-select.test.js +17 -0
  14. package/dist/cjs/wizard/index.js +42 -6
  15. package/dist/cjs/wizard/index.test.js +146 -0
  16. package/dist/esm/card-feature/index.js +29 -4
  17. package/dist/esm/card-feature/index.test.js +100 -45
  18. package/dist/esm/card-feature/style.scss +1 -5
  19. package/dist/esm/integration-icon/index.js +5 -5
  20. package/dist/esm/integration-icon/style.scss +18 -2
  21. package/dist/esm/integration-icons/active-campaign.js +5 -8
  22. package/dist/esm/integration-icons/beehiiv.js +13 -0
  23. package/dist/esm/integration-icons/gravity-forms.js +13 -0
  24. package/dist/esm/integration-icons/index.js +15 -4
  25. package/dist/esm/integration-icons/salesforce.js +5 -3
  26. package/dist/esm/wizard/breadcrumbs-select.js +16 -3
  27. package/dist/esm/wizard/breadcrumbs-select.test.js +18 -1
  28. package/dist/esm/wizard/index.js +42 -6
  29. package/dist/esm/wizard/index.test.js +146 -0
  30. package/package.json +1 -1
  31. package/src/card-feature/README.md +10 -11
  32. package/src/card-feature/index.test.js +45 -15
  33. package/src/card-feature/index.tsx +43 -24
  34. package/src/card-feature/style.scss +1 -5
  35. package/src/integration-icon/index.tsx +5 -5
  36. package/src/integration-icon/style.scss +18 -2
  37. package/src/integration-icons/active-campaign.js +1 -5
  38. package/src/integration-icons/beehiiv.js +10 -0
  39. package/src/integration-icons/gravity-forms.js +10 -0
  40. package/src/integration-icons/index.js +14 -3
  41. package/src/integration-icons/salesforce.js +3 -3
  42. package/src/wizard/breadcrumbs-select.js +12 -4
  43. package/src/wizard/breadcrumbs-select.test.js +15 -1
  44. package/src/wizard/index.js +34 -5
  45. package/src/wizard/index.test.js +73 -0
@@ -197,4 +197,21 @@ describe('appendSectionName', function () {
197
197
  label: 'Access Control'
198
198
  }]);
199
199
  });
200
+ });
201
+ describe('activeSection', function () {
202
+ var sections = [{
203
+ path: '/settings',
204
+ exact: true,
205
+ subHeaderText: 'Intro'
206
+ }, {
207
+ path: '/settings/:integrationId'
208
+ }];
209
+ it('returns the section matching the route', function () {
210
+ expect((0, _breadcrumbsSelect.activeSection)(sections, '/settings/esp')).toBe(sections[1]);
211
+ expect((0, _breadcrumbsSelect.activeSection)(sections, '/settings').subHeaderText).toBe('Intro');
212
+ });
213
+ it('falls back to the first section, or nothing without sections', function () {
214
+ expect((0, _breadcrumbsSelect.activeSection)(sections, '/unknown')).toBe(sections[0]);
215
+ expect((0, _breadcrumbsSelect.activeSection)([], '/settings')).toBeUndefined();
216
+ });
200
217
  });
@@ -22,7 +22,10 @@ var _WizardError = _interopRequireDefault(require("./components/WizardError"));
22
22
  var _jsxRuntime = require("react/jsx-runtime");
23
23
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
24
24
  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; }
25
- 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) { (0, _defineProperty2["default"])(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; } /**
25
+ 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) { (0, _defineProperty2["default"])(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; }
26
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
27
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
28
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } /**
26
29
  * External dependencies.
27
30
  */ /**
28
31
  * WordPress dependencies.
@@ -58,6 +61,7 @@ var HashRouter = _router["default"].HashRouter,
58
61
  Redirect = _router["default"].Redirect,
59
62
  Route = _router["default"].Route,
60
63
  Switch = _router["default"].Switch,
64
+ matchPath = _router["default"].matchPath,
61
65
  useLocation = _router["default"].useLocation;
62
66
 
63
67
  /**
@@ -110,16 +114,46 @@ var ResetHeaderData = function ResetHeaderData() {
110
114
  * can read the current route and derive the active-tab breadcrumb.
111
115
  */
112
116
  var WizardHeaderRegion = function WizardHeaderRegion(_ref) {
117
+ var _activeSection;
113
118
  var hideHeader = _ref.hideHeader,
114
119
  headerText = _ref.headerText,
115
120
  sections = _ref.sections,
116
121
  sectionName = _ref.sectionName,
117
122
  subTitle = _ref.subTitle,
118
123
  actions = _ref.actions,
119
- tabbedNavigation = _ref.tabbedNavigation,
124
+ wizardTabbedNavigation = _ref.tabbedNavigation,
120
125
  children = _ref.children;
121
126
  var _useLocation = useLocation(),
122
127
  pathname = _useLocation.pathname;
128
+
129
+ // A section can carry its own tabs, built from its route params, in place of
130
+ // the wizard's. The first match wins, as it does in the wizard's `<Switch>`.
131
+ var tabbedNavigation = wizardTabbedNavigation;
132
+ var _iterator = _createForOfIteratorHelper(sections),
133
+ _step;
134
+ try {
135
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
136
+ var _section$exact;
137
+ var section = _step.value;
138
+ var match = matchPath(pathname, {
139
+ path: section.path,
140
+ exact: (_section$exact = section.exact) !== null && _section$exact !== void 0 ? _section$exact : false
141
+ });
142
+ if (match) {
143
+ if (typeof section.tabbedNavigation === 'function') {
144
+ tabbedNavigation = /*#__PURE__*/(0, _jsxRuntime.jsx)(_.TabbedNavigation, {
145
+ items: section.tabbedNavigation(match.params),
146
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_WizardError["default"], {})
147
+ });
148
+ }
149
+ break;
150
+ }
151
+ }
152
+ } catch (err) {
153
+ _iterator.e(err);
154
+ } finally {
155
+ _iterator.f();
156
+ }
123
157
  if (hideHeader) {
124
158
  // Without the Page shell the tabs still own the content: it renders
125
159
  // inside the active tab's panel.
@@ -139,9 +173,10 @@ var WizardHeaderRegion = function WizardHeaderRegion(_ref) {
139
173
  // Append any render-time leaf crumb(s) the section supplied via
140
174
  // headerData.sectionName (deduped against the current trailing label).
141
175
  breadcrumbItems = (0, _breadcrumbsSelect.appendSectionName)(breadcrumbItems, sectionName);
176
+ var sectionSubTitle = (_activeSection = (0, _breadcrumbsSelect.activeSection)(sections, pathname)) === null || _activeSection === void 0 ? void 0 : _activeSection.subHeaderText;
142
177
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_.Page, {
143
178
  breadcrumbItems: breadcrumbItems,
144
- subTitle: subTitle,
179
+ subTitle: sectionSubTitle !== null && sectionSubTitle !== void 0 ? sectionSubTitle : subTitle,
145
180
  actions: actions,
146
181
  tabbedNavigation: tabbedNavigation,
147
182
  children: children
@@ -154,7 +189,8 @@ var WizardHeaderRegion = function WizardHeaderRegion(_ref) {
154
189
  * @property {string} [subHeaderText] The sub-header text, optional.
155
190
  * @property {string} [apiSlug] The API slug, optional.
156
191
  * @property {string} [className] CSS classes, optional.
157
- * @property {any[]} sections Array of sections.
192
+ * @property {any[]} sections Array of sections. A section's own `subHeaderText` replaces the wizard's while it is active.
193
+ * Its optional `tabbedNavigation( params )` returns the tab items shown while its route matches.
158
194
  * @property {boolean} [hasSimpleFooter] Indicates if a simple footer is used, optional.
159
195
  * @property {() => void} [renderAboveSections] Function to render content above sections, optional.
160
196
  * @property {string[]} [requiredPlugins] Array of required plugin strings, optional.
@@ -332,11 +368,11 @@ var Wizard = function Wizard(_ref2, ref) {
332
368
  bubblesVirtually: true
333
369
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(Switch, {
334
370
  children: [routedSections.map(function (section, index) {
335
- var _section$exact;
371
+ var _section$exact2;
336
372
  var SectionComponent = section.render;
337
373
  var sectionProps = section.props || {};
338
374
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(Route, {
339
- exact: (_section$exact = section.exact) !== null && _section$exact !== void 0 ? _section$exact : false,
375
+ exact: (_section$exact2 = section.exact) !== null && _section$exact2 !== void 0 ? _section$exact2 : false,
340
376
  path: section.path,
341
377
  render: function render(routerProps) {
342
378
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
@@ -402,4 +402,150 @@ describe('Wizard content width', function () {
402
402
  }
403
403
  }, _callee8);
404
404
  })));
405
+ });
406
+ describe('Wizard section tabs', function () {
407
+ var sections = [{
408
+ label: 'List',
409
+ path: '/',
410
+ exact: true,
411
+ render: function render() {
412
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
413
+ children: "List content"
414
+ });
415
+ }
416
+ }, {
417
+ path: '/items/:itemId/logs/:tab?',
418
+ isHidden: true,
419
+ render: function render() {
420
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
421
+ children: "Logs content"
422
+ });
423
+ },
424
+ tabbedNavigation: function tabbedNavigation(_ref10) {
425
+ var itemId = _ref10.itemId;
426
+ return [{
427
+ label: 'First Tab',
428
+ path: "/items/".concat(itemId, "/logs"),
429
+ exact: true
430
+ }, {
431
+ label: 'Second Tab',
432
+ path: "/items/".concat(itemId, "/logs/second"),
433
+ exact: true
434
+ }];
435
+ }
436
+ }];
437
+ beforeEach(function () {
438
+ _apiFetch["default"].mockReset();
439
+ });
440
+ afterEach(function () {
441
+ window.location.hash = '';
442
+ });
443
+ it('shows the tabs a section builds from its route params while it matches', /*#__PURE__*/(0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator().m(function _callee9() {
444
+ var _t8;
445
+ return _regenerator().w(function (_context9) {
446
+ while (1) switch (_context9.n) {
447
+ case 0:
448
+ window.location.hash = '#/items/42/logs/second';
449
+ (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_["default"], {
450
+ headerText: "Test wizard",
451
+ sections: sections
452
+ }));
453
+ _t8 = expect;
454
+ _context9.n = 1;
455
+ return _react.screen.findByText('Logs content');
456
+ case 1:
457
+ _t8(_context9.v).toBeInTheDocument();
458
+ expect(_react.screen.getByRole('tab', {
459
+ name: 'First Tab'
460
+ })).toHaveAttribute('href', '#/items/42/logs');
461
+ expect(_react.screen.getByRole('tab', {
462
+ name: 'Second Tab'
463
+ })).toHaveAttribute('aria-selected', 'true');
464
+ case 2:
465
+ return _context9.a(2);
466
+ }
467
+ }, _callee9);
468
+ })));
469
+ it('leaves the tabs out on a route the section does not match', /*#__PURE__*/(0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator().m(function _callee0() {
470
+ var _t9;
471
+ return _regenerator().w(function (_context0) {
472
+ while (1) switch (_context0.n) {
473
+ case 0:
474
+ window.location.hash = '#/';
475
+ (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_["default"], {
476
+ headerText: "Test wizard",
477
+ sections: sections
478
+ }));
479
+ _t9 = expect;
480
+ _context0.n = 1;
481
+ return _react.screen.findByText('List content');
482
+ case 1:
483
+ _t9(_context0.v).toBeInTheDocument();
484
+ expect(_react.screen.queryByRole('tab')).toBeNull();
485
+ case 2:
486
+ return _context0.a(2);
487
+ }
488
+ }, _callee0);
489
+ })));
490
+ });
491
+ describe('Wizard section subtitle', function () {
492
+ beforeEach(function () {
493
+ _apiFetch["default"].mockReset();
494
+ _apiFetch["default"].mockResolvedValue({});
495
+ });
496
+ it("shows the active section's subHeaderText in the header, falling back to the wizard's", /*#__PURE__*/(0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator().m(function _callee1() {
497
+ var _render4, container, subtitle, _t0, _t1;
498
+ return _regenerator().w(function (_context1) {
499
+ while (1) switch (_context1.n) {
500
+ case 0:
501
+ window.location.hash = '#/list';
502
+ _render4 = (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_["default"], {
503
+ headerText: "Test wizard",
504
+ subHeaderText: "Wizard intro",
505
+ sections: [{
506
+ label: 'List',
507
+ path: '/list',
508
+ exact: true,
509
+ subHeaderText: 'List intro',
510
+ render: function render() {
511
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
512
+ children: "List view"
513
+ });
514
+ }
515
+ }, {
516
+ label: 'Item',
517
+ path: '/list/:id',
518
+ isHidden: true,
519
+ render: function render() {
520
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
521
+ children: "Item view"
522
+ });
523
+ }
524
+ }]
525
+ })), container = _render4.container;
526
+ subtitle = function subtitle() {
527
+ var _container$querySelec;
528
+ return (_container$querySelec = container.querySelector('.newspack-page__header-subtitle')) === null || _container$querySelec === void 0 ? void 0 : _container$querySelec.textContent;
529
+ };
530
+ _t0 = expect;
531
+ _context1.n = 1;
532
+ return _react.screen.findByText('List view');
533
+ case 1:
534
+ _t0(_context1.v).toBeInTheDocument();
535
+ expect(subtitle()).toBe('List intro');
536
+ (0, _react.act)(function () {
537
+ window.location.hash = '#/list/esp';
538
+ window.dispatchEvent(new HashChangeEvent('hashchange'));
539
+ });
540
+ _t1 = expect;
541
+ _context1.n = 2;
542
+ return _react.screen.findByText('Item view');
543
+ case 2:
544
+ _t1(_context1.v).toBeInTheDocument();
545
+ expect(subtitle()).toBe('Wizard intro');
546
+ case 3:
547
+ return _context1.a(2);
548
+ }
549
+ }, _callee1);
550
+ })));
405
551
  });
@@ -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,
@@ -95,7 +95,8 @@ describe('CardFeature', function () {
95
95
  it('names the feature in the configure state too', function () {
96
96
  render(/*#__PURE__*/_jsx(CardFeature, {
97
97
  title: "Content Gifting",
98
- enabled: true
98
+ enabled: true,
99
+ onConfigure: function onConfigure() {}
99
100
  }));
100
101
  expect(screen.getByRole('button', {
101
102
  name: 'Configure Content Gifting'
@@ -164,6 +165,72 @@ describe('CardFeature', function () {
164
165
  expect(onConfigure).toHaveBeenCalledTimes(1);
165
166
  expect(onEnable).not.toHaveBeenCalled();
166
167
  });
168
+
169
+ // A button reading Configure with nothing behind it would click to nowhere.
170
+ it('drops the primary button once enabled when there is nothing to configure', function () {
171
+ render(/*#__PURE__*/_jsx(CardFeature, {
172
+ title: "Content gifting",
173
+ enabled: true,
174
+ onEnable: jest.fn(),
175
+ moreControls: [{
176
+ title: 'Disable',
177
+ onClick: jest.fn()
178
+ }]
179
+ }));
180
+ expect(screen.queryByRole('button', {
181
+ name: /Configure/
182
+ })).toBeNull();
183
+ expect(moreMenu()).toBeInTheDocument();
184
+ });
185
+
186
+ // Enabling removes the button that was just used; without a hand-off,
187
+ // keyboard focus falls to the page.
188
+ it('hands focus to the More menu when enabling removes the focused button', function () {
189
+ var props = {
190
+ title: 'Content gifting',
191
+ onEnable: jest.fn(),
192
+ moreControls: [{
193
+ title: 'Disable',
194
+ onClick: jest.fn()
195
+ }]
196
+ };
197
+ var _render2 = render(/*#__PURE__*/_jsx(CardFeature, _objectSpread({}, props))),
198
+ rerender = _render2.rerender;
199
+ primaryButton().focus();
200
+ rerender(/*#__PURE__*/_jsx(CardFeature, _objectSpread(_objectSpread({}, props), {}, {
201
+ enabled: true
202
+ })));
203
+ expect(moreMenu()).toHaveFocus();
204
+ });
205
+ it('leaves focus alone when the removed button did not have it', function () {
206
+ var props = {
207
+ title: 'Content gifting',
208
+ onEnable: jest.fn(),
209
+ moreControls: [{
210
+ title: 'Disable',
211
+ onClick: jest.fn()
212
+ }]
213
+ };
214
+ var _render3 = render(/*#__PURE__*/_jsxs(_Fragment, {
215
+ children: [/*#__PURE__*/_jsx(CardFeature, _objectSpread({}, props)), /*#__PURE__*/_jsx("button", {
216
+ children: "Elsewhere"
217
+ })]
218
+ })),
219
+ rerender = _render3.rerender;
220
+ screen.getByRole('button', {
221
+ name: 'Elsewhere'
222
+ }).focus();
223
+ rerender(/*#__PURE__*/_jsxs(_Fragment, {
224
+ children: [/*#__PURE__*/_jsx(CardFeature, _objectSpread(_objectSpread({}, props), {}, {
225
+ enabled: true
226
+ })), /*#__PURE__*/_jsx("button", {
227
+ children: "Elsewhere"
228
+ })]
229
+ }));
230
+ expect(screen.getByRole('button', {
231
+ name: 'Elsewhere'
232
+ })).toHaveFocus();
233
+ });
167
234
  it('still routes to onEnable when enabled with an unmet requirement, since the button reads Enable', function () {
168
235
  var onEnable = jest.fn();
169
236
  var onConfigure = jest.fn();
@@ -221,18 +288,19 @@ describe('CardFeature', function () {
221
288
  it('leaves the enabled badge unlinked, since it explains nothing about the button', function () {
222
289
  render(/*#__PURE__*/_jsx(CardFeature, {
223
290
  title: "Content gifting",
224
- enabled: true
291
+ enabled: true,
292
+ onConfigure: function onConfigure() {}
225
293
  }));
226
294
  expect(primaryButton()).not.toHaveAttribute('aria-describedby');
227
295
  });
228
296
  it('disables the button while an action is in flight, even with an actionable requirement', function () {
229
297
  var onEnable = jest.fn();
230
- var _render2 = render(/*#__PURE__*/_jsx(CardFeature, {
298
+ var _render4 = render(/*#__PURE__*/_jsx(CardFeature, {
231
299
  title: "Content gifting",
232
300
  busy: true,
233
301
  onEnable: onEnable
234
302
  })),
235
- rerender = _render2.rerender;
303
+ rerender = _render4.rerender;
236
304
  expect(primaryButton()).toHaveAttribute('aria-disabled', 'true');
237
305
  fireEvent.click(primaryButton());
238
306
  expect(onEnable).not.toHaveBeenCalled();
@@ -246,12 +314,12 @@ describe('CardFeature', function () {
246
314
  expect(primaryButton()).toHaveAttribute('aria-disabled', 'true');
247
315
  });
248
316
  it('accepts custom labels for both states', function () {
249
- var _render3 = render(/*#__PURE__*/_jsx(CardFeature, {
317
+ var _render5 = render(/*#__PURE__*/_jsx(CardFeature, {
250
318
  title: "Apple News",
251
319
  enableLabel: "Connect",
252
320
  configureLabel: "Manage connection"
253
321
  })),
254
- rerender = _render3.rerender;
322
+ rerender = _render5.rerender;
255
323
  expect(screen.getByRole('button', {
256
324
  name: 'Connect Apple News'
257
325
  })).toBeInTheDocument();
@@ -259,7 +327,8 @@ describe('CardFeature', function () {
259
327
  title: "Apple News",
260
328
  enabled: true,
261
329
  enableLabel: "Connect",
262
- configureLabel: "Manage connection"
330
+ configureLabel: "Manage connection",
331
+ onConfigure: function onConfigure() {}
263
332
  }));
264
333
  expect(screen.getByRole('button', {
265
334
  name: 'Manage connection Apple News'
@@ -268,10 +337,10 @@ describe('CardFeature', function () {
268
337
  });
269
338
  describe('badge', function () {
270
339
  it('shows nothing when off, and the enabled badge when on', function () {
271
- var _render4 = render(/*#__PURE__*/_jsx(CardFeature, {
340
+ var _render6 = render(/*#__PURE__*/_jsx(CardFeature, {
272
341
  title: "Content gifting"
273
342
  })),
274
- rerender = _render4.rerender;
343
+ rerender = _render6.rerender;
275
344
  expect(screen.queryByText('Enabled')).not.toBeInTheDocument();
276
345
  rerender(/*#__PURE__*/_jsx(CardFeature, {
277
346
  title: "Content gifting",
@@ -306,11 +375,11 @@ describe('CardFeature', function () {
306
375
  onClick: jest.fn()
307
376
  }];
308
377
  it('appears only when enabled and controls are supplied', function () {
309
- var _render5 = render(/*#__PURE__*/_jsx(CardFeature, {
378
+ var _render7 = render(/*#__PURE__*/_jsx(CardFeature, {
310
379
  title: "Content gifting",
311
380
  moreControls: controls
312
381
  })),
313
- rerender = _render5.rerender;
382
+ rerender = _render7.rerender;
314
383
  expect(moreMenu()).not.toBeInTheDocument();
315
384
  rerender(/*#__PURE__*/_jsx(CardFeature, {
316
385
  title: "Content gifting",
@@ -325,14 +394,14 @@ describe('CardFeature', function () {
325
394
  expect(moreMenu()).toBeInTheDocument();
326
395
  });
327
396
  it('stays available on an actionable requirement and hides on a locked one', function () {
328
- var _render6 = render(/*#__PURE__*/_jsx(CardFeature, {
397
+ var _render8 = render(/*#__PURE__*/_jsx(CardFeature, {
329
398
  title: "Content gifting",
330
399
  enabled: true,
331
400
  requirements: "Requires metering",
332
401
  requirementsActionable: true,
333
402
  moreControls: controls
334
403
  })),
335
- rerender = _render6.rerender;
404
+ rerender = _render8.rerender;
336
405
  expect(moreMenu()).toBeInTheDocument();
337
406
  rerender(/*#__PURE__*/_jsx(CardFeature, {
338
407
  title: "Content gifting",
@@ -345,88 +414,74 @@ describe('CardFeature', function () {
345
414
  });
346
415
  describe('icon', function () {
347
416
  it('renders a ready element as-is, without the descriptor container', function () {
348
- var _render7 = render(/*#__PURE__*/_jsx(CardFeature, {
417
+ var _render9 = render(/*#__PURE__*/_jsx(CardFeature, {
349
418
  title: "Content gifting",
350
419
  icon: /*#__PURE__*/_jsx("span", {
351
420
  "data-testid": "ready-icon"
352
421
  })
353
422
  })),
354
- container = _render7.container;
423
+ container = _render9.container;
355
424
  expect(screen.getByTestId('ready-icon')).toBeInTheDocument();
356
425
  expect(container.querySelector('.newspack-card-feature__icon')).toBeNull();
357
426
  });
358
- it('applies the descriptor colours inline and rounds fully on request', function () {
359
- var _render8 = render(/*#__PURE__*/_jsx(CardFeature, {
427
+ it('applies the descriptor colours inline and rounds a backed icon', function () {
428
+ var _render0 = render(/*#__PURE__*/_jsx(CardFeature, {
360
429
  title: "Content gifting",
361
430
  icon: {
362
431
  node: /*#__PURE__*/_jsx("span", {
363
432
  "data-testid": "descriptor-icon"
364
433
  }),
365
434
  fill: '#003da5',
366
- backgroundColor: '#dfe7f4',
367
- radius: 'full'
435
+ backgroundColor: '#dfe7f4'
368
436
  }
369
437
  })),
370
- container = _render8.container;
438
+ container = _render0.container;
371
439
  var iconContainer = container.querySelector('.newspack-card-feature__icon');
372
440
  expect(screen.getByTestId('descriptor-icon')).toBeInTheDocument();
373
- expect(iconContainer).toHaveClass('newspack-card-feature__icon--radius-full');
441
+ expect(iconContainer).toHaveClass('newspack-card-feature__icon--has-background');
374
442
  expect(iconContainer).toHaveStyle({
375
443
  backgroundColor: '#dfe7f4',
376
444
  color: '#003da5'
377
445
  });
378
446
  });
379
447
  it('hides the descriptor container from assistive tech, since the title already names the feature', function () {
380
- var _render9 = render(/*#__PURE__*/_jsx(CardFeature, {
448
+ var _render1 = render(/*#__PURE__*/_jsx(CardFeature, {
381
449
  title: "Content gifting",
382
450
  icon: {
383
451
  node: /*#__PURE__*/_jsx("svg", {})
384
452
  }
385
453
  })),
386
- container = _render9.container;
454
+ container = _render1.container;
387
455
  expect(container.querySelector('.newspack-card-feature__icon')).toHaveAttribute('aria-hidden', 'true');
388
456
  });
389
- it('falls back to small corners when a background is set without a radius', function () {
390
- var _render0 = render(/*#__PURE__*/_jsx(CardFeature, {
391
- title: "Content gifting",
392
- icon: {
393
- node: /*#__PURE__*/_jsx("span", {}),
394
- backgroundColor: '#dfe7f4'
395
- }
396
- })),
397
- container = _render0.container;
398
- var iconContainer = container.querySelector('.newspack-card-feature__icon');
399
- expect(iconContainer).toHaveClass('newspack-card-feature__icon--radius-small');
400
- expect(iconContainer).not.toHaveClass('newspack-card-feature__icon--radius-full');
401
- });
402
- it('leaves an unbacked descriptor icon without a radius class', function () {
403
- var _render1 = render(/*#__PURE__*/_jsx(CardFeature, {
457
+ it('leaves an unbacked descriptor icon without the background class', function () {
458
+ var _render10 = render(/*#__PURE__*/_jsx(CardFeature, {
404
459
  title: "Content gifting",
405
460
  icon: {
406
461
  node: /*#__PURE__*/_jsx("span", {}),
407
462
  fill: '#003da5'
408
463
  }
409
464
  })),
410
- container = _render1.container;
465
+ container = _render10.container;
411
466
  var iconContainer = container.querySelector('.newspack-card-feature__icon');
412
- expect(iconContainer).not.toHaveClass('newspack-card-feature__icon--radius-small');
467
+ expect(iconContainer).not.toHaveClass('newspack-card-feature__icon--has-background');
413
468
  });
414
469
  });
415
470
  it('passes className through to the card element', function () {
416
- var _render10 = render(/*#__PURE__*/_jsx(CardFeature, {
471
+ var _render11 = render(/*#__PURE__*/_jsx(CardFeature, {
417
472
  title: "Content gifting",
418
473
  className: "newspack-subscribers__card"
419
474
  })),
420
- container = _render10.container;
475
+ container = _render11.container;
421
476
  expect(container.querySelector('.newspack-card-feature')).toHaveClass('newspack-subscribers__card');
422
477
  });
423
478
  it('marks the card as muted only when requirements are set', function () {
424
- var _render11 = render(/*#__PURE__*/_jsx(CardFeature, {
479
+ var _render12 = render(/*#__PURE__*/_jsx(CardFeature, {
425
480
  title: "Content gifting",
426
481
  enabled: true
427
482
  })),
428
- container = _render11.container,
429
- rerender = _render11.rerender;
483
+ container = _render12.container,
484
+ rerender = _render12.rerender;
430
485
  expect(container.querySelector('.newspack-card-feature--muted')).toBeNull();
431
486
  rerender(/*#__PURE__*/_jsx(CardFeature, {
432
487
  title: "Content gifting",
@@ -49,11 +49,7 @@
49
49
  width: wp-vars.$grid-unit-30;
50
50
  }
51
51
 
52
- &--radius-small {
53
- border-radius: wp-vars.$radius-small;
54
- }
55
-
56
- &--radius-full {
52
+ &--has-background {
57
53
  border-radius: wp-vars.$radius-round;
58
54
  }
59
55
  }
@@ -6,20 +6,20 @@ import classnames from 'classnames';
6
6
  /**
7
7
  * Internal dependencies
8
8
  */
9
- import { espProviderIcons } from '../integration-icons';
9
+ import { providerIcons } from '../integration-icons';
10
10
  import './style.scss';
11
11
 
12
12
  /**
13
- * The single source for how an ESP provider is shown: brand mark plus badge
14
- * background. A new provider needs an icon file, an `espProviderIcons` entry,
13
+ * The single source for how a provider is shown: brand mark plus badge
14
+ * background. A new provider needs an icon file, a `providerIcons` entry,
15
15
  * and a background rule in this component's stylesheet.
16
16
  */
17
17
  import { jsx as _jsx } from "react/jsx-runtime";
18
18
  var IntegrationIcon = function IntegrationIcon(_ref) {
19
19
  var provider = _ref.provider,
20
20
  className = _ref.className;
21
- var icons = espProviderIcons;
22
- var node = Object.prototype.hasOwnProperty.call(espProviderIcons, provider) ? icons[provider] : null;
21
+ var icons = providerIcons;
22
+ var node = Object.prototype.hasOwnProperty.call(providerIcons, provider) ? icons[provider] : null;
23
23
  if (!node) {
24
24
  return null;
25
25
  }