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
@@ -1,9 +1,8 @@
1
- import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
1
  /**
3
2
  * WordPress dependencies
4
3
  */
5
- import { useState } from '@wordpress/element';
6
- import { Icon, chevronRight } from '@wordpress/icons';
4
+ import { __experimentalVStack as VStack, PanelBody } from '@wordpress/components'; // eslint-disable-line @wordpress/no-unsafe-wp-apis
5
+ import { Children, Fragment, cloneElement } from '@wordpress/element';
7
6
 
8
7
  /**
9
8
  * External dependencies
@@ -13,35 +12,52 @@ import classNames from 'classnames';
13
12
  /**
14
13
  * Internal dependencies
15
14
  */
15
+ import Divider from '../divider';
16
16
  import './style.scss';
17
17
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
- var Accordion = function Accordion(_ref) {
18
+ export var AccordionPanel = function AccordionPanel(_ref) {
19
19
  var children = _ref.children,
20
+ className = _ref.className,
20
21
  title = _ref.title,
21
22
  _ref$defaultOpen = _ref.defaultOpen,
22
23
  defaultOpen = _ref$defaultOpen === void 0 ? false : _ref$defaultOpen;
23
- var _useState = useState(defaultOpen),
24
- _useState2 = _slicedToArray(_useState, 2),
25
- isOpen = _useState2[0],
26
- setIsOpen = _useState2[1];
27
- return /*#__PURE__*/_jsxs("details", {
28
- className: classNames('newspack-accordion', {
29
- 'newspack-accordion--is-open': isOpen
30
- }),
31
- open: isOpen,
32
- onToggle: function onToggle(e) {
33
- return setIsOpen(e.currentTarget.open);
34
- },
35
- children: [/*#__PURE__*/_jsxs("summary", {
36
- children: [title, /*#__PURE__*/_jsx(Icon, {
37
- className: "newspack-accordion__icon",
38
- icon: chevronRight,
39
- size: 24
40
- })]
41
- }), /*#__PURE__*/_jsx("div", {
42
- className: "newspack-accordion__content",
43
- children: children
44
- })]
24
+ return /*#__PURE__*/_jsx(PanelBody, {
25
+ className: className,
26
+ title: title,
27
+ initialOpen: defaultOpen,
28
+ children: children
29
+ });
30
+ };
31
+ var Accordion = function Accordion(_ref2) {
32
+ var children = _ref2.children,
33
+ className = _ref2.className,
34
+ _ref2$hideSingleTitle = _ref2.hideSingleTitle,
35
+ hideSingleTitle = _ref2$hideSingleTitle === void 0 ? false : _ref2$hideSingleTitle,
36
+ _ref2$spacing = _ref2.spacing,
37
+ spacing = _ref2$spacing === void 0 ? 6 : _ref2$spacing;
38
+ var panels = Children.toArray(children);
39
+ // With nothing to collapse against, a lone panel can render open and untitled.
40
+ if (hideSingleTitle && panels.length === 1) {
41
+ return /*#__PURE__*/_jsx("div", {
42
+ className: classNames('newspack-accordion', className),
43
+ children: cloneElement(panels[0], {
44
+ defaultOpen: true,
45
+ title: undefined
46
+ })
47
+ });
48
+ }
49
+ return /*#__PURE__*/_jsx(VStack, {
50
+ className: classNames('newspack-accordion', className),
51
+ spacing: spacing,
52
+ children: panels.map(function (panel, index) {
53
+ return /*#__PURE__*/_jsxs(Fragment, {
54
+ children: [panel, index < panels.length - 1 && /*#__PURE__*/_jsx(Divider, {
55
+ variant: "secondary",
56
+ marginBottom: 0,
57
+ marginTop: 0
58
+ })]
59
+ }, panel.key);
60
+ })
45
61
  });
46
62
  };
47
63
  export default Accordion;
@@ -0,0 +1,79 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ 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; }
3
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
+ /**
5
+ * External dependencies
6
+ */
7
+ import { render, screen } from '@testing-library/react';
8
+
9
+ /**
10
+ * Internal dependencies
11
+ */
12
+ import Accordion, { AccordionPanel } from './index';
13
+ import { jsx as _jsx } from "react/jsx-runtime";
14
+ var renderPanels = function renderPanels(count) {
15
+ var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
16
+ return render(/*#__PURE__*/_jsx(Accordion, _objectSpread(_objectSpread({}, props), {}, {
17
+ children: Array.from({
18
+ length: count
19
+ }, function (_, i) {
20
+ return /*#__PURE__*/_jsx(AccordionPanel, {
21
+ title: "Panel ".concat(i),
22
+ children: "content"
23
+ }, i);
24
+ })
25
+ })));
26
+ };
27
+ var dividers = function dividers(container) {
28
+ return container.querySelectorAll('.newspack-divider');
29
+ };
30
+ describe('Accordion dividers', function () {
31
+ it('renders no divider for a single panel', function () {
32
+ var _renderPanels = renderPanels(1),
33
+ container = _renderPanels.container;
34
+ expect(container.querySelectorAll('.components-panel__body')).toHaveLength(1);
35
+ expect(dividers(container)).toHaveLength(0);
36
+ });
37
+ it('renders a divider between panels but not after the last', function () {
38
+ var _renderPanels2 = renderPanels(3),
39
+ container = _renderPanels2.container;
40
+ expect(container.querySelectorAll('.components-panel__body')).toHaveLength(3);
41
+ expect(dividers(container)).toHaveLength(2);
42
+ expect(container.querySelector('.newspack-accordion').lastElementChild).not.toHaveClass('newspack-divider');
43
+ });
44
+ it('renders secondary dividers', function () {
45
+ var _renderPanels3 = renderPanels(2),
46
+ container = _renderPanels3.container;
47
+ expect(dividers(container)[0]).toHaveClass('newspack-divider--variant-secondary');
48
+ });
49
+ });
50
+ describe('Accordion hideSingleTitle', function () {
51
+ it('keeps the title on a lone panel by default', function () {
52
+ renderPanels(1);
53
+ expect(screen.getByRole('button', {
54
+ name: 'Panel 0'
55
+ })).toBeInTheDocument();
56
+ });
57
+ it('drops the title and opens a lone panel when set', function () {
58
+ var _renderPanels4 = renderPanels(1, {
59
+ hideSingleTitle: true
60
+ }),
61
+ container = _renderPanels4.container;
62
+ expect(screen.queryByRole('button', {
63
+ name: 'Panel 0'
64
+ })).not.toBeInTheDocument();
65
+ expect(container.querySelector('.components-panel__body')).toHaveClass('is-opened');
66
+ expect(screen.getByText('content')).toBeInTheDocument();
67
+ });
68
+ it('leaves titles alone when there is more than one panel', function () {
69
+ renderPanels(2, {
70
+ hideSingleTitle: true
71
+ });
72
+ expect(screen.getByRole('button', {
73
+ name: 'Panel 0'
74
+ })).toBeInTheDocument();
75
+ expect(screen.getByRole('button', {
76
+ name: 'Panel 1'
77
+ })).toBeInTheDocument();
78
+ });
79
+ });
@@ -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,75 @@
1
+ /**
2
+ * WordPress dependencies.
3
+ */
4
+ import { __ } from '@wordpress/i18n';
5
+ import { Link, Stack, Text } from '@wordpress/ui';
6
+
7
+ /**
8
+ * Internal dependencies.
9
+ */
10
+ import './style.scss';
11
+
12
+ /**
13
+ * Newspack breadcrumb trail.
14
+ *
15
+ * Data-driven: the last item is the current page (the single h1, never a link);
16
+ * every other item renders as a link when it has a `url`, otherwise plain text.
17
+ * A plain `<a href>` drives both full admin URLs and hash routes, so the
18
+ * component needs no router context.
19
+ *
20
+ * @param {Object} props
21
+ * @param {Array} props.items Trail items: `{ label, url? }`.
22
+ * @return {JSX.Element|null} Breadcrumbs component.
23
+ */
24
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
25
+ var Breadcrumbs = function Breadcrumbs(_ref) {
26
+ var _ref$items = _ref.items,
27
+ items = _ref$items === void 0 ? [] : _ref$items;
28
+ if (!items.length) {
29
+ return null;
30
+ }
31
+ var preceding = items.slice(0, -1);
32
+ var last = items[items.length - 1];
33
+ return /*#__PURE__*/_jsx("nav", {
34
+ "aria-label": __('Breadcrumbs', 'newspack-plugin'),
35
+ children: /*#__PURE__*/_jsxs(Stack, {
36
+ render: /*#__PURE__*/_jsx("ul", {}),
37
+ direction: "row",
38
+ align: "center",
39
+ className: "newspack-breadcrumbs__list",
40
+ children: [preceding.map(function (item, index) {
41
+ return /*#__PURE__*/_jsxs("li", {
42
+ children: [item.url ?
43
+ /*#__PURE__*/
44
+ // eslint-disable-next-line jsx-a11y/anchor-has-content -- content is supplied via the Text children through @wordpress/ui's render prop.
45
+ _jsx(Text, {
46
+ variant: "body-lg",
47
+ render: /*#__PURE__*/_jsx(Link, {
48
+ tone: "neutral",
49
+ render: /*#__PURE__*/_jsx("a", {
50
+ href: item.url
51
+ })
52
+ }),
53
+ children: item.label
54
+ }) : /*#__PURE__*/_jsx(Text, {
55
+ variant: "body-lg",
56
+ children: item.label
57
+ }), /*#__PURE__*/_jsx(Text, {
58
+ variant: "body-lg",
59
+ "aria-hidden": "true",
60
+ className: "newspack-breadcrumbs__separator",
61
+ children: "/"
62
+ })]
63
+ }, item.url || index);
64
+ }), last && last.label && /*#__PURE__*/_jsx("li", {
65
+ children: /*#__PURE__*/_jsx(Text, {
66
+ variant: "heading-lg",
67
+ render: /*#__PURE__*/_jsx("h1", {}),
68
+ className: "newspack-breadcrumbs__current",
69
+ children: last.label
70
+ })
71
+ })]
72
+ })
73
+ });
74
+ };
75
+ export default Breadcrumbs;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * External dependencies.
3
+ */
4
+ import { render, screen } from '@testing-library/react';
5
+
6
+ /**
7
+ * Internal dependencies.
8
+ */
9
+ import Breadcrumbs from './';
10
+ import { jsx as _jsx } from "react/jsx-runtime";
11
+ describe('Breadcrumbs', function () {
12
+ it('renders the last item as the only h1 and never a link', function () {
13
+ render(/*#__PURE__*/_jsx(Breadcrumbs, {
14
+ items: [{
15
+ label: 'Newsletters'
16
+ }, {
17
+ label: 'Settings',
18
+ url: '#/x'
19
+ }]
20
+ }));
21
+ var h1s = screen.getAllByRole('heading', {
22
+ level: 1
23
+ });
24
+ expect(h1s).toHaveLength(1);
25
+ expect(h1s[0]).toHaveTextContent('Settings');
26
+ expect(screen.queryByRole('link', {
27
+ name: 'Settings'
28
+ })).not.toBeInTheDocument();
29
+ });
30
+ it('renders a non-last item with a url as a link, without a url as text', function () {
31
+ render(/*#__PURE__*/_jsx(Breadcrumbs, {
32
+ items: [{
33
+ label: 'Advertising'
34
+ }, {
35
+ label: 'Sponsors',
36
+ url: '/wp-admin/x'
37
+ }, {
38
+ label: 'All sponsors'
39
+ }]
40
+ }));
41
+ var links = screen.getAllByRole('link');
42
+ expect(links).toHaveLength(1);
43
+ expect(links[0]).toHaveTextContent('Sponsors');
44
+ expect(links[0].getAttribute('href')).toBe('/wp-admin/x');
45
+ expect(screen.getByText('Advertising')).toBeInTheDocument();
46
+ });
47
+ it('does not special-case the first item: it links when it has a url', function () {
48
+ render(/*#__PURE__*/_jsx(Breadcrumbs, {
49
+ items: [{
50
+ label: 'Audience',
51
+ url: '#/'
52
+ }, {
53
+ label: 'Donations'
54
+ }]
55
+ }));
56
+ var link = screen.getByRole('link', {
57
+ name: 'Audience'
58
+ });
59
+ expect(link.getAttribute('href')).toBe('#/');
60
+ });
61
+ it('renders a single-item trail as just the h1 with no separator', function () {
62
+ var _render = render(/*#__PURE__*/_jsx(Breadcrumbs, {
63
+ items: [{
64
+ label: 'Dashboard'
65
+ }]
66
+ })),
67
+ container = _render.container;
68
+ expect(screen.getByRole('heading', {
69
+ level: 1
70
+ })).toHaveTextContent('Dashboard');
71
+ expect(container.querySelector('.newspack-breadcrumbs__separator')).toBeNull();
72
+ expect(screen.queryByRole('link')).not.toBeInTheDocument();
73
+ });
74
+ it('renders a "/" separator after each preceding item', function () {
75
+ var _render2 = render(/*#__PURE__*/_jsx(Breadcrumbs, {
76
+ items: [{
77
+ label: 'Audience'
78
+ }, {
79
+ label: 'Access control'
80
+ }]
81
+ })),
82
+ container = _render2.container;
83
+ var separators = container.querySelectorAll('.newspack-breadcrumbs__separator');
84
+ expect(separators).toHaveLength(1);
85
+ expect(separators[0]).toHaveTextContent('/');
86
+ expect(separators[0]).toHaveAttribute('aria-hidden', 'true');
87
+ });
88
+ it('renders no heading when the last item has no label', function () {
89
+ render(/*#__PURE__*/_jsx(Breadcrumbs, {
90
+ items: [{
91
+ label: undefined
92
+ }]
93
+ }));
94
+ expect(screen.queryByRole('heading', {
95
+ level: 1
96
+ })).not.toBeInTheDocument();
97
+ });
98
+ it('renders nothing when there are no items', function () {
99
+ var _render3 = render(/*#__PURE__*/_jsx(Breadcrumbs, {
100
+ items: []
101
+ })),
102
+ container = _render3.container;
103
+ expect(container.querySelector('nav')).not.toBeInTheDocument();
104
+ });
105
+ });
@@ -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
+ }
@@ -1,6 +1,6 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
3
- 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"];
3
+ 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"];
4
4
  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; }
