Skip to content

Commit

Permalink
DataForm: centralize edit logic in field type definitions (#64171)
Browse files Browse the repository at this point in the history
Co-authored-by: oandregal <[email protected]>
Co-authored-by: youknowriad <[email protected]>
  • Loading branch information
3 people authored Aug 1, 2024
1 parent eab0643 commit 58166db
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 120 deletions.
122 changes: 5 additions & 117 deletions packages/dataviews/src/components/dataform/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,13 @@ import type { Dispatch, SetStateAction } from 'react';
/**
* WordPress dependencies
*/
import {
TextControl,
__experimentalNumberControl as NumberControl,
SelectControl,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useCallback, useMemo } from '@wordpress/element';
import { useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import type { Form, Field, NormalizedField, FieldType } from '../../types';
import { normalizeFields } from '../../normalize-fields';
import type { Field, Form } from '../../types';

type DataFormProps< Item > = {
data: Item;
Expand All @@ -27,111 +21,6 @@ type DataFormProps< Item > = {
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, label, placeholder } = field;
const value = field.getValue( { item: data } );

const onChangeControl = useCallback(
( newValue: string ) =>
onChange( ( prevItem: Item ) => ( {
...prevItem,
[ id ]: newValue,
} ) ),
[ id, onChange ]
);

return (
<TextControl
label={ label }
placeholder={ placeholder }
value={ value ?? '' }
onChange={ onChangeControl }
__next40pxDefaultSize
/>
);
}

function DataFormNumberControl< Item >( {
data,
field,
onChange,
}: DataFormControlProps< Item > ) {
const { id, label, description } = field;
const value = field.getValue( { item: data } ) ?? '';
const onChangeControl = useCallback(
( newValue: string | undefined ) =>
onChange( ( prevItem: Item ) => ( {
...prevItem,
[ id ]: newValue,
} ) ),
[ id, onChange ]
);

if ( field.elements ) {
const elements = [
/*
* Value can be undefined when:
*
* - the field is not required
* - in bulk editing
*
*/
{ label: __( 'Select item' ), value: '' },
...field.elements,
];

return (
<SelectControl
label={ label }
value={ value }
options={ elements }
onChange={ onChangeControl }
/>
);
}

return (
<NumberControl
label={ label }
help={ description }
value={ value }
onChange={ onChangeControl }
__next40pxDefaultSize
/>
);
}

const controls: {
[ key in FieldType ]: < Item >(
props: DataFormControlProps< Item >
) => JSX.Element;
} = {
text: DataFormTextControl,
integer: DataFormNumberControl,
};

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,
Expand All @@ -149,14 +38,13 @@ export default function DataForm< Item >( {
);

return visibleFields.map( ( field ) => {
const DataFormControl = getControlForField( field );
return DataFormControl ? (
<DataFormControl
return (
<field.Edit
key={ field.id }
data={ data }
field={ field }
onChange={ onChange }
/>
) : null;
);
} );
}
1 change: 1 addition & 0 deletions packages/dataviews/src/field-types/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@ export default function getFieldTypeDefinition( type?: FieldType ) {

return true;
},
Edit: () => null,
};
}
67 changes: 66 additions & 1 deletion packages/dataviews/src/field-types/integer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
/**
* WordPress dependencies
*/
import {
__experimentalNumberControl as NumberControl,
SelectControl,
} from '@wordpress/components';
import { useCallback } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import type { SortDirection, ValidationContext } from '../types';
import type {
SortDirection,
ValidationContext,
DataFormControlProps,
} from '../types';

function sort( a: any, b: any, direction: SortDirection ) {
return direction === 'asc' ? a - b : b - a;
Expand All @@ -27,7 +41,58 @@ function isValid( value: any, context?: ValidationContext ) {
return true;
}

function Edit< Item >( {
data,
field,
onChange,
}: DataFormControlProps< Item > ) {
const { id, label, description } = field;
const value = field.getValue( { item: data } ) ?? '';
const onChangeControl = useCallback(
( newValue: string | undefined ) =>
onChange( ( prevItem: Item ) => ( {
...prevItem,
[ id ]: newValue,
} ) ),
[ id, onChange ]
);

if ( field.elements ) {
const elements = [
/*
* Value can be undefined when:
*
* - the field is not required
* - in bulk editing
*
*/
{ label: __( 'Select item' ), value: '' },
...field.elements,
];

return (
<SelectControl
label={ label }
value={ value }
options={ elements }
onChange={ onChangeControl }
/>
);
}

return (
<NumberControl
label={ label }
help={ description }
value={ value }
onChange={ onChangeControl }
__next40pxDefaultSize
/>
);
}

export default {
sort,
isValid,
Edit,
};
41 changes: 40 additions & 1 deletion packages/dataviews/src/field-types/text.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
/**
* WordPress dependencies
*/
import { TextControl } from '@wordpress/components';
import { useCallback } from '@wordpress/element';

/**
* Internal dependencies
*/
import type { SortDirection, ValidationContext } from '../types';
import type {
SortDirection,
ValidationContext,
DataFormControlProps,
} from '../types';

function sort( valueA: any, valueB: any, direction: SortDirection ) {
return direction === 'asc'
Expand All @@ -20,7 +30,36 @@ function isValid( value: any, context?: ValidationContext ) {
return true;
}

function Edit< Item >( {
data,
field,
onChange,
}: DataFormControlProps< Item > ) {
const { id, label, placeholder } = field;
const value = field.getValue( { item: data } );

const onChangeControl = useCallback(
( newValue: string ) =>
onChange( ( prevItem: Item ) => ( {
...prevItem,
[ id ]: newValue,
} ) ),
[ id, onChange ]
);

return (
<TextControl
label={ label }
placeholder={ placeholder }
value={ value ?? '' }
onChange={ onChangeControl }
__next40pxDefaultSize
/>
);
}

export default {
sort,
isValid,
Edit,
};
3 changes: 3 additions & 0 deletions packages/dataviews/src/normalize-fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,16 @@ export function normalizeFields< Item >(
);
};

const Edit = field.Edit || fieldTypeDefinition.Edit;

return {
...field,
label: field.label || field.id,
getValue,
render: field.render || getValue,
sort,
isValid,
Edit,
};
} );
}
19 changes: 18 additions & 1 deletion packages/dataviews/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
/**
* External dependencies
*/
import type { ReactElement, ComponentType } from 'react';
import type {
ReactElement,
ComponentType,
Dispatch,
SetStateAction,
} from 'react';

/**
* Internal dependencies
Expand Down Expand Up @@ -84,6 +89,11 @@ export type Field< Item > = {
*/
render?: ComponentType< { item: Item } >;

/**
* Callback used to render an edit control for the field.
*/
Edit?: ComponentType< DataFormControlProps< Item > >;

/**
* Callback used to sort the field.
*/
Expand Down Expand Up @@ -138,6 +148,7 @@ export type NormalizedField< Item > = Field< Item > & {
label: string;
getValue: ( args: { item: Item } ) => any;
render: ComponentType< { item: Item } >;
Edit: ComponentType< DataFormControlProps< Item > >;
sort: ( a: Item, b: Item, direction: SortDirection ) => number;
isValid: ( item: Item, context?: ValidationContext ) => boolean;
};
Expand All @@ -156,6 +167,12 @@ export type Form = {
visibleFields?: string[];
};

export type DataFormControlProps< Item > = {
data: Item;
field: NormalizedField< Item >;
onChange: Dispatch< SetStateAction< Item > >;
};

/**
* The filters applied to the dataset.
*/
Expand Down

0 comments on commit 58166db

Please sign in to comment.