React Hook Form + Ashel M3 Kit
Сегодня разберёмся с вами как строить правильные, удобные, типизированные, с валидацией и самое главное - красивые HTML формы.
Примеры
Первый пример показывает форму с использованием FilledTextInput.
Первый пример показывает форму с использованием OutlinedTextInput.
Компонент
Скопируйте и вставьте код в свой проект.
Обратите внимание, что компонент имеет зависимости таких компонентов как FilledTextField и OutlinedTextField.
Можно сохранить компонент в файл src/shared/ui/Form.tsx:
"use client"import * as React from "react"import { Slot } from "@radix-ui/react-slot"import {Controller,ControllerProps,FieldPath,FieldValues,FormProvider,useFormContext,} from "react-hook-form"import FilledTextField from "./FilledTextField"import OutlinedTextField from "./OutlinedTextField"import { cn } from "@/lib/cn"const Form = FormProvidertype FormFieldContextValue<TFieldValues extends FieldValues = FieldValues,TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>> = {name: TName}const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue)const FormField = <TFieldValues extends FieldValues = FieldValues,TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>({...props}: ControllerProps<TFieldValues, TName>) => {return (<FormFieldContext.Provider value={{ name: props.name }}><Controller {...props} /></FormFieldContext.Provider>)}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 <FormField>")}const { id } = itemContextreturn {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<FormItemContextValue>({} as FormItemContextValue)const FormItem = React.forwardRef<HTMLDivElement,React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => {const id = React.useId()return (<FormItemContext.Provider value={{ id }}><div ref={ref} className={cn("space-y-2", className)} {...props} /></FormItemContext.Provider>)})FormItem.displayName = "FormItem"const FormControl = React.forwardRef<React.ElementRef<typeof Slot>,React.ComponentPropsWithoutRef<typeof Slot>>(({ ...props }, ref) => {const { error, formItemId, formDescriptionId, formMessageId } = useFormField()return (<Slotref={ref}id={formItemId}aria-describedby={!error? `${formDescriptionId}`: `${formDescriptionId} ${formMessageId}`}aria-invalid={!!error}{...props}/>)})FormControl.displayName = "FormControl"const FormFilledInput = React.forwardRef<React.ElementRef<typeof FilledTextField>,React.ComponentPropsWithoutRef<typeof FilledTextField>>(({ className, ...props }, forwardedRef) => {const { error } = useFormField()return (<FilledTextFieldclassName={cn(className)}supportingText={error && error.message}variant={error ? "error" : "default"}ref={forwardedRef}{...props}/>)})FormFilledInput.displayName = "FormFilledInput"const FormOutlinedInput = React.forwardRef<React.ElementRef<typeof FilledTextField>,React.ComponentPropsWithoutRef<typeof FilledTextField>>(({ className, ...props }, forwardedRef) => {const { error } = useFormField()return (<OutlinedTextFieldclassName={cn(className)}supportingText={error && error.message}variant={error ? "error" : "default"}ref={forwardedRef}{...props}/>)})FormOutlinedInput.displayName = "FormOutlinedInput"const FormMessage = React.forwardRef<HTMLParagraphElement,React.HTMLAttributes<HTMLParagraphElement>>(({ className, children, ...props }, ref) => {const { error, formMessageId } = useFormField()const body = error ? String(error?.message) : childrenif (!body) {return null}return (<pref={ref}id={formMessageId}className={cn("text-sm font-medium text-error", className)}{...props}>{body}</p>)})FormMessage.displayName = "FormMessage"export {useFormField,Form,FormItem,FormControl,FormFilledInput,FormOutlinedInput,FormMessage,FormField,}