@wordpress/dataviews 11.2.1-next.v.0 → 11.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/build/components/dataform-controls/combobox.cjs +80 -0
  3. package/build/components/dataform-controls/combobox.cjs.map +7 -0
  4. package/build/components/dataform-controls/date.cjs +35 -10
  5. package/build/components/dataform-controls/date.cjs.map +2 -2
  6. package/build/components/dataform-controls/index.cjs +2 -0
  7. package/build/components/dataform-controls/index.cjs.map +3 -3
  8. package/build/components/dataform-layouts/card/index.cjs +58 -3
  9. package/build/components/dataform-layouts/card/index.cjs.map +3 -3
  10. package/build/components/dataform-layouts/panel/dropdown.cjs +18 -8
  11. package/build/components/dataform-layouts/panel/dropdown.cjs.map +3 -3
  12. package/build/components/dataform-layouts/panel/index.cjs +5 -3
  13. package/build/components/dataform-layouts/panel/index.cjs.map +2 -2
  14. package/build/components/dataform-layouts/panel/modal.cjs +16 -10
  15. package/build/components/dataform-layouts/panel/modal.cjs.map +3 -3
  16. package/build/components/dataviews-bulk-actions/index.cjs +16 -18
  17. package/build/components/dataviews-bulk-actions/index.cjs.map +3 -3
  18. package/build/components/dataviews-item-actions/index.cjs +4 -1
  19. package/build/components/dataviews-item-actions/index.cjs.map +2 -2
  20. package/build/components/dataviews-layouts/activity/activity-item.cjs +6 -1
  21. package/build/components/dataviews-layouts/activity/activity-item.cjs.map +2 -2
  22. package/build/components/dataviews-layouts/table/column-header-menu.cjs +73 -66
  23. package/build/components/dataviews-layouts/table/column-header-menu.cjs.map +2 -2
  24. package/build/components/dataviews-layouts/table/index.cjs +3 -2
  25. package/build/components/dataviews-layouts/table/index.cjs.map +2 -2
  26. package/build/components/dataviews-picker-footer/index.cjs +8 -15
  27. package/build/components/dataviews-picker-footer/index.cjs.map +3 -3
  28. package/build/field-types/index.cjs +2 -0
  29. package/build/field-types/index.cjs.map +3 -3
  30. package/build/field-types/utils/get-filter.cjs +36 -0
  31. package/build/field-types/utils/get-filter.cjs.map +7 -0
  32. package/build/hooks/use-report-validity.cjs +39 -0
  33. package/build/hooks/use-report-validity.cjs.map +7 -0
  34. package/build/types/field-api.cjs.map +1 -1
  35. package/build/utils/filter-sort-and-paginate.cjs +6 -174
  36. package/build/utils/filter-sort-and-paginate.cjs.map +2 -2
  37. package/build/utils/get-footer-message.cjs +49 -0
  38. package/build/utils/get-footer-message.cjs.map +7 -0
  39. package/build/utils/operators.cjs +203 -24
  40. package/build/utils/operators.cjs.map +2 -2
  41. package/build-module/components/dataform-controls/combobox.mjs +49 -0
  42. package/build-module/components/dataform-controls/combobox.mjs.map +7 -0
  43. package/build-module/components/dataform-controls/date.mjs +35 -10
  44. package/build-module/components/dataform-controls/date.mjs.map +2 -2
  45. package/build-module/components/dataform-controls/index.mjs +2 -0
  46. package/build-module/components/dataform-controls/index.mjs.map +2 -2
  47. package/build-module/components/dataform-layouts/card/index.mjs +59 -3
  48. package/build-module/components/dataform-layouts/card/index.mjs.map +2 -2
  49. package/build-module/components/dataform-layouts/panel/dropdown.mjs +20 -10
  50. package/build-module/components/dataform-layouts/panel/dropdown.mjs.map +2 -2
  51. package/build-module/components/dataform-layouts/panel/index.mjs +5 -3
  52. package/build-module/components/dataform-layouts/panel/index.mjs.map +2 -2
  53. package/build-module/components/dataform-layouts/panel/modal.mjs +18 -12
  54. package/build-module/components/dataform-layouts/panel/modal.mjs.map +2 -2
  55. package/build-module/components/dataviews-bulk-actions/index.mjs +17 -19
  56. package/build-module/components/dataviews-bulk-actions/index.mjs.map +2 -2
  57. package/build-module/components/dataviews-item-actions/index.mjs +4 -1
  58. package/build-module/components/dataviews-item-actions/index.mjs.map +2 -2
  59. package/build-module/components/dataviews-layouts/activity/activity-item.mjs +6 -1
  60. package/build-module/components/dataviews-layouts/activity/activity-item.mjs.map +2 -2
  61. package/build-module/components/dataviews-layouts/table/column-header-menu.mjs +74 -67
  62. package/build-module/components/dataviews-layouts/table/column-header-menu.mjs.map +2 -2
  63. package/build-module/components/dataviews-layouts/table/index.mjs +4 -3
  64. package/build-module/components/dataviews-layouts/table/index.mjs.map +2 -2
  65. package/build-module/components/dataviews-picker-footer/index.mjs +8 -15
  66. package/build-module/components/dataviews-picker-footer/index.mjs.map +2 -2
  67. package/build-module/field-types/index.mjs +2 -0
  68. package/build-module/field-types/index.mjs.map +2 -2
  69. package/build-module/field-types/utils/get-filter.mjs +15 -0
  70. package/build-module/field-types/utils/get-filter.mjs.map +7 -0
  71. package/build-module/hooks/use-report-validity.mjs +18 -0
  72. package/build-module/hooks/use-report-validity.mjs.map +7 -0
  73. package/build-module/utils/filter-sort-and-paginate.mjs +7 -198
  74. package/build-module/utils/filter-sort-and-paginate.mjs.map +2 -2
  75. package/build-module/utils/get-footer-message.mjs +28 -0
  76. package/build-module/utils/get-footer-message.mjs.map +7 -0
  77. package/build-module/utils/operators.mjs +203 -24
  78. package/build-module/utils/operators.mjs.map +2 -2
  79. package/build-style/style-rtl.css +15 -16
  80. package/build-style/style.css +15 -16
  81. package/build-types/components/dataform-controls/combobox.d.ts +6 -0
  82. package/build-types/components/dataform-controls/combobox.d.ts.map +1 -0
  83. package/build-types/components/dataform-controls/date.d.ts.map +1 -1
  84. package/build-types/components/dataform-controls/index.d.ts.map +1 -1
  85. package/build-types/components/dataform-layouts/card/index.d.ts +2 -0
  86. package/build-types/components/dataform-layouts/card/index.d.ts.map +1 -1
  87. package/build-types/components/dataform-layouts/panel/dropdown.d.ts +3 -2
  88. package/build-types/components/dataform-layouts/panel/dropdown.d.ts.map +1 -1
  89. package/build-types/components/dataform-layouts/panel/index.d.ts.map +1 -1
  90. package/build-types/components/dataform-layouts/panel/modal.d.ts +3 -2
  91. package/build-types/components/dataform-layouts/panel/modal.d.ts.map +1 -1
  92. package/build-types/components/dataviews-bulk-actions/index.d.ts.map +1 -1
  93. package/build-types/components/dataviews-item-actions/index.d.ts.map +1 -1
  94. package/build-types/components/dataviews-layouts/activity/activity-item.d.ts.map +1 -1
  95. package/build-types/components/dataviews-layouts/table/column-header-menu.d.ts.map +1 -1
  96. package/build-types/components/dataviews-layouts/table/index.d.ts.map +1 -1
  97. package/build-types/components/dataviews-picker-footer/index.d.ts.map +1 -1
  98. package/build-types/dataform/stories/content.story.d.ts +14 -0
  99. package/build-types/dataform/stories/content.story.d.ts.map +1 -0
  100. package/build-types/dataform/stories/index.story.d.ts +1 -1
  101. package/build-types/dataform/stories/index.story.d.ts.map +1 -1
  102. package/build-types/dataform/stories/validation.d.ts +1 -1
  103. package/build-types/dataform/stories/validation.d.ts.map +1 -1
  104. package/build-types/dataviews/stories/fixtures.d.ts.map +1 -1
  105. package/build-types/dataviews/stories/index.story.d.ts +4 -1
  106. package/build-types/dataviews/stories/index.story.d.ts.map +1 -1
  107. package/build-types/dataviews/stories/layout-custom.d.ts +11 -0
  108. package/build-types/dataviews/stories/layout-custom.d.ts.map +1 -0
  109. package/build-types/dataviews-picker/stories/fixtures.d.ts.map +1 -1
  110. package/build-types/dataviews-picker/stories/index.story.d.ts +1 -1
  111. package/build-types/dataviews-picker/stories/index.story.d.ts.map +1 -1
  112. package/build-types/field-types/index.d.ts.map +1 -1
  113. package/build-types/field-types/stories/index.story.d.ts +1 -1
  114. package/build-types/field-types/stories/index.story.d.ts.map +1 -1
  115. package/build-types/field-types/utils/get-filter.d.ts +7 -0
  116. package/build-types/field-types/utils/get-filter.d.ts.map +1 -0
  117. package/build-types/hooks/use-report-validity.d.ts +14 -0
  118. package/build-types/hooks/use-report-validity.d.ts.map +1 -0
  119. package/build-types/types/field-api.d.ts +3 -0
  120. package/build-types/types/field-api.d.ts.map +1 -1
  121. package/build-types/utils/filter-sort-and-paginate.d.ts.map +1 -1
  122. package/build-types/utils/get-footer-message.d.ts +10 -0
  123. package/build-types/utils/get-footer-message.d.ts.map +1 -0
  124. package/build-types/utils/operators.d.ts +2 -1
  125. package/build-types/utils/operators.d.ts.map +1 -1
  126. package/build-wp/index.js +2730 -2179
  127. package/package.json +22 -20
  128. package/src/components/dataform-controls/combobox.tsx +58 -0
  129. package/src/components/dataform-controls/date.tsx +45 -10
  130. package/src/components/dataform-controls/index.tsx +2 -0
  131. package/src/components/dataform-layouts/card/index.tsx +81 -3
  132. package/src/components/dataform-layouts/panel/dropdown.tsx +26 -11
  133. package/src/components/dataform-layouts/panel/index.tsx +6 -4
  134. package/src/components/dataform-layouts/panel/modal.tsx +24 -12
  135. package/src/components/dataviews-bulk-actions/index.tsx +23 -20
  136. package/src/components/dataviews-bulk-actions/style.scss +0 -3
  137. package/src/components/dataviews-item-actions/index.tsx +6 -1
  138. package/src/components/dataviews-layouts/activity/activity-item.tsx +8 -1
  139. package/src/components/dataviews-layouts/table/column-header-menu.tsx +99 -73
  140. package/src/components/dataviews-layouts/table/index.tsx +12 -3
  141. package/src/components/dataviews-layouts/table/style.scss +14 -7
  142. package/src/components/dataviews-picker-footer/index.tsx +8 -18
  143. package/src/dataform/stories/content.story.mdx +159 -0
  144. package/src/dataform/stories/content.story.tsx +390 -0
  145. package/src/dataform/stories/index.story.tsx +8 -1
  146. package/src/dataform/stories/validation.tsx +98 -5
  147. package/src/dataviews/stories/best-practices.story.mdx +55 -0
  148. package/src/dataviews/stories/fixtures.tsx +1 -3
  149. package/src/dataviews/stories/index.story.tsx +6 -1
  150. package/src/dataviews/stories/layout-custom.tsx +140 -0
  151. package/src/dataviews/test/dataviews.tsx +66 -1
  152. package/src/dataviews-picker/stories/fixtures.tsx +1 -3
  153. package/src/dataviews-picker/stories/index.story.tsx +1 -1
  154. package/src/field-types/index.tsx +2 -0
  155. package/src/field-types/stories/index.story.tsx +2 -0
  156. package/src/field-types/utils/get-filter.ts +18 -0
  157. package/src/hooks/use-report-validity.ts +32 -0
  158. package/src/types/field-api.ts +11 -0
  159. package/src/utils/filter-sort-and-paginate.ts +11 -306
  160. package/src/utils/get-footer-message.ts +41 -0
  161. package/src/utils/operators.tsx +303 -31
