newspack-components 4.5.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/DEVELOPMENT.md +1 -1
  3. package/dist/cjs/accordion/index.js +43 -26
  4. package/dist/cjs/accordion/index.test.js +80 -0
  5. package/dist/cjs/accordion/style.scss +25 -21
  6. package/dist/cjs/action-card/style.scss +0 -22
  7. package/dist/cjs/breadcrumbs/index.js +80 -0
  8. package/dist/cjs/breadcrumbs/index.test.js +109 -0
  9. package/dist/cjs/breadcrumbs/style.scss +41 -0
  10. package/dist/cjs/card/core-card.js +8 -4
  11. package/dist/cjs/card/index.js +1 -1
  12. package/dist/cjs/card/style-core.scss +14 -0
  13. package/dist/cjs/card/style.scss +0 -9
  14. package/dist/cjs/card-feature/index.js +25 -13
  15. package/dist/cjs/card-settings-group/index.js +3 -0
  16. package/dist/cjs/confirm-dialog/index.js +34 -8
  17. package/dist/cjs/confirm-dialog/index.test.js +173 -0
  18. package/dist/cjs/divider/style.scss +4 -2
  19. package/dist/cjs/grid/style.scss +5 -0
  20. package/dist/cjs/handoff/index.js +8 -5
  21. package/dist/cjs/index.js +34 -1
  22. package/dist/cjs/integration-icon/index.js +40 -0
  23. package/dist/cjs/integration-icon/style.scss +42 -0
  24. package/dist/cjs/integration-icons/index.js +21 -2
  25. package/dist/cjs/modal/style.scss +4 -0
  26. package/dist/cjs/page/index.js +121 -0
  27. package/dist/cjs/page/index.test.js +69 -0
  28. package/dist/cjs/page/style.scss +86 -0
  29. package/dist/cjs/route-match.js +59 -0
  30. package/dist/cjs/route-match.test.js +77 -0
  31. package/dist/cjs/section-header/index.js +8 -1
  32. package/dist/cjs/section-header/style.scss +11 -11
  33. package/dist/cjs/select-control/GroupedSelectControl.js +1 -1
  34. package/dist/cjs/select-control/style.scss +4 -0
  35. package/dist/cjs/style.scss +1 -4
  36. package/dist/cjs/tabbed-navigation/index.js +188 -40
  37. package/dist/cjs/tabbed-navigation/index.test.js +299 -0
  38. package/dist/cjs/tabbed-navigation/style.scss +77 -57
  39. package/dist/cjs/with-wizard/index.js +34 -45
  40. package/dist/cjs/with-wizard-screen/index.js +82 -41
  41. package/dist/cjs/with-wizard-screen/index.test.js +50 -0
  42. package/dist/cjs/with-wizard-screen/style.scss +15 -118
  43. package/dist/cjs/wizard/breadcrumbs-select.js +70 -0
  44. package/dist/cjs/wizard/breadcrumbs-select.test.js +200 -0
  45. package/dist/cjs/wizard/components/WizardError.js +8 -4
  46. package/dist/cjs/wizard/index.js +162 -141
  47. package/dist/esm/accordion/index.js +42 -26
  48. package/dist/esm/accordion/index.test.js +79 -0
  49. package/dist/esm/accordion/style.scss +25 -21
  50. package/dist/esm/action-card/style.scss +0 -22
  51. package/dist/esm/breadcrumbs/index.js +75 -0
  52. package/dist/esm/breadcrumbs/index.test.js +105 -0
  53. package/dist/esm/breadcrumbs/style.scss +41 -0
  54. package/dist/esm/card/core-card.js +8 -4
  55. package/dist/esm/card/index.js +1 -1
  56. package/dist/esm/card/style-core.scss +14 -0
  57. package/dist/esm/card/style.scss +0 -9
  58. package/dist/esm/card-feature/index.js +25 -13
  59. package/dist/esm/card-settings-group/index.js +3 -0
  60. package/dist/esm/confirm-dialog/index.js +34 -8
  61. package/dist/esm/confirm-dialog/index.test.js +173 -0
  62. package/dist/esm/divider/style.scss +4 -2
  63. package/dist/esm/grid/style.scss +5 -0
  64. package/dist/esm/handoff/index.js +7 -5
  65. package/dist/esm/index.js +5 -1
  66. package/dist/esm/integration-icon/index.js +34 -0
  67. package/dist/esm/integration-icon/style.scss +42 -0
  68. package/dist/esm/integration-icons/index.js +22 -1
  69. package/dist/esm/modal/style.scss +4 -0
  70. package/dist/esm/page/index.js +113 -0
  71. package/dist/esm/page/index.test.js +65 -0
  72. package/dist/esm/page/style.scss +86 -0
  73. package/dist/esm/route-match.js +51 -0
  74. package/dist/esm/route-match.test.js +74 -0
  75. package/dist/esm/section-header/index.js +8 -1
  76. package/dist/esm/section-header/style.scss +11 -11
  77. package/dist/esm/select-control/GroupedSelectControl.js +1 -1
  78. package/dist/esm/select-control/style.scss +4 -0
  79. package/dist/esm/style.scss +1 -4
  80. package/dist/esm/tabbed-navigation/index.js +192 -38
  81. package/dist/esm/tabbed-navigation/index.test.js +296 -0
  82. package/dist/esm/tabbed-navigation/style.scss +77 -57
  83. package/dist/esm/with-wizard/index.js +33 -45
  84. package/dist/esm/with-wizard-screen/index.js +78 -41
  85. package/dist/esm/with-wizard-screen/index.test.js +46 -0
  86. package/dist/esm/with-wizard-screen/style.scss +15 -118
  87. package/dist/esm/wizard/breadcrumbs-select.js +63 -0
  88. package/dist/esm/wizard/breadcrumbs-select.test.js +197 -0
  89. package/dist/esm/wizard/components/WizardError.js +7 -5
  90. package/dist/esm/wizard/index.js +165 -144
  91. package/package.json +10 -2
  92. package/src/accordion/index.js +27 -15
  93. package/src/accordion/index.test.js +62 -0
  94. package/src/accordion/style.scss +25 -21
  95. package/src/action-card/style.scss +0 -22
  96. package/src/breadcrumbs/index.js +63 -0
  97. package/src/breadcrumbs/index.test.js +59 -0
  98. package/src/breadcrumbs/style.scss +41 -0
  99. package/src/card/core-card.js +11 -6
  100. package/src/card/index.js +0 -1
  101. package/src/card/style-core.scss +14 -0
  102. package/src/card/style.scss +0 -9
  103. package/src/card-feature/README.md +3 -3
  104. package/src/card-feature/index.tsx +37 -21
  105. package/src/card-settings-group/index.tsx +4 -0
  106. package/src/card-sortable-list/index.tsx +1 -1
  107. package/src/confirm-dialog/index.test.js +142 -0
  108. package/src/confirm-dialog/index.tsx +34 -7
  109. package/src/divider/style.scss +4 -2
  110. package/src/grid/style.scss +5 -0
  111. package/src/handoff/index.js +4 -3
  112. package/src/index.js +5 -1
  113. package/src/integration-icon/index.tsx +35 -0
  114. package/src/integration-icon/style.scss +42 -0
  115. package/src/integration-icons/index.js +23 -0
  116. package/src/modal/style.scss +4 -0
  117. package/src/page/index.js +89 -0
  118. package/src/page/index.test.js +43 -0
  119. package/src/page/style.scss +86 -0
  120. package/src/route-match.js +47 -0
  121. package/src/route-match.test.js +50 -0
  122. package/src/section-header/index.js +7 -3
  123. package/src/section-header/style.scss +11 -11
  124. package/src/select-control/GroupedSelectControl.js +1 -1
  125. package/src/select-control/style.scss +4 -0
  126. package/src/style.scss +1 -4
  127. package/src/tabbed-navigation/index.js +181 -34
  128. package/src/tabbed-navigation/index.test.js +228 -0
  129. package/src/tabbed-navigation/style.scss +77 -57
  130. package/src/with-wizard/index.js +18 -39
  131. package/src/with-wizard-screen/index.js +70 -35
  132. package/src/with-wizard-screen/index.test.js +36 -0
  133. package/src/with-wizard-screen/style.scss +15 -118
  134. package/src/wizard/breadcrumbs-select.js +51 -0
  135. package/src/wizard/breadcrumbs-select.test.js +124 -0
  136. package/src/wizard/components/WizardError.js +4 -3
  137. package/src/wizard/index.js +160 -146
  138. package/tsconfig.json +7 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,43 @@
