Skip to content

Commit

Permalink
DataForm: implement first prototype using duplicate page action (Word…
Browse files Browse the repository at this point in the history
…Press#63032)

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 and carstingaxion committed Jul 18, 2024
1 parent 4c2f5b7 commit 3cb97b5
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

0 comments on commit 3cb97b5

Please sign in to comment.