certimate/ui/src/components/ModalForm.tsx
2025-01-06 19:10:29 +08:00

143 lines
3.3 KiB
TypeScript

import { useControllableValue } from "ahooks";
import { Form, type FormProps, Modal, type ModalProps } from "antd";
import { useAntdForm, useTriggerElement } from "@/hooks";
export interface ModalFormProps<T extends NonNullable<unknown> = any> extends Omit<FormProps<T>, "title" | "onFinish"> {
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
cancelButtonProps?: ModalProps["cancelButtonProps"];
cancelText?: ModalProps["cancelText"];
defaultOpen?: boolean;
modalProps?: Omit<
ModalProps,
| "cancelButtonProps"
| "cancelText"
| "confirmLoading"
| "defaultOpen"
| "forceRender"
| "okButtonProps"
| "okText"
| "okType"
| "open"
| "title"
| "width"
| "onCancel"
| "onOk"
| "onOpenChange"
>;
okButtonProps?: ModalProps["okButtonProps"];
okText?: ModalProps["okText"];
open?: boolean;
title?: ModalProps["title"];
trigger?: React.ReactNode;
width?: ModalProps["width"];
onClose?: (e: React.MouseEvent | React.KeyboardEvent) => void | Promise<unknown>;
onFinish?: (values: T) => unknown | Promise<unknown>;
onOpenChange?: (open: boolean) => void;
}
const ModalForm = <T extends NonNullable<unknown> = any>({
className,
style,
children,
cancelButtonProps,
cancelText,
form,
modalProps,
okButtonProps,
okText,
title,
trigger,
width,
onFinish,
...props
}: ModalFormProps<T>) => {
const [open, setOpen] = useControllableValue<boolean>(props, {
valuePropName: "open",
defaultValuePropName: "defaultOpen",
trigger: "onOpenChange",
});
const triggerEl = useTriggerElement(trigger, { onClick: () => setOpen(true) });
const {
form: formInst,
formPending,
formProps,
submit: submitForm,
} = useAntdForm({
form,
onSubmit: (values) => {
return onFinish?.(values);
},
});
const mergedFormProps: FormProps = {
clearOnDestroy: modalProps?.destroyOnClose ? true : undefined,
...formProps,
...props,
};
const mergedModalProps: ModalProps = {
...modalProps,
afterClose: () => {
if (!mergedFormProps.preserve) {
formInst.resetFields();
}
modalProps?.afterClose?.();
},
onClose: async (e) => {
if (formPending) return;
// 关闭 Modal 时 Promise.reject 阻止关闭
await modalProps?.onClose?.(e as React.MouseEvent | React.KeyboardEvent);
setOpen(false);
},
};
const handleOkClick = async () => {
// 提交表单返回 Promise.reject 时不关闭 Modal
await submitForm();
setOpen(false);
};
const handleCancelClick = () => {
if (formPending) return;
setOpen(false);
};
return (
<>
{triggerEl}
<Modal
{...mergedModalProps}
cancelButtonProps={cancelButtonProps}
cancelText={cancelText}
confirmLoading={formPending}
forceRender
okButtonProps={okButtonProps}
okText={okText}
okType="primary"
open={open}
title={title}
width={width}
onOk={handleOkClick}
onCancel={handleCancelClick}
>
<div className="pb-2 pt-4">
<Form className={className} style={style} {...mergedFormProps} form={formInst}>
{children}
</Form>
</div>
</Modal>
</>
);
};
export default ModalForm;