@shadcn/ui 的 <AutoForm />
AutoForm 是一个 React 组件,可以根据 zod schema 自动创建 @shadcn/ui 表单。
在线演示可以在 https://vantezzen.github.io/auto-form/ 查看。
何时使用 AutoForm?
AutoForm 主要用于为现有 zod schema 的内部和低优先级表单提供即插即用的表单构建器。例如,如果你已经为你的 API 创建了 zod schema,并想创建一个简单的管理面板来编辑用户资料,只需将 schema 传递给 AutoForm 即可。
AutoForm 尽可能原生地使用 @shadcn/ui 组件,只进行最少的类覆盖。这样,如果你在项目中自定义了 @shadcn/ui 组件,AutoForm 不会干扰你的自定义设置。
由于表单几乎总是变得越来越复杂,AutoForm 提供了自定义表单渲染方式的选项(例如使用 fieldConfig
选项和依赖支持),并提供了进一步自定义表单的方法(例如渲染自定义父组件和添加自定义字段类型)。
然而,AutoForm 并不旨在成为一个功能齐全的表单构建器。它不打算支持 zod schema 中的每个边缘情况或允许构建复杂的多页表单。如果你需要更多自定义,可以在你的项目中自定义 AutoForm 的渲染器,或使用更强大的表单构建器,如 Formik - 尽管这些需要更专门的配置,而不是简单地支持你的 zod schema。关于如何扩展 AutoForm 以实现更强大的基于 YAML 的多页表单,请参见 AutoForm YAML。
安装
该组件依赖于 shadcn/ui 的以下组件:
- accordion
- button
- calendar
- card
- checkbox
- form
- input
- label
- popover
- radio-group
- select
- separator
- switch
- textarea
- tooltip
- toggle
你可以一次性安装所有组件:
npx shadcn-ui@latest add accordion button calendar card checkbox form input label popover radio-group select separator switch textarea tooltip toggle
要安装组件本身,将 src/components/ui
中的 auto-form
文件夹和 date-picker.tsx
复制到你项目的 ui 文件夹中。
你可以删除 auto-form/tests
中的测试文件夹。
字段类型
目前,以下字段类型开箱即用:
- boolean(复选框、开关)
- date(日期选择器)
- enum(选择、单选组)
- number(输入框)
- string(输入框、文本域)
你可以通过在 auto-form/config.tsx
中的 INPUT_COMPONENTS
对象添加其他字段类型来支持它们。
使用方法
基本用法:
"use client";
import AutoForm, { AutoFormSubmit } from "./components/ui/auto-form";
import * as z from "zod";
// 使用 zod 定义你的表单 schema
const formSchema = z.object({
username: z
.string({
required_error: "用户名是必填项。",
})
// 你可以像平常一样使用 zod 的内置验证
.min(2, {
message: "用户名至少需要 2 个字符。",
}),
password: z
.string({
required_error: "密码是必填项。",
})
// 使用 "describe" 方法设置标签
// 如果没有设置标签,将使用字段名并取消驼峰命名
.describe("你的安全密码")
.min(8, {
message: "密码至少需要 8 个字符。",
}),
favouriteNumber: z.coerce // 使用数字和日期时,必须使用 coerce
.number({
invalid_type_error: "喜爱的数字必须是一个数字。",
})
.min(1, {
message: "喜爱的数字必须至少为 1。",
})
.max(10, {
message: "喜爱的数字最多为 10。",
})
.default(5) // 你可以设置默认值
.optional(),
acceptTerms: z
.boolean()
.describe("接受条款和条件。")
.refine((value) => value, {
message: "你必须接受条款和条件。",
path: ["acceptTerms"],
}),
// Date 将显示一个日期选择器
birthday: z.coerce.date().optional(),
sendMeMails: z.boolean().optional(),
// Enum 将显示一个选择框
color: z.enum(["red", "green", "blue"]),
// 创建子对象以创建手风琴部分
address: z.object({
street: z.string(),
city: z.string(),
zip: z.string(),
}),
});
function App() {
return (
<AutoForm
// 将 schema 传递给表单
formSchema={formSchema}
// 你可以为每个字段添加额外的配置
// 以自定义 UI
fieldConfig={{
password: {
// 使用 "inputProps" 将 props 传递给输入组件
// 你可以使用组件接受的任何 props
inputProps: {
type: "password",
placeholder: "••••••••",
},
},
favouriteNumber: {
// 设置 "description",将显示在字段下方
description: "你最喜欢的 1 到 10 之间的数字。",
},
acceptTerms: {
inputProps: {
required: true,
},
// 你可以在描述中使用 JSX
description: (
<>
我同意{" "}
<a
href="#"
className="text-primary underline"
onClick={(e) => {
e.preventDefault();
alert("点击了条款和条件。");
}}
>
条款和条件
</a>
。
</>
),
},
birthday: {
description: "我们需要你的生日来给你送礼物。",
},
sendMeMails: {
// 布尔值默认使用复选框,你可以改用开关
fieldType: "switch",
},
}}
// 可选地,定义字段之间的依赖关系
dependencies={[
{
// 当 "sendMeMails" 未被选中时隐藏 "color",因为我们只需要在发送邮件时知道颜色
sourceField: "sendMeMails",
type: DependencyType.HIDES,
targetField: "color",
when: (sendMeMails) => !sendMeMails,
},
]}
>
{/*
传入 AutoFormSubmit 或 type="submit" 的按钮。
或者,你可以不传递提交按钮
以创建自动保存表单等。
*/}
<AutoFormSubmit>立即发送</AutoFormSubmit>
{/*
传递给表单的所有子元素将在表单下方渲染。
*/}
<p className="text-gray-500 text-sm">
提交此表单即表示你同意我们的{" "}
<a href="#" className="text-primary underline">
条款和条件
</a>
。
</p>
</AutoForm>
);
}
Next.js 和 RSC
由于 zod schema 和值需要序列化到你的事件监听器,AutoForm 只能在客户端 React 组件中使用。如果你想在 Next.js 应用中使用它,只需在你的组件中标记 "use client":
// MyPage.tsx
export default function MyPage() {
return (
<div>
<MyForm />
</div>
);
}
// MyForm.tsx "use client"; import AutoForm from "./components/ui/auto-form"; export default function MyForm() { return <AutoForm onSubmit={...} ... />; }
### Zod 配置
#### 验证
你的表单 schema 可以使用 zod 的任何验证方法,包括 refine。
AutoForm 能够自动将一些 zod 的验证元素转换为 HTML 属性。例如,如果你使用 `zod.string().min(8)`,输入框将自动具有 `minlength="8"` 属性。
HTML 不支持的验证方法将在表单提交时自动检查。
#### 描述
你可以使用 `describe` 方法为每个字段设置标签和描述。如果没有设置标签,将使用字段名并去掉驼峰命名。
```tsx
const formSchema = z.object({
username: z.string().describe("你的用户名"),
someValue: z.string(), // 将显示为 "Some Value"
});
强制转换
使用数字和日期时,你应该使用 coerce。这是因为输入元素可能返回应自动转换的字符串。
const formSchema = z.object({
favouriteNumber: z.coerce.number(),
birthday: z.coerce.date(),
});
可选字段
默认情况下,所有字段都是必填的。你可以使用 optional
方法使字段成为可选的。
const formSchema = z.object({
username: z.string().optional(),
});
默认值
你可以使用 default
方法为字段设置默认值。
const formSchema = z.object({
favouriteNumber: z.number().default(5),
});
如果你想为日期设置默认值,请先使用 new Date(val)
将其转换为 Date。
子对象
你可以嵌套对象来创建手风琴式部分。
const formSchema = z.object({
address: z.object({
street: z.string(),
city: z.string(),
zip: z.string(),
// 你可以根据需要嵌套对象
nested: z.object({
foo: z.string(),
bar: z.string(),
nested: z.object({
foo: z.string(),
bar: z.string(),
}),
}),
}),
});
与普通对象一样,你可以使用 describe
方法为该部分设置标签和描述:
const formSchema = z.object({
address: z
.object({
street: z.string(),
city: z.string(),
zip: z.string(),
})
.describe("你的地址"),
});
选择/枚举
AutoForm 支持 enum
和 nativeEnum
来创建选择字段。
const formSchema = z.object({
color: z.enum(["red", "green", "blue"]),
});
enum BreadTypes {
// 对于原生枚举,你也可以定义一个带后缀的枚举来设置自定义标签
White = "白面包",
Brown = "全麦面包",
Wholegrain = "全谷物面包",
Other,
}
// 请注意,zod 将验证并返回枚举标签,而不是枚举值!
const formSchema = z.object({
bread: z.nativeEnum(BreadTypes),
});
数组
AutoForm 支持对象数组。由于从字符串/数字等数组中推断字段标签等信息比较困难,因此只支持对象数组。
const formSchema = z.object({
guestListName: z.string(),
invitedGuests: z
.array(
// 定义每个项目的字段
z.object({
name: z.string(),
age: z.coerce.number(),
}),
)
// 可选设置自定义标签 - 否则将从字段名推断
.describe("受邀参加派对的客人"),
});
数组不支持作为表单 schema 的根元素。
你也可以使用 .default() 设置数组的默认值,但请确保数组元素与 schema 结构相同。
const formSchema = z.object({
guestListName: z.string(),
invitedGuests: z
.array(
// 定义每个项目的字段
z.object({
name: z.string(),
age: z.coerce.number(),
}),
)
.describe("受邀参加派对的客人")
.default([
{
name: "John",
age: 24,
},
{
name: "Jane",
age: 20,
},
]),
});
字段配置
由于 zod 不允许向 schema 添加其他属性,你可以使用 fieldConfig
属性为每个字段的 UI 添加额外配置。
<AutoForm
fieldConfig={{
// 在此处为每个字段添加配置 - 不要添加字段名以保留所有默认值
username: {
// 配置在这里
},
}}
/>
输入属性
你可以使用 inputProps
属性将属性传递给输入组件。你可以使用 HTML 组件接受的任何属性。
<AutoForm
fieldConfig={{
username: {
inputProps: {
type: "text",
placeholder: "用户名",
},
},
}}
/>
// 这将被渲染为:
<input type="text" placeholder="用户名" /* ... */ />
通过在 inputProps
中使用 showLabel
属性可以禁用输入的标签。
<AutoForm
fieldConfig={{
username: {
inputProps: {
type: "text",
placeholder: "用户名",
showLabel: false,
},
},
}}
/>
字段类型
默认情况下,AutoForm 将使用 Zod 类型来确定使用哪个输入组件。你可以使用 fieldType
属性来覆盖此行为。
<AutoForm
fieldConfig={{
sendMeMails: {
// 布尔值默认使用复选框,这里改用开关
fieldType: "switch",
},
}}
/>
支持的字段类型完整列表已经被定义。当前支持的类型有:
- "checkbox"(布尔值默认)
- "switch"
- "date"(日期默认)
- "select"(枚举默认)
- "radio"
- "textarea"
- "fallback"(其他所有类型默认,简单输入字段)
或者,你可以将 React 组件传递给 fieldType
属性以使用自定义组件。
<AutoForm
fieldConfig={{
sendMeMails: {
fieldType: ({
label,
isRequired,
field,
fieldConfigItem,
fieldProps,
}: AutoFormInputComponentProps) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
{...fieldProps}
/>
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>
{label}
{isRequired && <span className="text-destructive"> *</span>}
</FormLabel>
{fieldConfigItem.description && (
<FormDescription>{fieldConfigItem.description}</FormDescription>
)}
</div>
</FormItem>
),
},
}}
/>
描述
你可以使用 description
属性在字段下方添加描述。
<AutoForm
fieldConfig={{
username: {
description:
"输入一个唯一的用户名。这将显示给其他用户。",
},
}}
/>
你可以在描述中使用 JSX。
自定义父组件
你可以使用 renderParent
属性自定义输入的父元素,以添加装饰等。
默认情况下,这是一个 React 片段。
<AutoForm
fieldConfig={{
username: {
renderParent: ({ children }) => (
<div className="flex items-end gap-3">
<div className="flex-1">
{children} // 这是带有标签等的输入框
</div>
<div>
<Button type="button">检查</Button>
</div>
</div>
),
},
}}
/>
访问表单数据
有两种方式可以访问表单数据:
onSubmit
推荐的方式是使用 onSubmit
属性。当表单提交且数据有效时,该函数会被调用。
<AutoForm
onSubmit={(data) => {
// 处理数据
// 数据会自动通过 zod 进行验证和转换
}}
/>
受控表单
你也可以使用 values
和 onValuesChange
属性来自行控制表单数据。
const [values, setValues] = useState<Partial<z.infer<typeof formSchema>>>({});
<AutoForm values={values} onValuesChange={setValues} />;
请注意,使用这种方法时,数据不会被验证或转换,因为它们会立即更新。
或者,你可以使用 onParsedValuesChange
来获取只有在值可以被 zod 验证和解析时才更新的值:
const [values, setValues] = useState<z.infer<typeof formSchema>>({});
<AutoForm values={values} onParsedValuesChange={setValues} />;
提交表单
你可以使用 AutoFormSubmit
组件来创建一个提交按钮。
<AutoForm>
<AutoFormSubmit>立即发送</AutoFormSubmit>
</AutoForm>
// 或者
<AutoForm>
<button type="submit">立即发送</button>
</AutoForm>
添加其他元素
所有传递给 AutoForm
组件的子元素都会在表单下方渲染。
<AutoForm>
<AutoFormSubmit>立即发送</AutoFormSubmit>
<p className="text-gray-500 text-sm">
提交此表单即表示您同意我们的{" "}
<a href="#" className="text-primary underline">
条款和条件
</a>
。
</p>
</AutoForm>
依赖关系
AutoForm 允许您在字段之间添加依赖关系,以根据其他字段的值控制字段。为此,可以向 AutoForm
组件传递一个 dependencies
数组。
<AutoForm
dependencies={[
{
// 当年龄为18岁或以上时,"age"隐藏"parentsAllowed"
sourceField: "age",
type: DependencyType.HIDES,
targetField: "parentsAllowed",
when: (age) => age >= 18,
},
{
// 如果未选中"素食主义者"复选框,则从"mealOptions"中隐藏"惠灵顿牛排"选项
sourceField: "vegetarian",
type: DependencyType.SETS_OPTIONS,
targetField: "mealOptions",
when: (vegetarian, mealOption) =>
vegetarian && mealOption !== "惠灵顿牛排",
options: ["意大利面", "沙拉"],
},
]}
/>
支持以下依赖类型:
DependencyType.HIDES
:当when
函数返回 true 时隐藏目标字段DependencyType.DISABLES
:当when
函数返回 true 时禁用目标字段DependencyType.REQUIRES
:当when
函数返回 true 时将目标字段设置为必填DependencyType.SETS_OPTIONS
:当when
函数返回 true 时将目标字段的选项设置为options
数组
when
函数接收源字段的值和目标字段的值作为参数,应返回一个布尔值,以指示是否应用依赖关系。
请注意,依赖关系在返回 false
时不会导致相反的操作 - 例如,如果您在 zod schema 中将字段标记为必填(即不显式设置 optional
),在 REQUIRES
依赖中返回 false
不会将其标记为可选。相反,您应该使用 zod 的 optional
方法将其默认标记为可选,并使用 REQUIRES
依赖在满足依赖条件时将其标记为必填。
请注意,依赖关系对表单的验证没有任何影响。您应该使用 zod 的 refine
方法根据其他字段的值验证表单。
您可以为同一字段和依赖类型创建多个依赖关系 - 例如,根据多个其他字段隐藏一个字段。这将在满足任何依赖关系时隐藏该字段。
贡献
欢迎贡献!请提出问题或提交拉取请求。
- Fork 仓库
- 克隆您的 fork 并使用
npm install
安装依赖项 - 运行
npm run dev
启动开发服务器并进行更改 - 运行
npm run fix
运行格式化程序和 linter - 运行
npm test
运行测试 - 提交您的更改并打开拉取请求
添加新组件
如果您想添加新组件,请确保将其添加到 auto-form/config.tsx
中的 INPUT_COMPONENTS
对象中。
- 在
src/components/ui/auto-form/fields
中创建一个新组件。您可以复制现有组件(如input.tsx
)作为起点。 - 将组件添加到
auto-form/config.tsx
中的INPUT_COMPONENTS
对象中,以给它一个名称。 - 可选地,在
auto-form/config.tsx
中的DEFAULT_ZOD_HANDLERS
下为 zod 类型添加组件名称作为默认处理程序。
许可证
MIT