Skip to content

Commit

Permalink
DataForm: implement first prototype using duplicate page action (#63032)
Browse files Browse the repository at this point in the history
Co-authored-by: oandregal <oandregal@git.wordpress.org>
Co-authored-by: fabiankaegy <fabiankaegy@git.wordpress.org>
Co-authored-by: youknowriad <youknowriad@git.wordpress.org>
  • Loading branch information
4 people authored Jul 3, 2024
1 parent e35fc99 commit 566b82a
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 19 deletions.
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.

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

## Unreleased

### New features

- Added a new `DataForm` component to render controls from a given configuration (fields, form), and data.

## 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 >( {
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).
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

1 comment on commit 566b82a

@github-actions
Copy link

Choose a reason for hiding this comment

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

Flaky tests detected in 566b82a.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/9780824572
📝 Reported issues:

Please sign in to comment.