@@ -0,0 +1,390 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useMemo, useState, useRef, useEffect } from '@wordpress/element';
5
+ import { Stack } from '@wordpress/ui';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import type { Meta, StoryObj } from '@storybook/react-vite';
11
+ import DataForm from '../index';
12
+ import useFormValidity from '../../hooks/use-form-validity';
13
+ import type { Field, Form } from '../../types';
14
+
15
+ const meta: Meta< typeof DataForm > = {
16
+ title: 'DataViews/DataForm/Content',
17
+ component: DataForm,
18
+ parameters: {
19
+ controls: { disable: true },
20
+ },
21
+ tags: [ '!dev' /* Hide individual story pages from sidebar */ ],
22
+ };
23
+ export default meta;
24
+
25
+ type Story = StoryObj< typeof DataForm >;
26
+
27
+ type SampleData = {
28
+ name: string;
29
+ email: string;
30
+ phone: string;
31
+ };
32
+
33
+ export const Labels: Story = {
34
+ render: () => {
35
+ const [ data, setData ] = useState< SampleData >( {
36
+ name: '',
37
+ email: '',
38
+ phone: '',
39
+ } );
40
+
41
+ const fields: Field< SampleData >[] = useMemo(
42
+ () => [
43
+ {
44
+ id: 'name',
45
+ label: 'Name',
46
+ type: 'text',
47
+ },
48
+ {
49
+ id: 'email',
50
+ label: 'Email',
51
+ type: 'email',
52
+ },
53
+ {
54
+ id: 'phone',
55
+ label: 'Phone number',
56
+ type: 'telephone',
57
+ },
58
+ ],
59
+ []
60
+ );
61
+
62
+ const form: Form = useMemo(
63
+ () => ( {
64
+ layout: { type: 'regular' },
65
+ fields: [ 'name', 'email', 'phone' ],
66
+ } ),
67
+ []
68
+ );
69
+
70
+ return (
71
+ <Stack direction="column" gap="md">
72
+ <DataForm< SampleData >
73
+ data={ data }
74
+ fields={ fields }
75
+ form={ form }
76
+ onChange={ ( edits ) =>
77
+ setData( ( prev ) => ( { ...prev, ...edits } ) )
78
+ }
79
+ />
80
+ </Stack>
81
+ );
82
+ },
83
+ };
84
+
85
+ type HelpTextData = {
86
+ name: string;
87
+ email: string;
88
+ phone: string;
89
+ };
90
+
91
+ export const HelpText: Story = {
92
+ render: () => {
93
+ const [ data, setData ] = useState< HelpTextData >( {
94
+ name: '',
95
+ email: '',
96
+ phone: '',
97
+ } );
98
+
99
+ const fields: Field< HelpTextData >[] = useMemo(
100
+ () => [
101
+ {
102
+ id: 'name',
103
+ label: 'Name',
104
+ type: 'text',
105
+ placeholder: 'Jane Doe',
106
+ description:
107
+ 'Enter your full legal name as it appears on official documents.',
108
+ },
109
+ {
110
+ id: 'email',
111
+ label: 'Email',
112
+ type: 'email',
113
+ placeholder: 'you@example.com',
114
+ description:
115
+ 'We will use this to send you important account notifications and updates.',
116
+ },
117
+ {
118
+ id: 'phone',
119
+ label: 'Phone number',
120
+ type: 'telephone',
121
+ placeholder: '+1 (555) 123-4567',
122
+ description:
123
+ 'Include your country code. This number will be used for account verification.',
124
+ },
125
+ ],
126
+ []
127
+ );
128
+
129
+ const form: Form = useMemo(
130
+ () => ( {
131
+ layout: { type: 'regular' },
132
+ fields: [ 'name', 'email', 'phone' ],
133
+ } ),
134
+ []
135
+ );
136
+
137
+ return (
138
+ <Stack direction="column" gap="md">
139
+ <DataForm< HelpTextData >
140
+ data={ data }
141
+ fields={ fields }
142
+ form={ form }
143
+ onChange={ ( edits ) =>
144
+ setData( ( prev ) => ( { ...prev, ...edits } ) )
145
+ }
146
+ />
147
+ </Stack>
148
+ );
149
+ },
150
+ };
151
+
152
+ type ValidationMessagesData = {
153
+ name: string;
154
+ email: string;
155
+ phone: string;
156
+ };
157
+
158
+ export const ValidationMessages: Story = {
159
+ render: () => {
160
+ const [ data, setData ] = useState< ValidationMessagesData >( {
161
+ name: '',
162
+ email: 'invalid-email',
163
+ phone: '123',
164
+ } );
165
+
166
+ const fields: Field< ValidationMessagesData >[] = useMemo(
167
+ () => [
168
+ {
169
+ id: 'name',
170
+ label: 'Name',
171
+ type: 'text',
172
+ placeholder: 'Jane Doe',
173
+ isValid: {
174
+ required: true,
175
+ },
176
+ },
177
+ {
178
+ id: 'email',
179
+ label: 'Email',
180
+ type: 'email',
181
+ placeholder: 'you@example.com',
182
+ isValid: {
183
+ required: true,
184
+ custom: ( item ) => {
185
+ if ( ! item.email ) {
186
+ return null;
187
+ }
188
+ if ( ! item.email.includes( '@' ) ) {
189
+ return 'Please enter a valid email address.';
190
+ }
191
+ return null;
192
+ },
193
+ },
194
+ },
195
+ {
196
+ id: 'phone',
197
+ label: 'Phone number',
198
+ type: 'telephone',
199
+ placeholder: '+1 (555) 123-4567',
200
+ isValid: {
201
+ required: true,
202
+ custom: ( item ) => {
203
+ if ( ! item.phone ) {
204
+ return null;
205
+ }
206
+ if ( item.phone.length < 10 ) {
207
+ return 'Phone number must be at least 10 digits long.';
208
+ }
209
+ return null;
210
+ },
211
+ },
212
+ },
213
+ ],
214
+ []
215
+ );
216
+
217
+ const form: Form = useMemo(
218
+ () => ( {
219
+ layout: { type: 'regular' },
220
+ fields: [ 'name', 'email', 'phone' ],
221
+ } ),
222
+ []
223
+ );
224
+
225
+ const { validity } = useFormValidity( data, fields, form );
226
+ const containerRef = useRef< HTMLDivElement >( null );
227
+
228
+ // Show validation messages on load without focusing
229
+ useEffect( () => {
230
+ if ( validity && containerRef.current ) {
231
+ const inputs = containerRef.current.querySelectorAll( 'input' );
232
+ inputs.forEach( ( input ) => {
233
+ // Dispatch 'invalid' event to trigger the validation message display
234
+ input.dispatchEvent(
235
+ new Event( 'invalid', { bubbles: false } )
236
+ );
237
+ } );
238
+ }
239
+ }, [ validity ] );
240
+
241
+ return (
242
+ <div ref={ containerRef }>
243
+ <Stack direction="column" gap="lg">
244
+ <DataForm< ValidationMessagesData >
245
+ data={ data }
246
+ fields={ fields }
247
+ form={ form }
248
+ validity={ validity }
249
+ onChange={ ( edits ) =>
250
+ setData( ( prev ) => ( { ...prev, ...edits } ) )
251
+ }
252
+ />
253
+ </Stack>
254
+ </div>
255
+ );
256
+ },
257
+ };
258
+
259
+ type HighLevelHelpTextData = {
260
+ name: string;
261
+ email: string;
262
+ phone: string;
263
+ };
264
+
265
+ export const HighLevelHelpText: Story = {
266
+ render: () => {
267
+ const [ data, setData ] = useState< HighLevelHelpTextData >( {
268
+ name: '',
269
+ email: '',
270
+ phone: '',
271
+ } );
272
+
273
+ const fields: Field< HighLevelHelpTextData >[] = useMemo(
274
+ () => [
275
+ {
276
+ id: 'name',
277
+ label: 'Name',
278
+ type: 'text',
279
+ },
280
+ {
281
+ id: 'email',
282
+ label: 'Email',
283
+ type: 'email',
284
+ },
285
+ {
286
+ id: 'phone',
287
+ label: 'Phone number',
288
+ type: 'telephone',
289
+ },
290
+ ],
291
+ []
292
+ );
293
+
294
+ const form: Form = useMemo(
295
+ () => ( {
296
+ layout: { type: 'regular' },
297
+ fields: [
298
+ {
299
+ id: 'accountForm',
300
+ label: 'Account Information',
301
+ description:
302
+ 'We collect this information to create your account and provide personalized services. Your data will be kept secure and used only for account management and service improvements.',
303
+ children: [ 'name', 'email', 'phone' ],
304
+ layout: {
305
+ isCollapsible: false,
306
+ summary: 'account-form',
307
+ type: 'card',
308
+ withHeader: true,
309
+ },
310
+ },
311
+ ],
312
+ } ),
313
+ []
314
+ );
315
+
316
+ return (
317
+ <Stack direction="column" gap="md">
318
+ <DataForm< HighLevelHelpTextData >
319
+ data={ data }
320
+ fields={ fields }
321
+ form={ form }
322
+ onChange={ ( edits ) =>
323
+ setData( ( prev ) => ( { ...prev, ...edits } ) )
324
+ }
325
+ />
326
+ </Stack>
327
+ );
328
+ },
329
+ };
330
+
331
+ type PlaceholdersData = {
332
+ name: string;
333
+ email: string;
334
+ phone: string;
335
+ };
336
+
337
+ export const Placeholders: Story = {
338
+ render: () => {
339
+ const [ data, setData ] = useState< PlaceholdersData >( {
340
+ name: '',
341
+ email: '',
342
+ phone: '',
343
+ } );
344
+
345
+ const fields: Field< PlaceholdersData >[] = useMemo(
346
+ () => [
347
+ {
348
+ id: 'name',
349
+ label: 'Name',
350
+ type: 'text',
351
+ placeholder: 'Jane Doe',
352
+ },
353
+ {
354
+ id: 'email',
355
+ label: 'Email',
356
+ type: 'email',
357
+ placeholder: 'you@example.com',
358
+ },
359
+ {
360
+ id: 'phone',
361
+ label: 'Phone number',
362
+ type: 'telephone',
363
+ placeholder: '+1 (555) 123-4567',
364
+ },
365
+ ],
366
+ []
367
+ );
368
+
369
+ const form: Form = useMemo(
370
+ () => ( {
371
+ layout: { type: 'regular' },
372
+ fields: [ 'name', 'email', 'phone' ],
373
+ } ),
374
+ []
375
+ );
376
+
377
+ return (
378
+ <Stack direction="column" gap="md">
379
+ <DataForm< PlaceholdersData >
380
+ data={ data }
381
+ fields={ fields }
382
+ form={ form }
383
+ onChange={ ( edits ) =>
384
+ setData( ( prev ) => ( { ...prev, ...edits } ) )
385
+ }
386
+ />
387
+ </Stack>
388
+ );
389
+ },
390
+ };
@@ -100,7 +100,14 @@ export const Validation = {
100
100
  layout: {
101
101
  control: { type: 'select' },
102
102
  description: 'Choose the form layout type.',
103
- options: [ 'regular', 'panel', 'card', 'details' ],
103
+ options: [
104
+ 'regular',
105
+ 'panel-dropdown',
106
+ 'panel-modal',
107
+ 'card-collapsible',
108
+ 'card-not-collapsible',
109
+ 'details',
110
+ ],
104
111
  },
105
112
  required: {
106
113
  control: { type: 'boolean' },
@@ -86,7 +86,13 @@ const ValidationComponent = ( {
86
86
  custom: 'sync' | 'async' | 'none';
87
87
  pattern: boolean;
88
88
  minMax: boolean;
89
- layout: 'regular' | 'panel' | 'card' | 'details';
89
+ layout:
90
+ | 'regular'
91
+ | 'panel-dropdown'
92
+ | 'panel-modal'
93
+ | 'card-collapsible'
94
+ | 'card-not-collapsible'
95
+ | 'details';
90
96
  } ) => {
91
97
  type ValidatedItem = {
92
98
  text: string;
@@ -106,6 +112,7 @@ const ValidationComponent = ( {
106
112
  password: string;
107
113
  toggle?: boolean;
108
114
  toggleGroup?: string;
115
+ combobox?: string;
109
116
  date?: string;
110
117
  dateRange?: string;
111
118
  datetime?: string;
@@ -129,6 +136,7 @@ const ValidationComponent = ( {
129
136
  password: 'secretpassword123',
130
137
  toggle: undefined,
131
138
  toggleGroup: undefined,
139
+ combobox: undefined,
132
140
  date: undefined,
133
141
  dateRange: undefined,
134
142
  datetime: undefined,
@@ -242,6 +250,25 @@ const ValidationComponent = ( {
242
250
  );
243
251
  break;
244
252
 
253
+ case 'combobox':
254
+ promiseCache[ fieldId ] = new Promise( ( resolve ) =>
255
+ setTimeout(
256
+ () =>
257
+ resolve( [
258
+ { value: 'apple', label: 'Apple' },
259
+ { value: 'banana', label: 'Banana' },
260
+ { value: 'cherry', label: 'Cherry' },
261
+ { value: 'date', label: 'Date' },
262
+ {
263
+ value: 'elderberry',
264
+ label: 'Elderberry',
265
+ },
266
+ ] ),
267
+ 3500
268
+ )
269
+ );
270
+ break;
271
+
245
272
  default:
246
273
  throw new Error( `Unknown field ID: ${ fieldId }` );
247
274
  }
@@ -364,6 +391,14 @@ const ValidationComponent = ( {
364
391
  return null;
365
392
  };
366
393
 
394
+ const customComboboxRule = ( value: ValidatedItem ) => {
395
+ if ( value.combobox !== 'apple' ) {
396
+ return 'Value must be Apple.';
397
+ }
398
+
399
+ return null;
400
+ };
401
+
367
402
  const customPasswordRule = ( value: ValidatedItem ) => {
368
403
  if ( value.password.length < 8 ) {
369
404
  return 'Password must be at least 8 characters long.';
@@ -768,6 +803,48 @@ const ValidationComponent = ( {
768
803
  custom: maybeCustomRule( customToggleGroupRule ),
769
804
  },
770
805
  },
806
+ {
807
+ id: 'combobox',
808
+ type: 'text',
809
+ Edit: 'combobox',
810
+ label: 'Combobox',
811
+ placeholder: 'Search and select a fruit',
812
+ elements:
813
+ elements === 'async'
814
+ ? undefined
815
+ : [
816
+ { value: 'apple', label: 'Apple' },
817
+ { value: 'banana', label: 'Banana' },
818
+ { value: 'blueberry', label: 'Blueberry' },
819
+ { value: 'cherry', label: 'Cherry' },
820
+ { value: 'date', label: 'Date' },
821
+ { value: 'elderberry', label: 'Elderberry' },
822
+ { value: 'fig', label: 'Fig' },
823
+ { value: 'grape', label: 'Grape' },
824
+ { value: 'honeydew', label: 'Honeydew' },
825
+ { value: 'kiwi', label: 'Kiwi' },
826
+ { value: 'lemon', label: 'Lemon' },
827
+ { value: 'mango', label: 'Mango' },
828
+ { value: 'nectarine', label: 'Nectarine' },
829
+ { value: 'orange', label: 'Orange' },
830
+ { value: 'papaya', label: 'Papaya' },
831
+ { value: 'pear', label: 'Pear' },
832
+ { value: 'quince', label: 'Quince' },
833
+ { value: 'raspberry', label: 'Raspberry' },
834
+ { value: 'strawberry', label: 'Strawberry' },
835
+ { value: 'tangerine', label: 'Tangerine' },
836
+ { value: 'watermelon', label: 'Watermelon' },
837
+ ],
838
+ getElements:
839
+ elements === 'async'
840
+ ? getElements( 'combobox' )
841
+ : undefined,
842
+ isValid: {
843
+ required,
844
+ elements: elements !== 'none' ? true : false,
845
+ custom: maybeCustomRule( customComboboxRule ),
846
+ },
847
+ },
771
848
  {
772
849
  id: 'date',
773
850
  type: 'date',
@@ -853,6 +930,7 @@ const ValidationComponent = ( {
853
930
  'password',
854
931
  'textarea',
855
932
  'select',
933
+ 'combobox',
856
934
  'textWithRadio',
857
935
  'boolean',
858
936
  'toggle',
@@ -885,7 +963,7 @@ const ValidationComponent = ( {
885
963
  {
886
964
  id: 'selectFields',
887
965
  label: 'Selection Fields',
888
- children: [ 'select', 'textWithRadio' ],
966
+ children: [ 'select', 'combobox', 'textWithRadio' ],
889
967
  },
890
968
  {
891
969
  id: 'booleanFields',
@@ -901,9 +979,16 @@ const ValidationComponent = ( {
901
979
  },
902
980
  ];
903
981
 
904
- if ( layout === 'panel' ) {
982
+ if ( layout === 'panel-dropdown' ) {
905
983
  return {
906
- layout: { type: 'panel' as const },
984
+ layout: { type: 'panel' as const, openAs: 'dropdown' as const },
985
+ fields: groupedFields,
986
+ };
987
+ }
988
+
989
+ if ( layout === 'panel-modal' ) {
990
+ return {
991
+ layout: { type: 'panel' as const, openAs: 'modal' as const },
907
992
  fields: groupedFields,
908
993
  };
909
994
  }
@@ -915,8 +1000,16 @@ const ValidationComponent = ( {
915
1000
  };
916
1001
  }
917
1002
 
1003
+ if ( layout === 'card-collapsible' ) {
1004
+ return {
1005
+ layout: { type: 'card' as const },
1006
+ fields: groupedFields,
1007
+ };
1008
+ }
1009
+
1010
+ // card-not-collapsible
918
1011
  return {
919
- layout: { type: 'card' as const },
1012
+ layout: { type: 'card' as const, isCollapsible: false },
920
1013
  fields: groupedFields,
921
1014
  };
922
1015
  }, [ layout ] );
@@ -0,0 +1,55 @@
1
+ import { Meta } from '@storybook/addon-docs/blocks';
2
+
3
+ import * as DataViewsStories from './index.story';
4
+
5
+ <Meta of={ DataViewsStories } name="Best practices" />
6
+
7
+ # Data Views
8
+
9
+ Data Views allow users to display and interact with information through different layouts such as table or grid. It includes features like search, filtering, sorting, pagination, and customization options to adjust column order, items per page, and hide columns, enhancing data exploration and flexibility.
10
+
11
+ A typical flow might be:
12
+
13
+ - Use search and filters to narrow down a large collection.
14
+ - Adjust the layout and visible fields to match the task (compare, skim, or visually scan).
15
+ - Take inline actions on item metadata, or open an editing interface when more detail is needed.
16
+
17
+ ## Choosing a layout
18
+
19
+ ### When to use **List**
20
+
21
+ Use the **List** layout when:
22
+
23
+ - **Content needs the most compact layout**, whether because it's used in a constrained context, or if paired with a preview surface.
24
+ - Default information density allows for a reduced set of secondary metadata.
25
+ - The view needs to work well in **narrow spaces**.
26
+
27
+ List is a good fit for items like Pages that benefit from a live preview next to the list.
28
+
29
+ ### When to use **Grid**
30
+
31
+ Use the **Grid** layout when:
32
+
33
+ - **Visual previews across multiple items at once are important** for recognition (patterns, templates, media, products).
34
+ - You want a **card‑based experience**, with each item having its own tile (image + metadata).
35
+ - You expect more **browsing and scanning by look** than comparing raw values.
36
+
37
+ Grid works well when choosing “by look” is more important than comparing precise numbers or dates.
38
+
39
+ ### When to use **Table**
40
+
41
+ Use the **Table** layout when:
42
+
43
+ - Users need to **compare multiple attributes across items** at once.
44
+ - **Column headers and alignment** are critical for understanding the data.
45
+ - The task involves working with structured data.
46
+
47
+ Table is ideal for dense, structured information where sorting and scanning down aligned columns is the primary workflow, such as posts or comments where metadata is important for choosing the next action.
48
+
49
+ ## When to use something else
50
+
51
+ Data Views are designed for **browsing and managing collections**. In other situations, consider other components in the `@wordpress/dataviews` package:
52
+
53
+ - **Use `DataForm`** when the primary workflow primarily relates to **creating or editing a single item** at a level that doesn't require a full editor.
54
+ - **Use `DataViewsPicker`** when the goal is **selecting one or more items** and returning that selection to another part of the UI. This is a better fit for dialogs and sidebars where the outcome is a selection, not ongoing management of a collection, e.g. inserting from a media library.
55
+
@@ -377,9 +377,7 @@ export const fields: Field< SpaceObject >[] = [
377
377
  </Stack>
378
378
  ),
379
379
  render: ( { item } ) => {
380
- return (
381
- <img src={ item.image } alt="" style={ { width: '100%' } } />
382
- );
380
+ return <img src={ item.image } alt="" />;
383
381
  },
384
382
  },
385
383
  {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import type { Meta } from '@storybook/react-webpack5';
4
+ import type { Meta } from '@storybook/react-vite';
5
5
 
6
6
  /**
7
7
  * Internal dependencies
@@ -11,6 +11,7 @@ import LayoutActivityComponent from './layout-activity';
11
11
  import LayoutTableComponent from './layout-table';
12
12
  import LayoutGridComponent from './layout-grid';
13
13
  import LayoutListComponent from './layout-list';
14
+ import LayoutCustomComponent from './layout-custom';
14
15
  import InfiniteScrollComponent from './infinite-scroll';
15
16
  import WithCardComponent from './with-card';
16
17
  import FreeCompositionComponent from './free-composition';
@@ -191,6 +192,10 @@ export const LayoutActivity = {
191
192
  },
192
193
  };
193
194
 
195
+ export const LayoutCustom = {
196
+ render: LayoutCustomComponent,
197
+ };
198
+
194
199
  export const Empty = {
195
200
  render: EmptyComponent,
196
201
  args: {