Skip to content
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: centralize edit logic in field type definitions #64171

Merged
merged 2 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading