newspack-components 4.7.0 → 4.8.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.
- package/CHANGELOG.md +20 -0
- package/DEVELOPMENT.md +42 -21
- package/dist/cjs/_mixins.scss +13 -0
- package/dist/cjs/_variables.scss +12 -0
- package/dist/cjs/action-card/index.js +18 -10
- package/dist/cjs/action-card/index.test.js +91 -0
- package/dist/cjs/action-card/style.scss +2 -40
- package/dist/cjs/button/index.js +8 -3
- package/dist/cjs/button/index.test.js +61 -5
- package/dist/cjs/card/core-card.js +0 -2
- package/dist/cjs/card/style-core.scss +7 -5
- package/dist/cjs/card-feature/index.js +75 -61
- package/dist/cjs/card-feature/index.test.js +396 -54
- package/dist/cjs/card-feature/style.scss +20 -31
- package/dist/cjs/card-form/index.js +5 -5
- package/dist/cjs/card-sortable-list/index.js +5 -3
- package/dist/cjs/card-sortable-list/index.test.js +50 -0
- package/dist/cjs/collapsible-group/context.js +19 -0
- package/dist/cjs/collapsible-group/index.js +24 -0
- package/dist/cjs/collapsible-group/index.test.js +215 -0
- package/dist/cjs/collapsible-group/item.js +61 -0
- package/dist/cjs/collapsible-group/root.js +62 -0
- package/dist/cjs/collapsible-group/style.scss +47 -0
- package/dist/cjs/collapsible-group/types.js +5 -0
- package/dist/cjs/empty-state/actions.js +45 -0
- package/dist/cjs/empty-state/context.js +41 -0
- package/dist/cjs/empty-state/header.js +66 -0
- package/dist/cjs/empty-state/index.js +31 -0
- package/dist/cjs/empty-state/index.test.js +303 -0
- package/dist/cjs/empty-state/root.js +56 -0
- package/dist/cjs/empty-state/style.scss +51 -0
- package/dist/cjs/empty-state/types.js +5 -0
- package/dist/cjs/index.js +34 -20
- package/dist/cjs/info-button/index.js +59 -43
- package/dist/cjs/info-button/index.test.js +199 -0
- package/dist/cjs/info-button/style.scss +41 -9
- package/dist/cjs/modal/style.scss +12 -12
- package/dist/cjs/newspack-icon/style.scss +4 -4
- package/dist/cjs/notice/style.scss +3 -3
- package/dist/cjs/page/style.scss +16 -16
- package/dist/cjs/plugin-installer/index.js +5 -9
- package/dist/cjs/plugin-installer/style.scss +3 -14
- package/dist/cjs/section-header/index.js +31 -18
- package/dist/cjs/section-header/index.test.js +40 -0
- package/dist/cjs/section-header/style.scss +22 -6
- package/dist/cjs/settings/DateRangeSetting.js +309 -0
- package/dist/cjs/settings/SettingsSection.js +19 -5
- package/dist/cjs/settings/SettingsSection.test.js +49 -0
- package/dist/cjs/settings/index.js +3 -1
- package/dist/cjs/settings/style.scss +40 -7
- package/dist/cjs/stat-card/body.js +41 -0
- package/dist/cjs/stat-card/constants.js +11 -0
- package/dist/cjs/stat-card/context.js +54 -0
- package/dist/cjs/stat-card/delta.js +91 -0
- package/dist/cjs/stat-card/footer.js +72 -0
- package/dist/cjs/stat-card/index.js +47 -0
- package/dist/cjs/stat-card/index.test.js +938 -0
- package/dist/cjs/stat-card/label.js +66 -0
- package/dist/cjs/stat-card/root.js +69 -0
- package/dist/cjs/stat-card/secondary.js +38 -0
- package/dist/cjs/stat-card/style.scss +89 -0
- package/dist/cjs/stat-card/types.js +5 -0
- package/dist/cjs/stat-card/value.js +78 -0
- package/dist/cjs/status-indicator/index.js +66 -0
- package/dist/cjs/status-indicator/index.test.js +101 -0
- package/dist/cjs/status-indicator/statuses.js +39 -0
- package/dist/cjs/status-indicator/style.scss +9 -0
- package/dist/cjs/status-indicator/types.js +5 -0
- package/dist/cjs/style.scss +9 -9
- package/dist/cjs/tabbed-navigation/style.scss +6 -4
- package/dist/cjs/types.js +5 -0
- package/dist/cjs/web-preview/style.scss +11 -11
- package/dist/cjs/with-wizard/style.scss +10 -10
- package/dist/cjs/with-wizard-screen/style.scss +9 -9
- package/dist/cjs/wizard/index.js +33 -4
- package/dist/cjs/wizard/store/index.js +23 -1
- package/dist/esm/_mixins.scss +13 -0
- package/dist/esm/_variables.scss +12 -0
- package/dist/esm/action-card/index.js +18 -10
- package/dist/esm/action-card/index.test.js +89 -0
- package/dist/esm/action-card/style.scss +2 -40
- package/dist/esm/button/index.js +9 -4
- package/dist/esm/button/index.test.js +63 -5
- package/dist/esm/card/core-card.js +0 -2
- package/dist/esm/card/style-core.scss +7 -5
- package/dist/esm/card-feature/index.js +78 -62
- package/dist/esm/card-feature/index.test.js +397 -55
- package/dist/esm/card-feature/style.scss +20 -31
- package/dist/esm/card-form/index.js +5 -5
- package/dist/esm/card-sortable-list/index.js +6 -4
- package/dist/esm/card-sortable-list/index.test.js +46 -0
- package/dist/esm/collapsible-group/context.js +13 -0
- package/dist/esm/collapsible-group/index.js +17 -0
- package/dist/esm/collapsible-group/index.test.js +215 -0
- package/dist/esm/collapsible-group/item.js +53 -0
- package/dist/esm/collapsible-group/root.js +54 -0
- package/dist/esm/collapsible-group/style.scss +47 -0
- package/dist/esm/collapsible-group/types.js +1 -0
- package/dist/esm/empty-state/actions.js +37 -0
- package/dist/esm/empty-state/context.js +35 -0
- package/dist/esm/empty-state/header.js +58 -0
- package/dist/esm/empty-state/index.js +23 -0
- package/dist/esm/empty-state/index.test.js +299 -0
- package/dist/esm/empty-state/root.js +56 -0
- package/dist/esm/empty-state/style.scss +51 -0
- package/dist/esm/empty-state/types.js +1 -0
- package/dist/esm/index.js +4 -2
- package/dist/esm/info-button/index.js +54 -43
- package/dist/esm/info-button/index.test.js +195 -0
- package/dist/esm/info-button/style.scss +41 -9
- package/dist/esm/modal/style.scss +12 -12
- package/dist/esm/newspack-icon/style.scss +4 -4
- package/dist/esm/notice/style.scss +3 -3
- package/dist/esm/page/style.scss +16 -16
- package/dist/esm/plugin-installer/index.js +5 -9
- package/dist/esm/plugin-installer/style.scss +3 -14
- package/dist/esm/section-header/index.js +27 -14
- package/dist/esm/section-header/index.test.js +36 -0
- package/dist/esm/section-header/style.scss +22 -6
- package/dist/esm/settings/DateRangeSetting.js +304 -0
- package/dist/esm/settings/SettingsSection.js +16 -3
- package/dist/esm/settings/SettingsSection.test.js +45 -0
- package/dist/esm/settings/index.js +3 -1
- package/dist/esm/settings/style.scss +40 -7
- package/dist/esm/stat-card/body.js +36 -0
- package/dist/esm/stat-card/constants.js +5 -0
- package/dist/esm/stat-card/context.js +48 -0
- package/dist/esm/stat-card/delta.js +86 -0
- package/dist/esm/stat-card/footer.js +67 -0
- package/dist/esm/stat-card/index.js +33 -0
- package/dist/esm/stat-card/index.test.js +939 -0
- package/dist/esm/stat-card/label.js +61 -0
- package/dist/esm/stat-card/root.js +64 -0
- package/dist/esm/stat-card/secondary.js +33 -0
- package/dist/esm/stat-card/style.scss +89 -0
- package/dist/esm/stat-card/types.js +1 -0
- package/dist/esm/stat-card/value.js +73 -0
- package/dist/esm/status-indicator/index.js +52 -0
- package/dist/esm/status-indicator/index.test.js +96 -0
- package/dist/esm/status-indicator/statuses.js +33 -0
- package/dist/esm/status-indicator/style.scss +9 -0
- package/dist/esm/status-indicator/types.js +1 -0
- package/dist/esm/style.scss +9 -9
- package/dist/esm/tabbed-navigation/style.scss +6 -4
- package/dist/esm/types.js +1 -0
- package/dist/esm/web-preview/style.scss +11 -11
- package/dist/esm/with-wizard/style.scss +10 -10
- package/dist/esm/with-wizard-screen/style.scss +9 -9
- package/dist/esm/wizard/index.js +38 -7
- package/dist/esm/wizard/store/index.js +23 -1
- package/package.json +1 -1
- package/src/_mixins.scss +13 -0
- package/src/_variables.scss +12 -0
- package/src/action-card/action-card.d.ts +3 -2
- package/src/action-card/index.js +12 -12
- package/src/action-card/index.test.js +56 -0
- package/src/action-card/style.scss +2 -40
- package/src/button/index.test.js +52 -5
- package/src/button/index.tsx +9 -4
- package/src/card/core-card.js +0 -2
- package/src/card/style-core.scss +7 -5
- package/src/card-feature/README.md +68 -27
- package/src/card-feature/index.test.js +242 -27
- package/src/card-feature/index.tsx +77 -67
- package/src/card-feature/style.scss +20 -31
- package/src/card-form/README.md +10 -10
- package/src/card-form/index.tsx +4 -8
- package/src/card-sortable-list/README.md +4 -5
- package/src/card-sortable-list/index.test.js +29 -0
- package/src/card-sortable-list/index.tsx +7 -4
- package/src/collapsible-group/README.md +50 -0
- package/src/collapsible-group/context.ts +13 -0
- package/src/collapsible-group/index.test.js +168 -0
- package/src/collapsible-group/index.tsx +17 -0
- package/src/collapsible-group/item.tsx +42 -0
- package/src/collapsible-group/root.tsx +42 -0
- package/src/collapsible-group/style.scss +47 -0
- package/src/collapsible-group/types.ts +22 -0
- package/src/drawer/README.md +2 -2
- package/src/empty-state/README.md +165 -0
- package/src/empty-state/actions.tsx +37 -0
- package/src/empty-state/context.ts +40 -0
- package/src/empty-state/header.tsx +48 -0
- package/src/empty-state/index.test.js +247 -0
- package/src/empty-state/index.tsx +22 -0
- package/src/empty-state/root.tsx +39 -0
- package/src/empty-state/style.scss +51 -0
- package/src/empty-state/types.ts +33 -0
- package/src/index.js +4 -2
- package/src/info-button/README.md +115 -0
- package/src/info-button/index.test.js +163 -0
- package/src/info-button/index.tsx +64 -0
- package/src/info-button/style.scss +41 -9
- package/src/modal/style.scss +12 -12
- package/src/newspack-icon/style.scss +4 -4
- package/src/notice/style.scss +3 -3
- package/src/page/style.scss +16 -16
- package/src/plugin-installer/index.js +3 -9
- package/src/plugin-installer/style.scss +3 -14
- package/src/section-header/index.js +27 -14
- package/src/section-header/index.test.js +28 -0
- package/src/section-header/style.scss +22 -6
- package/src/settings/DateRangeSetting.js +266 -0
- package/src/settings/SettingsSection.js +24 -4
- package/src/settings/SettingsSection.test.js +45 -0
- package/src/settings/index.js +2 -0
- package/src/settings/style.scss +40 -7
- package/src/stat-card/README.md +434 -0
- package/src/stat-card/body.tsx +28 -0
- package/src/stat-card/constants.ts +5 -0
- package/src/stat-card/context.ts +53 -0
- package/src/stat-card/delta.tsx +88 -0
- package/src/stat-card/footer.tsx +68 -0
- package/src/stat-card/index.test.js +818 -0
- package/src/stat-card/index.tsx +48 -0
- package/src/stat-card/label.tsx +61 -0
- package/src/stat-card/root.tsx +40 -0
- package/src/stat-card/secondary.tsx +27 -0
- package/src/stat-card/style.scss +89 -0
- package/src/stat-card/types.ts +84 -0
- package/src/stat-card/value.tsx +72 -0
- package/src/status-indicator/README.md +123 -0
- package/src/status-indicator/index.test.js +70 -0
- package/src/status-indicator/index.tsx +39 -0
- package/src/status-indicator/statuses.ts +35 -0
- package/src/status-indicator/style.scss +9 -0
- package/src/status-indicator/types.ts +28 -0
- package/src/style.scss +9 -9
- package/src/tabbed-navigation/style.scss +6 -4
- package/src/types.ts +25 -0
- package/src/web-preview/style.scss +11 -11
- package/src/with-wizard/style.scss +10 -10
- package/src/with-wizard-screen/style.scss +9 -9
- package/src/wizard/index.js +49 -7
- package/src/wizard/store/index.js +26 -1
- package/dist/cjs/accordion/index.js +0 -72
- package/dist/cjs/accordion/index.test.js +0 -80
- package/dist/cjs/accordion/style.scss +0 -35
- package/dist/cjs/badge/index.js +0 -32
- package/dist/cjs/badge/style.scss +0 -36
- package/dist/esm/accordion/index.js +0 -63
- package/dist/esm/accordion/index.test.js +0 -79
- package/dist/esm/accordion/style.scss +0 -35
- package/dist/esm/badge/index.js +0 -24
- package/dist/esm/badge/style.scss +0 -36
- package/src/accordion/index.js +0 -46
- package/src/accordion/index.test.js +0 -62
- package/src/accordion/style.scss +0 -35
- package/src/badge/index.tsx +0 -26
- package/src/badge/style.scss +0 -36
- package/src/info-button/index.js +0 -38
|
@@ -7,20 +7,19 @@ import classnames from 'classnames';
|
|
|
7
7
|
* WordPress dependencies
|
|
8
8
|
*/
|
|
9
9
|
import { __, _x, sprintf } from '@wordpress/i18n';
|
|
10
|
-
import { isValidElement } from '@wordpress/element';
|
|
11
|
-
import {
|
|
10
|
+
import { createElement, isValidElement } from '@wordpress/element';
|
|
11
|
+
import { useInstanceId } from '@wordpress/compose';
|
|
12
|
+
import { DropdownMenu } from '@wordpress/components';
|
|
12
13
|
import { moreVertical } from '@wordpress/icons';
|
|
14
|
+
import { Badge, Card, Stack } from '@wordpress/ui';
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Internal dependencies
|
|
16
18
|
*/
|
|
17
|
-
import Badge from '../badge';
|
|
18
19
|
import Button from '../button';
|
|
19
|
-
import
|
|
20
|
+
import type { BadgeIntent, CardBadge, HeadingLevel } from '../types';
|
|
20
21
|
import './style.scss';
|
|
21
22
|
|
|
22
|
-
type BadgeLevel = 'default' | 'info' | 'success' | 'warning' | 'error';
|
|
23
|
-
|
|
24
23
|
type CardFeatureIcon = {
|
|
25
24
|
/** The icon node to render (e.g. a WordPress <Icon> component). */
|
|
26
25
|
node: React.ReactNode;
|
|
@@ -31,7 +30,7 @@ type CardFeatureIcon = {
|
|
|
31
30
|
/**
|
|
32
31
|
* Border-radius of the icon container.
|
|
33
32
|
* 'small' uses $radius-small (2px), 'full' uses $radius-round (50%).
|
|
34
|
-
* Only relevant when backgroundColor is set.
|
|
33
|
+
* Only relevant when backgroundColor is set, where it defaults to 'small'.
|
|
35
34
|
*/
|
|
36
35
|
radius?: 'small' | 'full';
|
|
37
36
|
};
|
|
@@ -44,16 +43,19 @@ type MoreControl = {
|
|
|
44
43
|
|
|
45
44
|
type CardFeatureProps = {
|
|
46
45
|
title: string;
|
|
46
|
+
/** Heading level for the title. Defaults to 3, which sits under a `SectionHeader` or a `WizardsTab` heading. */
|
|
47
|
+
headingLevel?: HeadingLevel;
|
|
47
48
|
description?: string;
|
|
48
49
|
/** Icon shown beside the title: a descriptor (coloured badge) or a ready element rendered as-is. */
|
|
49
50
|
icon?: CardFeatureIcon | React.ReactElement;
|
|
50
51
|
/** Whether the feature is currently enabled. */
|
|
51
52
|
enabled?: boolean;
|
|
52
53
|
/**
|
|
53
|
-
* When set, the card enters the "unmet requirements" state: an error
|
|
54
|
-
*
|
|
55
|
-
* default the primary button is
|
|
54
|
+
* When set, the card enters the "unmet requirements" state: an error badge
|
|
55
|
+
* displays this string and the title drops to the muted text colour. By
|
|
56
|
+
* default the primary button is blocked — set `requirementsActionable`
|
|
56
57
|
* if the primary button is the remediation for the unmet requirement.
|
|
58
|
+
* Also the button's accessible description, so it must read after the label.
|
|
57
59
|
*/
|
|
58
60
|
requirements?: string;
|
|
59
61
|
/**
|
|
@@ -63,22 +65,24 @@ type CardFeatureProps = {
|
|
|
63
65
|
* (e.g. can be disabled), unlike a hard-locked requirement.
|
|
64
66
|
*/
|
|
65
67
|
requirementsActionable?: boolean;
|
|
66
|
-
/**
|
|
68
|
+
/** Label for the primary button in its "Enable" states: not enabled, or enabled with an unmet requirement. Default: "Enable". */
|
|
67
69
|
enableLabel?: string;
|
|
68
70
|
/** Show the primary button as busy (spinner) and disabled while an action is in flight. */
|
|
69
71
|
busy?: boolean;
|
|
70
|
-
/**
|
|
72
|
+
/** Label for the primary button in its "Configure" state: enabled, with no unmet requirement. Default: "Configure". */
|
|
71
73
|
configureLabel?: string;
|
|
72
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Called when the primary button is clicked while it reads "Enable". That is
|
|
76
|
+
* the not-enabled state, and also the enabled state with unmet requirements,
|
|
77
|
+
* where the requirement rather than the feature is what the button acts on.
|
|
78
|
+
*/
|
|
73
79
|
onEnable?: () => void;
|
|
74
|
-
/** Called when the primary button is clicked
|
|
80
|
+
/** Called when the primary button is clicked while it reads "Configure": enabled, with no unmet requirements. */
|
|
75
81
|
onConfigure?: () => void;
|
|
76
82
|
/** Controls rendered inside the "More" dropdown, shown when enabled — including the unmet-requirements state when `requirementsActionable`. */
|
|
77
83
|
moreControls?: MoreControl[];
|
|
78
|
-
/** Badge
|
|
79
|
-
|
|
80
|
-
/** Badge level shown when enabled. Default: "success". */
|
|
81
|
-
badgeLevel?: BadgeLevel;
|
|
84
|
+
/** Badge shown when enabled. Ignored while `requirements` is set, which takes the badge. Defaults to "Enabled" at the "stable" intent. */
|
|
85
|
+
badge?: CardBadge;
|
|
82
86
|
className?: string;
|
|
83
87
|
};
|
|
84
88
|
|
|
@@ -91,6 +95,7 @@ type CardFeatureProps = {
|
|
|
91
95
|
*/
|
|
92
96
|
const CardFeature = ( {
|
|
93
97
|
title,
|
|
98
|
+
headingLevel = 3,
|
|
94
99
|
description,
|
|
95
100
|
icon,
|
|
96
101
|
enabled = false,
|
|
@@ -102,20 +107,22 @@ const CardFeature = ( {
|
|
|
102
107
|
onEnable,
|
|
103
108
|
onConfigure,
|
|
104
109
|
moreControls,
|
|
105
|
-
|
|
106
|
-
badgeLevel = 'success',
|
|
110
|
+
badge: badgeProp,
|
|
107
111
|
className,
|
|
108
112
|
}: CardFeatureProps ) => {
|
|
113
|
+
const instanceId = useInstanceId( CardFeature, 'newspack-card-feature' );
|
|
114
|
+
const badgeId = `${ instanceId }__badge`;
|
|
115
|
+
const describedById = requirements ? badgeId : undefined;
|
|
109
116
|
const isMuted = !! requirements;
|
|
110
117
|
const classes = classnames( 'newspack-card-feature', className, {
|
|
111
118
|
'newspack-card-feature--muted': isMuted,
|
|
112
119
|
} );
|
|
113
120
|
|
|
114
|
-
let badge: {
|
|
121
|
+
let badge: { label: string; intent: BadgeIntent } | undefined;
|
|
115
122
|
if ( requirements ) {
|
|
116
|
-
badge = {
|
|
123
|
+
badge = { label: requirements, intent: 'high' };
|
|
117
124
|
} else if ( enabled ) {
|
|
118
|
-
badge = {
|
|
125
|
+
badge = { label: badgeProp?.label ?? __( 'Enabled', 'newspack-plugin' ), intent: badgeProp?.intent ?? 'stable' };
|
|
119
126
|
}
|
|
120
127
|
|
|
121
128
|
const isConfigureState = enabled && ! requirements;
|
|
@@ -149,7 +156,9 @@ const CardFeature = ( {
|
|
|
149
156
|
renderedIcon = icon;
|
|
150
157
|
} else if ( iconDescriptor ) {
|
|
151
158
|
renderedIcon = (
|
|
159
|
+
// Decorative: a vendor mark passed as `node` carries no aria-hidden of its own.
|
|
152
160
|
<div
|
|
161
|
+
aria-hidden="true"
|
|
153
162
|
className={ iconClasses }
|
|
154
163
|
style={ {
|
|
155
164
|
backgroundColor: iconDescriptor.backgroundColor,
|
|
@@ -162,51 +171,52 @@ const CardFeature = ( {
|
|
|
162
171
|
}
|
|
163
172
|
|
|
164
173
|
return (
|
|
165
|
-
<Card
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
title
|
|
198
|
-
) }
|
|
199
|
-
controls={ moreControls }
|
|
200
|
-
toggleProps={ { size: 'compact' } }
|
|
201
|
-
/>
|
|
174
|
+
<Card.Root className={ classes }>
|
|
175
|
+
<Card.Header>
|
|
176
|
+
<Stack direction="row" align="start" gap="lg">
|
|
177
|
+
<Stack className="newspack-card-feature__content" direction="column" gap="sm">
|
|
178
|
+
{ createElement( `h${ headingLevel }`, { className: 'newspack-card-feature__title' }, title ) }
|
|
179
|
+
{ description && <p className="newspack-card-feature__description">{ description }</p> }
|
|
180
|
+
</Stack>
|
|
181
|
+
{ renderedIcon }
|
|
182
|
+
</Stack>
|
|
183
|
+
</Card.Header>
|
|
184
|
+
<Card.Content className="newspack-card-feature__actions">
|
|
185
|
+
<Stack direction="row" align="center" justify="space-between" gap="sm" wrap="wrap">
|
|
186
|
+
<Stack direction="row" align="center" gap="sm">
|
|
187
|
+
<Button
|
|
188
|
+
variant={ isConfigureState ? 'tertiary' : 'secondary' }
|
|
189
|
+
accessibleWhenDisabled
|
|
190
|
+
aria-describedby={ describedById }
|
|
191
|
+
aria-label={ buttonAccessibleLabel }
|
|
192
|
+
disabled={ ( isMuted && ! requirementsActionable ) || busy }
|
|
193
|
+
isBusy={ busy }
|
|
194
|
+
onClick={ handleButtonClick }
|
|
195
|
+
size="compact"
|
|
196
|
+
>
|
|
197
|
+
{ buttonLabel }
|
|
198
|
+
</Button>
|
|
199
|
+
{ showMoreControls && (
|
|
200
|
+
<DropdownMenu
|
|
201
|
+
icon={ moreVertical }
|
|
202
|
+
label={ sprintf(
|
|
203
|
+
// translators: %s: the feature's name.
|
|
204
|
+
__( 'More options for %s', 'newspack-plugin' ),
|
|
205
|
+
title
|
|
202
206
|
) }
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
207
|
+
controls={ moreControls }
|
|
208
|
+
toggleProps={ { size: 'compact' } }
|
|
209
|
+
/>
|
|
210
|
+
) }
|
|
211
|
+
</Stack>
|
|
212
|
+
{ badge && (
|
|
213
|
+
<Badge id={ describedById } className="newspack-card-feature__badge" intent={ badge.intent }>
|
|
214
|
+
{ badge.label }
|
|
215
|
+
</Badge>
|
|
216
|
+
) }
|
|
217
|
+
</Stack>
|
|
218
|
+
</Card.Content>
|
|
219
|
+
</Card.Root>
|
|
210
220
|
);
|
|
211
221
|
};
|
|
212
222
|
|
|
@@ -2,55 +2,51 @@
|
|
|
2
2
|
* CardFeature
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
@use "~@wordpress/base-styles/
|
|
5
|
+
@use "~@wordpress/base-styles/mixins" as wp-mixins;
|
|
6
6
|
@use "~@wordpress/base-styles/variables" as wp-vars;
|
|
7
|
+
@use "../mixins" as np-mixins;
|
|
7
8
|
|
|
8
9
|
.newspack-card-feature {
|
|
9
10
|
height: 100%;
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.newspack-card--core__header-content {
|
|
16
|
-
gap: 16px;
|
|
17
|
-
display: flex;
|
|
18
|
-
flex-direction: column;
|
|
19
|
-
height: 100%;
|
|
12
|
+
&__actions {
|
|
13
|
+
margin-top: auto;
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
.newspack-card-feature__badge {
|
|
16
|
+
flex-shrink: 1;
|
|
17
|
+
min-width: 0;
|
|
24
18
|
}
|
|
25
19
|
}
|
|
26
20
|
|
|
27
21
|
&__content {
|
|
28
22
|
flex: 1;
|
|
29
|
-
min-width: 0;
|
|
23
|
+
min-width: 0;
|
|
30
24
|
}
|
|
31
25
|
|
|
32
26
|
&__title {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
@include wp-mixins.heading-x-large();
|
|
28
|
+
|
|
29
|
+
margin: 0;
|
|
36
30
|
}
|
|
37
31
|
|
|
38
32
|
&__description {
|
|
39
|
-
text-
|
|
33
|
+
@include np-mixins.text-muted;
|
|
34
|
+
|
|
35
|
+
margin: 0;
|
|
40
36
|
}
|
|
41
37
|
|
|
42
38
|
&__icon {
|
|
43
39
|
align-items: center;
|
|
44
40
|
display: flex;
|
|
45
|
-
flex: 0 0
|
|
46
|
-
height:
|
|
41
|
+
flex: 0 0 wp-vars.$grid-unit-50;
|
|
42
|
+
height: wp-vars.$grid-unit-50;
|
|
47
43
|
justify-content: center;
|
|
48
44
|
|
|
49
45
|
svg {
|
|
50
46
|
display: block;
|
|
51
47
|
fill: currentcolor;
|
|
52
|
-
height:
|
|
53
|
-
width:
|
|
48
|
+
height: wp-vars.$grid-unit-30;
|
|
49
|
+
width: wp-vars.$grid-unit-30;
|
|
54
50
|
}
|
|
55
51
|
|
|
56
52
|
&--radius-small {
|
|
@@ -62,14 +58,7 @@
|
|
|
62
58
|
}
|
|
63
59
|
}
|
|
64
60
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
border-color: wp-colors.$gray-100;
|
|
68
|
-
box-shadow: wp-colors.$gray-100 0 0 0 1px;
|
|
69
|
-
|
|
70
|
-
.newspack-card-feature__title,
|
|
71
|
-
.newspack-card-feature__description {
|
|
72
|
-
color: wp-colors.$gray-700;
|
|
73
|
-
}
|
|
61
|
+
&.newspack-card-feature--muted &__title {
|
|
62
|
+
@include np-mixins.text-muted;
|
|
74
63
|
}
|
|
75
64
|
}
|
package/src/card-form/README.md
CHANGED
|
@@ -33,7 +33,7 @@ const handleClose = () => setIsOpen( false );
|
|
|
33
33
|
<CardForm
|
|
34
34
|
title={ __( 'Above Header', 'newspack-plugin' ) }
|
|
35
35
|
description={ __( 'Displays an ad above the site header.', 'newspack-plugin' ) }
|
|
36
|
-
badge={ isEnabled ? {
|
|
36
|
+
badge={ isEnabled ? { intent: 'stable', label: __( 'Enabled', 'newspack-plugin' ) } : undefined }
|
|
37
37
|
actions={
|
|
38
38
|
isEnabled ? (
|
|
39
39
|
<Button variant="tertiary" size="compact" onClick={ () => isOpen ? handleClose() : setIsOpen( true ) }>
|
|
@@ -55,14 +55,16 @@ const handleClose = () => setIsOpen( false );
|
|
|
55
55
|
</CardForm>
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
## With a custom badge
|
|
58
|
+
## With a custom badge intent
|
|
59
59
|
|
|
60
|
-
The `badge` prop accepts any `
|
|
60
|
+
The `badge` prop accepts any `BadgeIntent`. Use `medium` or `high` to communicate a degraded state.
|
|
61
|
+
|
|
62
|
+
Omitting `intent` defaults to `stable`, because a `CardForm` badge marks a feature that is switched on. That differs from `ActionCard` and `SectionHeader`, whose badges are free-form labels and so default to `none`.
|
|
61
63
|
|
|
62
64
|
```tsx
|
|
63
65
|
<CardForm
|
|
64
66
|
title={ __( 'Above Header', 'newspack-plugin' ) }
|
|
65
|
-
badge={ {
|
|
67
|
+
badge={ { intent: 'medium', label: __( 'Missing ad unit', 'newspack-plugin' ) } }
|
|
66
68
|
actions={ <Button variant="tertiary" size="compact">{ __( 'Edit', 'newspack-plugin' ) }</Button> }
|
|
67
69
|
isOpen={ false }
|
|
68
70
|
/>
|
|
@@ -91,11 +93,11 @@ Omit `badge` (or pass `undefined`) to show no badge at all.
|
|
|
91
93
|
|---|---|---|---|
|
|
92
94
|
| `title` | `string` | — | Card heading (**required**) |
|
|
93
95
|
| `description` | `string` | — | Supporting text below the title |
|
|
94
|
-
| `badge` | `{
|
|
96
|
+
| `badge` | `{ label: string; intent?: BadgeIntent }` | — | Badge shown next to the actions slot. Omit or pass `undefined` to hide. |
|
|
95
97
|
| `actions` | `React.ReactNode` | — | JSX rendered in the header action area (buttons, dropdowns, etc.) |
|
|
96
98
|
| `isOpen` | `boolean` | `false` | When `true`, renders `children` in the card body and removes the header border |
|
|
97
99
|
| `onRequestClose` | `() => void` | — | Called when the user presses Escape while focus is inside the open form |
|
|
98
|
-
| `titleLevel` | `
|
|
100
|
+
| `titleLevel` | `2 \| 3 \| 4 \| 5 \| 6` | `3` | Heading level rendered for `title`. Pick the level that fits the surrounding document outline. |
|
|
99
101
|
| `className` | `string` | — | Additional class name applied to the card element |
|
|
100
102
|
| `children` | `React.ReactNode` | — | Form content rendered inside the card body when `isOpen` is `true` |
|
|
101
103
|
|
|
@@ -106,8 +108,6 @@ Omit `badge` (or pass `undefined`) to show no badge at all.
|
|
|
106
108
|
- The Escape listener is scoped to the open form's body, so multiple open cards do not all close on a single keypress. If an inner control needs to consume Escape (for example, to close its own menu), call `event.preventDefault()` and CardForm will ignore it.
|
|
107
109
|
|
|
108
110
|
|
|
109
|
-
### `
|
|
111
|
+
### `BadgeIntent`
|
|
110
112
|
|
|
111
|
-
|
|
112
|
-
type BadgeLevel = 'default' | 'info' | 'success' | 'warning' | 'error';
|
|
113
|
-
```
|
|
113
|
+
Defined in [`src/types.ts`](../types.ts), which derives the union from the `@wordpress/ui` `Badge` component so the values track the library instead of a copy kept here.
|
package/src/card-form/index.tsx
CHANGED
|
@@ -17,23 +17,19 @@ import classnames from 'classnames';
|
|
|
17
17
|
import { useEffect, useRef, createElement } from '@wordpress/element';
|
|
18
18
|
import { useInstanceId } from '@wordpress/compose';
|
|
19
19
|
import { __experimentalHStack as HStack, __experimentalVStack as VStack } from '@wordpress/components'; // eslint-disable-line @wordpress/no-unsafe-wp-apis
|
|
20
|
+
import { Badge } from '@wordpress/ui';
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Internal dependencies
|
|
23
24
|
*/
|
|
24
|
-
import Badge, { BadgeLevel } from '../badge';
|
|
25
25
|
import Card from '../card';
|
|
26
|
+
import type { CardBadge, HeadingLevel } from '../types';
|
|
26
27
|
import './style.scss';
|
|
27
28
|
|
|
28
|
-
type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
29
|
-
|
|
30
29
|
type CardFormProps = {
|
|
31
30
|
title: string;
|
|
32
31
|
description?: string;
|
|
33
|
-
badge?:
|
|
34
|
-
text: string;
|
|
35
|
-
level?: BadgeLevel;
|
|
36
|
-
};
|
|
32
|
+
badge?: CardBadge;
|
|
37
33
|
/** JSX rendered in the header action area (buttons, etc.). */
|
|
38
34
|
actions?: React.ReactNode;
|
|
39
35
|
/** When true, children are shown and the header border is removed. */
|
|
@@ -109,7 +105,7 @@ const CardForm = ( { title, description, badge, actions, isOpen = false, onReque
|
|
|
109
105
|
{ description && <p className="newspack-card-form__description">{ description }</p> }
|
|
110
106
|
</VStack>
|
|
111
107
|
<HStack spacing={ 2 } expanded={ false }>
|
|
112
|
-
{ badge && <Badge
|
|
108
|
+
{ badge?.label && <Badge intent={ badge.intent ?? 'stable' }>{ badge.label }</Badge> }
|
|
113
109
|
{ actions }
|
|
114
110
|
</HStack>
|
|
115
111
|
</HStack>
|
|
@@ -16,8 +16,7 @@ A drag-and-drop sortable list of cards. Each item displays a title and a status
|
|
|
16
16
|
|-------|------|-------------|
|
|
17
17
|
| `id` | `string` \| `number` | Unique identifier for the item. Used as the React `key`. |
|
|
18
18
|
| `title` | `string` | Label rendered in the card header. |
|
|
19
|
-
| `
|
|
20
|
-
| `badgeText` | `string` | Text displayed inside the badge. |
|
|
19
|
+
| `badge` | `{ label: string; intent?: BadgeIntent }` | Status badge for the row. Omit it, or leave `label` empty, to render no badge. `intent` defaults to `none`. See [`BadgeIntent`](../types.ts) for the available intents. |
|
|
21
20
|
|
|
22
21
|
## Usage
|
|
23
22
|
|
|
@@ -25,9 +24,9 @@ A drag-and-drop sortable list of cards. Each item displays a title and a status
|
|
|
25
24
|
import { CardSortableList } from 'newspack-components';
|
|
26
25
|
|
|
27
26
|
const [ items, setItems ] = useState( [
|
|
28
|
-
{ id: 1, title: 'Homepage',
|
|
29
|
-
{ id: 2, title: 'About',
|
|
30
|
-
{ id: 3, title: 'Contact',
|
|
27
|
+
{ id: 1, title: 'Homepage', badge: { label: 'Active', intent: 'stable' } },
|
|
28
|
+
{ id: 2, title: 'About', badge: { label: 'Draft', intent: 'draft' } },
|
|
29
|
+
{ id: 3, title: 'Contact', badge: { label: 'Review', intent: 'medium' } },
|
|
31
30
|
] );
|
|
32
31
|
|
|
33
32
|
const handleReorder = ( fromIndex, toIndex ) => {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies.
|
|
3
|
+
*/
|
|
4
|
+
import { render, screen } from '@testing-library/react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal dependencies.
|
|
8
|
+
*/
|
|
9
|
+
import CardSortableList from './';
|
|
10
|
+
|
|
11
|
+
const items = [
|
|
12
|
+
{ id: 1, title: 'Homepage', badge: { label: 'Active', intent: 'stable' } },
|
|
13
|
+
{ id: 2, title: 'About', badge: { label: 'Draft', intent: 'draft' } },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
describe( 'CardSortableList', () => {
|
|
17
|
+
it( 'renders a badge for each item that has badge text', () => {
|
|
18
|
+
render( <CardSortableList items={ items } /> );
|
|
19
|
+
expect( screen.getByText( 'Active' ) ).toBeInTheDocument();
|
|
20
|
+
expect( screen.getByText( 'Draft' ) ).toBeInTheDocument();
|
|
21
|
+
} );
|
|
22
|
+
|
|
23
|
+
it( 'renders no badge for an item without badge text', () => {
|
|
24
|
+
const { container } = render( <CardSortableList items={ [ { id: 1, title: 'Homepage' } ] } /> );
|
|
25
|
+
expect( screen.getByText( 'Homepage' ) ).toBeInTheDocument();
|
|
26
|
+
// An unguarded Badge would still emit an empty span, so assert on element children.
|
|
27
|
+
expect( container.querySelector( 'h3' ).children ).toHaveLength( 0 );
|
|
28
|
+
} );
|
|
29
|
+
} );
|
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { Disabled, Draggable, __experimentalVStack as VStack } from '@wordpress/components'; // eslint-disable-line @wordpress/no-unsafe-wp-apis
|
|
9
9
|
import { useEffect, useLayoutEffect, useRef, useState } from '@wordpress/element';
|
|
10
|
+
import { Badge } from '@wordpress/ui';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Internal dependencies
|
|
13
14
|
*/
|
|
14
|
-
import {
|
|
15
|
+
import { Card } from '../';
|
|
16
|
+
import type { CardBadge } from '../types';
|
|
15
17
|
import './style.scss';
|
|
16
18
|
|
|
17
19
|
/**
|
|
@@ -34,8 +36,7 @@ type DraggableItem = {
|
|
|
34
36
|
id: string | number;
|
|
35
37
|
title: string;
|
|
36
38
|
description?: React.ReactNode;
|
|
37
|
-
|
|
38
|
-
badgeText: string;
|
|
39
|
+
badge?: CardBadge;
|
|
39
40
|
toggleChecked?: boolean;
|
|
40
41
|
onToggleChange?: () => void;
|
|
41
42
|
actions?: DraggableItemAction[];
|
|
@@ -402,7 +403,9 @@ const CardSortableList = ( {
|
|
|
402
403
|
<>
|
|
403
404
|
<h3>
|
|
404
405
|
{ item.title }
|
|
405
|
-
|
|
406
|
+
{ item.badge?.label && (
|
|
407
|
+
<Badge intent={ item.badge.intent ?? 'none' }>{ item.badge.label }</Badge>
|
|
408
|
+
) }
|
|
406
409
|
</h3>
|
|
407
410
|
{ item.description && <p>{ item.description }</p> }
|
|
408
411
|
</>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# CollapsibleGroup
|
|
2
|
+
|
|
3
|
+
A stack of independently collapsible items, separated by dividers and sitting flush with the surrounding column. Built on `Collapsible` from `@wordpress/ui`.
|
|
4
|
+
|
|
5
|
+
Each item is its own disclosure: opening one does not close the others. This is deliberately not a W3C accordion, which coordinates its panels and moves focus between headers with the arrow keys, so the component is not named for one. It is also not `CollapsibleCard` from the same library, which renders its root as a `Card` and so brings a border, a background and card padding; this component sits flush with the column around it.
|
|
6
|
+
|
|
7
|
+
A collapsed item is hidden with `hidden="until-found"`, so the browser's find-in-page can match text inside it and expand the item to reveal the result. Find-in-page can only match text that exists, so a collapsed item keeps its children mounted, out of the focus order and the accessibility tree but present in the DOM. Every item therefore renders on first paint, and a child's state and effects persist while it is closed. Keep expensive children, or anything that fetches on mount, behind the item's own open state.
|
|
8
|
+
|
|
9
|
+
## Props
|
|
10
|
+
|
|
11
|
+
| Prop | Type | Default | Description |
|
|
12
|
+
|------|------|---------|-------------|
|
|
13
|
+
| `className` | `string` | — | Additional class on the group wrapper. |
|
|
14
|
+
| `hideSingleTitle` | `boolean` | `false` | When the group holds exactly one item, render it open and drop its title. Use it where a group can collapse to a single section and the title would repeat the heading above it. |
|
|
15
|
+
| `gap` | `GapSize` | `'xl'` | `Stack` gap, on the design system's token scale (`'xl'` is 24px). The divider is a sibling of the items rather than part of one, so the gap applies on both sides of it and items sit twice this far apart: 49px at the default. |
|
|
16
|
+
| `titleLevel` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | inherited, or `2` | Heading level for every item title. Set it once on the group so the items share one place in the document outline: under a section header rendered as `h2`, pass `3`. It changes the tag only, never the size, so the same group looks the same wherever it sits. A group nested inside another matches the level it inherits rather than descending a step, so give a nested group its own `titleLevel`. A value outside the range is clamped to the nearest valid level. |
|
|
17
|
+
|
|
18
|
+
Children must be `CollapsibleGroup.Item`. Bare text and numbers are dropped; any other element counts as one item and takes a divider, so a wrapper component, or a `Fragment` holding two items, puts the dividers in the wrong place.
|
|
19
|
+
|
|
20
|
+
### `CollapsibleGroup.Item`
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Description |
|
|
23
|
+
|------|------|---------|-------------|
|
|
24
|
+
| `className` | `string` | — | Additional class on the item. |
|
|
25
|
+
| `defaultOpen` | `boolean` | `false` | Whether the item starts expanded. |
|
|
26
|
+
| `title` | `string` | — | Trigger label, rendered as a button inside the heading. Without a title there is no trigger and the content renders permanently open. |
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```jsx
|
|
31
|
+
import { CollapsibleGroup } from 'newspack-components';
|
|
32
|
+
|
|
33
|
+
<CollapsibleGroup titleLevel={ 3 }>
|
|
34
|
+
<CollapsibleGroup.Item title="Contact fields" defaultOpen>
|
|
35
|
+
…
|
|
36
|
+
</CollapsibleGroup.Item>
|
|
37
|
+
<CollapsibleGroup.Item title="Tags and segments">
|
|
38
|
+
…
|
|
39
|
+
</CollapsibleGroup.Item>
|
|
40
|
+
</CollapsibleGroup>
|
|
41
|
+
|
|
42
|
+
// A group that may collapse to one section
|
|
43
|
+
<CollapsibleGroup hideSingleTitle>
|
|
44
|
+
{ groups.map( group => (
|
|
45
|
+
<CollapsibleGroup.Item key={ group.id } title={ group.section }>
|
|
46
|
+
…
|
|
47
|
+
</CollapsibleGroup.Item>
|
|
48
|
+
) ) }
|
|
49
|
+
</CollapsibleGroup>
|
|
50
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { createContext, useContext } from '@wordpress/element';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal dependencies
|
|
8
|
+
*/
|
|
9
|
+
import type { HeadingLevel } from './types';
|
|
10
|
+
|
|
11
|
+
export const TitleLevelContext = createContext< HeadingLevel >( 2 );
|
|
12
|
+
|
|
13
|
+
export const useTitleLevel = (): HeadingLevel => useContext( TitleLevelContext );
|