newspack-components 4.2.0 → 4.4.0-epic-editor-refactor.1

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 (315) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/DEVELOPMENT.md +868 -0
  3. package/copy-styles.js +3 -2
  4. package/dist/cjs/accordion/index.js +17 -12
  5. package/dist/cjs/action-card/index.js +186 -140
  6. package/dist/cjs/action-card/style.scss +25 -2
  7. package/dist/cjs/autocomplete-tokenfield/index.js +254 -234
  8. package/dist/cjs/autocomplete-with-latest-posts/index.js +351 -0
  9. package/dist/cjs/autocomplete-with-latest-posts/style.scss +150 -0
  10. package/dist/cjs/autocomplete-with-suggestions/index.js +229 -126
  11. package/dist/cjs/badge/index.js +32 -0
  12. package/dist/cjs/badge/style.scss +36 -0
  13. package/dist/cjs/box-contrast/index.js +19 -15
  14. package/dist/cjs/button/index.js +41 -24
  15. package/dist/cjs/button-props/button-props.js +5 -5
  16. package/dist/cjs/button-props/index.js +2 -2
  17. package/dist/cjs/card/core-card.js +199 -0
  18. package/dist/cjs/card/index.js +75 -35
  19. package/dist/cjs/card/style-core.scss +295 -0
  20. package/dist/cjs/card/style.scss +4 -4
  21. package/dist/cjs/card-feature/index.js +141 -0
  22. package/dist/cjs/card-feature/style.scss +75 -0
  23. package/dist/cjs/card-form/index.js +144 -0
  24. package/dist/cjs/card-form/style.scss +20 -0
  25. package/dist/cjs/card-settings-group/index.js +69 -0
  26. package/dist/cjs/card-settings-group/style.scss +33 -0
  27. package/dist/cjs/card-sortable-list/index.js +426 -0
  28. package/dist/cjs/card-sortable-list/style.scss +48 -0
  29. package/dist/cjs/category-autocomplete/index.js +175 -155
  30. package/dist/cjs/color-picker/index.js +41 -23
  31. package/dist/cjs/color-picker/index.test.js +82 -0
  32. package/dist/cjs/color-picker/style.scss +11 -13
  33. package/dist/cjs/confirm-dialog/index.js +123 -0
  34. package/dist/cjs/consts.js +1 -1
  35. package/dist/cjs/custom-select-control/index.js +39 -28
  36. package/dist/cjs/dataviews/index.js +38 -0
  37. package/dist/cjs/dataviews/style.scss +9 -0
  38. package/dist/cjs/divider/index.js +47 -0
  39. package/dist/cjs/divider/style.scss +51 -0
  40. package/dist/cjs/footer/index.js +36 -30
  41. package/dist/cjs/form-token-field/index.js +51 -40
  42. package/dist/cjs/form-token-field/style.scss +0 -5
  43. package/dist/cjs/global-notices/index.js +5 -5
  44. package/dist/cjs/grid/index.js +25 -18
  45. package/dist/cjs/grid/style.scss +67 -8
  46. package/dist/cjs/handoff/index.js +204 -156
  47. package/dist/cjs/handoff-message/index.js +12 -7
  48. package/dist/cjs/hooks/index.js +10 -8
  49. package/dist/cjs/hooks/use-confirm-dialog.js +79 -0
  50. package/dist/cjs/hooks/use-unsaved-changes-dialog.js +144 -0
  51. package/dist/cjs/hooks/useObjectState.js +21 -12
  52. package/dist/cjs/hooks/useObjectState.test.js +55 -33
  53. package/dist/cjs/hooks/useOnClickOutside.js +6 -6
  54. package/dist/cjs/hooks/usePrompt.js +10 -13
  55. package/dist/cjs/image-upload/index.js +114 -123
  56. package/dist/cjs/image-upload/index.test.js +11 -13
  57. package/dist/cjs/image-upload/style.scss +12 -40
  58. package/dist/cjs/index.js +166 -92
  59. package/dist/cjs/info-button/index.js +43 -32
  60. package/dist/cjs/integration-icons/active-campaign.js +22 -0
  61. package/dist/cjs/integration-icons/constant-contact.js +28 -0
  62. package/dist/cjs/integration-icons/fundraise-up.js +25 -0
  63. package/dist/cjs/integration-icons/index.js +48 -0
  64. package/dist/cjs/integration-icons/mailchimp.js +19 -0
  65. package/dist/cjs/integration-icons/salesforce.js +17 -0
  66. package/dist/cjs/integration-icons/wisepops.js +20 -0
  67. package/dist/cjs/modal/index.js +29 -19
  68. package/dist/cjs/modal/style.scss +47 -13
  69. package/dist/cjs/newspack-icon/index.js +45 -38
  70. package/dist/cjs/newspack-icon/style.scss +5 -3
  71. package/dist/cjs/notice/index.js +64 -55
  72. package/dist/cjs/plugin-installer/index.js +259 -253
  73. package/dist/cjs/plugin-settings/SettingsSection.js +66 -60
  74. package/dist/cjs/plugin-settings/index.js +217 -200
  75. package/dist/cjs/plugin-toggle/index.js +192 -199
  76. package/dist/cjs/popover/index.js +41 -28
  77. package/dist/cjs/position-control/index.js +24 -23
  78. package/dist/cjs/progress-bar/index.js +71 -59
  79. package/dist/cjs/progress-bar/index.test.js +62 -68
  80. package/dist/cjs/proxied-imports/router.js +3 -3
  81. package/dist/cjs/radio-control/index.js +40 -27
  82. package/dist/cjs/section-header/index.js +143 -39
  83. package/dist/cjs/section-header/style.scss +129 -4
  84. package/dist/cjs/select-control/ButtonGroupControl.js +26 -21
  85. package/dist/cjs/select-control/GroupedSelectControl.js +41 -35
  86. package/dist/cjs/select-control/index.js +52 -40
  87. package/dist/cjs/settings/MinMaxSetting.js +30 -20
  88. package/dist/cjs/settings/SettingsCard.js +20 -17
  89. package/dist/cjs/settings/SettingsSection.js +22 -21
  90. package/dist/cjs/settings/index.js +5 -5
  91. package/dist/cjs/sortable-newsletter-list-control/index.js +78 -57
  92. package/dist/cjs/steps-list/index.js +44 -34
  93. package/dist/cjs/steps-list-item/index.js +47 -39
  94. package/dist/cjs/style-card/index.js +70 -63
  95. package/dist/cjs/style.scss +2 -0
  96. package/dist/cjs/tabbed-navigation/index.js +38 -36
  97. package/dist/cjs/text-control/index.js +27 -25
  98. package/dist/cjs/utils/color.js +4 -4
  99. package/dist/cjs/utils/editor-toolbar-back-button.js +61 -0
  100. package/dist/cjs/utils/index.js +31 -19
  101. package/dist/cjs/utils/style.scss +8 -0
  102. package/dist/cjs/waiting/index.js +48 -35
  103. package/dist/cjs/waiting/style.scss +4 -0
  104. package/dist/cjs/web-preview/index.js +219 -154
  105. package/dist/cjs/web-preview/style.scss +20 -53
  106. package/dist/cjs/with-wizard/index.js +362 -364
  107. package/dist/cjs/with-wizard/style.scss +28 -60
  108. package/dist/cjs/with-wizard-screen/index.js +54 -58
  109. package/dist/cjs/with-wizard-screen/style.scss +77 -10
  110. package/dist/cjs/wizard/components/WizardError.js +20 -18
  111. package/dist/cjs/wizard/components/WizardSnackbar.js +73 -0
  112. package/dist/cjs/wizard/components/style.scss +48 -0
  113. package/dist/cjs/wizard/index.js +207 -64
  114. package/dist/cjs/wizard/store/index.js +191 -97
  115. package/dist/cjs/wizard/store/utils.js +13 -8
  116. package/dist/esm/accordion/index.js +14 -9
  117. package/dist/esm/action-card/index.js +178 -124
  118. package/dist/esm/action-card/style.scss +25 -2
  119. package/dist/esm/autocomplete-tokenfield/index.js +248 -222
  120. package/dist/esm/autocomplete-with-latest-posts/index.js +347 -0
  121. package/dist/esm/autocomplete-with-latest-posts/style.scss +150 -0
  122. package/dist/esm/autocomplete-with-suggestions/index.js +226 -119
  123. package/dist/esm/badge/index.js +24 -0
  124. package/dist/esm/badge/style.scss +36 -0
  125. package/dist/esm/box-contrast/index.js +15 -11
  126. package/dist/esm/button/index.js +39 -19
  127. package/dist/esm/button-props/button-props.js +1 -1
  128. package/dist/esm/card/core-card.js +195 -0
  129. package/dist/esm/card/index.js +70 -22
  130. package/dist/esm/card/style-core.scss +295 -0
  131. package/dist/esm/card/style.scss +4 -4
  132. package/dist/esm/card-feature/index.js +131 -0
  133. package/dist/esm/card-feature/style.scss +75 -0
  134. package/dist/esm/card-form/index.js +134 -0
  135. package/dist/esm/card-form/style.scss +20 -0
  136. package/dist/esm/card-settings-group/index.js +61 -0
  137. package/dist/esm/card-settings-group/style.scss +33 -0
  138. package/dist/esm/card-sortable-list/index.js +425 -0
  139. package/dist/esm/card-sortable-list/style.scss +48 -0
  140. package/dist/esm/category-autocomplete/index.js +168 -140
  141. package/dist/esm/color-picker/index.js +39 -21
  142. package/dist/esm/color-picker/index.test.js +78 -0
  143. package/dist/esm/color-picker/style.scss +11 -13
  144. package/dist/esm/confirm-dialog/index.js +116 -0
  145. package/dist/esm/consts.js +1 -1
  146. package/dist/esm/custom-select-control/index.js +34 -15
  147. package/dist/esm/dataviews/index.js +34 -0
  148. package/dist/esm/dataviews/style.scss +9 -0
  149. package/dist/esm/divider/index.js +42 -0
  150. package/dist/esm/divider/style.scss +51 -0
  151. package/dist/esm/footer/index.js +34 -28
  152. package/dist/esm/form-token-field/index.js +46 -27
  153. package/dist/esm/form-token-field/style.scss +0 -5
  154. package/dist/esm/global-notices/index.js +3 -3
  155. package/dist/esm/grid/index.js +23 -13
  156. package/dist/esm/grid/style.scss +67 -8
  157. package/dist/esm/handoff/index.js +199 -143
  158. package/dist/esm/handoff-message/index.js +10 -6
  159. package/dist/esm/hooks/index.js +8 -6
  160. package/dist/esm/hooks/use-confirm-dialog.js +73 -0
  161. package/dist/esm/hooks/use-unsaved-changes-dialog.js +136 -0
  162. package/dist/esm/hooks/useObjectState.js +19 -9
  163. package/dist/esm/hooks/useObjectState.test.js +54 -32
  164. package/dist/esm/hooks/useOnClickOutside.js +4 -4
  165. package/dist/esm/hooks/usePrompt.js +9 -11
  166. package/dist/esm/image-upload/index.js +110 -111
  167. package/dist/esm/image-upload/index.test.js +11 -13
  168. package/dist/esm/image-upload/style.scss +12 -40
  169. package/dist/esm/index.js +13 -1
  170. package/dist/esm/info-button/index.js +38 -19
  171. package/dist/esm/integration-icons/active-campaign.js +16 -0
  172. package/dist/esm/integration-icons/constant-contact.js +22 -0
  173. package/dist/esm/integration-icons/fundraise-up.js +19 -0
  174. package/dist/esm/integration-icons/index.js +6 -0
  175. package/dist/esm/integration-icons/mailchimp.js +13 -0
  176. package/dist/esm/integration-icons/salesforce.js +11 -0
  177. package/dist/esm/integration-icons/wisepops.js +14 -0
  178. package/dist/esm/modal/index.js +27 -13
  179. package/dist/esm/modal/style.scss +47 -13
  180. package/dist/esm/newspack-icon/index.js +40 -25
  181. package/dist/esm/newspack-icon/style.scss +5 -3
  182. package/dist/esm/notice/index.js +59 -42
  183. package/dist/esm/plugin-installer/index.js +255 -243
  184. package/dist/esm/plugin-settings/SettingsSection.js +61 -50
  185. package/dist/esm/plugin-settings/index.js +212 -189
  186. package/dist/esm/plugin-toggle/index.js +187 -190
  187. package/dist/esm/popover/index.js +35 -16
  188. package/dist/esm/position-control/index.js +22 -18
  189. package/dist/esm/progress-bar/index.js +66 -49
  190. package/dist/esm/progress-bar/index.test.js +61 -67
  191. package/dist/esm/radio-control/index.js +34 -15
  192. package/dist/esm/section-header/index.js +140 -38
  193. package/dist/esm/section-header/style.scss +129 -4
  194. package/dist/esm/select-control/ButtonGroupControl.js +23 -18
  195. package/dist/esm/select-control/GroupedSelectControl.js +38 -30
  196. package/dist/esm/select-control/index.js +47 -27
  197. package/dist/esm/settings/MinMaxSetting.js +27 -16
  198. package/dist/esm/settings/SettingsCard.js +18 -13
  199. package/dist/esm/settings/SettingsSection.js +20 -19
  200. package/dist/esm/settings/index.js +3 -3
  201. package/dist/esm/sortable-newsletter-list-control/index.js +74 -53
  202. package/dist/esm/steps-list/index.js +39 -21
  203. package/dist/esm/steps-list-item/index.js +42 -26
  204. package/dist/esm/style-card/index.js +65 -50
  205. package/dist/esm/style.scss +2 -0
  206. package/dist/esm/tabbed-navigation/index.js +35 -33
  207. package/dist/esm/text-control/index.js +25 -19
  208. package/dist/esm/utils/color.js +4 -4
  209. package/dist/esm/utils/editor-toolbar-back-button.js +53 -0
  210. package/dist/esm/utils/index.js +29 -16
  211. package/dist/esm/utils/style.scss +8 -0
  212. package/dist/esm/waiting/index.js +43 -22
  213. package/dist/esm/waiting/style.scss +4 -0
  214. package/dist/esm/web-preview/index.js +218 -147
  215. package/dist/esm/web-preview/style.scss +20 -53
  216. package/dist/esm/with-wizard/index.js +360 -358
  217. package/dist/esm/with-wizard/style.scss +28 -60
  218. package/dist/esm/with-wizard-screen/index.js +48 -46
  219. package/dist/esm/with-wizard-screen/style.scss +77 -10
  220. package/dist/esm/wizard/components/WizardError.js +18 -16
  221. package/dist/esm/wizard/components/WizardSnackbar.js +70 -0
  222. package/dist/esm/wizard/components/style.scss +48 -0
  223. package/dist/esm/wizard/index.js +207 -58
  224. package/dist/esm/wizard/store/index.js +189 -90
  225. package/dist/esm/wizard/store/utils.js +13 -6
  226. package/package.json +30 -17
  227. package/release.config.js +1 -0
  228. package/shared/author-context.js +26 -0
  229. package/shared/hooks/use-coauthors.js +388 -0
  230. package/shared/hooks/use-coauthors.test.js +850 -0
  231. package/shared/hooks/use-custom-byline.js +47 -0
  232. package/shared/hooks/use-custom-byline.test.js +133 -0
  233. package/shared/scss/_mixins.scss +54 -0
  234. package/src/accordion/index.js +8 -7
  235. package/src/action-card/action-card.d.ts +7 -3
  236. package/src/action-card/index.js +39 -12
  237. package/src/action-card/style.scss +25 -2
  238. package/src/autocomplete-tokenfield/index.js +2 -2
  239. package/src/autocomplete-with-latest-posts/index.js +290 -0
  240. package/src/autocomplete-with-latest-posts/style.scss +150 -0
  241. package/src/badge/index.tsx +26 -0
  242. package/src/badge/style.scss +36 -0
  243. package/src/button/index.tsx +3 -2
  244. package/src/card/core-card.js +224 -0
  245. package/src/card/index.js +40 -1
  246. package/src/card/style-core.scss +295 -0
  247. package/src/card/style.scss +4 -4
  248. package/src/card-feature/README.md +188 -0
  249. package/src/card-feature/index.tsx +186 -0
  250. package/src/card-feature/style.scss +75 -0
  251. package/src/card-form/README.md +113 -0
  252. package/src/card-form/index.tsx +128 -0
  253. package/src/card-form/style.scss +20 -0
  254. package/src/card-settings-group/README.md +43 -0
  255. package/src/card-settings-group/index.tsx +78 -0
  256. package/src/card-settings-group/style.scss +33 -0
  257. package/src/card-sortable-list/README.md +51 -0
  258. package/src/card-sortable-list/index.tsx +435 -0
  259. package/src/card-sortable-list/style.scss +48 -0
  260. package/src/color-picker/index.js +15 -8
  261. package/src/color-picker/index.test.js +52 -0
  262. package/src/color-picker/style.scss +11 -13
  263. package/src/confirm-dialog/README.md +128 -0
  264. package/src/confirm-dialog/index.tsx +132 -0
  265. package/src/dataviews/index.tsx +34 -0
  266. package/src/dataviews/style.scss +9 -0
  267. package/src/divider/README.md +25 -0
  268. package/src/divider/index.js +31 -0
  269. package/src/divider/style.scss +51 -0
  270. package/src/form-token-field/style.scss +0 -5
  271. package/src/grid/index.tsx +2 -1
  272. package/src/grid/style.scss +67 -8
  273. package/src/handoff/README.md +93 -0
  274. package/src/handoff/index.js +46 -8
  275. package/src/hooks/index.ts +2 -1
  276. package/src/hooks/use-confirm-dialog.tsx +80 -0
  277. package/src/hooks/use-unsaved-changes-dialog.tsx +140 -0
  278. package/src/image-upload/index.js +10 -15
  279. package/src/image-upload/style.scss +12 -40
  280. package/src/index.js +13 -1
  281. package/src/integration-icons/active-campaign.js +14 -0
  282. package/src/integration-icons/constant-contact.js +22 -0
  283. package/src/integration-icons/fundraise-up.js +18 -0
  284. package/src/integration-icons/index.js +6 -0
  285. package/src/integration-icons/mailchimp.js +10 -0
  286. package/src/integration-icons/salesforce.js +10 -0
  287. package/src/integration-icons/wisepops.js +14 -0
  288. package/src/modal/index.js +12 -3
  289. package/src/modal/style.scss +47 -13
  290. package/src/newspack-icon/style.scss +5 -3
  291. package/src/plugin-installer/index.js +30 -19
  292. package/src/section-header/index.js +105 -14
  293. package/src/section-header/style.scss +129 -4
  294. package/src/select-control/index.js +4 -0
  295. package/src/style.scss +2 -0
  296. package/src/utils/editor-toolbar-back-button.tsx +48 -0
  297. package/src/utils/index.js +6 -0
  298. package/src/utils/style.scss +8 -0
  299. package/src/waiting/index.js +2 -1
  300. package/src/waiting/style.scss +4 -0
  301. package/src/web-preview/index.js +90 -35
  302. package/src/web-preview/style.scss +20 -53
  303. package/src/with-wizard/index.js +1 -1
  304. package/src/with-wizard/style.scss +28 -60
  305. package/src/with-wizard-screen/style.scss +77 -10
  306. package/src/wizard/components/WizardSnackbar.js +58 -0
  307. package/src/wizard/components/style.scss +48 -0
  308. package/src/wizard/index.js +152 -21
  309. package/src/wizard/store/index.js +28 -2
  310. package/dist/cjs/button-card/index.js +0 -72
  311. package/dist/cjs/button-card/style.scss +0 -180
  312. package/dist/esm/button-card/index.js +0 -64
  313. package/dist/esm/button-card/style.scss +0 -180
  314. package/src/button-card/index.js +0 -52
  315. package/src/button-card/style.scss +0 -180
