Form
承载各个 Field 的表单外框,基于 Base UI 的 Form:提交时校验所有 Field,并显示服务器返回的错误;字段可按组归入带倾斜标题的 Fieldset。
安装
# once per project: where @zzzui items come frompnpm dlx shadcn@latest registry add @zzzui=https://zzzui.catforest.dev/r/{name}.jsonpnpm dlx shadcn@latest add @zzzui/form示例
发布职位,含服务器返回的错误
import { useState } from "react";import { Button } from "@/components/zero/button";import { Field, FieldDescription, FieldError, FieldLabel,} from "@/components/zero/field";import { Fieldset, FieldsetLegend, Form, FormActions,} from "@/components/zero/form";import { Input } from "@/components/zero/input";import { Textarea } from "@/components/zero/textarea";export default function FormJob() { const [errors, setErrors] = useState<Record<string, string>>({}); const [posted, setPosted] = useState(""); return ( <Form className="w-full max-w-md" errors={errors} onFormSubmit={(values) => { // Stands in for the server: it rejects a reward under 500. if (Number(values.reward) < 500) { setErrors({ reward: "The dispatcher posts nothing under 500 cr." }); return; } setErrors({}); setPosted(String(values.title)); }} > <Fieldset> <FieldsetLegend>Post a job</FieldsetLegend> <Field name="title"> <FieldLabel>Title</FieldLabel> <Input required placeholder="Sealed crate" /> <FieldError>Give the job a title.</FieldError> </Field> <Field name="reward" validate={(value) => (value ? null : "Name the reward.")} > <FieldLabel>Reward</FieldLabel> <Input type="number" inputMode="numeric" placeholder="3100" /> <FieldDescription>In credits, paid on hand-in.</FieldDescription> {/* its own message, or the server's, from the form's errors */} <FieldError /> </Field> <Field name="notes"> <FieldLabel>Notes</FieldLabel> <Textarea placeholder="Anything the driver should know" /> </Field> </Fieldset> <FormActions> <Button type="submit">Post it</Button> {posted ? ( <span className="font-medium font-zero-text text-zero-sm text-zero-accent"> Posted: {posted} </span> ) : null} </FormActions> </Form> );}搭配 react-hook-form 与 zod
import { zodResolver } from "@hookform/resolvers/zod";import { Controller, useForm } from "react-hook-form";import { z } from "zod";import { Button } from "@/components/zero/button";import { Field, FieldError, FieldLabel } from "@/components/zero/field";import { Form, FormActions } from "@/components/zero/form";import { Input } from "@/components/zero/input";const schema = z.object({ driver: z.string().min(2, "At least two letters."), code: z .string() .regex(/^[A-Z0-9]{4}$/, "Four letters or digits, in capitals."),});type Values = z.infer<typeof schema>;export default function FormHook() { const form = useForm<Values>({ resolver: zodResolver(schema), defaultValues: { driver: "", code: "" }, }); return ( <Form className="w-full max-w-md" onSubmit={form.handleSubmit(() => form.reset())} > {(["driver", "code"] as const).map((name) => ( <Controller key={name} name={name} control={form.control} render={({ field, fieldState }) => ( <Field name={name} invalid={fieldState.invalid}> <FieldLabel> {name === "driver" ? "Driver" : "Gate code"} </FieldLabel> <Input {...field} /> <FieldError match={fieldState.invalid}> {fieldState.error?.message} </FieldError> </Field> )} /> ))} <FormActions> <Button type="submit">Sign for it</Button> </FormActions> </Form> );}行为
提交时,Form 收集每个 Field 的值,并执行各自的校验。errors 以各 Field 的 name 为键,把服务器返回的信息填入对应 Field 的 FieldError。Fieldset 将多个 Field 归为一组,组标题前有一道细长的斜切条;禁用 Fieldset 会禁用组内所有控件。FormActions 是表单末尾的按钮行。
搭配 react-hook-form 时,每个控件通过各自的 Controller 接入,并根据字段状态为 Field 设置 invalid、为 FieldError 设置 match,写法见第二个示例。
属性
Form
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| errors | Record<string, string | string[]> | — | 服务器返回的错误信息,按字段名索引。 |
| onFormSubmit | (values) => void | — | 所有 Field 均通过校验后调用,参数为各 Field 的值。 |
另可传入 Base UI Form 的全部属性。