Skip to content

Commit

Permalink
DataForm - Add combined fields support (WordPress#65399)
Browse files Browse the repository at this point in the history
Co-authored-by: louwie17 <[email protected]>
Co-authored-by: youknowriad <[email protected]>
Co-authored-by: gigitux <[email protected]>
Co-authored-by: oandregal <[email protected]>
Co-authored-by: jameskoster <[email protected]>
  • Loading branch information
6 people authored and huubl committed Oct 2, 2024
1 parent 7f08622 commit 766fda4
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 25 deletions.
66 changes: 66 additions & 0 deletions packages/dataviews/src/components/dataform-combined-edit/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* 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 Header( { title }: { title: string } ) {
return (
<VStack className="dataforms-layouts__dropdown-header" spacing={ 4 }>
<HStack alignment="center">
<Heading level={ 2 } size={ 13 }>
{ title }
</Heading>
<Spacer />
</HStack>
</VStack>
);
}

function DataFormCombinedEdit< Item >( {
field,
data,
onChange,
hideLabelFromVision,
}: 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 ) => {
return (
<div className="dataforms-combined-edit__field" key={ child.id }>
<child.Edit
data={ data }
field={ child }
onChange={ onChange }
/>
</div>
);
} );

const Stack = field.direction === 'horizontal' ? HStack : VStack;

return (
<>
{ ! hideLabelFromVision && <Header title={ field.label } /> }
<Stack spacing={ 4 } className={ className } as="fieldset">
{ children }
</Stack>
</>
);
}

export default DataFormCombinedEdit;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.dataforms-layouts-panel__field-dropdown {
.dataforms-combined-edit {
border: none;
padding: 0;
}
}

.dataforms-combined-edit {
&__field {
flex: 1 1 auto;
}
}
65 changes: 65 additions & 0 deletions packages/dataviews/src/components/dataform/stories/index.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useState } from '@wordpress/element';
* Internal dependencies
*/
import DataForm from '../index';
import type { CombinedFormField } from '../../../types';

const meta = {
title: 'DataViews/DataForm',
Expand Down Expand Up @@ -76,6 +77,11 @@ const fields = [
{ value: 'published', label: 'Published' },
],
},
{
id: 'password',
label: 'Password',
type: 'text' as const,
},
];

export const Default = ( { type }: { type: 'panel' | 'regular' } ) => {
Expand Down Expand Up @@ -118,3 +124,62 @@ export const Default = ( { type }: { type: 'panel' | 'regular' } ) => {
/>
);
};

const CombinedFieldsComponent = ( {
type = 'regular',
combinedFieldDirection = 'vertical',
}: {
type: 'panel' | 'regular';
combinedFieldDirection: 'vertical' | 'horizontal';
} ) => {
const [ post, setPost ] = useState( {
title: 'Hello, World!',
order: 2,
author: 1,
status: 'draft',
} );

const form = {
fields: [ 'title', 'status_and_visibility', 'order', 'author' ],
combinedFields: [
{
id: 'status_and_visibility',
label: 'Status & Visibility',
children: [ 'status', 'password' ],
direction: combinedFieldDirection,
render: ( { item } ) => item.status,
},
] as CombinedFormField< any >[],
};

return (
<DataForm
data={ post }
fields={ fields }
form={ {
...form,
type,
} }
onChange={ ( edits ) =>
setPost( ( prev ) => ( {
...prev,
...edits,
} ) )
}
/>
);
};

export const CombinedFields = {
title: 'DataViews/CombinedFields',
render: CombinedFieldsComponent,
argTypes: {
...meta.argTypes,
combinedFieldDirection: {
control: { type: 'select' },
description:
'Chooses the direction of the combined field. "vertical" is the default layout.',
options: [ 'vertical', 'horizontal' ],
},
},
};
29 changes: 29 additions & 0 deletions packages/dataviews/src/dataforms-layouts/get-visible-fields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Internal dependencies
*/
import { normalizeCombinedFields } from '../normalize-fields';
import type {
Field,
CombinedFormField,
NormalizedCombinedFormField,
} from '../types';

export function getVisibleFields< Item >(
fields: Field< Item >[],
formFields: string[] = [],
combinedFields?: CombinedFormField< Item >[]
): Field< Item >[] {
const visibleFields: Array<
Field< Item > | NormalizedCombinedFormField< Item >
> = [ ...fields ];
if ( combinedFields ) {
visibleFields.push(
...normalizeCombinedFields( combinedFields, fields )
);
}
return formFields
.map( ( fieldId ) =>
visibleFields.find( ( { id } ) => id === fieldId )
)
.filter( ( field ): field is Field< Item > => !! field );
}
15 changes: 8 additions & 7 deletions packages/dataviews/src/dataforms-layouts/panel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import { closeSmall } from '@wordpress/icons';
* Internal dependencies
*/
import { normalizeFields } from '../../normalize-fields';
import type { DataFormProps, NormalizedField, Field } from '../../types';
import { getVisibleFields } from '../get-visible-fields';
import type { DataFormProps, NormalizedField } from '../../types';

interface FormFieldProps< Item > {
data: Item;
Expand Down Expand Up @@ -142,13 +143,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< Item >(
fields,
form.fields,
form.combinedFields
)
),
[ fields, form.fields ]
[ fields, form.fields, form.combinedFields ]
);

return (
Expand Down
15 changes: 8 additions & 7 deletions packages/dataviews/src/dataforms-layouts/regular/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { useMemo } from '@wordpress/element';
* Internal dependencies
*/
import { normalizeFields } from '../../normalize-fields';
import type { DataFormProps, Field } from '../../types';
import { getVisibleFields } from '../get-visible-fields';
import type { DataFormProps } from '../../types';

export default function FormRegular< Item >( {
data,
Expand All @@ -19,13 +20,13 @@ export default function FormRegular< Item >( {
const visibleFields = useMemo(
() =>
normalizeFields(
( form.fields ?? [] )
.map( ( fieldId ) =>
fields.find( ( { id } ) => id === fieldId )
)
.filter( ( field ): field is Field< Item > => !! field )
getVisibleFields< Item >(
fields,
form.fields,
form.combinedFields
)
),
[ fields, form.fields ]
[ fields, form.fields, form.combinedFields ]
);

return (
Expand Down
34 changes: 33 additions & 1 deletion packages/dataviews/src/normalize-fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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< Item > => !! field )
),
};
} );
}
1 change: 1 addition & 0 deletions packages/dataviews/src/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
38 changes: 29 additions & 9 deletions packages/dataviews/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 >;
Expand Down Expand Up @@ -524,9 +516,37 @@ 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;
hideLabelFromVision?: boolean;
}

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[];
/**
* 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;
}
2 changes: 1 addition & 1 deletion packages/dataviews/src/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) )
Expand Down

0 comments on commit 766fda4

Please sign in to comment.