refactor: Modal support PureRender (#36012)

* chore: init

* chore: Modal pure render

* chore: update snapshot
pull/35935/head
二货机器人 3 years ago committed by GitHub
parent 8a0111341e
commit 821d540805
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,18 +1,13 @@
import CloseOutlined from '@ant-design/icons/CloseOutlined';
import classNames from 'classnames';
import Dialog from 'rc-dialog';
import * as React from 'react';
import Button from '../button';
import type { ButtonProps, LegacyButtonType } from '../button/button';
import { convertLegacyProps } from '../button/button';
import type { DirectionType } from '../config-provider';
import { ConfigContext } from '../config-provider';
import { NoFormStyle } from '../form/context';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import { getTransitionName } from '../_util/motion';
import { canUseDocElement } from '../_util/styleChecker';
import { getConfirmLocale } from './locale';
import { renderCloseIcon, renderFooter } from './PurePanel';
import useStyle from './style';
let mousePosition: { x: number; y: number } | null;
@ -152,28 +147,9 @@ const Modal: React.FC<ModalProps> = props => {
onOk?.(e);
};
const renderFooter = (locale: ModalLocale) => {
const { okText, okType, cancelText, confirmLoading } = props;
return (
<>
<Button onClick={handleCancel} {...props.cancelButtonProps}>
{cancelText || locale.cancelText}
</Button>
<Button
{...convertLegacyProps(okType)}
loading={confirmLoading}
onClick={handleOk}
{...props.okButtonProps}
>
{okText || locale.okText}
</Button>
</>
);
};
const {
prefixCls: customizePrefixCls,
footer,
className,
visible,
wrapClassName,
centered,
@ -188,18 +164,6 @@ const Modal: React.FC<ModalProps> = props => {
// Style
const [wrapSSR, hashId] = useStyle(prefixCls);
const defaultFooter = (
<LocaleReceiver componentName="Modal" defaultLocale={getConfirmLocale()}>
{renderFooter}
</LocaleReceiver>
);
const closeIconToRender = (
<span className={`${prefixCls}-close-x`}>
{closeIcon || <CloseOutlined className={`${prefixCls}-close-icon`} />}
</span>
);
const wrapClassNameExtended = classNames(wrapClassName, {
[`${prefixCls}-centered`]: !!centered,
[`${prefixCls}-wrap-rtl`]: direction === 'rtl',
@ -214,14 +178,19 @@ const Modal: React.FC<ModalProps> = props => {
prefixCls={prefixCls}
rootClassName={hashId}
wrapClassName={wrapClassNameExtended}
footer={footer === undefined ? defaultFooter : footer}
footer={renderFooter({
...props,
onOk: handleOk,
onCancel: handleCancel,
})}
visible={visible}
mousePosition={mousePosition}
onClose={handleCancel}
closeIcon={closeIconToRender}
closeIcon={renderCloseIcon(prefixCls, closeIcon)}
focusTriggerAfterClose={focusTriggerAfterClose}
transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)}
maskTransitionName={getTransitionName(rootPrefixCls, 'fade', props.maskTransitionName)}
className={classNames(hashId, className)}
/>
</NoFormStyle>,
);
@ -231,7 +200,6 @@ Modal.defaultProps = {
width: 520,
confirmLoading: false,
visible: false,
okType: 'primary' as LegacyButtonType,
};
export default Modal;

@ -0,0 +1,101 @@
import CloseOutlined from '@ant-design/icons/CloseOutlined';
import classNames from 'classnames';
import { Panel } from 'rc-dialog';
import type { PanelProps } from 'rc-dialog/lib/Dialog/Content/Panel';
import * as React from 'react';
import Button from '../button';
import { convertLegacyProps } from '../button/button';
import { ConfigContext } from '../config-provider';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import { getConfirmLocale } from './locale';
import type { ModalProps } from './Modal';
import useStyle from './style';
export interface PurePanelProps extends Omit<PanelProps, 'prefixCls'> {
prefixCls?: string;
style?: React.CSSProperties;
}
export function renderCloseIcon(prefixCls: string, closeIcon?: React.ReactNode) {
return (
<span className={`${prefixCls}-close-x`}>
{closeIcon || <CloseOutlined className={`${prefixCls}-close-icon`} />}
</span>
);
}
export function renderFooter(
props: Pick<
ModalProps,
| 'footer'
| 'okText'
| 'okType'
| 'cancelText'
| 'confirmLoading'
| 'okButtonProps'
| 'cancelButtonProps'
> & {
onOk?: React.MouseEventHandler;
onCancel?: React.MouseEventHandler;
},
) {
const {
okText,
okType = 'primary',
cancelText,
confirmLoading,
onOk,
onCancel,
okButtonProps,
cancelButtonProps,
footer,
} = props;
return (
footer ?? (
<LocaleReceiver componentName="Modal" defaultLocale={getConfirmLocale()}>
{locale => (
<>
<Button onClick={onCancel} {...cancelButtonProps}>
{cancelText || locale!.cancelText}
</Button>
<Button
{...convertLegacyProps(okType)}
loading={confirmLoading}
onClick={onOk}
{...okButtonProps}
>
{okText || locale!.okText}
</Button>
</>
)}
</LocaleReceiver>
)
);
}
export default function PurePanel(props: PurePanelProps) {
const {
prefixCls: customizePrefixCls,
className,
closeIcon,
closable = true,
...restProps
} = props;
const { getPrefixCls } = React.useContext(ConfigContext);
const prefixCls = customizePrefixCls || getPrefixCls('modal');
const [, hashId] = useStyle(prefixCls);
return (
<Panel
prefixCls={prefixCls}
className={classNames(hashId, `${prefixCls}-pure-panel`, className)}
{...restProps}
closeIcon={renderCloseIcon(prefixCls, closeIcon)}
footer={renderFooter(props)}
closable={closable}
/>
);
}

@ -164,6 +164,26 @@ exports[`Modal render without footer 1`] = `
>
Here is content of Modal
</div>
<div
class="ant-modal-footer"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Cancel
</span>
</button>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
OK
</span>
</button>
</div>
</div>
<div
aria-hidden="true"

@ -321,6 +321,92 @@ Array [
]
`;
exports[`renders ./components/modal/demo/render-panel.md extend context correctly 1`] = `
<div
aria-modal="true"
class="ant-modal ant-modal-pure-panel"
role="dialog"
>
<div
aria-hidden="true"
style="width:0;height:0;overflow:hidden;outline:none"
tabindex="0"
/>
<div
class="ant-modal-content"
>
<button
aria-label="Close"
class="ant-modal-close"
type="button"
>
<span
class="ant-modal-close-x"
>
<span
aria-label="close"
class="anticon anticon-close ant-modal-close-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
/>
</svg>
</span>
</span>
</button>
<div
class="ant-modal-header"
>
<div
class="ant-modal-title"
>
Hello World!
</div>
</div>
<div
class="ant-modal-body"
>
Hello World?!
</div>
<div
class="ant-modal-footer"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Cancel
</span>
</button>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
OK
</span>
</button>
</div>
</div>
<div
aria-hidden="true"
style="width:0;height:0;overflow:hidden;outline:none"
tabindex="0"
/>
</div>
`;
exports[`renders ./components/modal/demo/width.md extend context correctly 1`] = `
<button
class="ant-btn ant-btn-primary"

@ -321,6 +321,92 @@ Array [
]
`;
exports[`renders ./components/modal/demo/render-panel.md correctly 1`] = `
<div
aria-modal="true"
class="ant-modal ant-modal-pure-panel"
role="dialog"
>
<div
aria-hidden="true"
style="width:0;height:0;overflow:hidden;outline:none"
tabindex="0"
/>
<div
class="ant-modal-content"
>
<button
aria-label="Close"
class="ant-modal-close"
type="button"
>
<span
class="ant-modal-close-x"
>
<span
aria-label="close"
class="anticon anticon-close ant-modal-close-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
/>
</svg>
</span>
</span>
</button>
<div
class="ant-modal-header"
>
<div
class="ant-modal-title"
>
Hello World!
</div>
</div>
<div
class="ant-modal-body"
>
Hello World?!
</div>
<div
class="ant-modal-footer"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Cancel
</span>
</button>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
OK
</span>
</button>
</div>
</div>
<div
aria-hidden="true"
style="width:0;height:0;overflow:hidden;outline:none"
tabindex="0"
/>
</div>
`;
exports[`renders ./components/modal/demo/width.md correctly 1`] = `
<button
class="ant-btn ant-btn-primary"

@ -0,0 +1,24 @@
---
order: 99
title:
zh-CN: _DoNotUseOrYouWillBeFired
en-US: _DoNotUseOrYouWillBeFired
debug: true
---
## zh-CN
调试用组件,请勿直接使用。
## en-US
Debug usage. Do not use in your production.
```tsx
import { Modal } from 'antd';
/** Test usage. Do not use in your production. */
const { _DoNotUseOrYouWillBeFired: InternalPanel } = Modal;
export default () => <InternalPanel title="Hello World!">Hello World?!</InternalPanel>;
```

@ -1,18 +1,19 @@
import type { ModalFuncProps } from './Modal';
import OriginModal from './Modal';
import type { ModalStaticFunctions } from './confirm';
import confirm, {
withWarn,
modalGlobalConfig,
withConfirm,
withError,
withInfo,
withSuccess,
withError,
withConfirm,
modalGlobalConfig,
withWarn,
} from './confirm';
import useModal from './useModal';
import destroyFns from './destroyFns';
import type { ModalFuncProps } from './Modal';
import OriginModal from './Modal';
import PurePanel from './PurePanel';
import useModal from './useModal';
export { ModalProps, ModalFuncProps } from './Modal';
export { ModalFuncProps, ModalProps } from './Modal';
function modalWarn(props: ModalFuncProps) {
return confirm(withWarn(props));
@ -23,6 +24,8 @@ type ModalType = typeof OriginModal &
useModal: typeof useModal;
destroyAll: () => void;
config: typeof modalGlobalConfig;
/** @private Internal Component. Do not use in your production. */
_DoNotUseOrYouWillBeFired: typeof PurePanel;
};
const Modal = OriginModal as ModalType;
@ -60,4 +63,6 @@ Modal.destroyAll = function destroyAllFn() {
Modal.config = modalGlobalConfig;
Modal._DoNotUseOrYouWillBeFired = PurePanel;
export default Modal;

@ -1,9 +1,9 @@
// deps-lint-skip-all
import type React from 'react';
import type { CSSObject } from '@ant-design/cssinjs';
import type { TokenWithCommonCls } from 'antd/es/_util/theme/util/genComponentStyleHook';
import { genComponentStyleHook, mergeToken, resetComponent, clearFix } from '../../_util/theme';
import type { FullToken, GenerateStyle, AliasToken } from '../../_util/theme';
import type React from 'react';
import type { AliasToken, FullToken, GenerateStyle } from '../../_util/theme';
import { clearFix, genComponentStyleHook, mergeToken, resetComponent } from '../../_util/theme';
/** Component only token. Which will handle additional calculation of alias token */
export interface ComponentToken {
@ -79,10 +79,59 @@ export function modalMask(componentCls: string, token: TokenWithCommonCls<AliasT
const genModalStyle: GenerateStyle<ModalToken> = token => {
const { componentCls } = token;
return {
[`${componentCls}-root`]: {
...modalMask(componentCls, token),
return [
// ======================== Root =========================
{
[`${componentCls}-root`]: {
...modalMask(componentCls, token),
[`${componentCls}-wrap`]: {
zIndex: token.zIndexPopupBase,
position: 'fixed',
inset: 0,
overflow: 'auto',
outline: 0,
WebkitOverflowScrolling: 'touch',
},
[`${componentCls}-wrap-rtl`]: {
direction: 'rtl',
},
[`${componentCls}-centered`]: {
textAlign: 'center',
'&::before': {
display: 'inline-block',
width: 0,
height: '100%',
verticalAlign: 'middle',
content: '""',
},
[componentCls]: {
top: 0,
display: 'inline-block',
paddingBottom: 0,
textAlign: 'start',
verticalAlign: 'middle',
},
},
[`@media (max-width: ${token.screenSMMax})`]: {
[componentCls]: {
maxWidth: 'calc(100vw - 16px)',
margin: `${token.marginXS} auto`,
},
[`${componentCls}-centered`]: {
[componentCls]: {
flex: 1,
},
},
},
},
},
// ======================== Modal ========================
{
[componentCls]: {
...resetComponent(token),
pointerEvents: 'none',
@ -93,7 +142,7 @@ const genModalStyle: GenerateStyle<ModalToken> = token => {
margin: '0 auto',
paddingBottom: token.paddingLG,
'&-title': {
[`${componentCls}-title`]: {
margin: 0,
color: token.modalHeadingColor,
fontWeight: token.fontWeightStrong,
@ -102,7 +151,7 @@ const genModalStyle: GenerateStyle<ModalToken> = token => {
wordWrap: 'break-word',
},
'&-content': {
[`${componentCls}-content`]: {
position: 'relative',
backgroundColor: token.modalContentBg,
backgroundClip: 'padding-box',
@ -112,7 +161,7 @@ const genModalStyle: GenerateStyle<ModalToken> = token => {
pointerEvents: 'auto',
},
'&-close': {
[`${componentCls}-close`]: {
position: 'absolute',
top: 0,
insetInlineEnd: 0,
@ -146,7 +195,7 @@ const genModalStyle: GenerateStyle<ModalToken> = token => {
},
},
'&-header': {
[`${componentCls}-header`]: {
padding: token.modalHeaderPadding,
color: token.colorText,
background: token.modalHeaderBg,
@ -154,14 +203,14 @@ const genModalStyle: GenerateStyle<ModalToken> = token => {
borderRadius: `${token.controlRadius}px ${token.controlRadius}px 0 0`,
},
'&-body': {
[`${componentCls}-body`]: {
padding: token.modalBodyPadding,
fontSize: token.fontSizeBase,
lineHeight: token.lineHeight,
wordWrap: 'break-word',
},
'&-footer': {
[`${componentCls}-footer`]: {
padding: `${token.modalFooterPaddingVertical}px ${token.modalFooterPaddingHorizontal}px`,
textAlign: 'end',
background: token.modalFooterBg,
@ -174,55 +223,20 @@ const genModalStyle: GenerateStyle<ModalToken> = token => {
},
},
'&-open': {
[`${componentCls}-open`]: {
overflow: 'hidden',
},
},
},
[`${componentCls}-wrap`]: {
zIndex: token.zIndexPopupBase,
position: 'fixed',
inset: 0,
overflow: 'auto',
outline: 0,
WebkitOverflowScrolling: 'touch',
},
[`${componentCls}-wrap-rtl`]: {
direction: 'rtl',
},
[`${componentCls}-centered`]: {
textAlign: 'center',
'&::before': {
display: 'inline-block',
width: 0,
height: '100%',
verticalAlign: 'middle',
content: '""',
},
[componentCls]: {
top: 0,
display: 'inline-block',
paddingBottom: 0,
textAlign: 'start',
verticalAlign: 'middle',
},
},
[`@media (max-width: ${token.screenSMMax})`]: {
[componentCls]: {
maxWidth: 'calc(100vw - 16px)',
margin: `${token.marginXS} auto`,
},
[`${componentCls}-centered`]: {
[componentCls]: {
flex: 1,
},
},
// ======================== Pure =========================
{
[`${componentCls}-pure-panel`]: {
top: 'auto',
padding: 0,
},
},
};
];
};
const genModalConfirmStyle: GenerateStyle<ModalToken> = token => {

Loading…
Cancel
Save