newspack-components 4.10.0 → 4.12.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 (81) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/DEVELOPMENT.md +80 -38
  3. package/dist/cjs/breadcrumbs/index.js +1 -0
  4. package/dist/cjs/breadcrumbs/style.scss +3 -10
  5. package/dist/cjs/card/style-core.scss +1 -1
  6. package/dist/cjs/card-sortable-list/index.js +23 -12
  7. package/dist/cjs/card-sortable-list/index.test.js +21 -0
  8. package/dist/cjs/card-sortable-list/style.scss +8 -0
  9. package/dist/cjs/drawer/body.js +4 -6
  10. package/dist/cjs/drawer/content.js +7 -37
  11. package/dist/cjs/drawer/footer.js +10 -1
  12. package/dist/cjs/drawer/header.js +9 -1
  13. package/dist/cjs/drawer/index.test.js +3 -4
  14. package/dist/cjs/drawer/slots.test.js +25 -67
  15. package/dist/cjs/drawer/style.scss +0 -18
  16. package/dist/cjs/index.js +14 -0
  17. package/dist/cjs/page/index.js +10 -2
  18. package/dist/cjs/page/style.scss +24 -2
  19. package/dist/cjs/reorder-modal/index.js +521 -0
  20. package/dist/cjs/reorder-modal/index.test.js +1223 -0
  21. package/dist/cjs/reorder-modal/style.scss +114 -0
  22. package/dist/cjs/tabbed-navigation/style.scss +5 -0
  23. package/dist/cjs/tooltip-badge/index.js +50 -0
  24. package/dist/cjs/tooltip-badge/index.test.js +30 -0
  25. package/dist/cjs/wizard/index.js +21 -8
  26. package/dist/cjs/wizard/index.test.js +78 -0
  27. package/dist/cjs/wizard/store/index.js +2 -1
  28. package/dist/esm/breadcrumbs/index.js +1 -0
  29. package/dist/esm/breadcrumbs/style.scss +3 -10
  30. package/dist/esm/card/style-core.scss +1 -1
  31. package/dist/esm/card-sortable-list/index.js +25 -13
  32. package/dist/esm/card-sortable-list/index.test.js +21 -0
  33. package/dist/esm/card-sortable-list/style.scss +8 -0
  34. package/dist/esm/drawer/body.js +4 -5
  35. package/dist/esm/drawer/content.js +7 -36
  36. package/dist/esm/drawer/footer.js +10 -1
  37. package/dist/esm/drawer/header.js +9 -1
  38. package/dist/esm/drawer/index.test.js +3 -4
  39. package/dist/esm/drawer/slots.test.js +25 -67
  40. package/dist/esm/drawer/style.scss +0 -18
  41. package/dist/esm/index.js +2 -0
  42. package/dist/esm/page/index.js +10 -2
  43. package/dist/esm/page/style.scss +24 -2
  44. package/dist/esm/reorder-modal/index.js +514 -0
  45. package/dist/esm/reorder-modal/index.test.js +1224 -0
  46. package/dist/esm/reorder-modal/style.scss +114 -0
  47. package/dist/esm/tabbed-navigation/style.scss +5 -0
  48. package/dist/esm/tooltip-badge/index.js +43 -0
  49. package/dist/esm/tooltip-badge/index.test.js +26 -0
  50. package/dist/esm/wizard/index.js +23 -12
  51. package/dist/esm/wizard/index.test.js +78 -0
  52. package/dist/esm/wizard/store/index.js +2 -1
  53. package/package.json +2 -2
  54. package/src/breadcrumbs/index.js +1 -1
  55. package/src/breadcrumbs/style.scss +3 -10
  56. package/src/card/style-core.scss +1 -1
  57. package/src/card-form/README.md +2 -2
  58. package/src/card-sortable-list/index.test.js +13 -0
  59. package/src/card-sortable-list/index.tsx +31 -12
  60. package/src/card-sortable-list/style.scss +8 -0
  61. package/src/drawer/README.md +10 -16
  62. package/src/drawer/body.tsx +4 -5
  63. package/src/drawer/content.tsx +7 -33
  64. package/src/drawer/footer.tsx +8 -1
  65. package/src/drawer/header.tsx +8 -1
  66. package/src/drawer/index.test.js +3 -4
  67. package/src/drawer/slots.test.js +14 -48
  68. package/src/drawer/style.scss +0 -18
  69. package/src/drawer/types.ts +8 -3
  70. package/src/index.js +2 -0
  71. package/src/page/index.js +7 -3
  72. package/src/page/style.scss +24 -2
  73. package/src/reorder-modal/index.js +484 -0
  74. package/src/reorder-modal/index.test.js +566 -0
  75. package/src/reorder-modal/style.scss +114 -0
  76. package/src/tabbed-navigation/style.scss +5 -0
  77. package/src/tooltip-badge/index.test.js +24 -0
  78. package/src/tooltip-badge/index.tsx +40 -0
  79. package/src/wizard/index.js +25 -14
  80. package/src/wizard/index.test.js +38 -0
  81. package/src/wizard/store/index.js +1 -0
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * WordPress dependencies.
3
3
  */
