@wordpress/dataviews 5.0.1-next.719a03cbe.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 (254) hide show
  1. package/CHANGELOG.md +34 -4
  2. package/README.md +55 -26
  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 +91 -18
  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 +66 -0
  52. package/build/field-types/date.js.map +1 -0
  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 +20 -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 +28 -14
  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 +93 -20
  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 +60 -0
  121. package/build-module/field-types/date.js.map +1 -0
  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 +20 -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 +28 -14
  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 +84 -41
  142. package/build-style/style.css +84 -41
  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 +1 -0
  148. package/build-types/components/dataviews/stories/fixtures.d.ts.map +1 -1
  149. package/build-types/components/dataviews/stories/index.story.d.ts +16 -2
  150. package/build-types/components/dataviews/stories/index.story.d.ts.map +1 -1
  151. package/build-types/components/dataviews-context/index.d.ts +4 -2
  152. package/build-types/components/dataviews-context/index.d.ts.map +1 -1
  153. package/build-types/components/dataviews-filters/filter.d.ts.map +1 -1
  154. package/build-types/components/dataviews-filters/index.d.ts.map +1 -1
  155. package/build-types/components/dataviews-filters/input-widget.d.ts.map +1 -1
  156. package/build-types/components/dataviews-filters/reset-filters.d.ts.map +1 -1
  157. package/build-types/components/dataviews-layout/index.d.ts.map +1 -1
  158. package/build-types/components/dataviews-view-config/index.d.ts.map +1 -1
  159. package/build-types/components/stories/index.story.d.ts +4 -0
  160. package/build-types/components/stories/index.story.d.ts.map +1 -1
  161. package/build-types/constants.d.ts +2 -2
  162. package/build-types/dataform-controls/boolean.d.ts.map +1 -1
  163. package/build-types/dataform-controls/date.d.ts +3 -0
  164. package/build-types/dataform-controls/date.d.ts.map +1 -0
  165. package/build-types/dataform-controls/datetime.d.ts.map +1 -1
  166. package/build-types/dataform-controls/email.d.ts.map +1 -1
  167. package/build-types/dataform-controls/index.d.ts.map +1 -1
  168. package/build-types/dataform-controls/integer.d.ts.map +1 -1
  169. package/build-types/dataform-controls/relative-date-control.d.ts +46 -0
  170. package/build-types/dataform-controls/relative-date-control.d.ts.map +1 -0
  171. package/build-types/dataform-controls/select.d.ts.map +1 -1
  172. package/build-types/dataform-controls/text.d.ts.map +1 -1
  173. package/build-types/dataviews-layouts/grid/index.d.ts +1 -1
  174. package/build-types/dataviews-layouts/grid/index.d.ts.map +1 -1
  175. package/build-types/dataviews-layouts/grid/preview-size-picker.d.ts +0 -1
  176. package/build-types/dataviews-layouts/grid/preview-size-picker.d.ts.map +1 -1
  177. package/build-types/dataviews-layouts/index.d.ts +3 -3
  178. package/build-types/dataviews-layouts/list/index.d.ts.map +1 -1
  179. package/build-types/dataviews-layouts/table/column-primary.d.ts.map +1 -1
  180. package/build-types/dataviews-layouts/table/index.d.ts +1 -1
  181. package/build-types/dataviews-layouts/table/index.d.ts.map +1 -1
  182. package/build-types/field-types/array.d.ts.map +1 -1
  183. package/build-types/field-types/boolean.d.ts +5 -4
  184. package/build-types/field-types/boolean.d.ts.map +1 -1
  185. package/build-types/field-types/date.d.ts +20 -0
  186. package/build-types/field-types/date.d.ts.map +1 -0
  187. package/build-types/field-types/datetime.d.ts +4 -3
  188. package/build-types/field-types/datetime.d.ts.map +1 -1
  189. package/build-types/field-types/email.d.ts +4 -3
  190. package/build-types/field-types/email.d.ts.map +1 -1
  191. package/build-types/field-types/index.d.ts.map +1 -1
  192. package/build-types/field-types/integer.d.ts +4 -3
  193. package/build-types/field-types/integer.d.ts.map +1 -1
  194. package/build-types/field-types/media.d.ts +4 -3
  195. package/build-types/field-types/media.d.ts.map +1 -1
  196. package/build-types/field-types/text.d.ts +4 -3
  197. package/build-types/field-types/text.d.ts.map +1 -1
  198. package/build-types/filter-and-sort-data-view.d.ts.map +1 -1
  199. package/build-types/normalize-fields.d.ts.map +1 -1
  200. package/build-types/types.d.ts +25 -8
  201. package/build-types/types.d.ts.map +1 -1
  202. package/build-types/validation.d.ts.map +1 -1
  203. package/build-wp/index.js +2196 -739
  204. package/package.json +15 -14
  205. package/src/components/dataform/stories/index.story.tsx +229 -2
  206. package/src/components/dataviews/index.tsx +30 -10
  207. package/src/components/dataviews/stories/fixtures.tsx +82 -59
  208. package/src/components/dataviews/stories/index.story.tsx +65 -8
  209. package/src/components/dataviews/stories/style.css +6 -0
  210. package/src/components/dataviews-context/index.ts +8 -2
  211. package/src/components/dataviews-filters/filter.tsx +17 -7
  212. package/src/components/dataviews-filters/index.tsx +17 -2
  213. package/src/components/dataviews-filters/input-widget.tsx +7 -1
  214. package/src/components/dataviews-filters/reset-filters.tsx +4 -2
  215. package/src/components/dataviews-filters/style.scss +8 -2
  216. package/src/components/dataviews-layout/index.tsx +3 -0
  217. package/src/components/dataviews-view-config/index.tsx +5 -3
  218. package/src/components/stories/index.story.tsx +21 -0
  219. package/src/dataform-controls/boolean.tsx +19 -2
  220. package/src/dataform-controls/date.tsx +499 -0
  221. package/src/dataform-controls/datetime.tsx +5 -91
  222. package/src/dataform-controls/email.tsx +19 -2
  223. package/src/dataform-controls/index.tsx +2 -0
  224. package/src/dataform-controls/integer.tsx +30 -4
  225. package/src/dataform-controls/relative-date-control.tsx +106 -0
  226. package/src/dataform-controls/select.tsx +23 -13
  227. package/src/dataform-controls/style.scss +19 -2
  228. package/src/dataform-controls/text.tsx +19 -2
  229. package/src/dataviews-layouts/grid/index.tsx +168 -55
  230. package/src/dataviews-layouts/grid/preview-size-picker.tsx +48 -73
  231. package/src/dataviews-layouts/grid/style.scss +21 -26
  232. package/src/dataviews-layouts/list/index.tsx +7 -4
  233. package/src/dataviews-layouts/list/style.scss +3 -3
  234. package/src/dataviews-layouts/table/column-primary.tsx +29 -5
  235. package/src/dataviews-layouts/table/index.tsx +134 -42
  236. package/src/dataviews-layouts/table/style.scss +45 -1
  237. package/src/field-types/array.tsx +33 -21
  238. package/src/field-types/boolean.tsx +15 -9
  239. package/src/field-types/date.ts +92 -0
  240. package/src/field-types/datetime.tsx +19 -13
  241. package/src/field-types/email.tsx +26 -21
  242. package/src/field-types/index.tsx +23 -8
  243. package/src/field-types/integer.tsx +26 -22
  244. package/src/field-types/media.tsx +19 -13
  245. package/src/field-types/text.tsx +19 -13
  246. package/src/filter-and-sort-data-view.ts +38 -13
  247. package/src/normalize-fields.ts +4 -8
  248. package/src/test/dataviews.tsx +129 -0
  249. package/src/test/filter-and-sort-data-view.js +150 -31
  250. package/src/test/validation.ts +4 -15
  251. package/src/types.ts +34 -8
  252. package/src/validation.ts +30 -1
  253. package/tsconfig.json +1 -0
  254. package/tsconfig.tsbuildinfo +1 -1
