@wordpress/dataviews 0.6.0 → 0.8.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 (86) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +48 -15
  3. package/build/add-filter.js +0 -2
  4. package/build/add-filter.js.map +1 -1
  5. package/build/bulk-actions.js +41 -3
  6. package/build/bulk-actions.js.map +1 -1
  7. package/build/constants.js +28 -7
  8. package/build/constants.js.map +1 -1
  9. package/build/dataviews.js +34 -21
  10. package/build/dataviews.js.map +1 -1
  11. package/build/filter-summary.js +33 -12
  12. package/build/filter-summary.js.map +1 -1
  13. package/build/filters.js +10 -2
  14. package/build/filters.js.map +1 -1
  15. package/build/item-actions.js +20 -39
  16. package/build/item-actions.js.map +1 -1
  17. package/build/pagination.js +4 -3
  18. package/build/pagination.js.map +1 -1
  19. package/build/reset-filters.js +2 -1
  20. package/build/reset-filters.js.map +1 -1
  21. package/build/search-widget.js +117 -8
  22. package/build/search-widget.js.map +1 -1
  23. package/build/single-selection-checkbox.js +7 -2
  24. package/build/single-selection-checkbox.js.map +1 -1
  25. package/build/utils.js +24 -2
  26. package/build/utils.js.map +1 -1
  27. package/build/view-actions.js.map +1 -1
  28. package/build/view-grid.js +12 -13
  29. package/build/view-grid.js.map +1 -1
  30. package/build/view-list.js +1 -1
  31. package/build/view-list.js.map +1 -1
  32. package/build/view-table.js +111 -47
  33. package/build/view-table.js.map +1 -1
  34. package/build-module/add-filter.js +0 -2
  35. package/build-module/add-filter.js.map +1 -1
  36. package/build-module/bulk-actions.js +40 -4
  37. package/build-module/bulk-actions.js.map +1 -1
  38. package/build-module/constants.js +27 -6
  39. package/build-module/constants.js.map +1 -1
  40. package/build-module/dataviews.js +35 -22
  41. package/build-module/dataviews.js.map +1 -1
  42. package/build-module/filter-summary.js +34 -13
  43. package/build-module/filter-summary.js.map +1 -1
  44. package/build-module/filters.js +11 -3
  45. package/build-module/filters.js.map +1 -1
  46. package/build-module/item-actions.js +20 -39
  47. package/build-module/item-actions.js.map +1 -1
  48. package/build-module/pagination.js +4 -3
  49. package/build-module/pagination.js.map +1 -1
  50. package/build-module/reset-filters.js +2 -1
  51. package/build-module/reset-filters.js.map +1 -1
  52. package/build-module/search-widget.js +120 -11
  53. package/build-module/search-widget.js.map +1 -1
  54. package/build-module/single-selection-checkbox.js +7 -2
  55. package/build-module/single-selection-checkbox.js.map +1 -1
  56. package/build-module/utils.js +25 -3
  57. package/build-module/utils.js.map +1 -1
  58. package/build-module/view-actions.js.map +1 -1
  59. package/build-module/view-grid.js +13 -14
  60. package/build-module/view-grid.js.map +1 -1
  61. package/build-module/view-list.js +2 -2
  62. package/build-module/view-list.js.map +1 -1
  63. package/build-module/view-table.js +113 -49
  64. package/build-module/view-table.js.map +1 -1
  65. package/build-style/style-rtl.css +76 -46
  66. package/build-style/style.css +76 -46
  67. package/package.json +11 -11
  68. package/src/add-filter.js +0 -2
  69. package/src/bulk-actions.js +54 -4
  70. package/src/constants.js +35 -6
  71. package/src/dataviews.js +66 -49
  72. package/src/filter-summary.js +76 -23
  73. package/src/filters.js +16 -5
  74. package/src/item-actions.js +19 -55
  75. package/src/pagination.js +8 -3
  76. package/src/reset-filters.js +2 -1
  77. package/src/search-widget.js +182 -15
  78. package/src/single-selection-checkbox.js +7 -1
  79. package/src/stories/fixtures.js +12 -1
  80. package/src/stories/index.story.js +43 -4
  81. package/src/style.scss +108 -73
  82. package/src/utils.js +38 -4
  83. package/src/view-actions.js +1 -1
  84. package/src/view-grid.js +13 -12
  85. package/src/view-list.js +2 -1
  86. package/src/view-table.js +162 -81
@@ -8,12 +8,27 @@ import removeAccents from 'remove-accents';
8
8
  /**
9
9
  * WordPress dependencies
10
10
  */
11
- import { __ } from '@wordpress/i18n';
11
+ import { __, sprintf } from '@wordpress/i18n';
12
12
  import { useState, useMemo, useDeferredValue } from '@wordpress/element';
