From a877f1eac0937fc5c3d5928fe1c18622b73a80c4 Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Tue, 29 Aug 2023 11:04:25 +0200 Subject: [PATCH 1/2] Improve columns editor UX --- .../toolpad/propertyControls/GridColumns.tsx | 310 +++++++++++------- 1 file changed, 194 insertions(+), 116 deletions(-) diff --git a/packages/toolpad-app/src/toolpad/propertyControls/GridColumns.tsx b/packages/toolpad-app/src/toolpad/propertyControls/GridColumns.tsx index dc76357125b..e98f5c9d973 100644 --- a/packages/toolpad-app/src/toolpad/propertyControls/GridColumns.tsx +++ b/packages/toolpad-app/src/toolpad/propertyControls/GridColumns.tsx @@ -11,6 +11,8 @@ import { Popover, Stack, TextField, + TextFieldProps, + TextFieldVariants, Tooltip, } from '@mui/material'; import * as React from 'react'; @@ -48,6 +50,193 @@ const COLUMN_TYPES: string[] = [ ]; const ALIGNMENTS: GridAlignment[] = ['left', 'right', 'center']; +type ImmediateInputProps = TextFieldProps & { + validate?: (input: string) => string | null; +}; + +interface ImmediateInputState { + input: string; + error: string | null; +} + +function useImmediateTextField( + props: ImmediateInputProps, +): TextFieldProps { + const { value, onChange, error, helperText, required, onBlur, validate } = props; + const createInputState = React.useCallback( + (rawInput: unknown): ImmediateInputState => { + const input = String(rawInput); + let error = null; + if (required && !input) { + error = 'Input required'; + } else if (validate) { + error = validate(input); + } + return { input, error }; + }, + [validate], + ); + const [state, setState] = React.useState(createInputState(value)); + React.useEffect(() => { + setState(createInputState(value)); + }, [value]); + + return { + ...props, + value: state.input, + error: !!state.error || error, + helperText: state.error || helperText, + required, + onBlur: (event) => { + if (state.input !== value) { + setState(createInputState(value)); + } + onBlur?.(event); + }, + onChange: (event) => { + const newState = createInputState(event.target.value); + setState(newState); + if (!newState.error) { + onChange?.(event); + } + }, + }; +} + +interface GridColumnEditorProps { + value: SerializableGridColumn; + onChange: (newValue: SerializableGridColumn) => void; + disabled?: boolean; +} + +function GridColumnEditor({ + disabled, + value: editedColumn, + onChange: handleColumnChange, +}: GridColumnEditorProps) { + const { dom } = useDom(); + const toolpadComponents = useToolpadComponents(dom); + const codeComponents: ToolpadComponentDefinition[] = React.useMemo(() => { + return Object.values(toolpadComponents) + .filter(Boolean) + .filter((definition) => !definition.builtIn); + }, [toolpadComponents]); + + const fieldInput = useImmediateTextField({ + label: 'field', + disabled, + required: true, + value: editedColumn.field, + onChange: (event) => { + handleColumnChange({ ...editedColumn, field: event.target.value }); + }, + }); + + return ( + + + + + handleColumnChange({ + ...editedColumn, + headerName: event.target.value ? event.target.value : undefined, + }) + } + /> + + + handleColumnChange({ + ...editedColumn, + align: (event.target.value as GridAlignment) || undefined, + }) + } + > + {ALIGNMENTS.map((alignment) => ( + + {alignment} + + ))} + + + + handleColumnChange({ ...editedColumn, width: Number(event.target.value) }) + } + /> + + + handleColumnChange({ + ...editedColumn, + type: event.target.value, + numberFormat: undefined, + }) + } + > + {COLUMN_TYPES.map((type) => ( + + {type} + + ))} + + + + {editedColumn.type === 'number' ? ( + handleColumnChange({ ...editedColumn, numberFormat })} + /> + ) : null} + + {editedColumn.type === 'codeComponent' ? ( + + handleColumnChange({ + ...editedColumn, + codeComponent: event.target.value, + }) + } + > + {codeComponents.map(({ displayName }) => ( + + {displayName} + + ))} + + ) : null} + + + ); +} + function GridColumnsPropEditor({ propType, label, @@ -58,13 +247,6 @@ function GridColumnsPropEditor({ }: EditorProps) { const { bindings } = usePageEditorState(); const [editedIndex, setEditedIndex] = React.useState(null); - const { dom } = useDom(); - const toolpadComponents = useToolpadComponents(dom); - const codeComponents: ToolpadComponentDefinition[] = React.useMemo(() => { - return Object.values(toolpadComponents) - .filter(Boolean) - .filter((definition) => !definition.builtIn); - }, [toolpadComponents]); const editedColumn = typeof editedIndex === 'number' ? value[editedIndex] : null; @@ -177,115 +359,11 @@ function GridColumnsPropEditor({ setEditedIndex(null)}> - - - handleColumnChange({ ...editedColumn, field: event.target.value }) - } - /> - - - handleColumnChange({ ...editedColumn, headerName: event.target.value }) - } - /> - - - handleColumnChange({ - ...editedColumn, - align: (event.target.value as GridAlignment) || undefined, - }) - } - > - {ALIGNMENTS.map((alignment) => ( - - {alignment} - - ))} - - - - handleColumnChange({ ...editedColumn, width: Number(event.target.value) }) - } - /> - - - handleColumnChange({ - ...editedColumn, - type: event.target.value, - numberFormat: undefined, - }) - } - > - {COLUMN_TYPES.map((type) => ( - - {type} - - ))} - - - - {editedColumn.type === 'number' ? ( - - handleColumnChange({ ...editedColumn, numberFormat }) - } - /> - ) : null} - - {editedColumn.type === 'codeComponent' ? ( - - handleColumnChange({ - ...editedColumn, - codeComponent: event.target.value, - }) - } - > - {codeComponents.map(({ displayName }) => ( - - {displayName} - - ))} - - ) : null} - - + ) : ( From 439e3018ae332ba1ffb83fa6468aac18a1f64812 Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Tue, 29 Aug 2023 11:30:45 +0200 Subject: [PATCH 2/2] Update GridColumns.tsx --- .../src/toolpad/propertyControls/GridColumns.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/toolpad-app/src/toolpad/propertyControls/GridColumns.tsx b/packages/toolpad-app/src/toolpad/propertyControls/GridColumns.tsx index e98f5c9d973..c51d96a9346 100644 --- a/packages/toolpad-app/src/toolpad/propertyControls/GridColumns.tsx +++ b/packages/toolpad-app/src/toolpad/propertyControls/GridColumns.tsx @@ -66,20 +66,20 @@ function useImmediateTextField( const createInputState = React.useCallback( (rawInput: unknown): ImmediateInputState => { const input = String(rawInput); - let error = null; + let inputError = null; if (required && !input) { - error = 'Input required'; + inputError = 'Input required'; } else if (validate) { - error = validate(input); + inputError = validate(input); } - return { input, error }; + return { input, error: inputError }; }, - [validate], + [validate, required], ); const [state, setState] = React.useState(createInputState(value)); React.useEffect(() => { setState(createInputState(value)); - }, [value]); + }, [value, createInputState]); return { ...props,