diff --git a/registry/auto-form-src.json b/registry/auto-form-src.json index f6f9700..451521d 100644 --- a/registry/auto-form-src.json +++ b/registry/auto-form-src.json @@ -31,19 +31,19 @@ { "path": "utils.ts", "target": "src/components/ui/auto-form/utils.ts", - "content": "import React from \"react\";\nimport { DefaultValues } from \"react-hook-form\";\nimport { z } from \"zod\";\nimport { FieldConfig } from \"./types\";\n\n// TODO: This should support recursive ZodEffects but TypeScript doesn't allow circular type definitions.\nexport type ZodObjectOrWrapped =\n | z.ZodObject\n | z.ZodEffects>;\n\n/**\n * Beautify a camelCase string.\n * e.g. \"myString\" -> \"My String\"\n */\nexport function beautifyObjectName(string: string) {\n // if numbers only return the string\n let output = string.replace(/([A-Z])/g, \" $1\");\n output = output.charAt(0).toUpperCase() + output.slice(1);\n return output;\n}\n\n/**\n * Get the lowest level Zod type.\n * This will unpack optionals, refinements, etc.\n */\nexport function getBaseSchema<\n ChildType extends z.ZodAny | z.AnyZodObject = z.ZodAny,\n>(schema: ChildType | z.ZodEffects): ChildType | null {\n if (!schema) return null;\n if (\"innerType\" in schema._def) {\n return getBaseSchema(schema._def.innerType as ChildType);\n }\n if (\"schema\" in schema._def) {\n return getBaseSchema(schema._def.schema as ChildType);\n }\n\n return schema as ChildType;\n}\n\n/**\n * Get the type name of the lowest level Zod type.\n * This will unpack optionals, refinements, etc.\n */\nexport function getBaseType(schema: z.ZodAny): string {\n const baseSchema = getBaseSchema(schema);\n return baseSchema ? baseSchema._def.typeName : \"\";\n}\n\n/**\n * Search for a \"ZodDefult\" in the Zod stack and return its value.\n */\nexport function getDefaultValueInZodStack(schema: z.ZodAny): any {\n const typedSchema = schema as unknown as z.ZodDefault<\n z.ZodNumber | z.ZodString\n >;\n\n if (typedSchema._def.typeName === \"ZodDefault\") {\n return typedSchema._def.defaultValue();\n }\n\n if (\"innerType\" in typedSchema._def) {\n return getDefaultValueInZodStack(\n typedSchema._def.innerType as unknown as z.ZodAny,\n );\n }\n if (\"schema\" in typedSchema._def) {\n return getDefaultValueInZodStack(\n (typedSchema._def as any).schema as z.ZodAny,\n );\n }\n\n return undefined;\n}\n\n/**\n * Get all default values from a Zod schema.\n */\nexport function getDefaultValues>(\n schema: Schema,\n fieldConfig?: FieldConfig>,\n) {\n if (!schema) return null;\n const { shape } = schema;\n type DefaultValuesType = DefaultValues>>;\n const defaultValues = {} as DefaultValuesType;\n if (!shape) return defaultValues;\n\n for (const key of Object.keys(shape)) {\n const item = shape[key] as z.ZodAny;\n\n if (getBaseType(item) === \"ZodObject\") {\n const defaultItems = getDefaultValues(\n getBaseSchema(item) as unknown as z.ZodObject,\n fieldConfig?.[key] as FieldConfig>,\n );\n\n if (defaultItems !== null) {\n for (const defaultItemKey of Object.keys(defaultItems)) {\n const pathKey = `${key}.${defaultItemKey}` as keyof DefaultValuesType;\n defaultValues[pathKey] = defaultItems[defaultItemKey];\n }\n }\n } else {\n let defaultValue = getDefaultValueInZodStack(item);\n if (\n (defaultValue === null || defaultValue === \"\") &&\n fieldConfig?.[key]?.inputProps\n ) {\n defaultValue = (fieldConfig?.[key]?.inputProps as unknown as any)\n .defaultValue;\n }\n if (defaultValue !== undefined) {\n defaultValues[key as keyof DefaultValuesType] = defaultValue;\n }\n }\n }\n\n return defaultValues;\n}\n\nexport function getObjectFormSchema(\n schema: ZodObjectOrWrapped,\n): z.ZodObject {\n if (schema?._def.typeName === \"ZodEffects\") {\n const typedSchema = schema as z.ZodEffects>;\n return getObjectFormSchema(typedSchema._def.schema);\n }\n return schema as z.ZodObject;\n}\n\n/**\n * Convert a Zod schema to HTML input props to give direct feedback to the user.\n * Once submitted, the schema will be validated completely.\n */\nexport function zodToHtmlInputProps(\n schema:\n | z.ZodNumber\n | z.ZodString\n | z.ZodOptional\n | any,\n): React.InputHTMLAttributes {\n if ([\"ZodOptional\", \"ZodNullable\"].includes(schema._def.typeName)) {\n const typedSchema = schema as z.ZodOptional;\n return {\n ...zodToHtmlInputProps(typedSchema._def.innerType),\n required: false,\n };\n }\n const typedSchema = schema as z.ZodNumber | z.ZodString;\n\n if (!(\"checks\" in typedSchema._def))\n return {\n required: true,\n };\n\n const { checks } = typedSchema._def;\n const inputProps: React.InputHTMLAttributes = {\n required: true,\n };\n const type = getBaseType(schema);\n\n for (const check of checks) {\n if (check.kind === \"min\") {\n if (type === \"ZodString\") {\n inputProps.minLength = check.value;\n } else {\n inputProps.min = check.value;\n }\n }\n if (check.kind === \"max\") {\n if (type === \"ZodString\") {\n inputProps.maxLength = check.value;\n } else {\n inputProps.max = check.value;\n }\n }\n }\n\n return inputProps;\n}\n", + "content": "import React from \"react\";\nimport { DefaultValues } from \"react-hook-form\";\nimport { z } from \"zod\";\nimport { FieldConfig } from \"./types\";\n\n// TODO: This should support recursive ZodEffects but TypeScript doesn't allow circular type definitions.\nexport type ZodObjectOrWrapped =\n | z.ZodObject\n | z.ZodEffects>;\n\n/**\n * Beautify a camelCase string.\n * e.g. \"myString\" -> \"My String\"\n */\nexport function beautifyObjectName(string: string) {\n // if numbers only return the string\n let output = string.replace(/([A-Z])/g, \" $1\");\n output = output.charAt(0).toUpperCase() + output.slice(1);\n return output;\n}\n\n/**\n * Get the lowest level Zod type.\n * This will unpack optionals, refinements, etc.\n */\nexport function getBaseSchema<\n ChildType extends z.ZodAny | z.AnyZodObject = z.ZodAny,\n>(schema: ChildType | z.ZodEffects): ChildType | null {\n if (!schema) return null;\n if (\"innerType\" in schema._def) {\n return getBaseSchema(schema._def.innerType as ChildType);\n }\n if (\"schema\" in schema._def) {\n return getBaseSchema(schema._def.schema as ChildType);\n }\n\n return schema as ChildType;\n}\n\n/**\n * Get the type name of the lowest level Zod type.\n * This will unpack optionals, refinements, etc.\n */\nexport function getBaseType(schema: z.ZodAny): string {\n const baseSchema = getBaseSchema(schema);\n return baseSchema ? baseSchema._def.typeName : \"\";\n}\n\n/**\n * Search for a \"ZodDefult\" in the Zod stack and return its value.\n */\nexport function getDefaultValueInZodStack(schema: z.ZodAny): any {\n const typedSchema = schema as unknown as z.ZodDefault<\n z.ZodNumber | z.ZodString\n >;\n\n if (typedSchema._def.typeName === \"ZodDefault\") {\n return typedSchema._def.defaultValue();\n }\n\n if (\"innerType\" in typedSchema._def) {\n return getDefaultValueInZodStack(\n typedSchema._def.innerType as unknown as z.ZodAny,\n );\n }\n if (\"schema\" in typedSchema._def) {\n return getDefaultValueInZodStack(\n (typedSchema._def as any).schema as z.ZodAny,\n );\n }\n\n return undefined;\n}\n\n/**\n * Get all default values from a Zod schema.\n */\nexport function getDefaultValues>(\n schema: Schema,\n fieldConfig?: FieldConfig>,\n) {\n if (!schema) return null;\n const { shape } = schema;\n type DefaultValuesType = DefaultValues>>;\n const defaultValues = {} as DefaultValuesType;\n if (!shape) return defaultValues;\n\n for (const key of Object.keys(shape)) {\n const item = shape[key] as z.ZodAny;\n\n if (getBaseType(item) === \"ZodObject\") {\n const defaultItems = getDefaultValues(\n getBaseSchema(item) as unknown as z.ZodObject,\n fieldConfig?.[key] as FieldConfig>,\n );\n\n if (defaultItems !== null) {\n for (const defaultItemKey of Object.keys(defaultItems)) {\n const pathKey = `${key}.${defaultItemKey}` as keyof DefaultValuesType;\n defaultValues[pathKey] = defaultItems[defaultItemKey];\n }\n }\n } else {\n let defaultValue = getDefaultValueInZodStack(item);\n if (\n (defaultValue === null || defaultValue === \"\") &&\n fieldConfig?.[key]?.inputProps\n ) {\n defaultValue = (fieldConfig?.[key]?.inputProps as unknown as any)\n .defaultValue;\n }\n if (defaultValue !== undefined) {\n defaultValues[key as keyof DefaultValuesType] = defaultValue;\n }\n }\n }\n\n return defaultValues;\n}\n\nexport function getObjectFormSchema(\n schema: ZodObjectOrWrapped,\n): z.ZodObject {\n if (schema?._def.typeName === \"ZodEffects\") {\n const typedSchema = schema as z.ZodEffects>;\n return getObjectFormSchema(typedSchema._def.schema);\n }\n return schema as z.ZodObject;\n}\n\n/**\n * Convert a Zod schema to HTML input props to give direct feedback to the user.\n * Once submitted, the schema will be validated completely.\n */\nexport function zodToHtmlInputProps(\n schema:\n | z.ZodNumber\n | z.ZodString\n | z.ZodOptional\n | any,\n): React.InputHTMLAttributes {\n if ([\"ZodOptional\", \"ZodNullable\"].includes(schema._def.typeName)) {\n const typedSchema = schema as z.ZodOptional;\n return {\n ...zodToHtmlInputProps(typedSchema._def.innerType),\n required: false,\n };\n }\n const typedSchema = schema as z.ZodNumber | z.ZodString;\n\n if (!(\"checks\" in typedSchema._def))\n return {\n required: true,\n };\n\n const { checks } = typedSchema._def;\n const inputProps: React.InputHTMLAttributes = {\n required: true,\n };\n const type = getBaseType(schema);\n\n for (const check of checks) {\n if (check.kind === \"min\") {\n if (type === \"ZodString\") {\n inputProps.minLength = check.value;\n } else {\n inputProps.min = check.value;\n }\n }\n if (check.kind === \"max\") {\n if (type === \"ZodString\") {\n inputProps.maxLength = check.value;\n } else {\n inputProps.max = check.value;\n }\n }\n }\n\n return inputProps;\n}\n\n/**\n * Sort the fields by order.\n * If no order is set, the field will be sorted based on the order in the schema.\n */\n\nexport function sortFieldsByOrder>(\n fieldConfig: FieldConfig> | undefined,\n keys: string[]\n) {\n const sortedFields = keys.sort((a, b) => {\n const fieldA: number = (fieldConfig?.[a]?.order as number) ?? 0;\n const fieldB = (fieldConfig?.[b]?.order as number) ?? 0;\n return fieldA - fieldB;\n });\n\n return sortedFields;\n}", "type": "registry:ui" }, { "path": "types.ts", "target": "src/components/ui/auto-form/types.ts", - "content": "import { ControllerRenderProps, FieldValues } from \"react-hook-form\";\nimport * as z from \"zod\";\nimport { INPUT_COMPONENTS } from \"./config\";\n\nexport type FieldConfigItem = {\n description?: React.ReactNode;\n inputProps?: React.InputHTMLAttributes &\n React.TextareaHTMLAttributes &\n {\n showLabel?: boolean;\n };\n label?: string;\n fieldType?:\n | keyof typeof INPUT_COMPONENTS\n | React.FC;\n\n renderParent?: (props: {\n children: React.ReactNode;\n }) => React.ReactElement | null;\n};\n\nexport type FieldConfig>> = {\n // If SchemaType.key is an object, create a nested FieldConfig, otherwise FieldConfigItem\n [Key in keyof SchemaType]?: SchemaType[Key] extends object\n ? FieldConfig>\n : FieldConfigItem;\n};\n\nexport enum DependencyType {\n DISABLES,\n REQUIRES,\n HIDES,\n SETS_OPTIONS,\n}\n\ntype BaseDependency>> = {\n sourceField: keyof SchemaType;\n type: DependencyType;\n targetField: keyof SchemaType;\n when: (sourceFieldValue: any, targetFieldValue: any) => boolean;\n};\n\nexport type ValueDependency>> =\n BaseDependency & {\n type:\n | DependencyType.DISABLES\n | DependencyType.REQUIRES\n | DependencyType.HIDES;\n };\n\nexport type EnumValues = readonly [string, ...string[]];\n\nexport type OptionsDependency<\n SchemaType extends z.infer>,\n> = BaseDependency & {\n type: DependencyType.SETS_OPTIONS;\n\n // Partial array of values from sourceField that will trigger the dependency\n options: EnumValues;\n};\n\nexport type Dependency>> =\n | ValueDependency\n | OptionsDependency;\n\n/**\n * A FormInput component can handle a specific Zod type (e.g. \"ZodBoolean\")\n */\nexport type AutoFormInputComponentProps = {\n zodInputProps: React.InputHTMLAttributes;\n field: ControllerRenderProps;\n fieldConfigItem: FieldConfigItem;\n label: string;\n isRequired: boolean;\n fieldProps: any;\n zodItem: z.ZodAny;\n className?: string;\n};\n", + "content": "import {\n ControllerRenderProps,\n FieldValues,\n UseFormSetError,\n} from \"react-hook-form\";\nimport * as z from \"zod\";\nimport { INPUT_COMPONENTS } from \"./config\";\n\nexport type FieldConfigItem = {\n description?: React.ReactNode;\n inputProps?: React.InputHTMLAttributes &\n React.TextareaHTMLAttributes & {\n showLabel?: boolean;\n };\n label?: string;\n fieldType?:\n | keyof typeof INPUT_COMPONENTS\n | React.FC;\n\n renderParent?: (props: {\n children: React.ReactNode;\n }) => React.ReactElement | null;\n\n order?: number;\n};\n\nexport type FieldConfig>> = {\n // If SchemaType.key is an object, create a nested FieldConfig, otherwise FieldConfigItem\n [Key in keyof SchemaType]?: SchemaType[Key] extends object\n ? FieldConfig>\n : FieldConfigItem;\n};\n\nexport enum DependencyType {\n DISABLES,\n REQUIRES,\n HIDES,\n SETS_OPTIONS,\n}\n\ntype BaseDependency>> = {\n sourceField: keyof SchemaType;\n type: DependencyType;\n targetField: keyof SchemaType;\n when: (sourceFieldValue: any, targetFieldValue: any) => boolean;\n};\n\nexport type ValueDependency>> =\n BaseDependency & {\n type:\n | DependencyType.DISABLES\n | DependencyType.REQUIRES\n | DependencyType.HIDES;\n };\n\nexport type EnumValues = readonly [string, ...string[]];\n\nexport type OptionsDependency<\n SchemaType extends z.infer>,\n> = BaseDependency & {\n type: DependencyType.SETS_OPTIONS;\n\n // Partial array of values from sourceField that will trigger the dependency\n options: EnumValues;\n};\n\nexport type Dependency>> =\n | ValueDependency\n | OptionsDependency;\n\n/**\n * A FormInput component can handle a specific Zod type (e.g. \"ZodBoolean\")\n */\nexport type AutoFormInputComponentProps = {\n zodInputProps: React.InputHTMLAttributes;\n field: ControllerRenderProps;\n fieldConfigItem: FieldConfigItem;\n label: string;\n isRequired: boolean;\n fieldProps: any;\n zodItem: z.ZodAny;\n className?: string;\n};\n\nexport type SubmitOptions>> = {\n setError: UseFormSetError;\n};\n", "type": "registry:ui" }, { "path": "index.tsx", "target": "src/components/ui/auto-form/index.tsx", - "content": "\"use client\";\nimport { Form } from \"@/components/ui/form\";\nimport React from \"react\";\nimport { DefaultValues, FormState, useForm } from \"react-hook-form\";\nimport { z } from \"zod\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\n\nimport AutoFormObject from \"./fields/object\";\nimport { Dependency, FieldConfig } from \"./types\";\nimport {\n ZodObjectOrWrapped,\n getDefaultValues,\n getObjectFormSchema,\n} from \"./utils\";\n\nexport function AutoFormSubmit({\n children,\n className,\n disabled,\n}: {\n children?: React.ReactNode;\n className?: string;\n disabled?: boolean;\n}) {\n return (\n \n );\n}\n\nfunction AutoForm({\n formSchema,\n values: valuesProp,\n onValuesChange: onValuesChangeProp,\n onParsedValuesChange,\n onSubmit: onSubmitProp,\n fieldConfig,\n children,\n className,\n dependencies,\n}: {\n formSchema: SchemaType;\n values?: Partial>;\n onValuesChange?: (values: Partial>) => void;\n onParsedValuesChange?: (values: Partial>) => void;\n onSubmit?: (values: z.infer) => void;\n fieldConfig?: FieldConfig>;\n children?:\n | React.ReactNode\n | ((formState: FormState>) => React.ReactNode);\n className?: string;\n dependencies?: Dependency>[];\n}) {\n const objectFormSchema = getObjectFormSchema(formSchema);\n const defaultValues: DefaultValues> | null =\n getDefaultValues(objectFormSchema, fieldConfig);\n\n const form = useForm>({\n resolver: zodResolver(formSchema),\n defaultValues: defaultValues ?? undefined,\n values: valuesProp,\n });\n\n function onSubmit(values: z.infer) {\n const parsedValues = formSchema.safeParse(values);\n if (parsedValues.success) {\n onSubmitProp?.(parsedValues.data);\n }\n }\n\n React.useEffect(() => {\n const subscription = form.watch((values) => {\n onValuesChangeProp?.(values);\n const parsedValues = formSchema.safeParse(values);\n if (parsedValues.success) {\n onParsedValuesChange?.(parsedValues.data);\n }\n });\n\n return () => subscription.unsubscribe();\n }, [form, formSchema, onValuesChangeProp, onParsedValuesChange]);\n\n const renderChildren =\n typeof children === \"function\"\n ? children(form.formState as FormState>)\n : children;\n\n return (\n
\n
\n {\n form.handleSubmit(onSubmit)(e);\n }}\n className={cn(\"space-y-5\", className)}\n >\n \n\n {renderChildren}\n \n \n
\n );\n}\n\nexport default AutoForm;\n", + "content": "\"use client\";\nimport { Form } from \"@/components/ui/form\";\nimport React from \"react\";\nimport { DefaultValues, FormState, useForm } from \"react-hook-form\";\nimport { z } from \"zod\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\n\nimport AutoFormObject from \"./fields/object\";\nimport { Dependency, FieldConfig, SubmitOptions } from \"./types\";\nimport {\n ZodObjectOrWrapped,\n getDefaultValues,\n getObjectFormSchema,\n} from \"./utils\";\n\nexport function AutoFormSubmit({\n children,\n className,\n disabled,\n}: {\n children?: React.ReactNode;\n className?: string;\n disabled?: boolean;\n}) {\n return (\n \n );\n}\n\nfunction AutoForm({\n formSchema,\n values: valuesProp,\n onValuesChange: onValuesChangeProp,\n onParsedValuesChange,\n onSubmit: onSubmitProp,\n fieldConfig,\n children,\n className,\n dependencies,\n}: {\n formSchema: SchemaType;\n values?: Partial>;\n onValuesChange?: (values: Partial>) => void;\n onParsedValuesChange?: (values: Partial>) => void;\n onSubmit?: (\n values: z.infer,\n options: SubmitOptions>\n ) => void;\n fieldConfig?: FieldConfig>;\n children?:\n | React.ReactNode\n | ((formState: FormState>) => React.ReactNode);\n className?: string;\n dependencies?: Dependency>[];\n}) {\n const objectFormSchema = getObjectFormSchema(formSchema);\n const defaultValues: DefaultValues> | null =\n getDefaultValues(objectFormSchema, fieldConfig);\n\n const form = useForm>({\n resolver: zodResolver(formSchema),\n defaultValues: defaultValues ?? undefined,\n values: valuesProp,\n });\n\n function onSubmit(values: z.infer) {\n const parsedValues = formSchema.safeParse(values);\n if (parsedValues.success) {\n onSubmitProp?.(parsedValues.data, {\n setError: form.setError,\n });\n }\n }\n\n React.useEffect(() => {\n const subscription = form.watch((values) => {\n onValuesChangeProp?.(values);\n const parsedValues = formSchema.safeParse(values);\n if (parsedValues.success) {\n onParsedValuesChange?.(parsedValues.data);\n }\n });\n\n return () => subscription.unsubscribe();\n }, [form, formSchema, onValuesChangeProp, onParsedValuesChange]);\n\n const renderChildren =\n typeof children === \"function\"\n ? children(form.formState as FormState>)\n : children;\n\n return (\n
\n
\n {\n form.handleSubmit(onSubmit)(e);\n }}\n className={cn(\"space-y-5\", className)}\n >\n \n\n {renderChildren}\n \n \n
\n );\n}\n\nexport default AutoForm;\n", "type": "registry:ui" }, { @@ -85,7 +85,7 @@ { "path": "fields/object.tsx", "target": "src/components/ui/auto-form/fields/object.tsx", - "content": "import {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from \"@/components/ui/accordion\";\nimport { FormField } from \"@/components/ui/form\";\nimport { useForm, useFormContext } from \"react-hook-form\";\nimport * as z from \"zod\";\nimport { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from \"../config\";\nimport { Dependency, FieldConfig, FieldConfigItem } from \"../types\";\nimport {\n beautifyObjectName,\n getBaseSchema,\n getBaseType,\n zodToHtmlInputProps,\n} from \"../utils\";\nimport AutoFormArray from \"./array\";\nimport resolveDependencies from \"../dependencies\";\n\nfunction DefaultParent({ children }: { children: React.ReactNode }) {\n return <>{children};\n}\n\nexport default function AutoFormObject<\n SchemaType extends z.ZodObject,\n>({\n schema,\n form,\n fieldConfig,\n path = [],\n dependencies = [],\n}: {\n schema: SchemaType | z.ZodEffects;\n form: ReturnType;\n fieldConfig?: FieldConfig>;\n path?: string[];\n dependencies?: Dependency>[];\n}) {\n const { watch } = useFormContext(); // Use useFormContext to access the watch function\n\n if (!schema) {\n return null;\n }\n const { shape } = getBaseSchema(schema) || {};\n\n if (!shape) {\n return null;\n }\n\n const handleIfZodNumber = (item: z.ZodAny) => {\n const isZodNumber = (item as any)._def.typeName === \"ZodNumber\";\n const isInnerZodNumber =\n (item._def as any).innerType?._def?.typeName === \"ZodNumber\";\n\n if (isZodNumber) {\n (item as any)._def.coerce = true;\n } else if (isInnerZodNumber) {\n (item._def as any).innerType._def.coerce = true;\n }\n\n return item;\n };\n\n return (\n \n {Object.keys(shape).map((name) => {\n let item = shape[name] as z.ZodAny;\n item = handleIfZodNumber(item) as z.ZodAny;\n const zodBaseType = getBaseType(item);\n const itemName = item._def.description ?? beautifyObjectName(name);\n const key = [...path, name].join(\".\");\n\n const {\n isHidden,\n isDisabled,\n isRequired: isRequiredByDependency,\n overrideOptions,\n } = resolveDependencies(dependencies, name, watch);\n if (isHidden) {\n return null;\n }\n\n if (zodBaseType === \"ZodObject\") {\n return (\n \n {itemName}\n \n }\n form={form}\n fieldConfig={\n (fieldConfig?.[name] ?? {}) as FieldConfig<\n z.infer\n >\n }\n path={[...path, name]}\n />\n \n \n );\n }\n if (zodBaseType === \"ZodArray\") {\n return (\n }\n form={form}\n fieldConfig={fieldConfig?.[name] ?? {}}\n path={[...path, name]}\n />\n );\n }\n\n const fieldConfigItem: FieldConfigItem = fieldConfig?.[name] ?? {};\n const zodInputProps = zodToHtmlInputProps(item);\n const isRequired =\n isRequiredByDependency ||\n zodInputProps.required ||\n fieldConfigItem.inputProps?.required ||\n false;\n\n if (overrideOptions) {\n item = z.enum(overrideOptions) as unknown as z.ZodAny;\n }\n\n return (\n {\n const inputType =\n fieldConfigItem.fieldType ??\n DEFAULT_ZOD_HANDLERS[zodBaseType] ??\n \"fallback\";\n\n const InputComponent =\n typeof inputType === \"function\"\n ? inputType\n : INPUT_COMPONENTS[inputType];\n\n const ParentElement =\n fieldConfigItem.renderParent ?? DefaultParent;\n\n const defaultValue = fieldConfigItem.inputProps?.defaultValue;\n const value = field.value ?? defaultValue ?? \"\";\n\n const fieldProps = {\n ...zodToHtmlInputProps(item),\n ...field,\n ...fieldConfigItem.inputProps,\n disabled: fieldConfigItem.inputProps?.disabled || isDisabled,\n ref: undefined,\n value: value,\n };\n\n if (InputComponent === undefined) {\n return <>;\n }\n\n return (\n \n \n \n );\n }}\n />\n );\n })}\n \n );\n}\n", + "content": "import {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from \"@/components/ui/accordion\";\nimport { FormField } from \"@/components/ui/form\";\nimport { useForm, useFormContext } from \"react-hook-form\";\nimport * as z from \"zod\";\nimport { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from \"../config\";\nimport { Dependency, FieldConfig, FieldConfigItem } from \"../types\";\nimport {\n beautifyObjectName,\n getBaseSchema,\n getBaseType,\n sortFieldsByOrder,\n zodToHtmlInputProps,\n} from \"../utils\";\nimport AutoFormArray from \"./array\";\nimport resolveDependencies from \"../dependencies\";\n\nfunction DefaultParent({ children }: { children: React.ReactNode }) {\n return <>{children};\n}\n\nexport default function AutoFormObject<\n SchemaType extends z.ZodObject,\n>({\n schema,\n form,\n fieldConfig,\n path = [],\n dependencies = [],\n}: {\n schema: SchemaType | z.ZodEffects;\n form: ReturnType;\n fieldConfig?: FieldConfig>;\n path?: string[];\n dependencies?: Dependency>[];\n}) {\n const { watch } = useFormContext(); // Use useFormContext to access the watch function\n\n if (!schema) {\n return null;\n }\n const { shape } = getBaseSchema(schema) || {};\n\n if (!shape) {\n return null;\n }\n\n const handleIfZodNumber = (item: z.ZodAny) => {\n const isZodNumber = (item as any)._def.typeName === \"ZodNumber\";\n const isInnerZodNumber =\n (item._def as any).innerType?._def?.typeName === \"ZodNumber\";\n\n if (isZodNumber) {\n (item as any)._def.coerce = true;\n } else if (isInnerZodNumber) {\n (item._def as any).innerType._def.coerce = true;\n }\n\n return item;\n };\n\n const sortedFieldKeys = sortFieldsByOrder(fieldConfig, Object.keys(shape));\n\n return (\n \n {sortedFieldKeys.map((name) => {\n let item = shape[name] as z.ZodAny;\n item = handleIfZodNumber(item) as z.ZodAny;\n const zodBaseType = getBaseType(item);\n const itemName = item._def.description ?? beautifyObjectName(name);\n const key = [...path, name].join(\".\");\n\n const {\n isHidden,\n isDisabled,\n isRequired: isRequiredByDependency,\n overrideOptions,\n } = resolveDependencies(dependencies, name, watch);\n if (isHidden) {\n return null;\n }\n\n if (zodBaseType === \"ZodObject\") {\n return (\n \n {itemName}\n \n }\n form={form}\n fieldConfig={\n (fieldConfig?.[name] ?? {}) as FieldConfig<\n z.infer\n >\n }\n path={[...path, name]}\n />\n \n \n );\n }\n if (zodBaseType === \"ZodArray\") {\n return (\n }\n form={form}\n fieldConfig={fieldConfig?.[name] ?? {}}\n path={[...path, name]}\n />\n );\n }\n\n const fieldConfigItem: FieldConfigItem = fieldConfig?.[name] ?? {};\n const zodInputProps = zodToHtmlInputProps(item);\n const isRequired =\n isRequiredByDependency ||\n zodInputProps.required ||\n fieldConfigItem.inputProps?.required ||\n false;\n\n if (overrideOptions) {\n item = z.enum(overrideOptions) as unknown as z.ZodAny;\n }\n\n return (\n {\n const inputType =\n fieldConfigItem.fieldType ??\n DEFAULT_ZOD_HANDLERS[zodBaseType] ??\n \"fallback\";\n\n const InputComponent =\n typeof inputType === \"function\"\n ? inputType\n : INPUT_COMPONENTS[inputType];\n\n const ParentElement =\n fieldConfigItem.renderParent ?? DefaultParent;\n\n const defaultValue = fieldConfigItem.inputProps?.defaultValue;\n const value = field.value ?? defaultValue ?? \"\";\n\n const fieldProps = {\n ...zodToHtmlInputProps(item),\n ...field,\n ...fieldConfigItem.inputProps,\n disabled: fieldConfigItem.inputProps?.disabled || isDisabled,\n ref: undefined,\n value: value,\n };\n\n if (InputComponent === undefined) {\n return <>;\n }\n\n return (\n \n \n \n );\n }}\n />\n );\n })}\n \n );\n}\n", "type": "registry:ui" }, { diff --git a/registry/auto-form.json b/registry/auto-form.json index e0533c2..e8db0db 100644 --- a/registry/auto-form.json +++ b/registry/auto-form.json @@ -31,19 +31,19 @@ { "path": "utils.ts", "target": "components/ui/auto-form/utils.ts", - "content": "import React from \"react\";\nimport { DefaultValues } from \"react-hook-form\";\nimport { z } from \"zod\";\nimport { FieldConfig } from \"./types\";\n\n// TODO: This should support recursive ZodEffects but TypeScript doesn't allow circular type definitions.\nexport type ZodObjectOrWrapped =\n | z.ZodObject\n | z.ZodEffects>;\n\n/**\n * Beautify a camelCase string.\n * e.g. \"myString\" -> \"My String\"\n */\nexport function beautifyObjectName(string: string) {\n // if numbers only return the string\n let output = string.replace(/([A-Z])/g, \" $1\");\n output = output.charAt(0).toUpperCase() + output.slice(1);\n return output;\n}\n\n/**\n * Get the lowest level Zod type.\n * This will unpack optionals, refinements, etc.\n */\nexport function getBaseSchema<\n ChildType extends z.ZodAny | z.AnyZodObject = z.ZodAny,\n>(schema: ChildType | z.ZodEffects): ChildType | null {\n if (!schema) return null;\n if (\"innerType\" in schema._def) {\n return getBaseSchema(schema._def.innerType as ChildType);\n }\n if (\"schema\" in schema._def) {\n return getBaseSchema(schema._def.schema as ChildType);\n }\n\n return schema as ChildType;\n}\n\n/**\n * Get the type name of the lowest level Zod type.\n * This will unpack optionals, refinements, etc.\n */\nexport function getBaseType(schema: z.ZodAny): string {\n const baseSchema = getBaseSchema(schema);\n return baseSchema ? baseSchema._def.typeName : \"\";\n}\n\n/**\n * Search for a \"ZodDefult\" in the Zod stack and return its value.\n */\nexport function getDefaultValueInZodStack(schema: z.ZodAny): any {\n const typedSchema = schema as unknown as z.ZodDefault<\n z.ZodNumber | z.ZodString\n >;\n\n if (typedSchema._def.typeName === \"ZodDefault\") {\n return typedSchema._def.defaultValue();\n }\n\n if (\"innerType\" in typedSchema._def) {\n return getDefaultValueInZodStack(\n typedSchema._def.innerType as unknown as z.ZodAny,\n );\n }\n if (\"schema\" in typedSchema._def) {\n return getDefaultValueInZodStack(\n (typedSchema._def as any).schema as z.ZodAny,\n );\n }\n\n return undefined;\n}\n\n/**\n * Get all default values from a Zod schema.\n */\nexport function getDefaultValues>(\n schema: Schema,\n fieldConfig?: FieldConfig>,\n) {\n if (!schema) return null;\n const { shape } = schema;\n type DefaultValuesType = DefaultValues>>;\n const defaultValues = {} as DefaultValuesType;\n if (!shape) return defaultValues;\n\n for (const key of Object.keys(shape)) {\n const item = shape[key] as z.ZodAny;\n\n if (getBaseType(item) === \"ZodObject\") {\n const defaultItems = getDefaultValues(\n getBaseSchema(item) as unknown as z.ZodObject,\n fieldConfig?.[key] as FieldConfig>,\n );\n\n if (defaultItems !== null) {\n for (const defaultItemKey of Object.keys(defaultItems)) {\n const pathKey = `${key}.${defaultItemKey}` as keyof DefaultValuesType;\n defaultValues[pathKey] = defaultItems[defaultItemKey];\n }\n }\n } else {\n let defaultValue = getDefaultValueInZodStack(item);\n if (\n (defaultValue === null || defaultValue === \"\") &&\n fieldConfig?.[key]?.inputProps\n ) {\n defaultValue = (fieldConfig?.[key]?.inputProps as unknown as any)\n .defaultValue;\n }\n if (defaultValue !== undefined) {\n defaultValues[key as keyof DefaultValuesType] = defaultValue;\n }\n }\n }\n\n return defaultValues;\n}\n\nexport function getObjectFormSchema(\n schema: ZodObjectOrWrapped,\n): z.ZodObject {\n if (schema?._def.typeName === \"ZodEffects\") {\n const typedSchema = schema as z.ZodEffects>;\n return getObjectFormSchema(typedSchema._def.schema);\n }\n return schema as z.ZodObject;\n}\n\n/**\n * Convert a Zod schema to HTML input props to give direct feedback to the user.\n * Once submitted, the schema will be validated completely.\n */\nexport function zodToHtmlInputProps(\n schema:\n | z.ZodNumber\n | z.ZodString\n | z.ZodOptional\n | any,\n): React.InputHTMLAttributes {\n if ([\"ZodOptional\", \"ZodNullable\"].includes(schema._def.typeName)) {\n const typedSchema = schema as z.ZodOptional;\n return {\n ...zodToHtmlInputProps(typedSchema._def.innerType),\n required: false,\n };\n }\n const typedSchema = schema as z.ZodNumber | z.ZodString;\n\n if (!(\"checks\" in typedSchema._def))\n return {\n required: true,\n };\n\n const { checks } = typedSchema._def;\n const inputProps: React.InputHTMLAttributes = {\n required: true,\n };\n const type = getBaseType(schema);\n\n for (const check of checks) {\n if (check.kind === \"min\") {\n if (type === \"ZodString\") {\n inputProps.minLength = check.value;\n } else {\n inputProps.min = check.value;\n }\n }\n if (check.kind === \"max\") {\n if (type === \"ZodString\") {\n inputProps.maxLength = check.value;\n } else {\n inputProps.max = check.value;\n }\n }\n }\n\n return inputProps;\n}\n", + "content": "import React from \"react\";\nimport { DefaultValues } from \"react-hook-form\";\nimport { z } from \"zod\";\nimport { FieldConfig } from \"./types\";\n\n// TODO: This should support recursive ZodEffects but TypeScript doesn't allow circular type definitions.\nexport type ZodObjectOrWrapped =\n | z.ZodObject\n | z.ZodEffects>;\n\n/**\n * Beautify a camelCase string.\n * e.g. \"myString\" -> \"My String\"\n */\nexport function beautifyObjectName(string: string) {\n // if numbers only return the string\n let output = string.replace(/([A-Z])/g, \" $1\");\n output = output.charAt(0).toUpperCase() + output.slice(1);\n return output;\n}\n\n/**\n * Get the lowest level Zod type.\n * This will unpack optionals, refinements, etc.\n */\nexport function getBaseSchema<\n ChildType extends z.ZodAny | z.AnyZodObject = z.ZodAny,\n>(schema: ChildType | z.ZodEffects): ChildType | null {\n if (!schema) return null;\n if (\"innerType\" in schema._def) {\n return getBaseSchema(schema._def.innerType as ChildType);\n }\n if (\"schema\" in schema._def) {\n return getBaseSchema(schema._def.schema as ChildType);\n }\n\n return schema as ChildType;\n}\n\n/**\n * Get the type name of the lowest level Zod type.\n * This will unpack optionals, refinements, etc.\n */\nexport function getBaseType(schema: z.ZodAny): string {\n const baseSchema = getBaseSchema(schema);\n return baseSchema ? baseSchema._def.typeName : \"\";\n}\n\n/**\n * Search for a \"ZodDefult\" in the Zod stack and return its value.\n */\nexport function getDefaultValueInZodStack(schema: z.ZodAny): any {\n const typedSchema = schema as unknown as z.ZodDefault<\n z.ZodNumber | z.ZodString\n >;\n\n if (typedSchema._def.typeName === \"ZodDefault\") {\n return typedSchema._def.defaultValue();\n }\n\n if (\"innerType\" in typedSchema._def) {\n return getDefaultValueInZodStack(\n typedSchema._def.innerType as unknown as z.ZodAny,\n );\n }\n if (\"schema\" in typedSchema._def) {\n return getDefaultValueInZodStack(\n (typedSchema._def as any).schema as z.ZodAny,\n );\n }\n\n return undefined;\n}\n\n/**\n * Get all default values from a Zod schema.\n */\nexport function getDefaultValues>(\n schema: Schema,\n fieldConfig?: FieldConfig>,\n) {\n if (!schema) return null;\n const { shape } = schema;\n type DefaultValuesType = DefaultValues>>;\n const defaultValues = {} as DefaultValuesType;\n if (!shape) return defaultValues;\n\n for (const key of Object.keys(shape)) {\n const item = shape[key] as z.ZodAny;\n\n if (getBaseType(item) === \"ZodObject\") {\n const defaultItems = getDefaultValues(\n getBaseSchema(item) as unknown as z.ZodObject,\n fieldConfig?.[key] as FieldConfig>,\n );\n\n if (defaultItems !== null) {\n for (const defaultItemKey of Object.keys(defaultItems)) {\n const pathKey = `${key}.${defaultItemKey}` as keyof DefaultValuesType;\n defaultValues[pathKey] = defaultItems[defaultItemKey];\n }\n }\n } else {\n let defaultValue = getDefaultValueInZodStack(item);\n if (\n (defaultValue === null || defaultValue === \"\") &&\n fieldConfig?.[key]?.inputProps\n ) {\n defaultValue = (fieldConfig?.[key]?.inputProps as unknown as any)\n .defaultValue;\n }\n if (defaultValue !== undefined) {\n defaultValues[key as keyof DefaultValuesType] = defaultValue;\n }\n }\n }\n\n return defaultValues;\n}\n\nexport function getObjectFormSchema(\n schema: ZodObjectOrWrapped,\n): z.ZodObject {\n if (schema?._def.typeName === \"ZodEffects\") {\n const typedSchema = schema as z.ZodEffects>;\n return getObjectFormSchema(typedSchema._def.schema);\n }\n return schema as z.ZodObject;\n}\n\n/**\n * Convert a Zod schema to HTML input props to give direct feedback to the user.\n * Once submitted, the schema will be validated completely.\n */\nexport function zodToHtmlInputProps(\n schema:\n | z.ZodNumber\n | z.ZodString\n | z.ZodOptional\n | any,\n): React.InputHTMLAttributes {\n if ([\"ZodOptional\", \"ZodNullable\"].includes(schema._def.typeName)) {\n const typedSchema = schema as z.ZodOptional;\n return {\n ...zodToHtmlInputProps(typedSchema._def.innerType),\n required: false,\n };\n }\n const typedSchema = schema as z.ZodNumber | z.ZodString;\n\n if (!(\"checks\" in typedSchema._def))\n return {\n required: true,\n };\n\n const { checks } = typedSchema._def;\n const inputProps: React.InputHTMLAttributes = {\n required: true,\n };\n const type = getBaseType(schema);\n\n for (const check of checks) {\n if (check.kind === \"min\") {\n if (type === \"ZodString\") {\n inputProps.minLength = check.value;\n } else {\n inputProps.min = check.value;\n }\n }\n if (check.kind === \"max\") {\n if (type === \"ZodString\") {\n inputProps.maxLength = check.value;\n } else {\n inputProps.max = check.value;\n }\n }\n }\n\n return inputProps;\n}\n\n/**\n * Sort the fields by order.\n * If no order is set, the field will be sorted based on the order in the schema.\n */\n\nexport function sortFieldsByOrder>(\n fieldConfig: FieldConfig> | undefined,\n keys: string[]\n) {\n const sortedFields = keys.sort((a, b) => {\n const fieldA: number = (fieldConfig?.[a]?.order as number) ?? 0;\n const fieldB = (fieldConfig?.[b]?.order as number) ?? 0;\n return fieldA - fieldB;\n });\n\n return sortedFields;\n}", "type": "registry:ui" }, { "path": "types.ts", "target": "components/ui/auto-form/types.ts", - "content": "import { ControllerRenderProps, FieldValues } from \"react-hook-form\";\nimport * as z from \"zod\";\nimport { INPUT_COMPONENTS } from \"./config\";\n\nexport type FieldConfigItem = {\n description?: React.ReactNode;\n inputProps?: React.InputHTMLAttributes &\n React.TextareaHTMLAttributes &\n {\n showLabel?: boolean;\n };\n label?: string;\n fieldType?:\n | keyof typeof INPUT_COMPONENTS\n | React.FC;\n\n renderParent?: (props: {\n children: React.ReactNode;\n }) => React.ReactElement | null;\n};\n\nexport type FieldConfig>> = {\n // If SchemaType.key is an object, create a nested FieldConfig, otherwise FieldConfigItem\n [Key in keyof SchemaType]?: SchemaType[Key] extends object\n ? FieldConfig>\n : FieldConfigItem;\n};\n\nexport enum DependencyType {\n DISABLES,\n REQUIRES,\n HIDES,\n SETS_OPTIONS,\n}\n\ntype BaseDependency>> = {\n sourceField: keyof SchemaType;\n type: DependencyType;\n targetField: keyof SchemaType;\n when: (sourceFieldValue: any, targetFieldValue: any) => boolean;\n};\n\nexport type ValueDependency>> =\n BaseDependency & {\n type:\n | DependencyType.DISABLES\n | DependencyType.REQUIRES\n | DependencyType.HIDES;\n };\n\nexport type EnumValues = readonly [string, ...string[]];\n\nexport type OptionsDependency<\n SchemaType extends z.infer>,\n> = BaseDependency & {\n type: DependencyType.SETS_OPTIONS;\n\n // Partial array of values from sourceField that will trigger the dependency\n options: EnumValues;\n};\n\nexport type Dependency>> =\n | ValueDependency\n | OptionsDependency;\n\n/**\n * A FormInput component can handle a specific Zod type (e.g. \"ZodBoolean\")\n */\nexport type AutoFormInputComponentProps = {\n zodInputProps: React.InputHTMLAttributes;\n field: ControllerRenderProps;\n fieldConfigItem: FieldConfigItem;\n label: string;\n isRequired: boolean;\n fieldProps: any;\n zodItem: z.ZodAny;\n className?: string;\n};\n", + "content": "import {\n ControllerRenderProps,\n FieldValues,\n UseFormSetError,\n} from \"react-hook-form\";\nimport * as z from \"zod\";\nimport { INPUT_COMPONENTS } from \"./config\";\n\nexport type FieldConfigItem = {\n description?: React.ReactNode;\n inputProps?: React.InputHTMLAttributes &\n React.TextareaHTMLAttributes & {\n showLabel?: boolean;\n };\n label?: string;\n fieldType?:\n | keyof typeof INPUT_COMPONENTS\n | React.FC;\n\n renderParent?: (props: {\n children: React.ReactNode;\n }) => React.ReactElement | null;\n\n order?: number;\n};\n\nexport type FieldConfig>> = {\n // If SchemaType.key is an object, create a nested FieldConfig, otherwise FieldConfigItem\n [Key in keyof SchemaType]?: SchemaType[Key] extends object\n ? FieldConfig>\n : FieldConfigItem;\n};\n\nexport enum DependencyType {\n DISABLES,\n REQUIRES,\n HIDES,\n SETS_OPTIONS,\n}\n\ntype BaseDependency>> = {\n sourceField: keyof SchemaType;\n type: DependencyType;\n targetField: keyof SchemaType;\n when: (sourceFieldValue: any, targetFieldValue: any) => boolean;\n};\n\nexport type ValueDependency>> =\n BaseDependency & {\n type:\n | DependencyType.DISABLES\n | DependencyType.REQUIRES\n | DependencyType.HIDES;\n };\n\nexport type EnumValues = readonly [string, ...string[]];\n\nexport type OptionsDependency<\n SchemaType extends z.infer>,\n> = BaseDependency & {\n type: DependencyType.SETS_OPTIONS;\n\n // Partial array of values from sourceField that will trigger the dependency\n options: EnumValues;\n};\n\nexport type Dependency>> =\n | ValueDependency\n | OptionsDependency;\n\n/**\n * A FormInput component can handle a specific Zod type (e.g. \"ZodBoolean\")\n */\nexport type AutoFormInputComponentProps = {\n zodInputProps: React.InputHTMLAttributes;\n field: ControllerRenderProps;\n fieldConfigItem: FieldConfigItem;\n label: string;\n isRequired: boolean;\n fieldProps: any;\n zodItem: z.ZodAny;\n className?: string;\n};\n\nexport type SubmitOptions>> = {\n setError: UseFormSetError;\n};\n", "type": "registry:ui" }, { "path": "index.tsx", "target": "components/ui/auto-form/index.tsx", - "content": "\"use client\";\nimport { Form } from \"@/components/ui/form\";\nimport React from \"react\";\nimport { DefaultValues, FormState, useForm } from \"react-hook-form\";\nimport { z } from \"zod\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\n\nimport AutoFormObject from \"./fields/object\";\nimport { Dependency, FieldConfig } from \"./types\";\nimport {\n ZodObjectOrWrapped,\n getDefaultValues,\n getObjectFormSchema,\n} from \"./utils\";\n\nexport function AutoFormSubmit({\n children,\n className,\n disabled,\n}: {\n children?: React.ReactNode;\n className?: string;\n disabled?: boolean;\n}) {\n return (\n \n );\n}\n\nfunction AutoForm({\n formSchema,\n values: valuesProp,\n onValuesChange: onValuesChangeProp,\n onParsedValuesChange,\n onSubmit: onSubmitProp,\n fieldConfig,\n children,\n className,\n dependencies,\n}: {\n formSchema: SchemaType;\n values?: Partial>;\n onValuesChange?: (values: Partial>) => void;\n onParsedValuesChange?: (values: Partial>) => void;\n onSubmit?: (values: z.infer) => void;\n fieldConfig?: FieldConfig>;\n children?:\n | React.ReactNode\n | ((formState: FormState>) => React.ReactNode);\n className?: string;\n dependencies?: Dependency>[];\n}) {\n const objectFormSchema = getObjectFormSchema(formSchema);\n const defaultValues: DefaultValues> | null =\n getDefaultValues(objectFormSchema, fieldConfig);\n\n const form = useForm>({\n resolver: zodResolver(formSchema),\n defaultValues: defaultValues ?? undefined,\n values: valuesProp,\n });\n\n function onSubmit(values: z.infer) {\n const parsedValues = formSchema.safeParse(values);\n if (parsedValues.success) {\n onSubmitProp?.(parsedValues.data);\n }\n }\n\n React.useEffect(() => {\n const subscription = form.watch((values) => {\n onValuesChangeProp?.(values);\n const parsedValues = formSchema.safeParse(values);\n if (parsedValues.success) {\n onParsedValuesChange?.(parsedValues.data);\n }\n });\n\n return () => subscription.unsubscribe();\n }, [form, formSchema, onValuesChangeProp, onParsedValuesChange]);\n\n const renderChildren =\n typeof children === \"function\"\n ? children(form.formState as FormState>)\n : children;\n\n return (\n
\n
\n {\n form.handleSubmit(onSubmit)(e);\n }}\n className={cn(\"space-y-5\", className)}\n >\n \n\n {renderChildren}\n \n \n
\n );\n}\n\nexport default AutoForm;\n", + "content": "\"use client\";\nimport { Form } from \"@/components/ui/form\";\nimport React from \"react\";\nimport { DefaultValues, FormState, useForm } from \"react-hook-form\";\nimport { z } from \"zod\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\n\nimport AutoFormObject from \"./fields/object\";\nimport { Dependency, FieldConfig, SubmitOptions } from \"./types\";\nimport {\n ZodObjectOrWrapped,\n getDefaultValues,\n getObjectFormSchema,\n} from \"./utils\";\n\nexport function AutoFormSubmit({\n children,\n className,\n disabled,\n}: {\n children?: React.ReactNode;\n className?: string;\n disabled?: boolean;\n}) {\n return (\n \n );\n}\n\nfunction AutoForm({\n formSchema,\n values: valuesProp,\n onValuesChange: onValuesChangeProp,\n onParsedValuesChange,\n onSubmit: onSubmitProp,\n fieldConfig,\n children,\n className,\n dependencies,\n}: {\n formSchema: SchemaType;\n values?: Partial>;\n onValuesChange?: (values: Partial>) => void;\n onParsedValuesChange?: (values: Partial>) => void;\n onSubmit?: (\n values: z.infer,\n options: SubmitOptions>\n ) => void;\n fieldConfig?: FieldConfig>;\n children?:\n | React.ReactNode\n | ((formState: FormState>) => React.ReactNode);\n className?: string;\n dependencies?: Dependency>[];\n}) {\n const objectFormSchema = getObjectFormSchema(formSchema);\n const defaultValues: DefaultValues> | null =\n getDefaultValues(objectFormSchema, fieldConfig);\n\n const form = useForm>({\n resolver: zodResolver(formSchema),\n defaultValues: defaultValues ?? undefined,\n values: valuesProp,\n });\n\n function onSubmit(values: z.infer) {\n const parsedValues = formSchema.safeParse(values);\n if (parsedValues.success) {\n onSubmitProp?.(parsedValues.data, {\n setError: form.setError,\n });\n }\n }\n\n React.useEffect(() => {\n const subscription = form.watch((values) => {\n onValuesChangeProp?.(values);\n const parsedValues = formSchema.safeParse(values);\n if (parsedValues.success) {\n onParsedValuesChange?.(parsedValues.data);\n }\n });\n\n return () => subscription.unsubscribe();\n }, [form, formSchema, onValuesChangeProp, onParsedValuesChange]);\n\n const renderChildren =\n typeof children === \"function\"\n ? children(form.formState as FormState>)\n : children;\n\n return (\n
\n
\n {\n form.handleSubmit(onSubmit)(e);\n }}\n className={cn(\"space-y-5\", className)}\n >\n \n\n {renderChildren}\n \n \n
\n );\n}\n\nexport default AutoForm;\n", "type": "registry:ui" }, { @@ -85,7 +85,7 @@ { "path": "fields/object.tsx", "target": "components/ui/auto-form/fields/object.tsx", - "content": "import {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from \"@/components/ui/accordion\";\nimport { FormField } from \"@/components/ui/form\";\nimport { useForm, useFormContext } from \"react-hook-form\";\nimport * as z from \"zod\";\nimport { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from \"../config\";\nimport { Dependency, FieldConfig, FieldConfigItem } from \"../types\";\nimport {\n beautifyObjectName,\n getBaseSchema,\n getBaseType,\n zodToHtmlInputProps,\n} from \"../utils\";\nimport AutoFormArray from \"./array\";\nimport resolveDependencies from \"../dependencies\";\n\nfunction DefaultParent({ children }: { children: React.ReactNode }) {\n return <>{children};\n}\n\nexport default function AutoFormObject<\n SchemaType extends z.ZodObject,\n>({\n schema,\n form,\n fieldConfig,\n path = [],\n dependencies = [],\n}: {\n schema: SchemaType | z.ZodEffects;\n form: ReturnType;\n fieldConfig?: FieldConfig>;\n path?: string[];\n dependencies?: Dependency>[];\n}) {\n const { watch } = useFormContext(); // Use useFormContext to access the watch function\n\n if (!schema) {\n return null;\n }\n const { shape } = getBaseSchema(schema) || {};\n\n if (!shape) {\n return null;\n }\n\n const handleIfZodNumber = (item: z.ZodAny) => {\n const isZodNumber = (item as any)._def.typeName === \"ZodNumber\";\n const isInnerZodNumber =\n (item._def as any).innerType?._def?.typeName === \"ZodNumber\";\n\n if (isZodNumber) {\n (item as any)._def.coerce = true;\n } else if (isInnerZodNumber) {\n (item._def as any).innerType._def.coerce = true;\n }\n\n return item;\n };\n\n return (\n \n {Object.keys(shape).map((name) => {\n let item = shape[name] as z.ZodAny;\n item = handleIfZodNumber(item) as z.ZodAny;\n const zodBaseType = getBaseType(item);\n const itemName = item._def.description ?? beautifyObjectName(name);\n const key = [...path, name].join(\".\");\n\n const {\n isHidden,\n isDisabled,\n isRequired: isRequiredByDependency,\n overrideOptions,\n } = resolveDependencies(dependencies, name, watch);\n if (isHidden) {\n return null;\n }\n\n if (zodBaseType === \"ZodObject\") {\n return (\n \n {itemName}\n \n }\n form={form}\n fieldConfig={\n (fieldConfig?.[name] ?? {}) as FieldConfig<\n z.infer\n >\n }\n path={[...path, name]}\n />\n \n \n );\n }\n if (zodBaseType === \"ZodArray\") {\n return (\n }\n form={form}\n fieldConfig={fieldConfig?.[name] ?? {}}\n path={[...path, name]}\n />\n );\n }\n\n const fieldConfigItem: FieldConfigItem = fieldConfig?.[name] ?? {};\n const zodInputProps = zodToHtmlInputProps(item);\n const isRequired =\n isRequiredByDependency ||\n zodInputProps.required ||\n fieldConfigItem.inputProps?.required ||\n false;\n\n if (overrideOptions) {\n item = z.enum(overrideOptions) as unknown as z.ZodAny;\n }\n\n return (\n {\n const inputType =\n fieldConfigItem.fieldType ??\n DEFAULT_ZOD_HANDLERS[zodBaseType] ??\n \"fallback\";\n\n const InputComponent =\n typeof inputType === \"function\"\n ? inputType\n : INPUT_COMPONENTS[inputType];\n\n const ParentElement =\n fieldConfigItem.renderParent ?? DefaultParent;\n\n const defaultValue = fieldConfigItem.inputProps?.defaultValue;\n const value = field.value ?? defaultValue ?? \"\";\n\n const fieldProps = {\n ...zodToHtmlInputProps(item),\n ...field,\n ...fieldConfigItem.inputProps,\n disabled: fieldConfigItem.inputProps?.disabled || isDisabled,\n ref: undefined,\n value: value,\n };\n\n if (InputComponent === undefined) {\n return <>;\n }\n\n return (\n \n \n \n );\n }}\n />\n );\n })}\n \n );\n}\n", + "content": "import {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from \"@/components/ui/accordion\";\nimport { FormField } from \"@/components/ui/form\";\nimport { useForm, useFormContext } from \"react-hook-form\";\nimport * as z from \"zod\";\nimport { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from \"../config\";\nimport { Dependency, FieldConfig, FieldConfigItem } from \"../types\";\nimport {\n beautifyObjectName,\n getBaseSchema,\n getBaseType,\n sortFieldsByOrder,\n zodToHtmlInputProps,\n} from \"../utils\";\nimport AutoFormArray from \"./array\";\nimport resolveDependencies from \"../dependencies\";\n\nfunction DefaultParent({ children }: { children: React.ReactNode }) {\n return <>{children};\n}\n\nexport default function AutoFormObject<\n SchemaType extends z.ZodObject,\n>({\n schema,\n form,\n fieldConfig,\n path = [],\n dependencies = [],\n}: {\n schema: SchemaType | z.ZodEffects;\n form: ReturnType;\n fieldConfig?: FieldConfig>;\n path?: string[];\n dependencies?: Dependency>[];\n}) {\n const { watch } = useFormContext(); // Use useFormContext to access the watch function\n\n if (!schema) {\n return null;\n }\n const { shape } = getBaseSchema(schema) || {};\n\n if (!shape) {\n return null;\n }\n\n const handleIfZodNumber = (item: z.ZodAny) => {\n const isZodNumber = (item as any)._def.typeName === \"ZodNumber\";\n const isInnerZodNumber =\n (item._def as any).innerType?._def?.typeName === \"ZodNumber\";\n\n if (isZodNumber) {\n (item as any)._def.coerce = true;\n } else if (isInnerZodNumber) {\n (item._def as any).innerType._def.coerce = true;\n }\n\n return item;\n };\n\n const sortedFieldKeys = sortFieldsByOrder(fieldConfig, Object.keys(shape));\n\n return (\n \n {sortedFieldKeys.map((name) => {\n let item = shape[name] as z.ZodAny;\n item = handleIfZodNumber(item) as z.ZodAny;\n const zodBaseType = getBaseType(item);\n const itemName = item._def.description ?? beautifyObjectName(name);\n const key = [...path, name].join(\".\");\n\n const {\n isHidden,\n isDisabled,\n isRequired: isRequiredByDependency,\n overrideOptions,\n } = resolveDependencies(dependencies, name, watch);\n if (isHidden) {\n return null;\n }\n\n if (zodBaseType === \"ZodObject\") {\n return (\n \n {itemName}\n \n }\n form={form}\n fieldConfig={\n (fieldConfig?.[name] ?? {}) as FieldConfig<\n z.infer\n >\n }\n path={[...path, name]}\n />\n \n \n );\n }\n if (zodBaseType === \"ZodArray\") {\n return (\n }\n form={form}\n fieldConfig={fieldConfig?.[name] ?? {}}\n path={[...path, name]}\n />\n );\n }\n\n const fieldConfigItem: FieldConfigItem = fieldConfig?.[name] ?? {};\n const zodInputProps = zodToHtmlInputProps(item);\n const isRequired =\n isRequiredByDependency ||\n zodInputProps.required ||\n fieldConfigItem.inputProps?.required ||\n false;\n\n if (overrideOptions) {\n item = z.enum(overrideOptions) as unknown as z.ZodAny;\n }\n\n return (\n {\n const inputType =\n fieldConfigItem.fieldType ??\n DEFAULT_ZOD_HANDLERS[zodBaseType] ??\n \"fallback\";\n\n const InputComponent =\n typeof inputType === \"function\"\n ? inputType\n : INPUT_COMPONENTS[inputType];\n\n const ParentElement =\n fieldConfigItem.renderParent ?? DefaultParent;\n\n const defaultValue = fieldConfigItem.inputProps?.defaultValue;\n const value = field.value ?? defaultValue ?? \"\";\n\n const fieldProps = {\n ...zodToHtmlInputProps(item),\n ...field,\n ...fieldConfigItem.inputProps,\n disabled: fieldConfigItem.inputProps?.disabled || isDisabled,\n ref: undefined,\n value: value,\n };\n\n if (InputComponent === undefined) {\n return <>;\n }\n\n return (\n \n \n \n );\n }}\n />\n );\n })}\n \n );\n}\n", "type": "registry:ui" }, {