@wordpress/dataviews 0.4.1 → 0.5.1
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 +2 -0
- package/README.md +1 -0
- package/build/add-filter.js +25 -108
- package/build/add-filter.js.map +1 -1
- package/build/constants.js +9 -18
- package/build/constants.js.map +1 -1
- package/build/dataviews.js +22 -16
- package/build/dataviews.js.map +1 -1
- package/build/dropdown-menu-helper.js +1 -2
- package/build/dropdown-menu-helper.js.map +1 -1
- package/build/filter-summary.js +146 -78
- package/build/filter-summary.js.map +1 -1
- package/build/filters.js +32 -18
- package/build/filters.js.map +1 -1
- package/build/pagination.js +1 -2
- package/build/pagination.js.map +1 -1
- package/build/reset-filters.js +4 -1
- package/build/reset-filters.js.map +1 -1
- package/build/search-widget.js +111 -0
- package/build/search-widget.js.map +1 -0
- package/build/search.js +2 -3
- package/build/search.js.map +1 -1
- package/build/single-selection-checkbox.js +54 -0
- package/build/single-selection-checkbox.js.map +1 -0
- package/build/utils.js +14 -1
- package/build/utils.js.map +1 -1
- package/build/view-actions.js +2 -3
- package/build/view-actions.js.map +1 -1
- package/build/view-grid.js +92 -22
- package/build/view-grid.js.map +1 -1
- package/build/view-list.js +2 -1
- package/build/view-list.js.map +1 -1
- package/build/view-table.js +43 -132
- package/build/view-table.js.map +1 -1
- package/build-module/add-filter.js +28 -111
- package/build-module/add-filter.js.map +1 -1
- package/build-module/dataviews.js +23 -17
- package/build-module/dataviews.js.map +1 -1
- package/build-module/filter-summary.js +147 -80
- package/build-module/filter-summary.js.map +1 -1
- package/build-module/filters.js +32 -17
- package/build-module/filters.js.map +1 -1
- package/build-module/reset-filters.js +4 -1
- package/build-module/reset-filters.js.map +1 -1
- package/build-module/search-widget.js +101 -0
- package/build-module/search-widget.js.map +1 -0
- package/build-module/search.js +1 -1
- package/build-module/search.js.map +1 -1
- package/build-module/single-selection-checkbox.js +47 -0
- package/build-module/single-selection-checkbox.js.map +1 -0
- package/build-module/utils.js +12 -0
- package/build-module/utils.js.map +1 -1
- package/build-module/view-actions.js +1 -1
- package/build-module/view-actions.js.map +1 -1
- package/build-module/view-grid.js +92 -22
- package/build-module/view-grid.js.map +1 -1
- package/build-module/view-list.js +2 -1
- package/build-module/view-list.js.map +1 -1
- package/build-module/view-table.js +43 -131
- package/build-module/view-table.js.map +1 -1
- package/build-style/style-rtl.css +248 -44
- package/build-style/style.css +248 -44
- package/package.json +12 -11
- package/src/add-filter.js +39 -230
- package/src/dataviews.js +31 -20
- package/src/filter-summary.js +190 -136
- package/src/filters.js +42 -29
- package/src/reset-filters.js +12 -2
- package/src/search-widget.js +128 -0
- package/src/search.js +1 -1
- package/src/single-selection-checkbox.js +59 -0
- package/src/style.scss +254 -44
- package/src/utils.js +15 -0
- package/src/view-actions.js +1 -2
- package/src/view-grid.js +127 -53
- package/src/view-list.js +5 -1
- package/src/view-table.js +57 -230
package/src/filter-summary.js
CHANGED
|
@@ -1,45 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import classnames from 'classnames';
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
7
|
* WordPress dependencies
|
|
3
8
|
*/
|
|
4
9
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
10
|
+
Dropdown,
|
|
11
|
+
__experimentalVStack as VStack,
|
|
12
|
+
__experimentalHStack as HStack,
|
|
13
|
+
FlexItem,
|
|
14
|
+
SelectControl,
|
|
15
|
+
Tooltip,
|
|
7
16
|
Icon,
|
|
8
17
|
} from '@wordpress/components';
|
|
9
|
-
import { chevronDown } from '@wordpress/icons';
|
|
10
18
|
import { __, sprintf } from '@wordpress/i18n';
|
|
11
|
-
import {
|
|
19
|
+
import { useRef, createInterpolateElement } from '@wordpress/element';
|
|
20
|
+
import { closeSmall } from '@wordpress/icons';
|
|
21
|
+
import { ENTER, SPACE } from '@wordpress/keycodes';
|
|
12
22
|
|
|
13
23
|
/**
|
|
14
24
|
* Internal dependencies
|
|
15
25
|
*/
|
|
26
|
+
import SearchWidget from './search-widget';
|
|
16
27
|
import { OPERATOR_IN, OPERATOR_NOT_IN, OPERATORS } from './constants';
|
|
17
|
-
import { unlock } from './lock-unlock';
|
|
18
|
-
import { DropdownMenuRadioItemCustom } from './dropdown-menu-helper';
|
|
19
|
-
|
|
20
|
-
const {
|
|
21
|
-
DropdownMenuV2: DropdownMenu,
|
|
22
|
-
DropdownMenuGroupV2: DropdownMenuGroup,
|
|
23
|
-
DropdownMenuItemV2: DropdownMenuItem,
|
|
24
|
-
DropdownMenuSeparatorV2: DropdownMenuSeparator,
|
|
25
|
-
DropdownMenuItemLabelV2: DropdownMenuItemLabel,
|
|
26
|
-
DropdownMenuItemHelpTextV2: DropdownMenuItemHelpText,
|
|
27
|
-
} = unlock( componentsPrivateApis );
|
|
28
28
|
|
|
29
29
|
const FilterText = ( { activeElement, filterInView, filter } ) => {
|
|
30
30
|
if ( activeElement === undefined ) {
|
|
31
31
|
return filter.name;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
const filterTextWrappers = {
|
|
35
|
+
Span1: <span className="dataviews-filter-summary__filter-text-name" />,
|
|
36
|
+
Span2: <span className="dataviews-filter-summary__filter-text-value" />,
|
|
37
|
+
};
|
|
38
|
+
|
|
34
39
|
if (
|
|
35
40
|
activeElement !== undefined &&
|
|
36
41
|
filterInView?.operator === OPERATOR_IN
|
|
37
42
|
) {
|
|
38
|
-
return
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
return createInterpolateElement(
|
|
44
|
+
sprintf(
|
|
45
|
+
/* translators: 1: Filter name. 2: Filter value. e.g.: "Author is Admin". */
|
|
46
|
+
__( '<Span1>%1$s </Span1><Span2>is %2$s</Span2>' ),
|
|
47
|
+
filter.name,
|
|
48
|
+
activeElement.label
|
|
49
|
+
),
|
|
50
|
+
filterTextWrappers
|
|
43
51
|
);
|
|
44
52
|
}
|
|
45
53
|
|
|
@@ -47,11 +55,14 @@ const FilterText = ( { activeElement, filterInView, filter } ) => {
|
|
|
47
55
|
activeElement !== undefined &&
|
|
48
56
|
filterInView?.operator === OPERATOR_NOT_IN
|
|
49
57
|
) {
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
return createInterpolateElement(
|
|
59
|
+
sprintf(
|
|
60
|
+
/* translators: 1: Filter name. 2: Filter value. e.g.: "Author is not Admin". */
|
|
61
|
+
__( '<Span1>%1$s </Span1><Span2>is not %2$s</Span2>' ),
|
|
62
|
+
filter.name,
|
|
63
|
+
activeElement.label
|
|
64
|
+
),
|
|
65
|
+
filterTextWrappers
|
|
55
66
|
);
|
|
56
67
|
}
|
|
57
68
|
|
|
@@ -62,127 +73,170 @@ const FilterText = ( { activeElement, filterInView, filter } ) => {
|
|
|
62
73
|
);
|
|
63
74
|
};
|
|
64
75
|
|
|
65
|
-
function
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
function OperatorSelector( { filter, view, onChangeView } ) {
|
|
77
|
+
const operatorOptions = filter.operators?.map( ( operator ) => ( {
|
|
78
|
+
value: operator,
|
|
79
|
+
label: OPERATORS[ operator ]?.label,
|
|
80
|
+
} ) );
|
|
81
|
+
const currentFilter = view.filters.find(
|
|
82
|
+
( _filter ) => _filter.field === filter.field
|
|
83
|
+
);
|
|
84
|
+
const value = currentFilter?.operator || filter.operators[ 0 ];
|
|
85
|
+
return (
|
|
86
|
+
operatorOptions.length > 1 && (
|
|
87
|
+
<HStack
|
|
88
|
+
spacing={ 2 }
|
|
89
|
+
justify="flex-start"
|
|
90
|
+
className="dataviews-filter-summary__operators-container"
|
|
91
|
+
>
|
|
92
|
+
<FlexItem className="dataviews-filter-summary__operators-filter-name">
|
|
93
|
+
{ filter.name }
|
|
94
|
+
</FlexItem>
|
|
95
|
+
|
|
96
|
+
<SelectControl
|
|
97
|
+
label={ __( 'Conditions' ) }
|
|
98
|
+
value={ value }
|
|
99
|
+
options={ operatorOptions }
|
|
100
|
+
onChange={ ( newValue ) => {
|
|
101
|
+
const newFilters = currentFilter
|
|
102
|
+
? [
|
|
103
|
+
...view.filters.map( ( _filter ) => {
|
|
104
|
+
if ( _filter.field === filter.field ) {
|
|
105
|
+
return {
|
|
106
|
+
..._filter,
|
|
107
|
+
operator: newValue,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return _filter;
|
|
111
|
+
} ),
|
|
112
|
+
]
|
|
113
|
+
: [
|
|
114
|
+
...view.filters,
|
|
115
|
+
{
|
|
116
|
+
field: filter.field,
|
|
117
|
+
operator: newValue,
|
|
118
|
+
},
|
|
119
|
+
];
|
|
120
|
+
onChangeView( {
|
|
121
|
+
...view,
|
|
122
|
+
page: 1,
|
|
123
|
+
filters: newFilters,
|
|
124
|
+
} );
|
|
125
|
+
} }
|
|
126
|
+
size="small"
|
|
127
|
+
__nextHasNoMarginBottom
|
|
128
|
+
hideLabelFromVision
|
|
129
|
+
/>
|
|
130
|
+
</HStack>
|
|
131
|
+
)
|
|
132
|
+
);
|
|
74
133
|
}
|
|
75
134
|
|
|
76
|
-
export default function FilterSummary( {
|
|
135
|
+
export default function FilterSummary( {
|
|
136
|
+
addFilterRef,
|
|
137
|
+
openedFilter,
|
|
138
|
+
...commonProps
|
|
139
|
+
} ) {
|
|
140
|
+
const toggleRef = useRef();
|
|
141
|
+
const { filter, view, onChangeView } = commonProps;
|
|
77
142
|
const filterInView = view.filters.find( ( f ) => f.field === filter.field );
|
|
78
|
-
const otherFilters = view.filters.filter(
|
|
79
|
-
( f ) => f.field !== filter.field
|
|
80
|
-
);
|
|
81
143
|
const activeElement = filter.elements.find(
|
|
82
144
|
( element ) => element.value === filterInView?.value
|
|
83
145
|
);
|
|
84
|
-
const
|
|
85
|
-
|
|
146
|
+
const isPrimary = filter.isPrimary;
|
|
147
|
+
const hasValues = filterInView?.value !== undefined;
|
|
148
|
+
const canResetOrRemove = ! isPrimary || hasValues;
|
|
86
149
|
return (
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
150
|
+
<Dropdown
|
|
151
|
+
defaultOpen={ openedFilter === filter.field }
|
|
152
|
+
contentClassName="dataviews-filter-summary__popover"
|
|
153
|
+
popoverProps={ { placement: 'bottom-start', role: 'dialog' } }
|
|
154
|
+
onClose={ () => {
|
|
155
|
+
toggleRef.current?.focus();
|
|
156
|
+
} }
|
|
157
|
+
renderToggle={ ( { isOpen, onToggle } ) => (
|
|
158
|
+
<div className="dataviews-filter-summary__chip-container">
|
|
159
|
+
<Tooltip
|
|
160
|
+
text={ sprintf(
|
|
161
|
+
/* translators: 1: Filter name. */
|
|
162
|
+
__( 'Filter by: %1$s' ),
|
|
163
|
+
filter.name.toLowerCase()
|
|
164
|
+
) }
|
|
165
|
+
placement="top"
|
|
166
|
+
>
|
|
167
|
+
<div
|
|
168
|
+
className={ classnames(
|
|
169
|
+
'dataviews-filter-summary__chip',
|
|
170
|
+
{
|
|
171
|
+
'has-reset': canResetOrRemove,
|
|
172
|
+
'has-values': hasValues,
|
|
173
|
+
}
|
|
174
|
+
) }
|
|
175
|
+
role="button"
|
|
176
|
+
tabIndex={ 0 }
|
|
177
|
+
onClick={ onToggle }
|
|
178
|
+
onKeyDown={ ( event ) => {
|
|
179
|
+
if (
|
|
180
|
+
[ ENTER, SPACE ].includes( event.keyCode )
|
|
181
|
+
) {
|
|
182
|
+
onToggle();
|
|
183
|
+
event.preventDefault();
|
|
184
|
+
}
|
|
185
|
+
} }
|
|
186
|
+
aria-pressed={ isOpen }
|
|
187
|
+
aria-expanded={ isOpen }
|
|
188
|
+
ref={ toggleRef }
|
|
189
|
+
>
|
|
190
|
+
<FilterText
|
|
191
|
+
activeElement={ activeElement }
|
|
192
|
+
filterInView={ filterInView }
|
|
193
|
+
filter={ filter }
|
|
194
|
+
/>
|
|
195
|
+
</div>
|
|
196
|
+
</Tooltip>
|
|
197
|
+
{ canResetOrRemove && (
|
|
198
|
+
<Tooltip
|
|
199
|
+
text={ isPrimary ? __( 'Reset' ) : __( 'Remove' ) }
|
|
200
|
+
placement="top"
|
|
201
|
+
>
|
|
202
|
+
<button
|
|
203
|
+
className={ classnames(
|
|
204
|
+
'dataviews-filter-summary__chip-remove',
|
|
205
|
+
{ 'has-values': hasValues }
|
|
206
|
+
) }
|
|
207
|
+
onClick={ () => {
|
|
111
208
|
onChangeView( {
|
|
112
209
|
...view,
|
|
113
210
|
page: 1,
|
|
114
|
-
filters:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
211
|
+
filters: view.filters.filter(
|
|
212
|
+
( _filter ) =>
|
|
213
|
+
_filter.field !== filter.field
|
|
214
|
+
),
|
|
215
|
+
} );
|
|
216
|
+
// If the filter is not primary and can be removed, it will be added
|
|
217
|
+
// back to the available filters from `Add filter` component.
|
|
218
|
+
if ( ! isPrimary ) {
|
|
219
|
+
addFilterRef.current?.focus();
|
|
220
|
+
} else {
|
|
221
|
+
// If is primary, focus the toggle button.
|
|
222
|
+
toggleRef.current?.focus();
|
|
223
|
+
}
|
|
224
|
+
} }
|
|
126
225
|
>
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
<DropdownMenuItem
|
|
143
|
-
suffix={
|
|
144
|
-
<span aria-hidden="true">
|
|
145
|
-
{ OPERATORS[ activeOperator ]?.label }
|
|
146
|
-
</span>
|
|
147
|
-
}
|
|
148
|
-
>
|
|
149
|
-
<DropdownMenuItemLabel>
|
|
150
|
-
{ __( 'Conditions' ) }
|
|
151
|
-
</DropdownMenuItemLabel>
|
|
152
|
-
</DropdownMenuItem>
|
|
153
|
-
}
|
|
154
|
-
>
|
|
155
|
-
{ Object.entries( OPERATORS ).map(
|
|
156
|
-
( [ operator, { label, key } ] ) => (
|
|
157
|
-
<DropdownMenuRadioItemCustom
|
|
158
|
-
key={ key }
|
|
159
|
-
name={ `filter-summary-${ filter.field }-conditions` }
|
|
160
|
-
value={ operator }
|
|
161
|
-
checked={ activeOperator === operator }
|
|
162
|
-
onChange={ ( e ) => {
|
|
163
|
-
onChangeView( {
|
|
164
|
-
...view,
|
|
165
|
-
page: 1,
|
|
166
|
-
filters: [
|
|
167
|
-
...otherFilters,
|
|
168
|
-
{
|
|
169
|
-
field: filter.field,
|
|
170
|
-
operator: e.target.value,
|
|
171
|
-
value: filterInView?.value,
|
|
172
|
-
},
|
|
173
|
-
],
|
|
174
|
-
} );
|
|
175
|
-
} }
|
|
176
|
-
>
|
|
177
|
-
<DropdownMenuItemLabel>
|
|
178
|
-
{ label }
|
|
179
|
-
</DropdownMenuItemLabel>
|
|
180
|
-
</DropdownMenuRadioItemCustom>
|
|
181
|
-
)
|
|
182
|
-
) }
|
|
183
|
-
</DropdownMenu>
|
|
184
|
-
) }
|
|
185
|
-
</WithSeparators>
|
|
186
|
-
</DropdownMenu>
|
|
226
|
+
<Icon icon={ closeSmall } />
|
|
227
|
+
</button>
|
|
228
|
+
</Tooltip>
|
|
229
|
+
) }
|
|
230
|
+
</div>
|
|
231
|
+
) }
|
|
232
|
+
renderContent={ () => {
|
|
233
|
+
return (
|
|
234
|
+
<VStack spacing={ 0 } justify="flex-start">
|
|
235
|
+
<OperatorSelector { ...commonProps } />
|
|
236
|
+
<SearchWidget { ...commonProps } />
|
|
237
|
+
</VStack>
|
|
238
|
+
);
|
|
239
|
+
} }
|
|
240
|
+
/>
|
|
187
241
|
);
|
|
188
242
|
}
|
package/src/filters.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WordPress dependencies
|
|
3
3
|
*/
|
|
4
|
-
import { memo } from '@wordpress/element';
|
|
4
|
+
import { memo, useRef } from '@wordpress/element';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Internal dependencies
|
|
@@ -9,24 +9,17 @@ import { memo } from '@wordpress/element';
|
|
|
9
9
|
import FilterSummary from './filter-summary';
|
|
10
10
|
import AddFilter from './add-filter';
|
|
11
11
|
import ResetFilters from './reset-filters';
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
OPERATOR_IN,
|
|
15
|
-
OPERATOR_NOT_IN,
|
|
16
|
-
LAYOUT_LIST,
|
|
17
|
-
} from './constants';
|
|
12
|
+
import { sanitizeOperators } from './utils';
|
|
13
|
+
import { ENUMERATION_TYPE, OPERATOR_IN, OPERATOR_NOT_IN } from './constants';
|
|
18
14
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const Filters = memo( function Filters( { fields, view, onChangeView } ) {
|
|
15
|
+
const Filters = memo( function Filters( {
|
|
16
|
+
fields,
|
|
17
|
+
view,
|
|
18
|
+
onChangeView,
|
|
19
|
+
openedFilter,
|
|
20
|
+
setOpenedFilter,
|
|
21
|
+
} ) {
|
|
22
|
+
const addFilterRef = useRef();
|
|
30
23
|
const filters = [];
|
|
31
24
|
fields.forEach( ( field ) => {
|
|
32
25
|
if ( ! field.type ) {
|
|
@@ -43,34 +36,50 @@ const Filters = memo( function Filters( { fields, view, onChangeView } ) {
|
|
|
43
36
|
if ( ! field.elements?.length ) {
|
|
44
37
|
return;
|
|
45
38
|
}
|
|
39
|
+
|
|
40
|
+
const isPrimary = !! field.filterBy?.isPrimary;
|
|
46
41
|
filters.push( {
|
|
47
42
|
field: field.id,
|
|
48
43
|
name: field.header,
|
|
49
44
|
elements: field.elements,
|
|
50
45
|
operators,
|
|
51
|
-
isVisible:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
f.
|
|
56
|
-
|
|
57
|
-
|
|
46
|
+
isVisible:
|
|
47
|
+
isPrimary ||
|
|
48
|
+
view.filters.some(
|
|
49
|
+
( f ) =>
|
|
50
|
+
f.field === field.id &&
|
|
51
|
+
[ OPERATOR_IN, OPERATOR_NOT_IN ].includes(
|
|
52
|
+
f.operator
|
|
53
|
+
)
|
|
54
|
+
),
|
|
55
|
+
isPrimary,
|
|
58
56
|
} );
|
|
59
57
|
}
|
|
60
58
|
} );
|
|
61
|
-
|
|
59
|
+
// Sort filters by primary property. We need the primary filters to be first.
|
|
60
|
+
// Then we sort by name.
|
|
61
|
+
filters.sort( ( a, b ) => {
|
|
62
|
+
if ( a.isPrimary && ! b.isPrimary ) {
|
|
63
|
+
return -1;
|
|
64
|
+
}
|
|
65
|
+
if ( ! a.isPrimary && b.isPrimary ) {
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
68
|
+
return a.name.localeCompare( b.name );
|
|
69
|
+
} );
|
|
62
70
|
const addFilter = (
|
|
63
71
|
<AddFilter
|
|
64
72
|
key="add-filter"
|
|
65
73
|
filters={ filters }
|
|
66
74
|
view={ view }
|
|
67
75
|
onChangeView={ onChangeView }
|
|
76
|
+
ref={ addFilterRef }
|
|
77
|
+
setOpenedFilter={ setOpenedFilter }
|
|
68
78
|
/>
|
|
69
79
|
);
|
|
70
80
|
const filterComponents = [
|
|
71
|
-
addFilter,
|
|
72
81
|
...filters.map( ( filter ) => {
|
|
73
|
-
if ( ! filter.isVisible
|
|
82
|
+
if ( ! filter.isVisible ) {
|
|
74
83
|
return null;
|
|
75
84
|
}
|
|
76
85
|
|
|
@@ -80,15 +89,19 @@ const Filters = memo( function Filters( { fields, view, onChangeView } ) {
|
|
|
80
89
|
filter={ filter }
|
|
81
90
|
view={ view }
|
|
82
91
|
onChangeView={ onChangeView }
|
|
92
|
+
addFilterRef={ addFilterRef }
|
|
93
|
+
openedFilter={ openedFilter }
|
|
83
94
|
/>
|
|
84
95
|
);
|
|
85
96
|
} ),
|
|
97
|
+
addFilter,
|
|
86
98
|
];
|
|
87
99
|
|
|
88
|
-
if ( filterComponents.length > 1
|
|
100
|
+
if ( filterComponents.length > 1 ) {
|
|
89
101
|
filterComponents.push(
|
|
90
102
|
<ResetFilters
|
|
91
103
|
key="reset-filters"
|
|
104
|
+
filters={ filters }
|
|
92
105
|
view={ view }
|
|
93
106
|
onChangeView={ onChangeView }
|
|
94
107
|
/>
|
package/src/reset-filters.js
CHANGED
|
@@ -4,10 +4,20 @@
|
|
|
4
4
|
import { Button } from '@wordpress/components';
|
|
5
5
|
import { __ } from '@wordpress/i18n';
|
|
6
6
|
|
|
7
|
-
export default function ResetFilter( { view, onChangeView } ) {
|
|
7
|
+
export default function ResetFilter( { filters, view, onChangeView } ) {
|
|
8
|
+
const isPrimary = ( field ) =>
|
|
9
|
+
filters.some(
|
|
10
|
+
( _filter ) => _filter.field === field && _filter.isPrimary
|
|
11
|
+
);
|
|
12
|
+
const isDisabled =
|
|
13
|
+
! view.search &&
|
|
14
|
+
! view.filters?.some(
|
|
15
|
+
( _filter ) =>
|
|
16
|
+
_filter.value !== undefined || ! isPrimary( _filter.field )
|
|
17
|
+
);
|
|
8
18
|
return (
|
|
9
19
|
<Button
|
|
10
|
-
disabled={
|
|
20
|
+
disabled={ isDisabled }
|
|
11
21
|
__experimentalIsFocusable
|
|
12
22
|
size="compact"
|
|
13
23
|
variant="tertiary"
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
// eslint-disable-next-line no-restricted-imports
|
|
5
|
+
import * as Ariakit from '@ariakit/react';
|
|
6
|
+
import removeAccents from 'remove-accents';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* WordPress dependencies
|
|
10
|
+
*/
|
|
11
|
+
import { __ } from '@wordpress/i18n';
|
|
12
|
+
import { useState, useMemo, useDeferredValue } from '@wordpress/element';
|
|
13
|
+
import { VisuallyHidden, Icon } from '@wordpress/components';
|
|
14
|
+
import { search } from '@wordpress/icons';
|
|
15
|
+
import { SVG, Circle } from '@wordpress/primitives';
|
|
16
|
+
|
|
17
|
+
const radioCheck = (
|
|
18
|
+
<SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
19
|
+
<Circle cx={ 12 } cy={ 12 } r={ 3 }></Circle>
|
|
20
|
+
</SVG>
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
function normalizeSearchInput( input = '' ) {
|
|
24
|
+
return removeAccents( input.trim().toLowerCase() );
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function SearchWidget( { filter, view, onChangeView } ) {
|
|
28
|
+
const [ searchValue, setSearchValue ] = useState( '' );
|
|
29
|
+
const deferredSearchValue = useDeferredValue( searchValue );
|
|
30
|
+
const selectedFilter = view.filters.find(
|
|
31
|
+
( _filter ) => _filter.field === filter.field
|
|
32
|
+
);
|
|
33
|
+
const selectedValues = selectedFilter?.value;
|
|
34
|
+
const matches = useMemo( () => {
|
|
35
|
+
const normalizedSearch = normalizeSearchInput( deferredSearchValue );
|
|
36
|
+
return filter.elements.filter( ( item ) =>
|
|
37
|
+
normalizeSearchInput( item.label ).includes( normalizedSearch )
|
|
38
|
+
);
|
|
39
|
+
}, [ filter.elements, deferredSearchValue ] );
|
|
40
|
+
return (
|
|
41
|
+
<Ariakit.ComboboxProvider
|
|
42
|
+
value={ searchValue }
|
|
43
|
+
setSelectedValue={ ( value ) => {
|
|
44
|
+
const currentFilter = view.filters.find(
|
|
45
|
+
( _filter ) => _filter.field === filter.field
|
|
46
|
+
);
|
|
47
|
+
const newFilters = currentFilter
|
|
48
|
+
? [
|
|
49
|
+
...view.filters.map( ( _filter ) => {
|
|
50
|
+
if ( _filter.field === filter.field ) {
|
|
51
|
+
return {
|
|
52
|
+
..._filter,
|
|
53
|
+
operator:
|
|
54
|
+
currentFilter.operator ||
|
|
55
|
+
filter.operators[ 0 ],
|
|
56
|
+
value,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return _filter;
|
|
60
|
+
} ),
|
|
61
|
+
]
|
|
62
|
+
: [
|
|
63
|
+
...view.filters,
|
|
64
|
+
{
|
|
65
|
+
field: filter.field,
|
|
66
|
+
operator: filter.operators[ 0 ],
|
|
67
|
+
value,
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
onChangeView( {
|
|
71
|
+
...view,
|
|
72
|
+
page: 1,
|
|
73
|
+
filters: newFilters,
|
|
74
|
+
} );
|
|
75
|
+
} }
|
|
76
|
+
setValue={ setSearchValue }
|
|
77
|
+
>
|
|
78
|
+
<div className="dataviews-search-widget-filter-combobox__wrapper">
|
|
79
|
+
<Ariakit.ComboboxLabel render={ <VisuallyHidden /> }>
|
|
80
|
+
{ __( 'Search items' ) }
|
|
81
|
+
</Ariakit.ComboboxLabel>
|
|
82
|
+
<Ariakit.Combobox
|
|
83
|
+
autoSelect="always"
|
|
84
|
+
placeholder={ __( 'Search' ) }
|
|
85
|
+
className="dataviews-search-widget-filter-combobox__input"
|
|
86
|
+
/>
|
|
87
|
+
<div className="dataviews-search-widget-filter-combobox__icon">
|
|
88
|
+
<Icon icon={ search } />
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
<Ariakit.ComboboxList
|
|
92
|
+
className="dataviews-search-widget-filter-combobox-list"
|
|
93
|
+
alwaysVisible
|
|
94
|
+
>
|
|
95
|
+
{ matches.map( ( element ) => {
|
|
96
|
+
return (
|
|
97
|
+
<Ariakit.ComboboxItem
|
|
98
|
+
key={ element.value }
|
|
99
|
+
value={ element.value }
|
|
100
|
+
className="dataviews-search-widget-filter-combobox-item"
|
|
101
|
+
hideOnClick={ false }
|
|
102
|
+
setValueOnClick={ false }
|
|
103
|
+
focusOnHover
|
|
104
|
+
>
|
|
105
|
+
<span className="dataviews-search-widget-filter-combobox-item-check">
|
|
106
|
+
{ selectedValues === element.value && (
|
|
107
|
+
<Icon icon={ radioCheck } />
|
|
108
|
+
) }
|
|
109
|
+
</span>
|
|
110
|
+
<span>
|
|
111
|
+
<Ariakit.ComboboxItemValue
|
|
112
|
+
className="dataviews-search-widget-filter-combobox-item-value"
|
|
113
|
+
value={ element.label }
|
|
114
|
+
/>
|
|
115
|
+
{ !! element.description && (
|
|
116
|
+
<span className="dataviews-search-widget-filter-combobox-item-description">
|
|
117
|
+
{ element.description }
|
|
118
|
+
</span>
|
|
119
|
+
) }
|
|
120
|
+
</span>
|
|
121
|
+
</Ariakit.ComboboxItem>
|
|
122
|
+
);
|
|
123
|
+
} ) }
|
|
124
|
+
{ ! matches.length && <p>{ __( 'No results found' ) }</p> }
|
|
125
|
+
</Ariakit.ComboboxList>
|
|
126
|
+
</Ariakit.ComboboxProvider>
|
|
127
|
+
);
|
|
128
|
+
}
|
package/src/search.js
CHANGED
|
@@ -24,7 +24,7 @@ const Search = memo( function Search( { label, view, onChangeView } ) {
|
|
|
24
24
|
search: debouncedSearch,
|
|
25
25
|
} );
|
|
26
26
|
}, [ debouncedSearch ] );
|
|
27
|
-
const searchLabel = label || __( '
|
|
27
|
+
const searchLabel = label || __( 'Search' );
|
|
28
28
|
return (
|
|
29
29
|
<SearchControl
|
|
30
30
|
__nextHasNoMarginBottom
|