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.
- package/CHANGELOG.md +9 -1
- package/dist/cjs/card-feature/index.js +28 -3
- package/dist/cjs/card-feature/index.test.js +100 -45
- package/dist/cjs/card-feature/style.scss +1 -5
- package/dist/cjs/integration-icon/index.js +4 -4
- package/dist/cjs/integration-icon/style.scss +18 -2
- package/dist/cjs/integration-icons/active-campaign.js +4 -7
- package/dist/cjs/integration-icons/beehiiv.js +19 -0
- package/dist/cjs/integration-icons/gravity-forms.js +19 -0
- package/dist/cjs/integration-icons/index.js +26 -6
- package/dist/cjs/integration-icons/salesforce.js +5 -3
- package/dist/cjs/wizard/breadcrumbs-select.js +17 -4
- package/dist/cjs/wizard/breadcrumbs-select.test.js +17 -0
- package/dist/cjs/wizard/index.js +42 -6
- package/dist/cjs/wizard/index.test.js +146 -0
- package/dist/esm/card-feature/index.js +29 -4
- package/dist/esm/card-feature/index.test.js +100 -45
- package/dist/esm/card-feature/style.scss +1 -5
- package/dist/esm/integration-icon/index.js +5 -5
- package/dist/esm/integration-icon/style.scss +18 -2
- package/dist/esm/integration-icons/active-campaign.js +5 -8
- package/dist/esm/integration-icons/beehiiv.js +13 -0
- package/dist/esm/integration-icons/gravity-forms.js +13 -0
- package/dist/esm/integration-icons/index.js +15 -4
- package/dist/esm/integration-icons/salesforce.js +5 -3
- package/dist/esm/wizard/breadcrumbs-select.js +16 -3
- package/dist/esm/wizard/breadcrumbs-select.test.js +18 -1
- package/dist/esm/wizard/index.js +42 -6
- package/dist/esm/wizard/index.test.js +146 -0
- package/package.json +1 -1
- package/src/card-feature/README.md +10 -11
- package/src/card-feature/index.test.js +45 -15
- package/src/card-feature/index.tsx +43 -24
- package/src/card-feature/style.scss +1 -5
- package/src/integration-icon/index.tsx +5 -5
- package/src/integration-icon/style.scss +18 -2
- package/src/integration-icons/active-campaign.js +1 -5
- package/src/integration-icons/beehiiv.js +10 -0
- package/src/integration-icons/gravity-forms.js +10 -0
- package/src/integration-icons/index.js +14 -3
- package/src/integration-icons/salesforce.js +3 -3
- package/src/wizard/breadcrumbs-select.js +12 -4
- package/src/wizard/breadcrumbs-select.test.js +15 -1
- package/src/wizard/index.js +34 -5
- 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
|
});
|
package/dist/cjs/wizard/index.js
CHANGED
|
@@ -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
|
-
|
|
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$
|
|
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$
|
|
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--
|
|
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
|
|
298
|
+
var _render4 = render(/*#__PURE__*/_jsx(CardFeature, {
|
|
231
299
|
title: "Content gifting",
|
|
232
300
|
busy: true,
|
|
233
301
|
onEnable: onEnable
|
|
234
302
|
})),
|
|
235
|
-
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
|
|
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 =
|
|
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
|
|
340
|
+
var _render6 = render(/*#__PURE__*/_jsx(CardFeature, {
|
|
272
341
|
title: "Content gifting"
|
|
273
342
|
})),
|
|
274
|
-
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
|
|
378
|
+
var _render7 = render(/*#__PURE__*/_jsx(CardFeature, {
|
|
310
379
|
title: "Content gifting",
|
|
311
380
|
moreControls: controls
|
|
312
381
|
})),
|
|
313
|
-
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
359
|
-
var
|
|
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 =
|
|
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--
|
|
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
|
|
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 =
|
|
454
|
+
container = _render1.container;
|
|
387
455
|
expect(container.querySelector('.newspack-card-feature__icon')).toHaveAttribute('aria-hidden', 'true');
|
|
388
456
|
});
|
|
389
|
-
it('
|
|
390
|
-
var
|
|
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 =
|
|
465
|
+
container = _render10.container;
|
|
411
466
|
var iconContainer = container.querySelector('.newspack-card-feature__icon');
|
|
412
|
-
expect(iconContainer).not.toHaveClass('newspack-card-feature__icon--
|
|
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
|
|
471
|
+
var _render11 = render(/*#__PURE__*/_jsx(CardFeature, {
|
|
417
472
|
title: "Content gifting",
|
|
418
473
|
className: "newspack-subscribers__card"
|
|
419
474
|
})),
|
|
420
|
-
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
|
|
479
|
+
var _render12 = render(/*#__PURE__*/_jsx(CardFeature, {
|
|
425
480
|
title: "Content gifting",
|
|
426
481
|
enabled: true
|
|
427
482
|
})),
|
|
428
|
-
container =
|
|
429
|
-
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",
|
|
@@ -6,20 +6,20 @@ import classnames from 'classnames';
|
|
|
6
6
|
/**
|
|
7
7
|
* Internal dependencies
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import { providerIcons } from '../integration-icons';
|
|
10
10
|
import './style.scss';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* The single source for how
|
|
14
|
-
* background. A new provider needs an icon file,
|
|
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 =
|
|
22
|
-
var node = Object.prototype.hasOwnProperty.call(
|
|
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
|
}
|