@wordpress/dataviews 7.0.1-next.0f6f9d12c.0 → 7.0.1-next.f34ab90e9.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 (73) hide show
  1. package/CHANGELOG.md +4 -1
  2. package/build/components/dataviews-item-actions/index.js +1 -10
  3. package/build/components/dataviews-item-actions/index.js.map +1 -1
  4. package/build/dataform-controls/boolean.js +15 -7
  5. package/build/dataform-controls/boolean.js.map +1 -1
  6. package/build/dataform-controls/email.js +14 -7
  7. package/build/dataform-controls/email.js.map +1 -1
  8. package/build/dataform-controls/integer.js +14 -7
  9. package/build/dataform-controls/integer.js.map +1 -1
  10. package/build/dataform-controls/text.js +14 -7
  11. package/build/dataform-controls/text.js.map +1 -1
  12. package/build/dataforms-layouts/panel/dropdown.js +124 -0
  13. package/build/dataforms-layouts/panel/dropdown.js.map +1 -0
  14. package/build/dataforms-layouts/panel/index.js +19 -127
  15. package/build/dataforms-layouts/panel/index.js.map +1 -1
  16. package/build/dataforms-layouts/panel/modal.js +125 -0
  17. package/build/dataforms-layouts/panel/modal.js.map +1 -0
  18. package/build/normalize-form-fields.js +3 -2
  19. package/build/normalize-form-fields.js.map +1 -1
  20. package/build/types.js.map +1 -1
  21. package/build-module/components/dataviews-item-actions/index.js +1 -10
  22. package/build-module/components/dataviews-item-actions/index.js.map +1 -1
  23. package/build-module/dataform-controls/boolean.js +15 -7
  24. package/build-module/dataform-controls/boolean.js.map +1 -1
  25. package/build-module/dataform-controls/email.js +15 -8
  26. package/build-module/dataform-controls/email.js.map +1 -1
  27. package/build-module/dataform-controls/integer.js +15 -8
  28. package/build-module/dataform-controls/integer.js.map +1 -1
  29. package/build-module/dataform-controls/text.js +15 -8
  30. package/build-module/dataform-controls/text.js.map +1 -1
  31. package/build-module/dataforms-layouts/panel/dropdown.js +118 -0
  32. package/build-module/dataforms-layouts/panel/dropdown.js.map +1 -0
  33. package/build-module/dataforms-layouts/panel/index.js +23 -131
  34. package/build-module/dataforms-layouts/panel/index.js.map +1 -1
  35. package/build-module/dataforms-layouts/panel/modal.js +119 -0
  36. package/build-module/dataforms-layouts/panel/modal.js.map +1 -0
  37. package/build-module/normalize-form-fields.js +3 -2
  38. package/build-module/normalize-form-fields.js.map +1 -1
  39. package/build-module/types.js.map +1 -1
  40. package/build-style/style-rtl.css +4 -0
  41. package/build-style/style.css +4 -0
  42. package/build-types/components/dataform/stories/index.story.d.ts +58 -25
  43. package/build-types/components/dataform/stories/index.story.d.ts.map +1 -1
  44. package/build-types/components/dataviews-item-actions/index.d.ts.map +1 -1
  45. package/build-types/dataform-controls/boolean.d.ts.map +1 -1
  46. package/build-types/dataform-controls/email.d.ts.map +1 -1
  47. package/build-types/dataform-controls/integer.d.ts.map +1 -1
  48. package/build-types/dataform-controls/text.d.ts.map +1 -1
  49. package/build-types/dataforms-layouts/panel/dropdown.d.ts +14 -0
  50. package/build-types/dataforms-layouts/panel/dropdown.d.ts.map +1 -0
  51. package/build-types/dataforms-layouts/panel/index.d.ts.map +1 -1
  52. package/build-types/dataforms-layouts/panel/modal.d.ts +13 -0
  53. package/build-types/dataforms-layouts/panel/modal.d.ts.map +1 -0
  54. package/build-types/normalize-form-fields.d.ts.map +1 -1
  55. package/build-types/types.d.ts +2 -0
  56. package/build-types/types.d.ts.map +1 -1
  57. package/build-wp/index.js +394 -178
  58. package/package.json +15 -15
  59. package/src/components/dataform/stories/index.story.tsx +354 -219
  60. package/src/components/dataviews-item-actions/index.tsx +7 -16
  61. package/src/dataform-controls/boolean.tsx +24 -10
  62. package/src/dataform-controls/email.tsx +24 -11
  63. package/src/dataform-controls/integer.tsx +27 -13
  64. package/src/dataform-controls/text.tsx +24 -11
  65. package/src/dataforms-layouts/panel/dropdown.tsx +160 -0
  66. package/src/dataforms-layouts/panel/index.tsx +28 -172
  67. package/src/dataforms-layouts/panel/modal.tsx +165 -0
  68. package/src/dataforms-layouts/panel/style.scss +4 -0
  69. package/src/normalize-form-fields.ts +1 -0
  70. package/src/test/dataform.tsx +169 -0
  71. package/src/test/normalize-form-fields.ts +15 -3
  72. package/src/types.ts +2 -0
  73. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,165 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import {
5
+ __experimentalHStack as HStack,
6
+ __experimentalSpacer as Spacer,
7
+ Button,
8
+ Modal,
9
+ } from '@wordpress/components';
10
+ import { __, sprintf, _x } from '@wordpress/i18n';
11
+ import { useState, useMemo } from '@wordpress/element';
12
+
13
+ /**
14
+ * Internal dependencies
15
+ */
16
+ import type { Form, FormField, NormalizedField } from '../../types';
17
+ import { DataFormLayout } from '../data-form-layout';
18
+ import { isCombinedField } from '../is-combined-field';
19
+ import { DEFAULT_LAYOUT } from '../../normalize-form-fields';
20
+
21
+ function ModalContent< Item >( {
22
+ data,
23
+ form,
24
+ fieldLabel,
25
+ onChange,
26
+ onClose,
27
+ }: {
28
+ data: Item;
29
+ form: Form;
30
+ fieldLabel: string;
31
+ onChange: ( data: Partial< Item > ) => void;
32
+ onClose: () => void;
33
+ } ) {
34
+ const [ changes, setChanges ] = useState< Partial< Item > >( {} );
35
+
36
+ const onApply = () => {
37
+ onChange( changes );
38
+ onClose();
39
+ };
40
+
41
+ const handleOnChange = ( value: Partial< Item > ) => {
42
+ setChanges( ( prev ) => ( { ...prev, ...value } ) );
43
+ };
44
+
45
+ // Merge original data with local changes for display
46
+ const displayData = { ...data, ...changes };
47
+
48
+ return (
49
+ <Modal
50
+ className="dataforms-layouts-panel__modal"
51
+ onRequestClose={ onClose }
52
+ isFullScreen={ false }
53
+ title={ fieldLabel }
54
+ size="medium"
55
+ >
56
+ <DataFormLayout
57
+ data={ displayData }
58
+ form={ form }
59
+ onChange={ handleOnChange }
60
+ >
61
+ { ( FieldLayout, nestedField ) => (
62
+ <FieldLayout
63
+ key={ nestedField.id }
64
+ data={ displayData }
65
+ field={ nestedField }
66
+ onChange={ handleOnChange }
67
+ hideLabelFromVision={
68
+ ( form?.fields ?? [] ).length < 2
69
+ }
70
+ />
71
+ ) }
72
+ </DataFormLayout>
73
+ <HStack
74
+ className="dataforms-layouts-panel__modal-footer"
75
+ spacing={ 3 }
76
+ >
77
+ <Spacer />
78
+ <Button
79
+ variant="tertiary"
80
+ onClick={ onClose }
81
+ __next40pxDefaultSize
82
+ >
83
+ { __( 'Cancel' ) }
84
+ </Button>
85
+ <Button
86
+ variant="primary"
87
+ onClick={ onApply }
88
+ __next40pxDefaultSize
89
+ >
90
+ { __( 'Apply' ) }
91
+ </Button>
92
+ </HStack>
93
+ </Modal>
94
+ );
95
+ }
96
+
97
+ function PanelModal< Item >( {
98
+ fieldDefinition,
99
+ labelPosition,
100
+ data,
101
+ onChange,
102
+ field,
103
+ }: {
104
+ fieldDefinition: NormalizedField< Item >;
105
+ labelPosition: 'side' | 'top' | 'none';
106
+ data: Item;
107
+ onChange: ( value: any ) => void;
108
+ field: FormField;
109
+ } ) {
110
+ const [ isOpen, setIsOpen ] = useState( false );
111
+
112
+ const fieldLabel = isCombinedField( field )
113
+ ? field.label
114
+ : fieldDefinition?.label;
115
+
116
+ const form: Form = useMemo(
117
+ (): Form => ( {
118
+ layout: DEFAULT_LAYOUT,
119
+ fields: isCombinedField( field )
120
+ ? field.children
121
+ : // If not explicit children return the field id itself.
122
+ [ { id: field.id } ],
123
+ } ),
124
+ [ field ]
125
+ );
126
+
127
+ return (
128
+ <>
129
+ <Button
130
+ className="dataforms-layouts-modal__field-control"
131
+ size="compact"
132
+ variant={
133
+ [ 'none', 'top' ].includes( labelPosition )
134
+ ? 'link'
135
+ : 'tertiary'
136
+ }
137
+ aria-expanded={ isOpen }
138
+ aria-label={ sprintf(
139
+ // translators: %s: Field name.
140
+ _x( 'Edit %s', 'field' ),
141
+ fieldLabel || ''
142
+ ) }
143
+ onClick={ () => setIsOpen( true ) }
144
+ disabled={ fieldDefinition.readOnly === true }
145
+ accessibleWhenDisabled
146
+ >
147
+ <fieldDefinition.render
148
+ item={ data }
149
+ field={ fieldDefinition }
150
+ />
151
+ </Button>
152
+ { isOpen && (
153
+ <ModalContent
154
+ data={ data }
155
+ form={ form as Form }
156
+ fieldLabel={ fieldLabel ?? '' }
157
+ onChange={ onChange }
158
+ onClose={ () => setIsOpen( false ) }
159
+ />
160
+ ) }
161
+ </>
162
+ );
163
+ }
164
+
165
+ export default PanelModal;
@@ -52,6 +52,10 @@
52
52
  margin-bottom: $grid-unit-20;
