@wordpress/dataviews 0.2.0 → 0.3.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 (87) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/LICENSE.md +1 -1
  3. package/README.md +30 -6
  4. package/build/add-filter.js +109 -49
  5. package/build/add-filter.js.map +1 -1
  6. package/build/constants.js +24 -2
  7. package/build/constants.js.map +1 -1
  8. package/build/dataviews.js +12 -9
  9. package/build/dataviews.js.map +1 -1
  10. package/build/dropdown-menu-helper.js +72 -0
  11. package/build/dropdown-menu-helper.js.map +1 -0
  12. package/build/filter-summary.js +43 -54
  13. package/build/filter-summary.js.map +1 -1
  14. package/build/filters.js +27 -17
  15. package/build/filters.js.map +1 -1
  16. package/build/index.js +13 -0
  17. package/build/index.js.map +1 -1
  18. package/build/item-actions.js +12 -12
  19. package/build/item-actions.js.map +1 -1
  20. package/build/pagination.js +31 -65
  21. package/build/pagination.js.map +1 -1
  22. package/build/reset-filters.js +8 -8
  23. package/build/reset-filters.js.map +1 -1
  24. package/build/search.js +8 -6
  25. package/build/search.js.map +1 -1
  26. package/build/utils.js +71 -0
  27. package/build/utils.js.map +1 -0
  28. package/build/view-actions.js +72 -95
  29. package/build/view-actions.js.map +1 -1
  30. package/build/view-grid.js +4 -6
  31. package/build/view-grid.js.map +1 -1
  32. package/build/view-list.js +26 -13
  33. package/build/view-list.js.map +1 -1
  34. package/build/view-table.js +153 -154
  35. package/build/view-table.js.map +1 -1
  36. package/build-module/add-filter.js +113 -53
  37. package/build-module/add-filter.js.map +1 -1
  38. package/build-module/constants.js +20 -0
  39. package/build-module/constants.js.map +1 -1
  40. package/build-module/dataviews.js +13 -10
  41. package/build-module/dataviews.js.map +1 -1
  42. package/build-module/dropdown-menu-helper.js +64 -0
  43. package/build-module/dropdown-menu-helper.js.map +1 -0
  44. package/build-module/filter-summary.js +45 -56
  45. package/build-module/filter-summary.js.map +1 -1
  46. package/build-module/filters.js +26 -17
  47. package/build-module/filters.js.map +1 -1
  48. package/build-module/index.js +1 -0
  49. package/build-module/index.js.map +1 -1
  50. package/build-module/item-actions.js +12 -12
  51. package/build-module/item-actions.js.map +1 -1
  52. package/build-module/pagination.js +35 -69
  53. package/build-module/pagination.js.map +1 -1
  54. package/build-module/reset-filters.js +6 -6
  55. package/build-module/reset-filters.js.map +1 -1
  56. package/build-module/search.js +7 -6
  57. package/build-module/search.js.map +1 -1
  58. package/build-module/utils.js +63 -0
  59. package/build-module/utils.js.map +1 -0
  60. package/build-module/view-actions.js +73 -97
  61. package/build-module/view-actions.js.map +1 -1
  62. package/build-module/view-grid.js +4 -6
  63. package/build-module/view-grid.js.map +1 -1
  64. package/build-module/view-list.js +27 -14
  65. package/build-module/view-list.js.map +1 -1
  66. package/build-module/view-table.js +156 -157
  67. package/build-module/view-table.js.map +1 -1
  68. package/build-style/style-rtl.css +180 -70
  69. package/build-style/style.css +180 -70
  70. package/package.json +11 -10
  71. package/src/add-filter.js +227 -68
  72. package/src/constants.js +16 -0
  73. package/src/dataviews.js +19 -12
  74. package/src/dropdown-menu-helper.js +61 -0
  75. package/src/filter-summary.js +70 -103
  76. package/src/filters.js +41 -24
  77. package/src/index.js +1 -0
  78. package/src/item-actions.js +30 -25
  79. package/src/pagination.js +75 -123
  80. package/src/reset-filters.js +5 -5
  81. package/src/search.js +8 -6
  82. package/src/style.scss +182 -48
  83. package/src/utils.js +51 -0
  84. package/src/view-actions.js +113 -114
  85. package/src/view-grid.js +4 -4
  86. package/src/view-list.js +42 -28
  87. package/src/view-table.js +280 -238