1
+ # newspack-components [4.6.0](https://github.com/Automattic/newspack-workspace/compare/newspack-components@4.5.0...newspack-components@4.6.0) (2026-08-03)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **audience:** unblock the ESP connect flow in Integrations ([#258](https://github.com/Automattic/newspack-workspace/issues/258)) ([93f3a67](https://github.com/Automattic/newspack-workspace/commit/93f3a67a825b95e0e0f43ac09dad536ee5ccf6f2))
7
+ * **components:** balance section-header description text-wrap ([#624](https://github.com/Automattic/newspack-workspace/issues/624)) ([e6b7076](https://github.com/Automattic/newspack-workspace/commit/e6b70764f63ad98c781ae648728e92819308ace5))
8
+ * **components:** render routed content when no tab owns the route ([#672](https://github.com/Automattic/newspack-workspace/issues/672)) ([ff2ba8d](https://github.com/Automattic/newspack-workspace/commit/ff2ba8d3182e6be0e8e1ebdb7a7af666786ba93e))
9
+
10
+
11
+ ### Features
12
+
13
+ * explicit breadcrumb API for Newspack admin headers ([#472](https://github.com/Automattic/newspack-workspace/issues/472)) ([7c43233](https://github.com/Automattic/newspack-workspace/commit/7c4323301f5323822937e93274c4fda4279a787f))
14
+
15
+
16
+ ### Dependencies
17
+
18
+ * **newspack-icons:** upgraded to 1.1.0
19
+ * **newspack-scripts:** upgraded to 5.10.0
20
+
21
+ # newspack-components [4.6.0-alpha.1](https://github.com/Automattic/newspack-workspace/compare/newspack-components@4.5.0...newspack-components@4.6.0-alpha.1) (2026-07-23)
22
+
23
+
24
+ ### Bug Fixes
25
+
26
+ * **audience:** unblock the ESP connect flow in Integrations ([#258](https://github.com/Automattic/newspack-workspace/issues/258)) ([93f3a67](https://github.com/Automattic/newspack-workspace/commit/93f3a67a825b95e0e0f43ac09dad536ee5ccf6f2))
27
+ * **components:** balance section-header description text-wrap ([#624](https://github.com/Automattic/newspack-workspace/issues/624)) ([e6b7076](https://github.com/Automattic/newspack-workspace/commit/e6b70764f63ad98c781ae648728e92819308ace5))
28
+ * **components:** render routed content when no tab owns the route ([#672](https://github.com/Automattic/newspack-workspace/issues/672)) ([ff2ba8d](https://github.com/Automattic/newspack-workspace/commit/ff2ba8d3182e6be0e8e1ebdb7a7af666786ba93e))
29
+
30
+
31
+ ### Features
32
+
33
+ * explicit breadcrumb API for Newspack admin headers ([#472](https://github.com/Automattic/newspack-workspace/issues/472)) ([7c43233](https://github.com/Automattic/newspack-workspace/commit/7c4323301f5323822937e93274c4fda4279a787f))
34
+
35
+
36
+ ### Dependencies
37
+
38
+ * **newspack-icons:** upgraded to 1.1.0-alpha.1
39
+ * **newspack-scripts:** upgraded to 5.10.0-alpha.1
40
+
1
41
  # newspack-components [4.5.0](https://github.com/Automattic/newspack-workspace/compare/newspack-components@4.4.0...newspack-components@4.5.0) (2026-07-20)
2
42
 
3
43
 
package/DEVELOPMENT.md CHANGED
@@ -100,7 +100,7 @@ When building a screen, use the **spacing scale** (8px unit: 16, 24, 32, 48, 64)
100
100
  - **`Notice`** – Use for outcome feedback (success/error/warning) or short contextual messages. Vertical margin is 32px so notices don’t collide with cards; keep one primary message per area when possible.
101
101
  - **`Waiting`** – Loading state indicator.
102
102
  - **`ProgressBar`** – Progress indicator.
103
- - **`Accordion`** – Collapsible content sections.
103
+ - **`Accordion`** / **`AccordionPanel`** – Container for one or more collapsible panels.
104
104
  - **`StepsList`** / **`StepsListItem`** – Step-by-step list components.
105
105
  - **`StyleCard`** – Style preview card.
106
106
 
@@ -4,16 +4,17 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports["default"] = void 0;
8
- var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
7
+ exports["default"] = exports.AccordionPanel = void 0;
8
+ var _components = require("@wordpress/components");
9
9
  var _element = require("@wordpress/element");
10
- var _icons = require("@wordpress/icons");
11
10
  var _classnames = _interopRequireDefault(require("classnames"));
11
+ var _divider = _interopRequireDefault(require("../divider"));
12
12
  require("./style.scss");
13
13
  var _jsxRuntime = require("react/jsx-runtime");
14
14
  /**
15
15
  * WordPress dependencies
16
16
  */
17
+ // eslint-disable-line @wordpress/no-unsafe-wp-apis
17
18
 
18
19
  /**
19
20
  * External dependencies
@@ -23,33 +24,49 @@ var _jsxRuntime = require("react/jsx-runtime");
23
24
  * Internal dependencies
24
25
  */
25
26
 
26
- var Accordion = function Accordion(_ref) {
27
+ var AccordionPanel = exports.AccordionPanel = function AccordionPanel(_ref) {
27
28
  var children = _ref.children,
29
+ className = _ref.className,
28
30
  title = _ref.title,
29
31
  _ref$defaultOpen = _ref.defaultOpen,
30
32
  defaultOpen = _ref$defaultOpen === void 0 ? false : _ref$defaultOpen;
31
- var _useState = (0, _element.useState)(defaultOpen),
32
- _useState2 = (0, _slicedToArray2["default"])(_useState, 2),
33
- isOpen = _useState2[0],
34
- setIsOpen = _useState2[1];
35
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)("details", {
36
- className: (0, _classnames["default"])('newspack-accordion', {
37
- 'newspack-accordion--is-open': isOpen
38
- }),
39
- open: isOpen,
40
- onToggle: function onToggle(e) {
41
- return setIsOpen(e.currentTarget.open);
42
- },
43
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("summary", {
44
- children: [title, /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.Icon, {
45
- className: "newspack-accordion__icon",
46
- icon: _icons.chevronRight,
47
- size: 24
48
- })]
49
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
50
- className: "newspack-accordion__content",
51
- children: children
52
- })]
33
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.PanelBody, {
34
+ className: className,
35
+ title: title,
36
+ initialOpen: defaultOpen,
37
+ children: children
38
+ });
39
+ };
40
+ var Accordion = function Accordion(_ref2) {
41
+ var children = _ref2.children,
42
+ className = _ref2.className,
43
+ _ref2$hideSingleTitle = _ref2.hideSingleTitle,
44
+ hideSingleTitle = _ref2$hideSingleTitle === void 0 ? false : _ref2$hideSingleTitle,
45
+ _ref2$spacing = _ref2.spacing,
46
+ spacing = _ref2$spacing === void 0 ? 6 : _ref2$spacing;
47
+ var panels = _element.Children.toArray(children);
48
+ // With nothing to collapse against, a lone panel can render open and untitled.
49
+ if (hideSingleTitle && panels.length === 1) {
50
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
51
+ className: (0, _classnames["default"])('newspack-accordion', className),
52
+ children: (0, _element.cloneElement)(panels[0], {
53
+ defaultOpen: true,
54
+ title: undefined
55
+ })
56
+ });
57
+ }
58
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.__experimentalVStack, {
59
+ className: (0, _classnames["default"])('newspack-accordion', className),
60
+ spacing: spacing,
61
+ children: panels.map(function (panel, index) {
62
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_element.Fragment, {
63
+ children: [panel, index < panels.length - 1 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_divider["default"], {
64
+ variant: "secondary",
65
+ marginBottom: 0,
66
+ marginTop: 0
67
+ })]
68
+ }, panel.key);
69
+ })
53
70
  });
54
71
  };
55
72
  var _default = exports["default"] = Accordion;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
5
+ var _react = require("@testing-library/react");
6
+ var _index = _interopRequireWildcard(require("./index"));
7
+ var _jsxRuntime = require("react/jsx-runtime");
8
+ 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); }
9
+ 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; }
10
+ 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; } /**
11
+ * External dependencies
12
+ */ /**
13
+ * Internal dependencies
14
+ */
15
+ var renderPanels = function renderPanels(count) {
16
+ var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
17
+ return (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_index["default"], _objectSpread(_objectSpread({}, props), {}, {
18
+ children: Array.from({
19
+ length: count
20
+ }, function (_, i) {
21
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.AccordionPanel, {
22
+ title: "Panel ".concat(i),
23
+ children: "content"
24
+ }, i);
25
+ })
26
+ })));
27
+ };
28
+ var dividers = function dividers(container) {
29
+ return container.querySelectorAll('.newspack-divider');
30
+ };
31
+ describe('Accordion dividers', function () {
32
+ it('renders no divider for a single panel', function () {
33
+ var _renderPanels = renderPanels(1),
34
+ container = _renderPanels.container;
35
+ expect(container.querySelectorAll('.components-panel__body')).toHaveLength(1);
36
+ expect(dividers(container)).toHaveLength(0);
37
+ });
38
+ it('renders a divider between panels but not after the last', function () {
39
+ var _renderPanels2 = renderPanels(3),
40
+ container = _renderPanels2.container;
41
+ expect(container.querySelectorAll('.components-panel__body')).toHaveLength(3);
42
+ expect(dividers(container)).toHaveLength(2);
43
+ expect(container.querySelector('.newspack-accordion').lastElementChild).not.toHaveClass('newspack-divider');
44
+ });
45
+ it('renders secondary dividers', function () {
46
+ var _renderPanels3 = renderPanels(2),
47
+ container = _renderPanels3.container;
48
+ expect(dividers(container)[0]).toHaveClass('newspack-divider--variant-secondary');
49
+ });
50
+ });
51
+ describe('Accordion hideSingleTitle', function () {
52
+ it('keeps the title on a lone panel by default', function () {
53
+ renderPanels(1);
54
+ expect(_react.screen.getByRole('button', {
55
+ name: 'Panel 0'
56
+ })).toBeInTheDocument();
57
+ });
58
+ it('drops the title and opens a lone panel when set', function () {
59
+ var _renderPanels4 = renderPanels(1, {
60
+ hideSingleTitle: true
61
+ }),
62
+ container = _renderPanels4.container;
63
+ expect(_react.screen.queryByRole('button', {
64
+ name: 'Panel 0'
65
+ })).not.toBeInTheDocument();
66
+ expect(container.querySelector('.components-panel__body')).toHaveClass('is-opened');
67
+ expect(_react.screen.getByText('content')).toBeInTheDocument();
68
+ });
69
+ it('leaves titles alone when there is more than one panel', function () {
70
+ renderPanels(2, {
71
+ hideSingleTitle: true
72
+ });
73
+ expect(_react.screen.getByRole('button', {
74
+ name: 'Panel 0'
75
+ })).toBeInTheDocument();
76
+ expect(_react.screen.getByRole('button', {
77
+ name: 'Panel 1'
78
+ })).toBeInTheDocument();
79
+ });
80
+ });
@@ -1,31 +1,35 @@
1
- @use "~@wordpress/base-styles/colors" as wp-colors;
1
+ @use "~@wordpress/base-styles/mixins" as wp-mixins;
2
+ @use "~@wordpress/base-styles/variables" as wp-vars;
2
3
 
4
+ // Sit flush with the surrounding column: core insets panels horizontally, which
5
+ // would misalign them against the section they belong to.
3
6
  .newspack-accordion {
4
- border: 1px solid wp-colors.$gray-300;
7
+ .components-panel__body {
8
+ border: 0;
5
9
 
6
- &__icon {
7
- transition: transform 200ms ease-out;
8
- transform: rotate(90deg);
10
+ > .components-panel__body-title:hover {
11
+ background: none;
12
+ }
9
13
  }
10
14
 
11
- &--is-open {
12
- .newspack-accordion {
13
- &__icon {
14
- transform: rotate(-90deg);
15
- }
15
+ .components-panel__body.is-opened {
16
+ padding: 0;
17
+
18
+ > .components-panel__body-title {
19
+ margin: 0 0 wp-vars.$grid-unit-30;
16
20
  }
17
21
  }
18
- summary {
19
- padding: 16px 32px;
20
- display: flex;
21
- align-items: center;
22
- justify-content: space-between;
23
- cursor: pointer;
24
- font-size: 14px;
25
- font-weight: bold;
26
- }
27
22
 
28
- &__content {
29
- padding: 16px 32px;
23
+ .components-panel__body-toggle.components-button {
24
+ @include wp-mixins.heading-large();
25
+ padding: 0;
26
+
27
+ .components-panel__arrow {
28
+ right: 0;
29
+ }
30
+
31
+ &:focus:not(:focus-visible) {
32
+ box-shadow: none;
33
+ }
30
34
  }
31
35
  }
@@ -77,28 +77,6 @@
77
77
  padding: 0 4px;
78
78
  }
79
79
 
80
- &.mv0 {
81
- border-left-color: wp-colors.$gray-300;
82
- border-radius: 0;
83
- border-right-color: wp-colors.$gray-300;
84
-
85
- &:first-of-type {
86
- border-top-color: wp-colors.$gray-300;
87
- border-top-left-radius: 3px;
88
- border-top-right-radius: 3px;
89
- }
90
-
91
- &:last-of-type {
92
- border-bottom-color: wp-colors.$gray-300;
93
- border-bottom-left-radius: 3px;
94
- border-bottom-right-radius: 3px;
95
- }
96
-
97
- & + & {
98
- margin-top: -1px;
99
- }
100
- }
101
-
102
80
  &.newspack-card__is-disabled,
103
81
  &.newspack-card__is-primary,
104
82
  &.newspack-card__is-secondary,
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports["default"] = void 0;
7
+ var _i18n = require("@wordpress/i18n");
8
+ var _ui = require("@wordpress/ui");
9
+ require("./style.scss");
10
+ var _jsxRuntime = require("react/jsx-runtime");
11
+ /**
12
+ * WordPress dependencies.
13
+ */
14
+
15
+ /**
16
+ * Internal dependencies.
17
+ */
18
+
19
+ /**
20
+ * Newspack breadcrumb trail.
21
+ *
22
+ * Data-driven: the last item is the current page (the single h1, never a link);
23
+ * every other item renders as a link when it has a `url`, otherwise plain text.
24
+ * A plain `<a href>` drives both full admin URLs and hash routes, so the
25
+ * component needs no router context.
26
+ *
27
+ * @param {Object} props
28
+ * @param {Array} props.items Trail items: `{ label, url? }`.
29
+ * @return {JSX.Element|null} Breadcrumbs component.
30
+ */var Breadcrumbs = function Breadcrumbs(_ref) {
31
+ var _ref$items = _ref.items,
32
+ items = _ref$items === void 0 ? [] : _ref$items;
33
+ if (!items.length) {
34
+ return null;
35
+ }
36
+ var preceding = items.slice(0, -1);
37
+ var last = items[items.length - 1];
38
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("nav", {
39
+ "aria-label": (0, _i18n.__)('Breadcrumbs', 'newspack-plugin'),
40
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_ui.Stack, {
41
+ render: /*#__PURE__*/(0, _jsxRuntime.jsx)("ul", {}),
42
+ direction: "row",
43
+ align: "center",
44
+ className: "newspack-breadcrumbs__list",
45
+ children: [preceding.map(function (item, index) {
46
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)("li", {
47
+ children: [item.url ?
48
+ /*#__PURE__*/
49
+ // eslint-disable-next-line jsx-a11y/anchor-has-content -- content is supplied via the Text children through @wordpress/ui's render prop.
50
+ (0, _jsxRuntime.jsx)(_ui.Text, {
51
+ variant: "body-lg",
52
+ render: /*#__PURE__*/(0, _jsxRuntime.jsx)(_ui.Link, {
53
+ tone: "neutral",
54
+ render: /*#__PURE__*/(0, _jsxRuntime.jsx)("a", {
55
+ href: item.url
56
+ })
57
+ }),
58
+ children: item.label
59
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_ui.Text, {
60
+ variant: "body-lg",
61
+ children: item.label
62
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_ui.Text, {
63
+ variant: "body-lg",
64
+ "aria-hidden": "true",
65
+ className: "newspack-breadcrumbs__separator",
66
+ children: "/"
67
+ })]
68
+ }, item.url || index);
69
+ }), last && last.label && /*#__PURE__*/(0, _jsxRuntime.jsx)("li", {
70
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_ui.Text, {
71
+ variant: "heading-lg",
72
+ render: /*#__PURE__*/(0, _jsxRuntime.jsx)("h1", {}),
73
+ className: "newspack-breadcrumbs__current",
74
+ children: last.label
75
+ })
76
+ })]
77
+ })
78
+ });
79
+ };
80
+ var _default = exports["default"] = Breadcrumbs;
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _react = require("@testing-library/react");
5
+ var _ = _interopRequireDefault(require("./"));
6
+ var _jsxRuntime = require("react/jsx-runtime");
7
+ /**
8
+ * External dependencies.
9
+ */
10
+
11
+ /**
12
+ * Internal dependencies.
13
+ */
14
+
15
+ describe('Breadcrumbs', function () {
16
+ it('renders the last item as the only h1 and never a link', function () {
17
+ (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_["default"], {
18
+ items: [{
19
+ label: 'Newsletters'
20
+ }, {
21
+ label: 'Settings',
22
+ url: '#/x'
23
+ }]
24
+ }));
25
+ var h1s = _react.screen.getAllByRole('heading', {
26
+ level: 1
27
+ });
28
+ expect(h1s).toHaveLength(1);
29
+ expect(h1s[0]).toHaveTextContent('Settings');
30
+ expect(_react.screen.queryByRole('link', {
31
+ name: 'Settings'
32
+ })).not.toBeInTheDocument();
33
+ });
34
+ it('renders a non-last item with a url as a link, without a url as text', function () {
35
+ (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_["default"], {
36
+ items: [{
37
+ label: 'Advertising'
38
+ }, {
39
+ label: 'Sponsors',
40
+ url: '/wp-admin/x'
41
+ }, {
42
+ label: 'All sponsors'
43
+ }]
44
+ }));
45
+ var links = _react.screen.getAllByRole('link');
46
+ expect(links).toHaveLength(1);
47
+ expect(links[0]).toHaveTextContent('Sponsors');
48
+ expect(links[0].getAttribute('href')).toBe('/wp-admin/x');
49
+ expect(_react.screen.getByText('Advertising')).toBeInTheDocument();
50
+ });
51
+ it('does not special-case the first item: it links when it has a url', function () {
52
+ (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_["default"], {
53
+ items: [{
54
+ label: 'Audience',
55
+ url: '#/'
56
+ }, {
57
+ label: 'Donations'
58
+ }]
59
+ }));
60
+ var link = _react.screen.getByRole('link', {
61
+ name: 'Audience'
62
+ });
63
+ expect(link.getAttribute('href')).toBe('#/');
64
+ });
65
+ it('renders a single-item trail as just the h1 with no separator', function () {
66
+ var _render = (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_["default"], {
67
+ items: [{
68
+ label: 'Dashboard'
69
+ }]
70
+ })),
71
+ container = _render.container;
72
+ expect(_react.screen.getByRole('heading', {
73
+ level: 1
74
+ })).toHaveTextContent('Dashboard');
75
+ expect(container.querySelector('.newspack-breadcrumbs__separator')).toBeNull();
76
+ expect(_react.screen.queryByRole('link')).not.toBeInTheDocument();
77
+ });
78
+ it('renders a "/" separator after each preceding item', function () {
79
+ var _render2 = (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_["default"], {
80
+ items: [{
81
+ label: 'Audience'
82
+ }, {
83
+ label: 'Access control'
84
+ }]
85
+ })),
86
+ container = _render2.container;
87
+ var separators = container.querySelectorAll('.newspack-breadcrumbs__separator');
88
+ expect(separators).toHaveLength(1);
89
+ expect(separators[0]).toHaveTextContent('/');
90
+ expect(separators[0]).toHaveAttribute('aria-hidden', 'true');
91
+ });
92
+ it('renders no heading when the last item has no label', function () {
93
+ (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_["default"], {
94
+ items: [{
95
+ label: undefined
96
+ }]
97
+ }));
98
+ expect(_react.screen.queryByRole('heading', {
99
+ level: 1
100
+ })).not.toBeInTheDocument();
101
+ });
102
+ it('renders nothing when there are no items', function () {
103
+ var _render3 = (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_["default"], {
104
+ items: []
105
+ })),
106
+ container = _render3.container;
107
+ expect(container.querySelector('nav')).not.toBeInTheDocument();
108
+ });
109
+ });
@@ -0,0 +1,41 @@
1
+ @use "~@wordpress/base-styles/colors" as wp-colors;
2
+
3
+ .newspack-breadcrumbs__list {
4
+ list-style: none;
5
+ margin: 0;
6
+ padding: 0;
7
+
8
+ > li {
9
+ flex-shrink: 0;
10
+ margin: 0;
11
+
12
+ a {
13
+ color: wp-colors.$gray-900;
14
+
15
+ &:hover,
16
+ &:focus,
17
+ &:active {
18
+ color: wp-colors.$gray-900;
19
+ }
20
+ }
21
+ }
22
+
23
+ > li:last-child {
24
+ flex-shrink: 1;
25
+ min-width: 0;
26
+ }
27
+ }
28
+
29
+ .newspack-breadcrumbs__separator {
30
+ color: var(--wpds-color-stroke-surface-neutral, #dbdbdb);
31
+ margin: 0 var(--wpds-dimension-gap-sm, 8px);
32
+ user-select: none;
33
+ }
34
+
35
+ .newspack-breadcrumbs__list h1.newspack-breadcrumbs__current {
36
+ overflow: hidden;
37
+ text-overflow: ellipsis;
38
+ white-space: nowrap;
39
+ padding: 0;
40
+ line-height: normal;
41
+ }
@@ -13,7 +13,7 @@ var _icons = require("@wordpress/icons");
13
13
  require("./style-core.scss");
14
14
  var _classnames = _interopRequireDefault(require("classnames"));
15
15
  var _jsxRuntime = require("react/jsx-runtime");
16
- var _excluded = ["actions", "actionType", "as", "buttonsCard", "className", "footer", "header", "headerAction", "headerStyle", "childrenStyle", "footerStyle", "disabled", "icon", "iconBackgroundColor", "isActive", "isDraggable", "isFirstTarget", "isLastTarget", "isNarrow", "isSmall", "dragIndex", "onDragCallback", "onToggle", "onHeaderClick", "noBorder", "noMargin", "children", "hasGreyHeader", "hasHeaderBorder"];
16
+ var _excluded = ["actions", "actionType", "as", "buttonsCard", "className", "footer", "header", "headerAction", "headerStyle", "childrenStyle", "footerStyle", "disabled", "icon", "iconElement", "iconBackgroundColor", "isActive", "isDraggable", "isFirstTarget", "isLastTarget", "isNarrow", "isSmall", "dragIndex", "onDragCallback", "onToggle", "onHeaderClick", "noBorder", "noMargin", "children", "hasGreyHeader", "hasHeaderBorder"];
17
17
  /**
18
18
  * Card using WP Core's Card component.
19
19
  * https://wordpress.github.io/gutenberg/?path=/docs/components-card--docs
@@ -43,6 +43,7 @@ var CoreCard = function CoreCard(_ref) {
43
43
  footerStyle = _ref.footerStyle,
44
44
  disabled = _ref.disabled,
45
45
  icon = _ref.icon,
46
+ iconElement = _ref.iconElement,
46
47
  iconBackgroundColor = _ref.iconBackgroundColor,
47
48
  isActive = _ref.isActive,
48
49
  isDraggable = _ref.isDraggable,
@@ -64,7 +65,7 @@ var CoreCard = function CoreCard(_ref) {
64
65
  _ref$hasHeaderBorder = _ref.hasHeaderBorder,
65
66
  hasHeaderBorder = _ref$hasHeaderBorder === void 0 ? true : _ref$hasHeaderBorder,
66
67
  otherProps = (0, _objectWithoutProperties2["default"])(_ref, _excluded);
67
- var classes = (0, _classnames["default"])('newspack-card--core', className, (buttonsCard || as === 'a') && 'newspack-card--core__buttons-card', (actions === null || actions === void 0 ? void 0 : actions.length) > 0 && 'newspack-card--core__header--has-actions', isDraggable && 'newspack-card--core__is-draggable', isNarrow && 'newspack-card--core__is-narrow', isSmall && 'newspack-card--core__is-small', icon && 'newspack-card--core__has-icon', iconBackgroundColor && 'newspack-card--core__has-icon-background-color', isActive && 'newspack-card--core__is-active', disabled && 'newspack-card--core__is-disabled', children && 'newspack-card--core__has-children', noMargin && 'newspack-card--core__no-margin', hasGreyHeader && 'newspack-card--core__has-grey-header');
68
+ var classes = (0, _classnames["default"])('newspack-card--core', className, (buttonsCard || as === 'a') && 'newspack-card--core__buttons-card', (actions === null || actions === void 0 ? void 0 : actions.length) > 0 && 'newspack-card--core__header--has-actions', isDraggable && 'newspack-card--core__is-draggable', isNarrow && 'newspack-card--core__is-narrow', isSmall && 'newspack-card--core__is-small', (icon || iconElement) && 'newspack-card--core__has-icon', iconBackgroundColor && 'newspack-card--core__has-icon-background-color', isActive && 'newspack-card--core__is-active', disabled && 'newspack-card--core__is-disabled', children && 'newspack-card--core__has-children', noMargin && 'newspack-card--core__no-margin', hasGreyHeader && 'newspack-card--core__has-grey-header');
68
69
  var sizeProps = isSmall ? 'small' : otherProps.size;
69
70
  if (buttonsCard || as === 'a') {
70
71
  if (!isSmall) {
@@ -86,7 +87,7 @@ var CoreCard = function CoreCard(_ref) {
86
87
  as: as,
87
88
  className: classes
88
89
  }, otherProps), {}, {
89
- children: [(header || icon) && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_components.CardHeader, {
90
+ children: [(header || icon || iconElement) && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_components.CardHeader, {
90
91
  as: headerIsButton ? 'button' : undefined,
91
92
  className: (0, _classnames["default"])('newspack-card--core__header', isDraggable && 'newspack-card--core__header--is-draggable', !hasHeaderBorder && 'newspack-card--core__header--no-border'),
92
93
  style: headerStyle,
@@ -122,7 +123,10 @@ var CoreCard = function CoreCard(_ref) {
122
123
  size: "small"
123
124
  })]
124
125
  })]
125
- }), icon && /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
126
+ }), iconElement ? /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
127
+ className: "newspack-card--core__icon-slot",
128
+ children: iconElement
129
+ }) : icon && /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
126
130
  className: "newspack-card--core__icon",
127
131
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.Icon, {
128
132
  icon: icon,
@@ -81,7 +81,7 @@ var Card = /*#__PURE__*/function (_Component) {
81
81
  }, otherProps), __experimentalCoreProps);
