@wordpress/dataviews 0.8.0 → 1.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.
Files changed (92) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +3 -13
  3. package/build/add-filter.js.map +1 -1
  4. package/build/bulk-actions.js.map +1 -1
  5. package/build/constants.js +1 -4
  6. package/build/constants.js.map +1 -1
  7. package/build/dataviews.js +3 -17
  8. package/build/dataviews.js.map +1 -1
  9. package/build/dropdown-menu-helper.js.map +1 -1
  10. package/build/filter-and-sort-data-view.js +147 -0
  11. package/build/filter-and-sort-data-view.js.map +1 -0
  12. package/build/filter-summary.js +4 -2
  13. package/build/filter-summary.js.map +1 -1
  14. package/build/filters.js +11 -17
  15. package/build/filters.js.map +1 -1
  16. package/build/index.js +3 -9
  17. package/build/index.js.map +1 -1
  18. package/build/item-actions.js.map +1 -1
  19. package/build/lock-unlock.js.map +1 -1
  20. package/build/normalize-fields.js +25 -0
  21. package/build/normalize-fields.js.map +1 -0
  22. package/build/pagination.js.map +1 -1
  23. package/build/reset-filters.js.map +1 -1
  24. package/build/search-widget.js +5 -4
  25. package/build/search-widget.js.map +1 -1
  26. package/build/search.js.map +1 -1
  27. package/build/single-selection-checkbox.js +1 -1
  28. package/build/single-selection-checkbox.js.map +1 -1
  29. package/build/utils.js +1 -65
  30. package/build/utils.js.map +1 -1
  31. package/build/view-actions.js.map +1 -1
  32. package/build/view-grid.js +57 -19
  33. package/build/view-grid.js.map +1 -1
  34. package/build/view-list.js +112 -66
  35. package/build/view-list.js.map +1 -1
  36. package/build/view-table.js +32 -24
  37. package/build/view-table.js.map +1 -1
  38. package/build-module/add-filter.js.map +1 -1
  39. package/build-module/bulk-actions.js.map +1 -1
  40. package/build-module/constants.js +0 -3
  41. package/build-module/constants.js.map +1 -1
  42. package/build-module/dataviews.js +3 -17
  43. package/build-module/dataviews.js.map +1 -1
  44. package/build-module/dropdown-menu-helper.js.map +1 -1
  45. package/build-module/filter-and-sort-data-view.js +139 -0
  46. package/build-module/filter-and-sort-data-view.js.map +1 -0
  47. package/build-module/filter-summary.js +3 -2
  48. package/build-module/filter-summary.js.map +1 -1
  49. package/build-module/filters.js +12 -18
  50. package/build-module/filters.js.map +1 -1
  51. package/build-module/index.js +1 -1
  52. package/build-module/index.js.map +1 -1
  53. package/build-module/item-actions.js.map +1 -1
  54. package/build-module/lock-unlock.js.map +1 -1
  55. package/build-module/normalize-fields.js +19 -0
  56. package/build-module/normalize-fields.js.map +1 -0
  57. package/build-module/pagination.js.map +1 -1
  58. package/build-module/reset-filters.js.map +1 -1
  59. package/build-module/search-widget.js +4 -3
  60. package/build-module/search-widget.js.map +1 -1
  61. package/build-module/search.js.map +1 -1
  62. package/build-module/single-selection-checkbox.js +1 -1
  63. package/build-module/single-selection-checkbox.js.map +1 -1
  64. package/build-module/utils.js +0 -63
  65. package/build-module/utils.js.map +1 -1
  66. package/build-module/view-actions.js.map +1 -1
  67. package/build-module/view-grid.js +58 -20
  68. package/build-module/view-grid.js.map +1 -1
  69. package/build-module/view-list.js +114 -68
  70. package/build-module/view-list.js.map +1 -1
  71. package/build-module/view-table.js +33 -25
  72. package/build-module/view-table.js.map +1 -1
  73. package/build-style/style-rtl.css +75 -39
  74. package/build-style/style.css +75 -39
  75. package/package.json +11 -11
  76. package/src/constants.js +0 -3
  77. package/src/dataviews.js +2 -16
  78. package/src/filter-and-sort-data-view.js +154 -0
  79. package/src/filter-summary.js +4 -4
  80. package/src/filters.js +20 -32
  81. package/src/index.js +1 -1
  82. package/src/normalize-fields.js +17 -0
  83. package/src/search-widget.js +4 -3
  84. package/src/single-selection-checkbox.js +1 -1
  85. package/src/stories/fixtures.js +75 -1
  86. package/src/stories/index.story.js +5 -113
  87. package/src/style.scss +89 -49
  88. package/src/test/filter-and-sort-data-view.js +276 -0
  89. package/src/utils.js +0 -52
  90. package/src/view-grid.js +97 -36
  91. package/src/view-list.js +147 -77
  92. package/src/view-table.js +36 -24
