@wordpress/dataviews 5.0.0 → 6.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 (250) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/README.md +55 -27
  3. package/build/components/dataviews/index.js +13 -4
  4. package/build/components/dataviews/index.js.map +1 -1
  5. package/build/components/dataviews-context/index.js +3 -1
  6. package/build/components/dataviews-context/index.js.map +1 -1
  7. package/build/components/dataviews-filters/filter.js +15 -8
  8. package/build/components/dataviews-filters/filter.js.map +1 -1
  9. package/build/components/dataviews-filters/index.js +16 -5
  10. package/build/components/dataviews-filters/index.js.map +1 -1
  11. package/build/components/dataviews-filters/input-widget.js +7 -1
  12. package/build/components/dataviews-filters/input-widget.js.map +1 -1
  13. package/build/components/dataviews-filters/reset-filters.js +2 -2
  14. package/build/components/dataviews-filters/reset-filters.js.map +1 -1
  15. package/build/components/dataviews-layout/index.js +5 -2
  16. package/build/components/dataviews-layout/index.js.map +1 -1
  17. package/build/components/dataviews-view-config/index.js +4 -3
  18. package/build/components/dataviews-view-config/index.js.map +1 -1
  19. package/build/dataform-controls/boolean.js +15 -1
  20. package/build/dataform-controls/boolean.js.map +1 -1
  21. package/build/dataform-controls/date.js +385 -0
  22. package/build/dataform-controls/date.js.map +1 -0
  23. package/build/dataform-controls/datetime.js +5 -84
  24. package/build/dataform-controls/datetime.js.map +1 -1
  25. package/build/dataform-controls/email.js +15 -1
  26. package/build/dataform-controls/email.js.map +1 -1
  27. package/build/dataform-controls/index.js +2 -0
  28. package/build/dataform-controls/index.js.map +1 -1
  29. package/build/dataform-controls/integer.js +23 -4
  30. package/build/dataform-controls/integer.js.map +1 -1
  31. package/build/dataform-controls/relative-date-control.js +109 -0
  32. package/build/dataform-controls/relative-date-control.js.map +1 -0
  33. package/build/dataform-controls/select.js +12 -5
  34. package/build/dataform-controls/select.js.map +1 -1
  35. package/build/dataform-controls/text.js +15 -1
  36. package/build/dataform-controls/text.js.map +1 -1
  37. package/build/dataviews-layouts/grid/index.js +40 -23
  38. package/build/dataviews-layouts/grid/index.js.map +1 -1
  39. package/build/dataviews-layouts/grid/preview-size-picker.js +39 -85
  40. package/build/dataviews-layouts/grid/preview-size-picker.js.map +1 -1
  41. package/build/dataviews-layouts/list/index.js +7 -3
  42. package/build/dataviews-layouts/list/index.js.map +1 -1
  43. package/build/dataviews-layouts/table/column-primary.js +18 -3
  44. package/build/dataviews-layouts/table/column-primary.js.map +1 -1
  45. package/build/dataviews-layouts/table/index.js +57 -5
  46. package/build/dataviews-layouts/table/index.js.map +1 -1
  47. package/build/field-types/array.js +27 -18
  48. package/build/field-types/array.js.map +1 -1
  49. package/build/field-types/boolean.js +11 -7
  50. package/build/field-types/boolean.js.map +1 -1
  51. package/build/field-types/date.js +21 -12
  52. package/build/field-types/date.js.map +1 -1
  53. package/build/field-types/datetime.js +19 -10
  54. package/build/field-types/datetime.js.map +1 -1
  55. package/build/field-types/email.js +22 -18
  56. package/build/field-types/email.js.map +1 -1
  57. package/build/field-types/index.js +16 -6
  58. package/build/field-types/index.js.map +1 -1
  59. package/build/field-types/integer.js +22 -17
  60. package/build/field-types/integer.js.map +1 -1
  61. package/build/field-types/media.js +19 -10
  62. package/build/field-types/media.js.map +1 -1
  63. package/build/field-types/text.js +19 -10
  64. package/build/field-types/text.js.map +1 -1
  65. package/build/filter-and-sort-data-view.js +6 -4
  66. package/build/filter-and-sort-data-view.js.map +1 -1
  67. package/build/normalize-fields.js +4 -5
  68. package/build/normalize-fields.js.map +1 -1
  69. package/build/types.js.map +1 -1
  70. package/build/validation.js +15 -2
  71. package/build/validation.js.map +1 -1
  72. package/build-module/components/dataviews/index.js +15 -6
  73. package/build-module/components/dataviews/index.js.map +1 -1
  74. package/build-module/components/dataviews-context/index.js +3 -1
  75. package/build-module/components/dataviews-context/index.js.map +1 -1
  76. package/build-module/components/dataviews-filters/filter.js +15 -8
  77. package/build-module/components/dataviews-filters/filter.js.map +1 -1
  78. package/build-module/components/dataviews-filters/index.js +16 -5
  79. package/build-module/components/dataviews-filters/index.js.map +1 -1
  80. package/build-module/components/dataviews-filters/input-widget.js +7 -1
  81. package/build-module/components/dataviews-filters/input-widget.js.map +1 -1
  82. package/build-module/components/dataviews-filters/reset-filters.js +2 -2
  83. package/build-module/components/dataviews-filters/reset-filters.js.map +1 -1
  84. package/build-module/components/dataviews-layout/index.js +5 -2
  85. package/build-module/components/dataviews-layout/index.js.map +1 -1
  86. package/build-module/components/dataviews-view-config/index.js +4 -3
  87. package/build-module/components/dataviews-view-config/index.js.map +1 -1
  88. package/build-module/dataform-controls/boolean.js +17 -2
  89. package/build-module/dataform-controls/boolean.js.map +1 -1
  90. package/build-module/dataform-controls/date.js +376 -0
  91. package/build-module/dataform-controls/date.js.map +1 -0
  92. package/build-module/dataform-controls/datetime.js +3 -84
  93. package/build-module/dataform-controls/datetime.js.map +1 -1
  94. package/build-module/dataform-controls/email.js +17 -2
  95. package/build-module/dataform-controls/email.js.map +1 -1
  96. package/build-module/dataform-controls/index.js +2 -0
  97. package/build-module/dataform-controls/index.js.map +1 -1
  98. package/build-module/dataform-controls/integer.js +24 -5
  99. package/build-module/dataform-controls/integer.js.map +1 -1
  100. package/build-module/dataform-controls/relative-date-control.js +100 -0
  101. package/build-module/dataform-controls/relative-date-control.js.map +1 -0
  102. package/build-module/dataform-controls/select.js +12 -5
  103. package/build-module/dataform-controls/select.js.map +1 -1
  104. package/build-module/dataform-controls/text.js +17 -2
  105. package/build-module/dataform-controls/text.js.map +1 -1
  106. package/build-module/dataviews-layouts/grid/index.js +41 -24
  107. package/build-module/dataviews-layouts/grid/index.js.map +1 -1
  108. package/build-module/dataviews-layouts/grid/preview-size-picker.js +40 -85
  109. package/build-module/dataviews-layouts/grid/preview-size-picker.js.map +1 -1
  110. package/build-module/dataviews-layouts/list/index.js +7 -3
  111. package/build-module/dataviews-layouts/list/index.js.map +1 -1
  112. package/build-module/dataviews-layouts/table/column-primary.js +18 -3
  113. package/build-module/dataviews-layouts/table/column-primary.js.map +1 -1
  114. package/build-module/dataviews-layouts/table/index.js +58 -6
  115. package/build-module/dataviews-layouts/table/index.js.map +1 -1
  116. package/build-module/field-types/array.js +27 -18
  117. package/build-module/field-types/array.js.map +1 -1
  118. package/build-module/field-types/boolean.js +11 -7
  119. package/build-module/field-types/boolean.js.map +1 -1
  120. package/build-module/field-types/date.js +21 -12
  121. package/build-module/field-types/date.js.map +1 -1
  122. package/build-module/field-types/datetime.js +19 -10
  123. package/build-module/field-types/datetime.js.map +1 -1
  124. package/build-module/field-types/email.js +22 -18
  125. package/build-module/field-types/email.js.map +1 -1
  126. package/build-module/field-types/index.js +16 -6
  127. package/build-module/field-types/index.js.map +1 -1
  128. package/build-module/field-types/integer.js +22 -17
  129. package/build-module/field-types/integer.js.map +1 -1
  130. package/build-module/field-types/media.js +19 -10
  131. package/build-module/field-types/media.js.map +1 -1
  132. package/build-module/field-types/text.js +19 -10
  133. package/build-module/field-types/text.js.map +1 -1
  134. package/build-module/filter-and-sort-data-view.js +6 -4
  135. package/build-module/filter-and-sort-data-view.js.map +1 -1
  136. package/build-module/normalize-fields.js +4 -5
  137. package/build-module/normalize-fields.js.map +1 -1
  138. package/build-module/types.js.map +1 -1
  139. package/build-module/validation.js +15 -2
  140. package/build-module/validation.js.map +1 -1
  141. package/build-style/style-rtl.css +78 -43
  142. package/build-style/style.css +78 -43
  143. package/build-types/components/dataform/stories/index.story.d.ts +21 -0
  144. package/build-types/components/dataform/stories/index.story.d.ts.map +1 -1
  145. package/build-types/components/dataviews/index.d.ts +3 -2
  146. package/build-types/components/dataviews/index.d.ts.map +1 -1
  147. package/build-types/components/dataviews/stories/fixtures.d.ts.map +1 -1
  148. package/build-types/components/dataviews/stories/index.story.d.ts +16 -3
  149. package/build-types/components/dataviews/stories/index.story.d.ts.map +1 -1
  150. package/build-types/components/dataviews-context/index.d.ts +4 -2
  151. package/build-types/components/dataviews-context/index.d.ts.map +1 -1
  152. package/build-types/components/dataviews-filters/filter.d.ts.map +1 -1
  153. package/build-types/components/dataviews-filters/index.d.ts.map +1 -1
  154. package/build-types/components/dataviews-filters/input-widget.d.ts.map +1 -1
  155. package/build-types/components/dataviews-filters/reset-filters.d.ts.map +1 -1
  156. package/build-types/components/dataviews-layout/index.d.ts.map +1 -1
  157. package/build-types/components/dataviews-view-config/index.d.ts.map +1 -1
  158. package/build-types/constants.d.ts +2 -2
  159. package/build-types/dataform-controls/boolean.d.ts.map +1 -1
  160. package/build-types/dataform-controls/date.d.ts +3 -0
  161. package/build-types/dataform-controls/date.d.ts.map +1 -0
  162. package/build-types/dataform-controls/datetime.d.ts.map +1 -1
  163. package/build-types/dataform-controls/email.d.ts.map +1 -1
  164. package/build-types/dataform-controls/index.d.ts.map +1 -1
  165. package/build-types/dataform-controls/integer.d.ts.map +1 -1
  166. package/build-types/dataform-controls/relative-date-control.d.ts +46 -0
  167. package/build-types/dataform-controls/relative-date-control.d.ts.map +1 -0
  168. package/build-types/dataform-controls/select.d.ts.map +1 -1
  169. package/build-types/dataform-controls/text.d.ts.map +1 -1
  170. package/build-types/dataviews-layouts/grid/index.d.ts +1 -1
  171. package/build-types/dataviews-layouts/grid/index.d.ts.map +1 -1
  172. package/build-types/dataviews-layouts/grid/preview-size-picker.d.ts +0 -1
  173. package/build-types/dataviews-layouts/grid/preview-size-picker.d.ts.map +1 -1
  174. package/build-types/dataviews-layouts/index.d.ts +3 -3
  175. package/build-types/dataviews-layouts/list/index.d.ts.map +1 -1
  176. package/build-types/dataviews-layouts/table/column-primary.d.ts.map +1 -1
  177. package/build-types/dataviews-layouts/table/index.d.ts +1 -1
  178. package/build-types/dataviews-layouts/table/index.d.ts.map +1 -1
  179. package/build-types/field-types/array.d.ts.map +1 -1
  180. package/build-types/field-types/boolean.d.ts +5 -4
  181. package/build-types/field-types/boolean.d.ts.map +1 -1
  182. package/build-types/field-types/date.d.ts +9 -5
  183. package/build-types/field-types/date.d.ts.map +1 -1
  184. package/build-types/field-types/datetime.d.ts +4 -3
  185. package/build-types/field-types/datetime.d.ts.map +1 -1
  186. package/build-types/field-types/email.d.ts +4 -3
  187. package/build-types/field-types/email.d.ts.map +1 -1
  188. package/build-types/field-types/index.d.ts.map +1 -1
  189. package/build-types/field-types/integer.d.ts +4 -3
  190. package/build-types/field-types/integer.d.ts.map +1 -1
  191. package/build-types/field-types/media.d.ts +4 -3
  192. package/build-types/field-types/media.d.ts.map +1 -1
  193. package/build-types/field-types/text.d.ts +4 -3
  194. package/build-types/field-types/text.d.ts.map +1 -1
  195. package/build-types/filter-and-sort-data-view.d.ts.map +1 -1
  196. package/build-types/normalize-fields.d.ts.map +1 -1
  197. package/build-types/types.d.ts +20 -7
  198. package/build-types/types.d.ts.map +1 -1
  199. package/build-types/validation.d.ts.map +1 -1
  200. package/build-wp/index.js +1561 -670
  201. package/package.json +15 -14
  202. package/src/components/dataform/stories/index.story.tsx +229 -2
  203. package/src/components/dataviews/index.tsx +30 -10
  204. package/src/components/dataviews/stories/fixtures.tsx +3 -1
  205. package/src/components/dataviews/stories/index.story.tsx +49 -29
  206. package/src/components/dataviews/stories/style.css +6 -0
  207. package/src/components/dataviews-context/index.ts +8 -2
  208. package/src/components/dataviews-filters/filter.tsx +17 -7
  209. package/src/components/dataviews-filters/index.tsx +17 -2
  210. package/src/components/dataviews-filters/input-widget.tsx +7 -1
  211. package/src/components/dataviews-filters/reset-filters.tsx +4 -2
  212. package/src/components/dataviews-filters/style.scss +8 -2
  213. package/src/components/dataviews-layout/index.tsx +3 -0
  214. package/src/components/dataviews-view-config/index.tsx +5 -3
  215. package/src/dataform-controls/boolean.tsx +19 -2
  216. package/src/dataform-controls/date.tsx +499 -0
  217. package/src/dataform-controls/datetime.tsx +5 -91
  218. package/src/dataform-controls/email.tsx +19 -2
  219. package/src/dataform-controls/index.tsx +2 -0
  220. package/src/dataform-controls/integer.tsx +30 -4
  221. package/src/dataform-controls/relative-date-control.tsx +106 -0
  222. package/src/dataform-controls/select.tsx +23 -13
  223. package/src/dataform-controls/style.scss +19 -2
  224. package/src/dataform-controls/text.tsx +19 -2
  225. package/src/dataviews-layouts/grid/index.tsx +46 -24
  226. package/src/dataviews-layouts/grid/preview-size-picker.tsx +48 -73
  227. package/src/dataviews-layouts/grid/style.scss +15 -28
  228. package/src/dataviews-layouts/list/index.tsx +7 -4
  229. package/src/dataviews-layouts/list/style.scss +3 -3
  230. package/src/dataviews-layouts/table/column-primary.tsx +29 -5
  231. package/src/dataviews-layouts/table/index.tsx +134 -42
  232. package/src/dataviews-layouts/table/style.scss +45 -1
  233. package/src/field-types/array.tsx +33 -21
  234. package/src/field-types/boolean.tsx +15 -9
  235. package/src/field-types/date.ts +51 -15
  236. package/src/field-types/datetime.tsx +19 -13
  237. package/src/field-types/email.tsx +26 -21
  238. package/src/field-types/index.tsx +18 -8
  239. package/src/field-types/integer.tsx +26 -22
  240. package/src/field-types/media.tsx +19 -13
  241. package/src/field-types/text.tsx +19 -13
  242. package/src/filter-and-sort-data-view.ts +11 -4
  243. package/src/normalize-fields.ts +4 -8
  244. package/src/test/dataviews.tsx +129 -0
  245. package/src/test/filter-and-sort-data-view.js +52 -2
  246. package/src/test/validation.ts +4 -15
  247. package/src/types.ts +28 -8
  248. package/src/validation.ts +30 -1
  249. package/tsconfig.json +1 -0
  250. package/tsconfig.tsbuildinfo +1 -1
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { RangeControl } from '@wordpress/components';
5
5
  import { __ } from '@wordpress/i18n';
