newspack-components 4.3.0 → 4.4.0-alpha.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 (263) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/DEVELOPMENT.md +868 -0
  3. package/colors/colors.module.scss +52 -2
  4. package/copy-styles.js +3 -2
  5. package/dist/cjs/accordion/index.js +17 -12
  6. package/dist/cjs/action-card/index.js +176 -139
  7. package/dist/cjs/autocomplete-tokenfield/index.js +254 -234
  8. package/dist/cjs/autocomplete-with-latest-posts/index.js +172 -110
  9. package/dist/cjs/autocomplete-with-suggestions/index.js +229 -126
  10. package/dist/cjs/badge/index.js +7 -7
  11. package/dist/cjs/box-contrast/index.js +19 -15
  12. package/dist/cjs/button/index.js +41 -26
  13. package/dist/cjs/button-props/button-props.js +5 -5
  14. package/dist/cjs/button-props/index.js +2 -2
  15. package/dist/cjs/card/core-card.js +110 -52
  16. package/dist/cjs/card/index.js +74 -67
  17. package/dist/cjs/card/style-core.scss +93 -25
  18. package/dist/cjs/card/style.scss +2 -2
  19. package/dist/cjs/card-feature/index.js +141 -0
  20. package/dist/cjs/card-feature/style.scss +75 -0
  21. package/dist/cjs/card-form/index.js +144 -0
  22. package/dist/cjs/card-form/style.scss +20 -0
  23. package/dist/cjs/card-settings-group/index.js +36 -16
  24. package/dist/cjs/card-sortable-list/index.js +176 -140
  25. package/dist/cjs/category-autocomplete/index.js +175 -155
  26. package/dist/cjs/color-picker/index.js +41 -23
  27. package/dist/cjs/color-picker/index.test.js +82 -0
  28. package/dist/cjs/color-picker/style.scss +11 -13
  29. package/dist/cjs/confirm-dialog/index.js +86 -22
  30. package/dist/cjs/consts.js +1 -1
  31. package/dist/cjs/custom-select-control/index.js +39 -28
  32. package/dist/cjs/dataviews/index.js +38 -0
  33. package/dist/cjs/dataviews/style.scss +9 -0
  34. package/dist/cjs/divider/index.js +26 -21
  35. package/dist/cjs/footer/index.js +36 -30
  36. package/dist/cjs/form-token-field/index.js +51 -40
  37. package/dist/cjs/global-notices/index.js +5 -5
  38. package/dist/cjs/grid/index.js +25 -19
  39. package/dist/cjs/grid/style.scss +6 -1
  40. package/dist/cjs/handoff/index.js +204 -156
  41. package/dist/cjs/handoff-message/index.js +12 -7
  42. package/dist/cjs/hooks/index.js +10 -8
  43. package/dist/cjs/hooks/use-confirm-dialog.js +79 -0
  44. package/dist/cjs/hooks/use-unsaved-changes-dialog.js +144 -0
  45. package/dist/cjs/hooks/useObjectState.js +21 -12
  46. package/dist/cjs/hooks/useObjectState.test.js +55 -33
  47. package/dist/cjs/hooks/useOnClickOutside.js +6 -6
  48. package/dist/cjs/hooks/usePrompt.js +10 -13
  49. package/dist/cjs/image-upload/index.js +114 -123
  50. package/dist/cjs/image-upload/index.test.js +11 -13
  51. package/dist/cjs/image-upload/style.scss +12 -40
  52. package/dist/cjs/index.js +136 -104
  53. package/dist/cjs/info-button/index.js +43 -32
  54. package/dist/cjs/integration-icons/active-campaign.js +22 -0
  55. package/dist/cjs/integration-icons/constant-contact.js +28 -0
  56. package/dist/cjs/integration-icons/fundraise-up.js +25 -0
  57. package/dist/cjs/integration-icons/index.js +48 -0
  58. package/dist/cjs/integration-icons/mailchimp.js +19 -0
  59. package/dist/cjs/integration-icons/salesforce.js +17 -0
  60. package/dist/cjs/integration-icons/wisepops.js +20 -0
  61. package/dist/cjs/modal/index.js +22 -19
  62. package/dist/cjs/modal/style.scss +4 -0
  63. package/dist/cjs/newspack-icon/index.js +45 -38
  64. package/dist/cjs/notice/index.js +64 -55
  65. package/dist/cjs/plugin-installer/index.js +259 -253
  66. package/dist/cjs/plugin-settings/SettingsSection.js +66 -60
  67. package/dist/cjs/plugin-settings/index.js +217 -200
  68. package/dist/cjs/plugin-toggle/index.js +192 -199
  69. package/dist/cjs/popover/index.js +41 -28
  70. package/dist/cjs/position-control/index.js +24 -23
  71. package/dist/cjs/progress-bar/index.js +71 -59
  72. package/dist/cjs/progress-bar/index.test.js +62 -68
  73. package/dist/cjs/proxied-imports/router.js +3 -3
  74. package/dist/cjs/radio-control/index.js +40 -27
  75. package/dist/cjs/section-header/index.js +103 -58
  76. package/dist/cjs/section-header/style.scss +31 -11
  77. package/dist/cjs/select-control/ButtonGroupControl.js +26 -21
  78. package/dist/cjs/select-control/GroupedSelectControl.js +41 -35
  79. package/dist/cjs/select-control/index.js +52 -40
  80. package/dist/cjs/settings/MinMaxSetting.js +30 -20
  81. package/dist/cjs/settings/SettingsCard.js +20 -17
  82. package/dist/cjs/settings/SettingsSection.js +22 -21
  83. package/dist/cjs/settings/index.js +5 -5
  84. package/dist/cjs/sortable-newsletter-list-control/index.js +78 -57
  85. package/dist/cjs/steps-list/index.js +44 -34
  86. package/dist/cjs/steps-list-item/index.js +47 -39
  87. package/dist/cjs/style-card/index.js +70 -63
  88. package/dist/cjs/tabbed-navigation/index.js +38 -36
  89. package/dist/cjs/text-control/index.js +27 -25
  90. package/dist/cjs/utils/color.js +4 -4
  91. package/dist/cjs/utils/editor-toolbar-back-button.js +61 -0
  92. package/dist/cjs/utils/index.js +31 -19
  93. package/dist/cjs/utils/style.scss +8 -0
  94. package/dist/cjs/waiting/index.js +48 -37
  95. package/dist/cjs/web-preview/index.js +217 -211
  96. package/dist/cjs/with-wizard/index.js +362 -364
  97. package/dist/cjs/with-wizard/style.scss +4 -54
  98. package/dist/cjs/with-wizard-screen/index.js +54 -58
  99. package/dist/cjs/with-wizard-screen/style.scss +41 -3
  100. package/dist/cjs/wizard/components/WizardError.js +20 -18
  101. package/dist/cjs/wizard/components/WizardSnackbar.js +26 -23
  102. package/dist/cjs/wizard/index.js +176 -114
  103. package/dist/cjs/wizard/store/index.js +171 -123
  104. package/dist/cjs/wizard/store/utils.js +13 -8
  105. package/dist/esm/accordion/index.js +14 -9
  106. package/dist/esm/action-card/index.js +168 -123
  107. package/dist/esm/autocomplete-tokenfield/index.js +248 -222
  108. package/dist/esm/autocomplete-with-latest-posts/index.js +169 -103
  109. package/dist/esm/autocomplete-with-suggestions/index.js +226 -119
  110. package/dist/esm/badge/index.js +5 -5
  111. package/dist/esm/box-contrast/index.js +15 -11
  112. package/dist/esm/button/index.js +39 -21
  113. package/dist/esm/button-props/button-props.js +1 -1
  114. package/dist/esm/card/core-card.js +109 -47
  115. package/dist/esm/card/index.js +69 -54
  116. package/dist/esm/card/style-core.scss +93 -25
  117. package/dist/esm/card/style.scss +2 -2
  118. package/dist/esm/card-feature/index.js +131 -0
  119. package/dist/esm/card-feature/style.scss +75 -0
  120. package/dist/esm/card-form/index.js +134 -0
  121. package/dist/esm/card-form/style.scss +20 -0
  122. package/dist/esm/card-settings-group/index.js +33 -14
  123. package/dist/esm/card-sortable-list/index.js +170 -126
  124. package/dist/esm/category-autocomplete/index.js +168 -140
  125. package/dist/esm/color-picker/index.js +39 -21
  126. package/dist/esm/color-picker/index.test.js +78 -0
  127. package/dist/esm/color-picker/style.scss +11 -13
  128. package/dist/esm/confirm-dialog/index.js +85 -18
  129. package/dist/esm/consts.js +1 -1
  130. package/dist/esm/custom-select-control/index.js +34 -15
  131. package/dist/esm/dataviews/index.js +34 -0
  132. package/dist/esm/dataviews/style.scss +9 -0
  133. package/dist/esm/divider/index.js +24 -16
  134. package/dist/esm/footer/index.js +34 -28
  135. package/dist/esm/form-token-field/index.js +46 -27
  136. package/dist/esm/global-notices/index.js +3 -3
  137. package/dist/esm/grid/index.js +23 -14
  138. package/dist/esm/grid/style.scss +6 -1
  139. package/dist/esm/handoff/index.js +199 -143
  140. package/dist/esm/handoff-message/index.js +10 -6
  141. package/dist/esm/hooks/index.js +8 -6
  142. package/dist/esm/hooks/use-confirm-dialog.js +73 -0
  143. package/dist/esm/hooks/use-unsaved-changes-dialog.js +136 -0
  144. package/dist/esm/hooks/useObjectState.js +19 -9
  145. package/dist/esm/hooks/useObjectState.test.js +54 -32
  146. package/dist/esm/hooks/useOnClickOutside.js +4 -4
  147. package/dist/esm/hooks/usePrompt.js +9 -11
  148. package/dist/esm/image-upload/index.js +110 -111
  149. package/dist/esm/image-upload/index.test.js +11 -13
  150. package/dist/esm/image-upload/style.scss +12 -40
  151. package/dist/esm/index.js +7 -1
  152. package/dist/esm/info-button/index.js +38 -19
  153. package/dist/esm/integration-icons/active-campaign.js +16 -0
  154. package/dist/esm/integration-icons/constant-contact.js +22 -0
  155. package/dist/esm/integration-icons/fundraise-up.js +19 -0
  156. package/dist/esm/integration-icons/index.js +6 -0
  157. package/dist/esm/integration-icons/mailchimp.js +13 -0
  158. package/dist/esm/integration-icons/salesforce.js +11 -0
  159. package/dist/esm/integration-icons/wisepops.js +14 -0
  160. package/dist/esm/modal/index.js +20 -13
  161. package/dist/esm/modal/style.scss +4 -0
  162. package/dist/esm/newspack-icon/index.js +40 -25
  163. package/dist/esm/notice/index.js +59 -42
  164. package/dist/esm/plugin-installer/index.js +255 -243
  165. package/dist/esm/plugin-settings/SettingsSection.js +61 -50
  166. package/dist/esm/plugin-settings/index.js +212 -189
  167. package/dist/esm/plugin-toggle/index.js +187 -190
  168. package/dist/esm/popover/index.js +35 -16
  169. package/dist/esm/position-control/index.js +22 -18
  170. package/dist/esm/progress-bar/index.js +66 -49
  171. package/dist/esm/progress-bar/index.test.js +61 -67
  172. package/dist/esm/radio-control/index.js +34 -15
  173. package/dist/esm/section-header/index.js +102 -57
  174. package/dist/esm/section-header/style.scss +31 -11
  175. package/dist/esm/select-control/ButtonGroupControl.js +23 -18
  176. package/dist/esm/select-control/GroupedSelectControl.js +38 -30
  177. package/dist/esm/select-control/index.js +47 -27
  178. package/dist/esm/settings/MinMaxSetting.js +27 -16
  179. package/dist/esm/settings/SettingsCard.js +18 -13
  180. package/dist/esm/settings/SettingsSection.js +20 -19
  181. package/dist/esm/settings/index.js +3 -3
  182. package/dist/esm/sortable-newsletter-list-control/index.js +74 -53
  183. package/dist/esm/steps-list/index.js +39 -21
  184. package/dist/esm/steps-list-item/index.js +42 -26
  185. package/dist/esm/style-card/index.js +65 -50
  186. package/dist/esm/tabbed-navigation/index.js +35 -33
  187. package/dist/esm/text-control/index.js +25 -19
  188. package/dist/esm/utils/color.js +4 -4
  189. package/dist/esm/utils/editor-toolbar-back-button.js +53 -0
  190. package/dist/esm/utils/index.js +29 -16
  191. package/dist/esm/utils/style.scss +8 -0
  192. package/dist/esm/waiting/index.js +43 -24
  193. package/dist/esm/web-preview/index.js +213 -201
  194. package/dist/esm/with-wizard/index.js +360 -358
  195. package/dist/esm/with-wizard/style.scss +4 -54
  196. package/dist/esm/with-wizard-screen/index.js +48 -46
  197. package/dist/esm/with-wizard-screen/style.scss +41 -3
  198. package/dist/esm/wizard/components/WizardError.js +18 -16
  199. package/dist/esm/wizard/components/WizardSnackbar.js +23 -17
  200. package/dist/esm/wizard/index.js +171 -103
  201. package/dist/esm/wizard/store/index.js +169 -116
  202. package/dist/esm/wizard/store/utils.js +13 -6
  203. package/package.json +30 -16
  204. package/release.config.js +1 -0
  205. package/shared/hooks/use-coauthors.js +195 -27
  206. package/shared/hooks/use-coauthors.test.js +232 -6
  207. package/shared/scss/_mixins.scss +13 -5
  208. package/src/accordion/index.js +8 -7
  209. package/src/action-card/index.js +15 -1
  210. package/src/badge/index.tsx +3 -1
  211. package/src/card/core-card.js +95 -18
  212. package/src/card/index.js +1 -0
  213. package/src/card/style-core.scss +93 -25
  214. package/src/card/style.scss +2 -2
  215. package/src/card-feature/README.md +188 -0
  216. package/src/card-feature/index.tsx +186 -0
  217. package/src/card-feature/style.scss +75 -0
  218. package/src/card-form/README.md +113 -0
  219. package/src/card-form/index.tsx +128 -0
  220. package/src/card-form/style.scss +20 -0
  221. package/src/card-settings-group/index.tsx +28 -3
  222. package/src/card-sortable-list/index.tsx +27 -4
  223. package/src/color-picker/index.js +15 -8
  224. package/src/color-picker/index.test.js +52 -0
  225. package/src/color-picker/style.scss +11 -13
  226. package/src/confirm-dialog/README.md +128 -0
  227. package/src/confirm-dialog/index.tsx +74 -6
  228. package/src/dataviews/index.tsx +34 -0
  229. package/src/dataviews/style.scss +9 -0
  230. package/src/grid/style.scss +6 -1
  231. package/src/handoff/README.md +93 -0
  232. package/src/handoff/index.js +46 -8
  233. package/src/hooks/index.ts +2 -1
  234. package/src/hooks/use-confirm-dialog.tsx +80 -0
  235. package/src/hooks/use-unsaved-changes-dialog.tsx +140 -0
  236. package/src/image-upload/index.js +10 -15
  237. package/src/image-upload/style.scss +12 -40
  238. package/src/index.js +7 -1
  239. package/src/integration-icons/active-campaign.js +14 -0
  240. package/src/integration-icons/constant-contact.js +22 -0
  241. package/src/integration-icons/fundraise-up.js +18 -0
  242. package/src/integration-icons/index.js +6 -0
  243. package/src/integration-icons/mailchimp.js +10 -0
  244. package/src/integration-icons/salesforce.js +10 -0
  245. package/src/integration-icons/wisepops.js +14 -0
  246. package/src/modal/style.scss +4 -0
  247. package/src/plugin-installer/index.js +30 -19
  248. package/src/section-header/index.js +56 -24
  249. package/src/section-header/style.scss +31 -11
  250. package/src/select-control/index.js +4 -0
  251. package/src/utils/editor-toolbar-back-button.tsx +48 -0
  252. package/src/utils/index.js +6 -0
  253. package/src/utils/style.scss +8 -0
  254. package/src/with-wizard/style.scss +4 -54
  255. package/src/with-wizard-screen/style.scss +41 -3
  256. package/src/wizard/index.js +90 -53
  257. package/src/wizard/store/index.js +2 -2
  258. package/dist/cjs/button-card/index.js +0 -72
  259. package/dist/cjs/button-card/style.scss +0 -180
  260. package/dist/esm/button-card/index.js +0 -64
  261. package/dist/esm/button-card/style.scss +0 -180
  262. package/src/button-card/index.js +0 -52
  263. package/src/button-card/style.scss +0 -180
