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
@@ -36,13 +36,14 @@ var _jsxRuntime = require("react/jsx-runtime");
36
36
  * @return {JSX.Element} ActionCard component.
37
37
  */const ActionCard = ({
38
38
  badge,
39
- badgeLevel = 'info',
39
+ badgeLevel,
40
40
  className,
41
41
  checkbox,
42
42
  children,
43
43
  collapse,
44
44
  disabled,
45
45
  title,
46
+ heading = 2,
46
47
  description,
47
48
  handoff,
48
49
  editLink,
@@ -67,11 +68,13 @@ var _jsxRuntime = require("react/jsx-runtime");
67
68
  titleLink,
68
69
  toggleChecked = false,
69
70
  toggleOnChange,
71
+ togglePosition = 'leading',
70
72
  hasGreyHeader,
71
73
  hasWhiteHeader,
72
74
  noBorder,
73
75
  isPending,
74
76
  expandable = false,
77
+ isExpanded,
75
78
  isButtonEnabled = false,
76
79
  // Draggable props. All are required to enable drag sorting.
77
80
  draggable = false,
@@ -79,10 +82,15 @@ var _jsxRuntime = require("react/jsx-runtime");
79
82
  dragWrapperRef,
80
83
  onDragCallback
81
84
  }) => {
82
- const [expanded, setExpanded] = (0, _element.useState)(false);
85
+ const [expanded, setExpanded] = (0, _element.useState)(Boolean(isExpanded));
83
86
  const [dragging, setDragging] = (0, _element.useState)(false);
84
87
  const [targetIndex, setTargetIndex] = (0, _element.useState)(null);
85
88
  const [dragRef, setDragRef] = (0, _element.useState)(null);
89
+ (0, _element.useEffect)(() => {
90
+ if (typeof isExpanded === 'boolean') {
91
+ setExpanded(isExpanded);
92
+ }
93
+ }, [isExpanded]);
86
94
  (0, _element.useEffect)(() => {
87
95
  if (dragWrapperRef && !dragRef) {
88
96
  setDragRef(dragWrapperRef);
@@ -104,16 +112,19 @@ var _jsxRuntime = require("react/jsx-runtime");
104
112
  onClick: () => toggleOnChange(!toggleChecked),
105
113
  tabIndex: '0'
106
114
  } : {};
115
+ const togglePositionClass = togglePosition === 'trailing' ? 'is-toggle-trailing' : 'is-toggle-leading';
107
116
  const hasInternalLink = href && href.indexOf('http') !== 0;
108
117
  const isDisplayingSecondaryAction = secondaryActionText && onSecondaryActionClick;
109
118
  const badges = !Array.isArray(badge) && badge ? [badge] : badge;
110
- const Component = () => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
119
+ const HeadingTag = `h${heading}`;
120
+ const cardContent = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
111
121
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
112
122
  className: "newspack-action-card__region newspack-action-card__region-top",
113
123
  children: [toggleOnChange && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.ToggleControl, {
114
124
  checked: toggleChecked,
115
125
  onChange: toggleOnChange,
116
- disabled: disabled
126
+ disabled: disabled,
127
+ className: togglePositionClass
117
128
  }), image && !toggleOnChange && /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
118
129
  className: "newspack-action-card__region newspack-action-card__region-left",
119
130
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)("a", {
@@ -137,7 +148,7 @@ var _jsxRuntime = require("react/jsx-runtime");
137
148
  columns: 1,
138
149
  gutter: 8,
139
150
  noMargin: true,
140
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("h2", {
151
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(HeadingTag, {
141
152
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("span", {
142
153
  className: "newspack-action-card__title",
143
154
  ...titleProps,
@@ -214,10 +225,10 @@ var _jsxRuntime = require("react/jsx-runtime");
214
225
  isWarning: true,
215
226
  rawHTML: notificationHTML
216
227
  })]
217
- }), children && (expandable && expanded || !expandable) && /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
228
+ }), children && (expandable && expanded || !expandable) ? /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
218
229
  className: "newspack-action-card__region-children",
219
230
  children: children
220
- })]
231
+ }) : null]
221
232
  });