4
- import { __experimentalVStack as VStack } from '@wordpress/components'; // eslint-disable-line @wordpress/no-unsafe-wp-apis
5
4
  import { useEffect, useState } from '@wordpress/element';
6
5
  import { __ } from '@wordpress/i18n';
6
+ import { Stack } from '@wordpress/ui';
7
7
 
8
8
  /**
9
9
  * Internal dependencies.
@@ -42,18 +42,17 @@ const Body = ( { children }: DrawerBodyProps ) => {
42
42
  }, [ node ] );
43
43
 
44
44
  return (
45
- <VStack
45
+ <Stack
46
46
  ref={ setNode }
47
+ direction="column"
47
48
  className="newspack-drawer__body"
48
- spacing={ 0 }
49
- justify="flex-start"
50
49
  // A bare div is role=generic, where ARIA prohibits an author name.
51
50
  role={ isScrollable ? 'group' : undefined }
52
51
  tabIndex={ isScrollable ? 0 : undefined }
53
52
  aria-label={ isScrollable ? __( 'Scrollable section', 'newspack-plugin' ) : undefined }
54
53
  >
55
54
  { children }
56
- </VStack>
55
+ </Stack>
57
56
  );
58
57
  };
59
58
 
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * WordPress dependencies.
3
3
  */
4
- import { __experimentalVStack as VStack } from '@wordpress/components'; // eslint-disable-line @wordpress/no-unsafe-wp-apis
5
- import { Children, isValidElement } from '@wordpress/element';
4
+ import { Stack } from '@wordpress/ui';
6
5
 
7
6
  /**
8
7
  * External dependencies.
@@ -14,41 +13,16 @@ import classnames from 'classnames';
14
13
  */
15
14
  import type { DrawerContentProps } from './types';
16
15
 
