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)[] | - |
|
||||
| shouldUpdate | Custom field update logic. See [bellow](#shouldUpdate) | boolean \| (prevValue, curValue) => boolean | false |
|
||||
| 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 | - |
|
||||
| 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' |
|
||||
|
@ -86,6 +86,7 @@ const validateMessages = {
|
||||
| rules | 校验规则,设置字段的校验逻辑。点击[此处](#components-form-demo-basic)查看示例 | [Rule](#Rule)[] | - |
|
||||
| shouldUpdate | 自定义字段更新逻辑,说明[见下](#shouldUpdate) | boolean \| (prevValue, curValue) => boolean | false |
|
||||
| trigger | 设置收集字段值变更的时机 | string | onChange |
|
||||
| validateFirst | 当某一规则校验不通过时,是否停止剩下的规则的校验 | boolean | false |
|
||||
| validateStatus | 校验状态,如不设置,则会根据校验规则自动生成,可选:'success' 'warning' 'error' 'validating' | string | - |
|
||||
| validateTrigger | 设置字段校验的时机 | string \| string[] | onChange |
|
||||
| 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 { 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);
|
||||
}
|
||||
}
|
||||
|
||||
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}</>,
|
||||
];
|
||||
}
|
@ -1253,6 +1253,401 @@ exports[`renders ./components/transfer/demo/custom-item.md correctly 1`] = `
|
||||
</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`] = `
|
||||
<div
|
||||
class="ant-transfer"
|
||||
|
@ -519,4 +519,22 @@ describe('Transfer', () => {
|
||||
);
|
||||
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 | | |
|
||||
| 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\[] | - | |
|
||||
| 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 | | |
|
||||
| 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 | - | |
|
||||
|
@ -35,6 +35,10 @@ export interface ListStyle {
|
||||
direction: TransferDirection;
|
||||
}
|
||||
|
||||
export type SelectAllLabel =
|
||||
| React.ReactNode
|
||||
| ((info: { selectedCount: number; totalCount: number }) => React.ReactNode);
|
||||
|
||||
export interface TransferProps {
|
||||
prefixCls?: string;
|
||||
className?: string;
|
||||
@ -59,6 +63,7 @@ export interface TransferProps {
|
||||
onScroll?: (direction: TransferDirection, e: React.SyntheticEvent<HTMLUListElement>) => void;
|
||||
children?: (props: TransferListBodyProps) => React.ReactNode;
|
||||
showSelectAll?: boolean;
|
||||
selectAllLabels?: SelectAllLabel[];
|
||||
}
|
||||
|
||||
export interface TransferLocale {
|
||||
@ -332,6 +337,7 @@ class Transfer extends React.Component<TransferProps, any> {
|
||||
});
|
||||
|
||||
const titles = this.props.titles || locale.titles;
|
||||
const selectAllLabels = this.props.selectAllLabels || [];
|
||||
return (
|
||||
<div className={cls} style={style}>
|
||||
<List
|
||||
@ -353,6 +359,7 @@ class Transfer extends React.Component<TransferProps, any> {
|
||||
disabled={disabled}
|
||||
direction="left"
|
||||
showSelectAll={showSelectAll}
|
||||
selectAllLabel={selectAllLabels[0]}
|
||||
{...locale}
|
||||
/>
|
||||
<Operation
|
||||
@ -386,6 +393,7 @@ class Transfer extends React.Component<TransferProps, any> {
|
||||
disabled={disabled}
|
||||
direction="right"
|
||||
showSelectAll={showSelectAll}
|
||||
selectAllLabel={selectAllLabels[1]}
|
||||
{...locale}
|
||||
/>
|
||||
</div>
|
||||
|
@ -38,6 +38,7 @@ title: Transfer
|
||||
| style | 容器的自定义样式 | object | | |
|
||||
| targetKeys | 显示在右侧框数据的 key 集合 | string\[] | \[] | |
|
||||
| titles | 标题集合,顺序从左至右 | ReactNode\[] | \['', ''] | |
|
||||
| selectAllLabels | 自定义顶部多选框标题的集合 | (ReactNode \| (info: { selectedCount: number, totalCount: number }) => ReactNode)[] | | |
|
||||
| onChange | 选项在两栏之间转移时的回调函数 | (targetKeys, direction, moveKeys): void | | |
|
||||
| onScroll | 选项列表滚动时的回调函数 | (direction, event): void | | |
|
||||
| onSearch | 搜索框内容时改变时的回调函数 | (direction: 'left'\|'right', value: string): void | - | |
|
||||
|
@ -3,7 +3,13 @@ import omit from 'omit.js';
|
||||
import classNames from 'classnames';
|
||||
import PureRenderMixin from 'rc-util/lib/PureRenderMixin';
|
||||
import Checkbox from '../checkbox';
|
||||
import { TransferItem, TransferDirection, RenderResult, RenderResultObject } from './index';
|
||||
import {
|
||||
TransferItem,
|
||||
TransferDirection,
|
||||
RenderResult,
|
||||
RenderResultObject,
|
||||
SelectAllLabel,
|
||||
} from './index';
|
||||
import Search from './search';
|
||||
import defaultRenderList, { TransferListBodyProps, OmitProps } from './renderListBody';
|
||||
|
||||
@ -48,6 +54,7 @@ export interface TransferListProps {
|
||||
disabled?: boolean;
|
||||
direction: TransferDirection;
|
||||
showSelectAll?: boolean;
|
||||
selectAllLabel?: SelectAllLabel;
|
||||
}
|
||||
|
||||
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() {
|
||||
const { filterValue } = this.state;
|
||||
const {
|
||||
@ -259,8 +281,6 @@ export default class TransferList extends React.Component<TransferListProps, Tra
|
||||
style,
|
||||
searchPlaceholder,
|
||||
notFoundContent,
|
||||
itemUnit,
|
||||
itemsUnit,
|
||||
renderList,
|
||||
onItemSelectAll,
|
||||
showSelectAll,
|
||||
@ -278,7 +298,6 @@ export default class TransferList extends React.Component<TransferListProps, Tra
|
||||
const { filteredItems, filteredRenderItems } = this.getFilteredItems(dataSource, filterValue);
|
||||
|
||||
// ================================= List Body =================================
|
||||
const unit = filteredItems.length > 1 ? itemsUnit : itemUnit;
|
||||
|
||||
const listBody = this.getListBody(
|
||||
prefixCls,
|
||||
@ -310,10 +329,7 @@ export default class TransferList extends React.Component<TransferListProps, Tra
|
||||
<div className={`${prefixCls}-header`}>
|
||||
{checkAllCheckbox}
|
||||
<span className={`${prefixCls}-header-selected`}>
|
||||
<span>
|
||||
{(checkedKeys.length > 0 ? `${checkedKeys.length}/` : '') + filteredItems.length}{' '}
|
||||
{unit}
|
||||
</span>
|
||||
<span>{this.getSelectAllLabel(checkedKeys.length, filteredItems.length)}</span>
|
||||
<span className={`${prefixCls}-header-title`}>{titleText}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -108,7 +108,7 @@
|
||||
"rc-dialog": "~7.6.0",
|
||||
"rc-drawer": "~3.1.1",
|
||||
"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-mentions": "~1.0.0-alpha.3",
|
||||
"rc-menu": "~8.0.0-alpha.7",
|
||||
|
Loading…
Reference in New Issue
Block a user