mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-25 11:40:04 +08:00
Merge pull request #21226 from ant-design/feature
chore: Merge feature into master
This commit is contained in:
commit
74f01d8485
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];
|
||||||
|
}
|
@ -85,6 +85,7 @@ Form field component for data bidirectional binding, validation, layout, and so
|
|||||||
| rules | Rules for field validation. Click [here](#components-form-demo-basic) to see an example | [Rule](#Rule)[] | - |
|
| rules | Rules for field validation. Click [here](#components-form-demo-basic) to see an example | [Rule](#Rule)[] | - |
|
||||||
| shouldUpdate | Custom field update logic. See [bellow](#shouldUpdate) | boolean \| (prevValue, curValue) => boolean | false |
|
| shouldUpdate | Custom field update logic. See [bellow](#shouldUpdate) | boolean \| (prevValue, curValue) => boolean | false |
|
||||||
| trigger | When to collect the value of children node | string | onChange |
|
| trigger | When to collect the value of children node | string | onChange |
|
||||||
|
| validateFirst | Whether stop validate on first rule of error for this field | boolean | false |
|
||||||
| validateStatus | The validation status. If not provided, it will be generated by validation rule. options: 'success' 'warning' 'error' 'validating' | string | - |
|
| validateStatus | The validation status. If not provided, it will be generated by validation rule. options: 'success' 'warning' 'error' 'validating' | string | - |
|
||||||
| validateTrigger | When to validate the value of children node | string \| string[] | onChange |
|
| validateTrigger | When to validate the value of children node | string \| string[] | onChange |
|
||||||
| valuePropName | Props of children node, for example, the prop of Switch is 'checked' | string | 'value' |
|
| valuePropName | Props of children node, for example, the prop of Switch is 'checked' | string | 'value' |
|
||||||
|
@ -86,6 +86,7 @@ const validateMessages = {
|
|||||||
| rules | 校验规则,设置字段的校验逻辑。点击[此处](#components-form-demo-basic)查看示例 | [Rule](#Rule)[] | - |
|
| rules | 校验规则,设置字段的校验逻辑。点击[此处](#components-form-demo-basic)查看示例 | [Rule](#Rule)[] | - |
|
||||||
| shouldUpdate | 自定义字段更新逻辑,说明[见下](#shouldUpdate) | boolean \| (prevValue, curValue) => boolean | false |
|
| shouldUpdate | 自定义字段更新逻辑,说明[见下](#shouldUpdate) | boolean \| (prevValue, curValue) => boolean | false |
|
||||||
| trigger | 设置收集字段值变更的时机 | string | onChange |
|
| trigger | 设置收集字段值变更的时机 | string | onChange |
|
||||||
|
| validateFirst | 当某一规则校验不通过时,是否停止剩下的规则的校验 | boolean | false |
|
||||||
| validateStatus | 校验状态,如不设置,则会根据校验规则自动生成,可选:'success' 'warning' 'error' 'validating' | string | - |
|
| validateStatus | 校验状态,如不设置,则会根据校验规则自动生成,可选:'success' 'warning' 'error' 'validating' | string | - |
|
||||||
| validateTrigger | 设置字段校验的时机 | string \| string[] | onChange |
|
| validateTrigger | 设置字段校验的时机 | string \| string[] | onChange |
|
||||||
| valuePropName | 子节点的值的属性,如 Switch 的是 'checked' | string | 'value' |
|
| valuePropName | 子节点的值的属性,如 Switch 的是 'checked' | string | 'value' |
|
||||||
|
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 addEventListener from 'rc-util/lib/Dom/addEventListener';
|
||||||
import { CloseOutlined } from '@ant-design/icons';
|
import { CloseOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
import useModal from './useModal';
|
||||||
import { getConfirmLocale } from './locale';
|
import { getConfirmLocale } from './locale';
|
||||||
import Button from '../button';
|
import Button from '../button';
|
||||||
import { ButtonType, NativeButtonProps } from '../button/button';
|
import { ButtonType, NativeButtonProps } from '../button/button';
|
||||||
@ -113,13 +114,6 @@ export interface ModalFuncProps {
|
|||||||
maskTransitionName?: string;
|
maskTransitionName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ModalFunc = (
|
|
||||||
props: ModalFuncProps,
|
|
||||||
) => {
|
|
||||||
destroy: () => void;
|
|
||||||
update: (newConfig: ModalFuncProps) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface ModalLocale {
|
export interface ModalLocale {
|
||||||
okText: string;
|
okText: string;
|
||||||
cancelText: string;
|
cancelText: string;
|
||||||
@ -127,20 +121,10 @@ export interface ModalLocale {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class Modal extends React.Component<ModalProps, {}> {
|
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 destroyAll: () => void;
|
||||||
|
|
||||||
|
static useModal = useModal;
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
width: 520,
|
width: 520,
|
||||||
transitionName: 'zoom',
|
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 React from 'react';
|
||||||
import * as ReactDOM from 'react-dom';
|
import * as ReactDOM from 'react-dom';
|
||||||
import classNames from 'classnames';
|
import {
|
||||||
|
InfoCircleOutlined,
|
||||||
import Dialog, { ModalFuncProps, destroyFns } from './Modal';
|
CheckCircleOutlined,
|
||||||
import ActionButton from './ActionButton';
|
CloseCircleOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
import { getConfirmLocale } from './locale';
|
import { getConfirmLocale } from './locale';
|
||||||
import warning from '../_util/warning';
|
import { ModalFuncProps, destroyFns } from './Modal';
|
||||||
|
import ConfirmDialog from './ConfirmDialog';
|
||||||
|
|
||||||
interface ConfirmDialogProps extends ModalFuncProps {
|
export type ModalFunc = (
|
||||||
afterClose?: () => void;
|
props: ModalFuncProps,
|
||||||
close: (...args: any[]) => void;
|
) => {
|
||||||
autoFocusButton?: null | 'ok' | 'cancel';
|
destroy: () => void;
|
||||||
}
|
update: (newConfig: ModalFuncProps) => void;
|
||||||
|
|
||||||
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 interface ModalStaticFunctions {
|
||||||
|
info: ModalFunc;
|
||||||
|
success: ModalFunc;
|
||||||
|
error: ModalFunc;
|
||||||
|
warn: ModalFunc;
|
||||||
|
warning: ModalFunc;
|
||||||
|
confirm: ModalFunc;
|
||||||
|
}
|
||||||
|
|
||||||
export default function confirm(config: ModalFuncProps) {
|
export default function confirm(config: ModalFuncProps) {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
document.body.appendChild(div);
|
document.body.appendChild(div);
|
||||||
@ -145,8 +51,16 @@ export default function confirm(config: ModalFuncProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function render(props: any) {
|
function render({ okText, cancelText, ...props }: any) {
|
||||||
ReactDOM.render(<ConfirmDialog {...props} />, div);
|
const runtimeLocale = getConfirmLocale();
|
||||||
|
ReactDOM.render(
|
||||||
|
<ConfirmDialog
|
||||||
|
{...props}
|
||||||
|
okText={okText || (props.okCancel ? runtimeLocale.okText : runtimeLocale.justOkText)}
|
||||||
|
cancelText={cancelText || runtimeLocale.cancelText}
|
||||||
|
/>,
|
||||||
|
div,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function close(...args: any[]) {
|
function close(...args: any[]) {
|
||||||
@ -155,11 +69,7 @@ export default function confirm(config: ModalFuncProps) {
|
|||||||
visible: false,
|
visible: false,
|
||||||
afterClose: destroy.bind(this, ...args),
|
afterClose: destroy.bind(this, ...args),
|
||||||
};
|
};
|
||||||
if (IS_REACT_16) {
|
|
||||||
render(currentConfig);
|
render(currentConfig);
|
||||||
} else {
|
|
||||||
destroy(...args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function update(newConfig: ModalFuncProps) {
|
function update(newConfig: ModalFuncProps) {
|
||||||
@ -179,3 +89,47 @@ export default function confirm(config: ModalFuncProps) {
|
|||||||
update,
|
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.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 OriginModal, { ModalFuncProps, destroyFns } from './Modal';
|
||||||
import {
|
import confirm, {
|
||||||
InfoCircleOutlined,
|
withWarn,
|
||||||
CheckCircleOutlined,
|
withInfo,
|
||||||
CloseCircleOutlined,
|
withSuccess,
|
||||||
ExclamationCircleOutlined,
|
withError,
|
||||||
} from '@ant-design/icons';
|
withConfirm,
|
||||||
|
ModalStaticFunctions,
|
||||||
import Modal, { ModalFuncProps, destroyFns } from './Modal';
|
} from './confirm';
|
||||||
import confirm from './confirm';
|
|
||||||
|
|
||||||
export { ActionButtonProps } from './ActionButton';
|
export { ActionButtonProps } from './ActionButton';
|
||||||
export { ModalProps, ModalFuncProps } from './Modal';
|
export { ModalProps, ModalFuncProps } from './Modal';
|
||||||
|
|
||||||
function modalWarn(props: ModalFuncProps) {
|
function modalWarn(props: ModalFuncProps) {
|
||||||
const config = {
|
return confirm(withWarn(props));
|
||||||
type: 'warning',
|
|
||||||
icon: <ExclamationCircleOutlined />,
|
|
||||||
okCancel: false,
|
|
||||||
...props,
|
|
||||||
};
|
|
||||||
return confirm(config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Modal = typeof OriginModal & ModalStaticFunctions;
|
||||||
|
const Modal = OriginModal as Modal;
|
||||||
|
|
||||||
Modal.info = function infoFn(props: ModalFuncProps) {
|
Modal.info = function infoFn(props: ModalFuncProps) {
|
||||||
const config = {
|
return confirm(withInfo(props));
|
||||||
type: 'info',
|
|
||||||
icon: <InfoCircleOutlined />,
|
|
||||||
okCancel: false,
|
|
||||||
...props,
|
|
||||||
};
|
|
||||||
return confirm(config);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Modal.success = function successFn(props: ModalFuncProps) {
|
Modal.success = function successFn(props: ModalFuncProps) {
|
||||||
const config = {
|
return confirm(withSuccess(props));
|
||||||
type: 'success',
|
|
||||||
icon: <CheckCircleOutlined />,
|
|
||||||
okCancel: false,
|
|
||||||
...props,
|
|
||||||
};
|
|
||||||
return confirm(config);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Modal.error = function errorFn(props: ModalFuncProps) {
|
Modal.error = function errorFn(props: ModalFuncProps) {
|
||||||
const config = {
|
return confirm(withError(props));
|
||||||
type: 'error',
|
|
||||||
icon: <CloseCircleOutlined />,
|
|
||||||
okCancel: false,
|
|
||||||
...props,
|
|
||||||
};
|
|
||||||
return confirm(config);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Modal.warning = modalWarn;
|
Modal.warning = modalWarn;
|
||||||
@ -57,12 +35,7 @@ Modal.warning = modalWarn;
|
|||||||
Modal.warn = modalWarn;
|
Modal.warn = modalWarn;
|
||||||
|
|
||||||
Modal.confirm = function confirmFn(props: ModalFuncProps) {
|
Modal.confirm = function confirmFn(props: ModalFuncProps) {
|
||||||
const config = {
|
return confirm(withConfirm(props));
|
||||||
type: 'confirm',
|
|
||||||
okCancel: true,
|
|
||||||
...props,
|
|
||||||
};
|
|
||||||
return confirm(config);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Modal.destroyAll = function destroyAllFn() {
|
Modal.destroyAll = function destroyAllFn() {
|
||||||
|
@ -111,3 +111,19 @@ browserHistory.listen(() => {
|
|||||||
Modal.destroyAll();
|
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}</>,
|
||||||
|
];
|
||||||
|
}
|
@ -1253,6 +1253,401 @@ exports[`renders ./components/transfer/demo/custom-item.md correctly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`renders ./components/transfer/demo/custom-select-all-labels.md correctly 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-transfer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-transfer-list"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-transfer-list-header"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="ant-checkbox-wrapper"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="ant-checkbox-input"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox-inner"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<span
|
||||||
|
class="ant-transfer-list-header-selected"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Select All
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="ant-transfer-list-header-title"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-transfer-list-body"
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
class="ant-transfer-list-content"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="ant-transfer-list-content-item"
|
||||||
|
title="content1"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="ant-checkbox-wrapper"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="ant-checkbox-input"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox-inner"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<span
|
||||||
|
class="ant-transfer-list-content-item-text"
|
||||||
|
>
|
||||||
|
content1
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ant-transfer-list-content-item"
|
||||||
|
title="content2"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="ant-checkbox-wrapper"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="ant-checkbox-input"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox-inner"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<span
|
||||||
|
class="ant-transfer-list-content-item-text"
|
||||||
|
>
|
||||||
|
content2
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ant-transfer-list-content-item"
|
||||||
|
title="content4"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="ant-checkbox-wrapper"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="ant-checkbox-input"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox-inner"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<span
|
||||||
|
class="ant-transfer-list-content-item-text"
|
||||||
|
>
|
||||||
|
content4
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ant-transfer-list-content-item"
|
||||||
|
title="content5"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="ant-checkbox-wrapper"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="ant-checkbox-input"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox-inner"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<span
|
||||||
|
class="ant-transfer-list-content-item-text"
|
||||||
|
>
|
||||||
|
content5
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ant-transfer-list-content-item"
|
||||||
|
title="content7"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="ant-checkbox-wrapper"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="ant-checkbox-input"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox-inner"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<span
|
||||||
|
class="ant-transfer-list-content-item-text"
|
||||||
|
>
|
||||||
|
content7
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ant-transfer-list-content-item"
|
||||||
|
title="content8"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="ant-checkbox-wrapper"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="ant-checkbox-input"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox-inner"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<span
|
||||||
|
class="ant-transfer-list-content-item-text"
|
||||||
|
>
|
||||||
|
content8
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ant-transfer-list-content-item"
|
||||||
|
title="content10"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="ant-checkbox-wrapper"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="ant-checkbox-input"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox-inner"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<span
|
||||||
|
class="ant-transfer-list-content-item-text"
|
||||||
|
>
|
||||||
|
content10
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-transfer-operation"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only"
|
||||||
|
disabled=""
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="right"
|
||||||
|
class="anticon anticon-right"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class=""
|
||||||
|
data-icon="right"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only"
|
||||||
|
disabled=""
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="left"
|
||||||
|
class="anticon anticon-left"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class=""
|
||||||
|
data-icon="left"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-transfer-list"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-transfer-list-header"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="ant-checkbox-wrapper"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="ant-checkbox-input"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox-inner"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<span
|
||||||
|
class="ant-transfer-list-header-selected"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
0/3
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="ant-transfer-list-header-title"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-transfer-list-body"
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
class="ant-transfer-list-content"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="ant-transfer-list-content-item"
|
||||||
|
title="content3"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="ant-checkbox-wrapper"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="ant-checkbox-input"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox-inner"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<span
|
||||||
|
class="ant-transfer-list-content-item-text"
|
||||||
|
>
|
||||||
|
content3
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ant-transfer-list-content-item"
|
||||||
|
title="content6"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="ant-checkbox-wrapper"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="ant-checkbox-input"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox-inner"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<span
|
||||||
|
class="ant-transfer-list-content-item-text"
|
||||||
|
>
|
||||||
|
content6
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="ant-transfer-list-content-item"
|
||||||
|
title="content9"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="ant-checkbox-wrapper"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="ant-checkbox-input"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ant-checkbox-inner"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<span
|
||||||
|
class="ant-transfer-list-content-item-text"
|
||||||
|
>
|
||||||
|
content9
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/transfer/demo/large-data.md correctly 1`] = `
|
exports[`renders ./components/transfer/demo/large-data.md correctly 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ant-transfer"
|
class="ant-transfer"
|
||||||
|
@ -519,4 +519,22 @@ describe('Transfer', () => {
|
|||||||
);
|
);
|
||||||
expect(component).toMatchSnapshot();
|
expect(component).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render correct checkbox label when checkboxLabel is defined', () => {
|
||||||
|
const selectAllLabels = ['Checkbox Label'];
|
||||||
|
const wrapper = mount(<Transfer {...listCommonProps} selectAllLabels={selectAllLabels} />);
|
||||||
|
expect(headerText(wrapper)).toEqual('Checkbox Label');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render correct checkbox label when checkboxLabel is a function', () => {
|
||||||
|
const selectAllLabels = [
|
||||||
|
({ selectedCount, totalCount }) => (
|
||||||
|
<span>
|
||||||
|
{selectedCount} of {totalCount}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
];
|
||||||
|
const wrapper = mount(<Transfer {...listCommonProps} selectAllLabels={selectAllLabels} />);
|
||||||
|
expect(headerText(wrapper)).toEqual('1 of 2');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
51
components/transfer/demo/custom-select-all-labels.md
Normal file
51
components/transfer/demo/custom-select-all-labels.md
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
order: 99
|
||||||
|
debug: true
|
||||||
|
title:
|
||||||
|
zh-CN: 自定义全选文字
|
||||||
|
en-US: Custom Select All Labels
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
自定义穿梭框全选按钮的文字。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Custom the labels for select all checkboxs.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Transfer } from 'antd';
|
||||||
|
|
||||||
|
const mockData = [];
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
mockData.push({
|
||||||
|
key: i.toString(),
|
||||||
|
title: `content${i + 1}`,
|
||||||
|
description: `description of content${i + 1}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const oriTargetKeys = mockData.filter(item => +item.key % 3 > 1).map(item => item.key);
|
||||||
|
|
||||||
|
const selectAllLabels = [
|
||||||
|
'Select All',
|
||||||
|
({ selectedCount, totalCount }) => `${selectedCount}/${totalCount}`,
|
||||||
|
];
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
const [targetKeys, setTargetKeys] = useState(oriTargetKeys);
|
||||||
|
return (
|
||||||
|
<Transfer
|
||||||
|
dataSource={mockData}
|
||||||
|
targetKeys={targetKeys}
|
||||||
|
onChange={setTargetKeys}
|
||||||
|
render={item => item.title}
|
||||||
|
selectAllLabels={selectAllLabels}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ReactDOM.render(<App />, mountNode);
|
||||||
|
```
|
@ -36,6 +36,7 @@ One or more elements can be selected from either column, one click on the proper
|
|||||||
| style | A custom CSS style used for rendering wrapper element. | object | | |
|
| style | A custom CSS style used for rendering wrapper element. | object | | |
|
||||||
| targetKeys | A set of keys of elements that are listed on the right column. | string\[] | \[] | |
|
| targetKeys | A set of keys of elements that are listed on the right column. | string\[] | \[] | |
|
||||||
| titles | A set of titles that are sorted from left to right. | ReactNode\[] | - | |
|
| titles | A set of titles that are sorted from left to right. | ReactNode\[] | - | |
|
||||||
|
| selectAllLabels | A set of customized labels for select all checkboxs on the header | (ReactNode \| (info: { selectedCount: number, totalCount: number }) => ReactNode)[] | | |
|
||||||
| onChange | A callback function that is executed when the transfer between columns is complete. | (targetKeys, direction, moveKeys): void | | |
|
| onChange | A callback function that is executed when the transfer between columns is complete. | (targetKeys, direction, moveKeys): void | | |
|
||||||
| onScroll | A callback function which is executed when scroll options list | (direction, event): void | | |
|
| onScroll | A callback function which is executed when scroll options list | (direction, event): void | | |
|
||||||
| onSearch | A callback function which is executed when search field are changed | (direction: 'left'\|'right', value: string): void | - | |
|
| onSearch | A callback function which is executed when search field are changed | (direction: 'left'\|'right', value: string): void | - | |
|
||||||
|
@ -35,6 +35,10 @@ export interface ListStyle {
|
|||||||
direction: TransferDirection;
|
direction: TransferDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SelectAllLabel =
|
||||||
|
| React.ReactNode
|
||||||
|
| ((info: { selectedCount: number; totalCount: number }) => React.ReactNode);
|
||||||
|
|
||||||
export interface TransferProps {
|
export interface TransferProps {
|
||||||
prefixCls?: string;
|
prefixCls?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -59,6 +63,7 @@ export interface TransferProps {
|
|||||||
onScroll?: (direction: TransferDirection, e: React.SyntheticEvent<HTMLUListElement>) => void;
|
onScroll?: (direction: TransferDirection, e: React.SyntheticEvent<HTMLUListElement>) => void;
|
||||||
children?: (props: TransferListBodyProps) => React.ReactNode;
|
children?: (props: TransferListBodyProps) => React.ReactNode;
|
||||||
showSelectAll?: boolean;
|
showSelectAll?: boolean;
|
||||||
|
selectAllLabels?: SelectAllLabel[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransferLocale {
|
export interface TransferLocale {
|
||||||
@ -332,6 +337,7 @@ class Transfer extends React.Component<TransferProps, any> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const titles = this.props.titles || locale.titles;
|
const titles = this.props.titles || locale.titles;
|
||||||
|
const selectAllLabels = this.props.selectAllLabels || [];
|
||||||
return (
|
return (
|
||||||
<div className={cls} style={style}>
|
<div className={cls} style={style}>
|
||||||
<List
|
<List
|
||||||
@ -353,6 +359,7 @@ class Transfer extends React.Component<TransferProps, any> {
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
direction="left"
|
direction="left"
|
||||||
showSelectAll={showSelectAll}
|
showSelectAll={showSelectAll}
|
||||||
|
selectAllLabel={selectAllLabels[0]}
|
||||||
{...locale}
|
{...locale}
|
||||||
/>
|
/>
|
||||||
<Operation
|
<Operation
|
||||||
@ -386,6 +393,7 @@ class Transfer extends React.Component<TransferProps, any> {
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
direction="right"
|
direction="right"
|
||||||
showSelectAll={showSelectAll}
|
showSelectAll={showSelectAll}
|
||||||
|
selectAllLabel={selectAllLabels[1]}
|
||||||
{...locale}
|
{...locale}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -38,6 +38,7 @@ title: Transfer
|
|||||||
| style | 容器的自定义样式 | object | | |
|
| style | 容器的自定义样式 | object | | |
|
||||||
| targetKeys | 显示在右侧框数据的 key 集合 | string\[] | \[] | |
|
| targetKeys | 显示在右侧框数据的 key 集合 | string\[] | \[] | |
|
||||||
| titles | 标题集合,顺序从左至右 | ReactNode\[] | \['', ''] | |
|
| titles | 标题集合,顺序从左至右 | ReactNode\[] | \['', ''] | |
|
||||||
|
| selectAllLabels | 自定义顶部多选框标题的集合 | (ReactNode \| (info: { selectedCount: number, totalCount: number }) => ReactNode)[] | | |
|
||||||
| onChange | 选项在两栏之间转移时的回调函数 | (targetKeys, direction, moveKeys): void | | |
|
| onChange | 选项在两栏之间转移时的回调函数 | (targetKeys, direction, moveKeys): void | | |
|
||||||
| onScroll | 选项列表滚动时的回调函数 | (direction, event): void | | |
|
| onScroll | 选项列表滚动时的回调函数 | (direction, event): void | | |
|
||||||
| onSearch | 搜索框内容时改变时的回调函数 | (direction: 'left'\|'right', value: string): void | - | |
|
| onSearch | 搜索框内容时改变时的回调函数 | (direction: 'left'\|'right', value: string): void | - | |
|
||||||
|
@ -3,7 +3,13 @@ import omit from 'omit.js';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import PureRenderMixin from 'rc-util/lib/PureRenderMixin';
|
import PureRenderMixin from 'rc-util/lib/PureRenderMixin';
|
||||||
import Checkbox from '../checkbox';
|
import Checkbox from '../checkbox';
|
||||||
import { TransferItem, TransferDirection, RenderResult, RenderResultObject } from './index';
|
import {
|
||||||
|
TransferItem,
|
||||||
|
TransferDirection,
|
||||||
|
RenderResult,
|
||||||
|
RenderResultObject,
|
||||||
|
SelectAllLabel,
|
||||||
|
} from './index';
|
||||||
import Search from './search';
|
import Search from './search';
|
||||||
import defaultRenderList, { TransferListBodyProps, OmitProps } from './renderListBody';
|
import defaultRenderList, { TransferListBodyProps, OmitProps } from './renderListBody';
|
||||||
|
|
||||||
@ -48,6 +54,7 @@ export interface TransferListProps {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
direction: TransferDirection;
|
direction: TransferDirection;
|
||||||
showSelectAll?: boolean;
|
showSelectAll?: boolean;
|
||||||
|
selectAllLabel?: SelectAllLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TransferListState {
|
interface TransferListState {
|
||||||
@ -246,6 +253,21 @@ export default class TransferList extends React.Component<TransferListProps, Tra
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getSelectAllLabel = (selectedCount: number, totalCount: number): React.ReactNode => {
|
||||||
|
const { itemsUnit, itemUnit, selectAllLabel } = this.props;
|
||||||
|
if (selectAllLabel) {
|
||||||
|
return typeof selectAllLabel === 'function'
|
||||||
|
? selectAllLabel({ selectedCount, totalCount })
|
||||||
|
: selectAllLabel;
|
||||||
|
}
|
||||||
|
const unit = totalCount > 1 ? itemsUnit : itemUnit;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{(selectedCount > 0 ? `${selectedCount}/` : '') + totalCount} {unit}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { filterValue } = this.state;
|
const { filterValue } = this.state;
|
||||||
const {
|
const {
|
||||||
@ -259,8 +281,6 @@ export default class TransferList extends React.Component<TransferListProps, Tra
|
|||||||
style,
|
style,
|
||||||
searchPlaceholder,
|
searchPlaceholder,
|
||||||
notFoundContent,
|
notFoundContent,
|
||||||
itemUnit,
|
|
||||||
itemsUnit,
|
|
||||||
renderList,
|
renderList,
|
||||||
onItemSelectAll,
|
onItemSelectAll,
|
||||||
showSelectAll,
|
showSelectAll,
|
||||||
@ -278,7 +298,6 @@ export default class TransferList extends React.Component<TransferListProps, Tra
|
|||||||
const { filteredItems, filteredRenderItems } = this.getFilteredItems(dataSource, filterValue);
|
const { filteredItems, filteredRenderItems } = this.getFilteredItems(dataSource, filterValue);
|
||||||
|
|
||||||
// ================================= List Body =================================
|
// ================================= List Body =================================
|
||||||
const unit = filteredItems.length > 1 ? itemsUnit : itemUnit;
|
|
||||||
|
|
||||||
const listBody = this.getListBody(
|
const listBody = this.getListBody(
|
||||||
prefixCls,
|
prefixCls,
|
||||||
@ -310,10 +329,7 @@ export default class TransferList extends React.Component<TransferListProps, Tra
|
|||||||
<div className={`${prefixCls}-header`}>
|
<div className={`${prefixCls}-header`}>
|
||||||
{checkAllCheckbox}
|
{checkAllCheckbox}
|
||||||
<span className={`${prefixCls}-header-selected`}>
|
<span className={`${prefixCls}-header-selected`}>
|
||||||
<span>
|
<span>{this.getSelectAllLabel(checkedKeys.length, filteredItems.length)}</span>
|
||||||
{(checkedKeys.length > 0 ? `${checkedKeys.length}/` : '') + filteredItems.length}{' '}
|
|
||||||
{unit}
|
|
||||||
</span>
|
|
||||||
<span className={`${prefixCls}-header-title`}>{titleText}</span>
|
<span className={`${prefixCls}-header-title`}>{titleText}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -108,7 +108,7 @@
|
|||||||
"rc-dialog": "~7.6.0",
|
"rc-dialog": "~7.6.0",
|
||||||
"rc-drawer": "~3.1.1",
|
"rc-drawer": "~3.1.1",
|
||||||
"rc-dropdown": "~3.0.0-alpha.0",
|
"rc-dropdown": "~3.0.0-alpha.0",
|
||||||
"rc-field-form": "^0.0.0-rc.0",
|
"rc-field-form": "^0.0.0-rc.1",
|
||||||
"rc-input-number": "~4.5.0",
|
"rc-input-number": "~4.5.0",
|
||||||
"rc-mentions": "~1.0.0-alpha.3",
|
"rc-mentions": "~1.0.0-alpha.3",
|
||||||
"rc-menu": "~8.0.0-alpha.7",
|
"rc-menu": "~8.0.0-alpha.7",
|
||||||
|
Loading…
Reference in New Issue
Block a user