This commit includes the creation of new react components and fields for auto-form functionality. The components include AutoFormLabel, AutoFormTooltip, and specific field components for data types such as AutoFormObject, AutoFormArray, AutoFormDate, AutoFormCheckbox, and others. Added tests to ensure the correct rendering of fields, labels generation, among other behaviors.
184 lines
5.4 KiB
TypeScript
184 lines
5.4 KiB
TypeScript
import {
|
|
Accordion,
|
|
AccordionContent,
|
|
AccordionItem,
|
|
AccordionTrigger,
|
|
} from "@/components/ui/accordion";
|
|
import { FormField } from "@/components/ui/form";
|
|
import { useForm, useFormContext } from "react-hook-form";
|
|
import * as z from "zod";
|
|
import { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from "../config";
|
|
import { Dependency, FieldConfig, FieldConfigItem } from "../types";
|
|
import {
|
|
beautifyObjectName,
|
|
getBaseSchema,
|
|
getBaseType,
|
|
zodToHtmlInputProps,
|
|
} from "../utils";
|
|
import AutoFormArray from "./array";
|
|
import resolveDependencies from "../dependencies";
|
|
|
|
function DefaultParent({ children }: { children: React.ReactNode }) {
|
|
return <>{children}</>;
|
|
}
|
|
|
|
export default function AutoFormObject<
|
|
SchemaType extends z.ZodObject<any, any>,
|
|
>({
|
|
schema,
|
|
form,
|
|
fieldConfig,
|
|
path = [],
|
|
dependencies = [],
|
|
}: {
|
|
schema: SchemaType | z.ZodEffects<SchemaType>;
|
|
form: ReturnType<typeof useForm>;
|
|
fieldConfig?: FieldConfig<z.infer<SchemaType>>;
|
|
path?: string[];
|
|
dependencies?: Dependency<z.infer<SchemaType>>[];
|
|
}) {
|
|
const { watch } = useFormContext(); // Use useFormContext to access the watch function
|
|
|
|
if (!schema) {
|
|
return null;
|
|
}
|
|
const { shape } = getBaseSchema<SchemaType>(schema) || {};
|
|
|
|
if (!shape) {
|
|
return null;
|
|
}
|
|
|
|
const handleIfZodNumber = (item: z.ZodAny) => {
|
|
const isZodNumber = (item as any)._def.typeName === "ZodNumber";
|
|
const isInnerZodNumber =
|
|
(item._def as any).innerType?._def?.typeName === "ZodNumber";
|
|
|
|
if (isZodNumber) {
|
|
(item as any)._def.coerce = true;
|
|
} else if (isInnerZodNumber) {
|
|
(item._def as any).innerType._def.coerce = true;
|
|
}
|
|
|
|
return item;
|
|
};
|
|
|
|
return (
|
|
<Accordion type="multiple" className="space-y-5 border-none">
|
|
{Object.keys(shape).map((name) => {
|
|
let item = shape[name] as z.ZodAny;
|
|
item = handleIfZodNumber(item) as z.ZodAny;
|
|
const zodBaseType = getBaseType(item);
|
|
const itemName = item._def.description ?? beautifyObjectName(name);
|
|
const key = [...path, name].join(".");
|
|
|
|
const {
|
|
isHidden,
|
|
isDisabled,
|
|
isRequired: isRequiredByDependency,
|
|
overrideOptions,
|
|
} = resolveDependencies(dependencies, name, watch);
|
|
if (isHidden) {
|
|
return null;
|
|
}
|
|
|
|
if (zodBaseType === "ZodObject") {
|
|
return (
|
|
<AccordionItem value={name} key={key} className="border-none">
|
|
<AccordionTrigger>{itemName}</AccordionTrigger>
|
|
<AccordionContent className="p-2">
|
|
<AutoFormObject
|
|
schema={item as unknown as z.ZodObject<any, any>}
|
|
form={form}
|
|
fieldConfig={
|
|
(fieldConfig?.[name] ?? {}) as FieldConfig<
|
|
z.infer<typeof item>
|
|
>
|
|
}
|
|
path={[...path, name]}
|
|
/>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
);
|
|
}
|
|
if (zodBaseType === "ZodArray") {
|
|
return (
|
|
<AutoFormArray
|
|
key={key}
|
|
name={name}
|
|
item={item as unknown as z.ZodArray<any>}
|
|
form={form}
|
|
fieldConfig={fieldConfig?.[name] ?? {}}
|
|
path={[...path, name]}
|
|
/>
|
|
);
|
|
}
|
|
|
|
const fieldConfigItem: FieldConfigItem = fieldConfig?.[name] ?? {};
|
|
const zodInputProps = zodToHtmlInputProps(item);
|
|
const isRequired =
|
|
isRequiredByDependency ||
|
|
zodInputProps.required ||
|
|
fieldConfigItem.inputProps?.required ||
|
|
false;
|
|
|
|
if (overrideOptions) {
|
|
item = z.enum(overrideOptions) as unknown as z.ZodAny;
|
|
}
|
|
|
|
return (
|
|
<FormField
|
|
control={form.control}
|
|
name={key}
|
|
key={key}
|
|
render={({ field }) => {
|
|
const inputType =
|
|
fieldConfigItem.fieldType ??
|
|
DEFAULT_ZOD_HANDLERS[zodBaseType] ??
|
|
"fallback";
|
|
|
|
const InputComponent =
|
|
typeof inputType === "function"
|
|
? inputType
|
|
: INPUT_COMPONENTS[inputType];
|
|
|
|
const ParentElement =
|
|
fieldConfigItem.renderParent ?? DefaultParent;
|
|
|
|
const defaultValue = fieldConfigItem.inputProps?.defaultValue;
|
|
const value = field.value ?? defaultValue ?? "";
|
|
|
|
const fieldProps = {
|
|
...zodToHtmlInputProps(item),
|
|
...field,
|
|
...fieldConfigItem.inputProps,
|
|
disabled: fieldConfigItem.inputProps?.disabled || isDisabled,
|
|
ref: undefined,
|
|
value: value,
|
|
};
|
|
|
|
if (InputComponent === undefined) {
|
|
return <></>;
|
|
}
|
|
|
|
return (
|
|
<ParentElement key={`${key}.parent`}>
|
|
<InputComponent
|
|
zodInputProps={zodInputProps}
|
|
field={field}
|
|
fieldConfigItem={fieldConfigItem}
|
|
label={itemName}
|
|
isRequired={isRequired}
|
|
zodItem={item}
|
|
fieldProps={fieldProps}
|
|
className={fieldProps.className}
|
|
/>
|
|
</ParentElement>
|
|
);
|
|
}}
|
|
/>
|
|
);
|
|
})}
|
|
</Accordion>
|
|
);
|
|
}
|