-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Changes from 18 commits
49eb220
0adcfcb
f896aae
3f6de3f
77d64c6
04942f9
dac8567
d1cb385
caeef9f
a88fa6c
4ec608c
287e0ff
1979c74
bc13a9b
9287a11
ffab101
3747020
969ab6c
8e06615
c188d11
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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; | ||
} ); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
|
@@ -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). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I think right now the registry should be in the. My thinking is that I'll probably move |
||
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. | ||
* | ||
|
@@ -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 } = | ||
|
@@ -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' | ||
|
@@ -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 | ||
) | ||
), | ||
{ | ||
|
@@ -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 | ||
|
There was a problem hiding this comment.
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.