5
5
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
6
6
  /**
@@ -39,6 +39,7 @@ var CoreCard = function CoreCard(_ref) {
39
39
  footerStyle = _ref.footerStyle,
40
40
  disabled = _ref.disabled,
41
41
  icon = _ref.icon,
42
+ iconElement = _ref.iconElement,
42
43
  iconBackgroundColor = _ref.iconBackgroundColor,
43
44
  isActive = _ref.isActive,
44
45
  isDraggable = _ref.isDraggable,
@@ -60,7 +61,7 @@ var CoreCard = function CoreCard(_ref) {
60
61
  _ref$hasHeaderBorder = _ref.hasHeaderBorder,
61
62
  hasHeaderBorder = _ref$hasHeaderBorder === void 0 ? true : _ref$hasHeaderBorder,
62
63
  otherProps = _objectWithoutProperties(_ref, _excluded);
63
- var classes = classNames('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');
64
+ var classes = classNames('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');
64
65
  var sizeProps = isSmall ? 'small' : otherProps.size;
65
66
  if (buttonsCard || as === 'a') {
66
67
  if (!isSmall) {
@@ -82,7 +83,7 @@ var CoreCard = function CoreCard(_ref) {
82
83
  as: as,
83
84
  className: classes
84
85
  }, otherProps), {}, {
85
- children: [(header || icon) && /*#__PURE__*/_jsxs(CardHeader, {
86
+ children: [(header || icon || iconElement) && /*#__PURE__*/_jsxs(CardHeader, {
86
87
  as: headerIsButton ? 'button' : undefined,
87
88
  className: classNames('newspack-card--core__header', isDraggable && 'newspack-card--core__header--is-draggable', !hasHeaderBorder && 'newspack-card--core__header--no-border'),
88
89
  style: headerStyle,
@@ -118,7 +119,10 @@ var CoreCard = function CoreCard(_ref) {
118
119
  size: "small"
119
120
  })]
120
121
  })]