@@ -0,0 +1,154 @@
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
+
19
+ function normalizeSearchInput( input = '' ) {
20
+ return removeAccents( input.trim().toLowerCase() );
21
+ }
22
+
23
+ const EMPTY_ARRAY = [];
24
+
25
+ /**
26
+ * Applies the filtering, sorting and pagination to the raw data based on the view configuration.
27
+ *
28
+ * @param {any[]} data Raw data.
29
+ * @param {Object} view View config.
30
+ * @param {Object[]} fields Fields config.
31
+ *
32
+ * @return {Object} { data: any[], paginationInfo: { totalItems: number, totalPages: number } }
33
+ */
34
+ export function filterSortAndPaginate( data, view, fields ) {
35
+ if ( ! data ) {
36
+ return {
37
+ data: EMPTY_ARRAY,
38
+ paginationInfo: { totalItems: 0, totalPages: 0 },
39
+ };
40
+ }
41
+ const _fields = normalizeFields( fields );
42
+ let filteredData = [ ...data ];
43
+ // Handle global search.
44
+ if ( view.search ) {
45
+ const normalizedSearch = normalizeSearchInput( view.search );
46
+ filteredData = filteredData.filter( ( item ) => {
47
+ return _fields
48
+ .filter( ( field ) => field.enableGlobalSearch )
49
+ .map( ( field ) => {
50
+ return normalizeSearchInput( field.getValue( { item } ) );
51
+ } )
52
+ .some( ( field ) => field.includes( normalizedSearch ) );
53
+ } );
54
+ }
55
+
56
+ if ( view.filters.length > 0 ) {
57
+ view.filters.forEach( ( filter ) => {
58
+ const field = _fields.find(
59
+ ( _field ) => _field.id === filter.field
60
+ );
61
+ if (
62
+ filter.operator === OPERATOR_IS_ANY &&
63
+ filter?.value?.length > 0
64
+ ) {
65
+ filteredData = filteredData.filter( ( item ) => {
66
+ const fieldValue = field.getValue( { item } );
67
+ if ( Array.isArray( fieldValue ) ) {
68
+ return filter.value.some( ( filterValue ) =>
69
+ fieldValue.includes( filterValue )
70
+ );
71
+ } else if ( typeof fieldValue === 'string' ) {
72
+ return filter.value.includes( fieldValue );
73
+ }
74
+ return false;
75
+ } );
76
+ } else if (
77
+ filter.operator === OPERATOR_IS_NONE &&
78
+ filter?.value?.length > 0
79
+ ) {
80
+ filteredData = filteredData.filter( ( item ) => {
81
+ const fieldValue = field.getValue( { item } );
82
+ if ( Array.isArray( fieldValue ) ) {
83
+ return ! filter.value.some( ( filterValue ) =>
84
+ fieldValue.includes( filterValue )
85
+ );
86
+ } else if ( typeof fieldValue === 'string' ) {
87
+ return ! filter.value.includes( fieldValue );
88
+ }
89
+ return false;
90
+ } );
91
+ } else if (
92
+ filter.operator === OPERATOR_IS_ALL &&
93
+ filter?.value?.length > 0
94
+ ) {
95
+ filteredData = filteredData.filter( ( item ) => {
96
+ return filter.value.every( ( value ) => {
97
+ return field.getValue( { item } ).includes( value );
98
+ } );
99
+ } );
100
+ } else if (
101
+ filter.operator === OPERATOR_IS_NOT_ALL &&
102
+ filter?.value?.length > 0
103
+ ) {
104
+ filteredData = filteredData.filter( ( item ) => {
105
+ return filter.value.every( ( value ) => {
106
+ return ! field.getValue( { item } ).includes( value );
107
+ } );
108
+ } );
109
+ } else if ( filter.operator === OPERATOR_IS ) {
110
+ filteredData = filteredData.filter( ( item ) => {
111
+ return filter.value === field.getValue( { item } );
112
+ } );
113
+ } else if ( filter.operator === OPERATOR_IS_NOT ) {
114
+ filteredData = filteredData.filter( ( item ) => {
115
+ return filter.value !== field.getValue( { item } );
116
+ } );
117
+ }
118
+ } );
119
+ }
120
+
121
+ // Handle sorting.
122
+ if ( view.sort ) {
123
+ const fieldId = view.sort.field;
124
+ const fieldToSort = _fields.find( ( field ) => {
125
+ return field.id === fieldId;
126
+ } );
127
+ filteredData.sort( ( a, b ) => {
128
+ const valueA = fieldToSort.getValue( { item: a } ) ?? '';
129
+ const valueB = fieldToSort.getValue( { item: b } ) ?? '';
130
+ return view.sort.direction === 'asc'
131
+ ? valueA.localeCompare( valueB )
132
+ : valueB.localeCompare( valueA );
133
+ } );
134
+ }
135
+
136
+ // Handle pagination.
137
+ const hasPagination = view.page && view.perPage;
138
+ const start = hasPagination ? ( view.page - 1 ) * view.perPage : 0;
139
+ const totalItems = filteredData?.length || 0;
140
+ const totalPages = hasPagination
141
+ ? Math.ceil( totalItems / view.perPage )
142
+ : 1;
143
+ filteredData = hasPagination
144
+ ? filteredData?.slice( start, start + view.perPage )
145
+ : filteredData;
146
+
147
+ return {
148
+ data: filteredData,
149
+ paginationInfo: {
150
+ totalItems,
151
+ totalPages,
152
+ },
153
+ };
154
+ }
@@ -18,7 +18,9 @@ import {
18
18
  import { __, sprintf } from '@wordpress/i18n';
19
19
  import { useRef, createInterpolateElement } from '@wordpress/element';
20
20
  import { closeSmall } from '@wordpress/icons';
21
- import { ENTER, SPACE } from '@wordpress/keycodes';
21
+
22
+ const ENTER = 'Enter';
23
+ const SPACE = ' ';
22
24
 
23
25
  /**
24
26
  * Internal dependencies
@@ -229,9 +231,7 @@ export default function FilterSummary( {
229
231
  tabIndex={ 0 }
230
232
  onClick={ onToggle }
231
233
  onKeyDown={ ( event ) => {
232
- if (
233
- [ ENTER, SPACE ].includes( event.keyCode )
234
- ) {
234
+ if ( [ ENTER, SPACE ].includes( event.key ) ) {
235
235
  onToggle();
236
236
  event.preventDefault();
237
237
  }
package/src/filters.js CHANGED
@@ -10,12 +10,7 @@ import FilterSummary from './filter-summary';
10
10
  import AddFilter from './add-filter';
11
11
  import ResetFilters from './reset-filters';
12
12
  import { sanitizeOperators } from './utils';
13
- import {
14
- ENUMERATION_TYPE,
15
- ALL_OPERATORS,
16
- OPERATOR_IS,
17
- OPERATOR_IS_NOT,
18
- } from './constants';
13
+ import { ALL_OPERATORS, OPERATOR_IS, OPERATOR_IS_NOT } from './constants';
19
14
  import { __experimentalHStack as HStack } from '@wordpress/components';
20
15
 
21
16
  const Filters = memo( function Filters( {
@@ -28,7 +23,7 @@ const Filters = memo( function Filters( {
28
23
  const addFilterRef = useRef();
29
24
  const filters = [];
30
25
  fields.forEach( ( field ) => {
31
- if ( ! field.type ) {
26
+ if ( ! field.elements?.length ) {
32
27
  return;
33
28
  }
34
29
 
@@ -37,31 +32,24 @@ const Filters = memo( function Filters( {
37
32
  return;
38
33
  }
39
34
 
40
- switch ( field.type ) {
41
- case ENUMERATION_TYPE:
42
- if ( ! field.elements?.length ) {
43
- return;
44
- }
45
-
46
- const isPrimary = !! field.filterBy?.isPrimary;
47
- filters.push( {
48
- field: field.id,
49
- name: field.header,
50
- elements: field.elements,
51
- singleSelection: operators.some( ( op ) =>
52
- [ OPERATOR_IS, OPERATOR_IS_NOT ].includes( op )
53
- ),
54
- operators,
55
- isVisible:
56
- isPrimary ||
57
- view.filters.some(
58
- ( f ) =>
59
- f.field === field.id &&
60
- ALL_OPERATORS.includes( f.operator )
61
- ),
62
- isPrimary,
63
- } );
64
- }
35
+ const isPrimary = !! field.filterBy?.isPrimary;
36
+ filters.push( {
37
+ field: field.id,
38
+ name: field.header,
39
+ elements: field.elements,
40
+ singleSelection: operators.some( ( op ) =>
41
+ [ OPERATOR_IS, OPERATOR_IS_NOT ].includes( op )
42
+ ),
43
+ operators,
44
+ isVisible:
45
+ isPrimary ||
46
+ view.filters.some(
47
+ ( f ) =>
48
+ f.field === field.id &&
49
+ ALL_OPERATORS.includes( f.operator )
50
+ ),
51
+ isPrimary,
52
+ } );
65
53
  } );
66
54
  // Sort filters by primary property. We need the primary filters to be first.
67
55
  // Then we sort by name.
package/src/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  export { default as DataViews } from './dataviews';
2
- export { sortByTextFields, getPaginationResults } from './utils';
3
2
  export { VIEW_LAYOUTS } from './constants';
3
+ export { filterSortAndPaginate } from './filter-and-sort-data-view';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Apply default values and normalize the fields config.
3
+ *
4
+ * @param {Object[]} fields Raw Fields.
5
+ * @return {Object[]} Normalized fields.
6
+ */
7
+ export function normalizeFields( fields ) {
8
+ return fields.map( ( field ) => {
9
+ const getValue = field.getValue || ( ( { item } ) => item[ field.id ] );
10
+
11
+ return {
12
+ ...field,
13
+ getValue,
14
+ render: field.render || getValue,
15
+ };
16
+ } );
17
+ }
@@ -39,6 +39,7 @@ function normalizeSearchInput( input = '' ) {
39
39
  return removeAccents( input.trim().toLowerCase() );
40
40
  }
41
41
 
42
+ const EMPTY_ARRAY = [];
42
43
  const getCurrentValue = ( filterDefinition, currentFilter ) => {
43
44
  if ( filterDefinition.singleSelection ) {
44
45
  return currentFilter?.value;
@@ -52,7 +53,7 @@ const getCurrentValue = ( filterDefinition, currentFilter ) => {
52
53
  return [ currentFilter.value ];
53
54
  }
54
55
 
55
- return [];
56
+ return EMPTY_ARRAY;
56
57
  };
57
58
 
58
59
  const getNewValue = ( filterDefinition, currentFilter, value ) => {
@@ -73,7 +74,7 @@ function ListBox( { view, filter, onChangeView } ) {
73
74
  const compositeStore = useCompositeStore( {
74
75
  virtualFocus: true,
75
76
  focusLoop: true,
76
- // When we have no or just one operators, we can set the first item as active.
77
+ // When we have no or just one operator, we can set the first item as active.
77
78
  // We do that by passing `undefined` to `defaultActiveId`. Otherwise, we set it to `null`,
78
79
  // so the first item is not selected, since the focus is on the operators control.
79
80
  defaultActiveId: filter.operators?.length === 1 ? undefined : null,
@@ -198,7 +199,7 @@ function ComboboxList( { view, filter, onChangeView } ) {
198
199
  }, [ filter.elements, deferredSearchValue ] );
199
200
  return (
200
201
  <Ariakit.ComboboxProvider
201
- value={ searchValue }
202
+ resetValueOnSelect={ false }
202
203
  selectedValue={ currentValue }
203
204
  setSelectedValue={ ( value ) => {
204
205
  const newFilters = currentFilter
@@ -32,7 +32,7 @@ export default function SingleSelectionCheckbox( {
32
32
  <CheckboxControl
33
33
  className="dataviews-view-table-selection-checkbox"
34
34
  __nextHasNoMarginBottom
35
- label={ selectionLabel }
35
+ aria-label={ selectionLabel }
36
36
  aria-disabled={ disabled }
37
37
  checked={ isSelected }
38
38
  onChange={ () => {
@@ -21,6 +21,7 @@ export const data = [
21
21
  description: 'Apollo description',
22
22
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
23
23
  type: 'Not a planet',
24
+ categories: [ 'Space', 'NASA' ],
24
25
  },
25
26
  {
26
27
  id: 2,
@@ -28,6 +29,7 @@ export const data = [
28
29
  description: 'Space description',
29
30
  image: 'https://live.staticflickr.com/5678/21911065441_92e2d44708_b.jpg',
30
31
  type: 'Not a planet',
32
+ categories: [ 'Space' ],
31
33
  },
32
34
  {
33
35
  id: 3,
@@ -35,6 +37,7 @@ export const data = [
35
37
  description: 'NASA photo',
36
38
  image: 'https://live.staticflickr.com/742/21712365770_8f70a2c91e_b.jpg',
37
39
  type: 'Not a planet',
40
+ categories: [ 'NASA' ],
38
41
  },
39
42
  {
40
43
  id: 4,
@@ -42,6 +45,7 @@ export const data = [
42
45
  description: 'Neptune description',
43
46
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
44
47
  type: 'Ice giant',
48
+ categories: [ 'Space', 'Planet', 'Solar system' ],
45
49
  },
46
50
  {
47
51
  id: 5,
@@ -49,13 +53,15 @@ export const data = [
49
53
  description: 'Mercury description',
50
54
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
51
55
  type: 'Terrestrial',
56
+ categories: [ 'Space', 'Planet', 'Solar system' ],
52
57
  },
53
58
  {
54
59
  id: 6,
55
60
  title: 'Venus',
56
- description: 'Venus description',
61
+ description: 'La planète Vénus',
57
62
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
58
63
  type: 'Terrestrial',
64
+ categories: [ 'Space', 'Planet', 'Solar system' ],
59
65
  },
60
66
  {
61
67
  id: 7,
@@ -63,6 +69,7 @@ export const data = [
63
69
  description: 'Earth description',
64
70
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
65
71
  type: 'Terrestrial',
72
+ categories: [ 'Space', 'Planet', 'Solar system' ],
66
73
  },
67
74
  {
68
75
  id: 8,
@@ -70,6 +77,7 @@ export const data = [
70
77
  description: 'Mars description',
71
78
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
72
79
  type: 'Terrestrial',
80
+ categories: [ 'Space', 'Planet', 'Solar system' ],
73
81
  },
74
82
  {
75
83
  id: 9,
@@ -77,6 +85,7 @@ export const data = [
77
85
  description: 'Jupiter description',
78
86
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
79
87
  type: 'Gas giant',
88
+ categories: [ 'Space', 'Planet', 'Solar system' ],
80
89
  },
81
90
  {
82
91
  id: 10,
@@ -84,6 +93,7 @@ export const data = [
84
93
  description: 'Saturn description',
85
94
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
86
95
  type: 'Gas giant',
96
+ categories: [ 'Space', 'Planet', 'Solar system' ],
87
97
  },
88
98
  {
89
99
  id: 11,
@@ -91,6 +101,7 @@ export const data = [
91
101
  description: 'Uranus description',
92
102
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
93
103
  type: 'Ice giant',
104
+ categories: [ 'Space', 'Ice giant', 'Solar system' ],
94
105
  },
95
106
  ];
96
107
 
@@ -135,3 +146,66 @@ export const actions = [
135
146
  callback() {},
136
147
  },
137
148
  ];
149
+
150
+ export const fields = [
151
+ {
152
+ header: 'Image',
153
+ id: 'image',
154
+ render: ( { item } ) => {
155
+ return (
156
+ <img src={ item.image } alt="" style={ { width: '100%' } } />
157
+ );
158
+ },
159
+ width: 50,
160
+ enableSorting: false,
161
+ },
162
+ {
163
+ header: 'Title',
164
+ id: 'title',
165
+ maxWidth: 400,
166
+ enableHiding: false,
167
+ enableGlobalSearch: true,
168
+ },
169
+ {
170
+ header: 'Type',
171
+ id: 'type',
172
+ maxWidth: 400,
173
+ enableHiding: false,
174
+ type: 'enumeration',
175
+ elements: [
176
+ { value: 'Not a planet', label: 'Not a planet' },
177
+ { value: 'Ice giant', label: 'Ice giant' },
178
+ { value: 'Terrestrial', label: 'Terrestrial' },
179
+ { value: 'Gas giant', label: 'Gas giant' },
180
+ ],
181
+ },
182
+ {
183
+ header: 'Description',
184
+ id: 'description',
185
+ maxWidth: 200,
186
+ enableSorting: false,
187
+ enableGlobalSearch: true,
188
+ },
189
+ {
190
+ header: 'Categories',
191
+ id: 'categories',
192
+ type: 'enumeration',
193
+ elements: [
194
+ { value: 'Space', label: 'Space' },
195
+ { value: 'NASA', label: 'NASA' },
196
+ { value: 'Planet', label: 'Planet' },
197
+ { value: 'Solar system', label: 'Solar system' },
198
+ { value: 'Ice giant', label: 'Ice giant' },
199
+ ],
200
+ filterBy: {
201
+ operators: [ 'isAny', 'isNone', 'isAll', 'isNotAll' ],
202
+ },
203
+ getValue: ( { item } ) => {
204
+ return item.categories;
205
+ },
206
+ render: ( { item } ) => {
207
+ return item.categories.join( ',' );
208
+ },
209
+ enableSorting: false,
210
+ },
211
+ ];
@@ -7,13 +7,9 @@ import { useState, useMemo, useCallback } from '@wordpress/element';
7
7
  * Internal dependencies
8
8
  */
9
9
  import { DataViews } from '../index';
10
- import { DEFAULT_VIEW, actions, data } from './fixtures';
11
- import {
12
- LAYOUT_GRID,
13
- LAYOUT_TABLE,
14
- OPERATOR_IS_NONE,
15
- OPERATOR_IS_ANY,
16
- } from '../constants';
10
+ import { DEFAULT_VIEW, actions, data, fields } from './fixtures';
11
+ import { LAYOUT_GRID, LAYOUT_TABLE } from '../constants';
12
+ import { filterSortAndPaginate } from '../filter-and-sort-data-view';
17
13
 
18
14
  const meta = {
19
15
  title: 'DataViews/DataViews',
@@ -31,114 +27,10 @@ const defaultConfigPerViewType = {
31
27
  },
32
28
  };
33
29
 
34
- function normalizeSearchInput( input = '' ) {
35
- return input.trim().toLowerCase();
36
- }
37
-
38
- const fields = [
39
- {
40
- header: 'Image',
41
- id: 'image',
42
- render: ( { item } ) => {
43
- return (
44
- <img src={ item.image } alt="" style={ { width: '100%' } } />
45
- );
46
- },
47
- width: 50,
48
- enableSorting: false,
49
- },
50
- {
51
- header: 'Title',
52
- id: 'title',
53
- maxWidth: 400,
54
- enableHiding: false,
55
- },
56
- {
57
- header: 'Type',
58
- id: 'type',
59
- maxWidth: 400,
60
- enableHiding: false,
61
- type: 'enumeration',
62
- elements: [
63
- { value: 'Not a planet', label: 'Not a planet' },
64
- { value: 'Ice giant', label: 'Ice giant' },
65
- { value: 'Terrestrial', label: 'Terrestrial' },
66
- { value: 'Gas giant', label: 'Gas giant' },
67
- ],
68
- },
69
- {
70
- header: 'Description',
71
- id: 'description',
72
- maxWidth: 200,
73
- enableSorting: false,
74
- },
75
- ];
76
-
77
30
  export const Default = ( props ) => {
78
31
  const [ view, setView ] = useState( DEFAULT_VIEW );
79
- const { shownData, paginationInfo } = useMemo( () => {
80
- let filteredData = [ ...data ];
81
- // Handle global search.
82
- if ( view.search ) {
83
- const normalizedSearch = normalizeSearchInput( view.search );
84
- filteredData = filteredData.filter( ( item ) => {
85
- return [
86
- normalizeSearchInput( item.title ),
87
- normalizeSearchInput( item.description ),
88
- ].some( ( field ) => field.includes( normalizedSearch ) );
89
- } );
90
- }
91
-
92
- if ( view.filters.length > 0 ) {
93
- view.filters.forEach( ( filter ) => {
94
- if (
95
- filter.field === 'type' &&
96
- filter.operator === OPERATOR_IS_ANY &&
97
- filter?.value?.length > 0
98
- ) {
99
- filteredData = filteredData.filter( ( item ) => {
100
- return filter.value.includes( item.type );
101
- } );
102
- } else if (
103
- filter.field === 'type' &&
104
- filter.operator === OPERATOR_IS_NONE &&
105
- filter?.value?.length > 0
106
- ) {
107
- filteredData = filteredData.filter( ( item ) => {
108
- return ! filter.value.includes( item.type );
109
- } );
110
- }
111
- } );
112
- }
113
-
114
- // Handle sorting.
115
- if ( view.sort ) {
116
- const stringSortingFields = [ 'title' ];
117
- const fieldId = view.sort.field;
118
- if ( stringSortingFields.includes( fieldId ) ) {
119
- const fieldToSort = fields.find( ( field ) => {
120
- return field.id === fieldId;
121
- } );
122
- filteredData.sort( ( a, b ) => {
123
- const valueA = fieldToSort.getValue( { item: a } ) ?? '';
124
- const valueB = fieldToSort.getValue( { item: b } ) ?? '';
125
- return view.sort.direction === 'asc'
126
- ? valueA.localeCompare( valueB )
127
- : valueB.localeCompare( valueA );
128
- } );
129
- }
130
- }
131
- // Handle pagination.
132
- const start = ( view.page - 1 ) * view.perPage;
133
- const totalItems = filteredData?.length || 0;
134
- filteredData = filteredData?.slice( start, start + view.perPage );
135
- return {
136
- shownData: filteredData,
137
- paginationInfo: {
138
- totalItems,
139
- totalPages: Math.ceil( totalItems / view.perPage ),
140
- },
141
- };
32
+ const { data: shownData, paginationInfo } = useMemo( () => {
33
+ return filterSortAndPaginate( data, view, fields );
142
34
  }, [ view ] );
143
35
  const onChangeView = useCallback(
144
36
  ( newView ) => {