@@ -3,24 +3,24 @@
3
3
  */
4
4
  import {
5
5
  Button,
6
- Icon,
7
6
  privateApis as componentsPrivateApis,
8
7
  } from '@wordpress/components';
9
- import { chevronRightSmall, check, arrowUp, arrowDown } from '@wordpress/icons';
10
8
  import { __ } from '@wordpress/i18n';
9
+ import { memo } from '@wordpress/element';
11
10
 
12
11
  /**
13
12
  * Internal dependencies
14
13
  */
15
14
  import { unlock } from './lock-unlock';
16
- import { VIEW_LAYOUTS, LAYOUT_TABLE } from './constants';
15
+ import { VIEW_LAYOUTS, LAYOUT_TABLE, SORTING_DIRECTIONS } from './constants';
17
16
 
18
17
  const {
19
18
  DropdownMenuV2: DropdownMenu,
20
19
  DropdownMenuGroupV2: DropdownMenuGroup,
21
20
  DropdownMenuItemV2: DropdownMenuItem,
22
- DropdownSubMenuV2: DropdownSubMenu,
23
- DropdownSubMenuTriggerV2: DropdownSubMenuTrigger,
21
+ DropdownMenuRadioItemV2: DropdownMenuRadioItem,
22
+ DropdownMenuCheckboxItemV2: DropdownMenuCheckboxItem,
23
+ DropdownMenuItemLabelV2: DropdownMenuItemLabel,
24
24
  } = unlock( componentsPrivateApis );
25
25
 
