@automattic/newspack-blocks 4.31.1 → 4.32.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/composer.lock +73 -994
- package/dist/blocks/checkout-button/block.json +3 -0
- package/dist/carousel/view-rtl.css +1 -1
- package/dist/carousel/view.asset.php +1 -1
- package/dist/carousel/view.css +1 -1
- package/dist/editor-rtl.css +20 -16
- package/dist/editor.asset.php +1 -1
- package/dist/editor.css +20 -16
- package/dist/editor.js +32 -28
- package/dist/homepage-articles/view-rtl.css +1 -1
- package/dist/homepage-articles/view.asset.php +1 -1
- package/dist/homepage-articles/view.css +1 -1
- package/dist/modal.asset.php +1 -1
- package/dist/modal.js +1 -1
- package/dist/modalCheckout-rtl.css +1 -1
- package/dist/modalCheckout.asset.php +1 -1
- package/dist/modalCheckout.css +1 -1
- package/dist/modalCheckout.js +1 -1
- package/dist/tiersBased.asset.php +1 -1
- package/dist/tiersBased.js +1 -1
- package/includes/class-modal-checkout.php +354 -5
- package/includes/modal-checkout/class-checkout-data.php +30 -6
- package/languages/newspack-blocks-de_DE-37552bb09e2a9fceec1970e3c6d46557.json +1 -1
- package/languages/newspack-blocks-de_DE-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
- package/languages/newspack-blocks-de_DE-5c566531829f64486c13d7d8df1d6151.json +1 -0
- package/languages/newspack-blocks-de_DE-7c29764d5ef91723f5ce454f66431b9e.json +1 -1
- package/languages/newspack-blocks-de_DE-eeb28f99adedd5fad3081d2756d6f4a4.json +1 -1
- package/languages/newspack-blocks-de_DE-fef17c08abbcd3d3c4cdc0fd13a9bff5.json +1 -0
- package/languages/newspack-blocks-de_DE.po +427 -322
- package/languages/newspack-blocks-es_ES-37552bb09e2a9fceec1970e3c6d46557.json +1 -1
- package/languages/newspack-blocks-es_ES-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
- package/languages/newspack-blocks-es_ES-5c566531829f64486c13d7d8df1d6151.json +1 -0
- package/languages/newspack-blocks-es_ES-7c29764d5ef91723f5ce454f66431b9e.json +1 -1
- package/languages/newspack-blocks-es_ES-eeb28f99adedd5fad3081d2756d6f4a4.json +1 -1
- package/languages/newspack-blocks-es_ES-fef17c08abbcd3d3c4cdc0fd13a9bff5.json +1 -0
- package/languages/newspack-blocks-es_ES.po +425 -322
- package/languages/newspack-blocks-fr_BE-37552bb09e2a9fceec1970e3c6d46557.json +1 -1
- package/languages/newspack-blocks-fr_BE-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
- package/languages/newspack-blocks-fr_BE-5c566531829f64486c13d7d8df1d6151.json +1 -0
- package/languages/newspack-blocks-fr_BE-7c29764d5ef91723f5ce454f66431b9e.json +1 -1
- package/languages/newspack-blocks-fr_BE-eeb28f99adedd5fad3081d2756d6f4a4.json +1 -1
- package/languages/newspack-blocks-fr_BE-fef17c08abbcd3d3c4cdc0fd13a9bff5.json +1 -0
- package/languages/newspack-blocks-fr_BE.po +427 -322
- package/languages/newspack-blocks-nb_NO-37552bb09e2a9fceec1970e3c6d46557.json +1 -1
- package/languages/newspack-blocks-nb_NO-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
- package/languages/newspack-blocks-nb_NO-5c566531829f64486c13d7d8df1d6151.json +1 -0
- package/languages/newspack-blocks-nb_NO-7c29764d5ef91723f5ce454f66431b9e.json +1 -1
- package/languages/newspack-blocks-nb_NO-eeb28f99adedd5fad3081d2756d6f4a4.json +1 -1
- package/languages/newspack-blocks-nb_NO-fef17c08abbcd3d3c4cdc0fd13a9bff5.json +1 -0
- package/languages/newspack-blocks-nb_NO.po +426 -322
- package/languages/newspack-blocks-pt_PT-37552bb09e2a9fceec1970e3c6d46557.json +1 -1
- package/languages/newspack-blocks-pt_PT-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
- package/languages/newspack-blocks-pt_PT-5c566531829f64486c13d7d8df1d6151.json +1 -0
- package/languages/newspack-blocks-pt_PT-7c29764d5ef91723f5ce454f66431b9e.json +1 -1
- package/languages/newspack-blocks-pt_PT-eeb28f99adedd5fad3081d2756d6f4a4.json +1 -1
- package/languages/newspack-blocks-pt_PT-fef17c08abbcd3d3c4cdc0fd13a9bff5.json +1 -0
- package/languages/newspack-blocks-pt_PT.po +426 -322
- package/languages/newspack-blocks.pot +506 -379
- package/newspack-blocks.php +2 -2
- package/package.json +3 -2
- package/src/blocks/carousel/edit.js +1 -1
- package/src/blocks/carousel/view.scss +5 -1
- package/src/blocks/checkout-button/block.json +3 -0
- package/src/blocks/checkout-button/edit.js +32 -5
- package/src/blocks/checkout-button/view.php +37 -0
- package/src/blocks/donate/consts.ts +2 -1
- package/src/blocks/donate/frequency-slugs.js +10 -0
- package/src/blocks/donate/tiers-based/view.ts +8 -0
- package/src/blocks/homepage-articles/edit.tsx +1 -1
- package/src/blocks/homepage-articles/editor.scss +2 -1
- package/src/blocks/homepage-articles/view.scss +15 -5
- package/src/components/autocomplete-tokenfield.js +21 -7
- package/src/components/autocomplete-tokenfield.scss +1 -1
- package/src/components/autocomplete-tokenfield.test.js +58 -0
- package/src/components/query-controls.js +28 -6
- package/src/components/query-controls.test.js +56 -0
- package/src/components/reorder-modal.js +382 -0
- package/src/components/reorder-modal.scss +87 -0
- package/src/components/reorder-modal.test.js +487 -0
- package/src/components/specific-posts-control.js +91 -0
- package/src/components/specific-posts-control.scss +5 -0
- package/src/components/specific-posts-control.test.js +173 -0
- package/src/modal-checkout/analytics/ga4/utils/index.js +1 -0
- package/src/modal-checkout/checkout-button-trigger.js +29 -2
- package/src/modal-checkout/checkout-button-trigger.test.js +37 -0
- package/src/modal-checkout/checkout.scss +28 -0
- package/src/modal-checkout/donate-trigger.js +134 -0
- package/src/modal-checkout/donate-trigger.test.js +233 -0
- package/src/modal-checkout/index.js +91 -0
- package/src/modal-checkout/modal.js +129 -45
- package/src/modal-checkout/utils.test.js +49 -0
- package/src/shared/js/tiers-based-ready.js +26 -0
- package/vendor/autoload.php +1 -1
- package/vendor/composer/autoload_real.php +4 -4
- package/vendor/composer/autoload_static.php +2 -2
- package/vendor/composer/installed.php +2 -2
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import classnames from 'classnames';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* WordPress dependencies
|
|
8
|
+
*/
|
|
9
|
+
import { __, _n, sprintf } from '@wordpress/i18n';
|
|
10
|
+
import { speak } from '@wordpress/a11y';
|
|
11
|
+
import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
|
|
12
|
+
import {
|
|
13
|
+
Button,
|
|
14
|
+
Modal,
|
|
15
|
+
Notice,
|
|
16
|
+
Spinner,
|
|
17
|
+
__experimentalConfirmDialog as ConfirmDialog, // eslint-disable-line @wordpress/no-unsafe-wp-apis
|
|
18
|
+
} from '@wordpress/components';
|
|
19
|
+
import { Icon, chevronDown, chevronUp, close, dragHandle } from '@wordpress/icons';
|
|
20
|
+
import { Card } from '@wordpress/ui';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Internal dependencies
|
|
24
|
+
*/
|
|
25
|
+
import './reorder-modal.scss';
|
|
26
|
+
|
|
27
|
+
export const moveItem = ( items, from, to ) => {
|
|
28
|
+
if ( from === to || from < 0 || to < 0 || from >= items.length || to >= items.length ) {
|
|
29
|
+
return items;
|
|
30
|
+
}
|
|
31
|
+
const next = [ ...items ];
|
|
32
|
+
const [ moved ] = next.splice( from, 1 );
|
|
33
|
+
next.splice( to, 0, moved );
|
|
34
|
+
return next;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const OVERLAY_SELECTOR = '.components-modal__screen-overlay';
|
|
38
|
+
|
|
39
|
+
// The overlay carries the dismissal handling and the drop target, and `Modal`
|
|
40
|
+
// forwards its ref to it today. Deriving it from whatever node arrives keeps
|
|
41
|
+
// both working if core ever forwards that ref somewhere else.
|
|
42
|
+
export const findOverlay = node =>
|
|
43
|
+
node?.closest?.( OVERLAY_SELECTOR ) ||
|
|
44
|
+
node?.querySelector?.( OVERLAY_SELECTOR ) ||
|
|
45
|
+
node?.ownerDocument?.querySelector( OVERLAY_SELECTOR ) ||
|
|
46
|
+
null;
|
|
47
|
+
|
|
48
|
+
// Disabled controls stay focusable here, so they report `aria-disabled` rather
|
|
49
|
+
// than the native attribute.
|
|
50
|
+
const isAvailable = button => !! button && ! button.matches( ':disabled, [aria-disabled="true"]' );
|
|
51
|
+
|
|
52
|
+
// Speech input matches on the visible label, so it has to open the accessible
|
|
53
|
+
// name. Composing both from one translated string keeps that true in every locale.
|
|
54
|
+
const nameWithTitle = ( label, title ) =>
|
|
55
|
+
sprintf(
|
|
56
|
+
/* translators: keep %1$s first: it repeats the button's visible label, which speech input matches on. 1: the control's visible label. 2: title of the content being moved. */
|
|
57
|
+
__( '%1$s: %2$s', 'newspack-blocks' ),
|
|
58
|
+
label,
|
|
59
|
+
title
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const ReorderModal = ( { title, ids, fetchItems, onSave, onClose } ) => {
|
|
63
|
+
const moveUpLabel = __( 'Move Up', 'newspack-blocks' );
|
|
64
|
+
const moveDownLabel = __( 'Move Down', 'newspack-blocks' );
|
|
65
|
+
const [ items, setItems ] = useState( null );
|
|
66
|
+
const [ dragIndex, setDragIndex ] = useState( null );
|
|
67
|
+
const [ hasFetchError, setHasFetchError ] = useState( false );
|
|
68
|
+
const [ isConfirmingDiscard, setIsConfirmingDiscard ] = useState( false );
|
|
69
|
+
const listRef = useRef( null );
|
|
70
|
+
const overlayRef = useRef( null );
|
|
71
|
+
const pendingFocus = useRef( null );
|
|
72
|
+
const closeRef = useRef( null );
|
|
73
|
+
const preDragItems = useRef( null );
|
|
74
|
+
const dragBlocked = useRef( false );
|
|
75
|
+
const captureOverlay = useCallback( node => {
|
|
76
|
+
overlayRef.current = findOverlay( node );
|
|
77
|
+
}, [] );
|
|
78
|
+
|
|
79
|
+
// The order the modal opened with. Snapshotted so a change to `ids` from
|
|
80
|
+
// elsewhere in the editor cannot desynchronise it from `items`.
|
|
81
|
+
const openedWith = useRef( ids );
|
|
82
|
+
|
|
83
|
+
const isDirty = !! items && items.some( ( item, index ) => item.id !== openedWith.current[ index ] );
|
|
84
|
+
|
|
85
|
+
useEffect( () => {
|
|
86
|
+
let cancelled = false;
|
|
87
|
+
const openIds = openedWith.current;
|
|
88
|
+
const withLabels = labels => openIds.map( id => ( { id, label: labels[ id ] || __( '(no title)', 'newspack-blocks' ) } ) );
|
|
89
|
+
|
|
90
|
+
fetchItems( openIds )
|
|
91
|
+
.then( results => {
|
|
92
|
+
if ( cancelled ) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const labels = {};
|
|
96
|
+
results.forEach( ( { value, label } ) => {
|
|
97
|
+
labels[ value ] = label;
|
|
98
|
+
} );
|
|
99
|
+
const loaded = withLabels( labels );
|
|
100
|
+
setItems( loaded );
|
|
101
|
+
speak(
|
|
102
|
+
sprintf(
|
|
103
|
+
/* translators: %d: number of items in the list. */
|
|
104
|
+
_n( '%d item ready to reorder.', '%d items ready to reorder.', loaded.length, 'newspack-blocks' ),
|
|
105
|
+
loaded.length
|
|
106
|
+
)
|
|
107
|
+
);
|
|
108
|
+
} )
|
|
109
|
+
.catch( error => {
|
|
110
|
+
// eslint-disable-next-line no-console
|
|
111
|
+
console.error( 'Newspack Blocks: could not prepare the content to reorder.', error );
|
|
112
|
+
if ( ! cancelled ) {
|
|
113
|
+
setHasFetchError( true );
|
|
114
|
+
// Only to leave the loading state: the error view lists nothing.
|
|
115
|
+
setItems( [] );
|
|
116
|
+
}
|
|
117
|
+
} );
|
|
118
|
+
|
|
119
|
+
return () => {
|
|
120
|
+
cancelled = true;
|
|
121
|
+
};
|
|
122
|
+
}, [] ); // eslint-disable-line react-hooks/exhaustive-deps
|
|
123
|
+
|
|
124
|
+
// A chevron that reaches an end of the list stops accepting moves, so send
|
|
125
|
+
// focus to the row's other chevron.
|
|
126
|
+
useEffect( () => {
|
|
127
|
+
const pending = pendingFocus.current;
|
|
128
|
+
pendingFocus.current = null;
|
|
129
|
+
if ( ! pending || ! listRef.current ) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const row = listRef.current.querySelector( `[data-item-id="${ pending.id }"]` );
|
|
133
|
+
if ( ! row ) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const preferred = row.querySelector( `[data-direction="${ pending.direction }"]` );
|
|
137
|
+
const fallback = row.querySelector( `[data-direction="${ 'up' === pending.direction ? 'down' : 'up' }"]` );
|
|
138
|
+
if ( isAvailable( preferred ) ) {
|
|
139
|
+
preferred.focus();
|
|
140
|
+
} else if ( isAvailable( fallback ) ) {
|
|
141
|
+
fallback.focus();
|
|
142
|
+
}
|
|
143
|
+
}, [ items ] );
|
|
144
|
+
|
|
145
|
+
const moveTo = ( from, to, direction ) => {
|
|
146
|
+
const next = moveItem( items, from, to );
|
|
147
|
+
if ( next === items ) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
pendingFocus.current = { id: items[ from ].id, direction };
|
|
151
|
+
setItems( next );
|
|
152
|
+
speak(
|
|
153
|
+
sprintf(
|
|
154
|
+
/* translators: 1: title of the moved item. 2: its new position. 3: total number of items. */
|
|
155
|
+
__( '%1$s moved to position %2$d of %3$d.', 'newspack-blocks' ),
|
|
156
|
+
items[ from ].label,
|
|
157
|
+
to + 1,
|
|
158
|
+
items.length
|
|
159
|
+
)
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const requestClose = () => {
|
|
164
|
+
if ( isDirty ) {
|
|
165
|
+
setIsConfirmingDiscard( true );
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
onClose();
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
useEffect( () => {
|
|
172
|
+
closeRef.current = requestClose;
|
|
173
|
+
}, [ requestClose ] );
|
|
174
|
+
|
|
175
|
+
const handleKeyDown = event => {
|
|
176
|
+
if ( 'Escape' !== event.key || event.defaultPrevented ) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
event.preventDefault();
|
|
180
|
+
requestClose();
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// `Modal` plays its exit animation before it reports a close request, which
|
|
184
|
+
// would fade the modal out and back in whenever the confirmation vetoes one.
|
|
185
|
+
// Its own dismissal handlers are off, so drive the overlay press here.
|
|
186
|
+
useEffect( () => {
|
|
187
|
+
const overlay = overlayRef.current;
|
|
188
|
+
if ( ! overlay ) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
let pressedOverlay = false;
|
|
192
|
+
const onPointerDown = event => {
|
|
193
|
+
pressedOverlay = event.target === overlay;
|
|
194
|
+
if ( pressedOverlay ) {
|
|
195
|
+
event.preventDefault();
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const onPointerUp = event => {
|
|
199
|
+
const dismisses = pressedOverlay && 0 === event.button && event.target === overlay;
|
|
200
|
+
pressedOverlay = false;
|
|
201
|
+
if ( dismisses ) {
|
|
202
|
+
closeRef.current();
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
overlay.addEventListener( 'pointerdown', onPointerDown );
|
|
206
|
+
overlay.addEventListener( 'pointerup', onPointerUp );
|
|
207
|
+
return () => {
|
|
208
|
+
overlay.removeEventListener( 'pointerdown', onPointerDown );
|
|
209
|
+
overlay.removeEventListener( 'pointerup', onPointerUp );
|
|
210
|
+
};
|
|
211
|
+
}, [] );
|
|
212
|
+
|
|
213
|
+
// Rows are the only drop targets, and the frame's own padding is not one, so a
|
|
214
|
+
// release a few pixels wide of the list would report a cancelled drag and undo
|
|
215
|
+
// the reorder. Accepting the drop across the whole dialog leaves "cancelled"
|
|
216
|
+
// meaning only Escape or a release outside it.
|
|
217
|
+
useEffect( () => {
|
|
218
|
+
const frame = overlayRef.current?.querySelector( '.components-modal__frame' );
|
|
219
|
+
if ( ! frame ) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const accept = event => event.preventDefault();
|
|
223
|
+
frame.addEventListener( 'dragover', accept );
|
|
224
|
+
frame.addEventListener( 'drop', accept );
|
|
225
|
+
return () => {
|
|
226
|
+
frame.removeEventListener( 'dragover', accept );
|
|
227
|
+
frame.removeEventListener( 'drop', accept );
|
|
228
|
+
};
|
|
229
|
+
}, [] );
|
|
230
|
+
|
|
231
|
+
const handleDragOver = ( event, index ) => {
|
|
232
|
+
event.preventDefault();
|
|
233
|
+
if ( null === dragIndex ) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
event.dataTransfer.dropEffect = 'move';
|
|
237
|
+
if ( dragIndex === index ) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
setItems( moveItem( items, dragIndex, index ) );
|
|
241
|
+
setDragIndex( index );
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<>
|
|
246
|
+
<Modal
|
|
247
|
+
ref={ captureOverlay }
|
|
248
|
+
title={ title }
|
|
249
|
+
onRequestClose={ requestClose }
|
|
250
|
+
onKeyDown={ handleKeyDown }
|
|
251
|
+
isDismissible={ false }
|
|
252
|
+
shouldCloseOnEsc={ false }
|
|
253
|
+
shouldCloseOnClickOutside={ false }
|
|
254
|
+
headerActions={ <Button size="compact" icon={ close } label={ __( 'Close', 'newspack-blocks' ) } onClick={ requestClose } /> }
|
|
255
|
+
size="medium"
|
|
256
|
+
className="newspack-blocks-reorder-modal"
|
|
257
|
+
>
|
|
258
|
+
<div className="newspack-blocks-reorder-modal__body" aria-busy={ ! items }>
|
|
259
|
+
{ ! items && (
|
|
260
|
+
<div className="newspack-blocks-reorder-modal__loading">
|
|
261
|
+
<Spinner />
|
|
262
|
+
<span className="screen-reader-text">{ __( 'Loading content…', 'newspack-blocks' ) }</span>
|
|
263
|
+
</div>
|
|
264
|
+
) }
|
|
265
|
+
{ items && hasFetchError && (
|
|
266
|
+
<>
|
|
267
|
+
<Notice status="error" isDismissible={ false } className="newspack-blocks-reorder-modal__notice">
|
|
268
|
+
{ __( 'The content could not be loaded, so the order cannot be saved.', 'newspack-blocks' ) }
|
|
269
|
+
</Notice>
|
|
270
|
+
<div className="newspack-blocks-reorder-modal__footer">
|
|
271
|
+
<Button variant="tertiary" onClick={ requestClose }>
|
|
272
|
+
{ __( 'Cancel', 'newspack-blocks' ) }
|
|
273
|
+
</Button>
|
|
274
|
+
</div>
|
|
275
|
+
</>
|
|
276
|
+
) }
|
|
277
|
+
{ items && ! hasFetchError && (
|
|
278
|
+
<>
|
|
279
|
+
{ /* `list-style: none` drops list semantics in Safari, so the role is restated. */ }
|
|
280
|
+
{ /* eslint-disable-next-line jsx-a11y/no-redundant-roles */ }
|
|
281
|
+
<ul className="newspack-blocks-reorder-modal__list" role="list" ref={ listRef }>
|
|
282
|
+
{ items.map( ( item, index ) => (
|
|
283
|
+
<Card.Root
|
|
284
|
+
key={ item.id }
|
|
285
|
+
render={ <li /> }
|
|
286
|
+
data-item-id={ item.id }
|
|
287
|
+
className={ classnames( 'newspack-blocks-reorder-modal__item', {
|
|
288
|
+
'is-dragging': dragIndex === index,
|
|
289
|
+
} ) }
|
|
290
|
+
draggable
|
|
291
|
+
// `dragstart` always fires at the draggable row, never at the
|
|
292
|
+
// chevron inside it, so the grab point is recorded separately.
|
|
293
|
+
onPointerDown={ event => {
|
|
294
|
+
dragBlocked.current = !! event.target.closest?.( 'button' );
|
|
295
|
+
} }
|
|
296
|
+
onDragStart={ event => {
|
|
297
|
+
if ( dragBlocked.current ) {
|
|
298
|
+
event.preventDefault();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
preDragItems.current = items;
|
|
302
|
+
// Firefox and Safari will not start a drag until the payload is set.
|
|
303
|
+
event.dataTransfer.setData( 'text/plain', String( item.id ) );
|
|
304
|
+
event.dataTransfer.effectAllowed = 'move';
|
|
305
|
+
setDragIndex( index );
|
|
306
|
+
} }
|
|
307
|
+
onDragOver={ event => handleDragOver( event, index ) }
|
|
308
|
+
onDragEnd={ event => {
|
|
309
|
+
// A cancelled drag reports no drop effect: put the order back.
|
|
310
|
+
if ( 'none' === event.dataTransfer.dropEffect && preDragItems.current ) {
|
|
311
|
+
setItems( preDragItems.current );
|
|
312
|
+
}
|
|
313
|
+
preDragItems.current = null;
|
|
314
|
+
setDragIndex( null );
|
|
315
|
+
} }
|
|
316
|
+
onDrop={ event => event.preventDefault() }
|
|
317
|
+
>
|
|
318
|
+
<span className="newspack-blocks-reorder-modal__grip" aria-hidden="true">
|
|
319
|
+
<Icon icon={ dragHandle } />
|
|
320
|
+
</span>
|
|
321
|
+
<span className="newspack-blocks-reorder-modal__move">
|
|
322
|
+
<Button
|
|
323
|
+
icon={ chevronUp }
|
|
324
|
+
size="small"
|
|
325
|
+
data-direction="up"
|
|
326
|
+
disabled={ 0 === index }
|
|
327
|
+
accessibleWhenDisabled
|
|
328
|
+
label={ moveUpLabel }
|
|
329
|
+
aria-label={ nameWithTitle( moveUpLabel, item.label ) }
|
|
330
|
+
onClick={ () => moveTo( index, index - 1, 'up' ) }
|
|
331
|
+
/>
|
|
332
|
+
<Button
|
|
333
|
+
icon={ chevronDown }
|
|
334
|
+
size="small"
|
|
335
|
+
data-direction="down"
|
|
336
|
+
disabled={ index === items.length - 1 }
|
|
337
|
+
accessibleWhenDisabled
|
|
338
|
+
label={ moveDownLabel }
|
|
339
|
+
aria-label={ nameWithTitle( moveDownLabel, item.label ) }
|
|
340
|
+
onClick={ () => moveTo( index, index + 1, 'down' ) }
|
|
341
|
+
/>
|
|
342
|
+
</span>
|
|
343
|
+
<Card.Content className="newspack-blocks-reorder-modal__title" title={ item.label }>
|
|
344
|
+
{ item.label }
|
|
345
|
+
</Card.Content>
|
|
346
|
+
</Card.Root>
|
|
347
|
+
) ) }
|
|
348
|
+
</ul>
|
|
349
|
+
<div className="newspack-blocks-reorder-modal__footer">
|
|
350
|
+
<Button variant="tertiary" onClick={ requestClose }>
|
|
351
|
+
{ __( 'Cancel', 'newspack-blocks' ) }
|
|
352
|
+
</Button>
|
|
353
|
+
<Button
|
|
354
|
+
variant="primary"
|
|
355
|
+
disabled={ ! isDirty }
|
|
356
|
+
accessibleWhenDisabled
|
|
357
|
+
onClick={ () => onSave( items.map( item => item.id ) ) }
|
|
358
|
+
>
|
|
359
|
+
{ __( 'Save', 'newspack-blocks' ) }
|
|
360
|
+
</Button>
|
|
361
|
+
</div>
|
|
362
|
+
</>
|
|
363
|
+
) }
|
|
364
|
+
</div>
|
|
365
|
+
</Modal>
|
|
366
|
+
{ isConfirmingDiscard && (
|
|
367
|
+
<ConfirmDialog
|
|
368
|
+
isOpen
|
|
369
|
+
contentLabel={ __( 'Discard the new order?', 'newspack-blocks' ) }
|
|
370
|
+
confirmButtonText={ __( 'Discard', 'newspack-blocks' ) }
|
|
371
|
+
cancelButtonText={ __( 'Keep editing', 'newspack-blocks' ) }
|
|
372
|
+
onConfirm={ onClose }
|
|
373
|
+
onCancel={ () => setIsConfirmingDiscard( false ) }
|
|
374
|
+
>
|
|
375
|
+
{ __( 'The order you set will be lost.', 'newspack-blocks' ) }
|
|
376
|
+
</ConfirmDialog>
|
|
377
|
+
) }
|
|
378
|
+
</>
|
|
379
|
+
);
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
export default ReorderModal;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
@use "~@wordpress/base-styles/colors" as wp-colors;
|
|
2
|
+
@use "~@wordpress/base-styles/mixins" as wp-mixins;
|
|
3
|
+
@use "~@wordpress/base-styles/variables" as wp-vars;
|
|
4
|
+
|
|
5
|
+
.newspack-blocks-reorder-modal {
|
|
6
|
+
&__body {
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
min-height: calc(#{wp-vars.$grid-unit} * 23);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
&__loading {
|
|
13
|
+
align-items: center;
|
|
14
|
+
display: flex;
|
|
15
|
+
flex: 1;
|
|
16
|
+
justify-content: center;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
&__notice {
|
|
20
|
+
margin: 0 0 wp-vars.$grid-unit-10;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
&__list {
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
gap: wp-vars.$grid-unit-10;
|
|
27
|
+
list-style: none;
|
|
28
|
+
margin: 0;
|
|
29
|
+
padding: 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&__item {
|
|
33
|
+
--newspack-blocks-reorder-divider: #{wp-colors.$gray-100};
|
|
34
|
+
--wp-ui-card-padding: #{wp-vars.$grid-unit-15};
|
|
35
|
+
|
|
36
|
+
align-items: stretch;
|
|
37
|
+
cursor: grab;
|
|
38
|
+
flex-direction: row;
|
|
39
|
+
margin: 0;
|
|
40
|
+
|
|
41
|
+
&:focus-within,
|
|
42
|
+
&:hover {
|
|
43
|
+
--newspack-blocks-reorder-divider: #{wp-colors.$gray-300};
|
|
44
|
+
|
|
45
|
+
border-color: wp-colors.$gray-300;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&.is-dragging {
|
|
49
|
+
cursor: grabbing;
|
|
50
|
+
opacity: 0.5;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
&__grip {
|
|
55
|
+
align-items: center;
|
|
56
|
+
color: wp-colors.$gray-700;
|
|
57
|
+
display: flex;
|
|
58
|
+
padding-left: wp-vars.$grid-unit-05;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
&__move {
|
|
62
|
+
align-items: center;
|
|
63
|
+
border-right: 1px solid var(--newspack-blocks-reorder-divider);
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-direction: column;
|
|
66
|
+
padding: wp-vars.$grid-unit-05;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
&__title {
|
|
70
|
+
@include wp-mixins.heading-large();
|
|
71
|
+
|
|
72
|
+
align-self: center;
|
|
73
|
+
flex: 1;
|
|
74
|
+
min-width: 0;
|
|
75
|
+
overflow: hidden;
|
|
76
|
+
text-overflow: ellipsis;
|
|
77
|
+
white-space: nowrap;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
&__footer {
|
|
81
|
+
display: flex;
|
|
82
|
+
gap: wp-vars.$grid-unit-10;
|
|
83
|
+
justify-content: flex-end;
|
|
84
|
+
margin-top: auto;
|
|
85
|
+
padding-top: wp-vars.$grid-unit-30;
|
|
86
|
+
}
|
|
87
|
+
}
|