{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "form", "title": "Form", "files": [ { "path": "components/form/form.tsx", "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\n\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport type { DefaultValues, FieldErrors, FieldValues } from \"react-hook-form\";\nimport React from \"react\";\n\nimport type { FormRootProps } from \"./_components/form-root\";\n// import type { FormBaseProps, FormContextValue } from \"./context\";\nimport type { FormInstance, UseFormProps } from \"./hooks/use-form\";\nimport { useComponentConfig } from \"../config-provider/context\";\nimport { FormProvider } from \"./_components/form-root\";\nimport { useForm } from \"./hooks/use-form\";\n\ntype ErrorField = { name: string[]; errors: string[] };\n\ntype OnFinishFailedErrorInfo = {\n values: TFieldValues;\n errorFields: ErrorField[];\n outOfDate: boolean;\n};\n\ntype FormConfigProps<\n TFieldValues extends FieldValues = FieldValues,\n TContext = any,\n TTransformedValues = TFieldValues,\n> = Omit, \"onSubmit\"> &\n Omit<\n React.DetailedHTMLProps<\n React.FormHTMLAttributes,\n HTMLFormElement\n >,\n \"children\" | \"onSubmit\"\n > & {\n form?: never;\n initialValues?: DefaultValues;\n onFinish?: (values: TFieldValues) => void | Promise;\n onFinishFailed?: (errorInfo: OnFinishFailedErrorInfo) => void;\n };\n\ntype WithFormProp<\n TFieldValues extends FieldValues = FieldValues,\n TContext = any,\n TTransformedValues = TFieldValues,\n> = {\n form: FormInstance;\n\n onFinish?: never;\n onFinishFailed?: never;\n} & Omit<\n React.DetailedHTMLProps<\n React.FormHTMLAttributes,\n HTMLFormElement\n >,\n \"children\"\n>;\n\ntype FormProps<\n TFieldValues extends FieldValues = FieldValues,\n TContext = any,\n TTransformedValues = TFieldValues,\n> = Pick<\n FormRootProps,\n | \"layout\"\n | \"labelAlign\"\n | \"labelCol\"\n | \"labelWrap\"\n | \"wrapperCol\"\n | \"colon\"\n | \"classNames\"\n> &\n (\n | (FormConfigProps & {\n children: ReactNode;\n })\n | (WithFormProp & {\n children: ReactNode;\n })\n );\n\nconst Form = <\n TFieldValues extends FieldValues,\n TContext,\n TTransformedValues extends FieldValues = TFieldValues,\n>({\n id: idProps,\n form,\n layout: layoutProp,\n labelAlign,\n labelCol,\n labelWrap,\n wrapperCol,\n colon: colonProp,\n classNames,\n children,\n ...props\n}: FormProps) => {\n const generatedId = React.useId();\n const id = idProps ?? generatedId;\n\n const { layout: layoutConfig, colon: colonConfig } =\n useComponentConfig(\"form\");\n\n const layout = layoutProp ?? layoutConfig ?? \"vertical\";\n const colon =\n colonProp ?? (layout === \"vertical\" ? false : (colonConfig ?? true));\n\n if (form) {\n return (\n \n id={id}\n form={form}\n layout={layout}\n labelAlign={labelAlign}\n labelCol={labelCol}\n labelWrap={labelWrap}\n wrapperCol={wrapperCol}\n colon={colon}\n classNames={classNames}\n >\n
\n {children}\n
\n \n );\n }\n\n return (\n \n {children}\n \n );\n};\n\nconst FormWithoutFormProp = <\n TFieldValues extends FieldValues,\n TContext,\n TTransformedValues extends FieldValues = TFieldValues,\n>(\n props: FormConfigProps &\n Pick<\n FormRootProps,\n | \"id\"\n | \"layout\"\n | \"labelAlign\"\n | \"labelCol\"\n | \"labelWrap\"\n | \"wrapperCol\"\n | \"colon\"\n | \"classNames\"\n > & {\n children: ReactNode;\n },\n) => {\n const {\n id,\n initialValues,\n onFinish,\n onFinishFailed,\n layout,\n labelAlign,\n labelCol,\n labelWrap,\n wrapperCol,\n colon,\n classNames,\n children,\n schema,\n defaultValues,\n mode,\n reValidateMode,\n resolver,\n context,\n criteriaMode,\n shouldFocusError,\n shouldUnregister,\n shouldUseNativeValidation,\n progressive,\n delayError,\n disabled,\n resetOptions,\n values,\n errors,\n onValuesChange,\n ...htmlFormProps\n } = props;\n\n // Build error fields from react-hook-form errors\n const buildErrorFields = (\n errors: FieldErrors,\n ): ErrorField[] => {\n const result: ErrorField[] = [];\n const traverse = (obj: Record, path: string[] = []) => {\n for (const [key, value] of Object.entries(obj)) {\n const nextPath = [...path, key];\n if (value && typeof value === \"object\") {\n const maybeMsg = (value as { message?: unknown }).message;\n if (typeof maybeMsg === \"string\" && maybeMsg) {\n result.push({ name: nextPath, errors: [maybeMsg] });\n }\n traverse(value as Record, nextPath);\n }\n }\n };\n traverse(errors as Record);\n return result;\n };\n\n // Wrap onFinish to handle both success and error cases\n const handleFormSubmit = async (data: TTransformedValues) => {\n if (onFinish) {\n await onFinish(data as unknown as TFieldValues);\n }\n };\n\n // Create form instance with useForm\n const form = useForm({\n schema,\n defaultValues: initialValues ?? defaultValues,\n mode,\n reValidateMode,\n resolver,\n context,\n criteriaMode,\n shouldFocusError,\n shouldUnregister,\n shouldUseNativeValidation,\n progressive,\n delayError,\n disabled,\n resetOptions,\n values,\n errors,\n onValuesChange,\n onSubmit: handleFormSubmit,\n });\n\n // Wrap form submit to handle onFinishFailed\n const handleSubmit = (e?: React.BaseSyntheticEvent) => {\n if (onFinishFailed) {\n return form.handleSubmit(\n handleFormSubmit,\n (errors: FieldErrors) => {\n const errorFields = buildErrorFields(errors);\n const values = form.getValues();\n onFinishFailed({\n values,\n errorFields,\n outOfDate: false,\n });\n },\n )(e);\n }\n return form.submit(e);\n };\n\n return (\n \n id={id}\n {...form}\n layout={layout}\n labelAlign={labelAlign}\n labelCol={labelCol}\n labelWrap={labelWrap}\n wrapperCol={wrapperCol}\n colon={colon}\n classNames={classNames}\n >\n
\n {children}\n
\n \n );\n};\n\nexport { Form };\nexport type { FormProps, OnFinishFailedErrorInfo };\n", "type": "registry:file", "target": "components/form/form.tsx" } ], "type": "registry:component" }