@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
@@ -0,0 +1,499 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import {
5
+ BaseControl,
6
+ Button,
7
+ privateApis as componentsPrivateApis,
8
+ __experimentalInputControl as InputControl,
9
+ __experimentalHStack as HStack,
10
+ __experimentalVStack as VStack,
11
+ } from '@wordpress/components';
12
+ import { useCallback, useMemo, useState } from '@wordpress/element';
13
+ import { __ } from '@wordpress/i18n';
14
+ import { getDate, getSettings } from '@wordpress/date';
15
+
16
+ /**
17
+ * External dependencies
18
+ */
19
+ import {
20
+ format,
21
+ isValid,
22
+ subMonths,
23
+ subDays,
24
+ subYears,
25
+ startOfMonth,
26
+ startOfYear,
27
+ } from 'date-fns';
28
+
29
+ /**
30
+ * Internal dependencies
31
+ */
32
+ import RelativeDateControl, {
33
+ TIME_UNITS_OPTIONS,
34
+ } from './relative-date-control';
35
+ import {
36
+ OPERATOR_IN_THE_PAST,
37
+ OPERATOR_OVER,
38
+ OPERATOR_BETWEEN,
39
+ } from '../constants';
40
+ import { unlock } from '../lock-unlock';
41
+ import type { DataFormControlProps } from '../types';
42
+
43
+ const { DateCalendar, DateRangeCalendar } = unlock( componentsPrivateApis );
44
+
45
+ const DATE_PRESETS: {
46
+ id: string;
47
+ label: string;
48
+ getValue: () => Date;
49
+ }[] = [
50
+ {
51
+ id: 'today',
52
+ label: __( 'Today' ),
53
+ getValue: () => getDate( null ),
54
+ },
55
+ {
56
+ id: 'yesterday',
57
+ label: __( 'Yesterday' ),
58
+ getValue: () => {
59
+ const today = getDate( null );
60
+ return subDays( today, 1 );
61
+ },
62
+ },
63
+ {
64
+ id: 'past-week',
65
+ label: __( 'Past week' ),
66
+ getValue: () => {
67
+ const today = getDate( null );
68
+ return subDays( today, 7 );
69
+ },
70
+ },
71
+ {
72
+ id: 'past-month',
73
+ label: __( 'Past month' ),
74
+ getValue: () => {
75
+ const today = getDate( null );
76
+ return subMonths( today, 1 );
77
+ },
78
+ },
79
+ ];
80
+
81
+ const DATE_RANGE_PRESETS = [
82
+ {
83
+ id: 'last-7-days',
84
+ label: __( 'Last 7 days' ),
85
+ getValue: () => {
86
+ const today = getDate( null );
87
+ return [ subDays( today, 7 ), today ];
88
+ },
89
+ },
90
+ {
91
+ id: 'last-30-days',
92
+ label: __( 'Last 30 days' ),
93
+ getValue: () => {
94
+ const today = getDate( null );
95
+ return [ subDays( today, 30 ), today ];
96
+ },
97
+ },
98
+ {
99
+ id: 'month-to-date',
100
+ label: __( 'Month to date' ),
101
+ getValue: () => {
102
+ const today = getDate( null );
103
+ return [ startOfMonth( today ), today ];
104
+ },
105
+ },
106
+ {
107
+ id: 'last-year',
108
+ label: __( 'Last year' ),
109
+ getValue: () => {
110
+ const today = getDate( null );
111
+ return [ subYears( today, 1 ), today ];
112
+ },
113
+ },
114
+ {
115
+ id: 'year-to-date',
116
+ label: __( 'Year to date' ),
117
+ getValue: () => {
118
+ const today = getDate( null );
119
+ return [ startOfYear( today ), today ];
120
+ },
121
+ },
122
+ ];
123
+
124
+ const parseDate = ( dateString?: string ): Date | null => {
125
+ if ( ! dateString ) {
126
+ return null;
127
+ }
128
+ const parsed = getDate( dateString );
129
+ return parsed && isValid( parsed ) ? parsed : null;
130
+ };
131
+
132
+ const formatDate = ( date?: Date | string ): string => {
133
+ if ( ! date ) {
134
+ return '';
135
+ }
136
+ return typeof date === 'string' ? date : format( date, 'yyyy-MM-dd' );
137
+ };
138
+
139
+ function CalendarDateControl( {
140
+ id,
141
+ value,
142
+ onChange,
143
+ label,
144
+ hideLabelFromVision,
145
+ className,
146
+ }: {
147
+ id: string;
148
+ value: string | undefined;
149
+ onChange: ( value: any ) => void;
150
+ label: string;
151
+ hideLabelFromVision?: boolean;
152
+ className?: string;
153
+ } ) {
154
+ const [ selectedPresetId, setSelectedPresetId ] = useState< string | null >(
155
+ null
156
+ );
157
+
158
+ const [ calendarMonth, setCalendarMonth ] = useState< Date >( () => {
159
+ const parsedDate = parseDate( value );
160
+ return parsedDate || new Date(); // Default to current month
161
+ } );
162
+
163
+ const onSelectDate = useCallback(
164
+ ( newDate: Date | undefined | null ) => {
165
+ const dateValue = newDate
166
+ ? format( newDate, 'yyyy-MM-dd' )
167
+ : undefined;
168
+ onChange( { [ id ]: dateValue } );
169
+ setSelectedPresetId( null );
170
+ },
171
+ [ id, onChange ]
172
+ );
173
+
174
+ const handlePresetClick = useCallback(
175
+ ( preset: ( typeof DATE_PRESETS )[ 0 ] ) => {
176
+ const presetDate = preset.getValue();
177
+ const dateValue = formatDate( presetDate );
178
+
179
+ setCalendarMonth( presetDate );
180
+ onChange( { [ id ]: dateValue } );
181
+ setSelectedPresetId( preset.id );
182
+ },
183
+ [ id, onChange ]
184
+ );
185
+
186
+ const handleManualDateChange = useCallback(
187
+ ( newValue?: string ) => {
188
+ onChange( { [ id ]: newValue } );
189
+ if ( newValue ) {
190
+ const parsedDate = parseDate( newValue );
191
+ if ( parsedDate ) {
192
+ setCalendarMonth( parsedDate );
193
+ }
194
+ }
195
+ setSelectedPresetId( null );
196
+ },
197
+ [ id, onChange ]
198
+ );
199
+
200
+ const {
201
+ timezone: { string: timezoneString },
202
+ l10n: { startOfWeek },
203
+ } = getSettings();
204
+
205
+ return (
206
+ <BaseControl
207
+ __nextHasNoMarginBottom
208
+ id={ id }
209
+ className={ className }
210
+ label={ label }
211
+ hideLabelFromVision={ hideLabelFromVision }
212
+ >
213
+ <VStack spacing={ 4 }>
214
+ { /* Preset buttons */ }
215
+ <HStack spacing={ 2 } wrap justify="flex-start">
216
+ { DATE_PRESETS.map( ( preset ) => {
217
+ const isSelected = selectedPresetId === preset.id;
218
+ return (
219
+ <Button
220
+ className="dataviews-controls__date-preset"
221
+ key={ preset.id }
222
+ variant="tertiary"
223
+ isPressed={ isSelected }
224
+ size="small"
225
+ onClick={ () => handlePresetClick( preset ) }
226
+ >
227
+ { preset.label }
228
+ </Button>
229
+ );
230
+ } ) }
231
+ <Button
232
+ className="dataviews-controls__date-preset"
233
+ variant="tertiary"
234
+ isPressed={ ! selectedPresetId }
235
+ size="small"
236
+ disabled={ !! selectedPresetId }
237
+ accessibleWhenDisabled={ false }
238
+ >
239
+ { __( 'Custom' ) }
240
+ </Button>
241
+ </HStack>
242
+
243
+ { /* Manual date input */ }
244
+ <InputControl
245
+ __next40pxDefaultSize
246
+ type="date"
247
+ label={ __( 'Date' ) }
248
+ hideLabelFromVision
249
+ value={ value }
250
+ onChange={ handleManualDateChange }
251
+ />
252
+
253
+ { /* Calendar widget */ }
254
+ <DateCalendar
255
+ style={ { width: '100%' } }
256
+ selected={
257
+ value ? parseDate( value ) || undefined : undefined
258
+ }
259
+ onSelect={ onSelectDate }
260
+ month={ calendarMonth }
261
+ onMonthChange={ setCalendarMonth }
262
+ timeZone={ timezoneString || undefined }
263
+ weekStartsOn={ startOfWeek }
264
+ />
265
+ </VStack>
266
+ </BaseControl>
267
+ );
268
+ }
269
+
270
+ function CalendarDateRangeControl( {
271
+ id,
272
+ value,
273
+ onChange,
274
+ label,
275
+ hideLabelFromVision,
276
+ className,
277
+ }: {
278
+ id: string;
279
+ value: [ string, string ] | undefined;
280
+ onChange: ( value: any ) => void;
281
+ label: string;
282
+ hideLabelFromVision?: boolean;
283
+ className?: string;
284
+ } ) {
285
+ const [ selectedPresetId, setSelectedPresetId ] = useState< string | null >(
286
+ null
287
+ );
288
+
289
+ const selectedRange = useMemo( () => {
290
+ if ( ! value ) {
291
+ return { from: undefined, to: undefined };
292
+ }
293
+
294
+ const [ from, to ] = value;
295
+ return {
296
+ from: parseDate( from ) || undefined,
297
+ to: parseDate( to ) || undefined,
298
+ };
299
+ }, [ value ] );
300
+
301
+ const [ calendarMonth, setCalendarMonth ] = useState< Date >( () => {
302
+ return selectedRange.from || new Date();
303
+ } );
304
+
305
+ const updateDateRange = useCallback(
306
+ ( fromDate?: Date | string, toDate?: Date | string ) => {
307
+ if ( fromDate && toDate ) {
308
+ onChange( {
309
+ [ id ]: [ formatDate( fromDate ), formatDate( toDate ) ],
310
+ } );
311
+ } else if ( ! fromDate && ! toDate ) {
312
+ onChange( { [ id ]: undefined } );
313
+ }
314
+ // Do nothing if only one date is set - wait for both
315
+ },
316
+ [ id, onChange ]
317
+ );
318
+
319
+ const onSelectCalendarRange = useCallback(
320
+ (
321
+ newRange:
322
+ | { from: Date | undefined; to?: Date | undefined }
323
+ | undefined
324
+ ) => {
325
+ updateDateRange( newRange?.from, newRange?.to );
326
+ setSelectedPresetId( null );
327
+ },
328
+ [ updateDateRange ]
329
+ );
330
+
331
+ const handlePresetClick = useCallback(
332
+ ( preset: ( typeof DATE_RANGE_PRESETS )[ 0 ] ) => {
333
+ const [ startDate, endDate ] = preset.getValue();
334
+ setCalendarMonth( startDate );
335
+ updateDateRange( startDate, endDate );
336
+ setSelectedPresetId( preset.id );
337
+ },
338
+ [ updateDateRange ]
339
+ );
340
+
341
+ const handleManualDateChange = useCallback(
342
+ ( fromOrTo: 'from' | 'to', newValue?: string ) => {
343
+ const [ currentFrom, currentTo ] = value || [
344
+ undefined,
345
+ undefined,
346
+ ];
347
+ const updatedFrom = fromOrTo === 'from' ? newValue : currentFrom;
348
+ const updatedTo = fromOrTo === 'to' ? newValue : currentTo;
349
+
350
+ updateDateRange( updatedFrom, updatedTo );
351
+
352
+ if ( newValue ) {
353
+ const parsedDate = parseDate( newValue );
354
+ if ( parsedDate ) {
355
+ setCalendarMonth( parsedDate );
356
+ }
357
+ }
358
+
359
+ setSelectedPresetId( null );
360
+ },
361
+ [ value, updateDateRange ]
362
+ );
363
+
364
+ const { timezone, l10n } = getSettings();
365
+
366
+ return (
367
+ <BaseControl
368
+ __nextHasNoMarginBottom
369
+ id={ id }
370
+ className={ className }
371
+ label={ label }
372
+ hideLabelFromVision={ hideLabelFromVision }
373
+ >
374
+ <VStack spacing={ 4 }>
375
+ { /* Preset buttons */ }
376
+ <HStack spacing={ 2 } wrap justify="flex-start">
377
+ { DATE_RANGE_PRESETS.map( ( preset ) => {
378
+ const isSelected = selectedPresetId === preset.id;
379
+ return (
380
+ <Button
381
+ className="dataviews-controls__date-preset"
382
+ key={ preset.id }
383
+ variant="tertiary"
384
+ isPressed={ isSelected }
385
+ size="small"
386
+ onClick={ () => handlePresetClick( preset ) }
387
+ >
388
+ { preset.label }
389
+ </Button>
390
+ );
391
+ } ) }
392
+ <Button
393
+ className="dataviews-controls__date-preset"
394
+ variant="tertiary"
395
+ isPressed={ ! selectedPresetId }
396
+ size="small"
397
+ accessibleWhenDisabled={ false }
398
+ disabled={ !! selectedPresetId }
399
+ >
400
+ { __( 'Custom' ) }
401
+ </Button>
402
+ </HStack>
403
+
404
+ { /* Manual date range inputs */ }
405
+ <HStack spacing={ 2 }>
406
+ <InputControl
407
+ __next40pxDefaultSize
408
+ type="date"
409
+ label={ __( 'From' ) }
410
+ hideLabelFromVision
411
+ value={ value?.[ 0 ] }
412
+ onChange={ ( newValue ) =>
413
+ handleManualDateChange( 'from', newValue )
414
+ }
415
+ />
416
+ <InputControl
417
+ __next40pxDefaultSize
418
+ type="date"
419
+ label={ __( 'To' ) }
420
+ hideLabelFromVision
421
+ value={ value?.[ 1 ] }
422
+ onChange={ ( newValue ) =>
423
+ handleManualDateChange( 'to', newValue )
424
+ }
425
+ />
426
+ </HStack>
427
+
428
+ <DateRangeCalendar
429
+ style={ { width: '100%' } }
430
+ selected={ selectedRange }
431
+ onSelect={ onSelectCalendarRange }
432
+ month={ calendarMonth }
433
+ onMonthChange={ setCalendarMonth }
434
+ timeZone={ timezone.string || undefined }
435
+ weekStartsOn={ l10n.startOfWeek }
436
+ />
437
+ </VStack>
438
+ </BaseControl>
439
+ );
440
+ }
441
+
442
+ export default function DateControl< Item >( {
443
+ data,
444
+ field,
445
+ onChange,
446
+ hideLabelFromVision,
447
+ operator,
448
+ }: DataFormControlProps< Item > ) {
449
+ const { id, label } = field;
450
+ const value = field.getValue( { item: data } );
451
+
452
+ if ( operator === OPERATOR_IN_THE_PAST || operator === OPERATOR_OVER ) {
453
+ return (
454
+ <RelativeDateControl
455
+ className="dataviews-controls__date"
456
+ id={ id }
457
+ value={ value && typeof value === 'object' ? value : {} }
458
+ onChange={ onChange }
459
+ label={ label }
460
+ hideLabelFromVision={ hideLabelFromVision }
461
+ options={ TIME_UNITS_OPTIONS[ operator ] }
462
+ />
463
+ );
464
+ }
465
+
466
+ if ( operator === OPERATOR_BETWEEN ) {
467
+ let dateRangeValue: [ string, string ] | undefined;
468
+ if (
469
+ Array.isArray( value ) &&
470
+ value.length === 2 &&
471
+ value.every( ( date ) => typeof date === 'string' )
472
+ ) {
473
+ // Ensure the value is expected format
474
+ dateRangeValue = value as unknown as [ string, string ];
475
+ }
476
+
477
+ return (
478
+ <CalendarDateRangeControl
479
+ className="dataviews-controls__date"
480
+ id={ id }
481
+ value={ dateRangeValue }
482
+ onChange={ onChange }
483
+ label={ label }
484
+ hideLabelFromVision={ hideLabelFromVision }
485
+ />
486
+ );
487
+ }
488
+
489
+ return (
490
+ <CalendarDateControl
491
+ className="dataviews-controls__date"
492
+ id={ id }
493
+ value={ typeof value === 'string' ? value : undefined }
494
+ onChange={ onChange }
495
+ label={ label }
496
+ hideLabelFromVision={ hideLabelFromVision }
497
+ />
498
+ );
499
+ }
@@ -1,103 +1,17 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import {
5
- BaseControl,
6
- TimePicker,
7
- VisuallyHidden,
8
- SelectControl,
9
- __experimentalNumberControl as NumberControl,
10
- __experimentalHStack as HStack,
11
- } from '@wordpress/components';
4
+ import { BaseControl, TimePicker, VisuallyHidden } from '@wordpress/components';
12
5
  import { useCallback } from '@wordpress/element';