82
82
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_coreCard["default"], _objectSpread({}, props));
83
83
  }
84
- var classes = (0, _classnames["default"])('newspack-card', className, buttonsCard && 'newspack-card__buttons-card', headerActions && 'newspack-card__header-actions', isMedium && 'newspack-card__is-medium', isNarrow && 'newspack-card__is-narrow', isSmall && 'newspack-card__is-small', isWhite && 'newspack-card__is-white', noBorder && 'newspack-card__no-border');
84
+ var classes = (0, _classnames["default"])('newspack-card', className, headerActions && 'newspack-card__header-actions', isMedium && 'newspack-card__is-medium', isNarrow && 'newspack-card__is-narrow', isSmall && 'newspack-card__is-small', isWhite && 'newspack-card__is-white', noBorder && 'newspack-card__no-border');
85
85
  return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", _objectSpread({
86
86
  className: classes
87
87
  }, otherProps));
@@ -14,6 +14,11 @@
14
14
  svg {
15
15
  transition: fill 125ms ease-in-out;
16
16
  }
17
+ // Slot for a pre-rendered icon element that owns its own badge.
18
+ &__icon-slot {
19
+ align-items: center;
20
+ display: flex;
21
+ }
17
22
  &__icon {
18
23
  border-radius: wp-vars.$radius-round;
19
24
  display: grid;
@@ -149,6 +154,15 @@
149
154
  border: none;
150
155
  cursor: pointer;
151
156
  text-align: left;
157
+
158
+ // The header becomes a real button (headerIsButton) with no border, so
159
+ // give keyboard focus an explicit on-brand ring. Inset so it isn't clipped
160
+ // by the card; the transparent outline keeps it visible under Windows High
161
+ // Contrast / forced-colors mode.
162
+ &:focus-visible {
163
+ box-shadow: inset 0 0 0 2px var(--wp-admin-theme-color);
164
+ outline: 2px solid transparent;
165
+ }
152
166
  }
153
167
  &__header-content {
154
168
  display: grid;
@@ -15,15 +15,6 @@
15
15
  padding: 32px 64px;
16
16
  }
17
17
 
18
- // Buttons Card
19
-
20
- &__buttons-card {
21
- display: flex;
22
- flex-wrap: wrap;
23
- gap: 16px;
24
- margin: 32px 0;
25
- }
26
-
27
18
  // Header Actions Card
28
19
 
29
20
  &__header-actions {