@@ -7,7 +7,7 @@
7
7
  * WordPress dependencies
8
8
  */
9
9
  import { __ } from '@wordpress/i18n';
10
- import { Button, Card as CardWrapper, CardHeader, CardFooter, DropdownMenu, MenuItem, ToggleControl } from '@wordpress/components';
10
+ import { Button, Card as CardWrapper, CardHeader, CardFooter, DropdownMenu, MenuGroup, MenuItem, ToggleControl } from '@wordpress/components';
11
11
  import { Icon, chevronDown, chevronRight, chevronUp, dragHandle, moreVertical } from '@wordpress/icons';
12
12
 
13
13
  /**
@@ -28,6 +28,11 @@ const CoreCard = ( {
28
28
  className,
29
29
  footer,
30
30
  header,
31
+ headerAction,
32
+ headerStyle,
33
+ childrenStyle,
34
+ footerStyle,
35
+ disabled,
31
36
  icon,
32
37
  iconBackgroundColor,
33
38
  isActive,
@@ -38,24 +43,30 @@ const CoreCard = ( {
38
43
  isSmall,
39
44
  dragIndex,
40
45
  onDragCallback = () => {},
46
+ onToggle = () => {},
41
47
  onHeaderClick,
42
48
  noBorder,
43
49
  noMargin,
44
50
  children = null,
51
+ hasGreyHeader,
52
+ hasHeaderBorder = true,
45
53
  ...otherProps
46
54
  } ) => {
47
55
  const classes = classNames(
48
56
  'newspack-card--core',
49
57
  className,
50
58
  ( buttonsCard || as === 'a' ) && 'newspack-card--core__buttons-card',
59
+ actions?.length > 0 && 'newspack-card--core__header--has-actions',
51
60
  isDraggable && 'newspack-card--core__is-draggable',
52
61
  isNarrow && 'newspack-card--core__is-narrow',
53
62
  isSmall && 'newspack-card--core__is-small',
54
63
  icon && 'newspack-card--core__has-icon',
55
64
  iconBackgroundColor && 'newspack-card--core__has-icon-background-color',
56
65
  isActive && 'newspack-card--core__is-active',
66
+ disabled && 'newspack-card--core__is-disabled',
57
67
  children && 'newspack-card--core__has-children',
58
- noMargin && 'newspack-card--core__no-margin'
68
+ noMargin && 'newspack-card--core__no-margin',
69
+ hasGreyHeader && 'newspack-card--core__has-grey-header'
59
70
  );
60
71
  let sizeProps = isSmall ? 'small' : otherProps.size;
61
72
  if ( buttonsCard || as === 'a' ) {
@@ -74,9 +85,17 @@ const CoreCard = ( {
74
85
  { ( header || icon ) && (
75
86
  <CardHeader
76
87
  as={ onHeaderClick ? 'button' : undefined }
77
- className={ classNames( 'newspack-card--core__header', isDraggable && 'newspack-card--core__header--is-draggable' ) }
88
+ className={ classNames(
89
+ 'newspack-card--core__header',
90
+ isDraggable && 'newspack-card--core__header--is-draggable',
91
+ ! hasHeaderBorder && 'newspack-card--core__header--no-border'
92
+ ) }
93
+ style={ headerStyle }
78
94
  size={ sizeProps }
79
- onClick={ onHeaderClick }
95
+ gap={ 4 }
96
+ onClick={ disabled ? undefined : onHeaderClick }
97
+ disabled={ onHeaderClick && disabled ? true : undefined }
98
+ aria-disabled={ onHeaderClick && disabled ? true : undefined }
80
99
  >
81
100
  { isDraggable && (
82
101
  <div className="newspack-card--core__header__draggable-controls">
@@ -106,6 +125,15 @@ const CoreCard = ( {
106
125
  <Icon icon={ icon } height={ isSmall ? 24 : 48 } width={ isSmall ? 24 : 48 } />
107
126
  </div>
108
127
  ) }
128
+ { actions?.length > 0 && actionType === 'toggle' && (
129
+ <ToggleControl
130
+ className="newspack-card--core__action"
131
+ label={ otherProps.title }
132
+ hideLabelFromVision
133
+ checked={ isActive }
134
+ onChange={ onToggle }
135
+ />
136
+ ) }
109
137
  { header && <div className="newspack-card--core__header-content">{ header }</div> }
110
138
  { ! actions?.length > 0 && actionType === 'chevron' && (
111
139
  <Icon className="newspack-card--core__action" icon={ chevronRight } height={ 24 } width={ 24 } />
@@ -116,30 +144,79 @@ const CoreCard = ( {
116
144
  label={ otherProps.title }
117
145
  hideLabelFromVision
118
146
  checked={ isActive }
119
- onChange={ () => {} }
147
+ onChange={ onToggle }
120
148
  />
121
149
  ) }
122
150
  { actions?.length > 0 && (
123
151
  <DropdownMenu icon={ moreVertical } label={ __( 'More', 'newspack-plugin' ) }>
124
152
  { () =>
125
- actions.map( ( action, index ) => (
126
- <MenuItem
127
- key={ index }
128
- icon={ action.icon }
129
- onClick={ action.action }
130
- disabled={ action.disabled || false }
131
- isDestructive={ action.destructive || false }
132
- >
133
- { action.label }
134
- </MenuItem>
135
- ) )
153
+ actions.map( ( action, index ) => {
154
+ // Actions can be an array of sub-actions, which are rendered within a MenuGroup.
155
+ if ( Array.isArray( action ) ) {
156
+ return (
157
+ <MenuGroup key={ index }>
158
+ { action.map( ( subAction, i ) => {
159
+ return (
160
+ <MenuItem
161
+ key={ i }
162
+ icon={ subAction.icon }
163
+ onClick={ subAction.action }
164
+ href={ subAction.href }
165
+ disabled={ subAction.disabled || false }
166
+ isDestructive={ subAction.destructive || false }
167
+ >
168
+ { subAction.label }
169
+ </MenuItem>
170
+ );
171
+ } ) }
172
+ </MenuGroup>
173
+ );
174
+ }
175
+ return (
176
+ <MenuItem
177
+ key={ index }
178
+ icon={ action.icon }
179
+ onClick={ action.action }
180
+ href={ action.href }
181
+ disabled={ action.disabled || false }
182
+ isDestructive={ action.destructive || false }
183
+ >
184
+ { action.label }
185
+ </MenuItem>
186
+ );
187
+ } )
136
188
  }
137
189
  </DropdownMenu>
138
190
  ) }
191
+ { headerAction && (
192
+ <Button
193
+ className="newspack-card--core__header__action"
194
+ icon={ headerAction.icon }
195
+ href={ headerAction.href }
196
+ disabled={ headerAction.disabled || false }
197
+ isDestructive={ headerAction.destructive || false }
198
+ onClick={ headerAction.onClick }
199
+ tone={ headerAction.tone || 'primary' }
200
+ variant={ headerAction.variant || 'secondary' }
201
+ >
202
+ { headerAction.label }
203
+ </Button>
204
+ ) }
139
205
  </CardHeader>
140
206
  ) }
141
- { children && <div className="newspack-card--core__body">{ children }</div> }
142
- { footer && <CardFooter size={ sizeProps }>{ footer }</CardFooter> }
207
+ { children && (
208
+ <div
209
+ className={ classNames( 'newspack-card--core__body', ! hasHeaderBorder && 'newspack-card--core__body--no-header-border' ) }
210
+ style={ childrenStyle }
211
+ >
212
+ { children }
213
+ </div>
214
+ ) }
215
+ { footer && (
216
+ <CardFooter size={ sizeProps } style={ footerStyle }>
217
+ { footer }
218
+ </CardFooter>
219
+ ) }
143
220
  </CardWrapper>
144
221
  );
145
222
  };
package/src/card/index.js CHANGED
@@ -43,6 +43,7 @@ class Card extends Component {
43
43
  noMargin: false,
44
44
  isDraggable: false,
45
45
  dragIndex: null,
46
+ hasGreyHeader: false,
46
47
  onDragCallback: () => {},
47
48
  },
48
49
  ...otherProps
@@ -3,26 +3,27 @@
3
3
  */