13
- import { __ } from '@wordpress/i18n';
14
6
 
15
7
  /**
16
8
  * Internal dependencies
17
9
  */
18
10
  import type { DataFormControlProps } from '../types';
19
11
  import { OPERATOR_IN_THE_PAST, OPERATOR_OVER } from '../constants';
20
-
21
- const TIME_UNITS_OPTIONS = {
22
- [ OPERATOR_IN_THE_PAST ]: [
23
- { value: 'days', label: __( 'Days' ) },
24
- { value: 'weeks', label: __( 'Weeks' ) },
25
- { value: 'months', label: __( 'Months' ) },
26
- { value: 'years', label: __( 'Years' ) },
27
- ],
28
- [ OPERATOR_OVER ]: [
29
- { value: 'days', label: __( 'Days ago' ) },
30
- { value: 'weeks', label: __( 'Weeks ago' ) },
31
- { value: 'months', label: __( 'Months ago' ) },
32
- { value: 'years', label: __( 'Years ago' ) },
33
- ],
34
- };
35
-
36
- function RelativeDateControls( {
37
- id,
38
- value,
39
- onChange,
40
- label,
41
- hideLabelFromVision,
42
- options,
43
- }: {
44
- id: string;
45
- value: { value?: string | number; unit?: string };
46
- onChange: ( value: any ) => void;
47
- label: string;
48
- hideLabelFromVision?: boolean;
49
- options: { value: string; label: string }[];
50
- } ) {
51
- const { value: relValue = '', unit = options[ 0 ].value } = value;
52
-
53
- const onChangeValue = useCallback(
54
- ( newValue: string | undefined ) =>
55
- onChange( {
56
- [ id ]: { value: Number( newValue ), unit },
57
- } ),
58
- [ id, onChange, unit ]
59
- );
60
-
61
- const onChangeUnit = useCallback(
62
- ( newUnit: string | undefined ) =>
63
- onChange( {
64
- [ id ]: { value: relValue, unit: newUnit },
65
- } ),
66
- [ id, onChange, relValue ]
67
- );
68
-
69
- return (
70
- <BaseControl
71
- id={ id }
72
- __nextHasNoMarginBottom
73
- className="dataviews-controls__datetime"
74
- label={ label }
75
- hideLabelFromVision={ hideLabelFromVision }
76
- >
77
- <HStack spacing={ 2.5 }>
78
- <NumberControl
79
- __next40pxDefaultSize
80
- className="dataviews-controls__datetime-number"
81
- spinControls="none"
82
- min={ 1 }
83
- step={ 1 }
84
- value={ relValue }
85
- onChange={ onChangeValue }
86
- />
87
- <SelectControl
88
- className="dataviews-controls__datetime-unit"
89
- __next40pxDefaultSize
90
- __nextHasNoMarginBottom
91
- label={ __( 'Unit' ) }
92
- value={ unit }
93
- options={ options }
94
- onChange={ onChangeUnit }
95
- hideLabelFromVision
96
- />
97
- </HStack>
98
- </BaseControl>
99
- );
100
- }
12
+ import RelativeDateControl, {
13
+ TIME_UNITS_OPTIONS,
14
+ } from './relative-date-control';
101
15
 
102
16
  export default function DateTime< Item >( {
103
17
  data,
@@ -116,7 +30,7 @@ export default function DateTime< Item >( {
116
30
 
117
31
  if ( operator === OPERATOR_IN_THE_PAST || operator === OPERATOR_OVER ) {
118
32
  return (
119
- <RelativeDateControls
33
+ <RelativeDateControl
120
34
  id={ id }
121
35
  value={ value && typeof value === 'object' ? value : {} }
122
36
  onChange={ onChange }
@@ -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 Email< Item >( {
13
16
  data,
@@ -27,7 +30,21 @@ export default function Email< 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
  type="email"
32
49
  label={ label }
33
50
  placeholder={ placeholder }
@@ -13,6 +13,7 @@ import type {
13
13
  } from '../types';
14
14
  import checkbox from './checkbox';
15
15
  import datetime from './datetime';
16
+ import date from './date';
16
17
  import email from './email';
17
18
  import integer from './integer';
18
19
  import radio from './radio';
@@ -29,6 +30,7 @@ const FORM_CONTROLS: FormControls = {
29
30
  boolean,
30
31
  checkbox,
31
32
  datetime,
33
+ date,
32
34
  email,
33
35
  integer,
34
36
  radio,