From f750b3e970baf12f64614e0fbd6fced3425e3e5e Mon Sep 17 00:00:00 2001 From: Meng Zhang Date: Tue, 5 Dec 2023 16:15:04 +0800 Subject: [PATCH] adapt react forms --- ee/tabby-ui/app/auth/components/signup.tsx | 2 +- .../app/auth/components/user-auth-form.tsx | 123 +++++++----- ee/tabby-ui/components/ui/form.tsx | 176 ++++++++++++++++++ ee/tabby-ui/package.json | 3 + ee/tabby-ui/yarn.lock | 15 ++ 5 files changed, 268 insertions(+), 51 deletions(-) create mode 100644 ee/tabby-ui/components/ui/form.tsx diff --git a/ee/tabby-ui/app/auth/components/signup.tsx b/ee/tabby-ui/app/auth/components/signup.tsx index 0528fd7..f43bbc2 100644 --- a/ee/tabby-ui/app/auth/components/signup.tsx +++ b/ee/tabby-ui/app/auth/components/signup.tsx @@ -15,7 +15,7 @@ export default function Signup() { {title}

- Enter your credentials below to create account + Fill form below to create your account

diff --git a/ee/tabby-ui/app/auth/components/user-auth-form.tsx b/ee/tabby-ui/app/auth/components/user-auth-form.tsx index 3dd1b73..a5fb309 100644 --- a/ee/tabby-ui/app/auth/components/user-auth-form.tsx +++ b/ee/tabby-ui/app/auth/components/user-auth-form.tsx @@ -2,76 +2,99 @@ import * as React from "react" +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import * as z from "zod" + import { cn } from "@/lib/utils" import { IconSpinner } from "@/components/ui/icons" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form" + +const formSchema = z.object({ + email: z.string().email("Invalid email address"), + password1: z.string(), + password2: z.string(), + invitationCode: z.string().optional(), +}).refine((data) => data.password1 === data.password2, { + message: "Passwords don't match", + path: ["password2"], +}); interface UserAuthFormProps extends React.HTMLAttributes { invitationCode?: string } export function UserAuthForm({ className, invitationCode, ...props }: UserAuthFormProps) { - const [isLoading, setIsLoading] = React.useState(false) + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + email: "", + password1: "", + password2: "", + invitationCode, + } + }) - async function onSubmit(event: React.SyntheticEvent) { - event.preventDefault() - setIsLoading(true) + const { isSubmitSuccessful, isSubmitting } = form.formState; - setTimeout(() => { - setIsLoading(false) - }, 3000) + async function onSubmit(values: z.infer) { + await new Promise(resolve => setTimeout(resolve, 500000)) } return (
-
-
-
- - -
-
- - -
-
- - -
- -
-
+ +
) } \ No newline at end of file diff --git a/ee/tabby-ui/components/ui/form.tsx b/ee/tabby-ui/components/ui/form.tsx new file mode 100644 index 0000000..4603f8b --- /dev/null +++ b/ee/tabby-ui/components/ui/form.tsx @@ -0,0 +1,176 @@ +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { Slot } from "@radix-ui/react-slot" +import { + Controller, + ControllerProps, + FieldPath, + FieldValues, + FormProvider, + useFormContext, +} from "react-hook-form" + +import { cn } from "@/lib/utils" +import { Label } from "@/components/ui/label" + +const Form = FormProvider + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +> = { + name: TName +} + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue +) + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +>({ + ...props +}: ControllerProps) => { + return ( + + + + ) +} + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext) + const itemContext = React.useContext(FormItemContext) + const { getFieldState, formState } = useFormContext() + + const fieldState = getFieldState(fieldContext.name, formState) + + if (!fieldContext) { + throw new Error("useFormField should be used within ") + } + + const { id } = itemContext + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + } +} + +type FormItemContextValue = { + id: string +} + +const FormItemContext = React.createContext( + {} as FormItemContextValue +) + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId() + + return ( + +
+ + ) +}) +FormItem.displayName = "FormItem" + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField() + + return ( +