ci4sManagement-cloud/react-ui/src/utils/modal.tsx

134 lines
3.3 KiB
TypeScript

/*
* @Author: 赵伟
* @Date: 2024-04-13 10:08:35
* @Description: 以函数的方式打开 Modal
*/
import { ConfigProvider, type ModalProps } from 'antd';
import { globalConfig } from 'antd/es/config-provider';
import zhCN from 'antd/locale/zh_CN';
import React, { useState } from 'react';
import { createRoot } from 'react-dom/client';
const destroyFns: (() => void)[] = [];
/**
* Function to open an Ant Design modal.
*
* @param modal - The function that renders the modal content.
* @param modalProps - The modal properties.
* @return An object with a destroy method to close the modal.
*/
export const openAntdModal = <T extends Omit<ModalProps, 'onOk'>>(
modal: (props: T) => React.ReactNode,
modalProps: T,
) => {
const CustomModel = modal;
const container = document.createDocumentFragment();
const root = createRoot(container);
const { afterClose, onCancel } = modalProps;
const global = globalConfig();
let timeoutId: ReturnType<typeof setTimeout>;
function destroy() {
const index = destroyFns.indexOf(close);
if (index !== -1) {
destroyFns.splice(index, 1);
}
root.unmount();
}
function handleAfterClose() {
afterClose?.();
// Warning: Attempted to synchronously unmount a root while React was already rendering.
// React cannot finish unmounting the root until the current render has completed, which may lead to a race condition.
setTimeout(() => {
destroy();
}, 0);
}
function handleCancel(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
if (onCancel) {
onCancel(e);
} else {
close();
}
}
function render(props: T) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
const rootPrefixCls = global.getPrefixCls();
const iconPrefixCls = global.getIconPrefixCls();
const theme = global.getTheme();
const dom = (
<CustomModel {...props} onCancel={handleCancel} afterClose={handleAfterClose}></CustomModel>
);
root.render(
<ConfigProvider
prefixCls={rootPrefixCls}
iconPrefixCls={iconPrefixCls}
theme={theme}
locale={zhCN}
>
{global.holderRender ? global.holderRender(dom) : dom}
</ConfigProvider>,
);
});
}
function close() {
render({ ...modalProps, open: false });
}
render({ ...modalProps, open: true });
destroyFns.push(close);
return {
close,
};
};
/**
* Generates a custom hook for managing an Ant Design modal.
*
* @param modal - The function that renders the modal content.
* @param defaultProps - The default modal properties.
* @return The modal component, open function, and close function.
*/
export const useModal = <T extends ModalProps>(
modal: (props: T) => React.ReactNode,
defaultProps?: T,
) => {
const [visible, setVisible] = useState(false);
const [props, setProps] = useState<T>(defaultProps || ({} as T));
const CustomModel = modal;
const open = (props: T) => {
setProps((prev) => ({
...prev,
...props,
}));
setVisible(true);
};
const close = () => {
setVisible(false);
};
return [<CustomModel key="modal" open={visible} {...props} />, open, close] as const;
};
// 关闭没有手动关闭的 Modal
export const closeAllModals = () => {
let close = destroyFns.pop();
while (close) {
close();
close = destroyFns.pop();
}
};