diff --git a/packages/dataviews/src/components/dataform-combined-edit/index.tsx b/packages/dataviews/src/components/dataform-combined-edit/index.tsx new file mode 100644 index 00000000000000..98b73ec77d32d8 --- /dev/null +++ b/packages/dataviews/src/components/dataform-combined-edit/index.tsx @@ -0,0 +1,72 @@ +/** + * WordPress dependencies + */ +import { + __experimentalHStack as HStack, + __experimentalVStack as VStack, + __experimentalHeading as Heading, + __experimentalSpacer as Spacer, +} from '@wordpress/components'; + +/** + * Internal dependencies + */ +import type { DataFormCombinedEditProps, NormalizedField } from '../../types'; + +function FieldHeader( { title }: { title: string } ) { + return ( + + + + { title } + + + + + ); +} + +function DataFormCombinedEdit< Item >( { + field, + data, + onChange, +}: DataFormCombinedEditProps< Item > ) { + const className = 'dataforms-combined-edit'; + const visibleChildren = ( field.children ?? [] ) + .map( ( fieldId ) => field.fields.find( ( { id } ) => id === fieldId ) ) + .filter( + ( childField ): childField is NormalizedField< Item > => + !! childField + ); + const children = visibleChildren.map( ( child, index ) => { + return ( +
+ { index !== 0 && } + +
+ ); + } ); + + if ( field.direction === 'horizontal' ) { + return ( + + { children } + + ); + } + return ( + + { children } + + ); +} + +export default DataFormCombinedEdit; diff --git a/packages/dataviews/src/components/dataform-combined-edit/style.scss b/packages/dataviews/src/components/dataform-combined-edit/style.scss new file mode 100644 index 00000000000000..74aebf9f797a4d --- /dev/null +++ b/packages/dataviews/src/components/dataform-combined-edit/style.scss @@ -0,0 +1,6 @@ +.dataforms-combined-edit { + &__field:not(:first-child) { + border-top: $border-width solid $gray-200; + padding-top: $grid-unit-20; + } +} diff --git a/packages/dataviews/src/dataforms-layouts/panel/index.tsx b/packages/dataviews/src/dataforms-layouts/panel/index.tsx index 9f118584998bd3..acc0c59b853825 100644 --- a/packages/dataviews/src/dataforms-layouts/panel/index.tsx +++ b/packages/dataviews/src/dataforms-layouts/panel/index.tsx @@ -16,8 +16,17 @@ import { closeSmall } from '@wordpress/icons'; /** * Internal dependencies */ -import { normalizeFields } from '../../normalize-fields'; -import type { DataFormProps, NormalizedField, Field } from '../../types'; +import { + normalizeFields, + normalizeCombinedFields, +} from '../../normalize-fields'; +import type { + DataFormProps, + NormalizedField, + Field, + CombinedFormField, + NormalizedCombinedFormField, +} from '../../types'; interface FormFieldProps< Item > { data: Item; @@ -135,6 +144,26 @@ function FormField< Item >( { ); } +export function getVisibleFields( + fields: Field< any >[], + formFields: string[] = [], + combinedFields?: CombinedFormField< any >[] +): Field< any >[] { + const visibleFields: Array< + Field< any > | NormalizedCombinedFormField< any > + > = [ ...fields ]; + if ( combinedFields ) { + visibleFields.push( + ...normalizeCombinedFields( combinedFields, fields ) + ); + } + return formFields + .map( ( fieldId ) => + visibleFields.find( ( { id } ) => id === fieldId ) + ) + .filter( ( field ): field is Field< any > => !! field ); +} + export default function FormPanel< Item >( { data, fields, @@ -144,13 +173,13 @@ export default function FormPanel< Item >( { const visibleFields = useMemo( () => normalizeFields( - ( form.fields ?? [] ) - .map( ( fieldId ) => - fields.find( ( { id } ) => id === fieldId ) - ) - .filter( ( field ): field is Field< Item > => !! field ) + getVisibleFields( + fields, + form.fields, + form.layout?.combinedFields + ) ), - [ fields, form.fields ] + [ fields, form.fields, form.layout?.combinedFields ] ); return ( diff --git a/packages/dataviews/src/normalize-fields.ts b/packages/dataviews/src/normalize-fields.ts index 2d1cc0402bc206..cfd330934802de 100644 --- a/packages/dataviews/src/normalize-fields.ts +++ b/packages/dataviews/src/normalize-fields.ts @@ -2,8 +2,14 @@ * Internal dependencies */ import getFieldTypeDefinition from './field-types'; -import type { Field, NormalizedField } from './types'; +import type { + CombinedFormField, + Field, + NormalizedField, + NormalizedCombinedFormField, +} from './types'; import { getControl } from './dataform-controls'; +import DataFormCombinedEdit from './components/dataform-combined-edit'; /** * Apply default values and normalize the fields config. @@ -66,3 +72,29 @@ export function normalizeFields< Item >( }; } ); } + +/** + * Apply default values and normalize the fields config. + * + * @param combinedFields combined field list. + * @param fields Fields config. + * @return Normalized fields config. + */ +export function normalizeCombinedFields< Item >( + combinedFields: CombinedFormField< Item >[], + fields: Field< Item >[] +): NormalizedCombinedFormField< Item >[] { + return combinedFields.map( ( combinedField ) => { + return { + ...combinedField, + Edit: DataFormCombinedEdit, + fields: normalizeFields( + combinedField.children + .map( ( fieldId ) => + fields.find( ( { id } ) => id === fieldId ) + ) + .filter( ( field ): field is Field< any > => !! field ) + ), + }; + } ); +} diff --git a/packages/dataviews/src/style.scss b/packages/dataviews/src/style.scss index 087e812fffa192..26c6ecea645f43 100644 --- a/packages/dataviews/src/style.scss +++ b/packages/dataviews/src/style.scss @@ -6,6 +6,7 @@ @import "./components/dataviews-item-actions/style.scss"; @import "./components/dataviews-selection-checkbox/style.scss"; @import "./components/dataviews-view-config/style.scss"; +@import "./components/dataform-combined-edit/style.scss"; @import "./dataviews-layouts/grid/style.scss"; @import "./dataviews-layouts/list/style.scss"; diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index e95a43994cd63d..d8da33c546bd10 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -174,14 +174,6 @@ export type Fields< Item > = Field< Item >[]; export type Data< Item > = Item[]; -/** - * The form configuration. - */ -export type Form = { - type?: 'regular' | 'panel'; - fields?: string[]; -}; - export type DataFormControlProps< Item > = { data: Item; field: NormalizedField< Item >; @@ -524,9 +516,38 @@ export interface SupportedLayouts { table?: Omit< ViewTable, 'type' >; } +export interface CombinedFormField< Item > extends CombinedField { + render?: ComponentType< { item: Item } >; +} + +export interface DataFormCombinedEditProps< Item > { + field: NormalizedCombinedFormField< Item >; + data: Item; + onChange: ( value: Record< string, any > ) => void; +} + +export type NormalizedCombinedFormField< Item > = CombinedFormField< Item > & { + fields: NormalizedField< Item >[]; + Edit?: ComponentType< DataFormCombinedEditProps< Item > >; +}; + +/** + * The form configuration. + */ +export type Form< Item > = { + type?: 'regular' | 'panel'; + fields?: string[]; + layout?: { + /** + * The fields to combine. + */ + combinedFields?: CombinedFormField< Item >[]; + }; +}; + export interface DataFormProps< Item > { data: Item; fields: Field< Item >[]; - form: Form; + form: Form< Item >; onChange: ( value: Record< string, any > ) => void; } diff --git a/packages/dataviews/src/validation.ts b/packages/dataviews/src/validation.ts index cc0b031f6c96c6..41969a7960af65 100644 --- a/packages/dataviews/src/validation.ts +++ b/packages/dataviews/src/validation.ts @@ -7,7 +7,7 @@ import type { Field, Form } from './types'; export function isItemValid< Item >( item: Item, fields: Field< Item >[], - form: Form + form: Form< Item > ): boolean { const _fields = normalizeFields( fields.filter( ( { id } ) => !! form.fields?.includes( id ) )