zzzUI

Form

承载各个 Field 的表单外框,基于 Base UI 的 Form:提交时校验所有 Field,并显示服务器返回的错误;字段可按组归入带倾斜标题的 Fieldset。

安装

terminal
# 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

示例

发布职位,含服务器返回的错误

Post a job

In credits, paid on hand-in.

form-job.tsx
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

form-hook.tsx
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

属性类型默认值说明
errorsRecord<string, string | string[]>—服务器返回的错误信息,按字段名索引。
onFormSubmit(values) => void—所有 Field 均通过校验后调用,参数为各 Field 的值。

另可传入 Base UI Form 的全部属性。

Fieldset, FieldsetLegend

属性类型默认值说明
disabledbooleanfalse禁用组内所有控件。

另可传入 Base UI Fieldset.Root 的全部属性。

FormActions