26
26
  function ViewTypeMenu( { view, onChangeView, supportedLayouts } ) {
@@ -35,86 +35,81 @@ function ViewTypeMenu( { view, onChangeView, supportedLayouts } ) {
35
35
  }
36
36
  const activeView = _availableViews.find( ( v ) => view.type === v.type );
37
37
  return (
38
- <DropdownSubMenu
38
+ <DropdownMenu
39
39
  trigger={
40
- <DropdownSubMenuTrigger
40
+ <DropdownMenuItem
41
41
  suffix={
42
- <>
43
- { activeView.label }
44
- <Icon icon={ chevronRightSmall } />
45
- </>
42
+ <span aria-hidden="true">{ activeView.label }</span>
46
43
  }
47
44
  >
48
- { __( 'Layout' ) }
49
- </DropdownSubMenuTrigger>
45
+ <DropdownMenuItemLabel>
46
+ { __( 'Layout' ) }
47
+ </DropdownMenuItemLabel>
48
+ </DropdownMenuItem>
50
49
  }
51
50
  >
52
51
  { _availableViews.map( ( availableView ) => {
53
52
  return (
54
- <DropdownMenuItem
53
+ <DropdownMenuRadioItem
55
54
  key={ availableView.type }
56
- role="menuitemradio"
57
- aria-checked={ availableView.id === view.type }
58
- prefix={
59
- availableView.type === view.type && (
60
- <Icon icon={ check } />
61
- )
62
- }
63
- onSelect={ ( event ) => {
64
- // We need to handle this on DropDown component probably..
65
- event.preventDefault();
55
+ value={ availableView.type }
56
+ name="view-actions-available-view"
57
+ checked={ availableView.type === view.type }
58
+ hideOnClick={ true }
59
+ onChange={ ( e ) => {
66
60
  onChangeView( {
67
61
  ...view,
68
- type: availableView.type,
62
+ type: e.target.value,
69
63
  } );
70
64
  } }
71
65
  >
72
- { availableView.label }
73
- </DropdownMenuItem>
66
+ <DropdownMenuItemLabel>
67
+ { availableView.label }
68
+ </DropdownMenuItemLabel>
69
+ </DropdownMenuRadioItem>
74
70
  );
75
71
  } ) }
76
- </DropdownSubMenu>
72
+ </DropdownMenu>
77
73
  );
78
74
  }
79
75
 
80
76
  const PAGE_SIZE_VALUES = [ 10, 20, 50, 100 ];
81
77
  function PageSizeMenu( { view, onChangeView } ) {
82
78
  return (
83
- <DropdownSubMenu
79
+ <DropdownMenu
84
80
  trigger={
85
- <DropdownSubMenuTrigger
86
- suffix={
87
- <>
88
- { view.perPage }
89
- <Icon icon={ chevronRightSmall } />
90
- </>
91
- }
81
+ <DropdownMenuItem
82
+ suffix={ <span aria-hidden="true">{ view.perPage }</span> }
92
83
  >
93
- { /* TODO: probably label per view type. */ }
94
- { __( 'Rows per page' ) }
95
- </DropdownSubMenuTrigger>
84
+ <DropdownMenuItemLabel>
85
+ { /* TODO: probably label per view type. */ }
86
+ { __( 'Rows per page' ) }
87
+ </DropdownMenuItemLabel>
88
+ </DropdownMenuItem>
96
89
  }
97
90
  >
98
91
  { PAGE_SIZE_VALUES.map( ( size ) => {
99
92
  return (
100
- <DropdownMenuItem
93
+ <DropdownMenuRadioItem
101
94
  key={ size }
102
- role="menuitemradio"
103
- aria-checked={ view.perPage === size }
104
- prefix={
105
- view.perPage === size && <Icon icon={ check } />
106
- }
107
- onSelect={ ( event ) => {
108
- // We need to handle this on DropDown component probably..
109
- event.preventDefault();
110
- onChangeView( { ...view, perPage: size, page: 1 } );
95
+ value={ size }
96
+ name="view-actions-page-size"
97
+ checked={ view.perPage === size }
98
+ onChange={ () => {
99
+ onChangeView( {
100
+ ...view,
101
+ // `e.target.value` holds the same value as `size` but as a string,
102
+ // so we use `size` directly to avoid parsing to int.
103
+ perPage: size,
104
+ page: 1,
105
+ } );
111
106
  } }
112
107
  >
113
- { size }
114
- </DropdownMenuItem>
108
+ <DropdownMenuItemLabel>{ size }</DropdownMenuItemLabel>
109
+ </DropdownMenuRadioItem>
115
110
  );
116
111
  } ) }
117
- </DropdownSubMenu>
112
+ </DropdownMenu>
118
113
  );
119
114
  }
120
115
 
@@ -127,27 +122,22 @@ function FieldsVisibilityMenu( { view, onChangeView, fields } ) {
127
122
  return null;
128
123
  }
129
124
  return (
130
- <DropdownSubMenu
125
+ <DropdownMenu
131
126
  trigger={
132
- <DropdownSubMenuTrigger
133
- suffix={ <Icon icon={ chevronRightSmall } /> }
134
- >
135
- { __( 'Fields' ) }
136
- </DropdownSubMenuTrigger>
127
+ <DropdownMenuItem>
128
+ <DropdownMenuItemLabel>
129
+ { __( 'Fields' ) }
130
+ </DropdownMenuItemLabel>
131
+ </DropdownMenuItem>
137
132
  }
138
133
  >
139
134
  { hidableFields?.map( ( field ) => {
140
135
  return (
141
- <DropdownMenuItem
136
+ <DropdownMenuCheckboxItem
142
137
  key={ field.id }
143
- role="menuitemcheckbox"
144
- prefix={
145
- ! view.hiddenFields?.includes( field.id ) && (
146
- <Icon icon={ check } />
147
- )
148
- }
149
- onSelect={ ( event ) => {
150
- event.preventDefault();
138
+ value={ field.id }
139
+ checked={ ! view.hiddenFields?.includes( field.id ) }
140
+ onChange={ () => {
151
141
  onChangeView( {
152
142
  ...view,
153
143
  hiddenFields: view.hiddenFields?.includes(
@@ -163,19 +153,16 @@ function FieldsVisibilityMenu( { view, onChangeView, fields } ) {
163
153
  } );
164
154
  } }
165
155
  >
166
- { field.header }
167
- </DropdownMenuItem>
156
+ <DropdownMenuItemLabel>
157
+ { field.header }
158
+ </DropdownMenuItemLabel>
159
+ </DropdownMenuCheckboxItem>
168
160
  );
169
161
  } ) }
170
- </DropdownSubMenu>
162
+ </DropdownMenu>
171
163
  );
172
164
  }
173
165
 
174
- // This object is used to construct the sorting options per sortable field.
175
- const sortingItemsInfo = {
176
- asc: { icon: arrowUp, label: __( 'Sort ascending' ) },
177
- desc: { icon: arrowDown, label: __( 'Sort descending' ) },
178
- };
179
166
  function SortMenu( { fields, view, onChangeView } ) {
180
167
  const sortableFields = fields.filter(
181
168
  ( field ) => field.enableSorting !== false
@@ -187,51 +174,58 @@ function SortMenu( { fields, view, onChangeView } ) {
187
174
  ( field ) => field.id === view.sort?.field
188
175
  );
189
176
  return (
190
- <DropdownSubMenu
177
+ <DropdownMenu
191
178
  trigger={
192
- <DropdownSubMenuTrigger
179
+ <DropdownMenuItem
193
180
  suffix={
194
- <>
181
+ <span aria-hidden="true">
195
182
  { currentSortedField?.header }
196
- <Icon icon={ chevronRightSmall } />
197
- </>
183
+ </span>
198
184
  }
199
185
  >
200
- { __( 'Sort by' ) }
201
- </DropdownSubMenuTrigger>
186
+ <DropdownMenuItemLabel>
187
+ { __( 'Sort by' ) }
188
+ </DropdownMenuItemLabel>
189
+ </DropdownMenuItem>
202
190
  }
203
191
  >
204
192
  { sortableFields?.map( ( field ) => {
205
193
  const sortedDirection = view.sort?.direction;
206
194
  return (
207
- <DropdownSubMenu
195
+ <DropdownMenu
208
196
  key={ field.id }
209
197
  trigger={
210
- <DropdownSubMenuTrigger
211
- suffix={ <Icon icon={ chevronRightSmall } /> }
212
- >
213
- { field.header }
214
- </DropdownSubMenuTrigger>
198
+ <DropdownMenuItem>
199
+ <DropdownMenuItemLabel>
200
+ { field.header }
201
+ </DropdownMenuItemLabel>
202
+ </DropdownMenuItem>
215
203
  }
216
- side="left"
204
+ style={ {
205
+ minWidth: '220px',
206
+ } }
217
207
  >
218
- { Object.entries( sortingItemsInfo ).map(
208
+ { Object.entries( SORTING_DIRECTIONS ).map(
219
209
  ( [ direction, info ] ) => {
220
- const isActive =
221
- currentSortedField &&
210
+ const isChecked =
211
+ currentSortedField !== undefined &&
222
212
  sortedDirection === direction &&
223
213
  field.id === currentSortedField.id;
214
+
215
+ const value = `${ field.id }-${ direction }`;
216
+
224
217
  return (
225
- <DropdownMenuItem
226
- key={ direction }
227
- role="menuitemradio"
228
- aria-checked={ isActive }
229
- prefix={ <Icon icon={ info.icon } /> }
230
- suffix={
231
- isActive && <Icon icon={ check } />
232
- }
233
- onSelect={ ( event ) => {
234
- event.preventDefault();
218
+ <DropdownMenuRadioItem
219
+ key={ value }
220
+ // All sorting radio items share the same name, so that
221
+ // selecting a sorting option automatically deselects the
222
+ // previously selected one, even if it is displayed in
223
+ // another submenu. The field and direction are passed via
224
+ // the `value` prop.
225
+ name="view-actions-sorting"
226
+ value={ value }
227
+ checked={ isChecked }
228
+ onChange={ () => {
235
229
  onChangeView( {
236
230
  ...view,
237
231
  sort: {
@@ -241,19 +235,21 @@ function SortMenu( { fields, view, onChangeView } ) {
241
235
  } );
242
236
  } }
243
237
  >
244
- { info.label }
245
- </DropdownMenuItem>
238
+ <DropdownMenuItemLabel>
239
+ { info.label }
240
+ </DropdownMenuItemLabel>
241
+ </DropdownMenuRadioItem>
246
242
  );
247
243
  }
248
244
  ) }
249
- </DropdownSubMenu>
245
+ </DropdownMenu>
250
246
  );
251
247
  } ) }
252
- </DropdownSubMenu>
248
+ </DropdownMenu>
253
249
  );
254
250
  }
255
251
 
256
- export default function ViewActions( {
252
+ const ViewActions = memo( function ViewActions( {
257
253
  fields,
258
254
  view,
259
255
  onChangeView,
@@ -263,7 +259,6 @@ export default function ViewActions( {
263
259
  <DropdownMenu
264
260
  trigger={
265
261
  <Button
266
- variant="tertiary"
267
262
  size="compact"
268
263
  icon={
269
264
  VIEW_LAYOUTS.find( ( v ) => v.type === view.type )
@@ -276,11 +271,13 @@ export default function ViewActions( {
276
271
  }
277
272
  >
278
273
  <DropdownMenuGroup>
279
- <ViewTypeMenu
280
- view={ view }
281
- onChangeView={ onChangeView }
282
- supportedLayouts={ supportedLayouts }
283
- />
274
+ { window?.__experimentalAdminViews && (
275
+ <ViewTypeMenu
276
+ view={ view }
277
+ onChangeView={ onChangeView }
278
+ supportedLayouts={ supportedLayouts }
279
+ />
280
+ ) }
284
281
  <SortMenu
285
282
  fields={ fields }
286
283
  view={ view }
@@ -295,4 +292,6 @@ export default function ViewActions( {
295
292
  </DropdownMenuGroup>
296
293
  </DropdownMenu>
297
294
  );
298
- }
295
+ } );
296
+
297
+ export default ViewActions;
package/src/view-grid.js CHANGED
@@ -42,12 +42,12 @@ export default function ViewGrid( {
42
42
  gap={ 8 }
43
43
  columns={ 2 }
44
44
  alignment="top"
45
- className="dataviews-grid-view"
45
+ className="dataviews-view-grid"
46
46
  >
47
- { usedData.map( ( item, index ) => (
47
+ { usedData.map( ( item ) => (
48
48
  <VStack
49
49
  spacing={ 3 }
50
- key={ getItemId?.( item ) || index }
50
+ key={ getItemId( item ) }
51
51
  className="dataviews-view-grid__card"
52
52
  >
53
53
  <div className="dataviews-view-grid__media">
@@ -87,7 +87,7 @@ export default function ViewGrid( {
87
87
  { field.header }
88
88
  </div>
89
89
  <div className="dataviews-view-grid__field-value">
90
- { field.render( { item } ) }
90
+ { renderedValue }
91
91
  </div>
92
92
  </VStack>
93
93
  );
package/src/view-list.js CHANGED
@@ -10,8 +10,11 @@ import { useAsyncList } from '@wordpress/compose';
10
10
  import {
11
11
  __experimentalHStack as HStack,
12
12
  __experimentalVStack as VStack,
13
+ Button,
13
14
  } from '@wordpress/components';
14
15
  import { ENTER, SPACE } from '@wordpress/keycodes';
16
+ import { info } from '@wordpress/icons';
17
+ import { __ } from '@wordpress/i18n';
15
18
 
16
19
  export default function ViewList( {
17
20
  view,
@@ -19,6 +22,7 @@ export default function ViewList( {
19
22
  data,
20
23
  getItemId,
21
24
  onSelectionChange,
25
+ onDetailsChange,
22
26
  selection,
23
27
  deferredRendering,
24
28
  } ) {
@@ -46,39 +50,38 @@ export default function ViewList( {
46
50
  };
47
51
 
48
52
  return (
49
- <ul className="dataviews-list-view">
50
- { usedData.map( ( item, index ) => {
53
+ <ul className="dataviews-view-list">
54
+ { usedData.map( ( item ) => {
51
55
  return (
52
- <li key={ getItemId?.( item ) || index }>
53
- <div
54
- role="button"
55
- tabIndex={ 0 }
56
- aria-pressed={ selection.includes( item.id ) }
57
- onKeyDown={ onEnter( item ) }
58
- className={ classNames(
59
- 'dataviews-list-view__item',
60
- {
61
- 'dataviews-list-view__item-selected':
62
- selection.includes( item.id ),
63
- }
64
- ) }
65
- onClick={ () => onSelectionChange( [ item ] ) }
66
- >
67
- <HStack spacing={ 3 }>
68
- <div className="dataviews-list-view__media-wrapper">
69
- { mediaField?.render( { item } ) || (
70
- <div className="dataviews-list-view__media-placeholder"></div>
71
- ) }
72
- </div>
73
- <HStack>
56
+ <li
57
+ key={ getItemId( item ) }
58
+ className={ classNames( {
59
+ 'is-selected': selection.includes( item.id ),
60
+ } ) }
61
+ >
62
+ <HStack className="dataviews-view-list__item-wrapper">
63
+ <div
64
+ role="button"
65
+ tabIndex={ 0 }
66
+ aria-pressed={ selection.includes( item.id ) }
67
+ onKeyDown={ onEnter( item ) }
68
+ className="dataviews-view-list__item"
69
+ onClick={ () => onSelectionChange( [ item ] ) }
70
+ >
71
+ <HStack spacing={ 3 } justify="start">
72
+ <div className="dataviews-view-list__media-wrapper">
73
+ { mediaField?.render( { item } ) || (
74
+ <div className="dataviews-view-list__media-placeholder"></div>
75
+ ) }
76
+ </div>
74
77
  <VStack spacing={ 1 }>
75
78
  { primaryField?.render( { item } ) }
76
- <div className="dataviews-list-view__fields">
79
+ <div className="dataviews-view-list__fields">
77
80
  { visibleFields.map( ( field ) => {
78
81
  return (
79
82
  <span
80
83
  key={ field.id }
81
- className="dataviews-list-view__field"
84
+ className="dataviews-view-list__field"
82
85
  >
83
86
  { field.render( {
84
87
  item,
@@ -89,8 +92,19 @@ export default function ViewList( {
89
92
  </div>
90
93
  </VStack>
91
94
  </HStack>
92
- </HStack>
93
- </div>
95
+ </div>
96
+ { onDetailsChange && (
97
+ <Button
98
+ className="dataviews-view-list__details-button"
99
+ onClick={ () =>
100
+ onDetailsChange( [ item ] )
101
+ }
102
+ icon={ info }
103
+ label={ __( 'View details' ) }
104
+ size="compact"
105
+ />
106
+ ) }
107
+ </HStack>
94
108
  </li>
95
109
  );
96
110
  } ) }