newspack-components 4.1.0 → 4.3.0

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 (135) hide show
  1. package/colors/colors.module.scss +1 -1
  2. package/dist/cjs/action-card/index.js +21 -12
  3. package/dist/cjs/action-card/style.scss +32 -14
  4. package/dist/cjs/autocomplete-tokenfield/index.js +2 -2
  5. package/dist/cjs/autocomplete-with-latest-posts/index.js +289 -0
  6. package/dist/cjs/autocomplete-with-latest-posts/style.scss +150 -0
  7. package/dist/cjs/badge/index.js +32 -0
  8. package/dist/cjs/badge/style.scss +36 -0
  9. package/dist/cjs/button/index.js +2 -0
  10. package/dist/cjs/card/core-card.js +141 -0
  11. package/dist/cjs/card/index.js +33 -0
  12. package/dist/cjs/card/style-core.scss +227 -0
  13. package/dist/cjs/card/style.scss +2 -2
  14. package/dist/cjs/card-settings-group/index.js +49 -0
  15. package/dist/cjs/card-settings-group/style.scss +33 -0
  16. package/dist/cjs/card-sortable-list/index.js +390 -0
  17. package/dist/cjs/card-sortable-list/style.scss +48 -0
  18. package/dist/cjs/confirm-dialog/index.js +59 -0
  19. package/dist/cjs/divider/index.js +42 -0
  20. package/dist/cjs/divider/style.scss +51 -0
  21. package/dist/cjs/form-token-field/style.scss +0 -5
  22. package/dist/cjs/grid/index.js +2 -1
  23. package/dist/cjs/grid/style.scss +63 -9
  24. package/dist/cjs/index.js +42 -0
  25. package/dist/cjs/modal/index.js +11 -4
  26. package/dist/cjs/modal/style.scss +43 -13
  27. package/dist/cjs/newspack-icon/style.scss +5 -3
  28. package/dist/cjs/popover/style.scss +1 -1
  29. package/dist/cjs/section-header/index.js +76 -17
  30. package/dist/cjs/section-header/style.scss +109 -4
  31. package/dist/cjs/style.scss +2 -0
  32. package/dist/cjs/waiting/index.js +3 -1
  33. package/dist/cjs/waiting/style.scss +4 -0
  34. package/dist/cjs/web-preview/index.js +98 -39
  35. package/dist/cjs/web-preview/style.scss +20 -53
  36. package/dist/cjs/with-wizard/index.js +1 -1
  37. package/dist/cjs/with-wizard/style.scss +24 -6
  38. package/dist/cjs/with-wizard-screen/style.scss +39 -10
  39. package/dist/cjs/wizard/components/WizardSnackbar.js +70 -0
  40. package/dist/cjs/wizard/components/style.scss +48 -0
  41. package/dist/cjs/wizard/index.js +97 -16
  42. package/dist/cjs/wizard/store/index.js +46 -0
  43. package/dist/esm/action-card/index.js +21 -12
  44. package/dist/esm/action-card/style.scss +32 -14
  45. package/dist/esm/autocomplete-tokenfield/index.js +2 -2
  46. package/dist/esm/autocomplete-with-latest-posts/index.js +281 -0
  47. package/dist/esm/autocomplete-with-latest-posts/style.scss +150 -0
  48. package/dist/esm/badge/index.js +24 -0
  49. package/dist/esm/badge/style.scss +36 -0
  50. package/dist/esm/button/index.js +2 -0
  51. package/dist/esm/card/core-card.js +133 -0
  52. package/dist/esm/card/index.js +33 -0
  53. package/dist/esm/card/style-core.scss +227 -0
  54. package/dist/esm/card/style.scss +2 -2
  55. package/dist/esm/card-settings-group/index.js +42 -0
  56. package/dist/esm/card-settings-group/style.scss +33 -0
  57. package/dist/esm/card-sortable-list/index.js +381 -0
  58. package/dist/esm/card-sortable-list/style.scss +48 -0
  59. package/dist/esm/confirm-dialog/index.js +49 -0
  60. package/dist/esm/divider/index.js +34 -0
  61. package/dist/esm/divider/style.scss +51 -0
  62. package/dist/esm/form-token-field/style.scss +0 -5
  63. package/dist/esm/grid/index.js +2 -1
  64. package/dist/esm/grid/style.scss +63 -9
  65. package/dist/esm/index.js +6 -0
  66. package/dist/esm/modal/index.js +11 -4
  67. package/dist/esm/modal/style.scss +43 -13
  68. package/dist/esm/newspack-icon/style.scss +5 -3
  69. package/dist/esm/popover/style.scss +1 -1
  70. package/dist/esm/section-header/index.js +75 -18
  71. package/dist/esm/section-header/style.scss +109 -4
  72. package/dist/esm/style.scss +2 -0
  73. package/dist/esm/waiting/index.js +3 -1
  74. package/dist/esm/waiting/style.scss +4 -0
  75. package/dist/esm/web-preview/index.js +101 -42
  76. package/dist/esm/web-preview/style.scss +20 -53
  77. package/dist/esm/with-wizard/index.js +1 -1
  78. package/dist/esm/with-wizard/style.scss +24 -6
  79. package/dist/esm/with-wizard-screen/style.scss +39 -10
  80. package/dist/esm/wizard/components/WizardSnackbar.js +64 -0
  81. package/dist/esm/wizard/components/style.scss +48 -0
  82. package/dist/esm/wizard/index.js +101 -20
  83. package/dist/esm/wizard/store/index.js +46 -0
  84. package/package.json +5 -6
  85. package/shared/author-context.js +26 -0
  86. package/shared/hooks/use-coauthors.js +220 -0
  87. package/shared/hooks/use-coauthors.test.js +624 -0
  88. package/shared/hooks/use-custom-byline.js +47 -0
  89. package/shared/hooks/use-custom-byline.test.js +133 -0
  90. package/shared/scss/_mixins.scss +46 -0
  91. package/src/action-card/action-card.d.ts +7 -3
  92. package/src/action-card/index.js +24 -11
  93. package/src/action-card/style.scss +32 -14
  94. package/src/autocomplete-tokenfield/index.js +2 -2
  95. package/src/autocomplete-with-latest-posts/index.js +290 -0
  96. package/src/autocomplete-with-latest-posts/style.scss +150 -0
  97. package/src/badge/index.tsx +24 -0
  98. package/src/badge/style.scss +36 -0
  99. package/src/button/index.tsx +3 -2
  100. package/src/card/core-card.js +147 -0
  101. package/src/card/index.js +39 -1
  102. package/src/card/style-core.scss +227 -0
  103. package/src/card/style.scss +2 -2
  104. package/src/card-settings-group/README.md +43 -0
  105. package/src/card-settings-group/index.tsx +53 -0
  106. package/src/card-settings-group/style.scss +33 -0
  107. package/src/card-sortable-list/README.md +51 -0
  108. package/src/card-sortable-list/index.tsx +412 -0
  109. package/src/card-sortable-list/style.scss +48 -0
  110. package/src/confirm-dialog/index.tsx +64 -0
  111. package/src/divider/README.md +25 -0
  112. package/src/divider/index.js +31 -0
  113. package/src/divider/style.scss +51 -0
  114. package/src/form-token-field/style.scss +0 -5
  115. package/src/grid/index.tsx +2 -1
  116. package/src/grid/style.scss +63 -9
  117. package/src/index.js +6 -0
  118. package/src/modal/index.js +12 -3
  119. package/src/modal/style.scss +43 -13
  120. package/src/newspack-icon/style.scss +5 -3
  121. package/src/popover/style.scss +1 -1
  122. package/src/section-header/index.js +72 -13
  123. package/src/section-header/style.scss +109 -4
  124. package/src/style.scss +2 -0
  125. package/src/waiting/index.js +2 -1
  126. package/src/waiting/style.scss +4 -0
  127. package/src/web-preview/index.js +90 -35
  128. package/src/web-preview/style.scss +20 -53
  129. package/src/with-wizard/index.js +1 -1
  130. package/src/with-wizard/style.scss +24 -6
  131. package/src/with-wizard-screen/style.scss +39 -10
  132. package/src/wizard/components/WizardSnackbar.js +58 -0
  133. package/src/wizard/components/style.scss +48 -0
  134. package/src/wizard/index.js +108 -14
  135. package/src/wizard/store/index.js +26 -0
