From 26a5c490994750349667239ef67cb564434dea4c Mon Sep 17 00:00:00 2001 From: Mirjam Aulbach Date: Thu, 27 Oct 2022 16:32:57 +0200 Subject: [PATCH] feat: Add Form component based on aiven-core implementation. --- coral/package.json | 8 +- coral/pnpm-lock.yaml | 37 +++++++- coral/src/app/components/Form.tsx | 147 ++++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+), 4 deletions(-) create mode 100644 coral/src/app/components/Form.tsx diff --git a/coral/package.json b/coral/package.json index ce2c7f9648..e71f246424 100644 --- a/coral/package.json +++ b/coral/package.json @@ -29,14 +29,19 @@ }, "dependencies": { "@aivenio/design-system": "^18.4.3", + "@hookform/resolvers": "^2.9.10", + "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.4.2" + "react-hook-form": "^7.38.0", + "react-router-dom": "^6.4.2", + "zod": "^3.19.1" }, "devDependencies": { "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@types/jest": "^29.2.0", + "@types/lodash": "^4.14.186", "@types/node": "*", "@types/react": "^18.0.17", "@types/react-dom": "^18.0.6", @@ -53,7 +58,6 @@ "jest": "^29.2.1", "jest-environment-jsdom": "^29.2.1", "lint-staged": "^13.0.3", - "lodash": "4.x", "prettier": "^2.7.1", "ts-jest": "^29.0.3", "ts-node": "^10.9.1", diff --git a/coral/pnpm-lock.yaml b/coral/pnpm-lock.yaml index 9e83e8f9ac..50de65f7ec 100644 --- a/coral/pnpm-lock.yaml +++ b/coral/pnpm-lock.yaml @@ -2,9 +2,11 @@ lockfileVersion: 5.4 specifiers: '@aivenio/design-system': ^18.4.3 + '@hookform/resolvers': ^2.9.10 '@testing-library/jest-dom': ^5.16.5 '@testing-library/react': ^13.4.0 '@types/jest': ^29.2.0 + '@types/lodash': ^4.14.186 '@types/node': '*' '@types/react': ^18.0.17 '@types/react-dom': ^18.0.6 @@ -21,26 +23,33 @@ specifiers: jest: ^29.2.1 jest-environment-jsdom: ^29.2.1 lint-staged: ^13.0.3 - lodash: 4.x + lodash: ^4.17.21 prettier: ^2.7.1 react: ^18.2.0 react-dom: ^18.2.0 + react-hook-form: ^7.38.0 react-router-dom: ^6.4.2 ts-jest: ^29.0.3 ts-node: ^10.9.1 typescript: ^4.6.4 vite: ^3.1.0 + zod: ^3.19.1 dependencies: '@aivenio/design-system': 18.4.3_pvnihi4muhoy7kenlmiyxwzuqy + '@hookform/resolvers': 2.9.10_react-hook-form@7.38.0 + lodash: 4.17.21 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 + react-hook-form: 7.38.0_react@18.2.0 react-router-dom: 6.4.2_biqbaboplfbrettd7655fr4n2y + zod: 3.19.1 devDependencies: '@testing-library/jest-dom': 5.16.5 '@testing-library/react': 13.4.0_biqbaboplfbrettd7655fr4n2y '@types/jest': 29.2.0 + '@types/lodash': 4.14.186 '@types/node': 18.11.2 '@types/react': 18.0.21 '@types/react-dom': 18.0.6 @@ -57,7 +66,6 @@ devDependencies: jest: 29.2.1_5uyhgycj63wuqgvl4exdnr442q jest-environment-jsdom: 29.2.1 lint-staged: 13.0.3 - lodash: 4.17.21 prettier: 2.7.1 ts-jest: 29.0.3_7yfpbkrrkkmtlepb2un4d37cti ts-node: 10.9.1_id5sxmpllzol2kp2zgqrnepaum @@ -527,6 +535,14 @@ packages: - supports-color dev: true + /@hookform/resolvers/2.9.10_react-hook-form@7.38.0: + resolution: {integrity: sha512-JIL1DgJIlH9yuxcNGtyhsWX/PgNltz+5Gr6+8SX9fhXc/hPbEIk6wPI82nhgvp3uUb6ZfAM5mqg/x7KR7NAb+A==} + peerDependencies: + react-hook-form: ^7.0.0 + dependencies: + react-hook-form: 7.38.0_react@18.2.0 + dev: false + /@humanwhocodes/config-array/0.10.7: resolution: {integrity: sha512-MDl6D6sBsaV452/QSdX+4CXIjZhIcI0PELsxUjk4U828yd58vk3bTIvk/6w5FY+4hIy9sLW0sfrV7K7Kc++j/w==} engines: {node: '>=10.10.0'} @@ -1017,6 +1033,10 @@ packages: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true + /@types/lodash/4.14.186: + resolution: {integrity: sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw==} + dev: true + /@types/node/18.11.2: resolution: {integrity: sha512-BWN3M23gLO2jVG8g/XHIRFWiiV4/GckeFIqbU/C4V3xpoBBWSMk4OZomouN0wCkfQFPqgZikyLr7DOYDysIkkw==} dev: true @@ -3973,6 +3993,15 @@ packages: resolution: {integrity: sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==} dev: false + /react-hook-form/7.38.0_react@18.2.0: + resolution: {integrity: sha512-gxWW1kMeru9xR1GoR+Iw4hA+JBOM3SHfr4DWCUKY0xc7Vv1MLsF109oHtBeWl9shcyPFx67KHru44DheN0XY5A==} + engines: {node: '>=12.22.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + dependencies: + react: 18.2.0 + dev: false + /react-is/16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -4816,3 +4845,7 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true + + /zod/3.19.1: + resolution: {integrity: sha512-LYjZsEDhCdYET9ikFu6dVPGp2YH9DegXjdJToSzD9rO6fy4qiRYFoyEYwps88OseJlPyl2NOe2iJuhEhL7IpEA==} + dev: false diff --git a/coral/src/app/components/Form.tsx b/coral/src/app/components/Form.tsx new file mode 100644 index 0000000000..bf73a5ce45 --- /dev/null +++ b/coral/src/app/components/Form.tsx @@ -0,0 +1,147 @@ +// ❗️ This implementation mirrors the Aiven core and is kept up to date with it. +// The Aiven core solution will be its own open source package soon. +// we're mirroring the implementation as it is, using only the components we actively use in coral. +// ❗️The Aiven core code base is the source of truth for this component. +import { + PrimaryButton, + Input as BaseInput, + InputProps as BaseInputProps, + Option, +} from "@aivenio/design-system"; +import { zodResolver } from "@hookform/resolvers/zod"; +import React, { memo } from "react"; +import { + FieldError, + FieldPath, + FieldValues, + FormProvider, + SubmitErrorHandler, + SubmitHandler, + useForm as _useForm, + useWatch, + useFormContext, + UseFormProps as _UseFormProps, + UseFormReturn, +} from "react-hook-form"; + +// ts-jest can't use the import statement for "lodash/get" +// using required makes us able to still use get without +// having to add babel to jest +// eslint-disable-next-line @typescript-eslint/no-var-requires +const get = require("lodash/get"); + +type FormInputProps = { + name: FieldPath; +}; + +type FormRegisterProps = { + formContext: UseFormReturn; +}; + +export type { SubmitHandler, SubmitErrorHandler, FieldError }; + +export { useWatch, Option }; + +type UseFormProps = Omit< + _UseFormProps, + "resolver" +> & { + //@TODO fix typing + //eslint-disable-next-line @typescript-eslint/no-explicit-any + schema?: any; +}; + +export const useForm = ({ + defaultValues, + schema, + ...props +}: UseFormProps): UseFormReturn => { + return _useForm({ + ...props, + mode: "onBlur", + defaultValues, + resolver: schema && zodResolver(schema), + }); +}; + +type FormProps = UseFormReturn & { + onSubmit: SubmitHandler; + onError?: SubmitErrorHandler; +}; + +export const Form = ({ + onSubmit, + onError, + children, + ...form +}: React.PropsWithChildren>): React.ReactElement> => { + return ( + +
{children}
+
+ ); +}; + +// +// +// +function _TextInput({ + name, + formContext: form, + ...props +}: BaseInputProps & FormInputProps & FormRegisterProps) { + const { errors } = form.formState; + const error = get(errors, name)?.message; + return ( + + ); +} + +const TextInputMemo = memo(_TextInput) as typeof _TextInput; + +export const TextInput = ( + props: FormInputProps & BaseInputProps +): React.ReactElement & BaseInputProps> => { + const ctx = useFormContext(); + return ; +}; + +TextInput.Skeleton = BaseInput.Skeleton; + +type ButtonProps = React.ComponentProps; + +function _SubmitButton({ + formContext: { + formState: { isDirty, isValid }, + }, + ...props +}: ButtonProps & FormRegisterProps) { + return ( + + ); +} + +const SubmitButtonMemo = memo( + _SubmitButton, + // @TODO fix typing + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (_prev: FormRegisterProps, _next: FormRegisterProps) => { + return false; + } +) as typeof _SubmitButton; + +export const SubmitButton = ( + props: ButtonProps +): React.ReactElement => { + const ctx = useFormContext(); + return ; +};