17
- // VStack drops non-elements, a lone string aside. A run shares one wrapper so
18
- // `Edited by { name }` is one row, not three.
19
- const asRows = ( children: React.ReactNode ) => {
20
- if ( 'string' === typeof children ) {
21
- return children;
22
- }
23
- const rows: React.ReactNode[] = [];
24
- let text: React.ReactNode[] = [];
25
- const flushText = () => {
26
- if ( text.some( part => '' !== String( part ).trim() ) ) {
27
- rows.push( <div key={ `text-${ rows.length }` }>{ text }</div> );
28
- }
29
- text = [];
30
- };
31
- Children.toArray( children ).forEach( child => {
32
- if ( isValidElement( child ) ) {
33
- flushText();
34
- rows.push( child );
35
- } else {
36
- text.push( child );
37
- }
38
- } );
39
- flushText();
40
- return rows;
41
- };
42
-
43
- const Content = ( { padding = 6, gap = 4, className, children }: DrawerContentProps ) => (
44
- <VStack
16
+ const Content = ( { padding = 6, gap = 'lg', className, children }: DrawerContentProps ) => (
17
+ <Stack
18
+ direction="column"
19
+ gap={ gap }
45
20
  className={ classnames( 'newspack-drawer__content', className ) }
46
- spacing={ gap }
47
21
  // A custom property, not inline padding, so the stylesheet's seam rules win.
48
22
  style={ { '--newspack-drawer-content-padding': `${ padding * 4 }px` } as React.CSSProperties }
49
23
  >
50
- { asRows( children ) }
51
- </VStack>
24
+ { children }
25
+ </Stack>
52
26
  );
53
27
 
54
28
  export default Content;
@@ -1,3 +1,8 @@
1
+ /**
2
+ * WordPress dependencies.
3
+ */
4
+ import { Stack } from '@wordpress/ui';
5
+
1
6
  /**
2
7
  * External dependencies.
3
8
  */
@@ -9,7 +14,9 @@ import classnames from 'classnames';
9
14
  import type { DrawerFooterProps } from './types';
10
15
 
11
16
  const Footer = ( { className, children }: DrawerFooterProps ) => (
12
- <div className={ classnames( 'newspack-drawer__footer', className ) }>{ children }</div>
17
+ <Stack direction="row" justify="flex-end" wrap="wrap" gap="sm" className={ classnames( 'newspack-drawer__footer', className ) }>
18
+ { children }
19
+ </Stack>
13
20
  );
14
21
 
15
22
  export default Footer;
@@ -1,3 +1,8 @@
1
+ /**
2
+ * WordPress dependencies.
3
+ */
4
+ import { Stack } from '@wordpress/ui';
5
+
1
6
  /**
2
7
  * External dependencies.
3
8
  */
@@ -9,7 +14,9 @@ import classnames from 'classnames';
9
14
  import type { DrawerHeaderProps } from './types';
10
15
 
11
16
  const Header = ( { className, children }: DrawerHeaderProps ) => (
12
- <div className={ classnames( 'newspack-drawer__header', className ) }>{ children }</div>
17
+ <Stack direction="row" align="center" gap="sm" className={ classnames( 'newspack-drawer__header', className ) }>
18
+ { children }
19
+ </Stack>
13
20
  );
14
21
 
15
22
  export default Header;
@@ -318,13 +318,12 @@ describe( 'Drawer sections', () => {
318
318
  } );
319
319
 
320
320
  // The `> * { flex: none }` rule that stops sections compressing depends on this.
321
- it( 'lays the scroll container out as a flush VStack', () => {
321
+ it( 'lays the scroll container out as a flush column', () => {
322
322
  renderDrawer( { contents: [ <Drawer.Content key="one">One</Drawer.Content> ] } );
323
323
 
324
324
  const body = bodyOf( screen.getByText( 'One' ) );
325
- expect( body ).toHaveClass( 'components-v-stack' );
326
- expect( window.getComputedStyle( body ).gap ).toBe( '0' );
327
- expect( window.getComputedStyle( body ).justifyContent ).toBe( 'flex-start' );
325
+ expect( body.style.flexDirection ).toBe( 'column' );
326
+ expect( body.style.gap ).toBe( '' );
328
327
  } );
329
328
 
330
329
  it( 'starts a new container after a non-content child', () => {
@@ -83,65 +83,23 @@ describe( 'Drawer.Content', () => {
83
83
  expect( sectionOf( container ).style.getPropertyValue( '--newspack-drawer-content-padding' ) ).toBe( '0px' );
84
84
  } );
85
85
 
86
- it( 'renders each section as a VStack', () => {
87
- const { container } = render( <Content>stacked</Content> );
88
- expect( sectionOf( container ) ).toHaveClass( 'newspack-drawer__content', 'components-v-stack' );
89
- } );
90
-
91
- // VStack emits its spacing as a `calc()` on the 4px base, not as pixels.
92
- it( 'spaces its children by 16px, and by the gap prop', () => {
86
+ it( 'spaces its children by the lg gap token, and by the gap prop', () => {
93
87
  const { container, rerender } = render( <Content>stacked</Content> );
94
- expect( window.getComputedStyle( sectionOf( container ) ).gap ).toBe( 'calc(4px * 4)' );
88
+ expect( sectionOf( container ).style.gap ).toContain( 'gap-lg' );
95
89
 
96
- rerender( <Content gap={ 2 }>stacked</Content> );
97
- expect( window.getComputedStyle( sectionOf( container ) ).gap ).toBe( 'calc(4px * 2)' );
90
+ rerender( <Content gap="sm">stacked</Content> );
91
+ expect( sectionOf( container ).style.gap ).toContain( 'gap-sm' );
98
92
  } );
99
93
 
100
- // VStack keeps only elements, so these would otherwise vanish without warning.
101
94
  it( 'keeps plain text sitting beside an element', () => {
102
- render(
95
+ const { container } = render(
103
96
  <Content>
104
97
  Lead text
105
98
  <span>an element</span>
106
99
  { 42 }
107
100
  </Content>
108
101
  );
109
- expect( screen.getByText( 'Lead text' ) ).toBeInTheDocument();
110
- expect( screen.getByText( 'an element' ) ).toBeInTheDocument();
111
- expect( screen.getByText( '42' ) ).toBeInTheDocument();
112
- } );
113
-
114
- it( 'keeps a run of text and interpolations on one row', () => {
115
- const { container } = render( <Content>Edited by { 'Ada' } just now</Content> );
116
- const rows = sectionOf( container ).children;
117
- expect( rows ).toHaveLength( 1 );
118
- expect( rows[ 0 ] ).toHaveTextContent( 'Edited by Ada just now' );
119
- } );
120
-
121
- it( 'starts a new row either side of an element', () => {
122
- const { container } = render(
123
- <Content>
124
- before
125
- <span>middle</span>
126
- after
127
- </Content>
128
- );
129
- expect( Array.from( sectionOf( container ).children ).map( row => row.textContent ) ).toEqual( [ 'before', 'middle', 'after' ] );
130
- } );
131
-
132
- it( 'emits no row for whitespace or empty text between elements', () => {
133
- const { container } = render(
134
- <Content>
135
- <span>one</span> { '' }
136
- <span>two</span>
137
- </Content>
138
- );
139
- expect( Array.from( sectionOf( container ).children ).map( row => row.textContent ) ).toEqual( [ 'one', 'two' ] );
140
- } );
141
-
142
- it( 'keeps a lone string', () => {
143
- render( <Content>on its own</Content> );
144
- expect( screen.getByText( 'on its own' ) ).toBeInTheDocument();
102
+ expect( sectionOf( container ) ).toHaveTextContent( 'Lead textan element42' );
145
103
  } );
146
104
  } );
147
105
 
@@ -160,6 +118,14 @@ describe( 'Drawer.Footer', () => {
160
118
  expect( screen.getByText( 'actions' ) ).toHaveClass( 'newspack-drawer__footer' );
161
119
  } );
162
120
 
121
+ it( 'lays its actions out in one end-aligned row that wraps', () => {
122
+ render( <Footer>actions</Footer> );
123
+ const footer = screen.getByText( 'actions' );
124
+ expect( footer.style.flexDirection ).toBe( 'row' );
125
+ expect( footer.style.justifyContent ).toBe( 'flex-end' );
126
+ expect( footer.style.flexWrap ).toBe( 'wrap' );
127
+ } );
128
+
163
129
  it( 'renders its actions as direct children', () => {
164
130
  const { container } = render(
165
131
  <Footer>
@@ -80,10 +80,7 @@ $drawer-size-x-large: 640px;
80
80
  }
81
81
 
82
82
  .newspack-drawer__header {
83
- align-items: center;
84
- display: flex;
85
83
  flex: 0 0 auto;
86
- gap: wp-vars.$grid-unit-10;
87
84
  padding: wp-vars.$grid-unit-30 wp-vars.$grid-unit-30 wp-vars.$grid-unit-20;
88
85
  }
89
86
 
@@ -139,23 +136,8 @@ $drawer-size-x-large: 640px;
139
136
 
140
137
  .newspack-drawer__footer {
141
138
  background: wp-colors.$white;
142
- display: flex;
143
139
  flex: 0 0 auto;
144
- gap: wp-vars.$grid-unit-10;
145
140
  padding: wp-vars.$grid-unit-20 wp-vars.$grid-unit-30 wp-vars.$grid-unit-30;
146
-
147
- > .components-button {
148
- flex: 1;
149
- justify-content: center;
150
- }
151
-
152
- &:has(> :nth-child(3)) {
153
- flex-direction: column;
154
-
155
- > .components-button {
156
- flex: 0 0 auto;
157
- }
158
- }
159
141
  }
160
142
 
161
143
  @keyframes newspack-drawer-slide-in {
@@ -1,3 +1,8 @@
1
+ /**
2
+ * WordPress dependencies.
3
+ */
4
+ import type { Stack } from '@wordpress/ui';
5
+
1
6
  export type DrawerSize = 'small' | 'medium' | 'large' | 'x-large' | 'full';
2
7
 
3
8
  /** `text` is null unless the title's children are a plain string. */
@@ -39,10 +44,10 @@ export type DrawerCloseIconProps = {
39
44
  };
40
45
 
41
46
  export type DrawerContentProps = {
42
- /** 4px scale, as VStack's `spacing`. `0` for a flush section. */
47
+ /** 4px scale: `6` is 24px, `0` a flush section. */
43
48
  padding?: number;
44
- /** 4px scale, as VStack's `spacing`. */
45
- gap?: number;
49
+ /** A `Stack` gap token from `@wordpress/ui`. */
50
+ gap?: React.ComponentProps< typeof Stack >[ 'gap' ];
46
51
  className?: string;
47
52
  children?: React.ReactNode;
48
53
  };
package/src/index.js CHANGED
@@ -39,6 +39,7 @@ export { default as PluginToggle } from './plugin-toggle';
39
39
  export { default as Popover } from './popover';
40
40
  export { default as ProgressBar } from './progress-bar';
41
41
  export { default as RadioControl } from './radio-control';
42
+ export { default as ReorderModal } from './reorder-modal';
42
43
  export { default as SectionHeader } from './section-header';
43
44
  export { default as SelectControl } from './select-control';
44
45
  export { default as Settings } from './settings';
@@ -50,6 +51,7 @@ export { default as StyleCard } from './style-card';
50
51
  export { default as TabbedNavigation } from './tabbed-navigation';
51
52
  export { default as TableCard } from './table-card';
52
53
  export { default as TextControl } from './text-control';
54
+ export { default as TooltipBadge } from './tooltip-badge';
53
55
  export { default as Waiting } from './waiting';
54
56
  export { default as WebPreview } from './web-preview';
55
57
  export { default as Wizard } from './wizard';
package/src/page/index.js CHANGED
@@ -49,15 +49,19 @@ const Page = ( { breadcrumbItems = [], badges, subTitle, actions, tabbedNavigati
49
49
  <>
50
50
  <Stack direction="column" className="newspack-page__header-region">
51
51
  <Stack direction="column" className="newspack-page__header">
52
- <Stack direction="row" gap="sm" justify="space-between">
53
- <Stack direction="row" gap="sm" align="center" justify="start">
52
+ <Stack direction="row" gap="sm" justify="space-between" wrap="wrap">
53
+ <Stack direction="row" gap="sm" align="start" justify="start" className="newspack-page__header-title">
54
54
  <div className="newspack-page__header-visual" aria-hidden="true">
55
55
  <Icon icon={ newspack } />
56
56
  </div>
57
57
  <HStack className="newspack-page__breadcrumbs" justify="flex-start">
58
58
  <Breadcrumbs items={ breadcrumbItems } />
59
59
  </HStack>
60
- { badges }
60
+ { badges && (
61
+ <Stack direction="row" gap="sm" align="center" className="newspack-page__header-badges">
62
+ { badges }
63
+ </Stack>
64
+ ) }
61
65
  </Stack>
62
66
  { actions && (
63
67
  <Stack direction="row" gap="sm" align="center" className="newspack-page__header-actions">
@@ -56,8 +56,29 @@
56
56
  padding: var(--wpds-dimension-padding-lg) var(--wpds-dimension-padding-2xl);
57
57
  }
58
58
 
59
+ .newspack-page__header-title {
60
+ min-width: 0;
61
+ }
62
+
59
63
  .newspack-page__header-actions {
64
+ flex-basis: 100%;
65
+ flex-shrink: 0;
66
+ justify-content: flex-end;
67
+ margin-inline-start: auto;
68
+
69
+ @include wp-mixins.break-medium {
70
+ flex-basis: auto;
71
+ }
72
+
73
+ &:empty,
74
+ &:has(> :only-child:empty) {
75
+ display: none;
76
+ }
77
+ }
78
+
79
+ .newspack-page__header-badges {
60
80
  flex-shrink: 0;
81
+ min-height: wp-vars.$button-size;
61
82
  }
62
83
 
63
84
  .newspack-page__header-subtitle {
@@ -68,7 +89,7 @@
68
89
  .newspack-page__header-visual {
69
90
  display: grid;
70
91
  flex-shrink: 0;
71
- height: 24px;
92
+ height: wp-vars.$button-size;
72
93
  place-items: center;
73
94
  width: 24px;
74
95
 
@@ -80,5 +101,6 @@
80
101
  }
81
102
 
82
103
  .newspack-page__breadcrumbs {
83
- min-height: wp-vars.$button-size;
104
+ min-width: 0;
105
+ padding-block: calc((#{wp-vars.$button-size} - var(--wpds-typography-line-height-md)) / 2);
84
106
  }