newspack-components 4.2.0 → 4.3.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 (131) hide show
  1. package/dist/cjs/action-card/index.js +21 -12
  2. package/dist/cjs/action-card/style.scss +25 -2
  3. package/dist/cjs/autocomplete-tokenfield/index.js +2 -2
  4. package/dist/cjs/autocomplete-with-latest-posts/index.js +289 -0
  5. package/dist/cjs/autocomplete-with-latest-posts/style.scss +150 -0
  6. package/dist/cjs/badge/index.js +32 -0
  7. package/dist/cjs/badge/style.scss +36 -0
  8. package/dist/cjs/button/index.js +2 -0
  9. package/dist/cjs/card/core-card.js +141 -0
  10. package/dist/cjs/card/index.js +33 -0
  11. package/dist/cjs/card/style-core.scss +227 -0
  12. package/dist/cjs/card/style.scss +2 -2
  13. package/dist/cjs/card-settings-group/index.js +49 -0
  14. package/dist/cjs/card-settings-group/style.scss +33 -0
  15. package/dist/cjs/card-sortable-list/index.js +390 -0
  16. package/dist/cjs/card-sortable-list/style.scss +48 -0
  17. package/dist/cjs/confirm-dialog/index.js +59 -0
  18. package/dist/cjs/divider/index.js +42 -0
  19. package/dist/cjs/divider/style.scss +51 -0
  20. package/dist/cjs/form-token-field/style.scss +0 -5
  21. package/dist/cjs/grid/index.js +2 -1
  22. package/dist/cjs/grid/style.scss +63 -9
  23. package/dist/cjs/index.js +42 -0
  24. package/dist/cjs/modal/index.js +11 -4
  25. package/dist/cjs/modal/style.scss +43 -13
  26. package/dist/cjs/newspack-icon/style.scss +5 -3
  27. package/dist/cjs/section-header/index.js +76 -17
  28. package/dist/cjs/section-header/style.scss +109 -4
  29. package/dist/cjs/style.scss +2 -0
  30. package/dist/cjs/waiting/index.js +3 -1
  31. package/dist/cjs/waiting/style.scss +4 -0
  32. package/dist/cjs/web-preview/index.js +98 -39
  33. package/dist/cjs/web-preview/style.scss +20 -53
  34. package/dist/cjs/with-wizard/index.js +1 -1
  35. package/dist/cjs/with-wizard/style.scss +24 -6
  36. package/dist/cjs/with-wizard-screen/style.scss +39 -10
  37. package/dist/cjs/wizard/components/WizardSnackbar.js +70 -0
  38. package/dist/cjs/wizard/components/style.scss +48 -0
  39. package/dist/cjs/wizard/index.js +97 -16
  40. package/dist/cjs/wizard/store/index.js +46 -0
  41. package/dist/esm/action-card/index.js +21 -12
  42. package/dist/esm/action-card/style.scss +25 -2
  43. package/dist/esm/autocomplete-tokenfield/index.js +2 -2
  44. package/dist/esm/autocomplete-with-latest-posts/index.js +281 -0
  45. package/dist/esm/autocomplete-with-latest-posts/style.scss +150 -0
  46. package/dist/esm/badge/index.js +24 -0
  47. package/dist/esm/badge/style.scss +36 -0
  48. package/dist/esm/button/index.js +2 -0
  49. package/dist/esm/card/core-card.js +133 -0
  50. package/dist/esm/card/index.js +33 -0
  51. package/dist/esm/card/style-core.scss +227 -0
  52. package/dist/esm/card/style.scss +2 -2
  53. package/dist/esm/card-settings-group/index.js +42 -0
  54. package/dist/esm/card-settings-group/style.scss +33 -0
  55. package/dist/esm/card-sortable-list/index.js +381 -0
  56. package/dist/esm/card-sortable-list/style.scss +48 -0
  57. package/dist/esm/confirm-dialog/index.js +49 -0
  58. package/dist/esm/divider/index.js +34 -0
  59. package/dist/esm/divider/style.scss +51 -0
  60. package/dist/esm/form-token-field/style.scss +0 -5
  61. package/dist/esm/grid/index.js +2 -1
  62. package/dist/esm/grid/style.scss +63 -9
  63. package/dist/esm/index.js +6 -0
  64. package/dist/esm/modal/index.js +11 -4
  65. package/dist/esm/modal/style.scss +43 -13
  66. package/dist/esm/newspack-icon/style.scss +5 -3
  67. package/dist/esm/section-header/index.js +75 -18
  68. package/dist/esm/section-header/style.scss +109 -4
  69. package/dist/esm/style.scss +2 -0
  70. package/dist/esm/waiting/index.js +3 -1
  71. package/dist/esm/waiting/style.scss +4 -0
  72. package/dist/esm/web-preview/index.js +101 -42
  73. package/dist/esm/web-preview/style.scss +20 -53
  74. package/dist/esm/with-wizard/index.js +1 -1
  75. package/dist/esm/with-wizard/style.scss +24 -6
  76. package/dist/esm/with-wizard-screen/style.scss +39 -10
  77. package/dist/esm/wizard/components/WizardSnackbar.js +64 -0
  78. package/dist/esm/wizard/components/style.scss +48 -0
  79. package/dist/esm/wizard/index.js +101 -20
  80. package/dist/esm/wizard/store/index.js +46 -0
  81. package/package.json +5 -6
  82. package/shared/author-context.js +26 -0
  83. package/shared/hooks/use-coauthors.js +220 -0
  84. package/shared/hooks/use-coauthors.test.js +624 -0
  85. package/shared/hooks/use-custom-byline.js +47 -0
  86. package/shared/hooks/use-custom-byline.test.js +133 -0
  87. package/shared/scss/_mixins.scss +46 -0
  88. package/src/action-card/action-card.d.ts +7 -3
  89. package/src/action-card/index.js +24 -11
  90. package/src/action-card/style.scss +25 -2
  91. package/src/autocomplete-tokenfield/index.js +2 -2
  92. package/src/autocomplete-with-latest-posts/index.js +290 -0
  93. package/src/autocomplete-with-latest-posts/style.scss +150 -0
  94. package/src/badge/index.tsx +24 -0
  95. package/src/badge/style.scss +36 -0
  96. package/src/button/index.tsx +3 -2
  97. package/src/card/core-card.js +147 -0
  98. package/src/card/index.js +39 -1
  99. package/src/card/style-core.scss +227 -0
  100. package/src/card/style.scss +2 -2
  101. package/src/card-settings-group/README.md +43 -0
  102. package/src/card-settings-group/index.tsx +53 -0
  103. package/src/card-settings-group/style.scss +33 -0
  104. package/src/card-sortable-list/README.md +51 -0
  105. package/src/card-sortable-list/index.tsx +412 -0
  106. package/src/card-sortable-list/style.scss +48 -0
  107. package/src/confirm-dialog/index.tsx +64 -0
  108. package/src/divider/README.md +25 -0
  109. package/src/divider/index.js +31 -0
  110. package/src/divider/style.scss +51 -0
  111. package/src/form-token-field/style.scss +0 -5
  112. package/src/grid/index.tsx +2 -1
  113. package/src/grid/style.scss +63 -9
  114. package/src/index.js +6 -0
  115. package/src/modal/index.js +12 -3
  116. package/src/modal/style.scss +43 -13
  117. package/src/newspack-icon/style.scss +5 -3
  118. package/src/section-header/index.js +72 -13
  119. package/src/section-header/style.scss +109 -4
  120. package/src/style.scss +2 -0
  121. package/src/waiting/index.js +2 -1
  122. package/src/waiting/style.scss +4 -0
  123. package/src/web-preview/index.js +90 -35
  124. package/src/web-preview/style.scss +20 -53
  125. package/src/with-wizard/index.js +1 -1
  126. package/src/with-wizard/style.scss +24 -6
  127. package/src/with-wizard-screen/style.scss +39 -10
  128. package/src/wizard/components/WizardSnackbar.js +58 -0
  129. package/src/wizard/components/style.scss +48 -0
  130. package/src/wizard/index.js +108 -14
  131. package/src/wizard/store/index.js +26 -0