121
- }), icon && /*#__PURE__*/_jsx("div", {
122
+ }), iconElement ? /*#__PURE__*/_jsx("div", {
123
+ className: "newspack-card--core__icon-slot",
124
+ children: iconElement
125
+ }) : icon && /*#__PURE__*/_jsx("div", {
122
126
  className: "newspack-card--core__icon",
123
127
  children: /*#__PURE__*/_jsx(Icon, {
124
128
  icon: icon,
@@ -81,7 +81,7 @@ var Card = /*#__PURE__*/function (_Component) {
81
81
  }, otherProps), __experimentalCoreProps);
82
82
  return /*#__PURE__*/_jsx(CoreCard, _objectSpread({}, props));
83
83
  }
84
- var classes = classNames('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 = classNames('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__*/_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 {
@@ -7,6 +7,7 @@ import classnames from 'classnames';
7
7
  * WordPress dependencies
8
8
  */
9
9
  import { __ } from '@wordpress/i18n';
10
+ import { isValidElement } from '@wordpress/element';
10
11
  import { DropdownMenu, __experimentalHStack as HStack } from '@wordpress/components'; // eslint-disable-line @wordpress/no-unsafe-wp-apis
11
12
  import { moreVertical } from '@wordpress/icons';
12
13
 
@@ -35,6 +36,8 @@ var CardFeature = function CardFeature(_ref) {
35
36
  _ref$requirementsActi = _ref.requirementsActionable,
36
37
  requirementsActionable = _ref$requirementsActi === void 0 ? false : _ref$requirementsActi,
37
38
  enableLabel = _ref.enableLabel,
39
+ _ref$busy = _ref.busy,
40
+ busy = _ref$busy === void 0 ? false : _ref$busy,
38
41
  configureLabel = _ref.configureLabel,
39
42
  onEnable = _ref.onEnable,
40
43
  onConfigure = _ref.onConfigure,
@@ -61,6 +64,7 @@ var CardFeature = function CardFeature(_ref) {
61
64
  }
62
65
  var isConfigureState = enabled && !requirements;
63
66
  var buttonLabel = isConfigureState ? configureLabel !== null && configureLabel !== void 0 ? configureLabel : __('Configure', 'newspack-plugin') : enableLabel !== null && enableLabel !== void 0 ? enableLabel : __('Enable', 'newspack-plugin');
67
+ var showMoreControls = enabled && !!(moreControls !== null && moreControls !== void 0 && moreControls.length) && (!requirements || requirementsActionable);
64
68
  var handleButtonClick = function handleButtonClick() {
65
69
  if (isConfigureState) {
66
70
  onConfigure === null || onConfigure === void 0 || onConfigure();
@@ -68,10 +72,24 @@ var CardFeature = function CardFeature(_ref) {
68
72
  onEnable === null || onEnable === void 0 || onEnable();
69
73
  }
70
74
  };
71
- var iconClasses = icon ? classnames('newspack-card-feature__icon', {
72
- 'newspack-card-feature__icon--radius-small': !!icon.backgroundColor && icon.radius !== 'full',
73
- 'newspack-card-feature__icon--radius-full': icon.radius === 'full'
75
+ var iconDescriptor = icon && !isValidElement(icon) ? icon : null;
76
+ var iconClasses = iconDescriptor ? classnames('newspack-card-feature__icon', {
77
+ 'newspack-card-feature__icon--radius-small': !!iconDescriptor.backgroundColor && iconDescriptor.radius !== 'full',
78
+ 'newspack-card-feature__icon--radius-full': iconDescriptor.radius === 'full'
74
79
  }) : undefined;
80
+ var renderedIcon = null;
81
+ if (isValidElement(icon)) {
82
+ renderedIcon = icon;
83
+ } else if (iconDescriptor) {
84
+ renderedIcon = /*#__PURE__*/_jsx("div", {
85
+ className: iconClasses,
86
+ style: {
87
+ backgroundColor: iconDescriptor.backgroundColor,
88
+ color: iconDescriptor.fill
89
+ },
90
+ children: iconDescriptor.node
91
+ });
92
+ }
75
93
  return /*#__PURE__*/_jsx(Card, {
76
94
  className: classes,
77
95
  __experimentalCoreCard: true,
@@ -92,14 +110,7 @@ var CardFeature = function CardFeature(_ref) {
92
110
  className: "newspack-card-feature__description",
93
111
  children: description
94
112
  })]
95
- }), icon && /*#__PURE__*/_jsx("div", {
96
- className: iconClasses,
97
- style: {
98
- backgroundColor: icon.backgroundColor,
99
- color: icon.fill
100
- },
101
- children: icon.node
102
- })]
113
+ }), renderedIcon]
103
114
  }), /*#__PURE__*/_jsxs(HStack, {
104
115
  alignment: "edge",
105
116
  children: [/*#__PURE__*/_jsxs(HStack, {
@@ -107,11 +118,12 @@ var CardFeature = function CardFeature(_ref) {
107
118
  spacing: "8px",
108
119
  children: [/*#__PURE__*/_jsx(Button, {
109
120
  variant: isConfigureState ? 'tertiary' : 'secondary',
110
- disabled: isMuted && !requirementsActionable,
121
+ disabled: isMuted && !requirementsActionable || busy,
122
+ isBusy: busy,
111
123
  onClick: handleButtonClick,
112
124
  size: "compact",
113
125
  children: buttonLabel
114
- }), isConfigureState && !!(moreControls !== null && moreControls !== void 0 && moreControls.length) && /*#__PURE__*/_jsx(DropdownMenu, {
126
+ }), showMoreControls && /*#__PURE__*/_jsx(DropdownMenu, {
115
127
  icon: moreVertical,
116
128
  label: __('More', 'newspack-plugin'),
117
129
  controls: moreControls,