@wordpress/dataviews 1.1.0 → 2.0.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 +27 -5
- package/README.md +33 -30
- package/build/add-filter.js +30 -22
- package/build/add-filter.js.map +1 -1
- package/build/bulk-actions-toolbar.js +187 -0
- package/build/bulk-actions-toolbar.js.map +1 -0
- package/build/bulk-actions.js +75 -62
- package/build/bulk-actions.js.map +1 -1
- package/build/constants.js +17 -10
- package/build/constants.js.map +1 -1
- package/build/dataviews.js +64 -50
- package/build/dataviews.js.map +1 -1
- package/build/filter-and-sort-data-view.js +2 -2
- package/build/filter-and-sort-data-view.js.map +1 -1
- package/build/filter-summary.js +106 -96
- package/build/filter-summary.js.map +1 -1
- package/build/filters.js +18 -17
- package/build/filters.js.map +1 -1
- package/build/index.js.map +1 -1
- package/build/item-actions.js +101 -69
- package/build/item-actions.js.map +1 -1
- package/build/layouts.js.map +1 -1
- package/build/lock-unlock.js.map +1 -1
- package/build/normalize-fields.js.map +1 -1
- package/build/pagination.js +66 -57
- package/build/pagination.js.map +1 -1
- package/build/reset-filters.js +9 -4
- package/build/reset-filters.js.map +1 -1
- package/build/search-widget.js +108 -89
- package/build/search-widget.js.map +1 -1
- package/build/search.js +13 -6
- package/build/search.js.map +1 -1
- package/build/single-selection-checkbox.js +6 -2
- package/build/single-selection-checkbox.js.map +1 -1
- package/build/types.js.map +1 -1
- package/build/utils.js +3 -15
- package/build/utils.js.map +1 -1
- package/build/view-actions.js +168 -120
- package/build/view-actions.js.map +1 -1
- package/build/view-grid.js +119 -106
- package/build/view-grid.js.map +1 -1
- package/build/view-list.js +217 -83
- package/build/view-list.js.map +1 -1
- package/build/view-table.js +227 -199
- package/build/view-table.js.map +1 -1
- package/build-module/add-filter.js +30 -22
- package/build-module/add-filter.js.map +1 -1
- package/build-module/bulk-actions-toolbar.js +182 -0
- package/build-module/bulk-actions-toolbar.js.map +1 -0
- package/build-module/bulk-actions.js +77 -62
- package/build-module/bulk-actions.js.map +1 -1
- package/build-module/constants.js +16 -9
- package/build-module/constants.js.map +1 -1
- package/build-module/dataviews.js +65 -50
- package/build-module/dataviews.js.map +1 -1
- package/build-module/filter-and-sort-data-view.js +2 -2
- package/build-module/filter-and-sort-data-view.js.map +1 -1
- package/build-module/filter-summary.js +107 -97
- package/build-module/filter-summary.js.map +1 -1
- package/build-module/filters.js +18 -17
- package/build-module/filters.js.map +1 -1
- package/build-module/index.js.map +1 -1
- package/build-module/item-actions.js +102 -71
- package/build-module/item-actions.js.map +1 -1
- package/build-module/layouts.js.map +1 -1
- package/build-module/lock-unlock.js.map +1 -1
- package/build-module/normalize-fields.js.map +1 -1
- package/build-module/pagination.js +67 -57
- package/build-module/pagination.js.map +1 -1
- package/build-module/reset-filters.js +9 -4
- package/build-module/reset-filters.js.map +1 -1
- package/build-module/search-widget.js +109 -89
- package/build-module/search-widget.js.map +1 -1
- package/build-module/search.js +13 -6
- package/build-module/search.js.map +1 -1
- package/build-module/single-selection-checkbox.js +6 -2
- package/build-module/single-selection-checkbox.js.map +1 -1
- package/build-module/types.js.map +1 -1
- package/build-module/utils.js +2 -13
- package/build-module/utils.js.map +1 -1
- package/build-module/view-actions.js +170 -121
- package/build-module/view-actions.js.map +1 -1
- package/build-module/view-grid.js +121 -106
- package/build-module/view-grid.js.map +1 -1
- package/build-module/view-list.js +219 -85
- package/build-module/view-list.js.map +1 -1
- package/build-module/view-table.js +230 -201
- package/build-module/view-table.js.map +1 -1
- package/build-style/style-rtl.css +168 -44
- package/build-style/style.css +168 -44
- package/build-types/add-filter.d.ts +11 -0
- package/build-types/add-filter.d.ts.map +1 -0
- package/build-types/bulk-actions-toolbar.d.ts +12 -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 +19 -32
- package/build-types/constants.d.ts.map +1 -1
- package/build-types/dataviews.d.ts +22 -0
- package/build-types/dataviews.d.ts.map +1 -0
- package/build-types/filter-and-sort-data-view.d.ts +3 -3
- package/build-types/filter-and-sort-data-view.d.ts.map +1 -1
- package/build-types/filter-summary.d.ts +14 -0
- package/build-types/filter-summary.d.ts.map +1 -0
- package/build-types/filters.d.ts +13 -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 +35 -0
- package/build-types/item-actions.d.ts.map +1 -0
- package/build-types/layouts.d.ts +24 -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 +2 -2
- package/build-types/normalize-fields.d.ts.map +1 -1
- package/build-types/pagination.d.ts +16 -0
- package/build-types/pagination.d.ts.map +1 -0
- package/build-types/reset-filters.d.ts +13 -0
- package/build-types/reset-filters.d.ts.map +1 -0
- package/build-types/search-widget.d.ts +10 -0
- package/build-types/search-widget.d.ts.map +1 -0
- package/build-types/search.d.ts +13 -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 +221 -21
- package/build-types/types.d.ts.map +1 -1
- package/build-types/utils.d.ts +3 -0
- package/build-types/utils.d.ts.map +1 -0
- package/build-types/view-actions.d.ts +12 -0
- package/build-types/view-actions.d.ts.map +1 -0
- package/build-types/view-grid.d.ts +4 -0
- package/build-types/view-grid.d.ts.map +1 -0
- package/build-types/view-list.d.ts +4 -0
- package/build-types/view-list.d.ts.map +1 -0
- package/build-types/view-table.d.ts +5 -0
- package/build-types/view-table.d.ts.map +1 -0
- package/package.json +12 -13
- package/src/{add-filter.js → add-filter.tsx} +17 -1
- package/src/bulk-actions-toolbar.tsx +272 -0
- package/src/{bulk-actions.js → bulk-actions.tsx} +77 -17
- package/src/constants.ts +12 -5
- package/src/{dataviews.js → dataviews.tsx} +54 -14
- package/src/filter-and-sort-data-view.ts +13 -8
- package/src/{filter-summary.js → filter-summary.tsx} +38 -9
- package/src/{filters.js → filters.tsx} +18 -6
- package/src/{item-actions.js → item-actions.tsx} +119 -30
- package/src/normalize-fields.ts +4 -2
- package/src/{pagination.js → pagination.tsx} +29 -8
- package/src/{reset-filters.js → reset-filters.tsx} +17 -2
- package/src/{search-widget.js → search-widget.tsx} +27 -7
- package/src/{search.js → search.tsx} +22 -5
- package/src/{single-selection-checkbox.js → single-selection-checkbox.tsx} +17 -2
- package/src/style.scss +166 -43
- package/src/types.ts +286 -21
- package/src/{utils.js → utils.ts} +5 -13
- package/src/{view-actions.js → view-actions.tsx} +105 -49
- package/src/{view-grid.js → view-grid.tsx} +31 -18
- package/src/view-list.tsx +410 -0
- package/src/{view-table.js → view-table.tsx} +99 -40
- package/tsconfig.json +3 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/build/dropdown-menu-helper.js +0 -71
- package/build/dropdown-menu-helper.js.map +0 -1
- package/build-module/dropdown-menu-helper.js +0 -64
- package/build-module/dropdown-menu-helper.js.map +0 -1
- package/src/dropdown-menu-helper.js +0 -61
- package/src/view-list.js +0 -207
- /package/src/{index.js → index.ts} +0 -0
- /package/src/{layouts.js → layouts.ts} +0 -0
- /package/src/{lock-unlock.js → lock-unlock.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordpress/dataviews",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "DataViews is a component that provides an API to render datasets using different types of layouts (table, grid, list, etc.).",
|
|
5
5
|
"author": "The WordPress Contributors",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"url": "https://github.com/WordPress/gutenberg/issues"
|
|
20
20
|
},
|
|
21
21
|
"engines": {
|
|
22
|
-
"node": ">=12"
|
|
22
|
+
"node": ">=18.12.0",
|
|
23
|
+
"npm": ">=8.19.2"
|
|
23
24
|
},
|
|
24
25
|
"main": "build/index.js",
|
|
25
26
|
"module": "build-module/index.js",
|
|
@@ -29,16 +30,14 @@
|
|
|
29
30
|
"dependencies": {
|
|
30
31
|
"@ariakit/react": "^0.3.12",
|
|
31
32
|
"@babel/runtime": "^7.16.0",
|
|
32
|
-
"@wordpress/
|
|
33
|
-
"@wordpress/
|
|
34
|
-
"@wordpress/
|
|
35
|
-
"@wordpress/
|
|
36
|
-
"@wordpress/
|
|
37
|
-
"@wordpress/
|
|
38
|
-
"@wordpress/
|
|
39
|
-
"
|
|
40
|
-
"@wordpress/private-apis": "^0.39.0",
|
|
41
|
-
"classnames": "^2.3.1",
|
|
33
|
+
"@wordpress/components": "^28.0.0",
|
|
34
|
+
"@wordpress/compose": "^7.0.0",
|
|
35
|
+
"@wordpress/element": "^6.0.0",
|
|
36
|
+
"@wordpress/i18n": "^5.0.0",
|
|
37
|
+
"@wordpress/icons": "^10.0.0",
|
|
38
|
+
"@wordpress/primitives": "^4.0.0",
|
|
39
|
+
"@wordpress/private-apis": "^1.0.0",
|
|
40
|
+
"clsx": "^2.1.1",
|
|
42
41
|
"remove-accents": "^0.5.0"
|
|
43
42
|
},
|
|
44
43
|
"peerDependencies": {
|
|
@@ -47,5 +46,5 @@
|
|
|
47
46
|
"publishConfig": {
|
|
48
47
|
"access": "public"
|
|
49
48
|
},
|
|
50
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "2f30cddff15723ac7017fd009fc5913b7b419400"
|
|
51
50
|
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { Ref } from 'react';
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
7
|
* WordPress dependencies
|
|
3
8
|
*/
|
|
@@ -12,6 +17,7 @@ import { forwardRef } from '@wordpress/element';
|
|
|
12
17
|
* Internal dependencies
|
|
13
18
|
*/
|
|
14
19
|
import { unlock } from './lock-unlock';
|
|
20
|
+
import type { NormalizedFilter, View } from './types';
|
|
15
21
|
|
|
16
22
|
const {
|
|
17
23
|
DropdownMenuV2: DropdownMenu,
|
|
@@ -19,7 +25,17 @@ const {
|
|
|
19
25
|
DropdownMenuItemLabelV2: DropdownMenuItemLabel,
|
|
20
26
|
} = unlock( componentsPrivateApis );
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
interface AddFilterProps {
|
|
29
|
+
filters: NormalizedFilter[];
|
|
30
|
+
view: View;
|
|
31
|
+
onChangeView: ( view: View ) => void;
|
|
32
|
+
setOpenedFilter: ( filter: string | null ) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function AddFilter(
|
|
36
|
+
{ filters, view, onChangeView, setOpenedFilter }: AddFilterProps,
|
|
37
|
+
ref: Ref< HTMLButtonElement >
|
|
38
|
+
) {
|
|
23
39
|
if ( ! filters.length || filters.every( ( { isPrimary } ) => isPrimary ) ) {
|
|
24
40
|
return null;
|
|
25
41
|
}
|
|
@@ -0,0 +1,272 @@
|
|
|
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
|
+
import type { Action, AnyItem } from './types';
|
|
21
|
+
import type { ActionTriggerProps } from './item-actions';
|
|
22
|
+
|
|
23
|
+
interface ActionButtonProps< Item extends AnyItem > {
|
|
24
|
+
action: Action< Item >;
|
|
25
|
+
selectedItems: Item[];
|
|
26
|
+
actionInProgress: string | null;
|
|
27
|
+
setActionInProgress: ( actionId: string | null ) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ToolbarContentProps< Item extends AnyItem > {
|
|
31
|
+
selection: string[];
|
|
32
|
+
actionsToShow: Action< Item >[];
|
|
33
|
+
selectedItems: Item[];
|
|
34
|
+
onSelectionChange: ( selection: Item[] ) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface BulkActionsToolbarProps< Item extends AnyItem > {
|
|
38
|
+
data: Item[];
|
|
39
|
+
selection: string[];
|
|
40
|
+
actions: Action< Item >[];
|
|
41
|
+
onSelectionChange: ( selection: Item[] ) => void;
|
|
42
|
+
getItemId: ( item: Item ) => string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const SNACKBAR_VARIANTS = {
|
|
46
|
+
init: {
|
|
47
|
+
bottom: -48,
|
|
48
|
+
},
|
|
49
|
+
open: {
|
|
50
|
+
bottom: 24,
|
|
51
|
+
transition: {
|
|
52
|
+
bottom: { type: 'tween', duration: 0.2, ease: [ 0, 0, 0.2, 1 ] },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
exit: {
|
|
56
|
+
opacity: 0,
|
|
57
|
+
bottom: 24,
|
|
58
|
+
transition: {
|
|
59
|
+
opacity: { type: 'tween', duration: 0.2, ease: [ 0, 0, 0.2, 1 ] },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function ActionTrigger< Item extends AnyItem >( {
|
|
65
|
+
action,
|
|
66
|
+
onClick,
|
|
67
|
+
isBusy,
|
|
68
|
+
items,
|
|
69
|
+
}: ActionTriggerProps< Item > ) {
|
|
70
|
+
const label =
|
|
71
|
+
typeof action.label === 'string' ? action.label : action.label( items );
|
|
72
|
+
return (
|
|
73
|
+
<ToolbarButton
|
|
74
|
+
disabled={ isBusy }
|
|
75
|
+
label={ label }
|
|
76
|
+
icon={ action.icon }
|
|
77
|
+
isDestructive={ action.isDestructive }
|
|
78
|
+
size="compact"
|
|
79
|
+
onClick={ onClick }
|
|
80
|
+
isBusy={ isBusy }
|
|
81
|
+
__experimentalIsFocusable
|
|
82
|
+
tooltipPosition="top"
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const EMPTY_ARRAY: [] = [];
|
|
88
|
+
|
|
89
|
+
function ActionButton< Item extends AnyItem >( {
|
|
90
|
+
action,
|
|
91
|
+
selectedItems,
|
|
92
|
+
actionInProgress,
|
|
93
|
+
setActionInProgress,
|
|
94
|
+
}: ActionButtonProps< Item > ) {
|
|
95
|
+
const selectedEligibleItems = useMemo( () => {
|
|
96
|
+
return selectedItems.filter( ( item ) => {
|
|
97
|
+
return ! action.isEligible || action.isEligible( item );
|
|
98
|
+
} );
|
|
99
|
+
}, [ action, selectedItems ] );
|
|
100
|
+
if ( 'RenderModal' in action ) {
|
|
101
|
+
return (
|
|
102
|
+
<ActionWithModal
|
|
103
|
+
key={ action.id }
|
|
104
|
+
action={ action }
|
|
105
|
+
items={ selectedEligibleItems }
|
|
106
|
+
ActionTrigger={ ActionTrigger }
|
|
107
|
+
/>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return (
|
|
111
|
+
<ActionTrigger
|
|
112
|
+
key={ action.id }
|
|
113
|
+
action={ action }
|
|
114
|
+
onClick={ () => {
|
|
115
|
+
setActionInProgress( action.id );
|
|
116
|
+
action.callback( selectedItems );
|
|
117
|
+
} }
|
|
118
|
+
items={ selectedEligibleItems }
|
|
119
|
+
isBusy={ actionInProgress === action.id }
|
|
120
|
+
/>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function renderToolbarContent< Item extends AnyItem >(
|
|
125
|
+
selection: string[],
|
|
126
|
+
actionsToShow: Action< Item >[],
|
|
127
|
+
selectedItems: Item[],
|
|
128
|
+
actionInProgress: string | null,
|
|
129
|
+
setActionInProgress: ( actionId: string | null ) => void,
|
|
130
|
+
onSelectionChange: ( selection: Item[] ) => void
|
|
131
|
+
) {
|
|
132
|
+
return (
|
|
133
|
+
<>
|
|
134
|
+
<ToolbarGroup>
|
|
135
|
+
<div className="dataviews-bulk-actions__selection-count">
|
|
136
|
+
{ selection.length === 1
|
|
137
|
+
? __( '1 item selected' )
|
|
138
|
+
: sprintf(
|
|
139
|
+
// translators: %s: Total number of selected items.
|
|
140
|
+
_n(
|
|
141
|
+
'%s item selected',
|
|
142
|
+
'%s items selected',
|
|
143
|
+
selection.length
|
|
144
|
+
),
|
|
145
|
+
selection.length
|
|
146
|
+
) }
|
|
147
|
+
</div>
|
|
148
|
+
</ToolbarGroup>
|
|
149
|
+
<ToolbarGroup>
|
|
150
|
+
{ actionsToShow.map( ( action ) => {
|
|
151
|
+
return (
|
|
152
|
+
<ActionButton
|
|
153
|
+
key={ action.id }
|
|
154
|
+
action={ action }
|
|
155
|
+
selectedItems={ selectedItems }
|
|
156
|
+
actionInProgress={ actionInProgress }
|
|
157
|
+
setActionInProgress={ setActionInProgress }
|
|
158
|
+
/>
|
|
159
|
+
);
|
|
160
|
+
} ) }
|
|
161
|
+
</ToolbarGroup>
|
|
162
|
+
<ToolbarGroup>
|
|
163
|
+
<ToolbarButton
|
|
164
|
+
icon={ closeSmall }
|
|
165
|
+
showTooltip
|
|
166
|
+
tooltipPosition="top"
|
|
167
|
+
label={ __( 'Cancel' ) }
|
|
168
|
+
disabled={ !! actionInProgress }
|
|
169
|
+
onClick={ () => {
|
|
170
|
+
onSelectionChange( EMPTY_ARRAY );
|
|
171
|
+
} }
|
|
172
|
+
/>
|
|
173
|
+
</ToolbarGroup>
|
|
174
|
+
</>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function ToolbarContent< Item extends AnyItem >( {
|
|
179
|
+
selection,
|
|
180
|
+
actionsToShow,
|
|
181
|
+
selectedItems,
|
|
182
|
+
onSelectionChange,
|
|
183
|
+
}: ToolbarContentProps< Item > ) {
|
|
184
|
+
const [ actionInProgress, setActionInProgress ] = useState< string | null >(
|
|
185
|
+
null
|
|
186
|
+
);
|
|
187
|
+
const buttons = useRef< JSX.Element | null >( null );
|
|
188
|
+
if ( ! actionInProgress ) {
|
|
189
|
+
if ( buttons.current ) {
|
|
190
|
+
buttons.current = null;
|
|
191
|
+
}
|
|
192
|
+
return renderToolbarContent(
|
|
193
|
+
selection,
|
|
194
|
+
actionsToShow,
|
|
195
|
+
selectedItems,
|
|
196
|
+
actionInProgress,
|
|
197
|
+
setActionInProgress,
|
|
198
|
+
onSelectionChange
|
|
199
|
+
);
|
|
200
|
+
} else if ( ! buttons.current ) {
|
|
201
|
+
buttons.current = renderToolbarContent(
|
|
202
|
+
selection,
|
|
203
|
+
actionsToShow,
|
|
204
|
+
selectedItems,
|
|
205
|
+
actionInProgress,
|
|
206
|
+
setActionInProgress,
|
|
207
|
+
onSelectionChange
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
return buttons.current;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export default function BulkActionsToolbar< Item extends AnyItem >( {
|
|
214
|
+
data,
|
|
215
|
+
selection,
|
|
216
|
+
actions = EMPTY_ARRAY,
|
|
217
|
+
onSelectionChange,
|
|
218
|
+
getItemId,
|
|
219
|
+
}: BulkActionsToolbarProps< Item > ) {
|
|
220
|
+
const isReducedMotion = useReducedMotion();
|
|
221
|
+
const selectedItems = useMemo( () => {
|
|
222
|
+
return data.filter( ( item ) =>
|
|
223
|
+
selection.includes( getItemId( item ) )
|
|
224
|
+
);
|
|
225
|
+
}, [ selection, data, getItemId ] );
|
|
226
|
+
|
|
227
|
+
const actionsToShow = useMemo(
|
|
228
|
+
() =>
|
|
229
|
+
actions.filter( ( action ) => {
|
|
230
|
+
return (
|
|
231
|
+
action.supportsBulk &&
|
|
232
|
+
action.icon &&
|
|
233
|
+
selectedItems.some(
|
|
234
|
+
( item ) =>
|
|
235
|
+
! action.isEligible || action.isEligible( item )
|
|
236
|
+
)
|
|
237
|
+
);
|
|
238
|
+
} ),
|
|
239
|
+
[ actions, selectedItems ]
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
if (
|
|
243
|
+
( selection && selection.length === 0 ) ||
|
|
244
|
+
actionsToShow.length === 0
|
|
245
|
+
) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<AnimatePresence>
|
|
251
|
+
<motion.div
|
|
252
|
+
layout={ ! isReducedMotion } // See https://www.framer.com/docs/animation/#layout-animations
|
|
253
|
+
initial="init"
|
|
254
|
+
animate="open"
|
|
255
|
+
exit="exit"
|
|
256
|
+
variants={ isReducedMotion ? undefined : SNACKBAR_VARIANTS }
|
|
257
|
+
className="dataviews-bulk-actions"
|
|
258
|
+
>
|
|
259
|
+
<Toolbar label={ __( 'Bulk actions' ) }>
|
|
260
|
+
<div className="dataviews-bulk-actions-toolbar-wrapper">
|
|
261
|
+
<ToolbarContent
|
|
262
|
+
selection={ selection }
|
|
263
|
+
actionsToShow={ actionsToShow }
|
|
264
|
+
selectedItems={ selectedItems }
|
|
265
|
+
onSelectionChange={ onSelectionChange }
|
|
266
|
+
/>
|
|
267
|
+
</div>
|
|
268
|
+
</Toolbar>
|
|
269
|
+
</motion.div>
|
|
270
|
+
</AnimatePresence>
|
|
271
|
+
);
|
|
272
|
+
}
|
|
@@ -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,40 +22,85 @@ 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( () => {
|
|
53
95
|
setActionWithModal( undefined );
|
|
54
96
|
}, [ setActionWithModal ] );
|
|
97
|
+
const label =
|
|
98
|
+
typeof action.label === 'string'
|
|
99
|
+
? action.label
|
|
100
|
+
: action.label( selectedItems );
|
|
55
101
|
return (
|
|
56
102
|
<Modal
|
|
57
|
-
title={ ! hideModalHeader
|
|
103
|
+
title={ ! hideModalHeader ? label : undefined }
|
|
58
104
|
__experimentalHideHeader={ !! hideModalHeader }
|
|
59
105
|
onRequestClose={ onCloseModal }
|
|
60
106
|
overlayClassName="dataviews-action-modal"
|
|
@@ -62,18 +108,24 @@ function ActionWithModal( {
|
|
|
62
108
|
<RenderModal
|
|
63
109
|
items={ eligibleItems }
|
|
64
110
|
closeModal={ onCloseModal }
|
|
65
|
-
|
|
111
|
+
onActionPerformed={ () => onMenuOpenChange( false ) }
|
|
66
112
|
/>
|
|
67
113
|
</Modal>
|
|
68
114
|
);
|
|
69
115
|
}
|
|
70
116
|
|
|
71
|
-
function BulkActionItem
|
|
117
|
+
function BulkActionItem< Item extends AnyItem >( {
|
|
118
|
+
action,
|
|
119
|
+
selectedItems,
|
|
120
|
+
setActionWithModal,
|
|
121
|
+
}: BulkActionsItemProps< Item > ) {
|
|
72
122
|
const eligibleItems = useMemo( () => {
|
|
73
|
-
return selectedItems.filter(
|
|
123
|
+
return selectedItems.filter(
|
|
124
|
+
( item ) => ! action.isEligible || action.isEligible( item )
|
|
125
|
+
);
|
|
74
126
|
}, [ action, selectedItems ] );
|
|
75
127
|
|
|
76
|
-
const shouldShowModal =
|
|
128
|
+
const shouldShowModal = 'RenderModal' in action;
|
|
77
129
|
|
|
78
130
|
return (
|
|
79
131
|
<DropdownMenuItem
|
|
@@ -96,7 +148,11 @@ function BulkActionItem( { action, selectedItems, setActionWithModal } ) {
|
|
|
96
148
|
);
|
|
97
149
|
}
|
|
98
150
|
|
|
99
|
-
function ActionsMenuGroup
|
|
151
|
+
function ActionsMenuGroup< Item extends AnyItem >( {
|
|
152
|
+
actions,
|
|
153
|
+
selectedItems,
|
|
154
|
+
setActionWithModal,
|
|
155
|
+
}: ActionsMenuGroupProps< Item > ) {
|
|
100
156
|
return (
|
|
101
157
|
<>
|
|
102
158
|
<DropdownMenuGroup>
|
|
@@ -114,22 +170,26 @@ function ActionsMenuGroup( { actions, selectedItems, setActionWithModal } ) {
|
|
|
114
170
|
);
|
|
115
171
|
}
|
|
116
172
|
|
|
117
|
-
export default function BulkActions( {
|
|
173
|
+
export default function BulkActions< Item extends AnyItem >( {
|
|
118
174
|
data,
|
|
119
175
|
actions,
|
|
120
176
|
selection,
|
|
121
177
|
onSelectionChange,
|
|
122
178
|
getItemId,
|
|
123
|
-
} ) {
|
|
179
|
+
}: BulkActionsProps< Item > ) {
|
|
124
180
|
const bulkActions = useMemo(
|
|
125
181
|
() => actions.filter( ( action ) => action.supportsBulk ),
|
|
126
182
|
[ actions ]
|
|
127
183
|
);
|
|
128
184
|
const [ isMenuOpen, onMenuOpenChange ] = useState( false );
|
|
129
|
-
const [ actionWithModal, setActionWithModal ] = useState
|
|
185
|
+
const [ actionWithModal, setActionWithModal ] = useState<
|
|
186
|
+
ActionModal< Item > | undefined
|
|
187
|
+
>();
|
|
130
188
|
const selectableItems = useMemo( () => {
|
|
131
189
|
return data.filter( ( item ) => {
|
|
132
|
-
return bulkActions.some(
|
|
190
|
+
return bulkActions.some(
|
|
191
|
+
( action ) => ! action.isEligible || action.isEligible( item )
|
|
192
|
+
);
|
|
133
193
|
} );
|
|
134
194
|
}, [ data, bulkActions ] );
|
|
135
195
|
|
package/src/constants.ts
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { __ } from '@wordpress/i18n';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Internal dependencies
|
|
8
|
+
*/
|
|
9
|
+
import type { Operator } from './types';
|
|
10
|
+
|
|
6
11
|
// Filter operators.
|
|
7
12
|
export const OPERATOR_IS = 'is';
|
|
8
13
|
export const OPERATOR_IS_NOT = 'isNot';
|
|
@@ -19,7 +24,7 @@ export const ALL_OPERATORS = [
|
|
|
19
24
|
OPERATOR_IS_ALL,
|
|
20
25
|
OPERATOR_IS_NOT_ALL,
|
|
21
26
|
];
|
|
22
|
-
export const OPERATORS = {
|
|
27
|
+
export const OPERATORS: Record< Operator, { key: string; label: string } > = {
|
|
23
28
|
[ OPERATOR_IS ]: {
|
|
24
29
|
key: 'is-filter',
|
|
25
30
|
label: __( 'Is' ),
|
|
@@ -46,10 +51,12 @@ export const OPERATORS = {
|
|
|
46
51
|
},
|
|
47
52
|
};
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
export const
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
export const SORTING_DIRECTIONS = [ 'asc', 'desc' ] as const;
|
|
55
|
+
export const sortArrows = { asc: '↑', desc: '↓' };
|
|
56
|
+
export const sortValues = { asc: 'ascending', desc: 'descending' } as const;
|
|
57
|
+
export const sortLabels = {
|
|
58
|
+
asc: __( 'Sort ascending' ),
|
|
59
|
+
desc: __( 'Sort descending' ),
|
|
53
60
|
};
|
|
54
61
|
|
|
55
62
|
// View layouts.
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { ComponentType } from 'react';
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
7
|
* WordPress dependencies
|
|
3
8
|
*/
|
|
@@ -15,21 +20,47 @@ import { LAYOUT_TABLE, LAYOUT_GRID } from './constants';
|
|
|
15
20
|
import { VIEW_LAYOUTS } from './layouts';
|
|
16
21
|
import BulkActions from './bulk-actions';
|
|
17
22
|
import { normalizeFields } from './normalize-fields';
|
|
23
|
+
import BulkActionsToolbar from './bulk-actions-toolbar';
|
|
24
|
+
import type { Action, AnyItem, Field, View, ViewBaseProps } from './types';
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
interface DataViewsProps< Item extends AnyItem > {
|
|
27
|
+
view: View;
|
|
28
|
+
onChangeView: ( view: View ) => void;
|
|
29
|
+
fields: Field< Item >[];
|
|
30
|
+
search?: boolean;
|
|
31
|
+
searchLabel?: string;
|
|
32
|
+
actions?: Action< Item >[];
|
|
33
|
+
data: Item[];
|
|
34
|
+
getItemId?: ( item: Item ) => string;
|
|
35
|
+
isLoading?: boolean;
|
|
36
|
+
paginationInfo: {
|
|
37
|
+
totalItems: number;
|
|
38
|
+
totalPages: number;
|
|
39
|
+
};
|
|
40
|
+
supportedLayouts: string[];
|
|
41
|
+
onSelectionChange?: ( items: Item[] ) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const defaultGetItemId = ( item: AnyItem ) => item.id;
|
|
20
45
|
const defaultOnSelectionChange = () => {};
|
|
21
46
|
|
|
22
|
-
function useSomeItemHasAPossibleBulkAction
|
|
47
|
+
function useSomeItemHasAPossibleBulkAction< Item extends AnyItem >(
|
|
48
|
+
actions: Action< Item >[],
|
|
49
|
+
data: Item[]
|
|
50
|
+
) {
|
|
23
51
|
return useMemo( () => {
|
|
24
52
|
return data.some( ( item ) => {
|
|
25
53
|
return actions.some( ( action ) => {
|
|
26
|
-
return
|
|
54
|
+
return (
|
|
55
|
+
action.supportsBulk &&
|
|
56
|
+
( ! action.isEligible || action.isEligible( item ) )
|
|
57
|
+
);
|
|
27
58
|
} );
|
|
28
59
|
} );
|
|
29
60
|
}, [ actions, data ] );
|
|
30
61
|
}
|
|
31
62
|
|
|
32
|
-
export default function DataViews( {
|
|
63
|
+
export default function DataViews< Item extends AnyItem >( {
|
|
33
64
|
view,
|
|
34
65
|
onChangeView,
|
|
35
66
|
fields,
|
|
@@ -42,9 +73,9 @@ export default function DataViews( {
|
|
|
42
73
|
paginationInfo,
|
|
43
74
|
supportedLayouts,
|
|
44
75
|
onSelectionChange = defaultOnSelectionChange,
|
|
45
|
-
} ) {
|
|
46
|
-
const [ selection, setSelection ] = useState( [] );
|
|
47
|
-
const [ openedFilter, setOpenedFilter ] = useState( null );
|
|
76
|
+
}: DataViewsProps< Item > ) {
|
|
77
|
+
const [ selection, setSelection ] = useState< string[] >( [] );
|
|
78
|
+
const [ openedFilter, setOpenedFilter ] = useState< string | null >( null );
|
|
48
79
|
|
|
49
80
|
useEffect( () => {
|
|
50
81
|
if (
|
|
@@ -66,16 +97,15 @@ export default function DataViews( {
|
|
|
66
97
|
}, [ selection, data, getItemId, onSelectionChange ] );
|
|
67
98
|
|
|
68
99
|
const onSetSelection = useCallback(
|
|
69
|
-
( items ) => {
|
|
100
|
+
( items: Item[] ) => {
|
|
70
101
|
setSelection( items.map( ( item ) => getItemId( item ) ) );
|
|
71
102
|
onSelectionChange( items );
|
|
72
103
|
},
|
|
73
104
|
[ setSelection, getItemId, onSelectionChange ]
|
|
74
105
|
);
|
|
75
106
|
|
|
76
|
-
const ViewComponent = VIEW_LAYOUTS.find(
|
|
77
|
-
|
|
78
|
-
).component;
|
|
107
|
+
const ViewComponent = VIEW_LAYOUTS.find( ( v ) => v.type === view.type )
|
|
108
|
+
?.component as ComponentType< ViewBaseProps< Item > >;
|
|
79
109
|
const _fields = useMemo( () => normalizeFields( fields ), [ fields ] );
|
|
80
110
|
|
|
81
111
|
const hasPossibleBulkAction = useSomeItemHasAPossibleBulkAction(
|
|
@@ -127,22 +157,32 @@ export default function DataViews( {
|
|
|
127
157
|
/>
|
|
128
158
|
</HStack>
|
|
129
159
|
<ViewComponent
|
|
130
|
-
fields={ _fields }
|
|
131
|
-
view={ view }
|
|
132
|
-
onChangeView={ onChangeView }
|
|
133
160
|
actions={ actions }
|
|
134
161
|
data={ data }
|
|
162
|
+
fields={ _fields }
|
|
135
163
|
getItemId={ getItemId }
|
|
136
164
|
isLoading={ isLoading }
|
|
165
|
+
onChangeView={ onChangeView }
|
|
137
166
|
onSelectionChange={ onSetSelection }
|
|
138
167
|
selection={ selection }
|
|
139
168
|
setOpenedFilter={ setOpenedFilter }
|
|
169
|
+
view={ view }
|
|
140
170
|
/>
|
|
141
171
|
<Pagination
|
|
142
172
|
view={ view }
|
|
143
173
|
onChangeView={ onChangeView }
|
|
144
174
|
paginationInfo={ paginationInfo }
|
|
145
175
|
/>
|
|
176
|
+
{ [ LAYOUT_TABLE, LAYOUT_GRID ].includes( view.type ) &&
|
|
177
|
+
hasPossibleBulkAction && (
|
|
178
|
+
<BulkActionsToolbar
|
|
179
|
+
data={ data }
|
|
180
|
+
actions={ actions }
|
|
181
|
+
selection={ selection }
|
|
182
|
+
onSelectionChange={ onSetSelection }
|
|
183
|
+
getItemId={ getItemId }
|
|
184
|
+
/>
|
|
185
|
+
) }
|
|
146
186
|
</div>
|
|
147
187
|
);
|
|
148
188
|
}
|