Skip to content

Commit 3cb97b5

Browse files
oandregalfabiankaegyyouknowriad
authored andcommitted
DataForm: implement first prototype using duplicate page action (WordPress#63032)
Co-authored-by: oandregal <[email protected]> Co-authored-by: fabiankaegy <[email protected]> Co-authored-by: youknowriad <[email protected]>
1 parent 4c2f5b7 commit 3cb97b5

File tree

8 files changed

+167
-19
lines changed

8 files changed

+167
-19
lines changed

package-lock.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/dataviews/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### New features
6+
7+
- Added a new `DataForm` component to render controls from a given configuration (fields, form), and data.
8+
59
## 2.2.0 (2024-06-26)
610

711
## 2.1.0 (2024-06-15)
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import type { Dispatch, SetStateAction } from 'react';
5+
6+
/**
7+
* WordPress dependencies
8+
*/
9+
import { __ } from '@wordpress/i18n';
10+
import { TextControl } from '@wordpress/components';
11+
import { useCallback, useMemo } from '@wordpress/element';
12+
13+
/**
14+
* Internal dependencies
15+
*/
16+
import type { Form, Field, NormalizedField } from './types';
17+
import { normalizeFields } from './normalize-fields';
18+
19+
type DataFormProps< Item > = {
20+
data: Item;
21+
fields: Field< Item >[];
22+
form: Form;
23+
onChange: Dispatch< SetStateAction< Item > >;
24+
};
25+
26+
type DataFormControlProps< Item > = {
27+
data: Item;
28+
field: NormalizedField< Item >;
29+
onChange: Dispatch< SetStateAction< Item > >;
30+
};
31+
32+
function DataFormTextControl< Item >( {
33+
data,
34+
field,
35+
onChange,
36+
}: DataFormControlProps< Item > ) {
37+
const { id, header, placeholder } = field;
38+
const value = field.getValue( { item: data } );
39+
40+
const onChangeControl = useCallback(
41+
( newValue: string ) =>
42+
onChange( ( prevItem: Item ) => ( {
43+
...prevItem,
44+
[ id ]: newValue,
45+
} ) ),
46+
[ id, onChange ]
47+
);
48+
49+
return (
50+
<TextControl
51+
label={ header }
52+
placeholder={ placeholder }
53+
value={ value }
54+
onChange={ onChangeControl }
55+
/>
56+
);
57+
}
58+
59+
const controls: {
60+
[ key: string ]: < Item >(
61+
props: DataFormControlProps< Item >
62+
) => JSX.Element;
63+
} = {
64+
text: DataFormTextControl,
65+
};
66+
67+
function getControlForField< Item >( field: NormalizedField< Item > ) {
68+
if ( ! field.type ) {
69+
return null;
70+
}
71+
72+
if ( ! Object.keys( controls ).includes( field.type ) ) {
73+
return null;
74+
}
75+
76+
return controls[ field.type ];
77+
}
78+
79+
export default function DataForm< Item >( {
80+
data,
81+
fields,
82+
form,
83+
onChange,
84+
}: DataFormProps< Item > ) {
85+
const visibleFields = useMemo(
86+
() =>
87+
normalizeFields(
88+
fields.filter(
89+
( { id } ) => !! form.visibleFields?.includes( id )
90+
)
91+
),
92+
[ fields, form.visibleFields ]
93+
);
94+
95+
return visibleFields.map( ( field ) => {
96+
const DataFormControl = getControlForField( field );
97+
return DataFormControl ? (
98+
<DataFormControl
99+
key={ field.id }
100+
data={ data }
101+
field={ field }
102+
onChange={ onChange }
103+
/>
104+
) : null;
105+
} );
106+
}

packages/dataviews/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export { default as DataViews } from './dataviews';
22
export { VIEW_LAYOUTS } from './layouts';
33
export { filterSortAndPaginate } from './filter-and-sort-data-view';
44
export type * from './types';
5+
export { default as DataForm } from './dataform';

packages/dataviews/src/types.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,17 @@ export type Operator =
4444

4545
export type ItemRecord = Record< string, unknown >;
4646

47+
export type FieldType = 'text';
48+
4749
/**
4850
* A dataview field for a specific property of a data type.
4951
*/
5052
export type Field< Item > = {
53+
/**
54+
* Type of the fields.
55+
*/
56+
type?: FieldType;
57+
5158
/**
5259
* The unique identifier of the field.
5360
*/
@@ -58,6 +65,11 @@ export type Field< Item > = {
5865
*/
5966
header?: string;
6067

68+
/**
69+
* Placeholder for the field.
70+
*/
71+
placeholder?: string;
72+
6173
/**
6274
* Callback used to render the field. Defaults to `field.getValue`.
6375
*/
@@ -131,6 +143,13 @@ export type Fields< Item > = Field< Item >[];
131143

132144
export type Data< Item > = Item[];
133145

146+
/**
147+
* The form configuration.
148+
*/
149+
export type Form = {
150+
visibleFields?: string[];
151+
};
152+
134153
/**
135154
* The filters applied to the dataset.
136155
*/

packages/editor/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"@wordpress/compose": "file:../compose",
4343
"@wordpress/core-data": "file:../core-data",
4444
"@wordpress/data": "file:../data",
45+
"@wordpress/dataviews": "file:../dataviews",
4546
"@wordpress/date": "file:../date",
4647
"@wordpress/deprecated": "file:../deprecated",
4748
"@wordpress/dom": "file:../dom",

packages/editor/src/components/post-actions/actions.js

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { store as noticesStore } from '@wordpress/notices';
1111
import { useMemo, useState } from '@wordpress/element';
1212
import { privateApis as patternsPrivateApis } from '@wordpress/patterns';
1313
import { parse } from '@wordpress/blocks';
14-
14+
import { DataForm } from '@wordpress/dataviews';
1515
import {
1616
Button,
1717
TextControl,
@@ -39,6 +39,21 @@ import { CreateTemplatePartModalContents } from '../create-template-part-modal';
3939
const { PATTERN_TYPES, CreatePatternModalContents, useDuplicatePatternProps } =
4040
unlock( patternsPrivateApis );
4141

42+
// TODO: this should be shared with other components (page-pages).
43+
const fields = [
44+
{
45+
type: 'text',
46+
header: __( 'Title' ),
47+
id: 'title',
48+
placeholder: __( 'No title' ),
49+
getValue: ( { item } ) => item.title,
50+
},
51+
];
52+
53+
const form = {
54+
visibleFields: [ 'title' ],
55+
};
56+
4257
/**
4358
* Check if a template is removable.
4459
*
@@ -649,16 +664,17 @@ const useDuplicatePostAction = ( postType ) => {
649664
return status !== 'trash';
650665
},
651666
RenderModal: ( { items, closeModal, onActionPerformed } ) => {
652-
const [ item ] = items;
667+
const [ item, setItem ] = useState( {
668+
...items[ 0 ],
669+
title: sprintf(
670+
/* translators: %s: Existing template title */
671+
__( '%s (Copy)' ),
672+
getItemTitle( items[ 0 ] )
673+
),
674+
} );
675+
653676
const [ isCreatingPage, setIsCreatingPage ] =
654677
useState( false );
655-
const [ title, setTitle ] = useState(
656-
sprintf(
657-
/* translators: %s: Existing item title */
658-
__( '%s (Copy)' ),
659-
getItemTitle( item )
660-
)
661-
);
662678

663679
const { saveEntityRecord } = useDispatch( coreStore );
664680
const { createSuccessNotice, createErrorNotice } =
@@ -673,8 +689,8 @@ const useDuplicatePostAction = ( postType ) => {
673689

674690
const newItemOject = {
675691
status: 'draft',
676-
title,
677-
slug: title || __( 'No title' ),
692+
title: item.title,
693+
slug: item.title || __( 'No title' ),
678694
comment_status: item.comment_status,
679695
content:
680696
typeof item.content === 'string'
@@ -725,7 +741,7 @@ const useDuplicatePostAction = ( postType ) => {
725741
// translators: %s: Title of the created template e.g: "Category".
726742
__( '"%s" successfully created.' ),
727743
decodeEntities(
728-
newItem.title?.rendered || title
744+
newItem.title?.rendered || item.title
729745
)
730746
),
731747
{
@@ -753,14 +769,15 @@ const useDuplicatePostAction = ( postType ) => {
753769
closeModal();
754770
}
755771
}
772+
756773
return (
757774
<form onSubmit={ createPage }>
758775
<VStack spacing={ 3 }>
759-
<TextControl
760-
label={ __( 'Title' ) }
761-
onChange={ setTitle }
762-
placeholder={ __( 'No title' ) }
763-
value={ title }
776+
<DataForm
777+
data={ item }
778+
fields={ fields }
779+
form={ form }
780+
onChange={ setItem }
764781
/>
765782
<HStack spacing={ 2 } justify="end">
766783
<Button

packages/editor/src/private-apis.native.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { EntitiesSavedStatesExtensible } from './components/entities-saved-state
1212
import useBlockEditorSettings from './components/provider/use-block-editor-settings';
1313
import PluginPostExcerpt from './components/post-excerpt/plugin';
1414
import PreferencesModal from './components/preferences-modal';
15-
import { usePostActions } from './components/post-actions/actions';
1615
import ToolsMoreMenuGroup from './components/more-menu/tools-more-menu-group';
1716
import ViewMoreMenuGroup from './components/more-menu/view-more-menu-group';
1817

@@ -24,7 +23,6 @@ lock( privateApis, {
2423
EntitiesSavedStatesExtensible,
2524
PluginPostExcerpt,
2625
PreferencesModal,
27-
usePostActions,
2826
ToolsMoreMenuGroup,
2927
ViewMoreMenuGroup,
3028

0 commit comments

Comments
 (0)