6
- import { useMemo, useContext } from '@wordpress/element';
6
+ import { useContext } from '@wordpress/element';
7
7
 
8
8
  /**
9
9
  * Internal dependencies
@@ -11,100 +11,75 @@ import { useMemo, useContext } from '@wordpress/element';
11
11
  import DataViewsContext from '../../components/dataviews-context';
12
12
  import type { ViewGrid } from '../../types';
13
13
 
14
- const viewportBreaks: {
15
- [ key: string ]: { min: number; max: number; default: number };
16
- } = {
17
- xhuge: { min: 3, max: 6, default: 5 },
18
- huge: { min: 2, max: 4, default: 4 },
19
- xlarge: { min: 2, max: 3, default: 3 },
20
- large: { min: 1, max: 2, default: 2 },
21
- mobile: { min: 1, max: 2, default: 2 },
22
- };
23
-
24
- /**
25
- * Breakpoints were adjusted from media queries breakpoints to account for
26
- * the sidebar width. This was done to match the existing styles we had.
27
- */
28
- const BREAKPOINTS = {
29
- xhuge: 1520,
30
- huge: 1140,
31
- xlarge: 780,
32
- large: 480,
33
- mobile: 0,
34
- };
35
-
36
- function useViewPortBreakpoint() {
37
- const containerWidth = useContext( DataViewsContext ).containerWidth;
38
- for ( const [ key, value ] of Object.entries( BREAKPOINTS ) ) {
39
- if ( containerWidth >= value ) {
40
- return key;
41
- }
42
- }
43
- return 'mobile';
44
- }
45
-
46
- export function useUpdatedPreviewSizeOnViewportChange() {
47
- const view = useContext( DataViewsContext ).view as ViewGrid;
48
- const viewport = useViewPortBreakpoint();
49
- return useMemo( () => {
50
- const previewSize = view.layout?.previewSize;
51
- let newPreviewSize;
52
- if ( ! previewSize ) {
53
- return;
54
- }
55
- const breakValues = viewportBreaks[ viewport ];
56
- if ( previewSize < breakValues.min ) {
57
- newPreviewSize = breakValues.min;
58
- }
59
- if ( previewSize > breakValues.max ) {
60
- newPreviewSize = breakValues.max;
61
- }
62
- return newPreviewSize;
63
- }, [ viewport, view ] );
64
- }
14
+ const imageSizes = [
15
+ {
16
+ value: 230,
17
+ breakpoint: 1,
18
+ },
19
+ {
20
+ value: 290,
21
+ breakpoint: 1112, // at minimum image width, 4 images display at this container size
22
+ },
23
+ {
24
+ value: 350,
25
+ breakpoint: 1636, // at minimum image width, 6 images display at this container size
26
+ },
27
+ {
28
+ value: 430,
29
+ breakpoint: 588, // at minimum image width, 2 images display at this container size
30
+ },
31
+ ];
65
32
 