222
233
  if (draggable && dragRef?.current && typeof dragIndex === 'number' && onDragCallback && id) {
223
234
  let wrapperRect = dragRef.current.getBoundingClientRect();
@@ -288,9 +299,7 @@ var _jsxRuntime = require("react/jsx-runtime");
288
299
  onDragStart: onDraggableStart,
289
300
  onDragEnd: onDraggableEnd,
290
301
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.Icon, {
291
- icon: _icons.dragHandle,
292
- height: 18,
293
- width: 18
302
+ icon: _icons.dragHandle
294
303
  })
295
304
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
296
305
  className: "movers",
@@ -306,7 +315,7 @@ var _jsxRuntime = require("react/jsx-runtime");
306
315
  label: (0, _i18n.__)('Move one position down', 'newspack-plugin')
307
316
  })]
308
317
  })]
309
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(Component, {})]
318
+ }), cardContent]
310
319
  })
311
320
  })
312
321
  });
@@ -316,7 +325,7 @@ var _jsxRuntime = require("react/jsx-runtime");
316
325
  onClick: simple && onClick,
317
326
  id: id !== null && id !== void 0 ? id : null,
318
327
  noBorder: noBorder,
319
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(Component, {})
328
+ children: cardContent
320
329
  });
321
330
  };
322
331
  var _default = exports.default = ActionCard;
@@ -260,6 +260,16 @@
260
260
  @media screen and (min-width: 744px) {
261
261
  margin-bottom: 0;
262
262
  margin-right: 24px;
263
+
264
+ &.is-toggle-trailing {
265
+ margin-left: 24px;
266
+ margin-right: 0;
267
+ order: 999;
268
+ }
269
+ }
270
+
271
+ label {
272
+ display: none;
263
273
  }
264
274
 
265
275
  .components-base-control__field {
@@ -433,13 +443,21 @@
433
443
  text-decoration: inherit;
434
444
  }
435
445
 
436
- .newspack-action-card__region-children {
437
- border-top: 1px solid wp-colors.$gray-100;
446
+ > .newspack-action-card__region-children {
447
+ border-top: 1px solid wp-colors.$gray-300;
438
448
  padding-top: 24px;
439
449
 
440
450
  + .newspack-action-card__region-children {
441
451
  border-top: none;
442
452
  }
453
+
454
+ > * + * {
455
+ margin-top: 24px !important;
456
+ }
457
+
458
+ .newspack-action-card__region-children:has(> div:not(:empty)) {
459
+ margin-top: 16px;
460
+ }
443
461
  }
444
462
 
445
463
  &.is-medium .newspack-action-card__region-children {
@@ -469,6 +487,11 @@
469
487
  margin-left: 8px;
470
488
  }
471
489
  }
490
+
491
+ hr:not(.newspack-divider) {
492
+ background: wp-colors.$gray-100;
493
+ margin: 0;
494
+ }
472
495
  }
473
496
 
474
497
  .newspack-card {
@@ -143,7 +143,7 @@ var _jsxRuntime = require("react/jsx-runtime");
143
143
  Object.keys(validValues).forEach(key => {
144
144
  if (validValues[key] === label) {
145
145
  // Preserve numeric or string type of values. Object.keys will convert numbers to strings.
146
- const value = isNaN(parseInt(key)) ? key.toString() : parseInt(key);
146
+ const value = /^\d+$/.test(key.toString()) ? parseInt(key) : key.toString();
147
147
  acc.push({
148
148
  value,
149
149
  label
@@ -153,7 +153,7 @@ var _jsxRuntime = require("react/jsx-runtime");
153
153
  return acc;
154
154
  }, []);
155
155
  }
156
- return labels.map(label => Object.keys(validValues).map(key => isNaN(parseInt(key)) ? key.toString() : parseInt(key)).find(key => validValues[key] === label));
156
+ return labels.map(label => Object.keys(validValues).map(key => /^\d+$/.test(key.toString()) ? parseInt(key) : key.toString()).find(key => validValues[key] === label));
157
157
  }
158
158
 
159
159
  /**
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _debounce = _interopRequireDefault(require("lodash/debounce"));
9
+ var _apiFetch = _interopRequireDefault(require("@wordpress/api-fetch"));
10
+ var _components = require("@wordpress/components");
11
+ var _htmlEntities = require("@wordpress/html-entities");
12
+ var _element = require("@wordpress/element");
13
+ var _i18n = require("@wordpress/i18n");
14
+ var _url = require("@wordpress/url");
15
+ require("./style.scss");
16
+ var _jsxRuntime = require("react/jsx-runtime");
17
+ /**
18
+ * External dependencies
19
+ */
20
+
21
+ /**
22
+ * WordPress dependencies
23
+ */
24
+
25
+ const AutocompleteWithLatestPosts = ({
26
+ fetchSuggestions = false,
27
+ // If passed, will use this function to fetch suggestions data.
28
+ help = (0, _i18n.__)('Begin typing search term, click autocomplete result to select.', 'newspack-plugin'),
29
+ hideHelp = true,
30
+ // If true, all help text will be hidden.
31
+ hideFormTokenHelp = true,
32
+ // If true, hides FormTokenField's built-in help text.
33
+ label = (0, _i18n.__)('Search', 'newspack-plugin'),
34
+ maxItemsToSuggest = 0,
35
+ // If passed, will be used to determine "load more" state. Necessary if you want "load more" functionality when using a custom `fetchSuggestions` function.
36
+ multiSelect = false,
37
+ // If true, component can select multiple values at once.
38
+ onChange = false,
39
+ // Function to call when selections change.
40
+ selectedItems = [],
41
+ // Array of saved items.
42
+ suggestionsToFetch = 20 // Number of suggestions to fetch per query.
43
+ }) => {
44
+ const [isLoading, setIsLoading] = (0, _element.useState)(true);
45
+ const [isLoadingMore, setIsLoadingMore] = (0, _element.useState)(false);
46
+ const [suggestions, setSuggestions] = (0, _element.useState)([]);
47
+ const [maxSuggestions, setMaxSuggestions] = (0, _element.useState)(0);
48
+ const [searchSuggestions, setSearchSuggestions] = (0, _element.useState)([]);
49
+ const [validValues, setValidValues] = (0, _element.useState)({});
50
+ const [isSearching, setIsSearching] = (0, _element.useState)(false);
51
+ // Leaving this in here so we can expand to use different post types in the future; right now it defaults to 'post'.
52
+ const postTypeToSearch = 'post';
53
+ const classNames = ['newspack-autocomplete-with-latest-posts'];
54
+ if (hideFormTokenHelp) {
55
+ classNames.push('hide-form-token-help');
56
+ }
57
+ if (hideHelp) {
58
+ classNames.push('hide-help');
59
+ }
60
+
61
+ /**
62
+ * Debounced function to update search suggestions.
63
+ */
64
+ const debouncedUpdateSuggestions = (0, _debounce.default)(input => {
65
+ if (!input || input.length < 2) {
66
+ setSearchSuggestions([]);
67
+ setIsSearching(false);
68
+ return;
69
+ }
70
+ setIsSearching(true);
71
+ handleFetchSuggestions(input, 0, postTypeToSearch).then(_suggestions => {
72
+ setSearchSuggestions(_suggestions);
73
+ // Update valid values for token conversion
74
+ const newValidValues = {
75
+ ...validValues
76
+ };
77
+ _suggestions.forEach(suggestion => {
78
+ newValidValues[suggestion.value] = suggestion.label;
79
+ });
80
+ setValidValues(newValidValues);
81
+ }).finally(() => setIsSearching(false));
82
+ }, 500);
83
+
84
+ /**
85
+ * Fetch recent posts to show as suggestions.
86
+ */
87
+ (0, _element.useEffect)(() => {
88
+ setIsLoading(true);
89
+ handleFetchSuggestions(null, 0, postTypeToSearch).then(_suggestions => {
90
+ if (0 < _suggestions.length) {
91
+ setSuggestions(_suggestions);
92
+ // Update valid values for token conversion
93
+ const newValidValues = {
94
+ ...validValues
95
+ };
96
+ _suggestions.forEach(suggestion => {
97
+ newValidValues[suggestion.value] = suggestion.label;
98
+ });
99
+ setValidValues(newValidValues);
100
+ }
101
+ }).finally(() => setIsLoading(false));
102
+ }, []);
103
+
104
+ /**
105
+ * Fetch more suggestions.
106
+ */
107
+ (0, _element.useEffect)(() => {
108
+ if (isLoadingMore) {
109
+ handleFetchSuggestions(null, suggestions.length, postTypeToSearch).then(_suggestions => {
110
+ if (0 < _suggestions.length) {
111
+ setSuggestions(suggestions.concat(_suggestions));
112
+ }
113
+ }).finally(() => setIsLoadingMore(false));
114
+ }
115
+ }, [isLoadingMore]);
116
+
117
+ /**
118
+ * If passed a `fetchSuggestions` prop, use that, otherwise, build it based on the selected post type.
119
+ */
120
+ const handleFetchSuggestions = fetchSuggestions ? fetchSuggestions : async (search = null, offset = 0, searchSlug = null) => {
121
+ const postTypeSlug = searchSlug || postTypeToSearch;
122
+ const endpoint = 'post' === postTypeSlug || 'page' === postTypeSlug ? postTypeSlug + 's' // Default post type endpoints are plural.
123
+ : postTypeSlug; // Custom post type endpoints are singular.
124
+ const response = await (0, _apiFetch.default)({
125
+ parse: false,
126
+ path: (0, _url.addQueryArgs)('/wp/v2/' + endpoint, {
127
+ search,
128
+ offset,
129
+ per_page: suggestionsToFetch,
130
+ _fields: 'id,title'
131
+ })
132
+ });
133
+ const total = parseInt(response.headers.get('x-wp-total') || 0);
134
+ const posts = await response.json();
135
+ setMaxSuggestions(total);
136
+
137
+ // Format suggestions for FormTokenField display.
138
+ return posts.reduce((acc, post) => {
139
+ acc.push({
140
+ value: parseInt(post.id),
141
+ label: (0, _htmlEntities.decodeEntities)(post?.title.rendered) || (0, _i18n.__)('(no title)', 'newspack-plugin')
142
+ });
143
+ return acc;
144
+ }, []);
145
+ };
146
+
147
+ /**
148
+ * Get labels for token values.
149
+ */
150
+ const getLabelsForValues = values => {
151
+ return values.reduce((accumulator, value) => {
152
+ if (!value) {
153
+ return accumulator;
154
+ }
155
+ if (value.label) {
156
+ return [...accumulator, value.label];
157
+ }
158
+ return validValues[value] ? [...accumulator, validValues[value]] : accumulator;
159
+ }, []);
160
+ };
161
+
162
+ /**
163
+ * Get values for token labels.
164
+ */
165
+ const getValuesForLabels = labels => {
166
+ // eslint-disable-next-line @typescript-eslint/no-shadow
167
+ return labels.reduce((acc, label) => {
168
+ Object.keys(validValues).forEach(key => {
169
+ if (validValues[key] === label) {
170
+ const value = isNaN(parseInt(key)) ? key.toString() : parseInt(key);
171
+ acc.push({
172
+ value,
173
+ label
174
+ });
175
+ }
176
+ });
177
+ return acc;
178
+ }, []);
179
+ };
180
+
181
+ /**
182
+ * Handle FormTokenField onChange.
183
+ */
184
+ const handleTokenChange = tokenStrings => {
185
+ const newSelections = getValuesForLabels(tokenStrings);
186
+
187
+ // If only allowing one selection, just return the one selected item.
188
+ if (!multiSelect) {
189
+ const newSelection = newSelections[newSelections.length - 1]; // Get the last selected item
190
+ if (newSelection) {
191
+ return onChange([{
192
+ ...newSelection,
193
+ postType: postTypeToSearch
194
+ }]);
195
+ }
196
+ return onChange([]);
197
+ }
198
+
199
+ // For multi-select, include currently selected post type in selection results.
200
+ onChange(newSelections.map(selection => ({
201
+ ...selection,
202
+ postType: postTypeToSearch
203
+ })));
204
+ };
205
+
206
+ /**
207
+ * Get tokens for FormTokenField.
208
+ */
209
+ const getTokens = () => {
210
+ return getLabelsForValues(selectedItems);
211
+ };
212
+
213
+ /**
214
+ * Render a single suggestion object that can be clicked to select it immediately.
215
+ *
216
+ * @param {Object} suggestion Suggestion object with value and label keys.
217
+ */
218
+ const renderSuggestion = suggestion => {
219
+ if (multiSelect) {
220
+ const isSelected = !!selectedItems.find(_selection => parseInt(_selection.value) === parseInt(suggestion.value) && _selection.label === suggestion.label);
221
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.CheckboxControl, {
222
+ checked: isSelected,
223
+ onChange: () => {
224
+ // For multi-select, we need to toggle the selection
225
+ const currentTokens = getTokens();
226
+ const suggestionLabel = suggestion.label;
227
+ if (isSelected) {
228
+ // Remove the suggestion
229
+ const newTokens = currentTokens.filter(token => token !== suggestionLabel);
230
+ handleTokenChange(newTokens);
231
+ } else {
232
+ // Add the suggestion
233
+ handleTokenChange([...currentTokens, suggestionLabel]);
234
+ }
235
+ },
236
+ label: suggestion.label
237
+ }, suggestion.value);
238
+ }
239
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Button, {
240
+ isLink: true,
241
+ onClick: () => handleTokenChange([...getTokens(), suggestion.label]),
242
+ children: suggestion.label
243
+ }, suggestion.value);
244
+ };
245
+
246
+ /**
247
+ * Render a list of suggestions that can be clicked to select instead of searching by title.
248
+ */
249
+ const renderSuggestions = () => {
250
+ if (0 === suggestions.length) {
251
+ return null;
252
+ }
253
+ const className = multiSelect ? 'newspack-autocomplete-with-latest-posts__search-suggestions-multiselect' : 'newspack-autocomplete-with-latest-posts__search-suggestions';
254
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
255
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
256
+ className: "newspack-autocomplete-with-latest-posts__search-suggestions-container",
257
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("p", {
258
+ className: "newspack-autocomplete-with-suggestions__label",
259
+ children: (0, _i18n.__)('Latest Posts', 'newspack-plugin')
260
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
261
+ className: className,
262
+ children: [suggestions.map(renderSuggestion), suggestions.length < (maxItemsToSuggest || maxSuggestions) && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Button, {
263
+ disabled: isLoadingMore,
264
+ isSecondary: true,
265
+ onClick: () => setIsLoadingMore(true),
266
+ children: isLoadingMore ? (0, _i18n.__)('Loading…', 'newspack-plugin') : (0, _i18n.__)('Load more', 'newspack-plugin')
267
+ })]
268
+ })]
269
+ })
270
+ });
271
+ };
272
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
273
+ className: classNames.join(' '),
274
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
275
+ className: "newspack-autocomplete-with-latest-posts__input-container",
276
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.FormTokenField, {
277
+ value: getTokens(),
278
+ suggestions: searchSuggestions.map(suggestion => suggestion.label),
279
+ onChange: handleTokenChange,
280
+ onInputChange: debouncedUpdateSuggestions,
281
+ label: label,
282
+ help: !hideHelp && help,
283
+ __next40pxDefaultSize: true,
284
+ __nextHasNoMarginBottom: true
285
+ }), (isLoading || isSearching) && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Spinner, {})]
286
+ }), renderSuggestions()]
287
+ });
288
+ };
289
+ var _default = exports.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,32 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ require("./style.scss");
9
+ var _classnames = _interopRequireDefault(require("classnames"));
10
+ var _jsxRuntime = require("react/jsx-runtime");
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+
15
+ /**
16
+ * External dependencies
17
+ */
18
+
19
+ /**
20
+ * Badge component
21
+ */
22
+ const Badge = ({
23
+ text,
24
+ level = 'default'
25
+ }) => {
26
+ const classes = (0, _classnames.default)('newspack-badge', `is-${level}`);
27
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
28
+ className: classes,
29
+ children: text
30
+ });
31
+ };
32
+ var _default = exports.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
+ }