53
53
  }
54
54
 
55
+ .dataforms-layouts-panel__modal-footer {
56
+ margin-top: $grid-unit-20;
57
+ }
58
+
55
59
  .components-popover.components-dropdown__content.dataforms-layouts-panel__field-dropdown {
56
60
  z-index: z-index(".components-popover.components-dropdown__content.dataforms-layouts-panel__field-dropdown");
57
61
  }
@@ -38,6 +38,7 @@ export function normalizeLayout( layout?: Layout ): NormalizedLayout {
38
38
  normalizedLayout = {
39
39
  type: 'panel',
40
40
  labelPosition: layout?.labelPosition ?? 'side',
41
+ openAs: layout?.openAs ?? 'dropdown',
41
42
  } satisfies NormalizedPanelLayout;
42
43
  } else if ( layout?.type === 'card' ) {
43
44
  if ( layout.withHeader === false ) {
@@ -218,6 +218,175 @@ describe( 'DataForm component', () => {
218
218
  }
219
219
  } );
220
220
 
221
+ it( 'should use dropdown panel type by default', async () => {
222
+ render(
223
+ <Dataform
224
+ onChange={ noop }
225
+ fields={ fields }
226
+ form={ formPanelMode }
227
+ data={ data }
228
+ />
229
+ );
230
+
231
+ const user = await userEvent.setup();
232
+ const titleButton = fieldsSelector.title.view();
233
+ await user.click( titleButton );
234
+
235
+ // Should show dropdown content (not modal)
236
+ expect(
237
+ screen.getByRole( 'textbox', { name: /title/i } )
238
+ ).toBeInTheDocument();
239
+ // Should not have modal dialog
240
+ expect( screen.queryByRole( 'dialog' ) ).not.toBeInTheDocument();
241
+ // Should not have modal buttons (Cancel/Apply)
242
+ expect(
243
+ screen.queryByRole( 'button', { name: /cancel/i } )
244
+ ).not.toBeInTheDocument();
245
+ expect(
246
+ screen.queryByRole( 'button', { name: /apply/i } )
247
+ ).not.toBeInTheDocument();
248
+ } );
249
+
250
+ it( 'should use dropdown panel type when explicitly set', async () => {
251
+ const formWithDropdownPanel = {
252
+ ...form,
253
+ layout: {
254
+ type: 'panel',
255
+ labelPosition: 'side',
256
+ openAs: 'dropdown',
257
+ } as const,
258
+ };
259
+
260
+ render(
261
+ <Dataform
262
+ onChange={ noop }
263
+ fields={ fields }
264
+ form={ formWithDropdownPanel }
265
+ data={ data }
266
+ />
267
+ );
268
+
269
+ const user = await userEvent.setup();
270
+ const titleButton = fieldsSelector.title.view();
271
+ await user.click( titleButton );
272
+
273
+ // Should show dropdown content
274
+ expect(
275
+ screen.getByRole( 'textbox', { name: /title/i } )
276
+ ).toBeInTheDocument();
277
+ } );
278
+
279
+ it( 'should use modal panel type when set', async () => {
280
+ const formWithModalPanel = {
281
+ ...form,
282
+ layout: {
283
+ type: 'panel',
284
+ labelPosition: 'side',
285
+ openAs: 'modal',
286
+ } as const,
287
+ };
288
+
289
+ render(
290
+ <Dataform
291
+ onChange={ noop }
292
+ fields={ fields }
293
+ form={ formWithModalPanel }
294
+ data={ data }
295
+ />
296
+ );
297
+
298
+ const user = await userEvent.setup();
299
+ const titleButton = fieldsSelector.title.view();
300
+ await user.click( titleButton );
301
+
302
+ // Should show modal content
303
+ expect( screen.getByRole( 'dialog' ) ).toBeInTheDocument();
304
+ expect(
305
+ screen.getByRole( 'textbox', { name: /title/i } )
306
+ ).toBeInTheDocument();
307
+ } );
308
+
309
+ it( 'should close modal when cancel button is clicked', async () => {
310
+ const formWithModalPanel = {
311
+ ...form,
312
+ layout: {
313
+ type: 'panel',
314
+ labelPosition: 'side',
315
+ openAs: 'modal',
316
+ } as const,
317
+ };
318
+
319
+ render(
320
+ <Dataform
321
+ onChange={ noop }
322
+ fields={ fields }
323
+ form={ formWithModalPanel }
324
+ data={ data }
325
+ />
326
+ );
327
+
328
+ const user = await userEvent.setup();
329
+ const titleButton = fieldsSelector.title.view();
330
+ await user.click( titleButton );
331
+
332
+ // Modal should be open
333
+ expect( screen.getByRole( 'dialog' ) ).toBeInTheDocument();
334
+
335
+ // Click cancel button
336
+ const cancelButton = screen.getByRole( 'button', {
337
+ name: /cancel/i,
338
+ } );
339
+ await user.click( cancelButton );
340
+
341
+ // Modal should be closed
342
+ expect( screen.queryByRole( 'dialog' ) ).not.toBeInTheDocument();
343
+ } );
344
+
345
+ it( 'should apply changes and close modal when apply button is clicked', async () => {
346
+ const onChange = jest.fn();
347
+ const formWithModalPanel = {
348
+ ...form,
349
+ layout: {
350
+ type: 'panel',
351
+ labelPosition: 'side',
352
+ openAs: 'modal',
353
+ } as const,
354
+ };
355
+
356
+ render(
357
+ <Dataform
358
+ onChange={ onChange }
359
+ fields={ fields }
360
+ form={ formWithModalPanel }
361
+ data={ data }
362
+ />
363
+ );
364
+
365
+ const user = await userEvent.setup();
366
+ const titleButton = fieldsSelector.title.view();
367
+ await user.click( titleButton );
368
+
369
+ // Modal should be open
370
+ expect( screen.getByRole( 'dialog' ) ).toBeInTheDocument();
371
+
372
+ // Type in the input
373
+ const titleInput = screen.getByRole( 'textbox', {
374
+ name: /title/i,
375
+ } );
376
+ await user.clear( titleInput );
377
+ await user.type( titleInput, 'New Title' );
378
+
379
+ // Click apply button
380
+ const applyButton = screen.getByRole( 'button', {
381
+ name: /apply/i,
382
+ } );
383
+ await user.click( applyButton );
384
+
385
+ // Modal should be closed and onChange should be called
386
+ expect( screen.queryByRole( 'dialog' ) ).not.toBeInTheDocument();
387
+ expect( onChange ).toHaveBeenCalledWith( { title: 'New Title' } );
388
+ } );
389
+
221
390
  it( 'should call onChange with the correct value for each typed character', async () => {
222
391
  const onChange = jest.fn();
223
392
  render(
@@ -100,7 +100,11 @@ describe( 'normalizeFormFields', () => {
100
100
  expect( result ).toEqual( [
101
101
  {
102
102
  id: 'field1',
103
- layout: { type: 'panel', labelPosition: 'side' },
103
+ layout: {
104
+ type: 'panel',
105
+ labelPosition: 'side',
106
+ openAs: 'dropdown',
107
+ },
104
108
  },
105
109
  ] );
106
110
  } );
@@ -114,7 +118,11 @@ describe( 'normalizeFormFields', () => {
114
118
  expect( result ).toEqual( [
115
119
  {
116
120
  id: 'field1',
117
- layout: { type: 'panel', labelPosition: 'top' },
121
+ layout: {
122
+ type: 'panel',
123
+ labelPosition: 'top',
124
+ openAs: 'dropdown',
125
+ },
118
126
  },
119
127
  ] );
120
128
  } );
@@ -195,7 +203,11 @@ describe( 'normalizeFormFields', () => {
195
203
  },
196
204
  {
197
205
  id: 'field2',
198
- layout: { type: 'panel', labelPosition: 'side' },
206
+ layout: {
207
+ type: 'panel',
208
+ labelPosition: 'side',
209
+ openAs: 'dropdown',
210
+ },
199
211
  },
200
212
  ] );
201
213
  } );
package/src/types.ts CHANGED
@@ -689,10 +689,12 @@ export type NormalizedRegularLayout = {
689
689
  export type PanelLayout = {
690
690
  type: 'panel';
691
691
  labelPosition?: LabelPosition;
692
+ openAs?: 'dropdown' | 'modal';
692
693
  };
693
694
  export type NormalizedPanelLayout = {
694
695
  type: 'panel';
695
696
  labelPosition: LabelPosition;
697
+ openAs: 'dropdown' | 'modal';
696
698
  };
697
699
 
698
700
  export type CardLayout =