Skip to content

Commit

Permalink
feat: Add Form component based on aiven-core implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
programmiri committed Oct 28, 2022
1 parent a580813 commit 26a5c49
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 4 deletions.
8 changes: 6 additions & 2 deletions coral/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
37 changes: 35 additions & 2 deletions coral/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

147 changes: 147 additions & 0 deletions coral/src/app/components/Form.tsx
Original file line number Diff line number Diff line change
@@ -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<T extends FieldValues = FieldValues> = {
name: FieldPath<T>;
};

type FormRegisterProps<T extends FieldValues = FieldValues> = {
formContext: UseFormReturn<T>;
};

export type { SubmitHandler, SubmitErrorHandler, FieldError };

export { useWatch, Option };

type UseFormProps<T extends FieldValues = FieldValues> = Omit<
_UseFormProps<T>,
"resolver"
> & {
//@TODO fix typing
//eslint-disable-next-line @typescript-eslint/no-explicit-any
schema?: any;
};

export const useForm = <T extends FieldValues = FieldValues>({
defaultValues,
schema,
...props
}: UseFormProps<T>): UseFormReturn<T> => {
return _useForm<T>({
...props,
mode: "onBlur",
defaultValues,
resolver: schema && zodResolver(schema),
});
};

type FormProps<T extends FieldValues = FieldValues> = UseFormReturn<T> & {
onSubmit: SubmitHandler<T>;
onError?: SubmitErrorHandler<T>;
};

export const Form = <T extends FieldValues = FieldValues>({
onSubmit,
onError,
children,
...form
}: React.PropsWithChildren<FormProps<T>>): React.ReactElement<FormProps<T>> => {
return (
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSubmit, onError)}>{children}</form>
</FormProvider>
);
};

//
// <TextInput>
//
function _TextInput<T extends FieldValues>({
name,
formContext: form,
...props
}: BaseInputProps & FormInputProps<T> & FormRegisterProps<T>) {
const { errors } = form.formState;
const error = get(errors, name)?.message;
return (
<BaseInput
{...props}
type="text"
{...form.register(name)}
valid={error ? false : undefined}
// @TODO fix typing
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
error={error}
/>
);
}

const TextInputMemo = memo(_TextInput) as typeof _TextInput;

export const TextInput = <T extends FieldValues>(
props: FormInputProps<T> & BaseInputProps
): React.ReactElement<FormInputProps<T> & BaseInputProps> => {
const ctx = useFormContext<T>();
return <TextInputMemo formContext={ctx} {...props} />;
};

TextInput.Skeleton = BaseInput.Skeleton;

type ButtonProps = React.ComponentProps<typeof PrimaryButton>;

function _SubmitButton<T extends FieldValues>({
formContext: {
formState: { isDirty, isValid },
},
...props
}: ButtonProps & FormRegisterProps<T>) {
return (
<PrimaryButton {...props} type="submit" disabled={!isDirty || !isValid} />
);
}

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 = <T extends FieldValues>(
props: ButtonProps
): React.ReactElement<ButtonProps> => {
const ctx = useFormContext<T>();
return <SubmitButtonMemo formContext={ctx} {...props} />;
};

0 comments on commit 26a5c49

Please sign in to comment.