newspack-components 4.6.3 → 4.7.0-alpha.2
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 +7 -0
- package/colors/colors.module.scss +13 -7
- package/dist/cjs/autocomplete-tokenfield/style.scss +4 -2
- package/dist/cjs/breadcrumbs/format-count.js +33 -0
- package/dist/cjs/breadcrumbs/index.js +78 -20
- package/dist/cjs/breadcrumbs/index.test.js +110 -0
- package/dist/cjs/breadcrumbs/style.scss +6 -0
- package/dist/cjs/button/index.js +41 -17
- package/dist/cjs/button/index.test.js +273 -0
- package/dist/cjs/card/core-card.js +9 -4
- package/dist/cjs/card/core-card.test.js +29 -0
- package/dist/cjs/card/index.js +4 -2
- package/dist/cjs/card/style-core.scss +59 -0
- package/dist/cjs/card-feature/index.js +7 -1
- package/dist/cjs/card-feature/index.test.js +92 -0
- package/dist/cjs/color-picker/index.js +1 -1
- package/dist/cjs/confirm-dialog/index.js +26 -9
- package/dist/cjs/confirm-dialog/index.test.js +176 -0
- package/dist/cjs/dataviews/index.js +11 -2
- package/dist/cjs/divider/index.js +13 -0
- package/dist/cjs/drawer/action.js +74 -0
- package/dist/cjs/drawer/body.js +79 -0
- package/dist/cjs/drawer/close-icon.js +45 -0
- package/dist/cjs/drawer/content.js +72 -0
- package/dist/cjs/drawer/context.js +23 -0
- package/dist/cjs/drawer/context.test.js +43 -0
- package/dist/cjs/drawer/divider.js +24 -0
- package/dist/cjs/drawer/footer.js +26 -0
- package/dist/cjs/drawer/header.js +26 -0
- package/dist/cjs/drawer/index.js +42 -0
- package/dist/cjs/drawer/index.test.js +1287 -0
- package/dist/cjs/drawer/root.js +350 -0
- package/dist/cjs/drawer/slots.test.js +422 -0
- package/dist/cjs/drawer/style.scss +205 -0
- package/dist/cjs/drawer/title.js +54 -0
- package/dist/cjs/drawer/types.js +5 -0
- package/dist/cjs/drawer/use-exit-animation.js +59 -0
- package/dist/cjs/hooks/use-confirm-dialog.js +15 -3
- package/dist/cjs/hooks/use-unsaved-changes-dialog.js +19 -4
- package/dist/cjs/index.js +28 -1
- package/dist/cjs/page/index.js +1 -1
- package/dist/cjs/page-control/index.js +82 -0
- package/dist/cjs/plugin-settings/SettingsSection.js +7 -1
- package/dist/cjs/section-header/index.js +5 -2
- package/dist/cjs/section-header/style.scss +25 -12
- package/dist/cjs/table-card/index.js +67 -0
- package/dist/cjs/table-card/index.test.js +153 -0
- package/dist/cjs/with-wizard-screen/style.scss +8 -0
- package/dist/cjs/wizard/index.js +102 -38
- package/dist/esm/autocomplete-tokenfield/style.scss +4 -2
- package/dist/esm/breadcrumbs/format-count.js +27 -0
- package/dist/esm/breadcrumbs/index.js +81 -20
- package/dist/esm/breadcrumbs/index.test.js +110 -0
- package/dist/esm/breadcrumbs/style.scss +6 -0
- package/dist/esm/button/index.js +42 -18
- package/dist/esm/button/index.test.js +273 -0
- package/dist/esm/card/core-card.js +9 -4
- package/dist/esm/card/core-card.test.js +29 -0
- package/dist/esm/card/index.js +4 -2
- package/dist/esm/card/style-core.scss +59 -0
- package/dist/esm/card-feature/index.js +8 -2
- package/dist/esm/card-feature/index.test.js +96 -0
- package/dist/esm/color-picker/index.js +1 -1
- package/dist/esm/confirm-dialog/index.js +26 -9
- package/dist/esm/confirm-dialog/index.test.js +176 -0
- package/dist/esm/dataviews/index.js +11 -2
- package/dist/esm/divider/index.js +14 -0
- package/dist/esm/drawer/action.js +68 -0
- package/dist/esm/drawer/body.js +70 -0
- package/dist/esm/drawer/close-icon.js +37 -0
- package/dist/esm/drawer/content.js +63 -0
- package/dist/esm/drawer/context.js +17 -0
- package/dist/esm/drawer/context.test.js +40 -0
- package/dist/esm/drawer/divider.js +16 -0
- package/dist/esm/drawer/footer.js +18 -0
- package/dist/esm/drawer/header.js +18 -0
- package/dist/esm/drawer/index.js +35 -0
- package/dist/esm/drawer/index.test.js +1285 -0
- package/dist/esm/drawer/root.js +342 -0
- package/dist/esm/drawer/slots.test.js +422 -0
- package/dist/esm/drawer/style.scss +205 -0
- package/dist/esm/drawer/title.js +46 -0
- package/dist/esm/drawer/types.js +1 -0
- package/dist/esm/drawer/use-exit-animation.js +52 -0
- package/dist/esm/hooks/use-confirm-dialog.js +15 -3
- package/dist/esm/hooks/use-unsaved-changes-dialog.js +20 -5
- package/dist/esm/index.js +4 -0
- package/dist/esm/page/index.js +1 -1
- package/dist/esm/page-control/index.js +82 -0
- package/dist/esm/plugin-settings/SettingsSection.js +8 -2
- package/dist/esm/section-header/index.js +5 -2
- package/dist/esm/section-header/style.scss +25 -12
- package/dist/esm/table-card/index.js +60 -0
- package/dist/esm/table-card/index.test.js +150 -0
- package/dist/esm/with-wizard-screen/style.scss +8 -0
- package/dist/esm/wizard/index.js +104 -38
- package/package.json +2 -2
- package/src/autocomplete-tokenfield/style.scss +4 -2
- package/src/breadcrumbs/format-count.js +27 -0
- package/src/breadcrumbs/index.js +68 -8
- package/src/breadcrumbs/index.test.js +57 -0
- package/src/breadcrumbs/style.scss +6 -0
- package/src/button/index.test.js +143 -0
- package/src/button/index.tsx +32 -7
- package/src/card/core-card.js +15 -2
- package/src/card/core-card.test.js +19 -0
- package/src/card/index.js +3 -0
- package/src/card/style-core.scss +59 -0
- package/src/card-feature/README.md +4 -0
- package/src/card-feature/index.test.js +65 -0
- package/src/card-feature/index.tsx +13 -2
- package/src/color-picker/index.js +1 -1
- package/src/confirm-dialog/README.md +3 -2
- package/src/confirm-dialog/index.test.js +146 -0
- package/src/confirm-dialog/index.tsx +23 -9
- package/src/dataviews/index.tsx +11 -2
- package/src/divider/index.js +13 -0
- package/src/drawer/README.md +454 -0
- package/src/drawer/action.tsx +67 -0
- package/src/drawer/body.tsx +60 -0
- package/src/drawer/close-icon.tsx +37 -0
- package/src/drawer/content.tsx +54 -0
- package/src/drawer/context.test.js +31 -0
- package/src/drawer/context.ts +25 -0
- package/src/drawer/divider.tsx +13 -0
- package/src/drawer/footer.tsx +15 -0
- package/src/drawer/header.tsx +15 -0
- package/src/drawer/index.test.js +1012 -0
- package/src/drawer/index.tsx +33 -0
- package/src/drawer/root.tsx +328 -0
- package/src/drawer/slots.test.js +314 -0
- package/src/drawer/style.scss +205 -0
- package/src/drawer/title.tsx +40 -0
- package/src/drawer/types.ts +79 -0
- package/src/drawer/use-exit-animation.ts +46 -0
- package/src/hooks/use-confirm-dialog.tsx +14 -3
- package/src/hooks/use-unsaved-changes-dialog.tsx +17 -5
- package/src/index.js +4 -0
- package/src/page/index.js +1 -1
- package/src/page-control/index.js +63 -0
- package/src/plugin-settings/SettingsSection.js +8 -2
- package/src/section-header/index.js +5 -2
- package/src/section-header/style.scss +25 -12
- package/src/table-card/README.md +100 -0
- package/src/table-card/index.test.js +112 -0
- package/src/table-card/index.tsx +56 -0
- package/src/with-wizard-screen/style.scss +8 -0
- package/src/wizard/index.js +95 -34
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
@use "~@wordpress/base-styles/breakpoints" as wp-breakpoints;
|
|
2
|
+
@use "~@wordpress/base-styles/colors" as wp-colors;
|
|
3
|
+
@use "~@wordpress/base-styles/variables" as wp-vars;
|
|
4
|
+
|
|
5
|
+
$drawer-size-small: 280px;
|
|
6
|
+
$drawer-size-medium: 350px;
|
|
7
|
+
$drawer-size-large: 480px;
|
|
8
|
+
$drawer-size-x-large: 640px;
|
|
9
|
+
|
|
10
|
+
.newspack-drawer,
|
|
11
|
+
.newspack-drawer *,
|
|
12
|
+
.newspack-drawer *::before,
|
|
13
|
+
.newspack-drawer *::after {
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.newspack-drawer {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.components-modal__screen-overlay.newspack-drawer__overlay {
|
|
23
|
+
align-items: stretch;
|
|
24
|
+
justify-content: flex-end;
|
|
25
|
+
padding: 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.components-modal__frame.newspack-drawer {
|
|
29
|
+
animation: 0.2s ease-out newspack-drawer-slide-in;
|
|
30
|
+
border-radius: wp-vars.$radius-large 0 0 wp-vars.$radius-large;
|
|
31
|
+
height: 100%;
|
|
32
|
+
margin: 0;
|
|
33
|
+
max-height: none;
|
|
34
|
+
min-width: 0;
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
width: 100%;
|
|
37
|
+
|
|
38
|
+
&.is-exiting {
|
|
39
|
+
animation: 0.2s ease-in forwards newspack-drawer-slide-out;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.components-modal__content {
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
padding: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.components-modal__children-container {
|
|
49
|
+
display: flex;
|
|
50
|
+
flex: 1 1 auto;
|
|
51
|
+
flex-direction: column;
|
|
52
|
+
min-height: 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
&.newspack-drawer--size-small { max-width: $drawer-size-small; }
|
|
56
|
+
&.newspack-drawer--size-medium { max-width: $drawer-size-medium; }
|
|
57
|
+
&.newspack-drawer--size-large { max-width: $drawer-size-large; }
|
|
58
|
+
&.newspack-drawer--size-x-large { max-width: $drawer-size-x-large; }
|
|
59
|
+
&.newspack-drawer--size-full {
|
|
60
|
+
border-radius: 0;
|
|
61
|
+
max-width: 100vw;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@media (max-width: $drawer-size-x-large) {
|
|
65
|
+
&.newspack-drawer--size-x-large {
|
|
66
|
+
border-radius: 0;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@media (max-width: wp-breakpoints.$break-small) {
|
|
71
|
+
&.newspack-drawer--size-small,
|
|
72
|
+
&.newspack-drawer--size-medium,
|
|
73
|
+
&.newspack-drawer--size-large,
|
|
74
|
+
&.newspack-drawer--size-x-large,
|
|
75
|
+
&.newspack-drawer--size-full {
|
|
76
|
+
border-radius: 0;
|
|
77
|
+
max-width: 100vw;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.newspack-drawer__header {
|
|
83
|
+
align-items: center;
|
|
84
|
+
display: flex;
|
|
85
|
+
flex: 0 0 auto;
|
|
86
|
+
gap: wp-vars.$grid-unit-10;
|
|
87
|
+
padding: wp-vars.$grid-unit-30 wp-vars.$grid-unit-30 wp-vars.$grid-unit-20;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.newspack-drawer__icon {
|
|
91
|
+
fill: currentcolor;
|
|
92
|
+
flex: none;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.newspack-drawer__title {
|
|
96
|
+
flex: 1;
|
|
97
|
+
font-size: inherit;
|
|
98
|
+
font-weight: wp-vars.$font-weight-medium;
|
|
99
|
+
margin: 0;
|
|
100
|
+
min-width: 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.newspack-drawer__dismiss {
|
|
104
|
+
flex: none;
|
|
105
|
+
margin-left: auto;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.newspack-drawer__body {
|
|
109
|
+
flex: 1 1 auto;
|
|
110
|
+
overflow-y: auto;
|
|
111
|
+
|
|
112
|
+
> * {
|
|
113
|
+
flex: none;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.newspack-drawer__content {
|
|
118
|
+
padding: var(--newspack-drawer-content-padding, #{wp-vars.$grid-unit-30});
|
|
119
|
+
|
|
120
|
+
> * {
|
|
121
|
+
margin-bottom: 0;
|
|
122
|
+
margin-top: 0;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.newspack-drawer__divider {
|
|
127
|
+
border: 0;
|
|
128
|
+
border-top: wp-vars.$border-width solid wp-colors.$gray-300;
|
|
129
|
+
margin: 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.newspack-drawer__header + .newspack-drawer__body .newspack-drawer__content:first-child {
|
|
133
|
+
padding-top: 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.newspack-drawer__body:has(+ .newspack-drawer__footer) .newspack-drawer__content:last-child {
|
|
137
|
+
padding-bottom: 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.newspack-drawer__footer {
|
|
141
|
+
background: wp-colors.$white;
|
|
142
|
+
display: flex;
|
|
143
|
+
flex: 0 0 auto;
|
|
144
|
+
gap: wp-vars.$grid-unit-10;
|
|
145
|
+
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
|
+
}
|
|
160
|
+
|
|
161
|
+
@keyframes newspack-drawer-slide-in {
|
|
162
|
+
from {
|
|
163
|
+
transform: translateX(100%);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
to {
|
|
167
|
+
transform: translateX(0);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@keyframes newspack-drawer-slide-out {
|
|
172
|
+
from {
|
|
173
|
+
transform: translateX(0);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
to {
|
|
177
|
+
transform: translateX(100%);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@keyframes newspack-drawer-overlay-fade-out {
|
|
182
|
+
from {
|
|
183
|
+
opacity: 1;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
to {
|
|
187
|
+
opacity: 0;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.newspack-drawer__overlay.is-exiting {
|
|
192
|
+
animation: 0.2s ease-in forwards newspack-drawer-overlay-fade-out;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.components-modal__frame.newspack-drawer .components-popover__content {
|
|
196
|
+
opacity: 1;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@media (prefers-reduced-motion: reduce) {
|
|
200
|
+
.components-modal__frame.newspack-drawer,
|
|
201
|
+
.components-modal__frame.newspack-drawer.is-exiting,
|
|
202
|
+
.newspack-drawer__overlay.is-exiting {
|
|
203
|
+
animation: none;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies.
|
|
3
|
+
*/
|
|
4
|
+
import { useInstanceId } from '@wordpress/compose';
|
|
5
|
+
import { useLayoutEffect } from '@wordpress/element';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* External dependencies.
|
|
9
|
+
*/
|
|
10
|
+
import classnames from 'classnames';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Internal dependencies.
|
|
14
|
+
*/
|
|
15
|
+
import { useDrawerContext } from './context';
|
|
16
|
+
import type { DrawerTitleProps } from './types';
|
|
17
|
+
|
|
18
|
+
const Title = ( { className, children }: DrawerTitleProps ) => {
|
|
19
|
+
const { setTitle } = useDrawerContext();
|
|
20
|
+
const id = useInstanceId( Title, 'newspack-drawer__title' ) as string;
|
|
21
|
+
|
|
22
|
+
// Derived, not keyed on `children`: a JSX title would re-register every render.
|
|
23
|
+
const text = typeof children === 'string' ? children : null;
|
|
24
|
+
|
|
25
|
+
useLayoutEffect( () => {
|
|
26
|
+
setTitle( { id, text } );
|
|
27
|
+
}, [ id, text, setTitle ] );
|
|
28
|
+
|
|
29
|
+
// Unmount only, scoped to this id: a text change must not re-order the stack,
|
|
30
|
+
// nor a second title unname the one still rendered.
|
|
31
|
+
useLayoutEffect( () => () => setTitle( null, id ), [ id, setTitle ] );
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<h2 className={ classnames( 'newspack-drawer__title', className ) } id={ id }>
|
|
35
|
+
{ children }
|
|
36
|
+
</h2>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default Title;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export type DrawerSize = 'small' | 'medium' | 'large' | 'x-large' | 'full';
|
|
2
|
+
|
|
3
|
+
/** `text` is null unless the title's children are a plain string. */
|
|
4
|
+
export type DrawerTitleInfo = {
|
|
5
|
+
id: string;
|
|
6
|
+
text: string | null;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type DrawerRootProps = {
|
|
10
|
+
isOpen?: boolean;
|
|
11
|
+
size?: DrawerSize;
|
|
12
|
+
onRequestClose: () => void;
|
|
13
|
+
isDirty?: boolean;
|
|
14
|
+
confirmCloseMessage?: React.ReactNode;
|
|
15
|
+
confirmButtonText?: string;
|
|
16
|
+
requestConfirm?: ( callback: () => void ) => void;
|
|
17
|
+
contentLabel?: string;
|
|
18
|
+
describedBy?: string;
|
|
19
|
+
className?: string;
|
|
20
|
+
/** Merged into the frame's own style. A `ref` reaches the overlay around it. */
|
|
21
|
+
style?: React.CSSProperties;
|
|
22
|
+
children?: React.ReactNode;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type DrawerHeaderProps = {
|
|
26
|
+
className?: string;
|
|
27
|
+
children?: React.ReactNode;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type DrawerTitleProps = {
|
|
31
|
+
className?: string;
|
|
32
|
+
children?: React.ReactNode;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type DrawerCloseIconProps = {
|
|
36
|
+
label?: string;
|
|
37
|
+
icon?: JSX.Element;
|
|
38
|
+
className?: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type DrawerContentProps = {
|
|
42
|
+
/** 4px scale, as VStack's `spacing`. `0` for a flush section. */
|
|
43
|
+
padding?: number;
|
|
44
|
+
/** 4px scale, as VStack's `spacing`. */
|
|
45
|
+
gap?: number;
|
|
46
|
+
className?: string;
|
|
47
|
+
children?: React.ReactNode;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type DrawerDividerProps = {
|
|
51
|
+
className?: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type DrawerBodyProps = {
|
|
55
|
+
children?: React.ReactNode;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type DrawerFooterProps = {
|
|
59
|
+
className?: string;
|
|
60
|
+
children?: React.ReactNode;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** `ariaLabel` extends the visible label and must contain it (WCAG 2.5.3, Label in Name). */
|
|
64
|
+
type DrawerActionBaseProps = {
|
|
65
|
+
ariaLabel?: string;
|
|
66
|
+
variant?: 'primary' | 'secondary' | 'tertiary' | 'link';
|
|
67
|
+
disabled?: boolean;
|
|
68
|
+
isBusy?: boolean;
|
|
69
|
+
isDestructive?: boolean;
|
|
70
|
+
className?: string;
|
|
71
|
+
children?: React.ReactNode;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** An action either navigates or acts: `href` cannot close the drawer or run a handler. */
|
|
75
|
+
export type DrawerActionProps = DrawerActionBaseProps &
|
|
76
|
+
(
|
|
77
|
+
| { href: string; onClick?: never; closes?: never }
|
|
78
|
+
| { href?: null; onClick?: ( event?: React.MouseEvent< HTMLElement > ) => void; closes?: boolean }
|
|
79
|
+
);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies.
|
|
3
|
+
*/
|
|
4
|
+
import { useReducedMotion } from '@wordpress/compose';
|
|
5
|
+
import { useEffect, useState } from '@wordpress/element';
|
|
6
|
+
|
|
7
|
+
// Covers an animationend that never arrives.
|
|
8
|
+
const EXIT_FALLBACK_MS = 400;
|
|
9
|
+
|
|
10
|
+
function useExitAnimation( isOpen: boolean, node: HTMLElement | null ) {
|
|
11
|
+
const [ isExiting, setIsExiting ] = useState( false );
|
|
12
|
+
const [ wasOpen, setWasOpen ] = useState( isOpen );
|
|
13
|
+
|
|
14
|
+
// The stylesheet zeroes the animation here, so animationend never fires and the
|
|
15
|
+
// fallback would hold a visible panel for 400ms.
|
|
16
|
+
const prefersReducedMotion = useReducedMotion();
|
|
17
|
+
|
|
18
|
+
// During render: a commit later the panel would leave the DOM and remount.
|
|
19
|
+
if ( wasOpen !== isOpen ) {
|
|
20
|
+
setWasOpen( isOpen );
|
|
21
|
+
setIsExiting( ! isOpen && ! prefersReducedMotion );
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
useEffect( () => {
|
|
25
|
+
if ( ! isExiting ) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Keyed on the name: animationend bubbles from anything else animating.
|
|
29
|
+
const finish = ( event?: AnimationEvent ) => {
|
|
30
|
+
if ( event?.animationName && 'newspack-drawer-slide-out' !== event.animationName ) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
setIsExiting( false );
|
|
34
|
+
};
|
|
35
|
+
const timer = setTimeout( finish, EXIT_FALLBACK_MS );
|
|
36
|
+
node?.addEventListener( 'animationend', finish );
|
|
37
|
+
return () => {
|
|
38
|
+
clearTimeout( timer );
|
|
39
|
+
node?.removeEventListener( 'animationend', finish );
|
|
40
|
+
};
|
|
41
|
+
}, [ isExiting, node ] );
|
|
42
|
+
|
|
43
|
+
return { isRendered: isOpen || isExiting, isExiting };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default useExitAnimation;
|
|
@@ -23,6 +23,7 @@ type UseConfirmDialogOptions = {
|
|
|
23
23
|
type UseConfirmDialogResult = {
|
|
24
24
|
confirmDialog: React.ReactElement;
|
|
25
25
|
requestConfirm: ( callback: () => void ) => void;
|
|
26
|
+
cancelConfirm: () => void;
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -40,6 +41,9 @@ type UseConfirmDialogResult = {
|
|
|
40
41
|
*
|
|
41
42
|
* The `confirmDialog` element must be rendered somewhere in the component's
|
|
42
43
|
* JSX for the dialog to appear.
|
|
44
|
+
*
|
|
45
|
+
* `cancelConfirm()` withdraws a pending request without invoking its callback,
|
|
46
|
+
* for when whatever raised the prompt goes away on its own.
|
|
43
47
|
*/
|
|
44
48
|
function useConfirmDialog( options: UseConfirmDialogOptions ): UseConfirmDialogResult {
|
|
45
49
|
const { message, when, ...dialogProps } = options;
|
|
@@ -59,14 +63,21 @@ function useConfirmDialog( options: UseConfirmDialogOptions ): UseConfirmDialogR
|
|
|
59
63
|
}
|
|
60
64
|
}, [] );
|
|
61
65
|
|
|
66
|
+
const cancelConfirm = useCallback( () => setPendingAction( null ), [] );
|
|
67
|
+
|
|
62
68
|
const confirmDialog = (
|
|
63
69
|
<ConfirmDialog
|
|
64
70
|
{ ...dialogProps }
|
|
65
71
|
when={ when }
|
|
66
72
|
isOpen={ !! pendingAction }
|
|
67
73
|
onConfirm={ () => {
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
// Cleared either way: a throwing callback would otherwise leave the
|
|
75
|
+
// dialog permanently unraisable for this instance.
|
|
76
|
+
try {
|
|
77
|
+
pendingAction?.();
|
|
78
|
+
} finally {
|
|
79
|
+
setPendingAction( null );
|
|
80
|
+
}
|
|
70
81
|
} }
|
|
71
82
|
onCancel={ () => setPendingAction( null ) }
|
|
72
83
|
>
|
|
@@ -74,7 +85,7 @@ function useConfirmDialog( options: UseConfirmDialogOptions ): UseConfirmDialogR
|
|
|
74
85
|
</ConfirmDialog>
|
|
75
86
|
);
|
|
76
87
|
|
|
77
|
-
return { confirmDialog, requestConfirm };
|
|
88
|
+
return { confirmDialog, requestConfirm, cancelConfirm };
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
export default useConfirmDialog;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WordPress dependencies.
|
|
3
3
|
*/
|
|
4
|
-
import { useEffect, useRef } from '@wordpress/element';
|
|
4
|
+
import { useCallback, useEffect, useRef } from '@wordpress/element';
|
|
5
5
|
import { __ } from '@wordpress/i18n';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -39,17 +39,20 @@ function isSameOriginNavigation( link: HTMLAnchorElement ): boolean {
|
|
|
39
39
|
* messaging, intercepts same-origin link clicks so the dialog fires instead
|
|
40
40
|
* of a silent navigation, and adds a `beforeunload` listener as the last-resort
|
|
41
41
|
* guard for refresh / tab-close (browser-native, cannot be styled). The
|
|
42
|
-
* returned `confirmDialog` element must be rendered in JSX
|
|
42
|
+
* returned `confirmDialog` element must be rendered in JSX, and `requestConfirm`
|
|
43
|
+
* and `cancelConfirm` behave as `useConfirmDialog` documents them.
|
|
43
44
|
*
|
|
44
45
|
* Single-consumer constraint: the click handler is attached at the document
|
|
45
46
|
* level in capture phase. Two simultaneously-active instances will both fire
|
|
46
47
|
* a dialog. A development-only warning surfaces this.
|
|
47
48
|
*/
|
|
48
49
|
function useUnsavedChangesDialog( { when }: UseUnsavedChangesDialogOptions ) {
|
|
49
|
-
const { confirmDialog, requestConfirm } = useConfirmDialog( {
|
|
50
|
+
const { confirmDialog, requestConfirm, cancelConfirm } = useConfirmDialog( {
|
|
50
51
|
when,
|
|
51
52
|
message: __( 'You have unsaved changes that will be lost. Discard changes?', 'newspack-plugin' ),
|
|
52
|
-
confirmButtonText: __( 'Discard
|
|
53
|
+
confirmButtonText: __( 'Discard Changes', 'newspack-plugin' ),
|
|
54
|
+
// Hidden, but still the dialog's accessible name.
|
|
55
|
+
title: __( 'Unsaved changes', 'newspack-plugin' ),
|
|
53
56
|
hideTitle: true,
|
|
54
57
|
} );
|
|
55
58
|
|
|
@@ -134,7 +137,16 @@ function useUnsavedChangesDialog( { when }: UseUnsavedChangesDialogOptions ) {
|
|
|
134
137
|
return () => window.removeEventListener( 'beforeunload', handler );
|
|
135
138
|
}, [ when ] );
|
|
136
139
|
|
|
137
|
-
|
|
140
|
+
// Escape hatch for an action that navigates on its own — a handoff that
|
|
141
|
+
// POSTs and then assigns `window.location` — once the dialog has already
|
|
142
|
+
// been confirmed. Left armed, the beforeunload guard would draw a second,
|
|
143
|
+
// native prompt on top of it. Call it immediately before navigating, so a
|
|
144
|
+
// failed action leaves the guard in place.
|
|
145
|
+
const allowNextUnload = useCallback( () => {
|
|
146
|
+
isNavigatingRef.current = true;
|
|
147
|
+
}, [] );
|
|
148
|
+
|
|
149
|
+
return { confirmDialog, requestConfirm, cancelConfirm, allowNextUnload };
|
|
138
150
|
}
|
|
139
151
|
|
|
140
152
|
export default useUnsavedChangesDialog;
|
package/src/index.js
CHANGED
|
@@ -19,6 +19,7 @@ export { default as ConfirmDialog } from './confirm-dialog';
|
|
|
19
19
|
export { default as CustomSelectControl } from './custom-select-control';
|
|
20
20
|
export { default as DataViews } from './dataviews';
|
|
21
21
|
export { default as Divider } from './divider';
|
|
22
|
+
export { default as Drawer } from './drawer';
|
|
22
23
|
export { default as FormTokenField } from './form-token-field';
|
|
23
24
|
export { default as Footer } from './footer';
|
|
24
25
|
export { default as Handoff } from './handoff';
|
|
@@ -31,6 +32,7 @@ export { default as Modal } from './modal';
|
|
|
31
32
|
export { default as NewspackIcon } from './newspack-icon';
|
|
32
33
|
export { default as Notice } from './notice';
|
|
33
34
|
export { default as Page } from './page';
|
|
35
|
+
export { default as PageControl } from './page-control';
|
|
34
36
|
export { default as PluginInstaller } from './plugin-installer';
|
|
35
37
|
export { default as PluginSettings } from './plugin-settings';
|
|
36
38
|
export { default as PluginToggle } from './plugin-toggle';
|
|
@@ -44,10 +46,12 @@ export { default as StepsList } from './steps-list';
|
|
|
44
46
|
export { default as StepsListItem } from './steps-list-item';
|
|
45
47
|
export { default as StyleCard } from './style-card';
|
|
46
48
|
export { default as TabbedNavigation } from './tabbed-navigation';
|
|
49
|
+
export { default as TableCard } from './table-card';
|
|
47
50
|
export { default as TextControl } from './text-control';
|
|
48
51
|
export { default as Waiting } from './waiting';
|
|
49
52
|
export { default as WebPreview } from './web-preview';
|
|
50
53
|
export { default as Wizard } from './wizard';
|
|
54
|
+
export { WizardBanner } from './wizard';
|
|
51
55
|
export { default as withWizard } from './with-wizard';
|
|
52
56
|
export { default as withWizardScreen } from './with-wizard-screen';
|
|
53
57
|
|
package/src/page/index.js
CHANGED
|
@@ -39,7 +39,7 @@ import './style.scss';
|
|
|
39
39
|
* inside its active tab panel — or, when no visible tab
|
|
40
40
|
* owns the route, as a sibling of the panels.
|
|
41
41
|
* @param {string} [props.className]
|
|
42
|
-
* @param {*} props.children
|
|
42
|
+
* @param {*} [props.children]
|
|
43
43
|
* @return {JSX.Element} Page component.
|
|
44
44
|
*/
|
|
45
45
|
const Page = ( { breadcrumbItems = [], badges, subTitle, actions, tabbedNavigation, className, children } ) => {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Control.
|
|
3
|
+
*
|
|
4
|
+
* A single-page selector backed by an autocomplete search, for settings that point at a
|
|
5
|
+
* page without enumerating every page the site has.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* WordPress dependencies
|
|
10
|
+
*/
|
|
11
|
+
import { useEffect, useState } from '@wordpress/element';
|
|
12
|
+
import { decodeEntities } from '@wordpress/html-entities';
|
|
13
|
+
import { __ } from '@wordpress/i18n';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Internal dependencies
|
|
17
|
+
*/
|
|
18
|
+
import { AutocompleteWithSuggestions } from '../';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Render a single-page selector.
|
|
22
|
+
*
|
|
23
|
+
* @param {Object} props Component props.
|
|
24
|
+
* @param {string} props.label Field label.
|
|
25
|
+
* @param {string} props.help Help text.
|
|
26
|
+
* @param {boolean} props.disabled Whether the field is disabled (e.g. while saving).
|
|
27
|
+
* @param {Object} props.selected The saved page as `{ label, value }`, if any.
|
|
28
|
+
* @param {Function} props.onChange Called with the selected page ID, or '' when cleared.
|
|
29
|
+
*/
|
|
30
|
+
const PageControl = ( { label, help, disabled, selected, onChange } ) => {
|
|
31
|
+
// Titles arrive HTML-encoded from the server, while the autocomplete decodes the
|
|
32
|
+
// suggestions it fetches. Decode here so the saved page and the search results below
|
|
33
|
+
// it render the same way.
|
|
34
|
+
const normalize = item => ( item ? { ...item, label: decodeEntities( item.label ) } : null );
|
|
35
|
+
const [ selectedItem, setSelectedItem ] = useState( normalize( selected ) );
|
|
36
|
+
// Settings are fetched after this control first renders, so adopt the saved page
|
|
37
|
+
// whenever it arrives or changes rather than only on mount.
|
|
38
|
+
useEffect( () => {
|
|
39
|
+
setSelectedItem( normalize( selected ) );
|
|
40
|
+
}, [ selected?.value, selected?.label ] );
|
|
41
|
+
return (
|
|
42
|
+
<fieldset disabled={ disabled } className="newspack-page-control">
|
|
43
|
+
<AutocompleteWithSuggestions
|
|
44
|
+
label={ label }
|
|
45
|
+
help={ help }
|
|
46
|
+
postTypes={ [ { slug: 'page', label: __( 'Page', 'newspack-plugin' ) } ] }
|
|
47
|
+
postTypeLabel={ __( 'page', 'newspack-plugin' ) }
|
|
48
|
+
postTypeLabelPlural={ __( 'pages', 'newspack-plugin' ) }
|
|
49
|
+
selectedItems={ selectedItem ? [ selectedItem ] : [] }
|
|
50
|
+
onChange={ items => {
|
|
51
|
+
if ( disabled ) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const item = items && items.length ? items[ items.length - 1 ] : null;
|
|
55
|
+
setSelectedItem( normalize( item ) );
|
|
56
|
+
onChange( item ? String( item.value ) : '' );
|
|
57
|
+
} }
|
|
58
|
+
/>
|
|
59
|
+
</fieldset>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default PageControl;
|
|
@@ -13,13 +13,17 @@ import { CheckboxControl } from '@wordpress/components';
|
|
|
13
13
|
/**
|
|
14
14
|
* Internal dependencies
|
|
15
15
|
*/
|
|
16
|
-
import { ActionCard, Button, Grid, Notice, SelectControl, TextControl } from '../';
|
|
16
|
+
import { ActionCard, Button, Grid, Notice, PageControl, SelectControl, TextControl } from '../';
|
|
17
17
|
import './style.scss';
|
|
18
18
|
|
|
19
19
|
const isSelectControl = setting => {
|
|
20
20
|
return Array.isArray( setting.options ) && setting.options.length;
|
|
21
21
|
};
|
|
22
22
|
const getControlComponent = setting => {
|
|
23
|
+
// An explicit `control` overrides the control implied by the setting's data type.
|
|
24
|
+
if ( 'page' === setting.control ) {
|
|
25
|
+
return PageControl;
|
|
26
|
+
}
|
|
23
27
|
if ( isSelectControl( setting ) ) {
|
|
24
28
|
return SelectControl;
|
|
25
29
|
}
|
|
@@ -66,6 +70,8 @@ const SettingsSection = props => {
|
|
|
66
70
|
label: option.name || option.label,
|
|
67
71
|
} ) ) || null,
|
|
68
72
|
value: setting.value,
|
|
73
|
+
// Only page controls consume `selected`; keep it off every other control's props.
|
|
74
|
+
selected: 'page' === setting.control ? setting.selected || null : null,
|
|
69
75
|
multiple: isSelectControl( setting ) && setting.multiple ? true : null,
|
|
70
76
|
checked: setting.type === 'boolean' ? !! setting.value : null,
|
|
71
77
|
onChange: value => {
|
|
@@ -104,7 +110,7 @@ const SettingsSection = props => {
|
|
|
104
110
|
setSaveDisabled( true );
|
|
105
111
|
} }
|
|
106
112
|
>
|
|
107
|
-
{ __( 'Save Settings', 'newspack' ) }
|
|
113
|
+
{ __( 'Save Settings', 'newspack-plugin' ) }
|
|
108
114
|
</Button>
|
|
109
115
|
)
|
|
110
116
|
}
|
|
@@ -36,6 +36,7 @@ import classnames from 'classnames';
|
|
|
36
36
|
* @property {boolean} [isWhite=false] - Indicates if the header should use a white theme.
|
|
37
37
|
* @property {boolean} [noMargin=false] - Indicates if the header should have no margin.
|
|
38
38
|
* @property {boolean} [pageHeader=false] - Indicates if the header is used as a page header.
|
|
39
|
+
* @property {string} [size='default'] - Size variant, either 'default' or 'small'. Scales the title, and the icon with it, independently of `pageHeader`.
|
|
39
40
|
* @property {string} title - The title of the section.
|
|
40
41
|
* @property {?string} [id=null] - Optional ID for the header element.
|
|
41
42
|
* @property {?string|Function|*} [children=null] - Optional children to display in the header.
|
|
@@ -57,6 +58,7 @@ const SectionHeader = ( {
|
|
|
57
58
|
isWhite = false,
|
|
58
59
|
noMargin = false,
|
|
59
60
|
pageHeader = false,
|
|
61
|
+
size = 'default',
|
|
60
62
|
title,
|
|
61
63
|
id = null,
|
|
62
64
|
menu,
|
|
@@ -82,7 +84,8 @@ const SectionHeader = ( {
|
|
|
82
84
|
centered && 'newspack-section-header--is-centered',
|
|
83
85
|
isWhite && 'newspack-section-header--is-white',
|
|
84
86
|
noMargin && 'newspack-section-header--no-margin',
|
|
85
|
-
pageHeader && 'newspack-section-header--page-header'
|
|
87
|
+
pageHeader && 'newspack-section-header--page-header',
|
|
88
|
+
size === 'small' && 'newspack-section-header--small'
|
|
86
89
|
);
|
|
87
90
|
|
|
88
91
|
// The breadcrumb `Page` owns the single page `<h1>`, so a `pageHeader` section
|
|
@@ -148,7 +151,7 @@ const SectionHeader = ( {
|
|
|
148
151
|
<Grid columns={ 1 } gutter={ 8 } className={ classes }>
|
|
149
152
|
{ icon && (
|
|
150
153
|
<div className="newspack-section-header__icon">
|
|
151
|
-
<Icon icon={ icon } size={ 48 } />
|
|
154
|
+
<Icon icon={ icon } size={ size === 'small' ? 24 : 48 } />
|
|
152
155
|
</div>
|
|
153
156
|
) }
|
|
154
157
|
{ backNav ? (
|
|
@@ -95,6 +95,31 @@
|
|
|
95
95
|
@include wp-mixins.heading-x-large();
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
&#{&}--small {
|
|
99
|
+
.newspack-section-header__title {
|
|
100
|
+
@include wp-mixins.heading-large();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
&__icon {
|
|
105
|
+
background-color: var(--wp-admin-theme-color-lighter-10);
|
|
106
|
+
border-radius: 100%;
|
|
107
|
+
display: grid;
|
|
108
|
+
height: 80px;
|
|
109
|
+
margin-bottom: 8px !important;
|
|
110
|
+
width: 80px;
|
|
111
|
+
place-items: center;
|
|
112
|
+
svg {
|
|
113
|
+
fill: var(--wp-admin-theme-color);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
&#{&}--small .newspack-section-header__icon {
|
|
118
|
+
height: wp-vars.$grid-unit-50;
|
|
119
|
+
margin-bottom: wp-vars.$grid-unit-05 !important;
|
|
120
|
+
width: wp-vars.$grid-unit-50;
|
|
121
|
+
}
|
|
122
|
+
|
|
98
123
|
&__title-container {
|
|
99
124
|
align-items: center;
|
|
100
125
|
display: flex;
|
|
@@ -154,17 +179,5 @@
|
|
|
154
179
|
> p {
|
|
155
180
|
text-align: center;
|
|
156
181
|
}
|
|
157
|
-
.newspack-section-header__icon {
|
|
158
|
-
background-color: var(--wp-admin-theme-color-lighter-10);
|
|
159
|
-
border-radius: 100%;
|
|
160
|
-
display: grid;
|
|
161
|
-
height: 80px;
|
|
162
|
-
margin-bottom: 8px !important;
|
|
163
|
-
width: 80px;
|
|
164
|
-
place-items: center;
|
|
165
|
-
svg {
|
|
166
|
-
fill: var(--wp-admin-theme-color);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
182
|
}
|
|
170
183
|
}
|