Files
a37f030d-6e8a-4f3d-9f23-1ce…/research/lovable/form.md
vitalijmulika fb5d40bc69 Initial commit
2026-04-29 13:20:41 +03:00

3.9 KiB

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<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 "); }

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 (
  <FormItemContext.Provider value={{ id }}>
    <div ref={ref} className={cn("space-y-2", className)} {...props} />
  </FormItemContext.Provider>
);

}, ); FormItem.displayName = "FormItem";

const FormLabel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef

(({ className, ...props }, ref) => { const { error, formItemId } = useFormField();

return <Label ref={ref} className={cn(error && "text-destructive", className)} htmlFor={formItemId} {...props} />; }); FormLabel.displayName = "FormLabel";

const FormControl = React.forwardRef<React.ElementRef, React.ComponentPropsWithoutRef>( ({ ...props }, ref) => { const { error, formItemId, formDescriptionId, formMessageId } = useFormField();

return (
  <Slot
    ref={ref}
    id={formItemId}
    aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
    aria-invalid={!!error}
    {...props}
  />
);

}, ); FormControl.displayName = "FormControl";

const FormDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes>( ({ className, ...props }, ref) => { const { formDescriptionId } = useFormField();

return <p ref={ref} id={formDescriptionId} className={cn("text-sm text-muted-foreground", className)} {...props} />;

}, ); FormDescription.displayName = "FormDescription";

const FormMessage = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes>( ({ className, children, ...props }, ref) => { const { error, formMessageId } = useFormField(); const body = error ? String(error?.message) : children;

if (!body) {
  return null;
}

return (
  <p ref={ref} id={formMessageId} className={cn("text-sm font-medium text-destructive", className)} {...props}>
    {body}
  </p>
);

}, ); FormMessage.displayName = "FormMessage";

export { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField };