newspack-components 3.0.0-alpha.2 → 3.0.0-alpha.4

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 (227) hide show
  1. package/babel.config.cjs.js +26 -0
  2. package/babel.config.js +1 -0
  3. package/copy-styles.js +10 -2
  4. package/dist/cjs/button/index.js +56 -0
  5. package/dist/cjs/grid/index.js +37 -0
  6. package/dist/cjs/hooks/index.js +25 -0
  7. package/dist/cjs/hooks/usePrompt.js +49 -0
  8. package/dist/cjs/text-control/index.js +64 -0
  9. package/dist/esm/accordion/index.js +42 -0
  10. package/dist/esm/accordion/style.scss +31 -0
  11. package/dist/esm/action-card/index.js +218 -0
  12. package/dist/esm/action-card/style.scss +483 -0
  13. package/dist/esm/autocomplete-tokenfield/index.js +261 -0
  14. package/dist/esm/autocomplete-tokenfield/style.scss +49 -0
  15. package/dist/esm/autocomplete-with-suggestions/index.js +274 -0
  16. package/dist/esm/autocomplete-with-suggestions/style.scss +163 -0
  17. package/dist/esm/button/index.js +48 -0
  18. package/dist/esm/button/style.scss +11 -0
  19. package/dist/esm/button-card/index.js +64 -0
  20. package/dist/esm/button-card/style.scss +180 -0
  21. package/dist/esm/card/index.js +43 -0
  22. package/dist/esm/card/style.scss +168 -0
  23. package/dist/esm/category-autocomplete/index.js +183 -0
  24. package/dist/esm/category-autocomplete/style.scss +34 -0
  25. package/dist/esm/color-picker/index.js +59 -0
  26. package/dist/esm/color-picker/style.scss +37 -0
  27. package/dist/esm/consts.js +1 -0
  28. package/dist/esm/custom-select-control/index.js +37 -0
  29. package/dist/esm/custom-select-control/style.scss +93 -0
  30. package/dist/esm/date-range-picker/index.js +31 -0
  31. package/dist/esm/date-range-picker/style.scss +148 -0
  32. package/dist/esm/footer/index.js +99 -0
  33. package/dist/esm/footer/style.scss +82 -0
  34. package/dist/esm/form-token-field/index.js +49 -0
  35. package/dist/esm/form-token-field/style.scss +37 -0
  36. package/dist/esm/global-notices/index.js +30 -0
  37. package/dist/esm/grid/index.js +29 -0
  38. package/dist/esm/grid/style.scss +166 -0
  39. package/dist/esm/handoff/index.js +175 -0
  40. package/dist/esm/handoff-message/index.js +47 -0
  41. package/dist/esm/hooks/index.js +17 -0
  42. package/dist/esm/hooks/useObjectState.js +30 -0
  43. package/dist/esm/hooks/useObjectState.test.js +82 -0
  44. package/dist/esm/hooks/useOnClickOutside.js +31 -0
  45. package/dist/esm/hooks/usePrompt.js +40 -0
  46. package/dist/esm/image-upload/index.js +140 -0
  47. package/dist/esm/image-upload/index.test.js +34 -0
  48. package/dist/esm/image-upload/style.scss +103 -0
  49. package/dist/esm/index.js +47 -0
  50. package/dist/esm/info-button/index.js +42 -0
  51. package/dist/esm/info-button/style.scss +18 -0
  52. package/dist/esm/modal/index.js +39 -0
  53. package/dist/esm/modal/style.scss +56 -0
  54. package/dist/esm/newspack-icon/index.js +50 -0
  55. package/dist/esm/newspack-icon/style.scss +26 -0
  56. package/dist/esm/newspack-logo/index.js +46 -0
  57. package/dist/esm/newspack-logo/style.scss +30 -0
  58. package/dist/esm/notice/index.js +65 -0
  59. package/dist/esm/notice/style.scss +111 -0
  60. package/dist/esm/patrons-logo/index.js +70 -0
  61. package/dist/esm/plugin-installer/index.js +289 -0
  62. package/dist/esm/plugin-installer/style.scss +72 -0
  63. package/dist/esm/plugin-settings/SettingsSection.js +133 -0
  64. package/dist/esm/plugin-settings/index.js +220 -0
  65. package/dist/esm/plugin-settings/style.scss +7 -0
  66. package/dist/esm/plugin-toggle/index.js +216 -0
  67. package/dist/esm/plugin-toggle/style.scss +20 -0
  68. package/dist/esm/popover/index.js +41 -0
  69. package/dist/esm/popover/style.scss +161 -0
  70. package/dist/esm/position-control/index.js +106 -0
  71. package/dist/esm/position-control/style.scss +66 -0
  72. package/dist/esm/progress-bar/index.js +68 -0
  73. package/dist/esm/progress-bar/index.test.js +108 -0
  74. package/dist/esm/progress-bar/style.scss +42 -0
  75. package/dist/esm/proxied-imports/router.js +8 -0
  76. package/dist/esm/radio-control/index.js +37 -0
  77. package/dist/esm/radio-control/style.scss +92 -0
  78. package/dist/esm/section-header/index.js +67 -0
  79. package/dist/esm/section-header/style.scss +45 -0
  80. package/dist/esm/select-control/ButtonGroupControl.js +53 -0
  81. package/dist/esm/select-control/GroupedSelectControl.js +78 -0
  82. package/dist/esm/select-control/index.js +51 -0
  83. package/dist/esm/select-control/style.scss +108 -0
  84. package/dist/esm/settings/MinMaxSetting.js +53 -0
  85. package/dist/esm/settings/SettingsCard.js +38 -0
  86. package/dist/esm/settings/SettingsSection.js +26 -0
  87. package/dist/esm/settings/index.js +9 -0
  88. package/dist/esm/settings/style.scss +119 -0
  89. package/dist/esm/sortable-newsletter-list-control/index.js +127 -0
  90. package/dist/esm/sortable-newsletter-list-control/style.scss +45 -0
  91. package/dist/esm/steps-list/index.js +43 -0
  92. package/dist/esm/steps-list/style.scss +11 -0
  93. package/dist/esm/steps-list-item/index.js +47 -0
  94. package/dist/esm/steps-list-item/style.scss +47 -0
  95. package/dist/esm/style-card/index.js +74 -0
  96. package/dist/esm/style-card/style.scss +94 -0
  97. package/dist/esm/style.scss +55 -0
  98. package/dist/esm/tabbed-navigation/index.js +53 -0
  99. package/dist/esm/tabbed-navigation/style.scss +86 -0
  100. package/dist/esm/text-control/index.js +56 -0
  101. package/dist/esm/text-control/style.scss +32 -0
  102. package/dist/esm/utils/index.js +29 -0
  103. package/dist/esm/waiting/index.js +44 -0
  104. package/dist/esm/waiting/style.scss +27 -0
  105. package/dist/esm/web-preview/index.js +183 -0
  106. package/dist/esm/web-preview/style.scss +155 -0
  107. package/dist/esm/with-wizard/index.js +320 -0
  108. package/dist/esm/with-wizard/style.scss +282 -0
  109. package/dist/esm/with-wizard-screen/index.js +109 -0
  110. package/dist/esm/with-wizard-screen/style.scss +160 -0
  111. package/dist/esm/wizard/components/WizardError.js +66 -0
  112. package/dist/esm/wizard/index.js +119 -0
  113. package/dist/esm/wizard/store/index.js +155 -0
  114. package/dist/esm/wizard/store/utils.js +14 -0
  115. package/package.json +8 -4
  116. package/dist/button/index.js +0 -51
  117. package/dist/button/index.tsx +0 -48
  118. package/dist/grid/index.js +0 -44
  119. package/dist/grid/index.tsx +0 -34
  120. package/dist/hooks/index.js +0 -29
  121. package/dist/hooks/index.ts +0 -15
  122. package/dist/hooks/usePrompt.js +0 -47
  123. package/dist/hooks/usePrompt.ts +0 -46
  124. package/dist/plugin-toggle/README.md +0 -53
  125. package/dist/text-control/index.js +0 -76
  126. package/dist/text-control/index.tsx +0 -71
  127. /package/dist/{accordion → cjs/accordion}/index.js +0 -0
  128. /package/dist/{accordion → cjs/accordion}/style.scss +0 -0
  129. /package/dist/{action-card → cjs/action-card}/index.js +0 -0
  130. /package/dist/{action-card → cjs/action-card}/style.scss +0 -0
  131. /package/dist/{autocomplete-tokenfield → cjs/autocomplete-tokenfield}/index.js +0 -0
  132. /package/dist/{autocomplete-tokenfield → cjs/autocomplete-tokenfield}/style.scss +0 -0
  133. /package/dist/{autocomplete-with-suggestions → cjs/autocomplete-with-suggestions}/index.js +0 -0
  134. /package/dist/{autocomplete-with-suggestions → cjs/autocomplete-with-suggestions}/style.scss +0 -0
  135. /package/dist/{button → cjs/button}/style.scss +0 -0
  136. /package/dist/{button-card → cjs/button-card}/index.js +0 -0
  137. /package/dist/{button-card → cjs/button-card}/style.scss +0 -0
  138. /package/dist/{card → cjs/card}/index.js +0 -0
  139. /package/dist/{card → cjs/card}/style.scss +0 -0
  140. /package/dist/{category-autocomplete → cjs/category-autocomplete}/index.js +0 -0
  141. /package/dist/{category-autocomplete → cjs/category-autocomplete}/style.scss +0 -0
  142. /package/dist/{color-picker → cjs/color-picker}/index.js +0 -0
  143. /package/dist/{color-picker → cjs/color-picker}/style.scss +0 -0
  144. /package/dist/{consts.js → cjs/consts.js} +0 -0
  145. /package/dist/{custom-select-control → cjs/custom-select-control}/index.js +0 -0
  146. /package/dist/{custom-select-control → cjs/custom-select-control}/style.scss +0 -0
  147. /package/dist/{date-range-picker → cjs/date-range-picker}/index.js +0 -0
  148. /package/dist/{date-range-picker → cjs/date-range-picker}/style.scss +0 -0
  149. /package/dist/{footer → cjs/footer}/index.js +0 -0
  150. /package/dist/{footer → cjs/footer}/style.scss +0 -0
  151. /package/dist/{form-token-field → cjs/form-token-field}/index.js +0 -0
  152. /package/dist/{form-token-field → cjs/form-token-field}/style.scss +0 -0
  153. /package/dist/{global-notices → cjs/global-notices}/index.js +0 -0
  154. /package/dist/{grid → cjs/grid}/style.scss +0 -0
  155. /package/dist/{handoff → cjs/handoff}/index.js +0 -0
  156. /package/dist/{handoff-message → cjs/handoff-message}/index.js +0 -0
  157. /package/dist/{hooks → cjs/hooks}/useObjectState.js +0 -0
  158. /package/dist/{hooks → cjs/hooks}/useObjectState.test.js +0 -0
  159. /package/dist/{hooks → cjs/hooks}/useOnClickOutside.js +0 -0
  160. /package/dist/{image-upload → cjs/image-upload}/index.js +0 -0
  161. /package/dist/{image-upload → cjs/image-upload}/index.test.js +0 -0
  162. /package/dist/{image-upload → cjs/image-upload}/style.scss +0 -0
  163. /package/dist/{index.js → cjs/index.js} +0 -0
  164. /package/dist/{info-button → cjs/info-button}/index.js +0 -0
  165. /package/dist/{info-button → cjs/info-button}/style.scss +0 -0
  166. /package/dist/{modal → cjs/modal}/index.js +0 -0
  167. /package/dist/{modal → cjs/modal}/style.scss +0 -0
  168. /package/dist/{newspack-icon → cjs/newspack-icon}/index.js +0 -0
  169. /package/dist/{newspack-icon → cjs/newspack-icon}/style.scss +0 -0
  170. /package/dist/{newspack-logo → cjs/newspack-logo}/index.js +0 -0
  171. /package/dist/{newspack-logo → cjs/newspack-logo}/style.scss +0 -0
  172. /package/dist/{notice → cjs/notice}/index.js +0 -0
  173. /package/dist/{notice → cjs/notice}/style.scss +0 -0
  174. /package/dist/{patrons-logo → cjs/patrons-logo}/index.js +0 -0
  175. /package/dist/{plugin-installer → cjs/plugin-installer}/index.js +0 -0
  176. /package/dist/{plugin-installer → cjs/plugin-installer}/style.scss +0 -0
  177. /package/dist/{plugin-settings → cjs/plugin-settings}/SettingsSection.js +0 -0
  178. /package/dist/{plugin-settings → cjs/plugin-settings}/index.js +0 -0
  179. /package/dist/{plugin-settings → cjs/plugin-settings}/style.scss +0 -0
  180. /package/dist/{plugin-toggle → cjs/plugin-toggle}/index.js +0 -0
  181. /package/dist/{plugin-toggle → cjs/plugin-toggle}/style.scss +0 -0
  182. /package/dist/{popover → cjs/popover}/index.js +0 -0
  183. /package/dist/{popover → cjs/popover}/style.scss +0 -0
  184. /package/dist/{position-control → cjs/position-control}/index.js +0 -0
  185. /package/dist/{position-control → cjs/position-control}/style.scss +0 -0
  186. /package/dist/{progress-bar → cjs/progress-bar}/index.js +0 -0
  187. /package/dist/{progress-bar → cjs/progress-bar}/index.test.js +0 -0
  188. /package/dist/{progress-bar → cjs/progress-bar}/style.scss +0 -0
  189. /package/dist/{proxied-imports → cjs/proxied-imports}/router.js +0 -0
  190. /package/dist/{radio-control → cjs/radio-control}/index.js +0 -0
  191. /package/dist/{radio-control → cjs/radio-control}/style.scss +0 -0
  192. /package/dist/{section-header → cjs/section-header}/index.js +0 -0
  193. /package/dist/{section-header → cjs/section-header}/style.scss +0 -0
  194. /package/dist/{select-control → cjs/select-control}/ButtonGroupControl.js +0 -0
  195. /package/dist/{select-control → cjs/select-control}/GroupedSelectControl.js +0 -0
  196. /package/dist/{select-control → cjs/select-control}/index.js +0 -0
  197. /package/dist/{select-control → cjs/select-control}/style.scss +0 -0
  198. /package/dist/{settings → cjs/settings}/MinMaxSetting.js +0 -0
  199. /package/dist/{settings → cjs/settings}/SettingsCard.js +0 -0
  200. /package/dist/{settings → cjs/settings}/SettingsSection.js +0 -0
  201. /package/dist/{settings → cjs/settings}/index.js +0 -0
  202. /package/dist/{settings → cjs/settings}/style.scss +0 -0
  203. /package/dist/{sortable-newsletter-list-control → cjs/sortable-newsletter-list-control}/index.js +0 -0
  204. /package/dist/{sortable-newsletter-list-control → cjs/sortable-newsletter-list-control}/style.scss +0 -0
  205. /package/dist/{steps-list → cjs/steps-list}/index.js +0 -0
  206. /package/dist/{steps-list → cjs/steps-list}/style.scss +0 -0
  207. /package/dist/{steps-list-item → cjs/steps-list-item}/index.js +0 -0
  208. /package/dist/{steps-list-item → cjs/steps-list-item}/style.scss +0 -0
  209. /package/dist/{style-card → cjs/style-card}/index.js +0 -0
  210. /package/dist/{style-card → cjs/style-card}/style.scss +0 -0
  211. /package/dist/{style.scss → cjs/style.scss} +0 -0
  212. /package/dist/{tabbed-navigation → cjs/tabbed-navigation}/index.js +0 -0
  213. /package/dist/{tabbed-navigation → cjs/tabbed-navigation}/style.scss +0 -0
  214. /package/dist/{text-control → cjs/text-control}/style.scss +0 -0
  215. /package/dist/{utils → cjs/utils}/index.js +0 -0
  216. /package/dist/{waiting → cjs/waiting}/index.js +0 -0
  217. /package/dist/{waiting → cjs/waiting}/style.scss +0 -0
  218. /package/dist/{web-preview → cjs/web-preview}/index.js +0 -0
  219. /package/dist/{web-preview → cjs/web-preview}/style.scss +0 -0
  220. /package/dist/{with-wizard → cjs/with-wizard}/index.js +0 -0
  221. /package/dist/{with-wizard → cjs/with-wizard}/style.scss +0 -0
  222. /package/dist/{with-wizard-screen → cjs/with-wizard-screen}/index.js +0 -0
  223. /package/dist/{with-wizard-screen → cjs/with-wizard-screen}/style.scss +0 -0
  224. /package/dist/{wizard → cjs/wizard}/components/WizardError.js +0 -0
  225. /package/dist/{wizard → cjs/wizard}/index.js +0 -0
  226. /package/dist/{wizard → cjs/wizard}/store/index.js +0 -0
  227. /package/dist/{wizard → cjs/wizard}/store/utils.js +0 -0
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Autocomplete Token Field
3
+ */
4
+
5
+ @use "~@wordpress/base-styles/colors" as wp-colors;
6
+
7
+ .newspack-autocomplete-tokenfield {
8
+ .components-spinner {
9
+ margin: 0;
10
+ position: absolute;
11
+ right: 8px;
12
+ top: 8px;
13
+ }
14
+
15
+ &__input-container {
16
+ position: relative;
17
+
18
+ &.has-label .components-spinner {
19
+ top: 32px;
20
+ }
21
+ }
22
+
23
+ /* Workaround for hard-coded help text in FormTokenField. */
24
+ .components-form-token-field {
25
+ > .components-form-token-field__help {
26
+ display: none;
27
+ }
28
+
29
+ > .components-form-token-field__token-text {
30
+ align-items: center;
31
+ display: flex;
32
+ }
33
+ }
34
+
35
+ .newspack-autocomplete-tokenfield__help {
36
+ color: wp-colors.$gray-700;
37
+ font-size: inherit;
38
+ margin-top: 0;
39
+ }
40
+
41
+ .components-form-token-field__suggestions-list {
42
+ margin-left: 0;
43
+ padding-left: 0;
44
+ }
45
+
46
+ .components-button:focus:not(:disabled) {
47
+ box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);
48
+ }
49
+ }
@@ -0,0 +1,274 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import apiFetch from '@wordpress/api-fetch';
5
+ import { Button, CheckboxControl, SelectControl } from '@wordpress/components';
6
+ import { decodeEntities } from '@wordpress/html-entities';
7
+ import { useEffect, useState } from '@wordpress/element';
8
+ import { __, _x, sprintf } from '@wordpress/i18n';
9
+ import { addQueryArgs } from '@wordpress/url';
10
+
11
+ /**
12
+ * External dependencies.
13
+ */
14
+ import { AutocompleteTokenField } from '../';
15
+ import './style.scss';
16
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
17
+ const AutocompleteWithSuggestions = ({
18
+ fetchSavedPosts = false,
19
+ // If passed, will use this function to fetch saved data.
20
+ fetchSuggestions = false,
21
+ // If passed, will use this function to fetch suggestions data.
22
+ help = __('Begin typing search term, click autocomplete result to select.', 'newspack-plugin'),
23
+ hideHelp = false,
24
+ // If true, all help text will be hidden.
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
+ onPostTypeChange = false,
33
+ // Funciton to call when the post type selector is changed.
34
+ postTypes = [{
35
+ slug: 'post',
36
+ label: 'Post'
37
+ }],
38
+ // If passed, will render a selector to change the post type queried for suggestions.
39
+ postTypeLabel = __('item', 'newspack-plugin'),
40
+ // String to describe the data being shown.
41
+ postTypeLabelPlural = __('items', 'newspack-plugin'),
42
+ // Plural string to describe the data being shown.
43
+ selectedItems = [],
44
+ // Array of saved items.
45
+ selectedPost = 0,
46
+ // Legacy prop when single-select was the only option.
47
+ suggestionsToFetch = 10 // Number of suggestions to fetch per query.
48
+ }) => {
49
+ const [isLoading, setIsLoading] = useState(true);
50
+ const [isLoadingMore, setIsLoadingMore] = useState(false);
51
+ const [suggestions, setSuggestions] = useState([]);
52
+ const [maxSuggestions, setMaxSuggestions] = useState(0);
53
+ const [postTypeToSearch, setPostTypeToSearch] = useState(postTypes[0].slug);
54
+ const classNames = ['newspack-autocomplete-with-suggestions'];
55
+ if (hideHelp) {
56
+ classNames.push('hide-help');
57
+ }
58
+
59
+ /**
60
+ * Fetch recent posts to show as suggestions.
61
+ */
62
+ useEffect(() => {
63
+ if (onPostTypeChange) {
64
+ onPostTypeChange(postTypeToSearch);
65
+ }
66
+ setIsLoading(true);
67
+ handleFetchSuggestions(null, 0, postTypeToSearch).then(_suggestions => {
68
+ if (0 < _suggestions.length) {
69
+ setSuggestions(_suggestions);
70
+ }
71
+ }).finally(() => setIsLoading(false));
72
+ }, [postTypeToSearch]);
73
+
74
+ /**
75
+ * Fetch more suggestions.
76
+ */
77
+ useEffect(() => {
78
+ if (isLoadingMore) {
79
+ handleFetchSuggestions(null, suggestions.length, postTypeToSearch).then(_suggestions => {
80
+ if (0 < _suggestions.length) {
81
+ setSuggestions(suggestions.concat(_suggestions));
82
+ }
83
+ }).finally(() => setIsLoadingMore(false));
84
+ }
85
+ }, [isLoadingMore]);
86
+
87
+ /**
88
+ * If passed a `fetchSavedPosts` prop, use that, otherwise, build it based on the selected post type.
89
+ */
90
+ const handleFetchSaved = fetchSavedPosts ? fetchSavedPosts : async (postIds = [], searchSlug = null) => {
91
+ const postTypeSlug = searchSlug || postTypeToSearch;
92
+ const endpoint = 'post' === postTypeSlug || 'page' === postTypeSlug ? postTypeSlug + 's' // Default post type endpoints are plural.
93
+ : postTypeSlug; // Custom post type endpoints are singular.
94
+ const posts = await apiFetch({
95
+ path: addQueryArgs('/wp/v2/' + endpoint, {
96
+ per_page: 100,
97
+ include: postIds.join(','),
98
+ _fields: 'id,title'
99
+ })
100
+ });
101
+ return posts.map(post => ({
102
+ value: parseInt(post.id),
103
+ label: decodeEntities(post.title) || __('(no title)', 'newspack-plugin')
104
+ }));
105
+ };
106
+
107
+ /**
108
+ * If passed a `fetchSuggestions` prop, use that, otherwise, build it based on the selected post type.
109
+ */
110
+ const handleFetchSuggestions = fetchSuggestions ? fetchSuggestions : async (search = null, offset = 0, searchSlug = null) => {
111
+ const postTypeSlug = searchSlug || postTypeToSearch;
112
+ const endpoint = 'post' === postTypeSlug || 'page' === postTypeSlug ? postTypeSlug + 's' // Default post type endpoints are plural.
113
+ : postTypeSlug; // Custom post type endpoints are singular.
114
+ const response = await apiFetch({
115
+ parse: false,
116
+ path: addQueryArgs('/wp/v2/' + endpoint, {
117
+ search,
118
+ offset,
119
+ per_page: suggestionsToFetch,
120
+ _fields: 'id,title'
121
+ })
122
+ });
123
+ const total = parseInt(response.headers.get('x-wp-total') || 0);
124
+ const posts = await response.json();
125
+ setMaxSuggestions(total);
126
+
127
+ // Format suggestions for FormTokenField display.
128
+ return posts.reduce((acc, post) => {
129
+ acc.push({
130
+ value: parseInt(post.id),
131
+ label: decodeEntities(post?.title.rendered) || __('(no title)', 'newspack-plugin')
132
+ });
133
+ return acc;
134
+ }, []);
135
+ };
136
+
137
+ /**
138
+ * Intercept onChange callback so we can decide whether to allow multiple selections.
139
+ */
140
+ const handleOnChange = _selections => {
141
+ // If only allowing one selection, just return the one selected item.
142
+ if (!multiSelect) {
143
+ return onChange(_selections);
144
+ }
145
+
146
+ // Handle legacy `selectedPost` prop.
147
+ const selections = selectedPost ? [...selectedItems, selectedPost] : [...selectedItems];
148
+
149
+ // Loop through new selections to determine whether to add or remove them.
150
+ _selections.forEach(_selection => {
151
+ const existingSelection = selections.findIndex(selection => parseInt(selection.value) === parseInt(_selection.value));
152
+ if (-1 < existingSelection) {
153
+ // If the selection is already selected, remove it.
154
+ selections.splice(existingSelection, 1);
155
+ } else {
156
+ // Otherwise, add it.
157
+ selections.push(_selection);
158
+ }
159
+ });
160
+
161
+ // Include currently selected post type in selection results.
162
+ onChange(selections.map(selection => ({
163
+ ...selection,
164
+ postType: postTypeToSearch
165
+ })));
166
+ };
167
+
168
+ /**
169
+ * Render selected item(s) for this component. Clicking on a selection deselects it.
170
+ */
171
+ const renderSelections = () => {
172
+ // Handle legacy `selectedPost` prop.
173
+ const selections = selectedPost ? [...selectedItems, selectedPost] : selectedItems;
174
+ const selectedMessage = multiSelect ? sprintf(
175
+ // Translators: %1: the length of selections. %2: the selection leabel.
176
+ __('%1$s %2$s selected', 'newspack-plugin'), selections.length, selections.length > 1 ? postTypeLabelPlural : postTypeLabel) : sprintf(
177
+ // Translators: %s: The label for the selection.
178
+ __('Selected %s', 'newspack-plugin'), postTypeLabel);
179
+ return /*#__PURE__*/_jsxs("div", {
180
+ className: "newspack-autocomplete-with-suggestions__selected-items",
181
+ children: [/*#__PURE__*/_jsxs("p", {
182
+ className: "newspack-autocomplete-with-suggestions__label",
183
+ children: [selectedMessage, 1 < selections.length && _x(' – ', 'separator character', 'newspack-plugin'), 1 < selections.length && /*#__PURE__*/_jsx(Button, {
184
+ onClick: () => onChange([]),
185
+ isLink: true,
186
+ isDestructive: true,
187
+ children: __('Clear all', 'newspack-plugin')
188
+ })]
189
+ }), selections.map(selection => /*#__PURE__*/_jsx(Button, {
190
+ className: "newspack-autocomplete-with-suggestions__selected-item-button",
191
+ isTertiary: true,
192
+ onClick: () => onChange(selections.filter(_selection => _selection.value !== selection.value)),
193
+ children: selection.label
194
+ }, selection.value))]
195
+ });
196
+ };
197
+
198
+ /**
199
+ * Render post type select dropdown.
200
+ */
201
+ const renderPostTypeSelect = () => {
202
+ return /*#__PURE__*/_jsx(SelectControl, {
203
+ label: sprintf(
204
+ // Translators: %s: The name of the type.
205
+ __('%s type', 'newspack-plugin'), postTypeLabel.charAt(0).toUpperCase() + postTypeLabel.slice(1)),
206
+ value: postTypeToSearch,
207
+ options: postTypes.map(postTypeOption => ({
208
+ label: postTypeOption.label,
209
+ value: postTypeOption.slug
210
+ })),
211
+ onChange: _postType => setPostTypeToSearch(_postType)
212
+ });
213
+ };
214
+
215
+ /**
216
+ * Render a single suggestion object that can be clicked to select it immediately.
217
+ *
218
+ * @param {Object} suggestion Suggestion object with value and label keys.
219
+ */
220
+ const renderSuggestion = suggestion => {
221
+ if (multiSelect) {
222
+ const selections = selectedPost ? [...selectedItems, selectedPost] : [...selectedItems];
223
+ const isSelected = !!selections.find(_selection => parseInt(_selection.value) === parseInt(suggestion.value) && _selection.label === suggestion.label);
224
+ return /*#__PURE__*/_jsx(CheckboxControl, {
225
+ checked: isSelected,
226
+ onChange: () => handleOnChange([suggestion]),
227
+ label: suggestion.label
228
+ }, suggestion.value);
229
+ }
230
+ return /*#__PURE__*/_jsx(Button, {
231
+ isLink: true,
232
+ onClick: () => handleOnChange([suggestion]),
233
+ children: suggestion.label
234
+ }, suggestion.value);
235
+ };
236
+
237
+ /**
238
+ * Render a list of suggestions that can be clicked to select instead of searching by title.
239
+ */
240
+ const renderSuggestions = () => {
241
+ if (0 === suggestions.length) {
242
+ return null;
243
+ }
244
+ const className = multiSelect ? 'newspack-autocomplete-with-suggestions__search-suggestions-multiselect' : 'newspack-autocomplete-with-suggestions__search-suggestions';
245
+ return /*#__PURE__*/_jsxs(_Fragment, {
246
+ children: [!hideHelp && /*#__PURE__*/_jsx("p", {
247
+ className: "newspack-autocomplete-with-suggestions__label",
248
+ children: /* Translators: %s: the name of a post type. */sprintf(__('Or, select a recent %s:', 'newspack-plugin'), postTypeLabel)
249
+ }), /*#__PURE__*/_jsxs("div", {
250
+ className: className,
251
+ children: [suggestions.map(renderSuggestion), suggestions.length < (maxItemsToSuggest || maxSuggestions) && /*#__PURE__*/_jsx(Button, {
252
+ disabled: isLoadingMore,
253
+ isSecondary: true,
254
+ onClick: () => setIsLoadingMore(true),
255
+ children: isLoadingMore ? __('Loading…', 'newspack-plugin') : __('Load more', 'newspack-plugin')
256
+ })]
257
+ })]
258
+ });
259
+ };
260
+ return /*#__PURE__*/_jsxs("div", {
261
+ className: classNames.join(' '),
262
+ children: [0 < selectedItems.length && renderSelections(), 1 < postTypes.length && renderPostTypeSelect(), /*#__PURE__*/_jsx(AutocompleteTokenField, {
263
+ tokens: [],
264
+ onChange: handleOnChange,
265
+ fetchSuggestions: async search => handleFetchSuggestions(search, 0, postTypeToSearch),
266
+ fetchSavedInfo: postIds => handleFetchSaved(postIds),
267
+ label: label,
268
+ loading: isLoading,
269
+ help: !hideHelp && help,
270
+ returnFullObjects: true
271
+ }), renderSuggestions()]
272
+ });
273
+ };
274
+ export default AutocompleteWithSuggestions;
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Styles for AutocompleteWithSuggestions component.
3
+ */
4
+
5
+ @use "~@wordpress/base-styles/colors" as wp-colors;
6
+
7
+ .newspack-autocomplete-with-suggestions {
8
+ width: 100%;
9
+
10
+ &.hide-help {
11
+ > .components-base-control,
12
+ > .newspack-autocomplete-tokenfield,
13
+ > .newspack-autocomplete-with-suggestions__search-suggestions,
14
+ > .newspack-autocomplete-with-suggestions__search-suggestions-multiselect {
15
+ margin-top: 16px;
16
+ }
17
+ }
18
+ .components-select-control__input {
19
+ max-width: none;
20
+ }
21
+
22
+ .components-input-control__backdrop {
23
+ border-color: wp-colors.$gray-700 !important;
24
+ }
25
+
26
+ .newspack-autocomplete-with-suggestions__label,
27
+ .components-form-token-field__label,
28
+ .components-input-control__label,
29
+ .components-checkbox-control__label {
30
+ color: wp-colors.$gray-900 !important;
31
+ }
32
+
33
+ &__suggestions-spinner {
34
+ background: wp-colors.$gray-100;
35
+ border: 1px solid wp-colors.$gray-700;
36
+ border-radius: 2px;
37
+ cursor: progress;
38
+ margin: 16px 0;
39
+ padding: 32px;
40
+
41
+ .components-spinner {
42
+ display: block;
43
+ margin: 0 auto;
44
+ position: relative;
45
+ right: auto;
46
+ top: 0;
47
+ }
48
+ }
49
+
50
+ .newspack-autocomplete-tokenfield__help {
51
+ font-size: inherit;
52
+ margin-bottom: 8px !important;
53
+ }
54
+
55
+ .components-form-token-field {
56
+ &__input-container {
57
+ border-color: wp-colors.$gray-700;
58
+ }
59
+
60
+ &__suggestions-list {
61
+ border-top-color: wp-colors.$gray-700;
62
+ margin: 4px -4px -2px;
63
+ }
64
+
65
+ &__suggestion {
66
+ padding: 8px;
67
+ }
68
+ }
69
+
70
+ p.newspack-autocomplete-with-suggestions__label {
71
+ font-size: inherit;
72
+ margin: 0 0 8px;
73
+ }
74
+
75
+ &__selected-items {
76
+ border-bottom: 1px solid wp-colors.$gray-700;
77
+ display: flex;
78
+ flex-wrap: wrap;
79
+ gap: 8px;
80
+ margin-bottom: 16px;
81
+ padding-bottom: 12px;
82
+
83
+ p.newspack-autocomplete-with-suggestions__label {
84
+ flex: 0 0 100%;
85
+ margin: 0;
86
+ }
87
+ }
88
+
89
+ &__selected-item-button.components-button.is-tertiary {
90
+ background-color: wp-colors.$gray-300;
91
+ border-radius: 2px;
92
+ color: wp-colors.$gray-900;
93
+ font-size: 13px;
94
+ height: auto;
95
+ margin: 0;
96
+ padding-left: 8px;
97
+ padding-right: 8px;
98
+
99
+ &::after {
100
+ content: "✕";
101
+ margin-left: 8px;
102
+ }
103
+
104
+ &:hover {
105
+ box-shadow: none;
106
+ color: black;
107
+ }
108
+ }
109
+
110
+ &__search-suggestions,
111
+ &__search-suggestions-multiselect {
112
+ border-radius: 2px;
113
+ border: 1px solid wp-colors.$gray-700;
114
+ max-height: 178px;
115
+ overflow: visible auto;
116
+ padding: 8px 8px 0;
117
+
118
+ .components-button {
119
+ display: block;
120
+ line-height: normal;
121
+ margin: auto;
122
+
123
+ &.is-link {
124
+ color: wp-colors.$gray-900;
125
+ padding: 8px;
126
+ text-decoration: none;
127
+ width: 100%;
128
+
129
+ &:active:not(:disabled),
130
+ &:hover:not(:disabled) {
131
+ background: wp-colors.$gray-900;
132
+ background: var(--wp-admin-theme-color);
133
+ color: white;
134
+ }
135
+ }
136
+ &.is-secondary {
137
+ margin-bottom: 8px;
138
+ margin-top: 8px;
139
+ width: 100%;
140
+ }
141
+ }
142
+ }
143
+
144
+ &__search-suggestions {
145
+ border-radius: 2px;
146
+ border: 1px solid wp-colors.$gray-700;
147
+ padding: 0;
148
+
149
+ .components-button {
150
+ line-height: normal;
151
+
152
+ &:focus:not(:disabled) {
153
+ border-radius: 0;
154
+ box-shadow: inset 0 0 0 1.5px wp-colors.$gray-900;
155
+ box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);
156
+ }
157
+
158
+ &.is-secondary {
159
+ width: calc(100% - 16px);
160
+ }
161
+ }
162
+ }
163
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Button
3
+ */
4
+
5
+ /**
6
+ * WordPress dependencies.
7
+ */
8
+ import { Button as BaseComponent } from '@wordpress/components';
9
+ import { useState } from '@wordpress/element';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import Router from '../proxied-imports/router';
15
+ import './style.scss';
16
+ import { jsx as _jsx } from "react/jsx-runtime";
17
+ const {
18
+ useHistory
19
+ } = Router;
20
+ const Button = ({
21
+ href,
22
+ onClick,
23
+ ...otherProps
24
+ }) => {
25
+ const history = useHistory();
26
+ const [isAwaitingOnClick, setIsAwaitingOnClick] = useState(false);
27
+
28
+ // If both onClick and href are present, await the onClick action an then redirect.
29
+ if (href && onClick) {
30
+ otherProps.onClick = async () => {
31
+ setIsAwaitingOnClick(true);
32
+ await onClick();
33
+ setIsAwaitingOnClick(false);
34
+ history.push((href || '').replace('#', ''));
35
+ };
36
+ } else {
37
+ otherProps.href = href;
38
+ otherProps.onClick = onClick;
39
+ }
40
+ if (isAwaitingOnClick) {
41
+ otherProps.disabled = true;
42
+ }
43
+ // @ts-ignore - @wordpress/components' Button can only have either href or onClick, not both.
44
+ return /*#__PURE__*/_jsx(BaseComponent, {
45
+ ...otherProps
46
+ });
47
+ };
48
+ export default Button;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Button
3
+ */
4
+
5
+ .components-button {
6
+ &.is-destructive {
7
+ &:hover:not(:disabled) {
8
+ box-shadow: none;
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Button Card
3
+ */
4
+
5
+ /**
6
+ * WordPress dependencies
7
+ */
8
+ import { Component } from '@wordpress/element';
9
+ import { Icon, chevronRight } from '@wordpress/icons';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import { Grid } from '../';
15
+ import './style.scss';
16
+
17
+ /**
18
+ * External dependencies
19
+ */
20
+ import classnames from 'classnames';
21
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
22
+ class ButtonCard extends Component {
23
+ /**
24
+ * Render
25
+ */
26
+ render() {
27
+ const {
28
+ chevron,
29
+ className,
30
+ desc,
31
+ grouped,
32
+ icon,
33
+ isDestructive,
34
+ isPressed,
35
+ isSmall,
36
+ title,
37
+ ...otherProps
38
+ } = this.props;
39
+ const classes = classnames('newspack-button-card', className, chevron && 'has-chevron', grouped && 'grouped', icon && 'has-icon', isDestructive && 'is-destructive', isPressed && 'is-pressed', isSmall && 'is-small');
40
+ return /*#__PURE__*/_jsxs("a", {
41
+ className: classes,
42
+ ...otherProps,
43
+ children: [icon && /*#__PURE__*/_jsx(Icon, {
44
+ icon: icon,
45
+ height: 48,
46
+ width: 48
47
+ }), /*#__PURE__*/_jsxs(Grid, {
48
+ noMargin: true,
49
+ columns: 1,
50
+ gutter: 8,
51
+ children: [title && /*#__PURE__*/_jsx("h3", {
52
+ children: title
53
+ }), desc && /*#__PURE__*/_jsx("p", {
54
+ children: desc
55
+ })]
56
+ }), chevron && /*#__PURE__*/_jsx(Icon, {
57
+ icon: chevronRight,
58
+ height: 24,
59
+ width: 24
60
+ })]
61
+ });
62
+ }
63
+ }
64
+ export default ButtonCard;