@wordpress/dataviews 1.0.0 → 1.2.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 +13 -5
- package/build/bulk-actions-toolbar.js +182 -0
- package/build/bulk-actions-toolbar.js.map +1 -0
- package/build/bulk-actions.js +8 -8
- package/build/bulk-actions.js.map +1 -1
- package/build/constants.js +1 -26
- package/build/constants.js.map +1 -1
- package/build/dataviews.js +13 -5
- package/build/dataviews.js.map +1 -1
- package/build/filter-and-sort-data-view.js +72 -65
- package/build/filter-and-sort-data-view.js.map +1 -1
- package/build/filter-summary.js +3 -3
- package/build/filter-summary.js.map +1 -1
- package/build/index.js +2 -2
- package/build/index.js.map +1 -1
- package/build/item-actions.js +41 -22
- package/build/item-actions.js.map +1 -1
- package/build/layouts.js +38 -0
- package/build/layouts.js.map +1 -0
- package/build/lock-unlock.js.map +1 -1
- package/build/normalize-fields.js +7 -2
- package/build/normalize-fields.js.map +1 -1
- package/build/pagination.js +13 -7
- package/build/pagination.js.map +1 -1
- package/build/single-selection-checkbox.js +4 -0
- package/build/single-selection-checkbox.js.map +1 -1
- package/build/types.js +6 -0
- package/build/types.js.map +1 -0
- package/build/view-actions.js +2 -1
- package/build/view-actions.js.map +1 -1
- package/build/view-grid.js +9 -10
- package/build/view-grid.js.map +1 -1
- package/build/view-list.js +134 -21
- package/build/view-list.js.map +1 -1
- package/build/view-table.js +9 -9
- package/build/view-table.js.map +1 -1
- package/build-module/bulk-actions-toolbar.js +175 -0
- package/build-module/bulk-actions-toolbar.js.map +1 -0
- package/build-module/bulk-actions.js +8 -8
- package/build-module/bulk-actions.js.map +1 -1
- package/build-module/constants.js +1 -25
- package/build-module/constants.js.map +1 -1
- package/build-module/dataviews.js +13 -5
- package/build-module/dataviews.js.map +1 -1
- package/build-module/filter-and-sort-data-view.js +72 -65
- package/build-module/filter-and-sort-data-view.js.map +1 -1
- package/build-module/filter-summary.js +3 -3
- package/build-module/filter-summary.js.map +1 -1
- package/build-module/index.js +1 -1
- package/build-module/index.js.map +1 -1
- package/build-module/item-actions.js +40 -24
- package/build-module/item-actions.js.map +1 -1
- package/build-module/layouts.js +30 -0
- package/build-module/layouts.js.map +1 -0
- package/build-module/lock-unlock.js.map +1 -1
- package/build-module/normalize-fields.js +7 -2
- package/build-module/normalize-fields.js.map +1 -1
- package/build-module/pagination.js +14 -7
- package/build-module/pagination.js.map +1 -1
- package/build-module/single-selection-checkbox.js +5 -0
- package/build-module/single-selection-checkbox.js.map +1 -1
- package/build-module/types.js +2 -0
- package/build-module/types.js.map +1 -0
- package/build-module/view-actions.js +2 -1
- package/build-module/view-actions.js.map +1 -1
- package/build-module/view-grid.js +9 -10
- package/build-module/view-grid.js.map +1 -1
- package/build-module/view-list.js +135 -23
- package/build-module/view-list.js.map +1 -1
- package/build-module/view-table.js +9 -9
- package/build-module/view-table.js.map +1 -1
- package/build-style/style-rtl.css +82 -44
- package/build-style/style.css +82 -44
- package/build-types/add-filter.d.ts +8 -0
- package/build-types/add-filter.d.ts.map +1 -0
- package/build-types/bulk-actions-toolbar.d.ts +8 -0
- package/build-types/bulk-actions-toolbar.d.ts.map +1 -0
- package/build-types/bulk-actions.d.ts +14 -0
- package/build-types/bulk-actions.d.ts.map +1 -0
- package/build-types/constants.d.ts +45 -0
- package/build-types/constants.d.ts.map +1 -0
- package/build-types/dataviews.d.ts +15 -0
- package/build-types/dataviews.d.ts.map +1 -0
- package/build-types/dropdown-menu-helper.d.ts +6 -0
- package/build-types/dropdown-menu-helper.d.ts.map +1 -0
- package/build-types/filter-and-sort-data-view.d.ts +18 -0
- package/build-types/filter-and-sort-data-view.d.ts.map +1 -0
- package/build-types/filter-summary.d.ts +6 -0
- package/build-types/filter-summary.d.ts.map +1 -0
- package/build-types/filters.d.ts +3 -0
- package/build-types/filters.d.ts.map +1 -0
- package/build-types/index.d.ts +4 -0
- package/build-types/index.d.ts.map +1 -0
- package/build-types/item-actions.d.ts +37 -0
- package/build-types/item-actions.d.ts.map +1 -0
- package/build-types/layouts.d.ts +20 -0
- package/build-types/layouts.d.ts.map +1 -0
- package/build-types/lock-unlock.d.ts +2 -0
- package/build-types/lock-unlock.d.ts.map +1 -0
- package/build-types/normalize-fields.d.ts +12 -0
- package/build-types/normalize-fields.d.ts.map +1 -0
- package/build-types/pagination.d.ts +16 -0
- package/build-types/pagination.d.ts.map +1 -0
- package/build-types/reset-filters.d.ts +6 -0
- package/build-types/reset-filters.d.ts.map +1 -0
- package/build-types/search-widget.d.ts +2 -0
- package/build-types/search-widget.d.ts.map +1 -0
- package/build-types/search.d.ts +3 -0
- package/build-types/search.d.ts.map +1 -0
- package/build-types/single-selection-checkbox.d.ts +17 -0
- package/build-types/single-selection-checkbox.d.ts.map +1 -0
- package/build-types/stories/fixtures.d.ts +114 -0
- package/build-types/stories/fixtures.d.ts.map +1 -0
- package/build-types/stories/index.story.d.ts +15 -0
- package/build-types/stories/index.story.d.ts.map +1 -0
- package/build-types/types.d.ts +254 -0
- package/build-types/types.d.ts.map +1 -0
- package/build-types/utils.d.ts +2 -0
- package/build-types/utils.d.ts.map +1 -0
- package/build-types/view-actions.d.ts +3 -0
- package/build-types/view-actions.d.ts.map +1 -0
- package/build-types/view-grid.d.ts +15 -0
- package/build-types/view-grid.d.ts.map +1 -0
- package/build-types/view-list.d.ts +16 -0
- package/build-types/view-list.d.ts.map +1 -0
- package/build-types/view-table.d.ts +14 -0
- package/build-types/view-table.d.ts.map +1 -0
- package/package.json +12 -12
- package/src/bulk-actions-toolbar.js +244 -0
- package/src/{bulk-actions.js → bulk-actions.tsx} +73 -17
- package/src/{constants.js → constants.ts} +1 -35
- package/src/dataviews.js +16 -4
- package/src/filter-and-sort-data-view.ts +169 -0
- package/src/filter-summary.js +3 -3
- package/src/index.js +1 -1
- package/src/{item-actions.js → item-actions.tsx} +112 -28
- package/src/layouts.js +39 -0
- package/src/normalize-fields.ts +25 -0
- package/src/{pagination.js → pagination.tsx} +28 -7
- package/src/{single-selection-checkbox.js → single-selection-checkbox.tsx} +17 -2
- package/src/stories/fixtures.js +0 -2
- package/src/style.scss +100 -44
- package/src/types.ts +314 -0
- package/src/view-actions.js +2 -1
- package/src/{view-grid.js → view-grid.tsx} +45 -16
- package/src/view-list.tsx +421 -0
- package/src/view-table.js +8 -8
- package/tsconfig.json +22 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/src/filter-and-sort-data-view.js +0 -154
- package/src/normalize-fields.js +0 -17
- package/src/view-list.js +0 -207
- /package/src/{lock-unlock.js → lock-unlock.ts} +0 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
ToolbarButton,
|
|
6
|
+
Toolbar,
|
|
7
|
+
ToolbarGroup,
|
|
8
|
+
__unstableMotion as motion,
|
|
9
|
+
__unstableAnimatePresence as AnimatePresence,
|
|
10
|
+
} from '@wordpress/components';
|
|
11
|
+
import { useMemo, useState, useRef } from '@wordpress/element';
|
|
12
|
+
import { _n, sprintf, __ } from '@wordpress/i18n';
|
|
13
|
+
import { closeSmall } from '@wordpress/icons';
|
|
14
|
+
import { useReducedMotion } from '@wordpress/compose';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Internal dependencies
|
|
18
|
+
*/
|
|
19
|
+
import { ActionWithModal } from './item-actions';
|
|
20
|
+
|
|
21
|
+
const SNACKBAR_VARIANTS = {
|
|
22
|
+
init: {
|
|
23
|
+
bottom: -48,
|
|
24
|
+
},
|
|
25
|
+
open: {
|
|
26
|
+
bottom: 24,
|
|
27
|
+
transition: {
|
|
28
|
+
bottom: { type: 'tween', duration: 0.2, ease: [ 0, 0, 0.2, 1 ] },
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
exit: {
|
|
32
|
+
opacity: 0,
|
|
33
|
+
bottom: 24,
|
|
34
|
+
transition: {
|
|
35
|
+
opacity: { type: 'tween', duration: 0.2, ease: [ 0, 0, 0.2, 1 ] },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function ActionTrigger( { action, onClick, isBusy } ) {
|
|
41
|
+
return (
|
|
42
|
+
<ToolbarButton
|
|
43
|
+
disabled={ isBusy }
|
|
44
|
+
label={ action.label }
|
|
45
|
+
icon={ action.icon }
|
|
46
|
+
isDestructive={ action.isDestructive }
|
|
47
|
+
size="compact"
|
|
48
|
+
onClick={ onClick }
|
|
49
|
+
isBusy={ isBusy }
|
|
50
|
+
__experimentalIsFocusable
|
|
51
|
+
tooltipPosition="top"
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const EMPTY_ARRAY = [];
|
|
57
|
+
|
|
58
|
+
function ActionButton( {
|
|
59
|
+
action,
|
|
60
|
+
selectedItems,
|
|
61
|
+
actionInProgress,
|
|
62
|
+
setActionInProgress,
|
|
63
|
+
} ) {
|
|
64
|
+
const selectedEligibleItems = useMemo( () => {
|
|
65
|
+
return selectedItems.filter( ( item ) => {
|
|
66
|
+
return action.isEligible( item );
|
|
67
|
+
} );
|
|
68
|
+
}, [ action, selectedItems ] );
|
|
69
|
+
if ( !! action.RenderModal ) {
|
|
70
|
+
return (
|
|
71
|
+
<ActionWithModal
|
|
72
|
+
key={ action.id }
|
|
73
|
+
action={ action }
|
|
74
|
+
items={ selectedEligibleItems }
|
|
75
|
+
ActionTrigger={ ActionTrigger }
|
|
76
|
+
onActionStart={ () => {
|
|
77
|
+
setActionInProgress( action.id );
|
|
78
|
+
} }
|
|
79
|
+
onActionPerformed={ () => {
|
|
80
|
+
setActionInProgress( null );
|
|
81
|
+
} }
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return (
|
|
86
|
+
<ActionTrigger
|
|
87
|
+
key={ action.id }
|
|
88
|
+
action={ action }
|
|
89
|
+
items={ selectedItems }
|
|
90
|
+
onClick={ () => {
|
|
91
|
+
setActionInProgress( action.id );
|
|
92
|
+
action.callback( selectedItems, () => {
|
|
93
|
+
setActionInProgress( action.id );
|
|
94
|
+
} );
|
|
95
|
+
} }
|
|
96
|
+
isBusy={ actionInProgress === action.id }
|
|
97
|
+
/>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function renderToolbarContent(
|
|
102
|
+
selection,
|
|
103
|
+
actionsToShow,
|
|
104
|
+
selectedItems,
|
|
105
|
+
actionInProgress,
|
|
106
|
+
setActionInProgress,
|
|
107
|
+
setSelection
|
|
108
|
+
) {
|
|
109
|
+
return (
|
|
110
|
+
<>
|
|
111
|
+
<ToolbarGroup>
|
|
112
|
+
<div className="dataviews-bulk-actions__selection-count">
|
|
113
|
+
{ selection.length === 1
|
|
114
|
+
? __( '1 item selected' )
|
|
115
|
+
: sprintf(
|
|
116
|
+
// translators: %s: Total number of selected items.
|
|
117
|
+
_n(
|
|
118
|
+
'%s item selected',
|
|
119
|
+
'%s items selected',
|
|
120
|
+
selection.length
|
|
121
|
+
),
|
|
122
|
+
selection.length
|
|
123
|
+
) }
|
|
124
|
+
</div>
|
|
125
|
+
</ToolbarGroup>
|
|
126
|
+
<ToolbarGroup>
|
|
127
|
+
{ actionsToShow.map( ( action ) => {
|
|
128
|
+
return (
|
|
129
|
+
<ActionButton
|
|
130
|
+
key={ action.id }
|
|
131
|
+
action={ action }
|
|
132
|
+
selectedItems={ selectedItems }
|
|
133
|
+
actionInProgress={ actionInProgress }
|
|
134
|
+
setActionInProgress={ setActionInProgress }
|
|
135
|
+
/>
|
|
136
|
+
);
|
|
137
|
+
} ) }
|
|
138
|
+
</ToolbarGroup>
|
|
139
|
+
<ToolbarGroup>
|
|
140
|
+
<ToolbarButton
|
|
141
|
+
icon={ closeSmall }
|
|
142
|
+
showTooltip
|
|
143
|
+
tooltipPosition="top"
|
|
144
|
+
label={ __( 'Cancel' ) }
|
|
145
|
+
disabled={ !! actionInProgress }
|
|
146
|
+
onClick={ () => {
|
|
147
|
+
setSelection( EMPTY_ARRAY );
|
|
148
|
+
} }
|
|
149
|
+
/>
|
|
150
|
+
</ToolbarGroup>
|
|
151
|
+
</>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function ToolbarContent( {
|
|
156
|
+
selection,
|
|
157
|
+
actionsToShow,
|
|
158
|
+
selectedItems,
|
|
159
|
+
setSelection,
|
|
160
|
+
} ) {
|
|
161
|
+
const [ actionInProgress, setActionInProgress ] = useState( null );
|
|
162
|
+
const buttons = useRef( null );
|
|
163
|
+
if ( ! actionInProgress ) {
|
|
164
|
+
if ( buttons.current ) {
|
|
165
|
+
buttons.current = null;
|
|
166
|
+
}
|
|
167
|
+
return renderToolbarContent(
|
|
168
|
+
selection,
|
|
169
|
+
actionsToShow,
|
|
170
|
+
selectedItems,
|
|
171
|
+
actionInProgress,
|
|
172
|
+
setActionInProgress,
|
|
173
|
+
setSelection
|
|
174
|
+
);
|
|
175
|
+
} else if ( ! buttons.current ) {
|
|
176
|
+
buttons.current = renderToolbarContent(
|
|
177
|
+
selection,
|
|
178
|
+
actionsToShow,
|
|
179
|
+
selectedItems,
|
|
180
|
+
actionInProgress,
|
|
181
|
+
setActionInProgress,
|
|
182
|
+
setSelection
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
return buttons.current;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export default function BulkActionsToolbar( {
|
|
189
|
+
data,
|
|
190
|
+
selection,
|
|
191
|
+
actions = EMPTY_ARRAY,
|
|
192
|
+
setSelection,
|
|
193
|
+
getItemId,
|
|
194
|
+
} ) {
|
|
195
|
+
const isReducedMotion = useReducedMotion();
|
|
196
|
+
const selectedItems = useMemo( () => {
|
|
197
|
+
return data.filter( ( item ) =>
|
|
198
|
+
selection.includes( getItemId( item ) )
|
|
199
|
+
);
|
|
200
|
+
}, [ selection, data, getItemId ] );
|
|
201
|
+
|
|
202
|
+
const actionsToShow = useMemo(
|
|
203
|
+
() =>
|
|
204
|
+
actions.filter( ( action ) => {
|
|
205
|
+
return (
|
|
206
|
+
action.supportsBulk &&
|
|
207
|
+
action.icon &&
|
|
208
|
+
selectedItems.some( ( item ) => action.isEligible( item ) )
|
|
209
|
+
);
|
|
210
|
+
} ),
|
|
211
|
+
[ actions, selectedItems ]
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
if (
|
|
215
|
+
( selection && selection.length === 0 ) ||
|
|
216
|
+
actionsToShow.length === 0
|
|
217
|
+
) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<AnimatePresence>
|
|
223
|
+
<motion.div
|
|
224
|
+
layout={ ! isReducedMotion } // See https://www.framer.com/docs/animation/#layout-animations
|
|
225
|
+
initial={ 'init' }
|
|
226
|
+
animate={ 'open' }
|
|
227
|
+
exit={ 'exit' }
|
|
228
|
+
variants={ isReducedMotion ? undefined : SNACKBAR_VARIANTS }
|
|
229
|
+
className="dataviews-bulk-actions"
|
|
230
|
+
>
|
|
231
|
+
<Toolbar label={ __( 'Bulk actions' ) }>
|
|
232
|
+
<div className="dataviews-bulk-actions-toolbar-wrapper">
|
|
233
|
+
<ToolbarContent
|
|
234
|
+
selection={ selection }
|
|
235
|
+
actionsToShow={ actionsToShow }
|
|
236
|
+
selectedItems={ selectedItems }
|
|
237
|
+
setSelection={ setSelection }
|
|
238
|
+
/>
|
|
239
|
+
</div>
|
|
240
|
+
</Toolbar>
|
|
241
|
+
</motion.div>
|
|
242
|
+
</AnimatePresence>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
@@ -13,6 +13,7 @@ import { useMemo, useState, useCallback, useEffect } from '@wordpress/element';
|
|
|
13
13
|
* Internal dependencies
|
|
14
14
|
*/
|
|
15
15
|
import { unlock } from './lock-unlock';
|
|
16
|
+
import type { Action, ActionModal, AnyItem } from './types';
|
|
16
17
|
|
|
17
18
|
const {
|
|
18
19
|
DropdownMenuV2: DropdownMenu,
|
|
@@ -21,32 +22,73 @@ const {
|
|
|
21
22
|
DropdownMenuSeparatorV2: DropdownMenuSeparator,
|
|
22
23
|
} = unlock( componentsPrivateApis );
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
interface ActionWithModalProps< Item extends AnyItem > {
|
|
26
|
+
action: ActionModal< Item >;
|
|
27
|
+
selectedItems: Item[];
|
|
28
|
+
setActionWithModal: ( action?: ActionModal< Item > ) => void;
|
|
29
|
+
onMenuOpenChange: ( isOpen: boolean ) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface BulkActionsItemProps< Item extends AnyItem > {
|
|
33
|
+
action: Action< Item >;
|
|
34
|
+
selectedItems: Item[];
|
|
35
|
+
setActionWithModal: ( action?: ActionModal< Item > ) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface ActionsMenuGroupProps< Item extends AnyItem > {
|
|
39
|
+
actions: Action< Item >[];
|
|
40
|
+
selectedItems: Item[];
|
|
41
|
+
setActionWithModal: ( action?: ActionModal< Item > ) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface BulkActionsProps< Item extends AnyItem > {
|
|
45
|
+
data: Item[];
|
|
46
|
+
actions: Action< Item >[];
|
|
47
|
+
selection: string[];
|
|
48
|
+
onSelectionChange: ( selection: Item[] ) => void;
|
|
49
|
+
getItemId: ( item: Item ) => string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function useHasAPossibleBulkAction< Item extends AnyItem >(
|
|
53
|
+
actions: Action< Item >[],
|
|
54
|
+
item: Item
|
|
55
|
+
) {
|
|
25
56
|
return useMemo( () => {
|
|
26
57
|
return actions.some( ( action ) => {
|
|
27
|
-
return
|
|
58
|
+
return (
|
|
59
|
+
action.supportsBulk &&
|
|
60
|
+
( ! action.isEligible || action.isEligible( item ) )
|
|
61
|
+
);
|
|
28
62
|
} );
|
|
29
63
|
}, [ actions, item ] );
|
|
30
64
|
}
|
|
31
65
|
|
|
32
|
-
export function useSomeItemHasAPossibleBulkAction
|
|
66
|
+
export function useSomeItemHasAPossibleBulkAction< Item extends AnyItem >(
|
|
67
|
+
actions: Action< Item >[],
|
|
68
|
+
data: Item[]
|
|
69
|
+
) {
|
|
33
70
|
return useMemo( () => {
|
|
34
71
|
return data.some( ( item ) => {
|
|
35
72
|
return actions.some( ( action ) => {
|
|
36
|
-
return
|
|
73
|
+
return (
|
|
74
|
+
action.supportsBulk &&
|
|
75
|
+
( ! action.isEligible || action.isEligible( item ) )
|
|
76
|
+
);
|
|
37
77
|
} );
|
|
38
78
|
} );
|
|
39
79
|
}, [ actions, data ] );
|
|
40
80
|
}
|
|
41
81
|
|
|
42
|
-
function ActionWithModal( {
|
|
82
|
+
function ActionWithModal< Item extends AnyItem >( {
|
|
43
83
|
action,
|
|
44
84
|
selectedItems,
|
|
45
85
|
setActionWithModal,
|
|
46
86
|
onMenuOpenChange,
|
|
47
|
-
} ) {
|
|
87
|
+
}: ActionWithModalProps< Item > ) {
|
|
48
88
|
const eligibleItems = useMemo( () => {
|
|
49
|
-
return selectedItems.filter(
|
|
89
|
+
return selectedItems.filter(
|
|
90
|
+
( item ) => ! action.isEligible || action.isEligible( item )
|
|
91
|
+
);
|
|
50
92
|
}, [ action, selectedItems ] );
|
|
51
93
|
const { RenderModal, hideModalHeader } = action;
|
|
52
94
|
const onCloseModal = useCallback( () => {
|
|
@@ -54,7 +96,7 @@ function ActionWithModal( {
|
|
|
54
96
|
}, [ setActionWithModal ] );
|
|
55
97
|
return (
|
|
56
98
|
<Modal
|
|
57
|
-
title={ ! hideModalHeader
|
|
99
|
+
title={ ! hideModalHeader ? action.label : undefined }
|
|
58
100
|
__experimentalHideHeader={ !! hideModalHeader }
|
|
59
101
|
onRequestClose={ onCloseModal }
|
|
60
102
|
overlayClassName="dataviews-action-modal"
|
|
@@ -62,18 +104,24 @@ function ActionWithModal( {
|
|
|
62
104
|
<RenderModal
|
|
63
105
|
items={ eligibleItems }
|
|
64
106
|
closeModal={ onCloseModal }
|
|
65
|
-
|
|
107
|
+
onActionPerformed={ () => onMenuOpenChange( false ) }
|
|
66
108
|
/>
|
|
67
109
|
</Modal>
|
|
68
110
|
);
|
|
69
111
|
}
|
|
70
112
|
|
|
71
|
-
function BulkActionItem
|
|
113
|
+
function BulkActionItem< Item extends AnyItem >( {
|
|
114
|
+
action,
|
|
115
|
+
selectedItems,
|
|
116
|
+
setActionWithModal,
|
|
117
|
+
}: BulkActionsItemProps< Item > ) {
|
|
72
118
|
const eligibleItems = useMemo( () => {
|
|
73
|
-
return selectedItems.filter(
|
|
119
|
+
return selectedItems.filter(
|
|
120
|
+
( item ) => ! action.isEligible || action.isEligible( item )
|
|
121
|
+
);
|
|
74
122
|
}, [ action, selectedItems ] );
|
|
75
123
|
|
|
76
|
-
const shouldShowModal =
|
|
124
|
+
const shouldShowModal = 'RenderModal' in action;
|
|
77
125
|
|
|
78
126
|
return (
|
|
79
127
|
<DropdownMenuItem
|
|
@@ -96,7 +144,11 @@ function BulkActionItem( { action, selectedItems, setActionWithModal } ) {
|
|
|
96
144
|
);
|
|
97
145
|
}
|
|
98
146
|
|
|
99
|
-
function ActionsMenuGroup
|
|
147
|
+
function ActionsMenuGroup< Item extends AnyItem >( {
|
|
148
|
+
actions,
|
|
149
|
+
selectedItems,
|
|
150
|
+
setActionWithModal,
|
|
151
|
+
}: ActionsMenuGroupProps< Item > ) {
|
|
100
152
|
return (
|
|
101
153
|
<>
|
|
102
154
|
<DropdownMenuGroup>
|
|
@@ -114,22 +166,26 @@ function ActionsMenuGroup( { actions, selectedItems, setActionWithModal } ) {
|
|
|
114
166
|
);
|
|
115
167
|
}
|
|
116
168
|
|
|
117
|
-
export default function BulkActions( {
|
|
169
|
+
export default function BulkActions< Item extends AnyItem >( {
|
|
118
170
|
data,
|
|
119
171
|
actions,
|
|
120
172
|
selection,
|
|
121
173
|
onSelectionChange,
|
|
122
174
|
getItemId,
|
|
123
|
-
} ) {
|
|
175
|
+
}: BulkActionsProps< Item > ) {
|
|
124
176
|
const bulkActions = useMemo(
|
|
125
177
|
() => actions.filter( ( action ) => action.supportsBulk ),
|
|
126
178
|
[ actions ]
|
|
127
179
|
);
|
|
128
180
|
const [ isMenuOpen, onMenuOpenChange ] = useState( false );
|
|
129
|
-
const [ actionWithModal, setActionWithModal ] = useState
|
|
181
|
+
const [ actionWithModal, setActionWithModal ] = useState<
|
|
182
|
+
ActionModal< Item > | undefined
|
|
183
|
+
>();
|
|
130
184
|
const selectableItems = useMemo( () => {
|
|
131
185
|
return data.filter( ( item ) => {
|
|
132
|
-
return bulkActions.some(
|
|
186
|
+
return bulkActions.some(
|
|
187
|
+
( action ) => ! action.isEligible || action.isEligible( item )
|
|
188
|
+
);
|
|
133
189
|
} );
|
|
134
190
|
}, [ data, bulkActions ] );
|
|
135
191
|
|
|
@@ -1,20 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WordPress dependencies
|
|
3
3
|
*/
|
|
4
|
-
import { __
|
|
5
|
-
import {
|
|
6
|
-
blockTable,
|
|
7
|
-
category,
|
|
8
|
-
formatListBullets,
|
|
9
|
-
formatListBulletsRTL,
|
|
10
|
-
} from '@wordpress/icons';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Internal dependencies
|
|
14
|
-
*/
|
|
15
|
-
import ViewTable from './view-table';
|
|
16
|
-
import ViewGrid from './view-grid';
|
|
17
|
-
import ViewList from './view-list';
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
18
5
|
|
|
19
6
|
// Filter operators.
|
|
20
7
|
export const OPERATOR_IS = 'is';
|
|
@@ -69,24 +56,3 @@ export const SORTING_DIRECTIONS = {
|
|
|
69
56
|
export const LAYOUT_TABLE = 'table';
|
|
70
57
|
export const LAYOUT_GRID = 'grid';
|
|
71
58
|
export const LAYOUT_LIST = 'list';
|
|
72
|
-
|
|
73
|
-
export const VIEW_LAYOUTS = [
|
|
74
|
-
{
|
|
75
|
-
type: LAYOUT_TABLE,
|
|
76
|
-
label: __( 'Table' ),
|
|
77
|
-
component: ViewTable,
|
|
78
|
-
icon: blockTable,
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
type: LAYOUT_GRID,
|
|
82
|
-
label: __( 'Grid' ),
|
|
83
|
-
component: ViewGrid,
|
|
84
|
-
icon: category,
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
type: LAYOUT_LIST,
|
|
88
|
-
label: __( 'List' ),
|
|
89
|
-
component: ViewList,
|
|
90
|
-
icon: isRTL() ? formatListBulletsRTL : formatListBullets,
|
|
91
|
-
},
|
|
92
|
-
];
|
package/src/dataviews.js
CHANGED
|
@@ -11,9 +11,11 @@ import Pagination from './pagination';
|
|
|
11
11
|
import ViewActions from './view-actions';
|
|
12
12
|
import Filters from './filters';
|
|
13
13
|
import Search from './search';
|
|
14
|
-
import {
|
|
14
|
+
import { LAYOUT_TABLE, LAYOUT_GRID } from './constants';
|
|
15
|
+
import { VIEW_LAYOUTS } from './layouts';
|
|
15
16
|
import BulkActions from './bulk-actions';
|
|
16
17
|
import { normalizeFields } from './normalize-fields';
|
|
18
|
+
import BulkActionsToolbar from './bulk-actions-toolbar';
|
|
17
19
|
|
|
18
20
|
const defaultGetItemId = ( item ) => item.id;
|
|
19
21
|
const defaultOnSelectionChange = () => {};
|
|
@@ -126,22 +128,32 @@ export default function DataViews( {
|
|
|
126
128
|
/>
|
|
127
129
|
</HStack>
|
|
128
130
|
<ViewComponent
|
|
129
|
-
fields={ _fields }
|
|
130
|
-
view={ view }
|
|
131
|
-
onChangeView={ onChangeView }
|
|
132
131
|
actions={ actions }
|
|
133
132
|
data={ data }
|
|
133
|
+
fields={ _fields }
|
|
134
134
|
getItemId={ getItemId }
|
|
135
135
|
isLoading={ isLoading }
|
|
136
|
+
onChangeView={ onChangeView }
|
|
136
137
|
onSelectionChange={ onSetSelection }
|
|
137
138
|
selection={ selection }
|
|
138
139
|
setOpenedFilter={ setOpenedFilter }
|
|
140
|
+
view={ view }
|
|
139
141
|
/>
|
|
140
142
|
<Pagination
|
|
141
143
|
view={ view }
|
|
142
144
|
onChangeView={ onChangeView }
|
|
143
145
|
paginationInfo={ paginationInfo }
|
|
144
146
|
/>
|
|
147
|
+
{ [ LAYOUT_TABLE, LAYOUT_GRID ].includes( view.type ) &&
|
|
148
|
+
hasPossibleBulkAction && (
|
|
149
|
+
<BulkActionsToolbar
|
|
150
|
+
data={ data }
|
|
151
|
+
actions={ actions }
|
|
152
|
+
selection={ selection }
|
|
153
|
+
setSelection={ setSelection }
|
|
154
|
+
getItemId={ getItemId }
|
|
155
|
+
/>
|
|
156
|
+
) }
|
|
145
157
|
</div>
|
|
146
158
|
);
|
|
147
159
|
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import removeAccents from 'remove-accents';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal dependencies
|
|
8
|
+
*/
|
|
9
|
+
import {
|
|
10
|
+
OPERATOR_IS,
|
|
11
|
+
OPERATOR_IS_NOT,
|
|
12
|
+
OPERATOR_IS_NONE,
|
|
13
|
+
OPERATOR_IS_ANY,
|
|
14
|
+
OPERATOR_IS_ALL,
|
|
15
|
+
OPERATOR_IS_NOT_ALL,
|
|
16
|
+
} from './constants';
|
|
17
|
+
import { normalizeFields } from './normalize-fields';
|
|
18
|
+
import type { Field, AnyItem, View } from './types';
|
|
19
|
+
|
|
20
|
+
function normalizeSearchInput( input = '' ) {
|
|
21
|
+
return removeAccents( input.trim().toLowerCase() );
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const EMPTY_ARRAY: [] = [];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Applies the filtering, sorting and pagination to the raw data based on the view configuration.
|
|
28
|
+
*
|
|
29
|
+
* @param data Raw data.
|
|
30
|
+
* @param view View config.
|
|
31
|
+
* @param fields Fields config.
|
|
32
|
+
*
|
|
33
|
+
* @return Filtered, sorted and paginated data.
|
|
34
|
+
*/
|
|
35
|
+
export function filterSortAndPaginate< Item extends AnyItem >(
|
|
36
|
+
data: Item[],
|
|
37
|
+
view: View,
|
|
38
|
+
fields: Field< Item >[]
|
|
39
|
+
): {
|
|
40
|
+
data: Item[];
|
|
41
|
+
paginationInfo: { totalItems: number; totalPages: number };
|
|
42
|
+
} {
|
|
43
|
+
if ( ! data ) {
|
|
44
|
+
return {
|
|
45
|
+
data: EMPTY_ARRAY,
|
|
46
|
+
paginationInfo: { totalItems: 0, totalPages: 0 },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const _fields = normalizeFields( fields );
|
|
50
|
+
let filteredData = [ ...data ];
|
|
51
|
+
// Handle global search.
|
|
52
|
+
if ( view.search ) {
|
|
53
|
+
const normalizedSearch = normalizeSearchInput( view.search );
|
|
54
|
+
filteredData = filteredData.filter( ( item ) => {
|
|
55
|
+
return _fields
|
|
56
|
+
.filter( ( field ) => field.enableGlobalSearch )
|
|
57
|
+
.map( ( field ) => {
|
|
58
|
+
return normalizeSearchInput( field.getValue( { item } ) );
|
|
59
|
+
} )
|
|
60
|
+
.some( ( field ) => field.includes( normalizedSearch ) );
|
|
61
|
+
} );
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if ( view.filters.length > 0 ) {
|
|
65
|
+
view.filters.forEach( ( filter ) => {
|
|
66
|
+
const field = _fields.find(
|
|
67
|
+
( _field ) => _field.id === filter.field
|
|
68
|
+
);
|
|
69
|
+
if ( field ) {
|
|
70
|
+
if (
|
|
71
|
+
filter.operator === OPERATOR_IS_ANY &&
|
|
72
|
+
filter?.value?.length > 0
|
|
73
|
+
) {
|
|
74
|
+
filteredData = filteredData.filter( ( item ) => {
|
|
75
|
+
const fieldValue = field.getValue( { item } );
|
|
76
|
+
if ( Array.isArray( fieldValue ) ) {
|
|
77
|
+
return filter.value.some( ( filterValue: any ) =>
|
|
78
|
+
fieldValue.includes( filterValue )
|
|
79
|
+
);
|
|
80
|
+
} else if ( typeof fieldValue === 'string' ) {
|
|
81
|
+
return filter.value.includes( fieldValue );
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
} );
|
|
85
|
+
} else if (
|
|
86
|
+
filter.operator === OPERATOR_IS_NONE &&
|
|
87
|
+
filter?.value?.length > 0
|
|
88
|
+
) {
|
|
89
|
+
filteredData = filteredData.filter( ( item ) => {
|
|
90
|
+
const fieldValue = field.getValue( { item } );
|
|
91
|
+
if ( Array.isArray( fieldValue ) ) {
|
|
92
|
+
return ! filter.value.some( ( filterValue: any ) =>
|
|
93
|
+
fieldValue.includes( filterValue )
|
|
94
|
+
);
|
|
95
|
+
} else if ( typeof fieldValue === 'string' ) {
|
|
96
|
+
return ! filter.value.includes( fieldValue );
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
} );
|
|
100
|
+
} else if (
|
|
101
|
+
filter.operator === OPERATOR_IS_ALL &&
|
|
102
|
+
filter?.value?.length > 0
|
|
103
|
+
) {
|
|
104
|
+
filteredData = filteredData.filter( ( item ) => {
|
|
105
|
+
return filter.value.every( ( value: any ) => {
|
|
106
|
+
return field
|
|
107
|
+
.getValue( { item } )
|
|
108
|
+
?.includes( value );
|
|
109
|
+
} );
|
|
110
|
+
} );
|
|
111
|
+
} else if (
|
|
112
|
+
filter.operator === OPERATOR_IS_NOT_ALL &&
|
|
113
|
+
filter?.value?.length > 0
|
|
114
|
+
) {
|
|
115
|
+
filteredData = filteredData.filter( ( item ) => {
|
|
116
|
+
return filter.value.every( ( value: any ) => {
|
|
117
|
+
return ! field
|
|
118
|
+
.getValue( { item } )
|
|
119
|
+
?.includes( value );
|
|
120
|
+
} );
|
|
121
|
+
} );
|
|
122
|
+
} else if ( filter.operator === OPERATOR_IS ) {
|
|
123
|
+
filteredData = filteredData.filter( ( item ) => {
|
|
124
|
+
return filter.value === field.getValue( { item } );
|
|
125
|
+
} );
|
|
126
|
+
} else if ( filter.operator === OPERATOR_IS_NOT ) {
|
|
127
|
+
filteredData = filteredData.filter( ( item ) => {
|
|
128
|
+
return filter.value !== field.getValue( { item } );
|
|
129
|
+
} );
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} );
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Handle sorting.
|
|
136
|
+
if ( view.sort ) {
|
|
137
|
+
const fieldId = view.sort.field;
|
|
138
|
+
const fieldToSort = _fields.find( ( field ) => {
|
|
139
|
+
return field.id === fieldId;
|
|
140
|
+
} );
|
|
141
|
+
if ( fieldToSort ) {
|
|
142
|
+
filteredData.sort( ( a, b ) => {
|
|
143
|
+
const valueA = fieldToSort.getValue( { item: a } ) ?? '';
|
|
144
|
+
const valueB = fieldToSort.getValue( { item: b } ) ?? '';
|
|
145
|
+
return view.sort?.direction === 'asc'
|
|
146
|
+
? valueA.localeCompare( valueB )
|
|
147
|
+
: valueB.localeCompare( valueA );
|
|
148
|
+
} );
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Handle pagination.
|
|
153
|
+
let totalItems = filteredData.length;
|
|
154
|
+
let totalPages = 1;
|
|
155
|
+
if ( view.page !== undefined && view.perPage !== undefined ) {
|
|
156
|
+
const start = ( view.page - 1 ) * view.perPage;
|
|
157
|
+
totalItems = filteredData?.length || 0;
|
|
158
|
+
totalPages = Math.ceil( totalItems / view.perPage );
|
|
159
|
+
filteredData = filteredData?.slice( start, start + view.perPage );
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
data: filteredData,
|
|
164
|
+
paginationInfo: {
|
|
165
|
+
totalItems,
|
|
166
|
+
totalPages,
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
package/src/filter-summary.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
4
|
+
import clsx from 'clsx';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* WordPress dependencies
|
|
@@ -220,7 +220,7 @@ export default function FilterSummary( {
|
|
|
220
220
|
placement="top"
|
|
221
221
|
>
|
|
222
222
|
<div
|
|
223
|
-
className={
|
|
223
|
+
className={ clsx(
|
|
224
224
|
'dataviews-filter-summary__chip',
|
|
225
225
|
{
|
|
226
226
|
'has-reset': canResetOrRemove,
|
|
@@ -253,7 +253,7 @@ export default function FilterSummary( {
|
|
|
253
253
|
placement="top"
|
|
254
254
|
>
|
|
255
255
|
<button
|
|
256
|
-
className={
|
|
256
|
+
className={ clsx(
|
|
257
257
|
'dataviews-filter-summary__chip-remove',
|
|
258
258
|
{ 'has-values': hasValues }
|
|
259
259
|
) }
|
package/src/index.js
CHANGED