66
33
  export default function PreviewSizePicker() {
67
- const viewport = useViewPortBreakpoint();
68
34
  const context = useContext( DataViewsContext );
69
35
  const view = context.view as ViewGrid;
70
- const breakValues = viewportBreaks[ viewport ];
71
- const previewSizeToUse = view.layout?.previewSize || breakValues.default;
72
- const marks = useMemo(
73
- () =>
74
- Array.from(
75
- { length: breakValues.max - breakValues.min + 1 },
76
- ( _, i ) => {
77
- return {
78
- value: breakValues.min + i,
79
- };
80
- }
81
- ),
82
- [ breakValues ]
83
- );
84
- if ( viewport === 'mobile' ) {
36
+
37
+ if ( context.containerWidth < 588 ) {
85
38
  return null;
86
39
  }
40
+
41
+ const breakValues = imageSizes.filter( ( size ) => {
42
+ return context.containerWidth >= size.breakpoint;
43
+ } );
44
+
45
+ // If the container has resized and the set preview size is no longer available,
46
+ // we reset it to the next smallest size.
47
+ const previewSizeToUse = view.layout?.previewSize
48
+ ? breakValues
49
+ .map( ( size, index ) => ( { ...size, index } ) )
50
+ .filter(
51
+ ( size ) => size.value <= ( view.layout?.previewSize ?? 0 ) // We know the view.layout?.previewSize exists at this point but the linter doesn't seem to.
52
+ )
53
+ .sort( ( a, b ) => b.value - a.value )[ 0 ].index
54
+ : 0;
55
+
56
+ const marks = breakValues.map( ( size, index ) => {
57
+ return {
58
+ value: index,
59
+ };
60
+ } );
61
+
87
62
  return (
88
63
  <RangeControl
89
64
  __nextHasNoMarginBottom
90
65
  __next40pxDefaultSize
91
66
  showTooltip={ false }
92
67
  label={ __( 'Preview size' ) }
93
- value={ breakValues.max + breakValues.min - previewSizeToUse }
94
- marks={ marks }
95
- min={ breakValues.min }
96
- max={ breakValues.max }
68
+ value={ previewSizeToUse }
69
+ min={ 0 }
70
+ max={ breakValues.length - 1 }
97
71
  withInputField={ false }
98
72
  onChange={ ( value = 0 ) => {
99
73
  context.onChangeView( {
100
74
  ...view,
101
75
  layout: {
102
76
  ...view.layout,
103
- previewSize: breakValues.max + breakValues.min - value,
77
+ previewSize: breakValues[ value ].value,
104
78
  },
105
79
  } );
106
80
  } }
107
81
  step={ 1 }
82
+ marks={ marks }
108
83
  />
109
84
  );
110
85
  }
@@ -1,8 +1,19 @@
1
1
  .dataviews-view-grid {
2
2
  margin-bottom: auto;
3
+ display: grid;
4
+ gap: $grid-unit-40;
3
5
  grid-template-rows: max-content;
6
+ grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
4
7
  padding: 0 $grid-unit-60 $grid-unit-30;
5
8
  container-type: inline-size;
9
+ /**
10
+ * Breakpoints were adjusted from media queries breakpoints to account for
11
+ * the sidebar width. This was done to match the existing styles we had.
12
+ */
13
+ @container (max-width: 480px) {
14
+ padding-left: $grid-unit-30;
15
+ padding-right: $grid-unit-30;
16
+ }
6
17
 
7
18
  @media not (prefers-reduced-motion) {
8
19
  transition: padding ease-out 0.1s;
@@ -49,8 +60,8 @@
49
60
  width: 100%;
50
61
  min-height: 200px;
51
62
  aspect-ratio: 1/1;
52
- background-color: $gray-100;
53
- border-radius: $grid-unit-05;
63
+ background-color: $white;
64
+ border-radius: $radius-medium;
54
65
  position: relative;
55
66
 
56
67
  img {
@@ -116,30 +127,6 @@
116
127
  }
117
128
  }
118
129
 
119
- .dataviews-view-grid.dataviews-view-grid {
120
- /**
121
- * Breakpoints were adjusted from media queries breakpoints to account for
122
- * the sidebar width. This was done to match the existing styles we had.
123
- */
124
- @container (max-width: 480px) {
125
- grid-template-columns: repeat(1, minmax(0, 1fr));
126
- padding-left: $grid-unit-30;
127
- padding-right: $grid-unit-30;
128
- }
129
- @container (min-width: 480px) {
130
- grid-template-columns: repeat(2, minmax(0, 1fr));
131
- }
132
- @container (min-width: 780px) {
133
- grid-template-columns: repeat(3, minmax(0, 1fr));
134
- }
135
- @container (min-width: 1140px) {
136
- grid-template-columns: repeat(4, minmax(0, 1fr));
137
- }
138
- @container (min-width: 1520px) {
139
- grid-template-columns: repeat(5, minmax(0, 1fr));
140
- }
141
- }
142
-
143
130
  .dataviews-view-grid__field-value:empty,
144
131
  .dataviews-view-grid__field:empty {
145
132
  display: none;
@@ -168,8 +155,8 @@
168
155
  }
169
156
 
170
157
  .dataviews-view-grid__group-header {
171
- font-size: 16px;
172
- font-weight: 600;
158
+ font-size: $font-size-large;
159
+ font-weight: $font-weight-medium;
173
160
  color: $gray-900;
174
161
  margin: 0 0 $grid-unit-10 0;
175
162
  padding: 0 $grid-unit-60;
@@ -199,7 +199,11 @@ function ListItem< Item >( {
199
199
  const renderedMediaField =
200
200
  showMedia && mediaField?.render ? (
201
201
  <div className="dataviews-view-list__media-wrapper">
202
- <mediaField.render item={ item } field={ mediaField } />
202
+ <mediaField.render
203
+ item={ item }
204
+ field={ mediaField }
205
+ config={ { sizes: '52px' } }
206
+ />
203
207
  </div>
204
208
  ) : null;
205
209
 
@@ -354,6 +358,7 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) {
354
358
  selection,
355
359
  view,
356
360
  className,
361
+ empty,
357
362
  } = props;
358
363
  const baseId = useInstanceId( ViewList, 'view-list' );
359
364
 
@@ -487,9 +492,7 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) {
487
492
  'dataviews-no-results': ! hasData && ! isLoading,
488
493
  } ) }
489
494
  >
490
- { ! hasData && (
491
- <p>{ isLoading ? <Spinner /> : __( 'No results' ) }</p>
492
- ) }
495
+ { ! hasData && <p>{ isLoading ? <Spinner /> : empty }</p> }
493
496
  </div>
494
497
  );