@@ -5,6 +5,7 @@ import {
5
5
  Flex,
6
6
  BaseControl,
7
7
  __experimentalNumberControl as NumberControl,
8
+ privateApis,
8
9
  } from '@wordpress/components';
9
10
  import { useCallback } from '@wordpress/element';
10
11
  import { __ } from '@wordpress/i18n';
@@ -14,6 +15,9 @@ import { __ } from '@wordpress/i18n';
14
15
  */
15
16
  import { OPERATOR_BETWEEN } from '../constants';
16
17
  import type { DataFormControlProps } from '../types';
18
+ import { unlock } from '../lock-unlock';
19
+
20
+ const { ValidatedNumberControl } = unlock( privateApis );
17
21
 
18
22
  function BetweenControls< Item >( {
19
23
  id,
@@ -81,10 +85,16 @@ export default function Integer< Item >( {
81
85
  const { id, label, description } = field;
82
86
  const value = field.getValue( { item: data } ) ?? '';
83
87
  const onChangeControl = useCallback(
84
- ( newValue: string | undefined ) =>
88
+ ( newValue: string | undefined ) => {
85
89
  onChange( {
86
- [ id ]: Number( newValue ),
87
- } ),
90
+ // Do not convert an empty string or undefined to a number,
91
+ // otherwise there's a mismatch between the UI control (empty)
92
+ // and the data relied by onChange (0).
93
+ [ id ]: [ '', undefined ].includes( newValue )
94
+ ? undefined
95
+ : Number( newValue ),
96
+ } );
97
+ },
88
98
  [ id, onChange ]
89
99
  );
90
100
 
@@ -100,7 +110,23 @@ export default function Integer< Item >( {
100
110
  }
101
111
 
102
112
  return (
103
- <NumberControl
113
+ <ValidatedNumberControl
114
+ required={ !! field.isValid?.required }
115
+ customValidator={ ( newValue: any ) => {
116
+ if ( field.isValid?.custom ) {
117
+ return field.isValid.custom(
118
+ {
119
+ ...data,
120
+ [ id ]: [ undefined, '', null ].includes( newValue )
121
+ ? undefined
122
+ : Number( newValue ),
123
+ },
124
+ field
125
+ );
126
+ }
127
+
128
+ return null;
129
+ } }
104
130
  label={ label }
105
131
  help={ description }
106
132
  value={ value }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import clsx from 'clsx';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import {
10
+ BaseControl,
11
+ SelectControl,
12
+ __experimentalNumberControl as NumberControl,
13
+ __experimentalHStack as HStack,
14
+ } from '@wordpress/components';
15
+ import { useCallback } from '@wordpress/element';
16
+ import { __ } from '@wordpress/i18n';
17
+
18
+ /**
19
+ * Internal dependencies
20
+ */
21
+ import { OPERATOR_IN_THE_PAST, OPERATOR_OVER } from '../constants';
22
+
23
+ interface RelativeDateControlProps {
24
+ id: string;
25
+ value: { value?: string | number; unit?: string };
26
+ onChange: ( value: any ) => void;
27
+ label: string;
28
+ hideLabelFromVision?: boolean;
29
+ options: { value: string; label: string }[];
30
+ className?: string;
31
+ }
32
+
33
+ export const TIME_UNITS_OPTIONS = {
34
+ [ OPERATOR_IN_THE_PAST ]: [
35
+ { value: 'days', label: __( 'Days' ) },
36
+ { value: 'weeks', label: __( 'Weeks' ) },
37
+ { value: 'months', label: __( 'Months' ) },
38
+ { value: 'years', label: __( 'Years' ) },
39
+ ],
40
+ [ OPERATOR_OVER ]: [
41
+ { value: 'days', label: __( 'Days ago' ) },
42
+ { value: 'weeks', label: __( 'Weeks ago' ) },
43
+ { value: 'months', label: __( 'Months ago' ) },
44
+ { value: 'years', label: __( 'Years ago' ) },
45
+ ],
46
+ };
47
+
48
+ export default function RelativeDateControl( {
49
+ id,
50
+ value,
51
+ onChange,
52
+ label,
53
+ hideLabelFromVision,
54
+ options,
55
+ className,
56
+ }: RelativeDateControlProps ) {
57
+ const { value: relValue = '', unit = options[ 0 ].value } = value;
58
+
59
+ const onChangeValue = useCallback(
60
+ ( newValue: string | undefined ) =>
61
+ onChange( {
62
+ [ id ]: { value: Number( newValue ), unit },
63
+ } ),
64
+ [ id, onChange, unit ]
65
+ );
66
+
67
+ const onChangeUnit = useCallback(
68
+ ( newUnit: string | undefined ) =>
69
+ onChange( {
70
+ [ id ]: { value: relValue, unit: newUnit },
71
+ } ),
72
+ [ id, onChange, relValue ]
73
+ );
74
+
75
+ return (
76
+ <BaseControl
77
+ id={ id }
78
+ __nextHasNoMarginBottom
79
+ className={ clsx( className, 'dataviews-controls__relative-date' ) }
80
+ label={ label }
81
+ hideLabelFromVision={ hideLabelFromVision }
82
+ >
83
+ <HStack spacing={ 2.5 }>
84
+ <NumberControl
85
+ __next40pxDefaultSize
86
+ className="dataviews-controls__relative-date-number"
87
+ spinControls="none"
88
+ min={ 1 }
89
+ step={ 1 }
90
+ value={ relValue }
91
+ onChange={ onChangeValue }
92
+ />
93
+ <SelectControl
94
+ className="dataviews-controls__relative-date-unit"
95
+ __next40pxDefaultSize
96
+ __nextHasNoMarginBottom
97
+ label={ __( 'Unit' ) }
98
+ value={ unit }
99
+ options={ options }
100
+ onChange={ onChangeUnit }
101
+ hideLabelFromVision
102
+ />
103
+ </HStack>
104
+ </BaseControl>
105
+ );
106
+ }
@@ -16,8 +16,9 @@ export default function Select< Item >( {
16
16
  onChange,
17
17
  hideLabelFromVision,
18
18
  }: DataFormControlProps< Item > ) {
19
- const { id, label } = field;
20
- const value = field.getValue( { item: data } ) ?? '';
19
+ const { id, label, type } = field;
20
+ const isMultiple = type === 'array';
21
+ const value = field.getValue( { item: data } ) ?? ( isMultiple ? [] : '' );
21
22
  const onChangeControl = useCallback(
22
23
  ( newValue: any ) =>
23
24
  onChange( {
@@ -26,17 +27,25 @@ export default function Select< Item >( {
26
27
  [ id, onChange ]
27
28
  );
28
29
 
29
- const elements = [
30
- /*
31
- * Value can be undefined when:
32
- *
33
- * - the field is not required
34
- * - in bulk editing
35
- *
36
- */
37
- { label: __( 'Select item' ), value: '' },
38
- ...( field?.elements ?? [] ),
39
- ];
30
+ const fieldElements = field?.elements ?? [];
31
+ const hasEmptyValue = fieldElements.some(
32
+ ( { value: elementValue } ) => elementValue === ''
33
+ );
34
+
35
+ const elements =
36
+ hasEmptyValue || isMultiple
37
+ ? fieldElements
38
+ : [
39
+ /*
40
+ * Value can be undefined when:
41
+ *
42
+ * - the field is not required
43
+ * - in bulk editing
44
+ *
45
+ */
46
+ { label: __( 'Select item' ), value: '' },
47
+ ...fieldElements,
48
+ ];
40
49
 
41
50
  return (
42
51
  <SelectControl
@@ -48,6 +57,7 @@ export default function Select< Item >( {
48
57
  __next40pxDefaultSize
49
58
  __nextHasNoMarginBottom
50
59
  hideLabelFromVision={ hideLabelFromVision }
60
+ multiple={ isMultiple }
51
61
  />
52
62
  );
53
63
  }
@@ -3,7 +3,24 @@
3
3
  padding: 0;
4
4
  }
5
5
 
6
- .dataviews-controls__datetime-number,
7
- .dataviews-controls__datetime-unit {
6
+ .dataviews-controls__relative-date-number,
7
+ .dataviews-controls__relative-date-unit {
8
8
  flex: 1 1 50%;
9
9
  }
10
+
11
+ .dataviews-controls__date {
12
+ // Hide the native date picker icon since we're using our own calendar.
13
+ input[type="date"]::-webkit-inner-spin-button,
14
+ input[type="date"]::-webkit-calendar-picker-indicator {
15
+ display: none;
16
+ -webkit-appearance: none;
17
+ }
18
+ }
19
+
20
+ .dataviews-controls__date-preset {
21
+ border: 1px solid #ddd;
22
+
23
+ &:active {
24
+ background-color: $black;
25
+ }
26
+ }
@@ -1,13 +1,16 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { TextControl } from '@wordpress/components';
4
+ import { privateApis } from '@wordpress/components';
5
5
  import { useCallback } from '@wordpress/element';
6
6
 
7
7
  /**
8
8
  * Internal dependencies
9
9
  */
10
10
  import type { DataFormControlProps } from '../types';
11
+ import { unlock } from '../lock-unlock';
12
+
13
+ const { ValidatedTextControl } = unlock( privateApis );
11
14
 
12
15
  export default function Text< Item >( {
13
16
  data,
@@ -27,7 +30,21 @@ export default function Text< Item >( {
27
30
  );
28
31
 
29
32
  return (
30
- <TextControl
33
+ <ValidatedTextControl
34
+ required={ !! field.isValid?.required }
35
+ customValidator={ ( newValue: any ) => {
36
+ if ( field.isValid?.custom ) {
37
+ return field.isValid.custom(
38
+ {
39
+ ...data,
40
+ [ id ]: newValue,
41
+ },
42
+ field
43
+ );
44
+ }
45
+
46
+ return null;
47
+ } }
31
48
  label={ label }
32
49
  placeholder={ placeholder }
33
50
  value={ value ?? '' }
@@ -8,7 +8,6 @@ import type { ComponentProps, ReactElement } from 'react';
8
8
  * WordPress dependencies
9
9
  */
10
10
  import {
11
- __experimentalGrid as Grid,
12
11
  __experimentalHStack as HStack,
13
12
  __experimentalVStack as VStack,
14
13
  Spinner,
@@ -16,8 +15,10 @@ import {
16
15
  FlexItem,
17
16
  privateApis as componentsPrivateApis,
18
17
  } from '@wordpress/components';
19
- import { __ } from '@wordpress/i18n';
18
+ import { __, sprintf } from '@wordpress/i18n';
20
19
  import { useInstanceId } from '@wordpress/compose';
20
+ import { isAppleOS } from '@wordpress/keycodes';
21
+ import { useContext } from '@wordpress/element';
21
22
 
22
23
  /**
23
24
  * Internal dependencies
@@ -25,6 +26,7 @@ import { useInstanceId } from '@wordpress/compose';
25
26
  import { unlock } from '../../lock-unlock';
26
27
  import ItemActions from '../../components/dataviews-item-actions';
27
28
  import DataViewsSelectionCheckbox from '../../components/dataviews-selection-checkbox';
29
+ import DataViewsContext from '../../components/dataviews-context';
28
30
  import {
29
31
  useHasAPossibleBulkAction,
30
32
  useSomeItemHasAPossibleBulkAction,
@@ -37,7 +39,6 @@ import type {
37
39
  } from '../../types';
38
40
  import type { SetSelection } from '../../private-types';
39
41
  import { ItemClickWrapper } from '../utils/item-click-wrapper';
40
- import { useUpdatedPreviewSizeOnViewportChange } from './preview-size-picker';
41
42
  const { Badge } = unlock( componentsPrivateApis );
42
43
 
43
44
  interface GridItemProps< Item > {
@@ -60,6 +61,9 @@ interface GridItemProps< Item > {
60
61
  regularFields: NormalizedField< Item >[];
61
62
  badgeFields: NormalizedField< Item >[];
62
63
  hasBulkActions: boolean;
64
+ config: {
65
+ sizes: string;
66
+ };
63
67
  }
64
68
 
65
69
  function GridItem< Item >( {
@@ -78,6 +82,7 @@ function GridItem< Item >( {
78
82
  regularFields,
79
83
  badgeFields,
80
84
  hasBulkActions,
85
+ config,
81
86
  }: GridItemProps< Item > ) {
82
87
  const { showTitle = true, showMedia = true, showDescription = true } = view;
83
88
  const hasBulkAction = useHasAPossibleBulkAction( actions, item );
@@ -85,7 +90,11 @@ function GridItem< Item >( {
85
90
  const instanceId = useInstanceId( GridItem );
86
91
  const isSelected = selection.includes( id );
87
92
  const renderedMediaField = mediaField?.render ? (
88
- <mediaField.render item={ item } field={ mediaField } />
93
+ <mediaField.render
94
+ item={ item }
95
+ field={ mediaField }
96
+ config={ config }
97
+ />
89
98
  ) : null;
90
99
  const renderedTitleField =
91
100
  showTitle && titleField?.render ? (
@@ -117,7 +126,7 @@ function GridItem< Item >( {
117
126
  'is-selected': hasBulkAction && isSelected,
118
127
  } ) }
119
128
  onClickCapture={ ( event ) => {
120
- if ( event.ctrlKey || event.metaKey ) {
129
+ if ( isAppleOS() ? event.metaKey : event.ctrlKey ) {
121
130
  event.stopPropagation();
122
131
  event.preventDefault();
123
132
  if ( ! hasBulkAction ) {
@@ -254,7 +263,9 @@ function ViewGrid< Item >( {
254
263
  selection,
255
264
  view,
256
265
  className,
266
+ empty,
257
267
  }: ViewGridProps< Item > ) {
268
+ const { resizeObserverRef } = useContext( DataViewsContext );
258
269
  const titleField = fields.find(
259
270
  ( field ) => field.id === view?.titleField
260
271
  );
@@ -285,59 +296,161 @@ function ViewGrid< Item >( {
285
296
  { regularFields: [], badgeFields: [] }
286
297
  );
287
298
  const hasData = !! data?.length;
288
- const updatedPreviewSize = useUpdatedPreviewSizeOnViewportChange();
289
299
  const hasBulkActions = useSomeItemHasAPossibleBulkAction( actions, data );
290
- const usedPreviewSize = updatedPreviewSize || view.layout?.previewSize;
291
- const gridStyle = usedPreviewSize
292
- ? {
293
- gridTemplateColumns: `repeat(${ usedPreviewSize }, minmax(0, 1fr))`,
294
- }
295
- : {};
300
+ const usedPreviewSize = view.layout?.previewSize;
301
+ /*
302
+ * This is the maximum width that an image can achieve in the grid. The reasoning is:
303
+ * The biggest min image width available is 430px (see /dataviews-layouts/grid/preview-size-picker.tsx).
304
+ * Because the grid is responsive, once there is room for another column, the images shrink to accommodate it.
305
+ * So each image will never grow past 2*430px plus a little more to account for the gaps.
306
+ */
307
+ const size = '900px';
308
+
309
+ const groupField = view.groupByField
310
+ ? fields.find( ( f ) => f.id === view.groupByField )
311
+ : null;
312
+
313
+ // Group data by groupByField if specified
314
+ const dataByGroup = groupField
315
+ ? data.reduce( ( groups: Map< string, typeof data >, item ) => {
316
+ const groupName = groupField.getValue( { item } );
317
+ if ( ! groups.has( groupName ) ) {
318
+ groups.set( groupName, [] );
319
+ }
320
+ groups.get( groupName )?.push( item );
321
+ return groups;
322
+ }, new Map< string, typeof data >() )
323
+ : null;
324
+
296
325
  return (
297
326
  <>
298
- { hasData && (
299
- <Grid
300
- gap={ 8 }
301
- columns={ 2 }
302
- alignment="top"
303
- className={ clsx( 'dataviews-view-grid', className ) }
304
- style={ gridStyle }
305
- aria-busy={ isLoading }
306
- >
307
- { data.map( ( item ) => {
308
- return (
309
- <GridItem
310
- key={ getItemId( item ) }
311
- view={ view }
312
- selection={ selection }
313
- onChangeSelection={ onChangeSelection }
314
- onClickItem={ onClickItem }
315
- isItemClickable={ isItemClickable }
316
- renderItemLink={ renderItemLink }
317
- getItemId={ getItemId }
318
- item={ item }
319
- actions={ actions }
320
- mediaField={ mediaField }
321
- titleField={ titleField }
322
- descriptionField={ descriptionField }
323
- regularFields={ regularFields }
324
- badgeFields={ badgeFields }
325
- hasBulkActions={ hasBulkActions }
326
- />
327
- );
328
- } ) }
329
- </Grid>
330
- ) }
331
- { ! hasData && (
332
- <div
333
- className={ clsx( {
334
- 'dataviews-loading': isLoading,
335
- 'dataviews-no-results': ! isLoading,
336
- } ) }
337
- >
338
- <p>{ isLoading ? <Spinner /> : __( 'No results' ) }</p>
339
- </div>
340
- ) }
327
+ {
328
+ // Render multiple groups.
329
+ hasData && groupField && dataByGroup && (
330
+ <VStack spacing={ 4 }>
331
+ { Array.from( dataByGroup.entries() ).map(
332
+ ( [ groupName, groupItems ] ) => (
333
+ <VStack key={ groupName } spacing={ 2 }>
334
+ <h3 className="dataviews-view-grid__group-header">
335
+ { sprintf(
336
+ // translators: 1: The label of the field e.g. "Date". 2: The value of the field, e.g.: "May 2022".
337
+ __( '%1$s: %2$s' ),
338
+ groupField.label,
339
+ groupName
340
+ ) }
341
+ </h3>
342
+ <div
343
+ className={ clsx(
344
+ 'dataviews-view-grid',
345
+ className
346
+ ) }
347
+ style={ {
348
+ gridTemplateColumns:
349
+ usedPreviewSize &&
350
+ `repeat(auto-fill, minmax(${ usedPreviewSize }px, 1fr))`,
351
+ } }
352
+ aria-busy={ isLoading }
353
+ ref={ resizeObserverRef }
354
+ >
355
+ { groupItems.map( ( item ) => {
356
+ return (
357
+ <GridItem
358
+ key={ getItemId( item ) }
359
+ view={ view }
360
+ selection={ selection }
361
+ onChangeSelection={
362
+ onChangeSelection
363
+ }
364
+ onClickItem={ onClickItem }
365
+ isItemClickable={
366
+ isItemClickable
367
+ }
368
+ renderItemLink={
369
+ renderItemLink
370
+ }
371
+ getItemId={ getItemId }
372
+ item={ item }
373
+ actions={ actions }
374
+ mediaField={ mediaField }
375
+ titleField={ titleField }
376
+ descriptionField={
377
+ descriptionField
378
+ }
379
+ regularFields={
380
+ regularFields
381
+ }
382
+ badgeFields={ badgeFields }
383
+ hasBulkActions={
384
+ hasBulkActions
385
+ }
386
+ config={ {
387
+ sizes: size,
388
+ } }
389
+ />
390
+ );
391
+ } ) }
392
+ </div>
393
+ </VStack>
394
+ )
395
+ ) }
396
+ </VStack>
397
+ )
398
+ }
399
+
400
+ {
401
+ // Render a single grid with all data.
402
+ hasData && ! dataByGroup && (
403
+ <div
404
+ className={ clsx( 'dataviews-view-grid', className ) }
405
+ style={ {
406
+ gridTemplateColumns:
407
+ usedPreviewSize &&
408
+ `repeat(auto-fill, minmax(${ usedPreviewSize }px, 1fr))`,
409
+ } }
410
+ aria-busy={ isLoading }
411
+ ref={ resizeObserverRef }
412
+ >
413
+ { data.map( ( item ) => {
414
+ return (
415
+ <GridItem
416
+ key={ getItemId( item ) }
417
+ view={ view }
418
+ selection={ selection }
419
+ onChangeSelection={ onChangeSelection }
420
+ onClickItem={ onClickItem }
421
+ isItemClickable={ isItemClickable }
422
+ renderItemLink={ renderItemLink }
423
+ getItemId={ getItemId }
424
+ item={ item }
425
+ actions={ actions }
426
+ mediaField={ mediaField }
427
+ titleField={ titleField }
428
+ descriptionField={ descriptionField }
429
+ regularFields={ regularFields }
430
+ badgeFields={ badgeFields }
431
+ hasBulkActions={ hasBulkActions }
432
+ config={ {
433
+ sizes: size,
434
+ } }
435
+ />
436
+ );
437
+ } ) }
438
+ </div>
439
+ )
440
+ }
441
+ {
442
+ // Render empty state.
443
+ ! hasData && (
444
+ <div
445
+ className={ clsx( {
446
+ 'dataviews-loading': isLoading,
447
+ 'dataviews-no-results': ! isLoading,
448
+ } ) }
449
+ >
450
+ <p>{ isLoading ? <Spinner /> : empty }</p>
451
+ </div>
452
+ )
453
+ }
341
454
  </>
342
455
  );
343
456
  }