13
- import { VisuallyHidden, Icon } from '@wordpress/components';
14
- import { search } from '@wordpress/icons';
13
+ import {
14
+ VisuallyHidden,
15
+ Icon,
16
+ privateApis as componentsPrivateApis,
17
+ } from '@wordpress/components';
18
+ import { search, check } from '@wordpress/icons';
15
19
  import { SVG, Circle } from '@wordpress/primitives';
16
20
 
21
+ /**
22
+ * Internal dependencies
23
+ */
24
+ import { unlock } from './lock-unlock';
25
+
26
+ const {
27
+ CompositeV2: Composite,
28
+ CompositeItemV2: CompositeItem,
29
+ useCompositeStoreV2: useCompositeStore,
30
+ } = unlock( componentsPrivateApis );
31
+
17
32
  const radioCheck = (
18
33
  <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
19
34
  <Circle cx={ 12 } cy={ 12 } r={ 3 }></Circle>
@@ -24,13 +39,157 @@ function normalizeSearchInput( input = '' ) {
24
39
  return removeAccents( input.trim().toLowerCase() );
25
40
  }
26
41
 
27
- export default function SearchWidget( { filter, view, onChangeView } ) {
42
+ const getCurrentValue = ( filterDefinition, currentFilter ) => {
43
+ if ( filterDefinition.singleSelection ) {
44
+ return currentFilter?.value;
45
+ }
46
+
47
+ if ( Array.isArray( currentFilter?.value ) ) {
48
+ return currentFilter.value;
49
+ }
50
+
51
+ if ( ! Array.isArray( currentFilter?.value ) && !! currentFilter?.value ) {
52
+ return [ currentFilter.value ];
53
+ }
54
+
55
+ return [];
56
+ };
57
+
58
+ const getNewValue = ( filterDefinition, currentFilter, value ) => {
59
+ if ( filterDefinition.singleSelection ) {
60
+ return value;
61
+ }
62
+
63
+ if ( Array.isArray( currentFilter?.value ) ) {
64
+ return currentFilter.value.includes( value )
65
+ ? currentFilter.value.filter( ( v ) => v !== value )
66
+ : [ ...currentFilter.value, value ];
67
+ }
68
+
69
+ return [ value ];
70
+ };
71
+
72
+ function ListBox( { view, filter, onChangeView } ) {
73
+ const compositeStore = useCompositeStore( {
74
+ virtualFocus: true,
75
+ focusLoop: true,
76
+ // When we have no or just one operators, we can set the first item as active.
77
+ // We do that by passing `undefined` to `defaultActiveId`. Otherwise, we set it to `null`,
78
+ // so the first item is not selected, since the focus is on the operators control.
79
+ defaultActiveId: filter.operators?.length === 1 ? undefined : null,
80
+ } );
81
+ const currentFilter = view.filters.find(
82
+ ( f ) => f.field === filter.field
83
+ );
84
+ const currentValue = getCurrentValue( filter, currentFilter );
85
+ return (
86
+ <Composite
87
+ store={ compositeStore }
88
+ role="listbox"
89
+ className="dataviews-search-widget-listbox"
90
+ aria-label={ sprintf(
91
+ /* translators: List of items for a filter. 1: Filter name. e.g.: "List of: Author". */
92
+ __( 'List of: %1$s' ),
93
+ filter.name
94
+ ) }
95
+ onFocusVisible={ () => {
96
+ if ( ! compositeStore.getState().activeId ) {
97
+ compositeStore.move( compositeStore.first() );
98
+ }
99
+ } }
100
+ render={ <Ariakit.CompositeTypeahead store={ compositeStore } /> }
101
+ >
102
+ { filter.elements.map( ( element ) => (
103
+ <Ariakit.CompositeHover
104
+ store={ compositeStore }
105
+ key={ element.value }
106
+ render={
107
+ <CompositeItem
108
+ render={
109
+ <div
110
+ aria-label={ element.label }
111
+ role="option"
112
+ className="dataviews-search-widget-listitem"
113
+ />
114
+ }
115
+ onClick={ () => {
116
+ const newFilters = currentFilter
117
+ ? [
118
+ ...view.filters.map(
119
+ ( _filter ) => {
120
+ if (
121
+ _filter.field ===
122
+ filter.field
123
+ ) {
124
+ return {
125
+ ..._filter,
126
+ operator:
127
+ currentFilter.operator ||
128
+ filter
129
+ .operators[ 0 ],
130
+ value: getNewValue(
131
+ filter,
132
+ currentFilter,
133
+ element.value
134
+ ),
135
+ };
136
+ }
137
+ return _filter;
138
+ }
139
+ ),
140
+ ]
141
+ : [
142
+ ...view.filters,
143
+ {
144
+ field: filter.field,
145
+ operator: filter.operators[ 0 ],
146
+ value: getNewValue(
147
+ filter,
148
+ currentFilter,
149
+ element.value
150
+ ),
151
+ },
152
+ ];
153
+ onChangeView( {
154
+ ...view,
155
+ page: 1,
156
+ filters: newFilters,
157
+ } );
158
+ } }
159
+ />
160
+ }
161
+ >
162
+ <span className="dataviews-search-widget-listitem-check">
163
+ { filter.singleSelection &&
164
+ currentValue === element.value && (
165
+ <Icon icon={ radioCheck } />
166
+ ) }
167
+ { ! filter.singleSelection &&
168
+ currentValue.includes( element.value ) && (
169
+ <Icon icon={ check } />
170
+ ) }
171
+ </span>
172
+ <span>
173
+ { element.label }
174
+ { !! element.description && (
175
+ <span className="dataviews-search-widget-listitem-description">
176
+ { element.description }
177
+ </span>
178
+ ) }
179
+ </span>
180
+ </Ariakit.CompositeHover>
181
+ ) ) }
182
+ </Composite>
183
+ );
184
+ }
185
+
186
+ function ComboboxList( { view, filter, onChangeView } ) {
28
187
  const [ searchValue, setSearchValue ] = useState( '' );
29
188
  const deferredSearchValue = useDeferredValue( searchValue );
30
- const selectedFilter = view.filters.find(
189
+ const currentFilter = view.filters.find(
31
190
  ( _filter ) => _filter.field === filter.field
32
191
  );
33
- const selectedValues = selectedFilter?.value;
192
+ const currentValue = getCurrentValue( filter, currentFilter );
34
193
  const matches = useMemo( () => {
35
194
  const normalizedSearch = normalizeSearchInput( deferredSearchValue );
36
195
  return filter.elements.filter( ( item ) =>
@@ -40,10 +199,8 @@ export default function SearchWidget( { filter, view, onChangeView } ) {
40
199
  return (
41
200
  <Ariakit.ComboboxProvider
42
201
  value={ searchValue }
202
+ selectedValue={ currentValue }
43
203
  setSelectedValue={ ( value ) => {
44
- const currentFilter = view.filters.find(
45
- ( _filter ) => _filter.field === filter.field
46
- );
47
204
  const newFilters = currentFilter
48
205
  ? [
49
206
  ...view.filters.map( ( _filter ) => {
@@ -97,15 +254,20 @@ export default function SearchWidget( { filter, view, onChangeView } ) {
97
254
  <Ariakit.ComboboxItem
98
255
  key={ element.value }
99
256
  value={ element.value }
100
- className="dataviews-search-widget-filter-combobox-item"
257
+ className="dataviews-search-widget-listitem"
101
258
  hideOnClick={ false }
102
259
  setValueOnClick={ false }
103
260
  focusOnHover
104
261
  >
105
- <span className="dataviews-search-widget-filter-combobox-item-check">
106
- { selectedValues === element.value && (
107
- <Icon icon={ radioCheck } />
108
- ) }
262
+ <span className="dataviews-search-widget-listitem-check">
263
+ { filter.singleSelection &&
264
+ currentValue === element.value && (
265
+ <Icon icon={ radioCheck } />
266
+ ) }
267
+ { ! filter.singleSelection &&
268
+ currentValue.includes( element.value ) && (
269
+ <Icon icon={ check } />
270
+ ) }
109
271
  </span>
110
272
  <span>
111
273
  <Ariakit.ComboboxItemValue
@@ -113,7 +275,7 @@ export default function SearchWidget( { filter, view, onChangeView } ) {
113
275
  value={ element.label }
114
276
  />
115
277
  { !! element.description && (
116
- <span className="dataviews-search-widget-filter-combobox-item-description">
278
+ <span className="dataviews-search-widget-listitem-description">
117
279
  { element.description }
118
280
  </span>
119
281
  ) }
@@ -126,3 +288,8 @@ export default function SearchWidget( { filter, view, onChangeView } ) {
126
288
  </Ariakit.ComboboxProvider>
127
289
  );
128
290
  }
291
+
292
+ export default function SearchWidget( props ) {
293
+ const Widget = props.filter.elements.length > 10 ? ComboboxList : ListBox;
294
+ return <Widget { ...props } />;
295
+ }
@@ -11,6 +11,7 @@ export default function SingleSelectionCheckbox( {
11
11
  data,
12
12
  getItemId,
13
13
  primaryField,
14
+ disabled,
14
15
  } ) {
15
16
  const id = getItemId( item );
16
17
  const isSelected = selection.includes( id );
@@ -31,9 +32,14 @@ export default function SingleSelectionCheckbox( {
31
32
  <CheckboxControl
32
33
  className="dataviews-view-table-selection-checkbox"
33
34
  __nextHasNoMarginBottom
34
- checked={ isSelected }
35
35
  label={ selectionLabel }
36
+ aria-disabled={ disabled }
37
+ checked={ isSelected }
36
38
  onChange={ () => {
39
+ if ( disabled ) {
40
+ return;
41
+ }
42
+
37
43
  if ( ! isSelected ) {
38
44
  onSelectionChange(
39
45
  data.filter( ( _item ) => {
@@ -20,66 +20,77 @@ export const data = [
20
20
  title: 'Apollo',
21
21
  description: 'Apollo description',
22
22
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
23
+ type: 'Not a planet',
23
24
  },
24
25
  {
25
26
  id: 2,
26
27
  title: 'Space',
27
28
  description: 'Space description',
28
29
  image: 'https://live.staticflickr.com/5678/21911065441_92e2d44708_b.jpg',
30
+ type: 'Not a planet',
29
31
  },
30
32
  {
31
33
  id: 3,
32
34
  title: 'NASA',
33
35
  description: 'NASA photo',
34
36
  image: 'https://live.staticflickr.com/742/21712365770_8f70a2c91e_b.jpg',
37
+ type: 'Not a planet',
35
38
  },
36
39
  {
37
40
  id: 4,
38
41
  title: 'Neptune',
39
42
  description: 'Neptune description',
40
43
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
44
+ type: 'Ice giant',
41
45
  },
42
46
  {
43
47
  id: 5,
44
48
  title: 'Mercury',
45
49
  description: 'Mercury description',
46
50
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
51
+ type: 'Terrestrial',
47
52
  },
48
53
  {
49
54
  id: 6,
50
55
  title: 'Venus',
51
56
  description: 'Venus description',
52
57
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
58
+ type: 'Terrestrial',
53
59
  },
54
60
  {
55
61
  id: 7,
56
62
  title: 'Earth',
57
63
  description: 'Earth description',
58
64
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
65
+ type: 'Terrestrial',
59
66
  },
60
67
  {
61
68
  id: 8,
62
69
  title: 'Mars',
63
70
  description: 'Mars description',
64
71
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
72
+ type: 'Terrestrial',
65
73
  },
66
74
  {
67
75
  id: 9,
68
76
  title: 'Jupiter',
69
77
  description: 'Jupiter description',
70
78
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
79
+ type: 'Gas giant',
71
80
  },
72
81
  {
73
82
  id: 10,
74
83
  title: 'Saturn',
75
84
  description: 'Saturn description',
76
85
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
86
+ type: 'Gas giant',
77
87
  },
78
88
  {
79
89
  id: 11,
80
90
  title: 'Uranus',
81
91
  description: 'Uranus description',
82
92
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
93
+ type: 'Ice giant',
83
94
  },
84
95
  ];
85
96
 
@@ -88,7 +99,7 @@ export const DEFAULT_VIEW = {
88
99
  search: '',
89
100
  page: 1,
90
101
  perPage: 10,
91
- hiddenFields: [ 'image' ],
102
+ hiddenFields: [ 'image', 'type' ],
92
103
  layout: {},
93
104
  filters: [],
94
105
  };
@@ -8,10 +8,15 @@ import { useState, useMemo, useCallback } from '@wordpress/element';
8
8
  */
9
9
  import { DataViews } from '../index';
10
10
  import { DEFAULT_VIEW, actions, data } from './fixtures';
11
- import { LAYOUT_GRID, LAYOUT_TABLE } from '../constants';
11
+ import {
12
+ LAYOUT_GRID,
13
+ LAYOUT_TABLE,
14
+ OPERATOR_IS_NONE,
15
+ OPERATOR_IS_ANY,
16
+ } from '../constants';
12
17
 
13
18
  const meta = {
14
- title: 'DataViews (Experimental)/DataViews',
19
+ title: 'DataViews/DataViews',
15
20
  component: DataViews,
16
21
  };
17
22
  export default meta;
@@ -45,14 +50,25 @@ const fields = [
45
50
  {
46
51
  header: 'Title',
47
52
  id: 'title',
48
- getValue: ( { item } ) => item.title,
49
53
  maxWidth: 400,
50
54
  enableHiding: false,
51
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
+ },
52
69
  {
53
70
  header: 'Description',
54
71
  id: 'description',
55
- getValue: ( { item } ) => item.description,
56
72
  maxWidth: 200,
57
73
  enableSorting: false,
58
74
  },
@@ -72,6 +88,29 @@ export const Default = ( props ) => {
72
88
  ].some( ( field ) => field.includes( normalizedSearch ) );
73
89
  } );
74
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
+
75
114
  // Handle sorting.
76
115
  if ( view.sort ) {
77
116
  const stringSortingFields = [ 'title' ];