@@ -0,0 +1,48 @@
1
+ $collapse-duration: 200ms;
2
+ $shift-duration: 150ms;
3
+ $drop-duration: 400ms; /* must match DROP_ANIMATION_DURATION in index.tsx */
4
+
5
+ .newspack-card--core--sortable-list {
6
+ &__item {
7
+ cursor: grab;
8
+
9
+ /*
10
+ * Animate items sliding to fill/make space around the dragged item.
11
+ */
12
+ transition: transform $shift-duration ease;
13
+
14
+ /*
15
+ * Collapse the source item's content — the wrapper stays in layout as the
16
+ * anchor point, and the container height is locked via inline style in JS.
17
+ */
18
+ &.is-source {
19
+ cursor: grabbing;
20
+ opacity: 0;
21
+ transition: opacity $collapse-duration ease;
22
+ }
23
+
24
+ /*
25
+ * Drop landing animation: brief fade-in and scale on the newly placed item.
26
+ */
27
+ &.is-dropped {
28
+ animation: newspack-sortable-drop $drop-duration ease forwards;
29
+ opacity: 0;
30
+ }
31
+
32
+ }
33
+ }
34
+
35
+ @keyframes newspack-sortable-drop {
36
+ 0% {
37
+ opacity: 0;
38
+ transform: scale(0.98);
39
+ }
40
+ 60% {
41
+ opacity: 1;
42
+ transform: scale(1.01);
43
+ }
44
+ 100% {
45
+ opacity: 1;
46
+ transform: scale(1);
47
+ }
48
+ }
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * WordPress dependencies.
3
3
  */
