newspack-components 4.8.0 → 4.8.1-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 (66) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/DEVELOPMENT.md +18 -17
  3. package/dist/cjs/action-card/index.js +85 -18
  4. package/dist/cjs/action-card/index.test.js +192 -5
  5. package/dist/cjs/action-card/style.scss +3 -9
  6. package/dist/cjs/debug-badge/index.js +44 -0
  7. package/dist/cjs/debug-badge/index.test.js +50 -0
  8. package/dist/cjs/debug-badge/style.scss +26 -0
  9. package/dist/cjs/empty-state/index.test.js +37 -24
  10. package/dist/cjs/empty-state/root.js +2 -1
  11. package/dist/cjs/footer/index.js +52 -16
  12. package/dist/cjs/footer/index.test.js +208 -0
  13. package/dist/cjs/footer/style.scss +17 -1
  14. package/dist/cjs/index.js +7 -0
  15. package/dist/cjs/notice/index.js +1 -4
  16. package/dist/cjs/notice/style.scss +0 -19
  17. package/dist/cjs/plugin-toggle/index.js +1 -1
  18. package/dist/cjs/with-wizard-screen/index.js +1 -3
  19. package/dist/cjs/with-wizard-screen/index.test.js +0 -3
  20. package/dist/cjs/wizard/index.js +8 -7
  21. package/dist/cjs/wizard/index.test.js +158 -5
  22. package/dist/cjs/wizard/store/index.js +2 -0
  23. package/dist/esm/action-card/index.js +86 -21
  24. package/dist/esm/action-card/index.test.js +192 -1
  25. package/dist/esm/action-card/style.scss +3 -9
  26. package/dist/esm/debug-badge/index.js +39 -0
  27. package/dist/esm/debug-badge/index.test.js +46 -0
  28. package/dist/esm/debug-badge/style.scss +26 -0
  29. package/dist/esm/empty-state/index.test.js +37 -24
  30. package/dist/esm/empty-state/root.js +2 -1
  31. package/dist/esm/footer/index.js +52 -17
  32. package/dist/esm/footer/index.test.js +210 -0
  33. package/dist/esm/footer/style.scss +17 -1
  34. package/dist/esm/index.js +1 -0
  35. package/dist/esm/notice/index.js +2 -5
  36. package/dist/esm/notice/style.scss +0 -19
  37. package/dist/esm/plugin-toggle/index.js +1 -1
  38. package/dist/esm/with-wizard-screen/index.js +2 -4
  39. package/dist/esm/with-wizard-screen/index.test.js +0 -3
  40. package/dist/esm/wizard/index.js +11 -10
  41. package/dist/esm/wizard/index.test.js +158 -5
  42. package/dist/esm/wizard/store/index.js +2 -0
  43. package/package.json +1 -1
  44. package/src/action-card/action-card.d.ts +3 -2
  45. package/src/action-card/index.js +81 -8
  46. package/src/action-card/index.test.js +159 -0
  47. package/src/action-card/style.scss +3 -9
  48. package/src/debug-badge/index.js +36 -0
  49. package/src/debug-badge/index.test.js +39 -0
  50. package/src/debug-badge/style.scss +26 -0
  51. package/src/empty-state/README.md +11 -10
  52. package/src/empty-state/index.test.js +10 -0
  53. package/src/empty-state/root.tsx +2 -1
  54. package/src/empty-state/types.ts +1 -1
  55. package/src/footer/index.js +47 -2
  56. package/src/footer/index.test.js +114 -0
  57. package/src/footer/style.scss +17 -1
  58. package/src/index.js +1 -0
  59. package/src/notice/index.js +2 -17
  60. package/src/notice/style.scss +0 -19
  61. package/src/plugin-toggle/index.js +1 -1
  62. package/src/with-wizard-screen/index.js +2 -2
  63. package/src/with-wizard-screen/index.test.js +0 -2
  64. package/src/wizard/index.js +11 -9
  65. package/src/wizard/index.test.js +96 -3
  66. package/src/wizard/store/index.js +2 -0
@@ -22,25 +22,6 @@
22
22
  margin-right: 4px;
23
23
  }
24
24
 
