{ "$schema": "https://blode.co/ui/schema/registry-item.json", "name": "form", "title": "Form", "author": "Matthew Blode", "description": "A form component with validation powered by React Hook Form and Zod.", "dependencies": ["@base-ui/react", "@hookform/resolvers", "zod", "react-hook-form"], "registryDependencies": ["button", "label"], "files": [ { "path": "ui/form.tsx", "content": "\"use client\";\n\nimport { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport type * as React from \"react\";\nimport { createContext, isValidElement, useContext, useId, useMemo } from \"react\";\nimport { Controller, FormProvider, useFormContext, useFormState } from \"react-hook-form\";\nimport type { ControllerProps, FieldPath, FieldValues } from \"react-hook-form\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Label } from \"@/components/ui/label\";\n\nconst Form = FormProvider;\n\ninterface FormFieldContextValue<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath = FieldPath,\n> {\n name: TName;\n}\n\nconst FormFieldContext = createContext({} as FormFieldContextValue);\n\nconst FormField = <\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath = FieldPath,\n>({\n ...props\n}: ControllerProps) => {\n const value = useMemo(() => ({ name: props.name }), [props.name]);\n\n return (\n \n \n \n );\n};\n\ninterface FormItemContextValue {\n id: string;\n}\n\nconst FormItemContext = createContext({} as FormItemContextValue);\n\nconst useFormField = () => {\n const fieldContext = useContext(FormFieldContext);\n const itemContext = useContext(FormItemContext);\n const { getFieldState } = useFormContext();\n const formState = useFormState({ name: fieldContext.name });\n const fieldState = getFieldState(fieldContext.name, formState);\n\n if (!fieldContext) {\n throw new Error(\"useFormField should be used within \");\n }\n\n const { id } = itemContext;\n\n return {\n formDescriptionId: `${id}-form-item-description`,\n formItemId: `${id}-form-item`,\n formMessageId: `${id}-form-item-message`,\n id,\n name: fieldContext.name,\n ...fieldState,\n };\n};\n\nconst FormItem = ({ className, ...props }: React.ComponentProps<\"div\">) => {\n const id = useId();\n const value = useMemo(() => ({ id }), [id]);\n\n return (\n \n
\n \n );\n};\n\nconst FormLabel = ({ className, ...props }: React.ComponentProps) => {\n const { error, formItemId } = useFormField();\n\n return (\n \n );\n};\n\nconst FormControl = ({ children, ...props }: React.HTMLAttributes) => {\n const { error, formItemId, formDescriptionId, formMessageId } = useFormField();\n const hasValidChild = isValidElement(children);\n\n const rendered = useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(\n {\n \"aria-describedby\": error\n ? `${formDescriptionId} ${formMessageId}`\n : `${formDescriptionId}`,\n \"aria-invalid\": !!error,\n id: formItemId,\n },\n props as React.ComponentProps<\"div\">,\n ),\n render: (hasValidChild ? children :
) as React.ReactElement,\n state: {\n slot: \"form-control\",\n },\n });\n\n if (!hasValidChild) {\n return null;\n }\n\n return rendered;\n};\n\nconst FormDescription = ({ className, ...props }: React.ComponentProps<\"p\">) => {\n const { formDescriptionId } = useFormField();\n\n return (\n \n );\n};\n\nconst FormMessage = ({ className, ...props }: React.ComponentProps<\"p\">) => {\n const { error, formMessageId } = useFormField();\n const body = error ? String(error?.message ?? \"\") : props.children;\n\n if (!body) {\n return null;\n }\n\n return (\n \n {body}\n

\n );\n};\n\nexport {\n useFormField,\n Form,\n FormItem,\n FormLabel,\n FormControl,\n FormDescription,\n FormMessage,\n FormField,\n};\n", "type": "registry:ui", "target": "" } ], "type": "registry:ui" }