From 298108ae63a5e05701e4b4de0f81d473d704710b Mon Sep 17 00:00:00 2001 From: Scott Thompson <145565743+scotttjob@users.noreply.github.com> Date: Tue, 5 Nov 2024 11:37:10 -0400 Subject: [PATCH 01/17] exploring what our forms could look like without react-hook-form in the middle --- .../components/src/FormField/FormField.tsx | 407 +++++++++++++----- .../src/FormField/FormFieldAffix.tsx | 3 +- .../src/FormField/FormFieldDescription.tsx | 6 +- .../src/FormField/FormFieldPostFix.tsx | 8 +- .../src/FormField/FormFieldTypes.ts | 5 + .../src/FormField/FormFieldWrapper.tsx | 346 ++++++++++----- .../src/FormField/components/ClearAction.tsx | 8 +- .../src/InputText/InputText.rebuilt.tsx | 126 ++++++ .../components/src/InputText/InputText.tsx | 2 +- packages/components/src/InputText/index.ts | 1 - packages/components/src/InputText/index.tsx | 31 ++ .../src/InputValidation/InputValidation.tsx | 8 +- 12 files changed, 725 insertions(+), 226 deletions(-) create mode 100644 packages/components/src/InputText/InputText.rebuilt.tsx delete mode 100644 packages/components/src/InputText/index.ts create mode 100644 packages/components/src/InputText/index.tsx diff --git a/packages/components/src/FormField/FormField.tsx b/packages/components/src/FormField/FormField.tsx index 130b81ea54..bfb5942896 100644 --- a/packages/components/src/FormField/FormField.tsx +++ b/packages/components/src/FormField/FormField.tsx @@ -6,8 +6,15 @@ import React, { useId, useImperativeHandle, } from "react"; -import { useController, useForm, useFormContext } from "react-hook-form"; -import { FormFieldProps } from "./FormFieldTypes"; +import { + FieldValues, + RegisterOptions, + UseFormSetValue, + useController, + useForm, + useFormContext, +} from "react-hook-form"; +import { FieldActionsRef, FormFieldProps } from "./FormFieldTypes"; import styles from "./FormField.module.css"; import { FormFieldWrapper } from "./FormFieldWrapper"; import { FormFieldPostFix } from "./FormFieldPostFix"; @@ -26,70 +33,34 @@ type FormFieldInternalProps = FormFieldProps & { readonly id: string; }; -// eslint-disable-next-line max-statements -function FormFieldInternal(props: FormFieldInternalProps) { - const { - actionsRef, - autocomplete = true, - children, - defaultValue, - description, - disabled, - id, - inputRef, - inline, - keyboard, - max, - maxLength, - min, - name: nameProp, - readonly, - rows, - loading, - type = "text", - validations, - value, - onChange, - onEnter, - onFocus, - onBlur, - onValidation, - onKeyUp, - clearable = "never", - autofocus, - } = props; +export function useAtlantisReactForm({ + actionsRef, + name, + defaultValue, + value, + validations, + inputRef, +}: { + actionsRef?: React.RefObject; + name: string; + defaultValue?: string | Date; + value?: string | Date | number; + validations?: RegisterOptions; + inputRef?: React.RefObject< + HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement + >; +}) { const formContext = useFormContext(); // If there isn't a Form Context being provided, get a form for this field. const { control, setValue, watch } = formContext ?? useForm({ mode: "onTouched" }); - - const descriptionIdentifier = `descriptionUUID--${id}`; - /** - * Generate a name if one is not supplied, this is the name - * that will be used for react-hook-form and not neccessarily - * attached to the DOM - */ - const name = nameProp ? nameProp : `generatedName--${id}`; - - useEffect(() => { - if (value != undefined) { - setValue(name, value); - } - }, [value, watch(name)]); - useImperativeHandle(actionsRef, () => ({ setValue: newValue => { setValue(name, newValue, { shouldValidate: true }); }, })); - const { - field: { - onChange: onControllerChange, - onBlur: onControllerBlur, - ref: fieldRef, - ...rest - }, + field: { onChange, onBlur, ref: fieldRef, ...rest }, fieldState: { error }, } = useController({ name, @@ -98,8 +69,90 @@ function FormFieldInternal(props: FormFieldInternalProps) { defaultValue: value ?? defaultValue ?? "", }); - const errorMessage = error?.message || ""; - useEffect(() => handleValidation(errorMessage), [errorMessage]); + useEffect(() => { + if (value != undefined) { + setValue(name, value); + } + }, [value, watch(name)]); + + const inputRefs = mergeRefs([inputRef, fieldRef]); + + return { + inputRefs, + rest, + setValue, + errorMessage: error?.message || "", + onControllerChange: onChange, + onControllerBlur: onBlur, + }; +} + +export function useAtlantisFormFieldName({ + id, + nameProp, +}: { + id: string; + nameProp?: string; +}) { + /** + * Generate a name if one is not supplied, this is the name + * that will be used for react-hook-form and not neccessarily + * attached to the DOM + */ + const name = nameProp ? nameProp : `generatedName--${id}`; + + return { name }; +} + +export function useAtlantisFormField({ + id, + nameProp, + name, + rest, + description, + disabled, + readonly, + keyboard, + autofocus, + handleChange, + handleBlur, + handleFocus, + inline, + validations, + handleKeyDown, + handleValidation, + errorMessage, +}: { + id: string; + nameProp?: string; + name: string; + actionsRef?: React.RefObject; + rest: object; + description?: string; + disabled?: boolean; + readonly?: boolean; + keyboard?: string; + errorMessage: string; + autofocus?: boolean; + validations: boolean; + handleChange: ( + event: ChangeEvent< + HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement + >, + ) => void; + handleBlur: () => void; + handleFocus: ( + event: FocusEvent< + HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement + >, + ) => void; + inline?: boolean; + handleKeyDown: ( + event: KeyboardEvent, + ) => void; + handleValidation: (message: string) => void; +}) { + const descriptionIdentifier = `descriptionUUID--${id}`; const fieldProps = { ...rest, @@ -108,7 +161,13 @@ function FormFieldInternal(props: FormFieldInternalProps) { name: (validations || nameProp) && name, disabled: disabled, readOnly: readonly, - inputMode: keyboard, + inputMode: keyboard as + | "text" + | "none" + | "tel" + | "url" + | "email" + | "numeric", onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, @@ -122,53 +181,45 @@ function FormFieldInternal(props: FormFieldInternalProps) { autoFocus: autofocus, onKeyDown: handleKeyDown, }; - const inputRefs = mergeRefs([inputRef, fieldRef]); - - return ( - - {renderField()} - - ); + useEffect(() => handleValidation(errorMessage), [errorMessage]); - function renderField() { - switch (type) { - case "select": - return ( - <> - - - - ); - case "textarea": - return