495
498
  }
@@ -136,8 +136,8 @@ div.dataviews-view-list {
136
136
  overflow: hidden;
137
137
  position: relative;
138
138
  flex-shrink: 0;
139
- background-color: $gray-100;
140
- border-radius: $grid-unit-05;
139
+ background-color: $white;
140
+ border-radius: $radius-medium;
141
141
 
142
142
  img {
143
143
  width: 100%;
@@ -153,7 +153,7 @@ div.dataviews-view-list {
153
153
  width: 100%;
154
154
  height: 100%;
155
155
  box-shadow: inset 0 0 0 $border-width rgba(0, 0, 0, 0.1);
156
- border-radius: $grid-unit-05;
156
+ border-radius: $radius-medium;
157
157
  }
158
158
  }
159
159
 
@@ -16,6 +16,7 @@ import {
16
16
  */
17
17
  import type { NormalizedField } from '../../types';
18
18
  import { ItemClickWrapper } from '../utils/item-click-wrapper';
19
+ import { sprintf, __ } from '@wordpress/i18n';
19
20
 
20
21
  function ColumnPrimary< Item >( {
21
22
  item,
@@ -43,11 +44,34 @@ function ColumnPrimary< Item >( {
43
44
  return (
44
45
  <HStack spacing={ 3 } justify="flex-start">
45
46
  { mediaField && (
46
- <div className="dataviews-view-table__cell-content-wrapper dataviews-column-primary__media">
47
- <mediaField.render item={ item } field={ mediaField } />
48
- </div>
47
+ <ItemClickWrapper
48
+ item={ item }
49
+ isItemClickable={ isItemClickable }
50
+ onClickItem={ onClickItem }
51
+ renderItemLink={ renderItemLink }
52
+ className="dataviews-view-table__cell-content-wrapper dataviews-column-primary__media"
53
+ aria-label={
54
+ titleField
55
+ ? sprintf(
56
+ // translators: %s is the item title.
57
+ __( 'Click item: %s' ),
58
+ titleField.getValue?.( { item } )
59
+ )
60
+ : undefined
61
+ }
62
+ >
63
+ <mediaField.render
64
+ item={ item }
65
+ field={ mediaField }
66
+ config={ { sizes: '32px' } }
67
+ />
68
+ </ItemClickWrapper>
49
69
  ) }
50
- <VStack spacing={ 0 }>
70
+ <VStack
71
+ spacing={ 0 }
72
+ alignment="flex-start"
73
+ className="dataviews-view-table__primary-column-content"
74
+ >
51
75
  { titleField && (
52
76
  <ItemClickWrapper
53
77
  item={ item }
@@ -56,7 +80,7 @@ function ColumnPrimary< Item >( {
56
80
  renderItemLink={ renderItemLink }
57
81
  className="dataviews-view-table__cell-content-wrapper dataviews-title-field"
58
82
  >
59
- { level !== undefined && (
83
+ { level !== undefined && level > 0 && (
60
84
  <span className="dataviews-view-table__level">
61
85
  { '—'.repeat( level ) }&nbsp;
62
86
  </span>
@@ -7,7 +7,7 @@ import type { ComponentProps, ReactElement } from 'react';
7
7
  /**
8
8
  * WordPress dependencies
9
9
  */
10
- import { __ } from '@wordpress/i18n';
10
+ import { __, sprintf } from '@wordpress/i18n';
11
11
  import { Spinner } from '@wordpress/components';
12
12
  import {
13
13
  useContext,
@@ -16,6 +16,7 @@ import {
16
16
  useRef,
17
17
  useState,
18
18
  } from '@wordpress/element';
19
+ import { isAppleOS } from '@wordpress/keycodes';
19
20
 
20
21
  /**
21
22
  * Internal dependencies
@@ -147,19 +148,34 @@ function TableRow< Item >( {
147
148
  onTouchStart={ () => {
148
149
  isTouchDeviceRef.current = true;
149
150
  } }
150
- onClick={ () => {
151
+ onClick={ ( event ) => {
151
152
  if ( ! hasPossibleBulkAction ) {
152
153
  return;
153
154
  }
155
+
154
156
  if (
155
157
  ! isTouchDeviceRef.current &&
156
158
  document.getSelection()?.type !== 'Range'
157
159
  ) {
158
- onChangeSelection(
159
- selection.includes( id )
160
- ? selection.filter( ( itemId ) => id !== itemId )
161
- : [ id ]
162
- );
160
+ if ( isAppleOS() ? event.metaKey : event.ctrlKey ) {
161
+ // Handle non-consecutive selection.
162
+ onChangeSelection(
163
+ selection.includes( id )
164
+ ? selection.filter(
165
+ ( itemId ) => id !== itemId
166
+ )
167
+ : [ ...selection, id ]
168
+ );
169
+ } else {
170
+ // Handle single selection
171
+ onChangeSelection(
172
+ selection.includes( id )
173
+ ? selection.filter(
174
+ ( itemId ) => id !== itemId
175
+ )
176
+ : [ id ]
177
+ );
178
+ }
163
179
  }
164
180
  } }
165
181
  >
@@ -256,6 +272,7 @@ function ViewTable< Item >( {
256
272
  renderItemLink,
257
273
  view,
258
274
  className,
275
+ empty,
259
276
  }: ViewTableProps< Item > ) {
260
277
  const { containerRef } = useContext( DataViewsContext );
261
278
  const headerMenuRefs = useRef<
@@ -305,6 +322,23 @@ function ViewTable< Item >( {
305
322
  const descriptionField = fields.find(
306
323
  ( field ) => field.id === view.descriptionField
307
324
  );
325
+
326
+ // Get group field if groupByField is specified
327
+ const groupField = view.groupByField
328
+ ? fields.find( ( f ) => f.id === view.groupByField )
329
+ : null;
330
+
331
+ // Group data by groupByField if specified
332
+ const dataByGroup = groupField
333
+ ? data.reduce( ( groups: Map< string, typeof data >, item ) => {
334
+ const groupName = groupField.getValue( { item } );
335
+ if ( ! groups.has( groupName ) ) {
336
+ groups.set( groupName, [] );
337
+ }
338
+ groups.get( groupName )?.push( item );
339
+ return groups;
340
+ }, new Map< string, typeof data >() )
341
+ : null;
308
342
  const { showTitle = true, showMedia = true, showDescription = true } = view;
309
343
  const hasPrimaryColumn =
310
344
  ( titleField && showTitle ) ||
@@ -423,38 +457,98 @@ function ViewTable< Item >( {
423
457
  ) }
424
458
  </tr>
425
459
  </thead>
426
- <tbody>
427
- { hasData &&
428
- data.map( ( item, index ) => (
429
- <TableRow
430
- key={ getItemId( item ) }
431
- item={ item }
432
- level={
433
- view.showLevels &&
434
- typeof getItemLevel === 'function'
435
- ? getItemLevel( item )
436
- : undefined
437
- }
438
- hasBulkActions={ hasBulkActions }
439
- actions={ actions }
440
- fields={ fields }
441
- id={ getItemId( item ) || index.toString() }
442
- view={ view }
443
- titleField={ titleField }
444
- mediaField={ mediaField }
445
- descriptionField={ descriptionField }
446
- selection={ selection }
447
- getItemId={ getItemId }
448
- onChangeSelection={ onChangeSelection }
449
- onClickItem={ onClickItem }
450
- renderItemLink={ renderItemLink }
451
- isItemClickable={ isItemClickable }
452
- isActionsColumnSticky={
453
- ! isHorizontalScrollEnd
454
- }
455
- />
456
- ) ) }
457
- </tbody>
460
+ { /* Render grouped data if groupByField is specified */ }
461
+ { hasData && groupField && dataByGroup ? (
462
+ Array.from( dataByGroup.entries() ).map(
463
+ ( [ groupName, groupItems ] ) => (
464
+ <tbody key={ `group-${ groupName }` }>
465
+ <tr className="dataviews-view-table__group-header-row">
466
+ <td
467
+ colSpan={
468
+ columns.length +
469
+ ( hasPrimaryColumn ? 1 : 0 ) +
470
+ ( hasBulkActions ? 1 : 0 ) +
471
+ ( actions?.length ? 1 : 0 )
472
+ }
473
+ className="dataviews-view-table__group-header-cell"
474
+ >
475
+ { sprintf(
476
+ // translators: 1: The label of the field e.g. "Date". 2: The value of the field, e.g.: "May 2022".
477
+ __( '%1$s: %2$s' ),
478
+ groupField.label,
479
+ groupName
480
+ ) }
481
+ </td>
482
+ </tr>
483
+ { groupItems.map( ( item, index ) => (
484
+ <TableRow
485
+ key={ getItemId( item ) }
486
+ item={ item }
487
+ level={
488
+ view.showLevels &&
489
+ typeof getItemLevel === 'function'
490
+ ? getItemLevel( item )
491
+ : undefined
492
+ }
493
+ hasBulkActions={ hasBulkActions }
494
+ actions={ actions }
495
+ fields={ fields }
496
+ id={
497
+ getItemId( item ) ||
498
+ index.toString()
499
+ }
500
+ view={ view }
501
+ titleField={ titleField }
502
+ mediaField={ mediaField }
503
+ descriptionField={ descriptionField }
504
+ selection={ selection }
505
+ getItemId={ getItemId }
506
+ onChangeSelection={ onChangeSelection }
507
+ onClickItem={ onClickItem }
508
+ renderItemLink={ renderItemLink }
509
+ isItemClickable={ isItemClickable }
510
+ isActionsColumnSticky={
511
+ ! isHorizontalScrollEnd
512
+ }
513
+ />
514
+ ) ) }
515
+ </tbody>
516
+ )
517
+ )
518
+ ) : (
519
+ <tbody>
520
+ { hasData &&
521
+ data.map( ( item, index ) => (
522
+ <TableRow
523
+ key={ getItemId( item ) }
524
+ item={ item }
525
+ level={
526
+ view.showLevels &&
527
+ typeof getItemLevel === 'function'
528
+ ? getItemLevel( item )
529
+ : undefined
530
+ }
531
+ hasBulkActions={ hasBulkActions }
532
+ actions={ actions }
533
+ fields={ fields }
534
+ id={ getItemId( item ) || index.toString() }
535
+ view={ view }
536
+ titleField={ titleField }
537
+ mediaField={ mediaField }
538
+ descriptionField={ descriptionField }
539
+ selection={ selection }
540
+ getItemId={ getItemId }
541
+ onChangeSelection={ onChangeSelection }
542
+ onClickItem={ onClickItem }
543
+ renderItemLink={ renderItemLink }
544
+ isItemClickable={ isItemClickable }
545
+ isActionsColumnSticky={
546
+ ! isHorizontalScrollEnd
547
+ }
548
+ />
549
+ ) ) }
550
+ </tbody>
551
+ ) }
458
552
  </table>
459
553
  <div
460
554
  className={ clsx( {
@@ -463,9 +557,7 @@ function ViewTable< Item >( {
463
557
  } ) }
464
558
  id={ tableNoticeId }
465
559
  >
466
- { ! hasData && (
467
- <p>{ isLoading ? <Spinner /> : __( 'No results' ) }</p>
468
- ) }
560
+ { ! hasData && <p>{ isLoading ? <Spinner /> : empty }</p> }
469
561
  </div>
470
562
  </>
471
563
  );
@@ -16,7 +16,6 @@
16
16
  td,
17
17
  th {
18
18
  padding: $grid-unit-15;
19
- white-space: nowrap;
20
19
 
21
20
  &.dataviews-view-table__actions-column {
22
21
  text-align: right;
@@ -44,6 +43,11 @@
44
43
  &.dataviews-view-table__checkbox-column {
45
44
  padding-right: 0;
46
45
  width: 1%;
46
+
47
+ .dataviews-view-table__cell-content-wrapper {
48
+ max-width: auto;
49
+ min-width: auto;
50
+ }
47
51
  }
48
52
  }
49
53
  tr {
@@ -198,6 +202,8 @@
198
202
 
199
203
  .dataviews-view-table__actions-column {
200
204
  width: 1%;
205
+ max-width: inherit;
206
+ min-width: inherit;
201
207
  }
202
208
 
203
209
  &:has(tr.is-selected) {
@@ -260,4 +266,42 @@
260
266
 
261
267
  .dataviews-column-primary__media {
262
268
  max-width: 60px;
269
+ overflow: hidden;
270
+ position: relative;
271
+ flex-shrink: 0;
272
+ background-color: $white;
273
+ border-radius: $radius-medium;
274
+
275
+ img {
276
+ width: 100%;
277
+ height: 100%;
278
+ object-fit: cover;
279
+ }
280
+
281
+ &::after {
282
+ content: "";
283
+ position: absolute;
284
+ top: 0;
285
+ left: 0;
286
+ width: 100%;
287
+ height: 100%;
288
+ box-shadow: inset 0 0 0 $border-width rgba(0, 0, 0, 0.1);
289
+ border-radius: $radius-medium;
290
+ }
291
+ }
292
+
293
+ .dataviews-view-table__cell-content-wrapper,
294
+ .dataviews-view-table__primary-column-content {
295
+ &:not(.dataviews-column-primary__media) {
296
+ min-width: 15ch;
297
+ max-width: 80ch;
298
+ }
299
+ }
300
+
301
+ .dataviews-view-table__group-header-row {
302
+ .dataviews-view-table__group-header-cell {
303
+ font-weight: $font-weight-medium;
304
+ padding: $grid-unit-15 $grid-unit-60;
305
+ color: $gray-900;
306
+ }
263
307
  }