@@ -0,0 +1,281 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import debounce from 'lodash/debounce';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import apiFetch from '@wordpress/api-fetch';
10
+ import { Button, CheckboxControl, FormTokenField, Spinner } from '@wordpress/components';
11
+ import { decodeEntities } from '@wordpress/html-entities';
12
+ import { useEffect, useState } from '@wordpress/element';
13
+ import { __ } from '@wordpress/i18n';
14
+ import { addQueryArgs } from '@wordpress/url';
15
+ import './style.scss';
16
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
17
+ const AutocompleteWithLatestPosts = ({
18
+ fetchSuggestions = false,
19
+ // If passed, will use this function to fetch suggestions data.
20
+ help = __('Begin typing search term, click autocomplete result to select.', 'newspack-plugin'),
21
+ hideHelp = true,
22
+ // If true, all help text will be hidden.
23
+ hideFormTokenHelp = true,
24
+ // If true, hides FormTokenField's built-in help text.
25
+ label = __('Search', 'newspack-plugin'),
26
+ maxItemsToSuggest = 0,
27
+ // If passed, will be used to determine "load more" state. Necessary if you want "load more" functionality when using a custom `fetchSuggestions` function.
28
+ multiSelect = false,
29
+ // If true, component can select multiple values at once.
30
+ onChange = false,
31
+ // Function to call when selections change.
32
+ selectedItems = [],
33
+ // Array of saved items.
34
+ suggestionsToFetch = 20 // Number of suggestions to fetch per query.
35
+ }) => {
36
+ const [isLoading, setIsLoading] = useState(true);
37
+ const [isLoadingMore, setIsLoadingMore] = useState(false);
38
+ const [suggestions, setSuggestions] = useState([]);
39
+ const [maxSuggestions, setMaxSuggestions] = useState(0);
40
+ const [searchSuggestions, setSearchSuggestions] = useState([]);
41
+ const [validValues, setValidValues] = useState({});
42
+ const [isSearching, setIsSearching] = useState(false);
43
+ // Leaving this in here so we can expand to use different post types in the future; right now it defaults to 'post'.
44
+ const postTypeToSearch = 'post';
45
+ const classNames = ['newspack-autocomplete-with-latest-posts'];
46
+ if (hideFormTokenHelp) {
47
+ classNames.push('hide-form-token-help');
48
+ }
49
+ if (hideHelp) {
50
+ classNames.push('hide-help');
51
+ }
52
+
53
+ /**
54
+ * Debounced function to update search suggestions.
55
+ */
56
+ const debouncedUpdateSuggestions = debounce(input => {
57
+ if (!input || input.length < 2) {
58
+ setSearchSuggestions([]);
59
+ setIsSearching(false);
60
+ return;
61
+ }
62
+ setIsSearching(true);
63
+ handleFetchSuggestions(input, 0, postTypeToSearch).then(_suggestions => {
64
+ setSearchSuggestions(_suggestions);
65
+ // Update valid values for token conversion
66
+ const newValidValues = {
67
+ ...validValues
68
+ };
69
+ _suggestions.forEach(suggestion => {
70
+ newValidValues[suggestion.value] = suggestion.label;
71
+ });
72
+ setValidValues(newValidValues);
73
+ }).finally(() => setIsSearching(false));
74
+ }, 500);
75
+
76
+ /**
77
+ * Fetch recent posts to show as suggestions.
78
+ */
79
+ useEffect(() => {
80
+ setIsLoading(true);
81
+ handleFetchSuggestions(null, 0, postTypeToSearch).then(_suggestions => {
82
+ if (0 < _suggestions.length) {
83
+ setSuggestions(_suggestions);
84
+ // Update valid values for token conversion
85
+ const newValidValues = {
86
+ ...validValues
87
+ };
88
+ _suggestions.forEach(suggestion => {
89
+ newValidValues[suggestion.value] = suggestion.label;
90
+ });
91
+ setValidValues(newValidValues);
92
+ }
93
+ }).finally(() => setIsLoading(false));
94
+ }, []);
95
+
96
+ /**
97
+ * Fetch more suggestions.
98
+ */
99
+ useEffect(() => {
100
+ if (isLoadingMore) {
101
+ handleFetchSuggestions(null, suggestions.length, postTypeToSearch).then(_suggestions => {
102
+ if (0 < _suggestions.length) {
103
+ setSuggestions(suggestions.concat(_suggestions));
104
+ }
105
+ }).finally(() => setIsLoadingMore(false));
106
+ }
107
+ }, [isLoadingMore]);
108
+
109
+ /**
110
+ * If passed a `fetchSuggestions` prop, use that, otherwise, build it based on the selected post type.
111
+ */
112
+ const handleFetchSuggestions = fetchSuggestions ? fetchSuggestions : async (search = null, offset = 0, searchSlug = null) => {
113
+ const postTypeSlug = searchSlug || postTypeToSearch;
114
+ const endpoint = 'post' === postTypeSlug || 'page' === postTypeSlug ? postTypeSlug + 's' // Default post type endpoints are plural.
115
+ : postTypeSlug; // Custom post type endpoints are singular.
116
+ const response = await apiFetch({
117
+ parse: false,
118
+ path: addQueryArgs('/wp/v2/' + endpoint, {
119
+ search,
120
+ offset,
121
+ per_page: suggestionsToFetch,
122
+ _fields: 'id,title'
123
+ })
124
+ });
125
+ const total = parseInt(response.headers.get('x-wp-total') || 0);
126
+ const posts = await response.json();
127
+ setMaxSuggestions(total);
128
+
129
+ // Format suggestions for FormTokenField display.
130
+ return posts.reduce((acc, post) => {
131
+ acc.push({
132
+ value: parseInt(post.id),
133
+ label: decodeEntities(post?.title.rendered) || __('(no title)', 'newspack-plugin')
134
+ });
135
+ return acc;
136
+ }, []);
137
+ };
138
+
139
+ /**
140
+ * Get labels for token values.
141
+ */
142
+ const getLabelsForValues = values => {
143
+ return values.reduce((accumulator, value) => {
144
+ if (!value) {
145
+ return accumulator;
146
+ }
147
+ if (value.label) {
148
+ return [...accumulator, value.label];
149
+ }
150
+ return validValues[value] ? [...accumulator, validValues[value]] : accumulator;
151
+ }, []);
152
+ };
153
+
154
+ /**
155
+ * Get values for token labels.
156
+ */
157
+ const getValuesForLabels = labels => {
158
+ // eslint-disable-next-line @typescript-eslint/no-shadow
159
+ return labels.reduce((acc, label) => {
160
+ Object.keys(validValues).forEach(key => {
161
+ if (validValues[key] === label) {
162
+ const value = isNaN(parseInt(key)) ? key.toString() : parseInt(key);
163
+ acc.push({
164
+ value,
165
+ label
166
+ });
167
+ }
168
+ });
169
+ return acc;
170
+ }, []);
171
+ };
172
+
173
+ /**
174
+ * Handle FormTokenField onChange.
175
+ */
176
+ const handleTokenChange = tokenStrings => {
177
+ const newSelections = getValuesForLabels(tokenStrings);
178
+
179
+ // If only allowing one selection, just return the one selected item.
180
+ if (!multiSelect) {
181
+ const newSelection = newSelections[newSelections.length - 1]; // Get the last selected item
182
+ if (newSelection) {
183
+ return onChange([{
184
+ ...newSelection,
185
+ postType: postTypeToSearch
186
+ }]);
187
+ }
188
+ return onChange([]);
189
+ }
190
+
191
+ // For multi-select, include currently selected post type in selection results.
192
+ onChange(newSelections.map(selection => ({
193
+ ...selection,
194
+ postType: postTypeToSearch
195
+ })));
196
+ };
197
+
198
+ /**
199
+ * Get tokens for FormTokenField.
200
+ */
201
+ const getTokens = () => {
202
+ return getLabelsForValues(selectedItems);
203
+ };
204
+
205
+ /**
206
+ * Render a single suggestion object that can be clicked to select it immediately.
207
+ *
208
+ * @param {Object} suggestion Suggestion object with value and label keys.
209
+ */
210
+ const renderSuggestion = suggestion => {
211
+ if (multiSelect) {
212
+ const isSelected = !!selectedItems.find(_selection => parseInt(_selection.value) === parseInt(suggestion.value) && _selection.label === suggestion.label);
213
+ return /*#__PURE__*/_jsx(CheckboxControl, {
214
+ checked: isSelected,
215
+ onChange: () => {
216
+ // For multi-select, we need to toggle the selection
217
+ const currentTokens = getTokens();
218
+ const suggestionLabel = suggestion.label;
219
+ if (isSelected) {
220
+ // Remove the suggestion
221
+ const newTokens = currentTokens.filter(token => token !== suggestionLabel);
222
+ handleTokenChange(newTokens);
223
+ } else {
224
+ // Add the suggestion
225
+ handleTokenChange([...currentTokens, suggestionLabel]);
226
+ }
227
+ },
228
+ label: suggestion.label
229
+ }, suggestion.value);
230
+ }
231
+ return /*#__PURE__*/_jsx(Button, {
232
+ isLink: true,
233
+ onClick: () => handleTokenChange([...getTokens(), suggestion.label]),
234
+ children: suggestion.label
235
+ }, suggestion.value);
236
+ };
237
+
238
+ /**
239
+ * Render a list of suggestions that can be clicked to select instead of searching by title.
240
+ */
241
+ const renderSuggestions = () => {
242
+ if (0 === suggestions.length) {
243
+ return null;
244
+ }
245
+ const className = multiSelect ? 'newspack-autocomplete-with-latest-posts__search-suggestions-multiselect' : 'newspack-autocomplete-with-latest-posts__search-suggestions';
246
+ return /*#__PURE__*/_jsx(_Fragment, {
247
+ children: /*#__PURE__*/_jsxs("div", {
248
+ className: "newspack-autocomplete-with-latest-posts__search-suggestions-container",
249
+ children: [/*#__PURE__*/_jsx("p", {
250
+ className: "newspack-autocomplete-with-suggestions__label",
251
+ children: __('Latest Posts', 'newspack-plugin')
252
+ }), /*#__PURE__*/_jsxs("div", {
253
+ className: className,
254
+ children: [suggestions.map(renderSuggestion), suggestions.length < (maxItemsToSuggest || maxSuggestions) && /*#__PURE__*/_jsx(Button, {
255
+ disabled: isLoadingMore,
256
+ isSecondary: true,
257
+ onClick: () => setIsLoadingMore(true),
258
+ children: isLoadingMore ? __('Loading…', 'newspack-plugin') : __('Load more', 'newspack-plugin')
259
+ })]
260
+ })]
261
+ })
262
+ });
263
+ };
264
+ return /*#__PURE__*/_jsxs("div", {
265
+ className: classNames.join(' '),
266
+ children: [/*#__PURE__*/_jsxs("div", {
267
+ className: "newspack-autocomplete-with-latest-posts__input-container",
268
+ children: [/*#__PURE__*/_jsx(FormTokenField, {
269
+ value: getTokens(),
270
+ suggestions: searchSuggestions.map(suggestion => suggestion.label),
271
+ onChange: handleTokenChange,
272
+ onInputChange: debouncedUpdateSuggestions,
273
+ label: label,
274
+ help: !hideHelp && help,
275
+ __next40pxDefaultSize: true,
276
+ __nextHasNoMarginBottom: true
277
+ }), (isLoading || isSearching) && /*#__PURE__*/_jsx(Spinner, {})]
278
+ }), renderSuggestions()]
279
+ });
280
+ };
281
+ export default AutocompleteWithLatestPosts;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Styles for AutocompleteWithLatestPosts component.
3
+ */
4
+
5
+ @use "~@wordpress/base-styles/colors" as wp-colors;
6
+
7
+ .newspack-autocomplete-with-latest-posts {
8
+ display: grid;
9
+ gap: 16px;
10
+ width: 100%;
11
+
12
+ .components-select-control__input {
13
+ max-width: none;
14
+ }
15
+
16
+ .components-input-control__backdrop {
17
+ border-color: wp-colors.$gray-700 !important;
18
+ }
19
+
20
+ .newspack-autocomplete-with-latest-posts__label,
21
+ .components-form-token-field__label,
22
+ .components-input-control__label,
23
+ .components-checkbox-control__label {
24
+ color: wp-colors.$gray-900 !important;
25
+ }
26
+
27
+ .newspack-autocomplete-tokenfield__help {
28
+ font-size: inherit;
29
+ margin-bottom: 8px !important;
30
+ }
31
+
32
+ .components-form-token-field {
33
+ &__input-container {
34
+ border-color: wp-colors.$gray-700;
35
+ }
36
+
37
+ &__suggestion {
38
+ padding: 8px;
39
+ }
40
+ }
41
+
42
+ // Hide the built-in "Separate with commas or the Enter key." help text when hideFormTokenHelp is true
43
+ &.hide-form-token-help .components-form-token-field__help {
44
+ display: none;
45
+ }
46
+
47
+ p.newspack-autocomplete-with-latest-posts__label {
48
+ font-size: inherit;
49
+ margin: 0 0 8px;
50
+ }
51
+
52
+ &__selected-items {
53
+ border-bottom: 1px solid wp-colors.$gray-700;
54
+ display: flex;
55
+ flex-wrap: wrap;
56
+ gap: 8px;
57
+ margin-bottom: 16px;
58
+ padding-bottom: 12px;
59
+
60
+ p.newspack-autocomplete-with-latest-posts__label {
61
+ flex: 0 0 100%;
62
+ margin: 0;
63
+ }
64
+ }
65
+
66
+ &__selected-item-button.components-button.is-tertiary {
67
+ background-color: wp-colors.$gray-300;
68
+ border-radius: 2px;
69
+ color: wp-colors.$gray-900;
70
+ font-size: 13px;
71
+ height: auto;
72
+ margin: 0;
73
+ padding-left: 8px;
74
+ padding-right: 8px;
75
+
76
+ &::after {
77
+ content: "✕";
78
+ margin-left: 8px;
79
+ }
80
+
81
+ &:hover {
82
+ box-shadow: none;
83
+ color: black;
84
+ }
85
+ }
86
+
87
+ &__search-suggestions,
88
+ &__search-suggestions-multiselect {
89
+ max-height: 220px;
90
+ overflow: visible auto;
91
+ margin: 0 -4px;
92
+ padding: 4px 4px 0;
93
+
94
+ .components-button {
95
+ display: block;
96
+ line-height: normal;
97
+ margin: auto;
98
+
99
+ &.is-link {
100
+ color: wp-colors.$gray-900;
101
+ padding: 8px;
102
+ text-decoration: none;
103
+ width: 100%;
104
+
105
+ &:active:not(:disabled),
106
+ &:hover:not(:disabled) {
107
+ background: wp-colors.$gray-900;
108
+ background: var(--wp-admin-theme-color);
109
+ color: white;
110
+ }
111
+ }
112
+ &.is-secondary {
113
+ margin-bottom: 8px;
114
+ margin-top: 8px;
115
+ width: 100%;
116
+ }
117
+ }
118
+ }
119
+
120
+ &__search-suggestions {
121
+ border-radius: 2px;
122
+ border: 1px solid wp-colors.$gray-700;
123
+ padding: 0;
124
+
125
+ .components-button {
126
+ line-height: normal;
127
+
128
+ &:focus:not(:disabled) {
129
+ border-radius: 0;
130
+ box-shadow: inset 0 0 0 1.5px wp-colors.$gray-900;
131
+ box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);
132
+ }
133
+
134
+ &.is-secondary {
135
+ width: calc(100% - 16px);
136
+ }
137
+ }
138
+ }
139
+
140
+ // Temp: CSS work-around.
141
+ .newspack-autocomplete-with-suggestions__label {
142
+ font-size: 11px;
143
+ font-weight: 500;
144
+ line-height: 1.4;
145
+ text-transform: uppercase;
146
+ display: block;
147
+ margin: 0 0 8px;
148
+ padding: 0;
149
+ }
150
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import './style.scss';
5
+
6
+ /**
7
+ * External dependencies
8
+ */
9
+ import classnames from 'classnames';
10
+ import { jsx as _jsx } from "react/jsx-runtime";
11
+ /**
12
+ * Badge component
13
+ */
14
+ const Badge = ({
15
+ text,
16
+ level = 'default'
17
+ }) => {
18
+ const classes = classnames('newspack-badge', `is-${level}`);
19
+ return /*#__PURE__*/_jsx("span", {
20
+ className: classes,
21
+ children: text
22
+ });
23
+ };
24
+ export default Badge;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Badge
3
+ */
4
+
5
+ @use "~@wordpress/base-styles/colors" as wp-colors;
6
+ @use "../../../colors/colors.module" as colors;
7
+
8
+ $badge-colors: (
9
+ "info": colors.$primary-600,
10
+ "warning": wp-colors.$alert-yellow,
11
+ "error": wp-colors.$alert-red,
12
+ "success": wp-colors.$alert-green,
13
+ );
14
+
15
+ .newspack-badge {
16
+ background-color: color-mix(in srgb, wp-colors.$white 90%, var(--base-color));
17
+ border-radius: 2px;
18
+ color: color-mix(in srgb, wp-colors.$black 50%, var(--base-color));
19
+ display: block;
20
+ flex: 0 0 auto;
21
+ font-size: 12px;
22
+ line-height: 1.5;
23
+ padding: 1px 4px;
24
+
25
+ &:where(.is-default) {
26
+ background-color: wp-colors.$gray-100;
27
+ color: wp-colors.$gray-800;
28
+ }
29
+
30
+ // Generate color variants
31
+ @each $type, $color in $badge-colors {
32
+ &.is-#{$type} {
33
+ --base-color: #{$color};
34
+ }
35
+ }
36
+ }
@@ -19,6 +19,7 @@ const {
19
19
  } = Router;
