Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DataForm: implement first prototype using duplicate page action #63032

Merged
merged 20 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/dataviews/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Added a new `DataForm` component to render controls from a given configuration (fields, form), and data.
oandregal marked this conversation as resolved.
Show resolved Hide resolved

## 2.2.0 (2024-06-26)

## 2.1.0 (2024-06-15)
Expand Down
106 changes: 106 additions & 0 deletions packages/dataviews/src/dataform.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* External dependencies
*/
import type { Dispatch, SetStateAction } from 'react';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { TextControl } from '@wordpress/components';
import { useCallback, useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import type { Form, Field, NormalizedField } from './types';
import { normalizeFields } from './normalize-fields';

type DataFormProps< Item > = {
data: Item;
fields: Field< Item >[];
form: Form;
onChange: Dispatch< SetStateAction< Item > >;
};

type DataFormControlProps< Item > = {
data: Item;
field: NormalizedField< Item >;
onChange: Dispatch< SetStateAction< Item > >;
};

function DataFormTextControl< Item >( {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be cool to have a "types" folder with a file per type maybe at some point.

data,
field,
onChange,
}: DataFormControlProps< Item > ) {
const { id, header, placeholder } = field;
const value = field.getValue( { item: data } );

const onChangeControl = useCallback(
( newValue: string ) =>
onChange( ( prevItem: Item ) => ( {
...prevItem,
[ id ]: newValue,
} ) ),
[ id, onChange ]
);

return (
<TextControl
label={ header }
placeholder={ placeholder }
value={ value }
onChange={ onChangeControl }
/>
);
}

const controls: {
[ key: string ]: < Item >(
props: DataFormControlProps< Item >
) => JSX.Element;
} = {
text: DataFormTextControl,
};

function getControlForField< Item >( field: NormalizedField< Item > ) {
if ( ! field.type ) {
return null;
}

if ( ! Object.keys( controls ).includes( field.type ) ) {
return null;
}

return controls[ field.type ];
}

export default function DataForm< Item >( {
data,
fields,
form,
onChange,
}: DataFormProps< Item > ) {
const visibleFields = useMemo(
() =>
normalizeFields(
fields.filter(
( { id } ) => !! form.visibleFields?.includes( id )
)
),
[ fields, form.visibleFields ]
);

return visibleFields.map( ( field ) => {
const DataFormControl = getControlForField( field );
return DataFormControl ? (
<DataFormControl
key={ field.id }
data={ data }
field={ field }
onChange={ onChange }
/>
) : null;
} );
}
1 change: 1 addition & 0 deletions packages/dataviews/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { default as DataViews } from './dataviews';
export { VIEW_LAYOUTS } from './layouts';
export { filterSortAndPaginate } from './filter-and-sort-data-view';
export type * from './types';
export { default as DataForm } from './dataform';
19 changes: 19 additions & 0 deletions packages/dataviews/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,17 @@ export type Operator =

export type ItemRecord = Record< string, unknown >;

export type FieldType = 'text';

/**
* A dataview field for a specific property of a data type.
*/
export type Field< Item > = {
/**
* Type of the fields.
*/
type?: FieldType;

/**
* The unique identifier of the field.
*/
Expand All @@ -58,6 +65,11 @@ export type Field< Item > = {
*/
header?: string;

/**
* Placeholder for the field.
*/
placeholder?: string;

/**
* Callback used to render the field. Defaults to `field.getValue`.
*/
Expand Down Expand Up @@ -131,6 +143,13 @@ export type Fields< Item > = Field< Item >[];

export type Data< Item > = Item[];

/**
* The form configuration.
*/
export type Form = {
visibleFields?: string[];
};

/**
* The filters applied to the dataset.
*/
Expand Down
1 change: 1 addition & 0 deletions packages/editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@wordpress/compose": "file:../compose",
"@wordpress/core-data": "file:../core-data",
"@wordpress/data": "file:../data",
"@wordpress/dataviews": "file:../dataviews",
"@wordpress/date": "file:../date",
"@wordpress/deprecated": "file:../deprecated",
"@wordpress/dom": "file:../dom",
Expand Down
51 changes: 34 additions & 17 deletions packages/editor/src/components/post-actions/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { store as noticesStore } from '@wordpress/notices';
import { useMemo, useState } from '@wordpress/element';
import { privateApis as patternsPrivateApis } from '@wordpress/patterns';
import { parse } from '@wordpress/blocks';

import { DataForm } from '@wordpress/dataviews';
import {
Button,
TextControl,
Expand Down Expand Up @@ -39,6 +39,21 @@ import { CreateTemplatePartModalContents } from '../create-template-part-modal';
const { PATTERN_TYPES, CreatePatternModalContents, useDuplicatePatternProps } =
unlock( patternsPrivateApis );

// TODO: this should be shared with other components (page-pages).
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consolidation can be done in a follow-up PR, but I wanted to gather early thoughts: do we create a registry for fields per postType? where do we extract the Field information for now? Actions are defined in the editor package while the edit-site package defines the DataViews.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think right now the registry should be in the. editor/src/dataviews/fields folder like we have editor/src/dataviews/actions folder if we're talking about the default WP entities fields.

My thinking is that I'll probably move editor/src/dataviews to either a dedicated package @wordpress/core-dataviews or just move it into @wordpress/core-data because it's actually lower level than the editor and can be used outside the editor.

const fields = [
{
type: 'text',
header: __( 'Title' ),
id: 'title',
placeholder: __( 'No title' ),
getValue: ( { item } ) => item.title,
},
];

const form = {
visibleFields: [ 'title' ],
};

/**
* Check if a template is removable.
*
Expand Down Expand Up @@ -649,16 +664,17 @@ const useDuplicatePostAction = ( postType ) => {
return status !== 'trash';
},
RenderModal: ( { items, closeModal, onActionPerformed } ) => {
const [ item ] = items;
const [ item, setItem ] = useState( {
...items[ 0 ],
title: sprintf(
/* translators: %s: Existing template title */
__( '%s (Copy)' ),
getItemTitle( items[ 0 ] )
),
} );

const [ isCreatingPage, setIsCreatingPage ] =
useState( false );
const [ title, setTitle ] = useState(
sprintf(
/* translators: %s: Existing item title */
__( '%s (Copy)' ),
getItemTitle( item )
)
);

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

const newItemOject = {
status: 'draft',
title,
slug: title || __( 'No title' ),
title: item.title,
slug: item.title || __( 'No title' ),
comment_status: item.comment_status,
content:
typeof item.content === 'string'
Expand Down Expand Up @@ -725,7 +741,7 @@ const useDuplicatePostAction = ( postType ) => {
// translators: %s: Title of the created template e.g: "Category".
__( '"%s" successfully created.' ),
decodeEntities(
newItem.title?.rendered || title
newItem.title?.rendered || item.title
)
),
{
Expand Down Expand Up @@ -753,14 +769,15 @@ const useDuplicatePostAction = ( postType ) => {
closeModal();
}
}

return (
<form onSubmit={ createPage }>
<VStack spacing={ 3 }>
<TextControl
label={ __( 'Title' ) }
onChange={ setTitle }
placeholder={ __( 'No title' ) }
value={ title }
<DataForm
data={ item }
fields={ fields }
form={ form }
onChange={ setItem }
/>
<HStack spacing={ 2 } justify="end">
<Button
Expand Down
2 changes: 0 additions & 2 deletions packages/editor/src/private-apis.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { EntitiesSavedStatesExtensible } from './components/entities-saved-state
import useBlockEditorSettings from './components/provider/use-block-editor-settings';
import PluginPostExcerpt from './components/post-excerpt/plugin';
import PreferencesModal from './components/preferences-modal';
import { usePostActions } from './components/post-actions/actions';
import ToolsMoreMenuGroup from './components/more-menu/tools-more-menu-group';
import ViewMoreMenuGroup from './components/more-menu/view-more-menu-group';

Expand All @@ -24,7 +23,6 @@ lock( privateApis, {
EntitiesSavedStatesExtensible,
PluginPostExcerpt,
PreferencesModal,
usePostActions,
ToolsMoreMenuGroup,
ViewMoreMenuGroup,

Expand Down
Loading