newspack-components 4.11.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.
- package/CHANGELOG.md +18 -0
- package/DEVELOPMENT.md +79 -38
- package/dist/cjs/breadcrumbs/index.js +1 -0
- package/dist/cjs/breadcrumbs/style.scss +3 -10
- package/dist/cjs/card/style-core.scss +1 -1
- package/dist/cjs/card-sortable-list/index.js +17 -9
- package/dist/cjs/drawer/body.js +4 -6
- package/dist/cjs/drawer/content.js +7 -37
- package/dist/cjs/drawer/footer.js +10 -1
- package/dist/cjs/drawer/header.js +9 -1
- package/dist/cjs/drawer/index.test.js +3 -4
- package/dist/cjs/drawer/slots.test.js +25 -67
- package/dist/cjs/drawer/style.scss +0 -18
- package/dist/cjs/index.js +7 -0
- package/dist/cjs/page/index.js +10 -2
- package/dist/cjs/page/style.scss +24 -2
- package/dist/cjs/tabbed-navigation/style.scss +5 -0
- package/dist/cjs/tooltip-badge/index.js +50 -0
- package/dist/cjs/tooltip-badge/index.test.js +30 -0
- package/dist/cjs/wizard/index.js +21 -8
- package/dist/cjs/wizard/index.test.js +78 -0
- package/dist/cjs/wizard/store/index.js +2 -1
- package/dist/esm/breadcrumbs/index.js +1 -0
- package/dist/esm/breadcrumbs/style.scss +3 -10
- package/dist/esm/card/style-core.scss +1 -1
- package/dist/esm/card-sortable-list/index.js +20 -11
- package/dist/esm/drawer/body.js +4 -5
- package/dist/esm/drawer/content.js +7 -36
- package/dist/esm/drawer/footer.js +10 -1
- package/dist/esm/drawer/header.js +9 -1
- package/dist/esm/drawer/index.test.js +3 -4
- package/dist/esm/drawer/slots.test.js +25 -67
- package/dist/esm/drawer/style.scss +0 -18
- package/dist/esm/index.js +1 -0
- package/dist/esm/page/index.js +10 -2
- package/dist/esm/page/style.scss +24 -2
- package/dist/esm/tabbed-navigation/style.scss +5 -0
- package/dist/esm/tooltip-badge/index.js +43 -0
- package/dist/esm/tooltip-badge/index.test.js +26 -0
- package/dist/esm/wizard/index.js +23 -12
- package/dist/esm/wizard/index.test.js +78 -0
- package/dist/esm/wizard/store/index.js +2 -1
- package/package.json +2 -2
- package/src/breadcrumbs/index.js +1 -1
- package/src/breadcrumbs/style.scss +3 -10
- package/src/card/style-core.scss +1 -1
- package/src/card-form/README.md +2 -2
- package/src/card-sortable-list/index.tsx +27 -11
- package/src/drawer/README.md +10 -16
- package/src/drawer/body.tsx +4 -5
- package/src/drawer/content.tsx +7 -33
- package/src/drawer/footer.tsx +8 -1
- package/src/drawer/header.tsx +8 -1
- package/src/drawer/index.test.js +3 -4
- package/src/drawer/slots.test.js +14 -48
- package/src/drawer/style.scss +0 -18
- package/src/drawer/types.ts +8 -3
- package/src/index.js +1 -0
- package/src/page/index.js +7 -3
- package/src/page/style.scss +24 -2
- package/src/tabbed-navigation/style.scss +5 -0
- package/src/tooltip-badge/index.test.js +24 -0
- package/src/tooltip-badge/index.tsx +40 -0
- package/src/wizard/index.js +25 -14
- package/src/wizard/index.test.js +38 -0
- package/src/wizard/store/index.js +1 -0
package/src/drawer/style.scss
CHANGED
|
@@ -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 {
|
package/src/drawer/types.ts
CHANGED
|
@@ -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
|
|
47
|
+
/** 4px scale: `6` is 24px, `0` a flush section. */
|
|
43
48
|
padding?: number;
|
|
44
|
-
/**
|
|
45
|
-
gap?:
|
|
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
|
@@ -51,6 +51,7 @@ export { default as StyleCard } from './style-card';
|
|
|
51
51
|
export { default as TabbedNavigation } from './tabbed-navigation';
|
|
52
52
|
export { default as TableCard } from './table-card';
|
|
53
53
|
export { default as TextControl } from './text-control';
|
|
54
|
+
export { default as TooltipBadge } from './tooltip-badge';
|
|
54
55
|
export { default as Waiting } from './waiting';
|
|
55
56
|
export { default as WebPreview } from './web-preview';
|
|
56
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="
|
|
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">
|
package/src/page/style.scss
CHANGED
|
@@ -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:
|
|
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-
|
|
104
|
+
min-width: 0;
|
|
105
|
+
padding-block: calc((#{wp-vars.$button-size} - var(--wpds-typography-line-height-md)) / 2);
|
|
84
106
|
}
|
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
/* stylelint-disable-next-line plugin-wpds/no-setting-wpds-custom-properties -- deliberate Newspack brand override of the indicator token. */
|
|
22
22
|
--wpds-color-stroke-interactive-neutral-strong: #{np-colors.$primary-600};
|
|
23
23
|
|
|
24
|
+
[role="tablist"] {
|
|
25
|
+
max-width: 100%;
|
|
26
|
+
overflow-x: auto;
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
a[role="tab"] {
|
|
25
30
|
text-decoration: none;
|
|
26
31
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { render, screen } from '@testing-library/react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal dependencies
|
|
8
|
+
*/
|
|
9
|
+
import TooltipBadge from './';
|
|
10
|
+
|
|
11
|
+
describe( 'TooltipBadge', () => {
|
|
12
|
+
it( 'describes its focusable trigger with the tooltip text, keeping it out of a surrounding heading name', () => {
|
|
13
|
+
render(
|
|
14
|
+
<h3>
|
|
15
|
+
Registration wall
|
|
16
|
+
<TooltipBadge label="Overrides paid access" tooltip="This grants registered readers access." intent="low" />
|
|
17
|
+
</h3>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
expect( screen.getByRole( 'heading', { name: 'Registration wall Overrides paid access' } ) ).toBeInTheDocument();
|
|
21
|
+
const trigger = screen.getByText( 'Overrides paid access' ).closest( '[tabindex="0"]' );
|
|
22
|
+
expect( trigger ).toHaveAccessibleDescription( 'This grants registered readers access.' );
|
|
23
|
+
} );
|
|
24
|
+
} );
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { useInstanceId } from '@wordpress/compose';
|
|
5
|
+
import { Badge, Tooltip } from '@wordpress/ui';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Internal dependencies
|
|
9
|
+
*/
|
|
10
|
+
import type { BadgeIntent } from '../types';
|
|
11
|
+
|
|
12
|
+
type TooltipBadgeProps = {
|
|
13
|
+
label: string;
|
|
14
|
+
tooltip: string;
|
|
15
|
+
intent?: BadgeIntent;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A badge whose tooltip explains it. The tooltip serves mouse and keyboard users;
|
|
20
|
+
* screen readers get the same text as the trigger's description, which keeps it
|
|
21
|
+
* out of the accessible name of a heading the badge sits in.
|
|
22
|
+
*/
|
|
23
|
+
const TooltipBadge = ( { label, tooltip, intent = 'none' }: TooltipBadgeProps ) => {
|
|
24
|
+
const descriptionId = useInstanceId( TooltipBadge, 'newspack-tooltip-badge' );
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<Tooltip.Root>
|
|
28
|
+
<Tooltip.Trigger render={ <span tabIndex={ 0 } aria-describedby={ descriptionId } /> }>
|
|
29
|
+
<Badge intent={ intent }>{ label }</Badge>
|
|
30
|
+
</Tooltip.Trigger>
|
|
31
|
+
<Tooltip.Popup>{ tooltip }</Tooltip.Popup>
|
|
32
|
+
</Tooltip.Root>
|
|
33
|
+
<span id={ descriptionId } hidden>
|
|
34
|
+
{ tooltip }
|
|
35
|
+
</span>
|
|
36
|
+
</>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default TooltipBadge;
|
package/src/wizard/index.js
CHANGED
|
@@ -6,16 +6,8 @@ import classnames from 'classnames';
|
|
|
6
6
|
/**
|
|
7
7
|
* WordPress dependencies.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
MenuGroup,
|
|
12
|
-
MenuItem,
|
|
13
|
-
Notice,
|
|
14
|
-
SlotFillProvider,
|
|
15
|
-
createSlotFill,
|
|
16
|
-
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
|
17
|
-
__experimentalVStack as VStack,
|
|
18
|
-
} from '@wordpress/components';
|
|
9
|
+
import { DropdownMenu, MenuGroup, MenuItem, Notice, SlotFillProvider, createSlotFill } from '@wordpress/components';
|
|
10
|
+
import { Stack } from '@wordpress/ui';
|
|
19
11
|
import { useDispatch, useSelect } from '@wordpress/data';
|
|
20
12
|
import { cloneElement, createInterpolateElement, isValidElement, useLayoutEffect, useRef, useState, forwardRef } from '@wordpress/element';
|
|
21
13
|
import { __ } from '@wordpress/i18n';
|
|
@@ -111,6 +103,7 @@ const WizardHeaderRegion = ( {
|
|
|
111
103
|
sections,
|
|
112
104
|
sectionName,
|
|
113
105
|
subTitle,
|
|
106
|
+
headerSubTitle,
|
|
114
107
|
actions,
|
|
115
108
|
tabbedNavigation: wizardTabbedNavigation,
|
|
116
109
|
children,
|
|
@@ -134,6 +127,17 @@ const WizardHeaderRegion = ( {
|
|
|
134
127
|
}
|
|
135
128
|
}
|
|
136
129
|
|
|
130
|
+
// WizardError normally mounts inside the tab bar, so a route without one renders it itself.
|
|
131
|
+
if ( activeSection( sections, pathname )?.hideTabbedNavigation && tabbedNavigation ) {
|
|
132
|
+
tabbedNavigation = null;
|
|
133
|
+
children = (
|
|
134
|
+
<>
|
|
135
|
+
<WizardError />
|
|
136
|
+
{ children }
|
|
137
|
+
</>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
137
141
|
if ( hideHeader ) {
|
|
138
142
|
// Without the Page shell the tabs still own the content: it renders
|
|
139
143
|
// inside the active tab's panel.
|
|
@@ -154,7 +158,12 @@ const WizardHeaderRegion = ( {
|
|
|
154
158
|
const sectionSubTitle = activeSection( sections, pathname )?.subHeaderText;
|
|
155
159
|
|
|
156
160
|
return (
|
|
157
|
-
<Page
|
|
161
|
+
<Page
|
|
162
|
+
breadcrumbItems={ breadcrumbItems }
|
|
163
|
+
subTitle={ headerSubTitle || ( sectionSubTitle ?? subTitle ) }
|
|
164
|
+
actions={ actions }
|
|
165
|
+
tabbedNavigation={ tabbedNavigation }
|
|
166
|
+
>
|
|
158
167
|
{ children }
|
|
159
168
|
</Page>
|
|
160
169
|
);
|
|
@@ -166,7 +175,7 @@ const WizardHeaderRegion = ( {
|
|
|
166
175
|
* @property {string} [subHeaderText] The sub-header text, optional.
|
|
167
176
|
* @property {string} [apiSlug] The API slug, optional.
|
|
168
177
|
* @property {string} [className] CSS classes, optional.
|
|
169
|
-
* @property {any[]} sections Array of sections. A section's own `subHeaderText` replaces the wizard's while it is active.
|
|
178
|
+
* @property {any[]} sections Array of sections. A section's own `subHeaderText` replaces the wizard's while it is active, and `hideTabbedNavigation` hides the tab bar on its route.
|
|
170
179
|
* Its optional `tabbedNavigation( params )` returns the tab items shown while its route matches.
|
|
171
180
|
* @property {boolean} [hasSimpleFooter] Indicates if a simple footer is used, optional.
|
|
172
181
|
* @property {() => void} [renderAboveSections] Function to render content above sections, optional.
|
|
@@ -215,6 +224,7 @@ const Wizard = (
|
|
|
215
224
|
sectionTitle,
|
|
216
225
|
sectionPrimaryAction,
|
|
217
226
|
sectionSecondaryAction,
|
|
227
|
+
subTitle: headerSubTitle,
|
|
218
228
|
} = headerData;
|
|
219
229
|
|
|
220
230
|
const mainActions = actions?.filter( action => action.type === 'primary' || action.type === 'secondary' );
|
|
@@ -268,10 +278,10 @@ const Wizard = (
|
|
|
268
278
|
<>
|
|
269
279
|
{ ! pluginRequirementsKnown && (
|
|
270
280
|
<div className="newspack-wizard__loader">
|
|
271
|
-
<
|
|
281
|
+
<Stack direction="column" align="center" gap="sm">
|
|
272
282
|
<Waiting noMargin />
|
|
273
283
|
<strong>{ __( 'Fetching…', 'newspack-plugin' ) }</strong>
|
|
274
|
-
</
|
|
284
|
+
</Stack>
|
|
275
285
|
</div>
|
|
276
286
|
) }
|
|
277
287
|
<div hidden={ ! pluginRequirementsKnown }>
|
|
@@ -443,6 +453,7 @@ const Wizard = (
|
|
|
443
453
|
sections={ routedSections }
|
|
444
454
|
sectionName={ sectionName }
|
|
445
455
|
subTitle={ subHeaderText }
|
|
456
|
+
headerSubTitle={ headerSubTitle }
|
|
446
457
|
actions={ headerActions }
|
|
447
458
|
tabbedNavigation={ tabbedNavigation }
|
|
448
459
|
>
|
package/src/wizard/index.test.js
CHANGED
|
@@ -320,3 +320,41 @@ describe( 'Wizard section subtitle', () => {
|
|
|
320
320
|
expect( subtitle() ).toBe( 'Wizard intro' );
|
|
321
321
|
} );
|
|
322
322
|
} );
|
|
323
|
+
|
|
324
|
+
describe( 'Wizard tab bar on hidden routes', () => {
|
|
325
|
+
beforeEach( () => {
|
|
326
|
+
apiFetch.mockReset();
|
|
327
|
+
apiFetch.mockResolvedValue( {} );
|
|
328
|
+
} );
|
|
329
|
+
|
|
330
|
+
it( 'hides the tab bar on a hideTabbedNavigation route and restores it on a tab route', async () => {
|
|
331
|
+
window.location.hash = '#/list';
|
|
332
|
+
render(
|
|
333
|
+
<Wizard
|
|
334
|
+
headerText="Test wizard"
|
|
335
|
+
sections={ [
|
|
336
|
+
{ label: 'List', path: '/list', exact: true, render: () => <div>List view</div> },
|
|
337
|
+
{ label: 'Other', path: '/other', exact: true, render: () => <div>Other view</div> },
|
|
338
|
+
{ path: '/list/:id', isHidden: true, hideTabbedNavigation: true, render: () => <div>Item view</div> },
|
|
339
|
+
] }
|
|
340
|
+
/>
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
expect( await screen.findByText( 'List view' ) ).toBeInTheDocument();
|
|
344
|
+
expect( screen.getByRole( 'tab', { name: 'Other' } ) ).toBeInTheDocument();
|
|
345
|
+
|
|
346
|
+
act( () => {
|
|
347
|
+
window.location.hash = '#/list/esp';
|
|
348
|
+
window.dispatchEvent( new HashChangeEvent( 'hashchange' ) );
|
|
349
|
+
} );
|
|
350
|
+
expect( await screen.findByText( 'Item view' ) ).toBeInTheDocument();
|
|
351
|
+
expect( screen.queryByRole( 'tab' ) ).not.toBeInTheDocument();
|
|
352
|
+
|
|
353
|
+
act( () => {
|
|
354
|
+
window.location.hash = '#/list';
|
|
355
|
+
window.dispatchEvent( new HashChangeEvent( 'hashchange' ) );
|
|
356
|
+
} );
|
|
357
|
+
expect( await screen.findByText( 'List view' ) ).toBeInTheDocument();
|
|
358
|
+
expect( screen.getByRole( 'tab', { name: 'Other' } ) ).toBeInTheDocument();
|
|
359
|
+
} );
|
|
360
|
+
} );
|