mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-27 20:49:53 +08:00
Merge branch 'feature-3.6.0'
This commit is contained in:
commit
e25323882a
@ -2,6 +2,7 @@ import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import intersperse from 'intersperse';
|
||||
import Animate from 'rc-animate';
|
||||
import Row from '../grid/row';
|
||||
import Col, { ColProps } from '../grid/col';
|
||||
@ -65,15 +66,20 @@ export default class FormItem extends React.Component<FormItemProps, any> {
|
||||
);
|
||||
}
|
||||
|
||||
getHelpMsg() {
|
||||
const props = this.props;
|
||||
const onlyControl = this.getOnlyControl();
|
||||
if (props.help === undefined && onlyControl) {
|
||||
getHelpMessage() {
|
||||
const { help } = this.props;
|
||||
if (help === undefined && this.getOnlyControl()) {
|
||||
const errors = this.getField().errors;
|
||||
return errors ? errors.map((e: any) => e.message).join(', ') : '';
|
||||
if (errors) {
|
||||
return intersperse(errors.map((e: any, index: number) => (
|
||||
React.isValidElement(e.message)
|
||||
? React.cloneElement(e.message, { key: index })
|
||||
: e.message
|
||||
)), ' ');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
return props.help;
|
||||
return help;
|
||||
}
|
||||
|
||||
getControls(children: React.ReactNode, recursively: boolean) {
|
||||
@ -132,7 +138,7 @@ export default class FormItem extends React.Component<FormItemProps, any> {
|
||||
|
||||
renderHelp() {
|
||||
const prefixCls = this.props.prefixCls;
|
||||
const help = this.getHelpMsg();
|
||||
const help = this.getHelpMessage();
|
||||
const children = help ? (
|
||||
<div className={`${prefixCls}-explain`} key="help">
|
||||
{help}
|
||||
|
100
components/form/__tests__/__snapshots__/message.test.js.snap
Normal file
100
components/form/__tests__/__snapshots__/message.test.js.snap
Normal file
@ -0,0 +1,100 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Form should display custom message 1`] = `
|
||||
<form
|
||||
class="ant-form ant-form-horizontal"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item ant-form-item-with-help"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-label"
|
||||
>
|
||||
<label
|
||||
class=""
|
||||
for="account"
|
||||
title="Account"
|
||||
>
|
||||
Account
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ant-form-item-control-wrapper"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control has-error"
|
||||
>
|
||||
<span
|
||||
class="ant-form-item-children"
|
||||
>
|
||||
<input
|
||||
data-__field="[object Object]"
|
||||
data-__meta="[object Object]"
|
||||
id="account"
|
||||
value="antd"
|
||||
/>
|
||||
</span>
|
||||
<div
|
||||
class="ant-form-explain show-help-enter"
|
||||
>
|
||||
<span>
|
||||
Account does not exist,
|
||||
<a
|
||||
href="https://www.alipay.com/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Forgot account?
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
|
||||
exports[`Form should display two message 1`] = `
|
||||
<form
|
||||
class="ant-form ant-form-horizontal"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item ant-form-item-with-help"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-label"
|
||||
>
|
||||
<label
|
||||
class=""
|
||||
for="account"
|
||||
title="Account"
|
||||
>
|
||||
Account
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ant-form-item-control-wrapper"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control has-error"
|
||||
>
|
||||
<span
|
||||
class="ant-form-item-children"
|
||||
>
|
||||
<input
|
||||
data-__field="[object Object]"
|
||||
data-__meta="[object Object]"
|
||||
id="account"
|
||||
value="+=-/"
|
||||
/>
|
||||
</span>
|
||||
<div
|
||||
class="ant-form-explain show-help-enter"
|
||||
>
|
||||
Error message 1 Error message 2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
56
components/form/__tests__/message.test.js
Normal file
56
components/form/__tests__/message.test.js
Normal file
@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import Form from '..';
|
||||
|
||||
describe('Form', () => {
|
||||
it('should display two message', () => {
|
||||
const rules = [{
|
||||
pattern: /^\w+$/,
|
||||
message: 'Error message 1',
|
||||
}, {
|
||||
pattern: /^\w+$/,
|
||||
message: 'Error message 2',
|
||||
}];
|
||||
let myForm;
|
||||
const Form1 = Form.create()(({ form }) => {
|
||||
myForm = form;
|
||||
return (
|
||||
<Form>
|
||||
<Form.Item label="Account">
|
||||
{form.getFieldDecorator('account', { initialValue: '+=-/', rules })(<input />)}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
});
|
||||
|
||||
const wrapper = mount(<Form1 />);
|
||||
myForm.validateFields();
|
||||
|
||||
wrapper.update();
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should display custom message', () => {
|
||||
const rules = [{
|
||||
pattern: /^$/,
|
||||
message: (<span>Account does not exist, <a rel="noopener noreferrer" href="https://www.alipay.com/" target="_blank">Forgot account?</a></span>),
|
||||
}];
|
||||
let myForm;
|
||||
const Form1 = Form.create()(({ form }) => {
|
||||
myForm = form;
|
||||
return (
|
||||
<Form>
|
||||
<Form.Item label="Account">
|
||||
{form.getFieldDecorator('account', { initialValue: 'antd', rules })(<input />)}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
});
|
||||
|
||||
const wrapper = mount(<Form1 />);
|
||||
myForm.validateFields();
|
||||
|
||||
wrapper.update();
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -147,7 +147,7 @@ Note:
|
||||
| enum | validate a value from a list of possible values | string | - |
|
||||
| len | validate an exact length of a field | number | - |
|
||||
| max | validate a max length of a field | number | - |
|
||||
| message | validation error message | string | - |
|
||||
| message | validation error message | string\|ReactNode | - |
|
||||
| min | validate a min length of a field | number | - |
|
||||
| pattern | validate from a regular expression | RegExp | - |
|
||||
| required | indicates whether field is required | boolean | `false` |
|
||||
|
@ -148,7 +148,7 @@ CustomizedForm = Form.create({})(CustomizedForm);
|
||||
| enum | 枚举类型 | string | - |
|
||||
| len | 字段长度 | number | - |
|
||||
| max | 最大长度 | number | - |
|
||||
| message | 校验文案 | string | - |
|
||||
| message | 校验文案 | string\|ReactNode | - |
|
||||
| min | 最小长度 | number | - |
|
||||
| pattern | 正则表达式校验 | RegExp | - |
|
||||
| required | 是否必选 | boolean | `false` |
|
||||
|
@ -31,6 +31,8 @@ const dimensionMap = {
|
||||
|
||||
export type CollapseType = 'clickTrigger' | 'responsive';
|
||||
|
||||
export type SiderTheme = 'light' | 'dark';
|
||||
|
||||
export interface SiderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
prefixCls?: string;
|
||||
collapsible?: boolean;
|
||||
@ -42,6 +44,7 @@ export interface SiderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
width?: number | string;
|
||||
collapsedWidth?: number | string;
|
||||
breakpoint?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
|
||||
theme?: SiderTheme;
|
||||
}
|
||||
|
||||
export interface SiderState {
|
||||
@ -73,6 +76,7 @@ export default class Sider extends React.Component<SiderProps, SiderState> {
|
||||
width: 200,
|
||||
collapsedWidth: 80,
|
||||
style: {},
|
||||
theme: 'dark' as SiderTheme,
|
||||
};
|
||||
|
||||
static childContextTypes = {
|
||||
@ -174,7 +178,7 @@ export default class Sider extends React.Component<SiderProps, SiderState> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { prefixCls, className,
|
||||
const { prefixCls, className, theme,
|
||||
collapsible, reverseArrow, trigger, style, width, collapsedWidth,
|
||||
...others,
|
||||
} = this.props;
|
||||
@ -210,7 +214,7 @@ export default class Sider extends React.Component<SiderProps, SiderState> {
|
||||
minWidth: siderWidth, // https://github.com/ant-design/ant-design/issues/6349
|
||||
width: siderWidth,
|
||||
};
|
||||
const siderCls = classNames(className, prefixCls, {
|
||||
const siderCls = classNames(className, prefixCls, `${prefixCls}-${theme}`, {
|
||||
[`${prefixCls}-collapsed`]: !!this.state.collapsed,
|
||||
[`${prefixCls}-has-trigger`]: collapsible && trigger !== null && !zeroWidthTrigger,
|
||||
[`${prefixCls}-below`]: !!this.state.below,
|
||||
|
@ -33,7 +33,7 @@ exports[`renders ./components/layout/demo/basic.md correctly 1`] = `
|
||||
class="ant-layout"
|
||||
>
|
||||
<div
|
||||
class="ant-layout-sider"
|
||||
class="ant-layout-sider ant-layout-sider-dark"
|
||||
style="flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
|
||||
>
|
||||
<div
|
||||
@ -71,7 +71,7 @@ exports[`renders ./components/layout/demo/basic.md correctly 1`] = `
|
||||
Content
|
||||
</div>
|
||||
<div
|
||||
class="ant-layout-sider"
|
||||
class="ant-layout-sider ant-layout-sider-dark"
|
||||
style="flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
|
||||
>
|
||||
<div
|
||||
@ -91,7 +91,7 @@ exports[`renders ./components/layout/demo/basic.md correctly 1`] = `
|
||||
class="ant-layout"
|
||||
>
|
||||
<div
|
||||
class="ant-layout-sider"
|
||||
class="ant-layout-sider ant-layout-sider-dark"
|
||||
style="flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
|
||||
>
|
||||
<div
|
||||
@ -128,7 +128,7 @@ exports[`renders ./components/layout/demo/custom-trigger.md correctly 1`] = `
|
||||
class="ant-layout"
|
||||
>
|
||||
<div
|
||||
class="ant-layout-sider"
|
||||
class="ant-layout-sider ant-layout-sider-dark"
|
||||
style="flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
|
||||
>
|
||||
<div
|
||||
@ -302,7 +302,7 @@ exports[`renders ./components/layout/demo/fixed-sider.md correctly 1`] = `
|
||||
class="ant-layout"
|
||||
>
|
||||
<div
|
||||
class="ant-layout-sider"
|
||||
class="ant-layout-sider ant-layout-sider-dark"
|
||||
style="overflow:auto;height:100vh;position:fixed;left:0;flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
|
||||
>
|
||||
<div
|
||||
@ -557,7 +557,7 @@ exports[`renders ./components/layout/demo/responsive.md correctly 1`] = `
|
||||
class="ant-layout"
|
||||
>
|
||||
<div
|
||||
class="ant-layout-sider"
|
||||
class="ant-layout-sider ant-layout-sider-dark"
|
||||
style="flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
|
||||
>
|
||||
<div
|
||||
@ -662,7 +662,7 @@ exports[`renders ./components/layout/demo/side.md correctly 1`] = `
|
||||
style="min-height:100vh"
|
||||
>
|
||||
<div
|
||||
class="ant-layout-sider ant-layout-sider-has-trigger"
|
||||
class="ant-layout-sider ant-layout-sider-dark ant-layout-sider-has-trigger"
|
||||
style="flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
|
||||
>
|
||||
<div
|
||||
@ -1006,7 +1006,7 @@ exports[`renders ./components/layout/demo/top-side.md correctly 1`] = `
|
||||
style="padding:24px 0;background:#fff"
|
||||
>
|
||||
<div
|
||||
class="ant-layout-sider"
|
||||
class="ant-layout-sider ant-layout-sider-dark"
|
||||
style="background:#fff;flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
|
||||
>
|
||||
<div
|
||||
@ -1176,7 +1176,7 @@ exports[`renders ./components/layout/demo/top-side-2.md correctly 1`] = `
|
||||
class="ant-layout"
|
||||
>
|
||||
<div
|
||||
class="ant-layout-sider"
|
||||
class="ant-layout-sider ant-layout-sider-dark"
|
||||
style="background:#fff;flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
|
||||
>
|
||||
<div
|
||||
|
@ -55,4 +55,18 @@ describe('Layout', () => {
|
||||
);
|
||||
expect(wrapper.find('.ant-layout-sider').hasClass('ant-layout-sider-zero-width')).toBe(true);
|
||||
});
|
||||
|
||||
it('detect ant-layout-sider-dark as default theme', async () => {
|
||||
const wrapper = mount(
|
||||
<Sider>Sider</Sider>
|
||||
);
|
||||
expect(wrapper.find('.ant-layout-sider').hasClass('ant-layout-sider-dark'));
|
||||
});
|
||||
|
||||
it('detect ant-layout-sider-light when set light theme', async () => {
|
||||
const wrapper = mount(
|
||||
<Sider theme="light">Sider</Sider>
|
||||
);
|
||||
expect(wrapper.find('.ant-layout-sider').hasClass('ant-layout-sider-light'));
|
||||
});
|
||||
});
|
||||
|
@ -98,6 +98,7 @@ The sidebar.
|
||||
| trigger | specify the customized trigger, set to null to hide the trigger | string\|ReactNode | - |
|
||||
| width | width of the sidebar | number\|string | 200 |
|
||||
| onCollapse | the callback function, executed by clicking the trigger or activating the responsive layout | (collapsed, type) => {} | - |
|
||||
| theme | color theme of the sidebar | string: `light` `dark` | `dark` |
|
||||
|
||||
#### breakpoint width
|
||||
|
||||
|
@ -99,6 +99,7 @@ title: Layout
|
||||
| trigger | 自定义 trigger,设置为 null 时隐藏 trigger | string\|ReactNode | - |
|
||||
| width | 宽度 | number\|string | 200 |
|
||||
| onCollapse | 展开-收起时的回调函数,有点击 trigger 以及响应式反馈两种方式可以触发 | (collapsed, type) => {} | - |
|
||||
| theme | 主题颜色 | string: `light` `dark` | `dark` |
|
||||
|
||||
#### breakpoint width
|
||||
|
||||
|
@ -110,3 +110,5 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@import './light';
|
||||
|
11
components/layout/style/light.less
Normal file
11
components/layout/style/light.less
Normal file
@ -0,0 +1,11 @@
|
||||
.@{layout-prefix-cls} {
|
||||
&-sider {
|
||||
&-light {
|
||||
background: @layout-sider-background-light;
|
||||
}
|
||||
&-light > &-trigger {
|
||||
color: @layout-trigger-color-light;
|
||||
background: @layout-trigger-background-light;
|
||||
}
|
||||
}
|
||||
}
|
@ -126,4 +126,15 @@ describe('List.pagination', () => {
|
||||
.hasClass('ant-pagination-item-active')
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('specify the position of pagination', () => {
|
||||
const wrapper = mount(createList({ pagination: { position: 'top' } }));
|
||||
expect(wrapper.find('.ant-list').childAt(0).find('.ant-pagination')).toHaveLength(1);
|
||||
wrapper.setProps({ pagination: { position: 'bottom' } });
|
||||
expect(wrapper.find('.ant-list').children().last().find('.ant-pagination')).toHaveLength(1);
|
||||
wrapper.setProps({ pagination: { position: 'both' } });
|
||||
expect(wrapper.find('.ant-pagination')).toHaveLength(2);
|
||||
expect(wrapper.find('.ant-list').childAt(0).find('.ant-pagination')).toHaveLength(1);
|
||||
expect(wrapper.find('.ant-list').children().last().find('.ant-pagination')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
---
|
||||
category: Components
|
||||
type: Data Display
|
||||
title: List
|
||||
title: List
|
||||
cols: 1
|
||||
---
|
||||
|
||||
@ -28,6 +28,16 @@ A list can be used to display content related to a single subject. The content c
|
||||
| pagination | Pagination [config](https://ant.design/components/pagination/), hide it by setting it to false | boolean \| object | false |
|
||||
| split | Toggles rendering of the split under the list item | boolean | true |
|
||||
|
||||
### pagination
|
||||
|
||||
Properties for pagination.
|
||||
|
||||
| Property | Description | Type | Default |
|
||||
| -------- | ----------- | ---- | ------- |
|
||||
| position | specify the position of `Pagination` | 'top' \| 'bottom' \| 'both' | 'bottom' |
|
||||
|
||||
More about pagination, please check [`Pagination`](/components/pagination/).
|
||||
|
||||
### List grid props
|
||||
|
||||
| Property | Description | Type | Default |
|
||||
|
@ -6,7 +6,7 @@ import LocaleReceiver from '../locale-provider/LocaleReceiver';
|
||||
import defaultLocale from '../locale-provider/default';
|
||||
|
||||
import Spin from '../spin';
|
||||
import Pagination from '../pagination';
|
||||
import Pagination, { PaginationConfig } from '../pagination';
|
||||
import { Row } from '../grid';
|
||||
|
||||
import Item from './Item';
|
||||
@ -49,7 +49,7 @@ export interface ListProps {
|
||||
itemLayout?: string;
|
||||
loading?: boolean | SpinProps;
|
||||
loadMore?: React.ReactNode;
|
||||
pagination?: any;
|
||||
pagination?: PaginationConfig;
|
||||
prefixCls?: string;
|
||||
rowKey?: any;
|
||||
renderItem: any;
|
||||
@ -200,8 +200,9 @@ export default class List extends React.Component<ListProps> {
|
||||
...this.defaultPaginationProps,
|
||||
total: dataSource.length,
|
||||
current: paginationCurrent,
|
||||
...pagination,
|
||||
...pagination || {},
|
||||
};
|
||||
|
||||
const largestPage = Math.ceil(
|
||||
paginationProps.total / paginationProps.pageSize,
|
||||
);
|
||||
@ -258,15 +259,18 @@ export default class List extends React.Component<ListProps> {
|
||||
);
|
||||
}
|
||||
|
||||
const paginationPosition = paginationProps.position || 'bottom';
|
||||
|
||||
return (
|
||||
<div className={classString} {...rest}>
|
||||
{(paginationPosition === 'top' || paginationPosition === 'both') && paginationContent}
|
||||
{header && <div className={`${prefixCls}-header`}>{header}</div>}
|
||||
<Spin {...loadingProp}>
|
||||
{childrenContent}
|
||||
{children}
|
||||
</Spin>
|
||||
{footer && <div className={`${prefixCls}-footer`}>{footer}</div>}
|
||||
{loadMore || paginationContent}
|
||||
{loadMore || (paginationPosition === 'bottom' || paginationPosition === 'both') && paginationContent}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
---
|
||||
category: Components
|
||||
type: Data Display
|
||||
title: List
|
||||
title: List
|
||||
subtitle: 列表
|
||||
cols: 1
|
||||
---
|
||||
@ -30,6 +30,16 @@ cols: 1
|
||||
| size | list 的尺寸 | `default` \| `middle` \| `small` | `default` |
|
||||
| split | 是否展示分割线 | boolean | true |
|
||||
|
||||
### pagination
|
||||
|
||||
分页的配置项。
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| position | 指定分页显示的位置 | 'top' \| 'bottom' \| 'both' | 'bottom' |
|
||||
|
||||
更多配置项,请查看 [`Pagination`](/components/pagination/)。
|
||||
|
||||
### List grid props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
|
@ -61,3 +61,14 @@ exports[`renders ./components/message/demo/other.md correctly 1`] = `
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/message/demo/thenable.md correctly 1`] = `
|
||||
<button
|
||||
class="ant-btn"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Display a sequence of message
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
|
@ -85,6 +85,17 @@ describe('message', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should be called like promise', () => {
|
||||
jest.useRealTimers();
|
||||
const defaultDuration = 3;
|
||||
const now = Date.now();
|
||||
message.info('whatever').then(() => {
|
||||
// calculate the approximately duration value
|
||||
const aboutDuration = parseInt((Date.now() - now) / 1000, 10);
|
||||
expect(aboutDuration).toBe(defaultDuration);
|
||||
});
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/8201
|
||||
it('should hide message correctly', () => {
|
||||
let hide;
|
||||
|
28
components/message/demo/thenable.md
Normal file
28
components/message/demo/thenable.md
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
order: 5
|
||||
title:
|
||||
zh-CN: Promise 接口
|
||||
en-US: Promise interface
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
可以通过 then 接口在关闭后运行 callback 。以上用例将在每个 message 将要结束时通过 then 显示新的 message 。
|
||||
|
||||
## en-US
|
||||
`message` provides promise interface for `onClose`. The above example will display a new message when old message is about to finish.
|
||||
|
||||
````jsx
|
||||
import { message, Button } from 'antd';
|
||||
|
||||
const success = () => {
|
||||
message.loading('Action in progress..', 2.5)
|
||||
.then(() => message.success('Loading finished', 2.5))
|
||||
.then(() => message.info('Loading finished is finished', 2.5));
|
||||
};
|
||||
|
||||
ReactDOM.render(
|
||||
<Button onClick={success}>Display a sequence of message</Button>
|
||||
, mountNode);
|
||||
````
|
||||
|
@ -34,6 +34,12 @@ Methods for global configuration and destruction are also provided:
|
||||
- `message.config(options)`
|
||||
- `message.destroy()`
|
||||
|
||||
`afterClose` can be called in then-able interface:
|
||||
- `message[level](content, [duration]).then(afterClose)`
|
||||
- `message[level](content, [duration], onClose).then(afterClose)`
|
||||
|
||||
where `level` refers one static methods of `message`. The result of `then` method will be a Promise.
|
||||
|
||||
### message.config
|
||||
|
||||
```js
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* global Promise */
|
||||
import * as React from 'react';
|
||||
import Notification from 'rc-notification';
|
||||
import Icon from '../icon';
|
||||
@ -34,12 +35,22 @@ function getMessageInstance(callback: (i: any) => void) {
|
||||
|
||||
type NoticeType = 'info' | 'success' | 'error' | 'warning' | 'loading';
|
||||
|
||||
export interface ThenableArgument {
|
||||
(_: any): any;
|
||||
}
|
||||
|
||||
export interface MessageType {
|
||||
(): void;
|
||||
then: (fill: ThenableArgument, reject: ThenableArgument) => Promise<any>;
|
||||
promise: Promise<any>;
|
||||
}
|
||||
|
||||
function notice(
|
||||
content: React.ReactNode,
|
||||
duration: (() => void) | number = defaultDuration,
|
||||
type: NoticeType,
|
||||
onClose?: () => void,
|
||||
) {
|
||||
): MessageType {
|
||||
const iconType = ({
|
||||
info: 'info-circle',
|
||||
success: 'check-circle',
|
||||
@ -54,25 +65,36 @@ function notice(
|
||||
}
|
||||
|
||||
const target = key++;
|
||||
getMessageInstance((instance) => {
|
||||
instance.notice({
|
||||
key: target,
|
||||
duration,
|
||||
style: {},
|
||||
content: (
|
||||
<div className={`${prefixCls}-custom-content ${prefixCls}-${type}`}>
|
||||
<Icon type={iconType} />
|
||||
<span>{content}</span>
|
||||
</div>
|
||||
),
|
||||
onClose,
|
||||
const closePromise = new Promise((resolve) => {
|
||||
const callback = () => {
|
||||
if (typeof onClose === 'function') {
|
||||
onClose();
|
||||
}
|
||||
return resolve(true);
|
||||
};
|
||||
getMessageInstance((instance) => {
|
||||
instance.notice({
|
||||
key: target,
|
||||
duration,
|
||||
style: {},
|
||||
content: (
|
||||
<div className={`${prefixCls}-custom-content ${prefixCls}-${type}`}>
|
||||
<Icon type={iconType} />
|
||||
<span>{content}</span>
|
||||
</div>
|
||||
),
|
||||
onClose: callback,
|
||||
});
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
const result: any = () => {
|
||||
if (messageInstance) {
|
||||
messageInstance.removeNotice(target);
|
||||
}
|
||||
};
|
||||
result.then = (filled: ThenableArgument, rejected: ThenableArgument) => closePromise.then(filled, rejected);
|
||||
result.promise = closePromise;
|
||||
return result;
|
||||
}
|
||||
|
||||
type ConfigContent = React.ReactNode | string;
|
||||
|
@ -35,6 +35,13 @@ title: Message
|
||||
- `message.config(options)`
|
||||
- `message.destroy()`
|
||||
|
||||
组件同时提供 promise 接口
|
||||
|
||||
- `message[level](content, [duration]).then(afterClose)`
|
||||
- `message[level](content, [duration], onClose).then(afterClose)`
|
||||
|
||||
其中`message[level]` 是组件已经提供的静态方法。`then` 接口返回值是 Promise 。
|
||||
|
||||
### message.config
|
||||
|
||||
```js
|
||||
|
@ -29,6 +29,10 @@ export interface PaginationProps {
|
||||
itemRender?: (page: number, type: 'page' | 'prev' | 'next' | 'jump-prev' | 'jump-next') => React.ReactNode;
|
||||
}
|
||||
|
||||
export interface PaginationConfig extends PaginationProps {
|
||||
position?: 'top' | 'bottom' | 'both';
|
||||
}
|
||||
|
||||
export type PaginationLocale = any;
|
||||
|
||||
export default class Pagination extends React.Component<PaginationProps, {}> {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Pagination from './Pagination';
|
||||
|
||||
export { PaginationProps } from './Pagination';
|
||||
export { PaginationProps, PaginationConfig } from './Pagination';
|
||||
export default Pagination;
|
||||
|
@ -205,18 +205,22 @@
|
||||
@grid-gutter-width : 0;
|
||||
|
||||
// Layout
|
||||
@layout-body-background : #f0f2f5;
|
||||
@layout-header-background : #001529;
|
||||
@layout-footer-background : @layout-body-background;
|
||||
@layout-header-height : 64px;
|
||||
@layout-header-padding : 0 50px;
|
||||
@layout-footer-padding : 24px 50px;
|
||||
@layout-sider-background : @layout-header-background;
|
||||
@layout-trigger-height : 48px;
|
||||
@layout-trigger-background : #002140;
|
||||
@layout-trigger-color : #fff;
|
||||
@layout-zero-trigger-width : 36px;
|
||||
@layout-zero-trigger-height : 42px;
|
||||
@layout-body-background : #f0f2f5;
|
||||
@layout-header-background : #001529;
|
||||
@layout-footer-background : @layout-body-background;
|
||||
@layout-header-height : 64px;
|
||||
@layout-header-padding : 0 50px;
|
||||
@layout-footer-padding : 24px 50px;
|
||||
@layout-sider-background : @layout-header-background;
|
||||
@layout-trigger-height : 48px;
|
||||
@layout-trigger-background : #002140;
|
||||
@layout-trigger-color : #fff;
|
||||
@layout-zero-trigger-width : 36px;
|
||||
@layout-zero-trigger-height : 42px;
|
||||
// Layout light theme
|
||||
@layout-sider-background-light : #fff;
|
||||
@layout-trigger-background-light: #fff;
|
||||
@layout-trigger-color-light : @text-color;
|
||||
|
||||
// z-index list
|
||||
@zindex-affix : 10;
|
||||
|
@ -29,9 +29,9 @@ import {
|
||||
TableStateFilters,
|
||||
SelectionItemSelectFn,
|
||||
SelectionInfo,
|
||||
TablePaginationConfig,
|
||||
TableSelectWay,
|
||||
TableRowSelection,
|
||||
PaginationConfig,
|
||||
} from './interface';
|
||||
import { RadioChangeEvent } from '../radio';
|
||||
import { CheckboxChangeEvent } from '../checkbox';
|
||||
@ -156,7 +156,7 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
|
||||
}
|
||||
|
||||
getDefaultPagination(props: TableProps<T>) {
|
||||
const pagination: TablePaginationConfig = props.pagination || {};
|
||||
const pagination: PaginationConfig = props.pagination || {};
|
||||
return this.hasPagination(props) ?
|
||||
{
|
||||
...defaultPagination,
|
||||
|
@ -1,9 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { PaginationProps } from '../pagination';
|
||||
import { SpinProps } from '../spin';
|
||||
import { Store } from './createStore';
|
||||
import { RadioChangeEvent } from '../radio';
|
||||
import { CheckboxChangeEvent } from '../checkbox';
|
||||
import { PaginationConfig } from '../pagination';
|
||||
export { PaginationConfig } from '../pagination';
|
||||
|
||||
export type CompareFn<T> = ((a: T, b: T, sortOrder?: 'ascend' | 'descend') => number);
|
||||
export type ColumnFilterItem = { text: string; value: string, children?: ColumnFilterItem[] };
|
||||
@ -61,10 +62,6 @@ export interface TableLocale {
|
||||
export type RowSelectionType = 'checkbox' | 'radio';
|
||||
export type SelectionSelectFn<T> = (record: T, selected: boolean, selectedRows: Object[], nativeEvent: Event) => any;
|
||||
|
||||
export interface TablePaginationConfig extends PaginationProps {
|
||||
position?: 'top' | 'bottom' | 'both';
|
||||
}
|
||||
|
||||
export type TableSelectWay = 'onSelect' | 'onSelectAll' | 'onSelectInvert';
|
||||
|
||||
export interface TableRowSelection<T> {
|
||||
@ -85,7 +82,7 @@ export interface TableProps<T> {
|
||||
prefixCls?: string;
|
||||
dropdownPrefixCls?: string;
|
||||
rowSelection?: TableRowSelection<T>;
|
||||
pagination?: TablePaginationConfig | false;
|
||||
pagination?: PaginationConfig | false;
|
||||
size?: 'default' | 'middle' | 'small';
|
||||
dataSource?: T[];
|
||||
components?: TableComponents;
|
||||
@ -101,7 +98,7 @@ export interface TableProps<T> {
|
||||
expandRowByClick?: boolean;
|
||||
onExpandedRowsChange?: (expandedRowKeys: string[] | number[]) => void;
|
||||
onExpand?: (expanded: boolean, record: T) => void;
|
||||
onChange?: (pagination: TablePaginationConfig | boolean, filters: string[], sorter: Object) => any;
|
||||
onChange?: (pagination: PaginationConfig | boolean, filters: string[], sorter: Object) => any;
|
||||
loading?: boolean | SpinProps;
|
||||
locale?: Object;
|
||||
indentSize?: number;
|
||||
@ -126,7 +123,7 @@ export interface TableStateFilters {
|
||||
}
|
||||
|
||||
export interface TableState<T> {
|
||||
pagination: TablePaginationConfig;
|
||||
pagination: PaginationConfig;
|
||||
filters: TableStateFilters;
|
||||
sortColumn: ColumnProps<T> | null;
|
||||
sortOrder: 'ascend' | 'descend' | undefined;
|
||||
|
@ -248,4 +248,28 @@ describe('Transfer', () => {
|
||||
const wrapper = render(<Transfer {...sortedTargetKeyProps} render={item => item.title} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should add custom styles when their props are provided', () => {
|
||||
const style = {
|
||||
backgroundColor: 'red',
|
||||
};
|
||||
const listStyle = {
|
||||
backgroundColor: 'blue',
|
||||
};
|
||||
const operationStyle = {
|
||||
backgroundColor: 'yellow',
|
||||
};
|
||||
|
||||
const component = mount(<Transfer {...listCommonProps} style={style} listStyle={listStyle} operationStyle={operationStyle} />);
|
||||
|
||||
const wrapper = component.find('.ant-transfer');
|
||||
const listSource = component.find('.ant-transfer-list').first();
|
||||
const listTarget = component.find('.ant-transfer-list').last();
|
||||
const operation = component.find('.ant-transfer-operation').first();
|
||||
|
||||
expect(wrapper.prop('style')).toHaveProperty('backgroundColor', 'red');
|
||||
expect(listSource.prop('style')).toHaveProperty('backgroundColor', 'blue');
|
||||
expect(listTarget.prop('style')).toHaveProperty('backgroundColor', 'blue');
|
||||
expect(operation.prop('style')).toHaveProperty('backgroundColor', 'yellow');
|
||||
});
|
||||
});
|
||||
|
@ -22,7 +22,9 @@ One or more elements can be selected from either column, one click on the proper
|
||||
| filterOption | A function to determine whether an item should show in search result list | (inputValue, option): boolean | |
|
||||
| footer | A function used for rendering the footer. | (props): ReactNode | |
|
||||
| lazy | property of [react-lazy-load](https://github.com/loktar00/react-lazy-load) for lazy rendering items. Turn off it by set to `false`. | object\|boolean | `{ height: 32, offset: 32 }` |
|
||||
| style | A custom CSS style used for rendering wrapper element. | object | |
|
||||
| listStyle | A custom CSS style used for rendering the transfer columns. | object | |
|
||||
| operationStyle | A custom CSS style used for rendering the operations column. | object | |
|
||||
| notFoundContent | Text to display when a column is empty. | string\|ReactNode | 'The list is empty' |
|
||||
| operations | A set of operations that are sorted from bottom to top. | string\[] | ['>', '<'] |
|
||||
| render | The function to generate the item shown on a column. Based on an record (element of the dataSource array), this function should return a React element which is generated from that record. Also, it can return a plain object with `value` and `label`, `label` is a React element and `value` is for title | Function(record) | |
|
||||
|
@ -34,6 +34,7 @@ export interface TransferProps {
|
||||
onSelectChange?: (sourceSelectedKeys: string[], targetSelectedKeys: string[]) => void;
|
||||
style?: React.CSSProperties;
|
||||
listStyle?: React.CSSProperties;
|
||||
operationStyle?: React.CSSProperties;
|
||||
titles?: string[];
|
||||
operations?: string[];
|
||||
showSearch?: boolean;
|
||||
@ -75,7 +76,9 @@ export default class Transfer extends React.Component<TransferProps, any> {
|
||||
targetKeys: PropTypes.array,
|
||||
onChange: PropTypes.func,
|
||||
height: PropTypes.number,
|
||||
style: PropTypes.object,
|
||||
listStyle: PropTypes.object,
|
||||
operationStyle: PropTypes.object,
|
||||
className: PropTypes.string,
|
||||
titles: PropTypes.array,
|
||||
operations: PropTypes.array,
|
||||
@ -328,7 +331,9 @@ export default class Transfer extends React.Component<TransferProps, any> {
|
||||
searchPlaceholder,
|
||||
body,
|
||||
footer,
|
||||
style,
|
||||
listStyle,
|
||||
operationStyle,
|
||||
filterOption,
|
||||
render,
|
||||
lazy,
|
||||
@ -343,7 +348,7 @@ export default class Transfer extends React.Component<TransferProps, any> {
|
||||
|
||||
const titles = this.getTitles(locale);
|
||||
return (
|
||||
<div className={cls}>
|
||||
<div className={cls} style={style}>
|
||||
<List
|
||||
prefixCls={`${prefixCls}-list`}
|
||||
titleText={titles[0]}
|
||||
@ -375,6 +380,7 @@ export default class Transfer extends React.Component<TransferProps, any> {
|
||||
leftActive={leftActive}
|
||||
leftArrowText={operations[1]}
|
||||
moveToLeft={this.moveToLeft}
|
||||
style={operationStyle}
|
||||
/>
|
||||
<List
|
||||
prefixCls={`${prefixCls}-list`}
|
||||
|
@ -9,6 +9,7 @@ export interface TransferOperationProps {
|
||||
moveToRight?: React.FormEventHandler<HTMLButtonElement>;
|
||||
leftActive?: boolean;
|
||||
rightActive?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export default class Operation extends React.Component<TransferOperationProps, any> {
|
||||
@ -21,9 +22,10 @@ export default class Operation extends React.Component<TransferOperationProps, a
|
||||
leftActive,
|
||||
rightActive,
|
||||
className,
|
||||
style,
|
||||
} = this.props;
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={className} style={style}>
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
|
@ -46,6 +46,7 @@
|
||||
"css-animation": "^1.2.5",
|
||||
"dom-closest": "^0.2.0",
|
||||
"enquire.js": "^2.1.1",
|
||||
"intersperse": "^1.0.0",
|
||||
"lodash": "^4.17.5",
|
||||
"moment": "^2.19.3",
|
||||
"omit.js": "^1.0.0",
|
||||
|
2
typings/custom-typings.d.ts
vendored
2
typings/custom-typings.d.ts
vendored
@ -98,4 +98,6 @@ declare module "lodash/debounce";
|
||||
|
||||
declare module "lodash/uniqBy";
|
||||
|
||||
declare module 'intersperse';
|
||||
|
||||
declare module "raf";
|
||||
|
Loading…
Reference in New Issue
Block a user