20
20
  const Button = ({
21
21
  href,
22
+ loading = undefined,
22
23
  onClick,
23
24
  ...otherProps
24
25
  }) => {
@@ -42,6 +43,7 @@ const Button = ({
42
43
  }
43
44
  // @ts-expect-error - @wordpress/components' Button can only have either href or onClick, not both.
44
45
  return /*#__PURE__*/_jsx(BaseComponent, {
46
+ loading: loading ? true : undefined,
45
47
  ...otherProps
46
48
  });
47
49
  };
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Card using WP Core's Card component.
3
+ * https://wordpress.github.io/gutenberg/?path=/docs/components-card--docs
4
+ */
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { __ } from '@wordpress/i18n';
10
+ import { Button, Card as CardWrapper, CardHeader, CardFooter, DropdownMenu, MenuItem, ToggleControl } from '@wordpress/components';
11
+ import { Icon, chevronDown, chevronRight, chevronUp, dragHandle, moreVertical } from '@wordpress/icons';
12
+
13
+ /**
14
+ * Internal dependencies
15
+ */
16
+ import './style-core.scss';
17
+
18
+ /**
19
+ * External dependencies
20
+ */
21
+ import classNames from 'classnames';
22
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
23
+ const CoreCard = ({
24
+ actions,
25
+ actionType,
26
+ as,
27
+ buttonsCard,
28
+ className,
29
+ footer,
30
+ header,
31
+ icon,
32
+ iconBackgroundColor,
33
+ isActive,
34
+ isDraggable,
35
+ isFirstTarget,
36
+ isLastTarget,
37
+ isNarrow,
38
+ isSmall,
39
+ dragIndex,
40
+ onDragCallback = () => {},
41
+ onHeaderClick,
42
+ noBorder,
43
+ noMargin,
44
+ children = null,
45
+ ...otherProps
46
+ }) => {
47
+ const classes = classNames('newspack-card--core', className, (buttonsCard || as === 'a') && 'newspack-card--core__buttons-card', 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', children && 'newspack-card--core__has-children', noMargin && 'newspack-card--core__no-margin');
48
+ let sizeProps = isSmall ? 'small' : otherProps.size;
49
+ if (buttonsCard || as === 'a') {
50
+ if (!isSmall) {
51
+ sizeProps = 'large';
52
+ }
53
+ if (as !== 'a') {
54
+ otherProps.as = 'a'; // Render as an anchor tag.
55
+ }
56
+ }
57
+ if (noBorder) {
58
+ otherProps.isBorderless = true;
59
+ }
60
+ return /*#__PURE__*/_jsxs(CardWrapper, {
61
+ as: as,
62
+ className: classes,
63
+ ...otherProps,
64
+ children: [(header || icon) && /*#__PURE__*/_jsxs(CardHeader, {
65
+ as: onHeaderClick ? 'button' : undefined,
66
+ className: classNames('newspack-card--core__header', isDraggable && 'newspack-card--core__header--is-draggable'),
67
+ size: sizeProps,
68
+ onClick: onHeaderClick,
69
+ children: [isDraggable && /*#__PURE__*/_jsxs("div", {
70
+ className: "newspack-card--core__header__draggable-controls",
71
+ children: [/*#__PURE__*/_jsx("div", {
72
+ className: "newspack-card--core__header__draggable-controls__drag-handle",
73
+ children: /*#__PURE__*/_jsx(Icon, {
74
+ icon: dragHandle
75
+ })
76
+ }), /*#__PURE__*/_jsxs("div", {
77
+ className: "newspack-card--core__header__draggable-controls__move-buttons",
78
+ children: [/*#__PURE__*/_jsx(Button, {
79
+ icon: chevronUp,
80
+ onClick: () => onDragCallback(dragIndex, dragIndex - 1),
81
+ disabled: isFirstTarget,
82
+ label: __('Move one position up', 'newspack-plugin'),
83
+ size: "small"
84
+ }), /*#__PURE__*/_jsx(Button, {
85
+ icon: chevronDown,
86
+ onClick: () => onDragCallback(dragIndex, dragIndex + 1),
87
+ disabled: isLastTarget,
88
+ label: __('Move one position down', 'newspack-plugin'),
89
+ size: "small"
90
+ })]
91
+ })]
92
+ }), icon && /*#__PURE__*/_jsx("div", {
93
+ className: "newspack-card--core__icon",
94
+ children: /*#__PURE__*/_jsx(Icon, {
95
+ icon: icon,
96
+ height: isSmall ? 24 : 48,
97
+ width: isSmall ? 24 : 48
98
+ })
99
+ }), header && /*#__PURE__*/_jsx("div", {
100
+ className: "newspack-card--core__header-content",
101
+ children: header
102
+ }), !actions?.length > 0 && actionType === 'chevron' && /*#__PURE__*/_jsx(Icon, {
103
+ className: "newspack-card--core__action",
104
+ icon: chevronRight,
105
+ height: 24,
106
+ width: 24
107
+ }), !actions?.length > 0 && actionType === 'toggle' && /*#__PURE__*/_jsx(ToggleControl, {
108
+ className: "newspack-card--core__action",
109
+ label: otherProps.title,
110
+ hideLabelFromVision: true,
111
+ checked: isActive,
112
+ onChange: () => {}
113
+ }), actions?.length > 0 && /*#__PURE__*/_jsx(DropdownMenu, {
114
+ icon: moreVertical,
115
+ label: __('More', 'newspack-plugin'),
116
+ children: () => actions.map((action, index) => /*#__PURE__*/_jsx(MenuItem, {
117
+ icon: action.icon,
118
+ onClick: action.action,
119
+ disabled: action.disabled || false,
120
+ isDestructive: action.destructive || false,
121
+ children: action.label
122
+ }, index))
123
+ })]
124
+ }), children && /*#__PURE__*/_jsx("div", {
125
+ className: "newspack-card--core__body",
126
+ children: children
127
+ }), footer && /*#__PURE__*/_jsx(CardFooter, {
128
+ size: sizeProps,
129
+ children: footer
130
+ })]
131
+ });
132
+ };
133
+ export default CoreCard;
@@ -10,6 +10,7 @@ import { Component } from '@wordpress/element';
10
10
  /**
11
11
  * Internal dependencies
12
12
  */
13
+ import CoreCard from './core-card';
13
14
  import './style.scss';
14
15
 
15
16
  /**
@@ -31,8 +32,40 @@ class Card extends Component {
31
32
  isSmall,
32
33
  isWhite,
33
34
  noBorder,
35
+ // Pass as `true` to render using WP Core's Card component: https://wordpress.github.io/gutenberg/?path=/docs/components-card--docs
36
+ __experimentalCoreCard,
37
+ // Pass props supported by WP Core's Card component in this single prop.
38
+ __experimentalCoreProps = {
39
+ actionType: null,
40
+ // chevron | toggle | button | link | none
41
+ header: null,
42
+ // Pass a React component to render in a CardHeader component.
43
+ icon: null,
44
+ footer: null,
45
+ // Pass a React component to render in a CardFooter component.
46
+ noMargin: false,
47
+ isDraggable: false,
48
+ dragIndex: null,
49
+ onDragCallback: () => {}
50
+ },
34
51
  ...otherProps
35
52
  } = this.props;
53
+ if (__experimentalCoreCard) {
54
+ const props = {
55
+ buttonsCard,
56
+ className,
57
+ isMedium,
58
+ isNarrow,
59
+ isSmall,
60
+ isWhite,
61
+ noBorder,
62
+ ...otherProps,
63
+ ...__experimentalCoreProps
64
+ };
65
+ return /*#__PURE__*/_jsx(CoreCard, {
66
+ ...props
67
+ });
68
+ }
36
69
  const 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');
37
70
  return /*#__PURE__*/_jsx("div", {
38
71
  className: classes,