import { useState } from "react"; import { z } from "zod"; import { Alert, AlertDescription } from "@godxjp/ui/feedback"; import { Input, Select } from "@godxjp/ui/data-entry"; import { Button } from "@godxjp/ui/general"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display"; import { Flex, PageContainer } from "@godxjp/ui/layout"; import { FormFieldArray, FormFieldControl, FormRoot, useFormDisabled, useFormWatch, useZodForm, } from "@godxjp/ui/form"; const schema = z.object({ plan: z.enum(["standard", "enterprise"]), contacts: z .array( z.object({ name: z.string().min(1, "担当者名を入力してください"), email: z.string().email("有効なメールアドレスを入力してください"), }), ) .min(1, "担当者を 1 名以上登録してください"), }); type Values = z.infer; const emptyContact = { name: "", email: "" }; /** Counter driven by useFormWatch — re-renders on `contacts` only, not on every keystroke elsewhere. */ function ContactCount() { const contacts = useFormWatch("contacts"); return ( {`登録済み ${Array.isArray(contacts) ? contacts.length : 0} 名 · 行の追加・削除・並べ替えは送信値の順序にそのまま反映されます。`} ); } /** Action buttons are not fields, so they read the form-level disabled state themselves. */ function Actions({ onReset }: { onReset: () => void }) { const disabled = useFormDisabled(); return ( ); } export default function Demo() { const [status, setStatus] = useState<"idle" | "saved" | "failed">("idle"); const [locked, setLocked] = useState(false); const form = useZodForm(schema, { defaultValues: { plan: "standard", contacts: [{ name: "田中 花子", email: "tanaka@example.jp" }], }, }); return ( 請求先の担当者 setStatus("saved")} onSubmitFailed={() => setStatus("failed")} onReset={() => setStatus("idle")} > {status === "failed" && ( 入力内容を確認してください。最初のエラー項目へ移動しました。 )} {status === "saved" && ( 保存しました。 )} name="plan" label="プラン"> {(field) => ( )} name={`${row.name}.email`} label={`担当者 ${position + 1} · メール`} required > {(field) => ( )} ))} )} setStatus("idle")} /> ); }