@@ -0,0 +1,133 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { renderHook } from '@testing-library/react';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { useSelect } from '@wordpress/data';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import { useCustomByline, extractAuthorIdsFromByline } from './use-custom-byline';
15
+
16
+ jest.mock( '@wordpress/data', () => ( {
17
+ useSelect: jest.fn(),
18
+ } ) );
19
+
20
+ jest.mock( '@wordpress/core-data', () => ( {
21
+ store: 'core',
22
+ } ) );
23
+
24
+ describe( 'useCustomByline', () => {
25
+ beforeEach( () => {
26
+ jest.clearAllMocks();
27
+ } );
28
+
29
+ it( 'should return inactive byline when meta is not set', () => {
30
+ useSelect.mockImplementation( callback =>
31
+ callback( () => ( {
32
+ getEditedEntityRecord: () => ( {} ),
33
+ } ) )
34
+ );
35
+
36
+ const { result } = renderHook( () => useCustomByline( 123, 'post' ) );
37
+
38
+ expect( result.current.bylineActive ).toBe( false );
39
+ expect( result.current.bylineContent ).toBe( '' );
40
+ } );
41
+
42
+ it( 'should return inactive byline when meta exists but byline is inactive', () => {
43
+ useSelect.mockImplementation( callback =>
44
+ callback( () => ( {
45
+ getEditedEntityRecord: () => ( {
46
+ meta: {
47
+ _newspack_byline_active: false,
48
+ _newspack_byline: 'Some content',
49
+ },
50
+ } ),
51
+ } ) )
52
+ );
53
+
54
+ const { result } = renderHook( () => useCustomByline( 123, 'post' ) );
55
+
56
+ expect( result.current.bylineActive ).toBe( false );
57
+ expect( result.current.bylineContent ).toBe( 'Some content' );
58
+ } );
59
+
60
+ it( 'should return active byline with content', () => {
61
+ const bylineContent = 'By [Author id=5]Jane Doe[/Author]';
62
+
63
+ useSelect.mockImplementation( callback =>
64
+ callback( () => ( {
65
+ getEditedEntityRecord: () => ( {
66
+ meta: {
67
+ _newspack_byline_active: true,
68
+ _newspack_byline: bylineContent,
69
+ },
70
+ } ),
71
+ } ) )
72
+ );
73
+
74
+ const { result } = renderHook( () => useCustomByline( 123, 'post' ) );
75
+
76
+ expect( result.current.bylineActive ).toBe( true );
77
+ expect( result.current.bylineContent ).toBe( bylineContent );
78
+ } );
79
+
80
+ it( 'should handle null post record gracefully', () => {
81
+ useSelect.mockImplementation( callback =>
82
+ callback( () => ( {
83
+ getEditedEntityRecord: () => null,
84
+ } ) )
85
+ );
86
+
87
+ const { result } = renderHook( () => useCustomByline( 123, 'post' ) );
88
+
89
+ expect( result.current.bylineActive ).toBe( false );
90
+ expect( result.current.bylineContent ).toBe( '' );
91
+ } );
92
+ } );
93
+
94
+ describe( 'extractAuthorIdsFromByline', () => {
95
+ it( 'should return empty array for empty content', () => {
96
+ expect( extractAuthorIdsFromByline( '' ) ).toEqual( [] );
97
+ expect( extractAuthorIdsFromByline( null ) ).toEqual( [] );
98
+ expect( extractAuthorIdsFromByline( undefined ) ).toEqual( [] );
99
+ } );
100
+
101
+ it( 'should extract single author ID', () => {
102
+ const byline = 'By [Author id=5]Jane Doe[/Author]';
103
+ expect( extractAuthorIdsFromByline( byline ) ).toEqual( [ 5 ] );
104
+ } );
105
+
106
+ it( 'should extract multiple author IDs', () => {
107
+ const byline = 'By [Author id=5]Jane Doe[/Author] and [Author id=12]John Smith[/Author]';
108
+ expect( extractAuthorIdsFromByline( byline ) ).toEqual( [ 5, 12 ] );
109
+ } );
110
+
111
+ it( 'should handle content with no author shortcodes', () => {
112
+ const byline = 'By Some Random Text';
113
+ expect( extractAuthorIdsFromByline( byline ) ).toEqual( [] );
114
+ } );
115
+
116
+ it( 'should handle byline with HTML entities', () => {
117
+ const byline = 'By [Author id=17]Darth Vader[/Author] with love';
118
+ expect( extractAuthorIdsFromByline( byline ) ).toEqual( [ 17 ] );
119
+ } );
120
+
121
+ it( 'should return integers, not strings', () => {
122
+ const byline = '[Author id=123]Test[/Author]';
123
+ const result = extractAuthorIdsFromByline( byline );
124
+ expect( result ).toEqual( [ 123 ] );
125
+ expect( typeof result[ 0 ] ).toBe( 'number' );
126
+ } );
127
+
128
+ it( 'should tolerate whitespace and case variations in shortcode', () => {
129
+ expect( extractAuthorIdsFromByline( '[author id=7]Name[/Author]' ) ).toEqual( [ 7 ] );
130
+ expect( extractAuthorIdsFromByline( '[Author id = 5]Name[/Author]' ) ).toEqual( [ 5 ] );
131
+ expect( extractAuthorIdsFromByline( '[AUTHOR ID=3]Name[/Author]' ) ).toEqual( [ 3 ] );
132
+ } );
133
+ } );
@@ -10,3 +10,49 @@
10
10
  position: absolute !important;
