mirror of
https://github.com/ant-design/ant-design.git
synced 2025-08-06 07:56:28 +08:00
feat: Modal support hooks (#20949)
* init hooks * portal it in * children with 2 context demo * Remove config since not more second demo * Quit with same Component for logic reuse * add rest functions * use localeReceiver * use localeReceiver * update test case * fix lint * update docs * update demo title
This commit is contained in:
parent
b36e96043f
commit
d02c486052
18
components/_util/usePatchElement.tsx
Normal file
18
components/_util/usePatchElement.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export default function usePatchElement(): [
|
||||
React.ReactElement[],
|
||||
(element: React.ReactElement) => Function,
|
||||
] {
|
||||
const [elements, setElements] = React.useState<React.ReactElement[]>([]);
|
||||
|
||||
function patchElement(element: React.ReactElement) {
|
||||
setElements(originElements => [...originElements, element]);
|
||||
|
||||
return () => {
|
||||
setElements(originElements => originElements.filter(ele => ele !== element));
|
||||
};
|
||||
}
|
||||
|
||||
return [elements, patchElement];
|
||||
}
|
117
components/modal/ConfirmDialog.tsx
Normal file
117
components/modal/ConfirmDialog.tsx
Normal file
@ -0,0 +1,117 @@
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Dialog, { ModalFuncProps } from './Modal';
|
||||
import ActionButton from './ActionButton';
|
||||
import warning from '../_util/warning';
|
||||
|
||||
interface ConfirmDialogProps extends ModalFuncProps {
|
||||
afterClose?: () => void;
|
||||
close: (...args: any[]) => void;
|
||||
autoFocusButton?: null | 'ok' | 'cancel';
|
||||
}
|
||||
|
||||
const ConfirmDialog = (props: ConfirmDialogProps) => {
|
||||
const {
|
||||
icon,
|
||||
onCancel,
|
||||
onOk,
|
||||
close,
|
||||
zIndex,
|
||||
afterClose,
|
||||
visible,
|
||||
keyboard,
|
||||
centered,
|
||||
getContainer,
|
||||
maskStyle,
|
||||
okText,
|
||||
okButtonProps,
|
||||
cancelText,
|
||||
cancelButtonProps,
|
||||
} = props;
|
||||
|
||||
warning(
|
||||
!(typeof icon === 'string' && icon.length > 2),
|
||||
'Modal',
|
||||
`\`icon\` is using ReactNode instead of string naming in v4. Please check \`${icon}\` at https://ant.design/components/icon`,
|
||||
);
|
||||
|
||||
// 支持传入{ icon: null }来隐藏`Modal.confirm`默认的Icon
|
||||
const okType = props.okType || 'primary';
|
||||
const prefixCls = props.prefixCls || 'ant-modal';
|
||||
const contentPrefixCls = `${prefixCls}-confirm`;
|
||||
// 默认为 true,保持向下兼容
|
||||
const okCancel = 'okCancel' in props ? props.okCancel! : true;
|
||||
const width = props.width || 416;
|
||||
const style = props.style || {};
|
||||
const mask = props.mask === undefined ? true : props.mask;
|
||||
// 默认为 false,保持旧版默认行为
|
||||
const maskClosable = props.maskClosable === undefined ? false : props.maskClosable;
|
||||
const autoFocusButton = props.autoFocusButton === null ? false : props.autoFocusButton || 'ok';
|
||||
const transitionName = props.transitionName || 'zoom';
|
||||
const maskTransitionName = props.maskTransitionName || 'fade';
|
||||
|
||||
const classString = classNames(
|
||||
contentPrefixCls,
|
||||
`${contentPrefixCls}-${props.type}`,
|
||||
props.className,
|
||||
);
|
||||
|
||||
const cancelButton = okCancel && (
|
||||
<ActionButton
|
||||
actionFn={onCancel}
|
||||
closeModal={close}
|
||||
autoFocus={autoFocusButton === 'cancel'}
|
||||
buttonProps={cancelButtonProps}
|
||||
>
|
||||
{cancelText}
|
||||
</ActionButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
prefixCls={prefixCls}
|
||||
className={classString}
|
||||
wrapClassName={classNames({ [`${contentPrefixCls}-centered`]: !!props.centered })}
|
||||
onCancel={() => close({ triggerCancel: true })}
|
||||
visible={visible}
|
||||
title=""
|
||||
transitionName={transitionName}
|
||||
footer=""
|
||||
maskTransitionName={maskTransitionName}
|
||||
mask={mask}
|
||||
maskClosable={maskClosable}
|
||||
maskStyle={maskStyle}
|
||||
style={style}
|
||||
width={width}
|
||||
zIndex={zIndex}
|
||||
afterClose={afterClose}
|
||||
keyboard={keyboard}
|
||||
centered={centered}
|
||||
getContainer={getContainer}
|
||||
>
|
||||
<div className={`${contentPrefixCls}-body-wrapper`}>
|
||||
<div className={`${contentPrefixCls}-body`}>
|
||||
{icon}
|
||||
{props.title === undefined ? null : (
|
||||
<span className={`${contentPrefixCls}-title`}>{props.title}</span>
|
||||
)}
|
||||
<div className={`${contentPrefixCls}-content`}>{props.content}</div>
|
||||
</div>
|
||||
<div className={`${contentPrefixCls}-btns`}>
|
||||
{cancelButton}
|
||||
<ActionButton
|
||||
type={okType}
|
||||
actionFn={onOk}
|
||||
closeModal={close}
|
||||
autoFocus={autoFocusButton === 'ok'}
|
||||
buttonProps={okButtonProps}
|
||||
>
|
||||
{okText}
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmDialog;
|
@ -4,6 +4,7 @@ import classNames from 'classnames';
|
||||
import addEventListener from 'rc-util/lib/Dom/addEventListener';
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
|
||||
import useModal from './useModal';
|
||||
import { getConfirmLocale } from './locale';
|
||||
import Button from '../button';
|
||||
import { ButtonType, NativeButtonProps } from '../button/button';
|
||||
@ -113,13 +114,6 @@ export interface ModalFuncProps {
|
||||
maskTransitionName?: string;
|
||||
}
|
||||
|
||||
export type ModalFunc = (
|
||||
props: ModalFuncProps,
|
||||
) => {
|
||||
destroy: () => void;
|
||||
update: (newConfig: ModalFuncProps) => void;
|
||||
};
|
||||
|
||||
export interface ModalLocale {
|
||||
okText: string;
|
||||
cancelText: string;
|
||||
@ -127,20 +121,10 @@ export interface ModalLocale {
|
||||
}
|
||||
|
||||
export default class Modal extends React.Component<ModalProps, {}> {
|
||||
static info: ModalFunc;
|
||||
|
||||
static success: ModalFunc;
|
||||
|
||||
static error: ModalFunc;
|
||||
|
||||
static warn: ModalFunc;
|
||||
|
||||
static warning: ModalFunc;
|
||||
|
||||
static confirm: ModalFunc;
|
||||
|
||||
static destroyAll: () => void;
|
||||
|
||||
static useModal = useModal;
|
||||
|
||||
static defaultProps = {
|
||||
width: 520,
|
||||
transitionName: 'zoom',
|
||||
|
54
components/modal/__tests__/hook.test.js
Normal file
54
components/modal/__tests__/hook.test.js
Normal file
@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import Modal from '..';
|
||||
import Button from '../../button';
|
||||
|
||||
jest.mock('rc-util/lib/Portal');
|
||||
|
||||
describe('Modal.hook', () => {
|
||||
it('hooks support context', () => {
|
||||
jest.useFakeTimers();
|
||||
const Context = React.createContext('light');
|
||||
let instance;
|
||||
|
||||
const Demo = () => {
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
return (
|
||||
<Context.Provider value="bamboo">
|
||||
<Button
|
||||
onClick={() => {
|
||||
instance = modal.confirm({
|
||||
content: (
|
||||
<Context.Consumer>
|
||||
{name => <div className="test-hook">{name}</div>}
|
||||
</Context.Consumer>
|
||||
),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{contextHolder}
|
||||
</Context.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const wrapper = mount(<Demo />);
|
||||
wrapper.find('button').simulate('click');
|
||||
|
||||
expect(wrapper.find('.test-hook').text()).toEqual('bamboo');
|
||||
|
||||
// Update instance
|
||||
instance.update({
|
||||
content: <div className="updated-content" />,
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('.updated-content')).toHaveLength(1);
|
||||
|
||||
// Destroy
|
||||
instance.destroy();
|
||||
jest.runAllTimers();
|
||||
wrapper.update();
|
||||
expect(wrapper.find('Modal')).toHaveLength(0);
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
});
|
@ -1,125 +1,31 @@
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import Dialog, { ModalFuncProps, destroyFns } from './Modal';
|
||||
import ActionButton from './ActionButton';
|
||||
import {
|
||||
InfoCircleOutlined,
|
||||
CheckCircleOutlined,
|
||||
CloseCircleOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { getConfirmLocale } from './locale';
|
||||
import warning from '../_util/warning';
|
||||
import { ModalFuncProps, destroyFns } from './Modal';
|
||||
import ConfirmDialog from './ConfirmDialog';
|
||||
|
||||
interface ConfirmDialogProps extends ModalFuncProps {
|
||||
afterClose?: () => void;
|
||||
close: (...args: any[]) => void;
|
||||
autoFocusButton?: null | 'ok' | 'cancel';
|
||||
}
|
||||
|
||||
const IS_REACT_16 = !!ReactDOM.createPortal;
|
||||
|
||||
const ConfirmDialog = (props: ConfirmDialogProps) => {
|
||||
const {
|
||||
icon,
|
||||
onCancel,
|
||||
onOk,
|
||||
close,
|
||||
zIndex,
|
||||
afterClose,
|
||||
visible,
|
||||
keyboard,
|
||||
centered,
|
||||
getContainer,
|
||||
maskStyle,
|
||||
okButtonProps,
|
||||
cancelButtonProps,
|
||||
} = props;
|
||||
|
||||
warning(
|
||||
!(typeof icon === 'string' && icon.length > 2),
|
||||
'Modal',
|
||||
`\`icon\` is using ReactNode instead of string naming in v4. Please check \`${icon}\` at https://ant.design/components/icon`,
|
||||
);
|
||||
|
||||
// 支持传入{ icon: null }来隐藏`Modal.confirm`默认的Icon
|
||||
const okType = props.okType || 'primary';
|
||||
const prefixCls = props.prefixCls || 'ant-modal';
|
||||
const contentPrefixCls = `${prefixCls}-confirm`;
|
||||
// 默认为 true,保持向下兼容
|
||||
const okCancel = 'okCancel' in props ? props.okCancel! : true;
|
||||
const width = props.width || 416;
|
||||
const style = props.style || {};
|
||||
const mask = props.mask === undefined ? true : props.mask;
|
||||
// 默认为 false,保持旧版默认行为
|
||||
const maskClosable = props.maskClosable === undefined ? false : props.maskClosable;
|
||||
const runtimeLocale = getConfirmLocale();
|
||||
const okText = props.okText || (okCancel ? runtimeLocale.okText : runtimeLocale.justOkText);
|
||||
const cancelText = props.cancelText || runtimeLocale.cancelText;
|
||||
const autoFocusButton = props.autoFocusButton === null ? false : props.autoFocusButton || 'ok';
|
||||
const transitionName = props.transitionName || 'zoom';
|
||||
const maskTransitionName = props.maskTransitionName || 'fade';
|
||||
|
||||
const classString = classNames(
|
||||
contentPrefixCls,
|
||||
`${contentPrefixCls}-${props.type}`,
|
||||
props.className,
|
||||
);
|
||||
|
||||
const cancelButton = okCancel && (
|
||||
<ActionButton
|
||||
actionFn={onCancel}
|
||||
closeModal={close}
|
||||
autoFocus={autoFocusButton === 'cancel'}
|
||||
buttonProps={cancelButtonProps}
|
||||
>
|
||||
{cancelText}
|
||||
</ActionButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
prefixCls={prefixCls}
|
||||
className={classString}
|
||||
wrapClassName={classNames({ [`${contentPrefixCls}-centered`]: !!props.centered })}
|
||||
onCancel={() => close({ triggerCancel: true })}
|
||||
visible={visible}
|
||||
title=""
|
||||
transitionName={transitionName}
|
||||
footer=""
|
||||
maskTransitionName={maskTransitionName}
|
||||
mask={mask}
|
||||
maskClosable={maskClosable}
|
||||
maskStyle={maskStyle}
|
||||
style={style}
|
||||
width={width}
|
||||
zIndex={zIndex}
|
||||
afterClose={afterClose}
|
||||
keyboard={keyboard}
|
||||
centered={centered}
|
||||
getContainer={getContainer}
|
||||
>
|
||||
<div className={`${contentPrefixCls}-body-wrapper`}>
|
||||
<div className={`${contentPrefixCls}-body`}>
|
||||
{icon}
|
||||
{props.title === undefined ? null : (
|
||||
<span className={`${contentPrefixCls}-title`}>{props.title}</span>
|
||||
)}
|
||||
<div className={`${contentPrefixCls}-content`}>{props.content}</div>
|
||||
</div>
|
||||
<div className={`${contentPrefixCls}-btns`}>
|
||||
{cancelButton}
|
||||
<ActionButton
|
||||
type={okType}
|
||||
actionFn={onOk}
|
||||
closeModal={close}
|
||||
autoFocus={autoFocusButton === 'ok'}
|
||||
buttonProps={okButtonProps}
|
||||
>
|
||||
{okText}
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
export type ModalFunc = (
|
||||
props: ModalFuncProps,
|
||||
) => {
|
||||
destroy: () => void;
|
||||
update: (newConfig: ModalFuncProps) => void;
|
||||
};
|
||||
|
||||
export interface ModalStaticFunctions {
|
||||
info: ModalFunc;
|
||||
success: ModalFunc;
|
||||
error: ModalFunc;
|
||||
warn: ModalFunc;
|
||||
warning: ModalFunc;
|
||||
confirm: ModalFunc;
|
||||
}
|
||||
|
||||
export default function confirm(config: ModalFuncProps) {
|
||||
const div = document.createElement('div');
|
||||
document.body.appendChild(div);
|
||||
@ -145,8 +51,16 @@ export default function confirm(config: ModalFuncProps) {
|
||||
}
|
||||
}
|
||||
|
||||
function render(props: any) {
|
||||
ReactDOM.render(<ConfirmDialog {...props} />, div);
|
||||
function render({ okText, cancelText, ...props }: any) {
|
||||
const runtimeLocale = getConfirmLocale();
|
||||
ReactDOM.render(
|
||||
<ConfirmDialog
|
||||
{...props}
|
||||
okText={okText || (props.okCancel ? runtimeLocale.okText : runtimeLocale.justOkText)}
|
||||
cancelText={cancelText || runtimeLocale.cancelText}
|
||||
/>,
|
||||
div,
|
||||
);
|
||||
}
|
||||
|
||||
function close(...args: any[]) {
|
||||
@ -155,11 +69,7 @@ export default function confirm(config: ModalFuncProps) {
|
||||
visible: false,
|
||||
afterClose: destroy.bind(this, ...args),
|
||||
};
|
||||
if (IS_REACT_16) {
|
||||
render(currentConfig);
|
||||
} else {
|
||||
destroy(...args);
|
||||
}
|
||||
render(currentConfig);
|
||||
}
|
||||
|
||||
function update(newConfig: ModalFuncProps) {
|
||||
@ -179,3 +89,47 @@ export default function confirm(config: ModalFuncProps) {
|
||||
update,
|
||||
};
|
||||
}
|
||||
|
||||
export function withWarn(props: ModalFuncProps): ModalFuncProps {
|
||||
return {
|
||||
type: 'warning',
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
okCancel: false,
|
||||
...props,
|
||||
};
|
||||
}
|
||||
|
||||
export function withInfo(props: ModalFuncProps): ModalFuncProps {
|
||||
return {
|
||||
type: 'info',
|
||||
icon: <InfoCircleOutlined />,
|
||||
okCancel: false,
|
||||
...props,
|
||||
};
|
||||
}
|
||||
|
||||
export function withSuccess(props: ModalFuncProps): ModalFuncProps {
|
||||
return {
|
||||
type: 'success',
|
||||
icon: <CheckCircleOutlined />,
|
||||
okCancel: false,
|
||||
...props,
|
||||
};
|
||||
}
|
||||
|
||||
export function withError(props: ModalFuncProps): ModalFuncProps {
|
||||
return {
|
||||
type: 'error',
|
||||
icon: <CloseCircleOutlined />,
|
||||
okCancel: false,
|
||||
...props,
|
||||
};
|
||||
}
|
||||
|
||||
export function withConfirm(props: ModalFuncProps): ModalFuncProps {
|
||||
return {
|
||||
type: 'confirm',
|
||||
okCancel: true,
|
||||
...props,
|
||||
};
|
||||
}
|
||||
|
77
components/modal/demo/hooks.md
Normal file
77
components/modal/demo/hooks.md
Normal file
@ -0,0 +1,77 @@
|
||||
---
|
||||
order: 12
|
||||
title:
|
||||
zh-CN: 使用 hooks 获得上下文
|
||||
en-US: Use hooks to get context
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
通过 `Modal.useModal` 创建支持读取 context 的 `contextHolder`。
|
||||
|
||||
## en-US
|
||||
|
||||
Use `Modal.useModal` to get `contextHolder` with context accessible issue.
|
||||
|
||||
```jsx
|
||||
import { Modal, Button } from 'antd';
|
||||
|
||||
const ReachableContext = React.createContext();
|
||||
const UnreachableContext = React.createContext();
|
||||
|
||||
const config = {
|
||||
title: 'Use Hook!',
|
||||
content: (
|
||||
<div>
|
||||
<ReachableContext.Consumer>{name => `Reachable: ${name}!`}</ReachableContext.Consumer>
|
||||
<br />
|
||||
<UnreachableContext.Consumer>{name => `Unreachable: ${name}!`}</UnreachableContext.Consumer>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
|
||||
return (
|
||||
<ReachableContext.Provider value="Light">
|
||||
<Button
|
||||
onClick={() => {
|
||||
modal.confirm(config);
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
modal.warning(config);
|
||||
}}
|
||||
>
|
||||
Warning
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
modal.info(config);
|
||||
}}
|
||||
>
|
||||
Info
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
modal.error(config);
|
||||
}}
|
||||
>
|
||||
Error
|
||||
</Button>
|
||||
|
||||
{/* `contextHolder` should always under the context you want to access */}
|
||||
{contextHolder}
|
||||
|
||||
{/* Can not access this context since `contextHolder` is not in it */}
|
||||
<UnreachableContext.Provider value="Bamboo" />
|
||||
</ReachableContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.render(<App />, mountNode);
|
||||
```
|
@ -109,3 +109,19 @@ browserHistory.listen(() => {
|
||||
Modal.destroyAll();
|
||||
});
|
||||
```
|
||||
|
||||
### Modal.useModal()
|
||||
|
||||
When you need using Context, you can use `contextHolder` which created by `Modal.useModal` to insert into children. Modal created by hooks will get all the context where `contextHolder` are. Created `modal` has the same creating function with `Modal.method`](<#Modal.method()>).
|
||||
|
||||
```jsx
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
|
||||
React.useEffect(() => {
|
||||
modal.confirm({
|
||||
// ...
|
||||
});
|
||||
}, []);
|
||||
|
||||
return <div>{contextHolder}</div>;
|
||||
```
|
||||
|
@ -1,55 +1,33 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
InfoCircleOutlined,
|
||||
CheckCircleOutlined,
|
||||
CloseCircleOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
import Modal, { ModalFuncProps, destroyFns } from './Modal';
|
||||
import confirm from './confirm';
|
||||
import OriginModal, { ModalFuncProps, destroyFns } from './Modal';
|
||||
import confirm, {
|
||||
withWarn,
|
||||
withInfo,
|
||||
withSuccess,
|
||||
withError,
|
||||
withConfirm,
|
||||
ModalStaticFunctions,
|
||||
} from './confirm';
|
||||
|
||||
export { ActionButtonProps } from './ActionButton';
|
||||
export { ModalProps, ModalFuncProps } from './Modal';
|
||||
|
||||
function modalWarn(props: ModalFuncProps) {
|
||||
const config = {
|
||||
type: 'warning',
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
okCancel: false,
|
||||
...props,
|
||||
};
|
||||
return confirm(config);
|
||||
return confirm(withWarn(props));
|
||||
}
|
||||
|
||||
type Modal = typeof OriginModal & ModalStaticFunctions;
|
||||
const Modal = OriginModal as Modal;
|
||||
|
||||
Modal.info = function infoFn(props: ModalFuncProps) {
|
||||
const config = {
|
||||
type: 'info',
|
||||
icon: <InfoCircleOutlined />,
|
||||
okCancel: false,
|
||||
...props,
|
||||
};
|
||||
return confirm(config);
|
||||
return confirm(withInfo(props));
|
||||
};
|
||||
|
||||
Modal.success = function successFn(props: ModalFuncProps) {
|
||||
const config = {
|
||||
type: 'success',
|
||||
icon: <CheckCircleOutlined />,
|
||||
okCancel: false,
|
||||
...props,
|
||||
};
|
||||
return confirm(config);
|
||||
return confirm(withSuccess(props));
|
||||
};
|
||||
|
||||
Modal.error = function errorFn(props: ModalFuncProps) {
|
||||
const config = {
|
||||
type: 'error',
|
||||
icon: <CloseCircleOutlined />,
|
||||
okCancel: false,
|
||||
...props,
|
||||
};
|
||||
return confirm(config);
|
||||
return confirm(withError(props));
|
||||
};
|
||||
|
||||
Modal.warning = modalWarn;
|
||||
@ -57,12 +35,7 @@ Modal.warning = modalWarn;
|
||||
Modal.warn = modalWarn;
|
||||
|
||||
Modal.confirm = function confirmFn(props: ModalFuncProps) {
|
||||
const config = {
|
||||
type: 'confirm',
|
||||
okCancel: true,
|
||||
...props,
|
||||
};
|
||||
return confirm(config);
|
||||
return confirm(withConfirm(props));
|
||||
};
|
||||
|
||||
Modal.destroyAll = function destroyAllFn() {
|
||||
|
@ -111,3 +111,19 @@ browserHistory.listen(() => {
|
||||
Modal.destroyAll();
|
||||
});
|
||||
```
|
||||
|
||||
### Modal.useModal()
|
||||
|
||||
当你需要使用 Context 时,可以通过 `Modal.useModal` 创建一个 `contextHolder` 插入子节点中。通过 hooks 创建的临时 Modal 将会得到 `contextHolder` 所在位置的所有上下文。创建的 `modal` 对象拥有与 [`Modal.method`](<#Modal.method()>) 相同的创建通知方法。
|
||||
|
||||
```jsx
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
|
||||
React.useEffect(() => {
|
||||
modal.confirm({
|
||||
// ...
|
||||
});
|
||||
}, []);
|
||||
|
||||
return <div>{contextHolder}</div>;
|
||||
```
|
||||
|
63
components/modal/useModal/HookModal.tsx
Normal file
63
components/modal/useModal/HookModal.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import * as React from 'react';
|
||||
import { ModalFuncProps } from '../Modal';
|
||||
import ConfirmDialog from '../ConfirmDialog';
|
||||
import defaultLocale from '../../locale/default';
|
||||
import LocaleReceiver from '../../locale-provider/LocaleReceiver';
|
||||
|
||||
export interface HookModalProps {
|
||||
afterClose: () => void;
|
||||
config: ModalFuncProps;
|
||||
}
|
||||
|
||||
export interface HookModalRef {
|
||||
destroy: () => void;
|
||||
update: (config: ModalFuncProps) => void;
|
||||
}
|
||||
|
||||
interface ModalLocale {
|
||||
okText: string;
|
||||
cancelText: string;
|
||||
justOkText: string;
|
||||
}
|
||||
|
||||
const HookModal: React.RefForwardingComponent<HookModalRef, HookModalProps> = (
|
||||
{ afterClose, config },
|
||||
ref,
|
||||
) => {
|
||||
const [visible, setVisible] = React.useState(true);
|
||||
const [innerConfig, setInnerConfig] = React.useState(config);
|
||||
|
||||
function close() {
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
destroy: close,
|
||||
update: (newConfig: ModalFuncProps) => {
|
||||
setInnerConfig(originConfig => ({
|
||||
...originConfig,
|
||||
...newConfig,
|
||||
}));
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<LocaleReceiver componentName="Modal" defaultLocale={defaultLocale.Modal}>
|
||||
{(modalLocale: ModalLocale) => (
|
||||
<ConfirmDialog
|
||||
{...innerConfig}
|
||||
close={close}
|
||||
visible={visible}
|
||||
afterClose={afterClose}
|
||||
okText={
|
||||
innerConfig.okText ||
|
||||
(innerConfig.okCancel ? modalLocale.okText : modalLocale.justOkText)
|
||||
}
|
||||
cancelText={innerConfig.cancelText || modalLocale.cancelText}
|
||||
/>
|
||||
)}
|
||||
</LocaleReceiver>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.forwardRef(HookModal);
|
64
components/modal/useModal/index.tsx
Normal file
64
components/modal/useModal/index.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import * as React from 'react';
|
||||
import { ModalFuncProps } from '../Modal';
|
||||
import usePatchElement from '../../_util/usePatchElement';
|
||||
import HookModal, { HookModalRef } from './HookModal';
|
||||
import {
|
||||
withConfirm,
|
||||
ModalStaticFunctions,
|
||||
withInfo,
|
||||
withSuccess,
|
||||
withError,
|
||||
withWarn,
|
||||
} from '../confirm';
|
||||
|
||||
let uuid = 0;
|
||||
|
||||
export default function useModal(): [Omit<ModalStaticFunctions, 'warn'>, React.ReactElement] {
|
||||
const [elements, patchElement] = usePatchElement();
|
||||
|
||||
function getConfirmFunc(withFunc: (config: ModalFuncProps) => ModalFuncProps) {
|
||||
return function hookConfirm(config: ModalFuncProps) {
|
||||
uuid += 1;
|
||||
|
||||
const modalRef = React.createRef<HookModalRef>();
|
||||
|
||||
let closeFunc: Function;
|
||||
const modal = (
|
||||
<HookModal
|
||||
key={`modal-${uuid}`}
|
||||
config={withFunc(config)}
|
||||
ref={modalRef}
|
||||
afterClose={() => {
|
||||
closeFunc();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
closeFunc = patchElement(modal);
|
||||
|
||||
return {
|
||||
destroy: () => {
|
||||
if (modalRef.current) {
|
||||
modalRef.current.destroy();
|
||||
}
|
||||
},
|
||||
update: (newConfig: ModalFuncProps) => {
|
||||
if (modalRef.current) {
|
||||
modalRef.current.update(newConfig);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
info: getConfirmFunc(withInfo),
|
||||
success: getConfirmFunc(withSuccess),
|
||||
error: getConfirmFunc(withError),
|
||||
warning: getConfirmFunc(withWarn),
|
||||
confirm: getConfirmFunc(withConfirm),
|
||||
},
|
||||
<>{elements}</>,
|
||||
];
|
||||
}
|
Loading…
Reference in New Issue
Block a user