4
4
 
5
5
  @use "~@wordpress/base-styles/colors" as wp-colors;
6
+ @use "~@wordpress/base-styles/variables" as wp-vars;
6
7
  @use "../../../colors/colors.module" as colors;
7
8
 
8
9
  .newspack-card--core,
9
10
  .newspack-wizard .newspack-card--core {
10
- background: white;
11
+ background: wp-colors.$white;
11
12
  position: relative;
12
13
  transition: background-color 125ms ease-in-out, box-shadow 125ms ease-in-out, color 125ms ease-in-out;
13
14
  svg {
14
15
  transition: fill 125ms ease-in-out;
15
16
  }
16
17
  &__icon {
17
- border-radius: 50%;
18
+ border-radius: wp-vars.$radius-round;
18
19
  display: grid;
19
- height: 48px;
20
+ height: wp-vars.$grid-unit-60;
20
21
  place-items: center;
21
- width: 48px;
22
+ width: wp-vars.$grid-unit-60;
22
23
  svg {
23
24
  fill: var(--wp-admin-theme-color);
24
- height: 40px;
25
- width: 40px;
25
+ height: wp-vars.$grid-unit-50;
26
+ width: wp-vars.$grid-unit-50;
26
27
  }
27
28
  .newspack-card--core__has-icon-background-color & {
28
29
  background: var(--wp-admin-theme-color-lighter-10);
@@ -35,24 +36,37 @@
35
36
  h4,
36
37
  h5,
37
38
  h6 {
38
- font-size: 14px;
39
- font-weight: 500;
39
+ font-size: wp-vars.$font-size-large;
40
40
  }
41
- .newspack-card--core__icon {
42
- height: 40px;
43
- width: 40px;
44
- svg {
45
- height: 24px;
46
- width: 24px;
41
+ .newspack-card--core {
42
+ &__header-content {
43
+ * {
44
+ line-height: wp-vars.$font-line-height-small;
45
+ }
46
+ }
47
+ &__icon {
48
+ height: wp-vars.$grid-unit-50;
49
+ width: wp-vars.$grid-unit-50;
50
+ svg {
51
+ height: wp-vars.$grid-unit-30;
52
+ width: wp-vars.$grid-unit-30;
53
+ }
47
54
  }
48
55
  }
49
56
  }
50
57
  &__no-margin .components-card__body {
51
58
  padding: 0 !important;
52
59
  }
60
+ &__is-disabled {
61
+ cursor: not-allowed;
62
+ opacity: 0.5;
63
+ .newspack-card--core__header {
64
+ pointer-events: none;
65
+ }
66
+ }
53
67
  &__header {
54
68
  color: wp-colors.$gray-700;
55
- column-gap: 16px;
69
+ flex-wrap: wrap;
56
70
  position: relative;
57
71
  .newspack-card--core__action {
58
72
  margin: 0;
@@ -68,22 +82,26 @@
68
82
  word-wrap: normal !important;
69
83
  }
70
84
  }
71
- .components-dropdown-menu {
85
+ > .components-dropdown-menu {
72
86
  aspect-ratio: 1;
73
87
  border-left: 1px solid wp-colors.$gray-200;
74
88
  display: grid;
75
89
  height: 100%;
90
+ max-width: 50px;
76
91
  padding: 4px;
77
92
  place-items: center;
78
93
  position: absolute;
79
94
  right: 0;
80
95
  top: 0;
81
96
  }
97
+ &__action {
98
+ background-color: wp-colors.$white;
99
+ flex: 1 100%;
100
+ justify-content: center !important;
101
+ }
82
102
  }
83
103
  &__header--is-draggable {
84
- .newspack-card--core__header-content {
85
- padding-left: 65px;
86
- }
104
+ padding-left: 80px !important;
87
105
  .newspack-card--core__header__draggable-controls {
88
106
  align-items: center;
89
107
  border-right: 1px solid wp-colors.$gray-200;
@@ -110,6 +128,21 @@
110
128
  }
111
129
  }
112
130
  }
131
+ // Two classes beat the single-class .components-card__header border-bottom
132
+ // rule from WP Core without needing !important.
133
+ &__header.newspack-card--core__header--no-border {
134
+ border-bottom: none;
135
+ }
136
+ &__body.newspack-card--core__body--no-header-border {
137
+ > div:not(.components-card__body):not(.components-card__media):not(.components-card__divider) {
138
+ padding-top: 0;
139
+ }
140
+ }
141
+ &__header--has-actions {
142
+ .newspack-card--core__header {
143
+ padding-right: 70px;
144
+ }
145
+ }
113
146
  button.newspack-card--core__header {
114
147
  appearance: none;
115
148
  background-color: white;
@@ -140,18 +173,25 @@
140
173
  h6 {
141
174
  align-items: center;
142
175
  color: wp-colors.$gray-900;
143
- font-weight: 500;
176
+ font-weight: 600;
144
177
  display: flex;
145
178
  gap: 8px;
179
+ a {
180
+ color: inherit;
181
+ text-decoration: none;
182
+ &:hover {
183
+ text-decoration: underline;
184
+ }
185
+ }
146
186
  .newspack-badge {
147
187
  font-weight: 400;
148
188
  }
149
189
  }
150
190
  }
151
- &__is-active {
191
+ &__is-active:not(.newspack-card--core__is-draggable) {
152
192
  border-color: var(--wp-admin-theme-color);
153
193
  box-shadow: var(--wp-admin-theme-color) 0 0 0 1px;
154
- button.newspack-card--core__header {
194
+ .newspack-card--core__header {
155
195
  background-color: rgba( var(--wp-admin-theme-color--rgb), 0.04 );
156
196
  color: var(--wp-admin-theme-color);
157
197
  h1,
@@ -168,7 +208,7 @@
168
208
  }
169
209
 
170
210
  &.newspack-card--core__has-children {
171
- button.newspack-card--core__header {
211
+ .newspack-card--core__header {
172
212
  border-bottom-left-radius: 0;
173
213
  border-bottom-right-radius: 0;
174
214
  }
@@ -203,14 +243,39 @@
203
243
  }
204
244
  }
205
245
  }
246
+ &.is-destructive {
247
+ .newspack-card--core__header {
248
+ h1,
249
+ h2,
250
+ h3,
251
+ h4,
252
+ h5,
253
+ h6 {
254
+ color: wp-colors.$alert-red;
255
+ }
256
+ &:hover,
257
+ &:focus {
258
+ background-color: wp-colors.$gray-100;
259
+ }
260
+ }
261
+ .newspack-card--core__action,
262
+ .newspack-card--core__action svg {
263
+ fill: wp-colors.$alert-red;
264
+ }
265
+ }
206
266
  hr.components-card__divider {
207
267
  margin: 0;
208
268
  }
209
269
  &__is-narrow {
210
270
  margin-left: auto;
211
271
  margin-right: auto;
212
- @media screen and ( min-width: 608px ) {
213
- max-width: 576px;
272
+ @media screen and ( min-width: 615px ) {
273
+ max-width: 583px;
274
+ }
275
+ }
276
+ &__has-grey-header {
277
+ .newspack-card--core__header {
278
+ background-color: rgba(black, 0.025);
214
279
  }
215
280
  }
216
281
  &__body {
@@ -223,5 +288,8 @@
223
288
  padding: 0;
224
289
  }
225
290
  }
291
+ > .components-card__media {
292
+ border-radius: 0 !important;
293
+ }
226
294
  }
227
295
  }
@@ -52,8 +52,8 @@
52
52
  margin-left: auto;
53
53
  margin-right: auto;
54
54
 
55
- @media screen and ( min-width: 608px ) {
56
- max-width: 576px;
55
+ @media screen and ( min-width: 615px ) {
56
+ max-width: 583px;
57
57
  }
58
58
  }
59
59
 
@@ -0,0 +1,188 @@
1
+ # CardFeature
2
+
3
+ A card component for presenting a named feature or setting with a predictable, state-driven interaction model. It handles the primary button, an optional "More" dropdown, and a badge automatically based on the `enabled` and `requirements` props.
4
+
5
+ ## Layout rules
6
+
7
+ - **Maximum 2 cards per row.** Use `<Grid columns={ 2 }>` — never 3 or more. Cards are designed to be read, not scanned, and 3+ columns makes them too narrow for the content.
8
+ - The icon is always displayed on the **right-hand side**, aligned to the top of the content.
9
+
10
+ ## States
11
+
12
+ | State | Condition | Button | Dropdown | Badge |
13
+ |---|---|---|---|---|
14
+ | **Unmet requirements** | `requirements` is set | "Enable" — disabled (clickable if `requirementsActionable`) | Hidden | Error badge with `requirements` text |
15
+ | **Disabled** | `!enabled`, no requirements | "Enable" | Hidden | None |
16
+ | **Enabled** | `enabled`, no requirements | "Configure" | Shown if `moreControls` provided | Success badge ("Enabled") |
17
+
18
+ The card content (title + description) is visually muted (gray text, lighter border) when `requirements` is set.
19
+
20
+ ## Basic usage
21
+
22
+ ```tsx
23
+ import { __ } from '@wordpress/i18n';
24
+
25
+ <CardFeature
26
+ title={ __( 'Metered countdown', 'newspack-plugin' ) }
27
+ description={ __( 'Show a countdown banner letting readers know how many free views they have left.', 'newspack-plugin' ) }
28
+ enabled={ isEnabled }
29
+ onEnable={ () => setEnabled( true ) }
30
+ onConfigure={ () => history.push( '/settings/countdown' ) }
31
+ moreControls={ [
32
+ { title: __( 'Disable', 'newspack-plugin' ), onClick: () => setEnabled( false ) },
33
+ ] }
34
+ />
35
+ ```
36
+
37
+ ## With unmet requirements
38
+
39
+ When `requirements` is set the button is disabled and an error badge displays the string. The title and description are visually muted.
40
+
41
+ ```tsx
42
+ import { __ } from '@wordpress/i18n';
43
+
44
+ <CardFeature
45
+ title={ __( 'Metered countdown', 'newspack-plugin' ) }
46
+ description={ __( 'Show a countdown banner letting readers know how many free views they have left.', 'newspack-plugin' ) }
47
+ enabled={ isEnabled }
48
+ requirements={ __( 'Requires metering', 'newspack-plugin' ) }
49
+ onEnable={ () => setEnabled( true ) }
50
+ onConfigure={ () => history.push( '/settings/countdown' ) }
51
+ moreControls={ [
52
+ { title: __( 'Disable', 'newspack-plugin' ), onClick: () => setEnabled( false ) },
53
+ ] }
54
+ />
55
+ ```
56
+
57
+ ## With a custom icon
58
+
59
+ The `icon` prop accepts an object, not a bare node. Pass `node` for the icon element, `fill` to control the SVG colour (applied via `currentColor`), `backgroundColor` for a container background, and `radius` for the corner treatment.
60
+
61
+ The icon container is always **40 × 40 px** with the SVG at **24 × 24 px**. `radius` only applies when `backgroundColor` is set.
62
+
63
+ ```tsx
64
+ import { __ } from '@wordpress/i18n';
65
+ import { Icon, starFilled } from '@wordpress/icons';
66
+
67
+ // Icon with fill only
68
+ <CardFeature
69
+ title={ __( 'Content gifting', 'newspack-plugin' ) }
70
+ description={ __( 'Let subscribers share gated articles with non-subscribers.', 'newspack-plugin' ) }
71
+ icon={ { node: <Icon icon={ starFilled } />, fill: '#003da5' } }
72
+ enabled={ isEnabled }
73
+ onEnable={ handleEnable }
74
+ onConfigure={ handleConfigure }
75
+ moreControls={ [ { title: __( 'Disable', 'newspack-plugin' ), onClick: handleDisable } ] }
76
+ />
77
+
78
+ // Icon with circular background
79
+ <CardFeature
80
+ title={ __( 'Content gifting', 'newspack-plugin' ) }
81
+ description={ __( 'Let subscribers share gated articles with non-subscribers.', 'newspack-plugin' ) }
82
+ icon={ { node: <Icon icon={ starFilled } />, fill: '#003da5', backgroundColor: '#dfe7f4', radius: 'full' } }
83
+ enabled={ isEnabled }
84
+ onEnable={ handleEnable }
85
+ onConfigure={ handleConfigure }
86
+ moreControls={ [ { title: __( 'Disable', 'newspack-plugin' ), onClick: handleDisable } ] }
87
+ />
88
+ ```
89
+
90
+ ## With custom button labels
91
+
92
+ Override `enableLabel` and `configureLabel` to match the context of the feature.
93
+
94
+ ```tsx
95
+ import { __ } from '@wordpress/i18n';
96
+
97
+ <CardFeature
98
+ title={ __( 'Apple News', 'newspack-plugin' ) }
99
+ description={ __( 'Automatically publish articles to Apple News.', 'newspack-plugin' ) }
100
+ enabled={ isEnabled }
101
+ enableLabel={ __( 'Connect', 'newspack-plugin' ) }
102
+ configureLabel={ __( 'Manage connection', 'newspack-plugin' ) }
103
+ onEnable={ handleConnect }
104
+ onConfigure={ () => history.push( '/settings/apple-news' ) }
105
+ moreControls={ [ { title: __( 'Disconnect', 'newspack-plugin' ), onClick: handleDisconnect } ] }
106
+ />
107
+ ```
108
+
109
+ ## With a custom badge
110
+
111
+ Override `badgeText` and `badgeLevel` to change the badge shown when the feature is enabled. Available levels: `default`, `info`, `success`, `warning`, `error`.
112
+
113
+ ```tsx
114
+ import { __ } from '@wordpress/i18n';
115
+
116
+ <CardFeature
117
+ title={ __( 'Stripe', 'newspack-plugin' ) }
118
+ description={ __( 'Accept payments via Stripe.', 'newspack-plugin' ) }
119
+ enabled={ isEnabled }
120
+ badgeText={ __( 'Live mode', 'newspack-plugin' ) }
121
+ badgeLevel="info"
122
+ onEnable={ handleEnable }
123
+ onConfigure={ () => history.push( '/settings/stripe' ) }
124
+ moreControls={ [ { title: __( 'Disable', 'newspack-plugin' ), onClick: handleDisable } ] }
125
+ />
126
+ ```
127
+
128
+ ## With multiple "More" controls
129
+
130
+ `moreControls` accepts any number of items, each with a `title`, `onClick`, and an optional `icon`.
131
+
132
+ ```tsx
133
+ import { __ } from '@wordpress/i18n';
134
+
135
+ <CardFeature
136
+ title={ __( 'Newsletters', 'newspack-plugin' ) }
137
+ description={ __( 'Send newsletters directly from the WordPress editor.', 'newspack-plugin' ) }
138
+ enabled={ isEnabled }
139
+ onEnable={ handleEnable }
140
+ onConfigure={ () => history.push( '/settings/newsletters' ) }
141
+ moreControls={ [
142
+ { title: __( 'Edit', 'newspack-plugin' ), onClick: handleEdit },
143
+ { title: __( 'Preview', 'newspack-plugin' ), onClick: handlePreview },
144
+ { title: __( 'Disable', 'newspack-plugin' ), onClick: handleDisable },
145
+ ] }
146
+ />
147
+ ```
148
+
149
+ ## Props
150
+
151
+ | Prop | Type | Default | Description |
152
+ |---|---|---|---|
153
+ | `title` | `string` | — | Card heading (**required**) |
154
+ | `description` | `string` | — | Supporting text below the title |
155
+ | `icon` | `CardFeatureIcon` | — | Icon displayed on the right. See `CardFeatureIcon` below. |
156
+ | `enabled` | `boolean` | `false` | Whether the feature is currently enabled |
157
+ | `requirements` | `string` | — | When set, enters the unmet-requirements state; value is used as the error badge text |
158
+ | `requirementsActionable` | `boolean` | `false` | When `requirements` is set, keep the primary button clickable so it can remediate the unmet requirement |
159
+ | `enableLabel` | `string` | `"Enable"` | Primary button label when not enabled |
160
+ | `configureLabel` | `string` | `"Configure"` | Primary button label when enabled |
161
+ | `onEnable` | `() => void` | — | Called when the primary button is clicked and the feature is not enabled |
162
+ | `onConfigure` | `() => void` | — | Called when the primary button is clicked and the feature is enabled |
163
+ | `moreControls` | `MoreControl[]` | — | Items for the "More" dropdown, shown only when enabled |
164
+ | `badgeText` | `string` | `"Enabled"` | Badge text shown when enabled |
165
+ | `badgeLevel` | `BadgeLevel` | `"success"` | Badge level shown when enabled |
166
+ | `className` | `string` | — | Additional class name applied to the card element |
167
+
168
+ ### `CardFeatureIcon`
169
+
170
+ ```ts
171
+ type CardFeatureIcon = {
172
+ node: React.ReactNode; // The icon element to render
173
+ fill?: string; // SVG fill colour (applied via currentColor)
174
+ backgroundColor?: string; // Background colour of the 40×40 container
175
+ radius?: 'small' | 'full'; // 'small' = 2px ($radius-small), 'full' = 50% ($radius-round)
176
+ // Only applied when backgroundColor is set.
177
+ };
178
+ ```
179
+
180
+ ### `MoreControl`
181
+
182
+ ```ts
183
+ type MoreControl = {
184
+ title: string;
185
+ onClick: () => void;
186
+ icon?: React.ReactNode;
187
+ };
188
+ ```