11
11
  word-wrap: normal !important; /* Many screen reader and browser combinations announce broken words as they would appear visually. */
12
12
  }
13
+
14
+ // Circular social button style
15
+ // Used by: author-profile-social, author-social-link blocks
16
+ // Hex fallbacks from newspack-block-theme theme.json defaults.
17
+ @mixin socialButton {
18
+ background: var(--wp--preset--color--base-2, #f0f0f0);
19
+ border-radius: 50%; // Circular
20
+ color: var(--wp--preset--color--contrast, #1e1e1e);
21
+ display: grid;
22
+ place-items: center;
23
+ text-decoration: none;
24
+ transition: background 125ms ease-in-out, color 125ms ease-in-out;
25
+
26
+ // Sizing - makes circular by using equal width/height
27
+ // --icon-size controls the circle diameter (e.g., 36px)
28
+ // Icon inside is 2/3 of circle size (e.g., 24px icon in 36px circle)
29
+ aspect-ratio: 1;
30
+ width: calc(var(--icon-size, 24px) * 1.5);
31
+ height: calc(var(--icon-size, 24px) * 1.5);
32
+ padding: 0;
33
+
34
+ &:hover {
35
+ background: var(--wp--preset--color--base-3, #ddd);
36
+ color: var(--wp--preset--color--contrast, #1e1e1e);
37
+ text-decoration: none;
38
+ }
39
+
40
+ &:focus {
41
+ outline: none;
42
+ }
43
+
44
+ &:focus-visible {
45
+ outline: 2px solid;
46
+ outline-offset: 1px;
47
+ }
48
+
49
+ // SVG icon sizing - icon is 2/3 of button size
50
+ // If button is 36px, icon is 24px (24/36 = 2/3)
51
+ svg,
52
+ span {
53
+ display: block;
54
+ fill: currentcolor;
55
+ width: var(--icon-size, 24px);
56
+ height: var(--icon-size, 24px);
57
+ }
58
+ }
@@ -6,6 +6,7 @@ export interface ActionCardProps {
6
6
  description?: string | React.ReactNode;
7
7
  actionText?: React.ReactNode | string | null;
8
8
  badge?: string;
9
+ badgeLevel?: 'success' | 'info' | 'warning' | 'error';
9
10
  className?: string;
10
11
  indent?: string;
11
12
  notification?: string | Error | null;
@@ -13,8 +14,10 @@ export interface ActionCardProps {
13
14
  isMedium?: boolean;
14
15
  disabled?: boolean | string;
15
16
  hasGreyHeader?: boolean;
17
+ heading?: number;
16
18
  toggleChecked?: boolean;
17
- toggleOnChange?: ( a: boolean ) => void;
19
+ toggleOnChange?: (a: boolean) => void;
20
+ togglePosition?: 'leading' | 'trailing';
18
21
  actionContent?: boolean | React.ReactNode | null;
19
22
  error?: Error | string | null;
20
23
  handoff?: string | null;
@@ -33,8 +36,9 @@ export interface ActionCardProps {
33
36
  noMargin?: boolean;
34
37
  collapse?: boolean;
35
38
  expandable?: boolean;
39
+ isExpanded?: boolean;
36
40
  draggable?: boolean;
37
41
  dragIndex?: number;
38
- onDragCallback?: ( index: number ) => void;
39
- dragWrapperRef?: React.MutableRefObject< HTMLDivElement | null >;
42
+ onDragCallback?: (index: number) => void;
43
+ dragWrapperRef?: React.MutableRefObject<HTMLDivElement | null>;
40
44
  }
@@ -29,13 +29,14 @@ import classnames from 'classnames';
29
29
  */
30
30
  const ActionCard = ( {
31
31
  badge,
32
- badgeLevel = 'info',
32
+ badgeLevel,
33
33
  className,
34
34
  checkbox,
35
35
  children,
36
36
  collapse,
37
37
  disabled,
38
38
  title,
39
+ heading = 2,
39
40
  description,
40
41
  handoff,
41
42
  editLink,
@@ -60,11 +61,13 @@ const ActionCard = ( {
60
61
  titleLink,
61
62
  toggleChecked = false,
62
63
  toggleOnChange,
64
+ togglePosition = 'leading',
63
65
  hasGreyHeader,
64
66
  hasWhiteHeader,
65
67
  noBorder,
66
68
  isPending,
67
69
  expandable = false,
70
+ isExpanded,
68
71
  isButtonEnabled = false,
69
72
  // Draggable props. All are required to enable drag sorting.
70
73
  draggable = false,
@@ -72,11 +75,17 @@ const ActionCard = ( {
72
75
  dragWrapperRef,
73
76
  onDragCallback,
74
77
  } ) => {
75
- const [ expanded, setExpanded ] = useState( false );
78
+ const [ expanded, setExpanded ] = useState( Boolean( isExpanded ) );
76
79
  const [ dragging, setDragging ] = useState( false );
77
80
  const [ targetIndex, setTargetIndex ] = useState( null );
78
81
  const [ dragRef, setDragRef ] = useState( null );
79
82
 
83
+ useEffect( () => {
84
+ if ( typeof isExpanded === 'boolean' ) {
85
+ setExpanded( isExpanded );
86
+ }
87
+ }, [ isExpanded ] );
88
+
80
89
  useEffect( () => {
81
90
  if ( dragWrapperRef && ! dragRef ) {
82
91
  setDragRef( dragWrapperRef );
@@ -109,14 +118,18 @@ const ActionCard = ( {
109
118
  return url ? { backgroundImage: `url(${ url })` } : {};
110
119
  };
111
120
  const titleProps = toggleOnChange && ! titleLink && ! disabled ? { onClick: () => toggleOnChange( ! toggleChecked ), tabIndex: '0' } : {};
121
+ const togglePositionClass = togglePosition === 'trailing' ? 'is-toggle-trailing' : 'is-toggle-leading';
112
122
  const hasInternalLink = href && href.indexOf( 'http' ) !== 0;
113
123
  const isDisplayingSecondaryAction = secondaryActionText && onSecondaryActionClick;
114
124
  const badges = ! Array.isArray( badge ) && badge ? [ badge ] : badge;
125
+ const HeadingTag = `h${ heading }`;
115
126
 
116
- const Component = () => (
127
+ const cardContent = (
117
128
  <>
118
129
  <div className="newspack-action-card__region newspack-action-card__region-top">
119
- { toggleOnChange && <ToggleControl checked={ toggleChecked } onChange={ toggleOnChange } disabled={ disabled } /> }
130
+ { toggleOnChange && (
131
+ <ToggleControl checked={ toggleChecked } onChange={ toggleOnChange } disabled={ disabled } className={ togglePositionClass } />
132
+ ) }
120
133
  { image && ! toggleOnChange && (
121
134
  <div className="newspack-action-card__region newspack-action-card__region-left">
122
135
  <a href={ imageLink }>
@@ -140,7 +153,7 @@ const ActionCard = ( {
140
153
  ) }
141
154
  <div className="newspack-action-card__region newspack-action-card__region-center">
142
155
  <Grid columns={ 1 } gutter={ 8 } noMargin>
143
- <h2>
156
+ <HeadingTag>
144
157
  <span className="newspack-action-card__title" { ...titleProps }>
145
158
  { titleLink && <a href={ titleLink }>{ title }</a> }
146
159
  { ! titleLink && expandable && (
@@ -159,7 +172,7 @@ const ActionCard = ( {
159
172
  { badgeText }
160
173
  </span>
161
174
  ) ) }
162
- </h2>
175
+ </HeadingTag>
163
176
  { description && (
164
177
  <p>
165
178
  { typeof description === 'string' && description }
@@ -224,9 +237,9 @@ const ActionCard = ( {
224
237
  { 'warning' === notificationLevel && <Notice noticeText={ notification } isWarning rawHTML={ notificationHTML } /> }
225
238
  </div>
226
239
  ) }
227
- { children && ( ( expandable && expanded ) || ! expandable ) && (
240
+ { children && ( ( expandable && expanded ) || ! expandable ) ? (
228
241
  <div className="newspack-action-card__region-children">{ children }</div>
229
- ) }
242
+ ) : null }
230
243
  </>
231
244
  );
232
245
 
@@ -289,7 +302,7 @@ const ActionCard = ( {
289
302
  <Card className={ classes } onClick={ simple && onClick } id={ id ?? null } noBorder={ noBorder }>
290
303
  <div className="newspack-action-card__draggable-controls">
291
304
  <div className="drag-handle" draggable onDragStart={ onDraggableStart } onDragEnd={ onDraggableEnd }>
292
- <Icon icon={ dragHandle } height={ 18 } width={ 18 } />
305
+ <Icon icon={ dragHandle } />
293
306
  </div>
294
307
  <div className="movers">
295
308
  <Button
@@ -306,7 +319,7 @@ const ActionCard = ( {
306
319
  />
307
320
  </div>
308
321
  </div>
309
- <Component />
322
+ { cardContent }
310
323
  </Card>
311
324
  ) }
312
325
  </Draggable>
@@ -316,7 +329,7 @@ const ActionCard = ( {
316
329
 
317
330
  return (
318
331
  <Card className={ classes } onClick={ simple && onClick } id={ id ?? null } noBorder={ noBorder }>
319
- <Component />
332
+ { cardContent }
320
333
  </Card>
321
334
  );
322
335
  };
@@ -55,7 +55,7 @@
55
55
  .components-toggle-control {
56
56
  margin-bottom: 16px;
57
57
 
58
- @media screen and ( min-width: 744px ) {
58
+ @media screen and (min-width: 744px) {
59
59
  margin-bottom: 0;
60
60
  margin-right: 16px;
61
61
  }
@@ -77,10 +77,6 @@
77
77
  padding: 0 4px;
78
78
  }
79
79
 
80
- & + & {
81
- margin-top: -16px;
82
- }
83
-
84
80
  &.mv0 {
85
81
  border-left-color: wp-colors.$gray-300;
86
82
  border-radius: 0;
@@ -122,7 +118,7 @@
122
118
  .components-toggle-control {
123
119
  margin-bottom: 16px;
124
120
 
125
- @media screen and ( min-width: 744px ) {
121
+ @media screen and (min-width: 744px) {
126
122
  margin-bottom: 0;
127
123
  margin-right: 16px;
128
124
  }
@@ -132,7 +128,7 @@
132
128
  .newspack-action-card__region-left {
133
129
  margin-bottom: 16px;
134
130
 
135
- @media screen and ( min-width: 744px ) {
131
+ @media screen and (min-width: 744px) {
136
132
  margin-bottom: 0;
137
133
  margin-right: 16px;
138
134
  }
@@ -252,7 +248,7 @@
252
248
  padding: 24px;
253
249
  display: block;
254
250
 
255
- @media screen and ( min-width: 744px ) {
251
+ @media screen and (min-width: 744px) {
256
252
  display: flex;
257
253
  }
258
254
 
@@ -261,9 +257,19 @@
261
257
  margin-bottom: 24px;
262
258
  margin-top: 0;
263
259
 
264
- @media screen and ( min-width: 744px ) {
260
+ @media screen and (min-width: 744px) {
265
261
  margin-bottom: 0;
266
262
  margin-right: 24px;
263
+
264
+ &.is-toggle-trailing {
265
+ margin-left: 24px;
266
+ margin-right: 0;
267
+ order: 999;
268
+ }
269
+ }
270
+
271
+ label {
272
+ display: none;
267
273
  }
268
274
 
269
275
  .components-base-control__field {
@@ -308,7 +314,7 @@
308
314
  .newspack-action-card__region-left {
309
315
  margin-bottom: 24px;
310
316
 
311
- @media screen and ( min-width: 744px ) {
317
+ @media screen and (min-width: 744px) {
312
318
  margin-bottom: 0;
313
319
  margin-right: 24px;
314
320
  }
@@ -335,7 +341,7 @@
335
341
  margin-top: 24px;
336
342
  text-align: right;
337
343
 
338
- @media screen and ( min-width: 744px ) {
344
+ @media screen and (min-width: 744px) {
339
345
  align-items: flex-end;
340
346
  flex-direction: column;
341
347
  margin-left: 24px;
@@ -437,13 +443,21 @@
437
443
  text-decoration: inherit;
438
444
  }
439
445
 
440
- .newspack-action-card__region-children {
441
- border-top: 1px solid wp-colors.$gray-100;
446
+ > .newspack-action-card__region-children {
447
+ border-top: 1px solid wp-colors.$gray-300;
442
448
  padding-top: 24px;
443
449
 
444
450
  + .newspack-action-card__region-children {
445
451
  border-top: none;
446
452
  }
453
+
454
+ > * + * {
455
+ margin-top: 24px !important;
456
+ }
457
+
458
+ .newspack-action-card__region-children:has(> div:not(:empty)) {
459
+ margin-top: 16px;
460
+ }
447
461
  }
448
462
 
449
463
  &.is-medium .newspack-action-card__region-children {
@@ -473,6 +487,11 @@
473
487
  margin-left: 8px;
474
488
  }
475
489
  }
490
+
491
+ hr:not(.newspack-divider) {
492
+ background: wp-colors.$gray-100;
493
+ margin: 0;
494
+ }
476
495
  }
477
496
 
478
497
  .newspack-card {
@@ -557,7 +576,6 @@
557
576
  }
558
577
 
559
578
  .newspack-action-card__region-top {
560
- flex: 1 1 auto;
561
579
  padding: 8px 16px 8px 0 !important;
562
580
  flex: 1;
563
581
  }
@@ -120,7 +120,7 @@ class AutocompleteTokenField extends Component {
120
120
  Object.keys( validValues ).forEach( key => {
121
121
  if ( validValues[ key ] === label ) {
122
122
  // Preserve numeric or string type of values. Object.keys will convert numbers to strings.
123
- const value = isNaN( parseInt( key ) ) ? key.toString() : parseInt( key );
123
+ const value = /^\d+$/.test( key.toString() ) ? parseInt( key ) : key.toString();
124
124
  acc.push( { value, label } );
125
125
  }
126
126
  } );
@@ -131,7 +131,7 @@ class AutocompleteTokenField extends Component {
131
131
 
132
132
  return labels.map( label =>
133
133
  Object.keys( validValues )
134
- .map( key => ( isNaN( parseInt( key ) ) ? key.toString() : parseInt( key ) ) )
134
+ .map( key => ( /^\d+$/.test( key.toString() ) ? parseInt( key ) : key.toString() ) )
135
135
  .find( key => validValues[ key ] === label )
136
136
  );
137
137
  }