4
- import { ColorPicker as ColorPickerComponent } from '@wordpress/components';
4
+ import { BaseControl, ColorPicker as ColorPickerComponent } from '@wordpress/components';
5
+ import { useInstanceId } from '@wordpress/compose';
5
6
  import { useState, useRef } from '@wordpress/element';
6
7
 
7
8
  /**
@@ -26,35 +27,41 @@ const { InteractiveDiv } = utils;
26
27
  *
27
28
  * @param {Object} props - Component props.
28
29
  * @param {JSX.Element|string} props.label - Label for the color picker.
30
+ * @param {JSX.Element|string} props.help - Help text for the color picker.
29
31
  * @param {string} [props.color] - Default color.
30
32
  * @param {Function} props.onChange - Function to call when the color changes.
31
33
  * @param {string} [props.className] - Additional class name.
32
34
  * @return {JSX.Element} ColorPicker component.
33
35
  */
34
- const ColorPicker = ( { label, color = '#fff', onChange, className } ) => {
36
+ const ColorPicker = ( { label, help, color = '#ffffff', onChange, className } ) => {
35
37
  const [ isExpanded, setIsExpanded ] = useState( false );
36
38
  const ref = useRef();
39
+ const id = useInstanceId( ColorPicker, 'newspack-color-picker' );
40
+ const labelId = `${ id }-label`;
37
41
  const colordColor = colord( color );
38
42
  hooks.useOnClickOutside( ref, () => setIsExpanded( false ) );
39
43
  return (
40
- <div className={ classnames( 'newspack-color-picker', className ) }>
41
- <div className="newspack-color-picker__label">{ label }</div>
42
-
44
+ <BaseControl id={ id } className={ classnames( 'newspack-color-picker', className ) } help={ help } __nextHasNoMarginBottom>
45
+ <BaseControl.VisualLabel id={ labelId }>{ label }</BaseControl.VisualLabel>
43
46
  <InteractiveDiv
47
+ id={ id }
48
+ aria-labelledby={ labelId }
49
+ aria-describedby={ help ? `${ id }__help` : undefined }
50
+ aria-expanded={ isExpanded }
44
51
  className={ 'newspack-color-picker__expander' }
45
52
  onClick={ () => setIsExpanded( ! isExpanded ) }
46
53
  style={ {
47
54
  backgroundColor: color,
48
- color: colordColor.contrast() > colordColor.contrast( '#000' ) ? '#fff' : '#000',
55
+ color: colordColor.contrast() > colordColor.contrast( '#000000' ) ? '#ffffff' : '#000000',
49
56
  } }
50
57
  >
51
58
  { color }
52
59
  </InteractiveDiv>
53
60
 
54
61
  <div className="newspack-color-picker__main" ref={ ref }>
55
- { isExpanded && <ColorPickerComponent color={ color } onChange={ hex => onChange( hex ) } enableAlpha={ false } /> }
62
+ { isExpanded && <ColorPickerComponent color={ color } onChange={ onChange } enableAlpha={ false } /> }
56
63
  </div>
57
- </div>
64
+ </BaseControl>
58
65
  );
59
66
  };
60
67
 
@@ -0,0 +1,52 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { render, fireEvent } from '@testing-library/react';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import ColorPicker from './';
10
+
11
+ describe( 'ColorPicker', () => {
12
+ it( 'should render the label', () => {
13
+ const { getByText } = render( <ColorPicker label="Background color" onChange={ () => {} } /> );
14
+ expect( getByText( 'Background color' ) ).toBeInTheDocument();
15
+ } );
16
+
17
+ it( 'should render help text', () => {
18
+ const { getByText } = render( <ColorPicker label="Background color" help="Choose a color" onChange={ () => {} } /> );
19
+ expect( getByText( 'Choose a color' ) ).toBeInTheDocument();
20
+ } );
21
+
22
+ it( 'should start collapsed', () => {
23
+ const { getByRole } = render( <ColorPicker label="Background color" onChange={ () => {} } /> );
24
+ expect( getByRole( 'button' ) ).toHaveAttribute( 'aria-expanded', 'false' );
25
+ } );
26
+
27
+ it( 'should expand when the expander is clicked', () => {
28
+ const { getByRole } = render( <ColorPicker label="Background color" onChange={ () => {} } /> );
29
+ const expander = getByRole( 'button' );
30
+ fireEvent.click( expander );
31
+ expect( expander ).toHaveAttribute( 'aria-expanded', 'true' );
32
+ } );
33
+
34
+ it( 'should associate label via aria-labelledby', () => {
35
+ const { getByRole, getByText } = render( <ColorPicker label="Background color" onChange={ () => {} } /> );
36
+ const expander = getByRole( 'button' );
37
+ const label = getByText( 'Background color' );
38
+ expect( expander.getAttribute( 'aria-labelledby' ) ).toBe( label.id );
39
+ } );
40
+
41
+ it( 'should associate help text via aria-describedby when help is provided', () => {
42
+ const { getByRole, getByText } = render( <ColorPicker label="Background color" help="Choose a color" onChange={ () => {} } /> );
43
+ const expander = getByRole( 'button' );
44
+ const help = getByText( 'Choose a color' );
45
+ expect( expander.getAttribute( 'aria-describedby' ) ).toBe( help.id );
46
+ } );
47
+
48
+ it( 'should not set aria-describedby when no help is provided', () => {
49
+ const { getByRole } = render( <ColorPicker label="Background color" onChange={ () => {} } /> );
50
+ expect( getByRole( 'button' ) ).not.toHaveAttribute( 'aria-describedby' );
51
+ } );
52
+ } );
@@ -2,31 +2,29 @@
2
2
  * Color Picker
3
3
  */
4
4
 
5
+ @use "~@wordpress/base-styles/colors" as wp-colors;
6
+ @use "~@wordpress/base-styles/variables" as wp-vars;
7
+
5
8
  .newspack-color-picker {
6
9
  position: relative;
7
-
8
- &__label {
9
- margin-bottom: 8px;
10
- }
11
-
12
10
  &__expander {
13
- box-shadow: inset 0 0 0 1px rgb(0 0 0 / 20%);
14
- border-radius: 2px;
15
- height: 64px;
16
- padding: 12px;
11
+ box-shadow: inset 0 0 0 1px rgba(wp-colors.$black, 0.54);
12
+ border-radius: wp-vars.$radius-small;
13
+ height: wp-vars.$grid-unit-80;
14
+ padding: wp-vars.$grid-unit-10;
17
15
  text-align: right;
18
16
  text-transform: uppercase;
19
17
 
20
18
  &:focus-visible {
21
19
  box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);
22
- border-color: white;
20
+ border-color: wp-colors.$white;
23
21
  }
24
22
  }
25
23
 
26
24
  &__main {
27
- background: white;
28
- box-shadow: 0 0 8px 4px rgba(black, 0.08);
29
- margin: 12px 0;
25
+ background: wp-colors.$white;
26
+ box-shadow: wp-vars.$elevation-small;
27
+ margin: wp-vars.$grid-unit-10 0 0;
30
28
  position: absolute;
31
29
  z-index: 2;
32
30
 
@@ -0,0 +1,128 @@
1
+ # ConfirmDialog
2
+
3
+ A modal confirmation dialog that intercepts client-side navigation when there are unsaved changes. Built on top of WordPress's `__experimentalConfirmDialog`. The dialog is invisible until a navigation attempt is blocked; when `when` becomes `true` and the user navigates away, the dialog appears automatically and resumes or cancels the navigation based on the user's choice.
4
+
5
+ For most imperative use cases (confirming a destructive action, guarding an action behind an unsaved-changes check) prefer the [`useConfirmDialog` hook](#useconfirmdialog-hook) over using `ConfirmDialog` directly.
6
+
7
+ ## Props
8
+
9
+ | Prop | Type | Default | Description |
10
+ |------|------|---------|-------------|
11
+ | `cancelButtonText` | `string` | — | Label for the cancel button. |
12
+ | `children` | `React.ReactNode` | — | Content rendered in the modal body. |
13
+ | `className` | `string` | — | Additional CSS class. |
14
+ | `confirmButtonText` | `string` | — | Label for the confirm button. |
15
+ | `hideTitle` | `boolean` | — | When `true`, hides the modal title and the × close button. |
16
+ | `isDestructive` | `boolean` | — | When `true`, applies destructive (e.g. red) styling to the confirm button. |
17
+ | `isOpen` | `boolean` | `false` | When `true`, shows the dialog immediately without blocking navigation. Use this for imperative confirmation (e.g. confirming a destructive action on a button click). |
18
+ | `size` | `'small'` \| `'medium'` \| `'large'` \| `'x-large'` \| `'full'` | `'small'` | Controls the width of the modal. |
19
+ | `title` | `string` | — | Title displayed in the modal header. |
20
+ | `when` | `boolean` | `false` | When `true`, blocks router navigation and shows the dialog on any attempted navigation. Set this to reflect whether the current form has unsaved changes. |
21
+
22
+ ## Usage
23
+
24
+ ```jsx
25
+ import { ConfirmDialog } from 'newspack-components';
26
+
27
+ // Guard navigation when a form has unsaved changes.
28
+ // The dialog appears automatically when the user tries to navigate away.
29
+ <ConfirmDialog
30
+ when={ hasUnsavedChanges }
31
+ title="Unsaved changes"
32
+ confirmButtonText="Leave anyway"
33
+ cancelButtonText="Stay"
34
+ isDestructive
35
+ >
36
+ You have unsaved changes. Are you sure you want to leave?
37
+ </ConfirmDialog>
38
+
39
+ // Larger modal with a custom size
40
+ <ConfirmDialog
41
+ when={ hasUnsavedChanges }
42
+ size="medium"
43
+ title="Discard changes?"
44
+ confirmButtonText="Discard"
45
+ cancelButtonText="Keep editing"
46
+ >
47
+ Your changes will be lost if you leave this page.
48
+ </ConfirmDialog>
49
+
50
+ // Guard both navigation and an imperative action with a single dialog instance.
51
+ // isOpen triggers the dialog immediately (e.g. on a button click) without
52
+ // blocking navigation. Both when and isOpen can be used together.
53
+ const [ pendingAction, setPendingAction ] = useState( null );
54
+ <ConfirmDialog
55
+ when={ hasUnsavedChanges }
56
+ isOpen={ !! pendingAction }
57
+ confirmButtonText="Discard changes"
58
+ isDestructive
59
+ hideTitle
60
+ onConfirm={ () => {
61
+ pendingAction?.();
62
+ setPendingAction( null );
63
+ } }
64
+ onCancel={ () => setPendingAction( null ) }
65
+ >
66
+ You have unsaved changes that will be lost. Discard changes?
67
+ </ConfirmDialog>
68
+ ```
69
+
70
+ ---
71
+
72
+ ## `useConfirmDialog` hook
73
+
74
+ A higher-level hook that wraps `ConfirmDialog` and manages the pending-action state for you. Prefer this over using `ConfirmDialog` directly when you need imperative confirmation.
75
+
76
+ ```jsx
77
+ import { useConfirmDialog } from 'newspack-components';
78
+ ```
79
+
80
+ ### Options
81
+
82
+ Accepts all `ConfirmDialog` props except `isOpen`, `onConfirm`, `onCancel`, and `children`, plus:
83
+
84
+ | Option | Type | Default | Description |
85
+ |--------|------|---------|-------------|
86
+ | `message` | `React.ReactNode` | — | Content rendered in the modal body. |
87
+ | `when` | `boolean` | — | When explicitly `false`, `requestConfirm` skips the dialog and invokes the callback immediately. Omit (or pass `true`) to always show the dialog. Also controls router navigation blocking, same as the `when` prop on `ConfirmDialog`. |
88
+
89
+ ### Returns
90
+
91
+ | Key | Type | Description |
92
+ |-----|------|-------------|
93
+ | `confirmDialog` | `React.ReactElement` | The dialog element — render this somewhere in your JSX. |
94
+ | `requestConfirm` | `( callback: () => void ) => void` | Call with a callback to request confirmation. Shows the dialog (unless `when` is `false`); invokes the callback only if the user confirms. |
95
+
96
+ ### Usage
97
+
98
+ ```jsx
99
+ // Always-confirm dialog (e.g. destructive delete action).
100
+ const { confirmDialog, requestConfirm } = useConfirmDialog( {
101
+ title: 'Are you sure?',
102
+ confirmButtonText: 'Delete',
103
+ isDestructive: true,
104
+ message: 'This will permanently delete the item and cannot be undone.',
105
+ } );
106
+
107
+ // Render the dialog element in JSX:
108
+ { confirmDialog }
109
+
110
+ // Call requestConfirm when the user initiates the action:
111
+ <Button onClick={ () => requestConfirm( handleDelete ) }>Delete</Button>
112
+
113
+ // Conditional dialog — skips confirmation when there are no unsaved changes.
114
+ // Combines navigation blocking with imperative use in a single instance.
115
+ const { confirmDialog, requestConfirm } = useConfirmDialog( {
116
+ when: isDirty,
117
+ message: 'You have unsaved changes that will be lost. Discard changes?',
118
+ confirmButtonText: 'Discard changes',
119
+ isDestructive: true,
120
+ hideTitle: true,
121
+ } );
122
+
123
+ { confirmDialog }
124
+
125
+ // In an action handler — shows the dialog only if isDirty, otherwise
126
+ // calls the callback immediately:
127
+ requestConfirm( () => handleToggleEnabled( newConfig ) );
128
+ ```
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Modal
3
+ */
4
+
5
+ /**
6
+ * WordPress dependencies.
7
+ */
8
+ import { __experimentalConfirmDialog as BaseComponent } from '@wordpress/components'; // eslint-disable-line @wordpress/no-unsafe-wp-apis
9
+ import { forwardRef, useCallback, useEffect, useRef, useState } from '@wordpress/element';
10
+
11
+ /**
12
+ * Internal dependencies.
13
+ */
14
+ import Router from '../proxied-imports/router';
15
+ const { useHistory } = Router;
16
+
17
+ /**
18
+ * External dependencies.
19
+ */
20
+ import classnames from 'classnames';
21
+
22
+ /*
23
+ * See both https://wordpress.github.io/gutenberg/?path=/docs/components-confirmdialog--docs and
24
+ * https://wordpress.github.io/gutenberg/?path=/docs/components-modal--docs for all supported props.
25
+ */
26
+ type ConfirmDialogProps = {
27
+ className?: string;
28
+ size?: 'small' | 'medium' | 'large' | 'x-large' | 'full';
29
+ hideTitle?: boolean;
30
+ title?: string;
31
+ isDestructive?: boolean;
32
+ isOpen?: boolean;
33
+ onConfirm?: () => void;
34
+ onCancel?: () => void;
35
+ cancelButtonText?: string;
36
+ confirmButtonText?: string;
37
+ children?: React.ReactNode;
38
+ when?: boolean;
39
+ };
40
+
41
+ const sizeClassMap = {
42
+ small: 'newspack-modal--size-small',
43
+ medium: 'newspack-modal--size-medium',
44
+ large: 'newspack-modal--size-large',
45
+ 'x-large': 'newspack-modal--size-x-large',
46
+ full: 'newspack-modal--size-full',
47
+ };
48
+
49
+ const noOp = () => {};
50
+
51
+ function ConfirmDialog(
52
+ {
53
+ className,
54
+ size = 'small',
55
+ hideTitle,
56
+ isDestructive,
57
+ onConfirm = noOp,
58
+ onCancel = noOp,
59
+ when = false,
60
+ isOpen = false,
61
+ ...otherProps
62
+ }: ConfirmDialogProps,
63
+ ref: React.Ref< HTMLDivElement >
64
+ ) {
65
+ const [ showDialog, setShowDialog ] = useState( isOpen );
66
+ const history = useHistory();
67
+ const pendingNavigation = useRef< ( () => void ) | null >( null );
68
+
69
+ const handleOnConfirm = useCallback( () => {
70
+ setShowDialog( false );
71
+ pendingNavigation.current?.();
72
+ pendingNavigation.current = null;
73
+ onConfirm();
74
+ }, [ onConfirm, pendingNavigation ] );
75
+
76
+ const handleOnCancel = useCallback( () => {
77
+ setShowDialog( false );
78
+ pendingNavigation.current = null;
79
+ onCancel();
80
+ }, [ onCancel, pendingNavigation ] );
81
+
82
+ // Block navigation when there are unsaved changes.
83
+ useEffect( () => {
84
+ if ( ! when ) {
85
+ return;
86
+ }
87
+ const unblock = history.block( ( location: string, action: string ) => {
88
+ pendingNavigation.current = () => {
89
+ unblock();
90
+ if ( action === 'REPLACE' ) {
91
+ history.replace( location );
92
+ } else {
93
+ history.push( location );
94
+ }
95
+ };
96
+ setShowDialog( true );
97
+ return false;
98
+ } );
99
+ return unblock;
100
+ }, [ when, history ] );
101
+
102
+ // Show the dialog imperatively without blocking navigation.
103
+ useEffect( () => {
104
+ if ( isOpen ) {
105
+ setShowDialog( true );
106
+ }
107
+ }, [ isOpen ] );
108
+
109
+ if ( ! showDialog ) {
110
+ return null;
111
+ }
112
+
113
+ const classes = classnames(
114
+ 'newspack-modal',
115
+ sizeClassMap[ size ],
116
+ hideTitle && 'newspack-modal--hide-title', // Note: also hides the X close button.
117
+ isDestructive && 'newspack-modal--destructive',
118
+ className
119
+ );
120
+
121
+ return (
122
+ <BaseComponent
123
+ className={ classes }
124
+ { ...otherProps }
125
+ ref={ ref }
126
+ onConfirm={ handleOnConfirm }
127
+ onCancel={ handleOnCancel }
128
+ __experimentalHideHeader={ false }
129
+ />
130
+ );
131
+ }
132
+ export default forwardRef( ConfirmDialog );
@@ -0,0 +1,34 @@
1
+ /**
2
+ * DataViews
3
+ *
4
+ * Wrapper around @wordpress/dataviews with Newspack styling.
5
+ */
6
+
7
+ /**
8
+ * WordPress dependencies
9
+ */
10
+ import { DataViews as BaseDataViews } from '@wordpress/dataviews';
11
+
12
+ /**
13
+ * External dependencies
14
+ */
15
+ import classnames from 'classnames';
16
+
17
+ /**
18
+ * Internal dependencies
19
+ */
20
+ import './style.scss';
21
+
22
+ type DataViewsProps = React.ComponentProps< typeof BaseDataViews > & {
23
+ className?: string;
24
+ };
25
+
26
+ export default function DataViews( { className, ...props }: DataViewsProps ) {
27
+ return (
28
+ <div className={ classnames( 'newspack-dataviews', className ) }>
29
+ <BaseDataViews { ...props } />
30
+ </div>
31
+ );
32
+ }
33
+
34
+ export type { Action, Field, View } from '@wordpress/dataviews';
@@ -0,0 +1,9 @@
1
+ /* stylelint-disable no-invalid-position-at-import-rule */
2
+ @import "@wordpress/dataviews/build-style/style.css";
3
+
4
+ .newspack-dataviews {
5
+ /* Make sure full-width DataViews aligns with the 48px margin. */
6
+ .newspack-wizard__content--full-width & {
7
+ margin-top: -16px;
8
+ }
9
+ }
@@ -0,0 +1,25 @@
1
+ # Divider
2
+
3
+ Horizontal rule component with alignment, variant, and margin options.
4
+
5
+ ## Props
6
+
7
+ | Prop | Type | Default | Description |
8
+ |------|------|---------|-------------|
9
+ | `alignment` | `'full-width'` \| `'none'` | `'none'` | `full-width` breaks out of the container to span the viewport; `none` stays within the container. |
10
+ | `className` | `string` | — | Additional CSS class. |
11
+ | `marginBottom` | `number` \| `string` | `64` | Bottom margin (e.g. `64` or `"2rem"`). Capped at 32px on viewports &lt; 783px. |
12
+ | `marginTop` | `number` \| `string` | `64` | Top margin (e.g. `64` or `"2rem"`). Capped at 32px on viewports &lt; 783px. |
13
+ | `variant` | `'default'` \| `'primary'` \| `'secondary'` \| `'tertiary'` | `'default'` | Line color: `default` uses `$gray-300`; `primary` uses the admin theme color (`--wp-admin-theme-color`); `secondary` uses `$gray-900`; `tertiary` uses `$gray-100`. |
14
+
15
+ ## Usage
16
+
17
+ ```jsx
18
+ import { Divider } from 'newspack-components';
19
+
20
+ <Divider />
21
+
22
+ <Divider alignment="full-width" variant="primary" />
23
+
24
+ <Divider marginBottom={ 48 } marginTop={ 32 } variant="secondary" />
25
+ ```
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Divider
3
+ */
4
+
5
+ /**
6
+ * Internal dependencies
7
+ */
8
+ import './style.scss';
9
+
10
+ /**
11
+ * External dependencies
12
+ */
13
+ import classNames from 'classnames';
14
+
15
+ const Divider = ( { alignment = 'none', className = undefined, marginBottom = 64, marginTop = 64, variant = 'default', ...otherProps } ) => {
16
+ const classes = classNames(
17
+ 'newspack-divider',
18
+ className,
19
+ alignment && `newspack-divider--alignment-${ alignment }`,
20
+ variant && `newspack-divider--variant-${ variant }`
21
+ );
22
+
23
+ const style = {
24
+ '--divider-margin-bottom': typeof marginBottom === 'number' ? `${ marginBottom }px` : marginBottom,
25
+ '--divider-margin-top': typeof marginTop === 'number' ? `${ marginTop }px` : marginTop,
26
+ };
27
+
28
+ return <hr className={ classes } style={ style } { ...otherProps } />;
29
+ };
30
+
31
+ export default Divider;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Divider
3
+ */
4
+
5
+ @use "~@wordpress/base-styles/colors" as wp-colors;
6
+
7
+ .newspack-divider {
8
+ background: transparent;
9
+ border: 0;
10
+ display: block;
11
+ height: 1px;
12
+ margin-bottom: min(32px, var(--divider-margin-bottom, 64px));
13
+ margin-left: 0;
14
+ margin-right: 0;
15
+ margin-top: min(32px, var(--divider-margin-top, 64px));
16
+ padding: 0;
17
+
18
+ @media screen and (min-width: 783px) {
19
+ margin-bottom: var(--divider-margin-bottom, 64px);
20
+ margin-top: var(--divider-margin-top, 64px);
21
+ }
22
+
23
+ // Alignment
24
+
25
+ &--alignment-full-width {
26
+ margin-left: calc(-50vw + 50%);
27
+ width: 100vw;
28
+ }
29
+
30
+ &--alignment-none {
31
+ width: 100%;
32
+ }
33
+
34
+ // Variant
35
+
36
+ &--variant-default {
37
+ background-color: wp-colors.$gray-300;
38
+ }
39
+
40
+ &--variant-primary {
41
+ background-color: var(--wp-admin-theme-color);
42
+ }
43
+
44
+ &--variant-secondary {
45
+ background-color: wp-colors.$gray-900;
46
+ }
47
+
48
+ &--variant-tertiary {
49
+ background-color: wp-colors.$gray-100;
50
+ }
51
+ }
@@ -7,11 +7,6 @@
7
7
  .newspack-form-token-field {
8
8
  width: 100%;
9
9
 
10
- .components-form-token-field__input-container {
11
- padding-bottom: 1px;
12
- padding-top: 1px;
13
- }
14
-
15
10
  // Help
16
11
 
17
12
  &__help {
@@ -12,9 +12,10 @@ import './style.scss';
12
12
  */
13
13
  import classnames from 'classnames';
14
14
 
15
- const Grid = ( { className = '', columns = 2, gutter = 32, noMargin = false, rowGap = 0, ...otherProps } ) => {
15
+ const Grid = ( { className = '', borders = false, columns = 2, gutter = 32, noMargin = false, rowGap = 0, ...otherProps } ) => {
16
16
  const classes = classnames(
17
17
  'newspack-grid',
18
+ borders && 'newspack-grid__borders',
18
19
  noMargin && 'newspack-grid--no-margin',
19
20
  columns && 'newspack-grid__columns-' + columns,
20
21
  gutter && 'newspack-grid__gutter-' + gutter,