@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.
- package/CHANGELOG.md +27 -1
- package/build/components/dataform-controls/combobox.cjs +80 -0
- package/build/components/dataform-controls/combobox.cjs.map +7 -0
- package/build/components/dataform-controls/date.cjs +35 -10
- package/build/components/dataform-controls/date.cjs.map +2 -2
- package/build/components/dataform-controls/index.cjs +2 -0
- package/build/components/dataform-controls/index.cjs.map +3 -3
- package/build/components/dataform-layouts/card/index.cjs +58 -3
- package/build/components/dataform-layouts/card/index.cjs.map +3 -3
- package/build/components/dataform-layouts/panel/dropdown.cjs +18 -8
- package/build/components/dataform-layouts/panel/dropdown.cjs.map +3 -3
- package/build/components/dataform-layouts/panel/index.cjs +5 -3
- package/build/components/dataform-layouts/panel/index.cjs.map +2 -2
- package/build/components/dataform-layouts/panel/modal.cjs +16 -10
- package/build/components/dataform-layouts/panel/modal.cjs.map +3 -3
- package/build/components/dataviews-bulk-actions/index.cjs +16 -18
- package/build/components/dataviews-bulk-actions/index.cjs.map +3 -3
- package/build/components/dataviews-item-actions/index.cjs +4 -1
- package/build/components/dataviews-item-actions/index.cjs.map +2 -2
- package/build/components/dataviews-layouts/activity/activity-item.cjs +6 -1
- package/build/components/dataviews-layouts/activity/activity-item.cjs.map +2 -2
- package/build/components/dataviews-layouts/table/column-header-menu.cjs +73 -66
- package/build/components/dataviews-layouts/table/column-header-menu.cjs.map +2 -2
- package/build/components/dataviews-layouts/table/index.cjs +3 -2
- package/build/components/dataviews-layouts/table/index.cjs.map +2 -2
- package/build/components/dataviews-picker-footer/index.cjs +8 -15
- package/build/components/dataviews-picker-footer/index.cjs.map +3 -3
- package/build/field-types/index.cjs +2 -0
- package/build/field-types/index.cjs.map +3 -3
- package/build/field-types/utils/get-filter.cjs +36 -0
- package/build/field-types/utils/get-filter.cjs.map +7 -0
- package/build/hooks/use-report-validity.cjs +39 -0
- package/build/hooks/use-report-validity.cjs.map +7 -0
- package/build/types/field-api.cjs.map +1 -1
- package/build/utils/filter-sort-and-paginate.cjs +6 -174
- package/build/utils/filter-sort-and-paginate.cjs.map +2 -2
- package/build/utils/get-footer-message.cjs +49 -0
- package/build/utils/get-footer-message.cjs.map +7 -0
- package/build/utils/operators.cjs +203 -24
- package/build/utils/operators.cjs.map +2 -2
- package/build-module/components/dataform-controls/combobox.mjs +49 -0
- package/build-module/components/dataform-controls/combobox.mjs.map +7 -0
- package/build-module/components/dataform-controls/date.mjs +35 -10
- package/build-module/components/dataform-controls/date.mjs.map +2 -2
- package/build-module/components/dataform-controls/index.mjs +2 -0
- package/build-module/components/dataform-controls/index.mjs.map +2 -2
- package/build-module/components/dataform-layouts/card/index.mjs +59 -3
- package/build-module/components/dataform-layouts/card/index.mjs.map +2 -2
- package/build-module/components/dataform-layouts/panel/dropdown.mjs +20 -10
- package/build-module/components/dataform-layouts/panel/dropdown.mjs.map +2 -2
- package/build-module/components/dataform-layouts/panel/index.mjs +5 -3
- package/build-module/components/dataform-layouts/panel/index.mjs.map +2 -2
- package/build-module/components/dataform-layouts/panel/modal.mjs +18 -12
- package/build-module/components/dataform-layouts/panel/modal.mjs.map +2 -2
- package/build-module/components/dataviews-bulk-actions/index.mjs +17 -19
- package/build-module/components/dataviews-bulk-actions/index.mjs.map +2 -2
- package/build-module/components/dataviews-item-actions/index.mjs +4 -1
- package/build-module/components/dataviews-item-actions/index.mjs.map +2 -2
- package/build-module/components/dataviews-layouts/activity/activity-item.mjs +6 -1
- package/build-module/components/dataviews-layouts/activity/activity-item.mjs.map +2 -2
- package/build-module/components/dataviews-layouts/table/column-header-menu.mjs +74 -67
- package/build-module/components/dataviews-layouts/table/column-header-menu.mjs.map +2 -2
- package/build-module/components/dataviews-layouts/table/index.mjs +4 -3
- package/build-module/components/dataviews-layouts/table/index.mjs.map +2 -2
- package/build-module/components/dataviews-picker-footer/index.mjs +8 -15
- package/build-module/components/dataviews-picker-footer/index.mjs.map +2 -2
- package/build-module/field-types/index.mjs +2 -0
- package/build-module/field-types/index.mjs.map +2 -2
- package/build-module/field-types/utils/get-filter.mjs +15 -0
- package/build-module/field-types/utils/get-filter.mjs.map +7 -0
- package/build-module/hooks/use-report-validity.mjs +18 -0
- package/build-module/hooks/use-report-validity.mjs.map +7 -0
- package/build-module/utils/filter-sort-and-paginate.mjs +7 -198
- package/build-module/utils/filter-sort-and-paginate.mjs.map +2 -2
- package/build-module/utils/get-footer-message.mjs +28 -0
- package/build-module/utils/get-footer-message.mjs.map +7 -0
- package/build-module/utils/operators.mjs +203 -24
- package/build-module/utils/operators.mjs.map +2 -2
- package/build-style/style-rtl.css +15 -16
- package/build-style/style.css +15 -16
- package/build-types/components/dataform-controls/combobox.d.ts +6 -0
- package/build-types/components/dataform-controls/combobox.d.ts.map +1 -0
- package/build-types/components/dataform-controls/date.d.ts.map +1 -1
- package/build-types/components/dataform-controls/index.d.ts.map +1 -1
- package/build-types/components/dataform-layouts/card/index.d.ts +2 -0
- package/build-types/components/dataform-layouts/card/index.d.ts.map +1 -1
- package/build-types/components/dataform-layouts/panel/dropdown.d.ts +3 -2
- package/build-types/components/dataform-layouts/panel/dropdown.d.ts.map +1 -1
- package/build-types/components/dataform-layouts/panel/index.d.ts.map +1 -1
- package/build-types/components/dataform-layouts/panel/modal.d.ts +3 -2
- package/build-types/components/dataform-layouts/panel/modal.d.ts.map +1 -1
- package/build-types/components/dataviews-bulk-actions/index.d.ts.map +1 -1
- package/build-types/components/dataviews-item-actions/index.d.ts.map +1 -1
- package/build-types/components/dataviews-layouts/activity/activity-item.d.ts.map +1 -1
- package/build-types/components/dataviews-layouts/table/column-header-menu.d.ts.map +1 -1
- package/build-types/components/dataviews-layouts/table/index.d.ts.map +1 -1
- package/build-types/components/dataviews-picker-footer/index.d.ts.map +1 -1
- package/build-types/dataform/stories/content.story.d.ts +14 -0
- package/build-types/dataform/stories/content.story.d.ts.map +1 -0
- package/build-types/dataform/stories/index.story.d.ts +1 -1
- package/build-types/dataform/stories/index.story.d.ts.map +1 -1
- package/build-types/dataform/stories/validation.d.ts +1 -1
- package/build-types/dataform/stories/validation.d.ts.map +1 -1
- package/build-types/dataviews/stories/fixtures.d.ts.map +1 -1
- package/build-types/dataviews/stories/index.story.d.ts +4 -1
- package/build-types/dataviews/stories/index.story.d.ts.map +1 -1
- package/build-types/dataviews/stories/layout-custom.d.ts +11 -0
- package/build-types/dataviews/stories/layout-custom.d.ts.map +1 -0
- package/build-types/dataviews-picker/stories/fixtures.d.ts.map +1 -1
- package/build-types/dataviews-picker/stories/index.story.d.ts +1 -1
- package/build-types/dataviews-picker/stories/index.story.d.ts.map +1 -1
- package/build-types/field-types/index.d.ts.map +1 -1
- package/build-types/field-types/stories/index.story.d.ts +1 -1
- package/build-types/field-types/stories/index.story.d.ts.map +1 -1
- package/build-types/field-types/utils/get-filter.d.ts +7 -0
- package/build-types/field-types/utils/get-filter.d.ts.map +1 -0
- package/build-types/hooks/use-report-validity.d.ts +14 -0
- package/build-types/hooks/use-report-validity.d.ts.map +1 -0
- package/build-types/types/field-api.d.ts +3 -0
- package/build-types/types/field-api.d.ts.map +1 -1
- package/build-types/utils/filter-sort-and-paginate.d.ts.map +1 -1
- package/build-types/utils/get-footer-message.d.ts +10 -0
- package/build-types/utils/get-footer-message.d.ts.map +1 -0
- package/build-types/utils/operators.d.ts +2 -1
- package/build-types/utils/operators.d.ts.map +1 -1
- package/build-wp/index.js +2730 -2179
- package/package.json +22 -20
- package/src/components/dataform-controls/combobox.tsx +58 -0
- package/src/components/dataform-controls/date.tsx +45 -10
- package/src/components/dataform-controls/index.tsx +2 -0
- package/src/components/dataform-layouts/card/index.tsx +81 -3
- package/src/components/dataform-layouts/panel/dropdown.tsx +26 -11
- package/src/components/dataform-layouts/panel/index.tsx +6 -4
- package/src/components/dataform-layouts/panel/modal.tsx +24 -12
- package/src/components/dataviews-bulk-actions/index.tsx +23 -20
- package/src/components/dataviews-bulk-actions/style.scss +0 -3
- package/src/components/dataviews-item-actions/index.tsx +6 -1
- package/src/components/dataviews-layouts/activity/activity-item.tsx +8 -1
- package/src/components/dataviews-layouts/table/column-header-menu.tsx +99 -73
- package/src/components/dataviews-layouts/table/index.tsx +12 -3
- package/src/components/dataviews-layouts/table/style.scss +14 -7
- package/src/components/dataviews-picker-footer/index.tsx +8 -18
- package/src/dataform/stories/content.story.mdx +159 -0
- package/src/dataform/stories/content.story.tsx +390 -0
- package/src/dataform/stories/index.story.tsx +8 -1
- package/src/dataform/stories/validation.tsx +98 -5
- package/src/dataviews/stories/best-practices.story.mdx +55 -0
- package/src/dataviews/stories/fixtures.tsx +1 -3
- package/src/dataviews/stories/index.story.tsx +6 -1
- package/src/dataviews/stories/layout-custom.tsx +140 -0
- package/src/dataviews/test/dataviews.tsx +66 -1
- package/src/dataviews-picker/stories/fixtures.tsx +1 -3
- package/src/dataviews-picker/stories/index.story.tsx +1 -1
- package/src/field-types/index.tsx +2 -0
- package/src/field-types/stories/index.story.tsx +2 -0
- package/src/field-types/utils/get-filter.ts +18 -0
- package/src/hooks/use-report-validity.ts +32 -0
- package/src/types/field-api.ts +11 -0
- package/src/utils/filter-sort-and-paginate.ts +11 -306
- package/src/utils/get-footer-message.ts +41 -0
- 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: [
|
|
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:
|
|
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-
|
|
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: {
|