25
- &__is-debug {
26
- background: np-colors.$primary-600;
27
- border-radius: 50%;
28
- bottom: 16px;
29
- box-shadow: 0 0 8px 4px rgba(black, 0.08);
30
- color: white;
31
- font-weight: bold;
32
- margin: 0 16px;
33
- padding: 6px;
34
- position: fixed;
35
- text-transform: uppercase;
36
- z-index: 9997;
37
-
38
- > svg {
39
- fill: white;
40
- margin: 0;
41
- }
42
- }
43
-
44
25
  &__is-error {
45
26
  background: color.adjust(wp-colors.$alert-red, $lightness: 35%);
46
27
 
@@ -99,7 +99,7 @@ class PluginToggle extends Component {
99
99
  toggleChecked={ this.isPluginInstalledAndActive( plugin ) }
100
100
  toggleOnChange={ value => this.managePlugin( slug, value ) }
101
101
  notification={ error }
102
- notificationHTML={ error }
102
+ notificationHTML
103
103
  notificationLevel="error"
104
104
  />
105
105
  );
@@ -6,7 +6,7 @@ import { cloneElement } from '@wordpress/element';
6
6
  /**
7
7
  * Internal dependencies
8
8
  */
9
- import { Button, Handoff, Notice, HandoffMessage, TabbedNavigation, Page } from '../';
9
+ import { Button, DebugBadge, Handoff, HandoffMessage, TabbedNavigation, Page } from '../';
10
10
  import { activeBreadcrumbs } from '../wizard/breadcrumbs-select';
11
11
  import { buttonProps } from '../button-props';
12
12
  import Router from '../proxied-imports/router';
@@ -112,7 +112,7 @@ export default function withWizardScreen( WrappedComponent, { hidePrimaryButton,
112
112
  }
113
113
  return (
114
114
  <>
115
- { newspack_aux_data.is_debug_mode && <Notice debugMode /> }
115
+ <DebugBadge />
116
116
  { hideHeader ? (
117
117
  // Without the Page shell the tabs still own the content: it
118
118
  // renders inside the active tab's panel.
@@ -9,8 +9,6 @@ import { HashRouter } from 'react-router-dom';
9
9
  */
10
10
  import withWizardScreen from './';
11
11
 
12
- global.newspack_aux_data = { is_debug_mode: false };
13
-
14
12
  describe( 'withWizardScreen', () => {
15
13
  it( 'falls back to a single headerText breadcrumb when no breadcrumbItems are provided', () => {
16
14
  const WrappedComponent = () => <div>Body content</div>;
@@ -6,26 +6,25 @@ import classnames from 'classnames';
6
6
  /**
7
7
  * WordPress dependencies.
8
8
  */
9
- // Notice is aliased: `Notice` below is Newspack's own, which this file also uses.
10
9
  import {
11
10
  DropdownMenu,
12
11
  MenuGroup,
13
12
  MenuItem,
14
- Notice as CoreNotice,
13
+ Notice,
15
14
  SlotFillProvider,
16
15
  createSlotFill,
17
16
  // eslint-disable-next-line @wordpress/no-unsafe-wp-apis
18
17
  __experimentalVStack as VStack,
19
18
  } from '@wordpress/components';
20
19
  import { useDispatch, useSelect } from '@wordpress/data';
21
- import { cloneElement, createInterpolateElement, isValidElement, useEffect, useRef, useState, forwardRef } from '@wordpress/element';
20
+ import { cloneElement, createInterpolateElement, isValidElement, useLayoutEffect, useRef, useState, forwardRef } from '@wordpress/element';
22
21
  import { __ } from '@wordpress/i18n';
23
22
  import { category, chevronLeft, moreVertical } from '@wordpress/icons';
24
23
 
25
24
  /**
26
25
  * Internal dependencies
27
26
  */
28
- import { Footer, Notice, Button, TabbedNavigation, PluginInstaller, SectionHeader, HandoffMessage, Page, Waiting } from '../';
27
+ import { Footer, DebugBadge, Button, TabbedNavigation, PluginInstaller, SectionHeader, HandoffMessage, Page, Waiting } from '../';
29
28
  import { activeBreadcrumbs, appendSectionName } from './breadcrumbs-select';
30
29
  import Router from '../proxied-imports/router';
31
30
  import registerStore, { WIZARD_STORE_NAMESPACE } from './store';
@@ -92,7 +91,9 @@ const ResetHeaderData = () => {
92
91
  const location = useLocation();
93
92
  const { resetHeaderData } = useDispatch( WIZARD_STORE_NAMESPACE );
94
93
 
95
- useEffect( () => {
94
+ // Must stay before paint: a passive effect here would run after a section that
95
+ // publishes from a layout effect, wiping the header it just set.
96
+ useLayoutEffect( () => {
96
97
  resetHeaderData();
97
98
  window.scrollTo( 0, 0 );
98
99
  }, [ location.pathname, resetHeaderData ] );
@@ -177,6 +178,7 @@ const Wizard = (
177
178
  actions,
178
179
  backNav,
179
180
  badges,
181
+ fullWidth: headerFullWidth,
180
182
  sectionDescription,
181
183
  sectionMenu,
182
184
  sectionName,
@@ -269,7 +271,7 @@ const Wizard = (
269
271
  // as page chrome rather than as content.
270
272
  const inertGating = window.newspack_aux_data?.inert_gating;
271
273
  const inertGatingNotice = inertGating?.show && (
272
- <CoreNotice status="warning" isDismissible={ false } className="newspack-wizard__inert-gating-notice">
274
+ <Notice status="warning" isDismissible={ false } className="newspack-wizard__inert-gating-notice">
273
275
  { /* The conversion map takes childless elements and fills them from the
274
276
  translated string, so jsx-a11y can't see the content they end up with. */ }
275
277
  { interpolateOrPlainText( inertGating.message, {
@@ -279,7 +281,7 @@ const Wizard = (
279
281
  /* eslint-enable jsx-a11y/anchor-has-content */
280
282
  strong: <strong />,
281
283
  } ) }
282
- </CoreNotice>
284
+ </Notice>
283
285
  );
284
286
 
285
287
  const content = (
@@ -303,7 +305,7 @@ const Wizard = (
303
305
  render={ routerProps => (
304
306
  <div
305
307
  className={ classnames( 'newspack-wizard__content', className, {
306
- 'newspack-wizard__content--full-width': section.fullWidth,
308
+ 'newspack-wizard__content--full-width': headerFullWidth ?? section.fullWidth,
307
309
  } ) }
308
310
  >
309
311
  { 'function' === typeof renderAboveSections ? renderAboveSections() : null }
@@ -403,7 +405,7 @@ const Wizard = (
403
405
  } ) }
404
406
  >
405
407
  <HashRouter hashType="slash">
406
- { newspack_aux_data.is_debug_mode && <Notice debugMode /> }
408
+ <DebugBadge />
407
409
  <WizardHeaderRegion
408
410
  hideHeader={ hideHeader }
409
411
  headerText={ headerText }
@@ -7,19 +7,22 @@ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
7
7
  * WordPress dependencies.
8
8
  */
9
9
  import apiFetch from '@wordpress/api-fetch';
10
+ import { select, useDispatch } from '@wordpress/data';
11
+ import { useLayoutEffect, useState } from '@wordpress/element';
10
12
 
11
13
  /**
12
14
  * Internal dependencies.
13
15
  */
14
16
  import Wizard from './';
15
17
  import { useWizardData } from './store/utils';
18
+ import { WIZARD_STORE_NAMESPACE } from './store';
16
19
 
17
20
  jest.mock( '@wordpress/api-fetch', () => jest.fn() );
18
21
 
19
- // Both globals are localized onto every real wizard screen. The footer's
20
- // ExternalLink needs a string href, and the debug Notice reads aux data.
21
- window.newspack_aux_data = { is_debug_mode: false };
22
+ // Localized onto every real wizard screen; the footer's ExternalLink needs a
23
+ // string href.
22
24
  window.newspack_urls = { support: 'https://help.newspack.com/' };
25
+ window.scrollTo = jest.fn();
23
26
 
24
27
  const SETTINGS = { minimumDonation: '5' };
25
28
 
@@ -154,3 +157,93 @@ describe( 'Wizard', () => {
154
157
  expect( counts.wizard ).toBe( 0 );
155
158
  } );
156
159
  } );
160
+
161
+ const NarrowingSection = () => {
162
+ const { setHeaderData } = useDispatch( WIZARD_STORE_NAMESPACE );
163
+ useLayoutEffect( () => {
164
+ setHeaderData( { fullWidth: false } );
165
+ }, [ setHeaderData ] );
166
+ return <div>Narrowing section</div>;
167
+ };
168
+
169
+ const RecoveringSection = () => {
170
+ const { setHeaderData } = useDispatch( WIZARD_STORE_NAMESPACE );
171
+ const [ failed, setFailed ] = useState( true );
172
+ useLayoutEffect( () => {
173
+ setHeaderData( { fullWidth: failed ? false : undefined } );
174
+ }, [ setHeaderData, failed ] );
175
+ return <button onClick={ () => setFailed( false ) }>Retry</button>;
176
+ };
177
+
178
+ // Two sections, because that is what mounts ResetHeaderData: a single-section
179
+ // wizard never exercises the reset these overrides have to survive.
180
+ const widthSections = renderSection => [
181
+ { label: 'List', path: '/', exact: true, fullWidth: true, render: renderSection },
182
+ { label: 'Other', path: '/other', render: () => <div>Other section</div> },
183
+ ];
184
+
185
+ describe( 'Wizard content width', () => {
186
+ beforeEach( () => {
187
+ apiFetch.mockReset();
188
+ } );
189
+
190
+ it( 'renders full-width when the section declares it and nothing overrides it', async () => {
191
+ const { container } = render(
192
+ <Wizard
193
+ headerText="Test wizard"
194
+ sections={ widthSections( () => (
195
+ <div>List content</div>
196
+ ) ) }
197
+ />
198
+ );
199
+
200
+ expect( await screen.findByText( 'List content' ) ).toBeInTheDocument();
201
+ expect( container.querySelector( '.newspack-wizard__content' ) ).toHaveClass( 'newspack-wizard__content--full-width' );
202
+ } );
203
+
204
+ it( 'falls back to the standard width when a section overrides fullWidth before paint', async () => {
205
+ const { container } = render(
206
+ <Wizard
207
+ headerText="Test wizard"
208
+ sections={ widthSections( () => (
209
+ <NarrowingSection />
210
+ ) ) }
211
+ />
212
+ );
213
+
214
+ expect( await screen.findByText( 'Narrowing section' ) ).toBeInTheDocument();
215
+ expect( container.querySelector( '.newspack-wizard__content' ) ).not.toHaveClass( 'newspack-wizard__content--full-width' );
216
+ } );
217
+
218
+ it( 'restores the declared width when the section clears its override', async () => {
219
+ const { container } = render(
220
+ <Wizard
221
+ headerText="Test wizard"
222
+ sections={ widthSections( () => (
223
+ <RecoveringSection />
224
+ ) ) }
225
+ />
226
+ );
227
+
228
+ const retry = await screen.findByRole( 'button', { name: 'Retry' } );
229
+ expect( container.querySelector( '.newspack-wizard__content' ) ).not.toHaveClass( 'newspack-wizard__content--full-width' );
230
+
231
+ fireEvent.click( retry );
232
+
233
+ await waitFor( () => expect( container.querySelector( '.newspack-wizard__content' ) ).toHaveClass( 'newspack-wizard__content--full-width' ) );
234
+ } );
235
+
236
+ it( 'keeps a section header published from a layout effect through the mount reset', async () => {
237
+ render(
238
+ <Wizard
239
+ headerText="Test wizard"
240
+ sections={ widthSections( () => (
241
+ <NarrowingSection />
242
+ ) ) }
243
+ />
244
+ );
245
+
246
+ expect( await screen.findByText( 'Narrowing section' ) ).toBeInTheDocument();
247
+ expect( select( WIZARD_STORE_NAMESPACE ).getHeaderData().fullWidth ).toBe( false );
248
+ } );
249
+ } );
@@ -16,6 +16,8 @@ import { createAction } from './utils.js';
16
16
  export const WIZARD_STORE_NAMESPACE = 'newspack/wizards';
17
17
 
18
18
  const DEFAULT_STATE = {
19
+ // `fullWidth` is deliberately absent: a default here would narrow every
20
+ // full-width section on reset, since Wizard only falls back when it is unset.
19
21
  headerData: {
20
22
  actions: [],
21
23
  backNav: '',