Files
hy2-panel/components/action-form.tsx
T

73 lines
2.1 KiB
TypeScript

"use client";
import { useRef, useState, type ComponentProps, type ReactNode } from "react";
import { useFormStatus } from "react-dom";
function FormFields({ children }: { children: ReactNode }) {
const { pending } = useFormStatus();
return (
<fieldset disabled={pending} style={{ display: "contents" }}>
{children}
</fieldset>
);
}
/** Keeps action errors beside the submitted form, without losing entered values. */
export function ActionForm({
action,
children,
...props
}: Omit<ComponentProps<"form">, "action"> & {
action: (data: FormData) => Promise<void | { error: string }>;
}) {
const preserveValues = useRef(false);
const [message, setMessage] = useState("");
const [failed, setFailed] = useState(false);
return (
<form
{...props}
onReset={(event) => {
if (preserveValues.current) event.preventDefault();
}}
onChange={() => setMessage("")}
onKeyDown={(event) => {
if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
event.preventDefault();
const button = event.currentTarget.querySelector<HTMLButtonElement>(
'button[type="submit"]',
);
if (button && !button.disabled) button.click();
}
}}
action={async (data) => {
setMessage("");
preserveValues.current = false;
try {
const result = await action(data);
if (result?.error) {
preserveValues.current = true;
setFailed(true);
setMessage(result.error);
return;
}
setFailed(false);
setMessage("已保存");
} catch {
preserveValues.current = true;
setFailed(true);
setMessage("操作失败,请检查输入后重试。填写内容已保留。");
}
}}
>
<FormFields>{children}</FormFields>
{message && (
<p
role={failed ? "alert" : "status"}
className={`full-span ${failed ? "error-text" : "muted"}`}
>
{message}
</p>
)}
</form>
);
}