merge master

This commit is contained in:
zombiej 2019-09-26 22:28:08 +08:00
commit 911867622e
33 changed files with 1187 additions and 202 deletions

View File

@ -1,76 +0,0 @@
name: Publish to github-package
on:
release:
types: [published]
jobs:
compile:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@master
- uses: actions/setup-node@v1
with:
node-version: '12.x'
registry-url: 'https://registry.npmjs.org'
- name: install
run: npm install
- name: compile
run: npm run compile
- name: dist
run: npm run dist
- uses: actions/upload-artifact@master
with:
name: esm
path: es
- uses: actions/upload-artifact@master
with:
name: cjs
path: lib
- uses: actions/upload-artifact@master
with:
name: dist
path: dist
- uses: actions/upload-artifact@master
with:
name: package
path: package.json
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@master
with:
name: esm
- uses: actions/download-artifact@master
with:
name: cjs
- uses: actions/download-artifact@master
with:
name: dist
- uses: actions/download-artifact@master
with:
name: package
- uses: actions/setup-node@v1
with:
node-version: '12.x'
registry-url: 'https://npm.pkg.github.com'
- name: publish
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

14
.github/workflows/rebase.yml vendored Normal file
View File

@ -0,0 +1,14 @@
on:
issue_comment:
types: [created]
name: Automatic Rebase
jobs:
rebase:
name: Rebase
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Automatic Rebase
uses: cirrus-actions/rebase@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -2,6 +2,7 @@ import * as React from 'react';
import * as PropTypes from 'prop-types';
import classNames from 'classnames';
import toArray from 'rc-util/lib/Children/toArray';
import omit from 'omit.js';
import BreadcrumbItem from './BreadcrumbItem';
import BreadcrumbSeparator from './BreadcrumbSeparator';
import Menu from '../menu';
@ -134,6 +135,7 @@ export default class Breadcrumb extends React.Component<BreadcrumbProps, any> {
className,
routes,
children,
...restProps
} = this.props;
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
if (routes && routes.length > 0) {
@ -159,7 +161,11 @@ export default class Breadcrumb extends React.Component<BreadcrumbProps, any> {
});
}
return (
<div className={classNames(className, prefixCls)} style={style}>
<div
className={classNames(className, prefixCls)}
style={style}
{...omit(restProps, ['itemRender', 'linkRender', 'nameRender', 'params'])}
>
{crumbs}
</div>
);

View File

@ -117,4 +117,14 @@ describe('Breadcrumb', () => {
expect(linkRender).not.toHaveBeenCalled();
expect(nameRender).not.toHaveBeenCalled();
});
it('should support custom attribute', () => {
const wrapper = render(
<Breadcrumb data-custom="custom">
<Breadcrumb.Item data-custom="custom-item">xxx</Breadcrumb.Item>
<Breadcrumb.Item>yyy</Breadcrumb.Item>
</Breadcrumb>,
);
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -158,3 +158,36 @@ exports[`Breadcrumb should render a menu 1`] = `
</span>
</div>
`;
exports[`Breadcrumb should support custom attribute 1`] = `
<div
class="ant-breadcrumb"
data-custom="custom"
>
<span>
<span
class="ant-breadcrumb-link"
data-custom="custom-item"
>
xxx
</span>
<span
class="ant-breadcrumb-separator"
>
/
</span>
</span>
<span>
<span
class="ant-breadcrumb-link"
>
yyy
</span>
<span
class="ant-breadcrumb-separator"
>
/
</span>
</span>
</div>
`;

View File

@ -22,37 +22,6 @@ const breadcrumbNameMap = {
'/apps/2/detail': 'Detail',
};
const Home = withRouter(props => {
const { location, history } = props;
const pathSnippets = location.pathname.split('/').filter(i => i);
const extraBreadcrumbItems = pathSnippets.map((_, index) => {
const url = `/${pathSnippets.slice(0, index + 1).join('/')}`;
return (
<Breadcrumb.Item key={url}>
<Link to={url}>{breadcrumbNameMap[url]}</Link>
</Breadcrumb.Item>
);
});
const breadcrumbItems = [
<Breadcrumb.Item key="home">
<Link to="/">Home</Link>
</Breadcrumb.Item>,
].concat(extraBreadcrumbItems);
return (
<div className="demo">
<div className="demo-nav">
<a onClick={() => history.push('/')}>Home</a>
<a onClick={() => history.push('/apps')}>Application List</a>
</div>
<Switch>
<Route path="/apps" component={Apps} />
<Route render={() => <span>Home Page</span>} />
</Switch>
<Breadcrumb>{breadcrumbItems}</Breadcrumb>
</div>
);
});
describe('react router', () => {
beforeAll(() => {
jest.useFakeTimers();
@ -62,7 +31,37 @@ describe('react router', () => {
jest.useRealTimers();
});
// https://github.com/airbnb/enzyme/issues/875
it('react router 4', () => {
(process.env.REACT === '15' ? it.skip : it)('react router 4', () => {
const Home = withRouter(props => {
const { location, history } = props;
const pathSnippets = location.pathname.split('/').filter(i => i);
const extraBreadcrumbItems = pathSnippets.map((_, index) => {
const url = `/${pathSnippets.slice(0, index + 1).join('/')}`;
return (
<Breadcrumb.Item key={url}>
<Link to={url}>{breadcrumbNameMap[url]}</Link>
</Breadcrumb.Item>
);
});
const breadcrumbItems = [
<Breadcrumb.Item key="home">
<Link to="/">Home</Link>
</Breadcrumb.Item>,
].concat(extraBreadcrumbItems);
return (
<div className="demo">
<div className="demo-nav">
<a onClick={() => history.push('/')}>Home</a>
<a onClick={() => history.push('/apps')}>Application List</a>
</div>
<Switch>
<Route path="/apps" component={Apps} />
<Route render={() => <span>Home Page</span>} />
</Switch>
<Breadcrumb>{breadcrumbItems}</Breadcrumb>
</div>
);
});
const wrapper = mount(
<MemoryRouter initialEntries={['/']} initialIndex={0}>
<Home />

View File

@ -130,6 +130,11 @@
.button-size(@btn-height-lg; @btn-padding-lg; @btn-font-size-lg; 0);
line-height: @btn-height-lg - 2px;
}
&-lg > .@{btnClassName}.@{btnClassName}-icon-only {
.square(@btn-height-lg);
padding-right: 0;
padding-left: 0;
}
&-sm > .@{btnClassName},
&-sm > span > .@{btnClassName} {
.button-size(@btn-height-sm; @btn-padding-sm; @font-size-base; 0);
@ -138,6 +143,11 @@
font-size: @font-size-base;
}
}
&-sm > .@{btnClassName}.@{btnClassName}-icon-only {
.square(@btn-height-sm);
padding-right: 0;
padding-left: 0;
}
}
// Base styles of buttons
// --------------------------------------------------

View File

@ -10,7 +10,7 @@ import ResponsiveObserve, {
responsiveArray,
} from '../_util/responsiveObserve';
const RowAligns = tuple('top', 'middle', 'bottom');
const RowAligns = tuple('top', 'middle', 'bottom', 'stretch');
const RowJustify = tuple('start', 'end', 'center', 'space-around', 'space-between');
export interface RowProps extends React.HTMLAttributes<HTMLDivElement> {
gutter?: number | Partial<Record<Breakpoint, number>>;

View File

@ -212,7 +212,7 @@ exports[`renders ./components/input/demo/addon.md correctly 1`] = `
</div>
`;
exports[`renders ./components/input/demo/algin.md correctly 1`] = `
exports[`renders ./components/input/demo/align.md correctly 1`] = `
<div>
<div
class="ant-mentions"

View File

@ -18,7 +18,7 @@ subtitle: 页头
| --- | --- | --- | --- | --- |
| title | 自定义标题文字 | ReactNode | - | 3.14.0 |
| subTitle | 自定义的二级标题文字 | ReactNode | - | 3.14.0 |
| avatar | 标题栏旁的头像 | [avatar props](/components/avatar-cn/) | - | 3.22.0 |
| avatar | 标题栏旁的头像 | [avatar props](/components/avatar/) | - | 3.22.0 |
| backIcon | 自定义 back icon ,如果为 false 不渲染 back icon | ReactNode | `<ArrowLeft />` | 3.14.0 |
| tags | title 旁的 tag 列表 | [Tag](https://ant.design/components/tag-cn/)[] \| [Tag](https://ant.design/components/tag-cn/) | - | 3.14.0 |
| extra | 操作区,位于 title 行的行尾 | ReactNode | - | 3.14.0 |

View File

@ -526,7 +526,7 @@ exports[`renders ./components/select/demo/coordinate.md correctly 1`] = `
exports[`renders ./components/select/demo/custom-dropdown-menu.md correctly 1`] = `
<div
class="ant-select ant-select-single ant-select-show-arrow"
style="width:120px"
style="width:240px"
>
<div
class="ant-select-selector"
@ -548,9 +548,9 @@ exports[`renders ./components/select/demo/custom-dropdown-menu.md correctly 1`]
/>
</span>
<span
class="ant-select-selection-item"
class="ant-select-selection-placeholder"
>
Lucy
custom dropdown render
</span>
</div>
<span

View File

@ -5,7 +5,6 @@ import Select from '..';
import Icon from '../../icon';
import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
import { resetWarned } from '../../_util/warning';
const { Option } = Select;
@ -127,14 +126,4 @@ describe('Select', () => {
expect(wrapper.render()).toMatchSnapshot();
});
});
it('not warning if user use `inputValue`', () => {
resetWarned();
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
mount(<Select inputValue="" />);
expect(errorSpy).not.toHaveBeenCalled();
errorSpy.mockRestore();
});
});

View File

@ -14,33 +14,52 @@ title:
Customize the dropdown menu via `dropdownRender`.
```jsx
import { Select, Divider, message } from 'antd';
import { Select, Divider } from 'antd';
import { Plus } from '@ant-design/icons';
const { Option } = Select;
ReactDOM.render(
<Select
defaultValue="lucy"
style={{ width: 120 }}
dropdownRender={menu => (
<div>
{menu}
<Divider style={{ margin: '4px 0' }} />
<a
style={{ padding: '8px', display: 'block', cursor: 'pointer' }}
onClick={() => {
message.info('Add an item!');
}}
>
<Plus /> Add item
</a>
</div>
)}
>
<Option value="jack">Jack</Option>
<Option value="lucy">Lucy</Option>
</Select>,
mountNode,
);
let index = 0;
class App extends React.Component {
state = {
items: ['jack', 'lucy'],
};
addItem = () => {
console.log('addItem');
const { items } = this.state;
this.setState({
items: [...items, `New item ${index++}`],
});
};
render() {
const { items } = this.state;
return (
<Select
style={{ width: 240 }}
placeholder="custom dropdown render"
dropdownRender={menu => (
<div>
{menu}
<Divider style={{ margin: '4px 0' }} />
<a
style={{ padding: '8px', display: 'block', cursor: 'pointer' }}
onClick={this.addItem}
>
<Plus /> Add item
</a>
</div>
)}
>
{items.map(item => (
<Option key={item}>{item}</Option>
))}
</Select>
);
}
}
ReactDOM.render(<App />, mountNode);
```

View File

@ -46,7 +46,6 @@ Select component to select value from options.
| optionFilterProp | Which prop value of option will be used for filter if filterOption is true | string | value | |
| optionLabelProp | Which prop value of option will render as content of select. [Example](https://codesandbox.io/s/antd-reproduction-template-tk678) | string | `value` for `combobox`, `children` for other modes | |
| placeholder | Placeholder of select | string\|ReactNode | - | |
| searchValue | Search input value | string | - | 3.23.2 |
| showArrow | Whether to show the drop-down arrow | boolean | true | 3.2.1 |
| showSearch | Whether show search input in single mode. | boolean | false | |
| size | Size of Select input. `default` `large` `small` | string | default | |
@ -94,3 +93,9 @@ Select component to select value from options.
| -------- | ----------- | --------------------- | ------- | ------- |
| key | | string | - | |
| label | Group label | string\|React.Element | - | |
## FAQ
### The dropdown is closed when click `dropdownRender` area?
See the [dropdownRender example](/components/select/#components-select-demo-custom-dropdown-menu).

View File

@ -47,7 +47,6 @@ title: Select
| optionFilterProp | 搜索时过滤对应的 option 属性,如设置为 children 表示对内嵌内容进行搜索。[示例](https://codesandbox.io/s/antd-reproduction-template-tk678) | string | value | |
| optionLabelProp | 回填到选择框的 Option 的属性值,默认是 Option 的子元素。比如在子元素需要高亮效果时,此值可以设为 `value`。 | string | `children` combobox 模式下为 `value` | |
| placeholder | 选择框默认文字 | string | - | |
| searchValue | 搜索框文本 | string | - | 3.23.2 |
| showArrow | 是否显示下拉小箭头 | boolean | true | 3.2.1 |
| showSearch | 使单选模式可搜索 | boolean | false | |
| size | 选择框大小,可选 `large` `small` | string | default | |
@ -97,3 +96,9 @@ title: Select
| ----- | ---- | --------------------- | ------ | ---- |
| key | | string | - | |
| label | 组名 | string\|React.Element | 无 | |
## FAQ
### 点击 `dropdownRender` 里的内容浮层关闭怎么办?
看下 [dropdownRender 例子](/components/select-cn/#components-select-demo-custom-dropdown-menu) 里的说明。

View File

@ -356,3 +356,473 @@ exports[`Transfer should show sorted targetkey 1`] = `
</div>
</div>
`;
exports[`Transfer should support render value and label in item 1`] = `
<Transfer
dataSource={
Array [
Object {
"key": "a",
"title": "title",
},
]
}
locale={Object {}}
render={[Function]}
showSearch={false}
>
<LocaleReceiver
componentName="Transfer"
defaultLocale={
Object {
"itemUnit": "item",
"itemsUnit": "items",
"searchPlaceholder": "Search here",
"titles": Array [
"",
"",
],
}
}
>
<div
className="ant-transfer"
>
<TransferList
checkedKeys={Array []}
dataSource={
Array [
Object {
"key": "a",
"title": "title",
},
]
}
direction="left"
handleClear={[Function]}
handleFilter={[Function]}
itemUnit="item"
itemsUnit="items"
lazy={Object {}}
notFoundContent={
<Context.Consumer>
[Function]
</Context.Consumer>
}
onItemSelect={[Function]}
onItemSelectAll={[Function]}
onScroll={[Function]}
prefixCls="ant-transfer-list"
render={[Function]}
searchPlaceholder="Search here"
showSearch={false}
titleText=""
titles={
Array [
"",
"",
]
}
>
<div
className="ant-transfer-list"
>
<div
className="ant-transfer-list-header"
>
<Checkbox
checked={false}
indeterminate={false}
onChange={[Function]}
>
<label
className="ant-checkbox-wrapper"
>
<Checkbox
checked={false}
className=""
defaultChecked={false}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
prefixCls="ant-checkbox"
style={Object {}}
type="checkbox"
>
<span
className="ant-checkbox"
style={Object {}}
>
<input
checked={false}
className="ant-checkbox-input"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="checkbox"
/>
<span
className="ant-checkbox-inner"
/>
</span>
</Checkbox>
</label>
</Checkbox>
<span
className="ant-transfer-list-header-selected"
>
<span>
1
item
</span>
<span
className="ant-transfer-list-header-title"
/>
</span>
</div>
<div
className="ant-transfer-list-body"
>
<ListBody
dataSource={
Array [
Object {
"key": "a",
"title": "title",
},
]
}
direction="left"
filteredItems={
Array [
Object {
"key": "a",
"title": "title",
},
]
}
filteredRenderItems={
Array [
Object {
"item": Object {
"key": "a",
"title": "title",
},
"renderedEl": "label",
"renderedText": "title value",
},
]
}
itemUnit="item"
itemsUnit="items"
lazy={Object {}}
notFoundContent={
<Context.Consumer>
[Function]
</Context.Consumer>
}
onItemSelect={[Function]}
onItemSelectAll={[Function]}
onScroll={[Function]}
prefixCls="ant-transfer-list"
render={[Function]}
searchPlaceholder="Search here"
selectedKeys={Array []}
showSearch={false}
titleText=""
titles={
Array [
"",
"",
]
}
>
<Animate
animation={Object {}}
className="ant-transfer-list-content"
component="ul"
componentProps={
Object {
"onScroll": [Function],
}
}
onAppear={[Function]}
onEnd={[Function]}
onEnter={[Function]}
onLeave={[Function]}
transitionAppear={false}
transitionEnter={true}
transitionLeave={false}
transitionName=""
>
<ul
className="ant-transfer-list-content"
onScroll={[Function]}
>
<AnimateChild
animation={Object {}}
key="a"
transitionAppear={false}
transitionEnter={true}
transitionLeave={false}
transitionName=""
>
<ListItem
checked={false}
item={
Object {
"key": "a",
"title": "title",
}
}
key="a"
lazy={Object {}}
onClick={[Function]}
prefixCls="ant-transfer-list"
renderedEl="label"
renderedText="title value"
>
<LazyLoad
debounce={false}
elementType="div"
height={32}
offset={500}
offsetBottom={0}
offsetHorizontal={0}
offsetLeft={0}
offsetRight={0}
offsetTop={0}
offsetVertical={0}
throttle={0}
>
<div
className="LazyLoad"
style={
Object {
"height": 32,
"width": undefined,
}
}
/>
</LazyLoad>
</ListItem>
</AnimateChild>
</ul>
</Animate>
</ListBody>
</div>
</div>
</TransferList>
<Operation
className="ant-transfer-operation"
leftActive={false}
moveToLeft={[Function]}
moveToRight={[Function]}
rightActive={false}
>
<div
className="ant-transfer-operation"
>
<Button
block={false}
disabled={true}
ghost={false}
htmlType="button"
icon="right"
loading={false}
onClick={[Function]}
size="small"
type="primary"
>
<Wave>
<button
className="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only"
disabled={true}
onClick={[Function]}
type="button"
>
right
</button>
</Wave>
</Button>
<Button
block={false}
disabled={true}
ghost={false}
htmlType="button"
icon="left"
loading={false}
onClick={[Function]}
size="small"
type="primary"
>
<Wave>
<button
className="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only"
disabled={true}
onClick={[Function]}
type="button"
>
left
</button>
</Wave>
</Button>
</div>
</Operation>
<TransferList
checkedKeys={Array []}
dataSource={Array []}
direction="right"
handleClear={[Function]}
handleFilter={[Function]}
itemUnit="item"
itemsUnit="items"
lazy={Object {}}
notFoundContent={
<Context.Consumer>
[Function]
</Context.Consumer>
}
onItemSelect={[Function]}
onItemSelectAll={[Function]}
onScroll={[Function]}
prefixCls="ant-transfer-list"
render={[Function]}
searchPlaceholder="Search here"
showSearch={false}
titleText=""
titles={
Array [
"",
"",
]
}
>
<div
className="ant-transfer-list"
>
<div
className="ant-transfer-list-header"
>
<Checkbox
checked={false}
indeterminate={false}
onChange={[Function]}
>
<label
className="ant-checkbox-wrapper"
>
<Checkbox
checked={false}
className=""
defaultChecked={false}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
prefixCls="ant-checkbox"
style={Object {}}
type="checkbox"
>
<span
className="ant-checkbox"
style={Object {}}
>
<input
checked={false}
className="ant-checkbox-input"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="checkbox"
/>
<span
className="ant-checkbox-inner"
/>
</span>
</Checkbox>
</label>
</Checkbox>
<span
className="ant-transfer-list-header-selected"
>
<span>
0
item
</span>
<span
className="ant-transfer-list-header-title"
/>
</span>
</div>
<div
className="ant-transfer-list-body"
>
<div
className="ant-transfer-list-body-not-found"
>
<Empty
className="ant-empty-small"
image={<Simple />}
>
<LocaleReceiver
componentName="Empty"
>
<div
className="ant-empty ant-empty-normal ant-empty-small"
>
<div
className="ant-empty-image"
>
<Simple>
<svg
height="41"
viewBox="0 0 64 41"
width="64"
xmlns="http://www.w3.org/2000/svg"
>
<g
fill="none"
fillRule="evenodd"
transform="translate(0 1)"
>
<ellipse
cx="32"
cy="33"
fill="#F5F5F5"
rx="32"
ry="7"
/>
<g
fillRule="nonzero"
stroke="#D9D9D9"
>
<path
d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"
/>
<path
d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z"
fill="#FAFAFA"
/>
</g>
</g>
</svg>
</Simple>
</div>
<p
className="ant-empty-description"
>
No Data
</p>
</div>
</LocaleReceiver>
</Empty>
</div>
</div>
</div>
</TransferList>
</div>
</LocaleReceiver>
</Transfer>
`;

View File

@ -113,6 +113,24 @@ describe('Transfer', () => {
expect(handleChange).toHaveBeenCalledWith(['a', 'b'], 'right', ['a']);
});
it('should move selected keys to left list', () => {
const handleChange = jest.fn();
const wrapper = mount(
<Transfer
{...listCommonProps}
selectedKeys={['a']}
targetKeys={['a']}
onChange={handleChange}
/>,
);
wrapper
.find(TransferOperation)
.find(Button)
.at(1)
.simulate('click'); // move selected keys to left list
expect(handleChange).toHaveBeenCalledWith([], 'left', ['a']);
});
it('should move selected keys expect disabled to corresponding list', () => {
const handleChange = jest.fn();
const wrapper = mount(<Transfer {...listDisabledProps} onChange={handleChange} />);
@ -459,4 +477,44 @@ describe('Transfer', () => {
expect(listTarget.prop('style')).toHaveProperty('backgroundColor', 'blue');
expect(operation.prop('style')).toHaveProperty('backgroundColor', 'yellow');
});
it('should support onScroll', () => {
const onScroll = jest.fn();
const component = mount(<Transfer {...listCommonProps} onScroll={onScroll} />);
component
.find('.ant-transfer-list')
.at(0)
.find('.ant-transfer-list-content')
.at(0)
.simulate('scroll');
expect(onScroll).toHaveBeenLastCalledWith('left', expect.anything());
component
.find('.ant-transfer-list')
.at(1)
.find('.ant-transfer-list-content')
.at(0)
.simulate('scroll');
expect(onScroll).toHaveBeenLastCalledWith('right', expect.anything());
});
it('should support rowKey is function', () => {
expect(() => {
mount(<Transfer {...listCommonProps} rowKey={record => record.key} />);
}).not.toThrow();
});
it('should support render value and label in item', () => {
const component = mount(
<Transfer
dataSource={[
{
key: 'a',
title: 'title',
},
]}
render={record => ({ value: `${record.title} value`, label: 'label' })}
/>,
);
expect(component).toMatchSnapshot();
});
});

View File

@ -25,6 +25,7 @@ describe('Transfer.Search', () => {
});
it('onSearch', () => {
jest.useFakeTimers();
const dataSource = [
{
key: 'a',
@ -59,6 +60,7 @@ describe('Transfer.Search', () => {
.find('.ant-input')
.at(0)
.simulate('change', { target: { value: 'a' } });
jest.runAllTimers();
expect(onSearch).toHaveBeenCalledWith('left', 'a');
onSearch.mockReset();
@ -68,6 +70,7 @@ describe('Transfer.Search', () => {
.at(0)
.simulate('click');
expect(onSearch).toHaveBeenCalledWith('left', '');
jest.useRealTimers();
});
it('legacy props#onSearchChange doesnot work anymore', () => {
@ -81,6 +84,7 @@ describe('Transfer.Search', () => {
.find('.ant-input')
.at(0)
.simulate('change', { target: { value: 'a' } });
jest.runAllTimers();
expect(errorSpy.mock.calls.length).toBe(0);
expect(onSearchChange).not.toHaveBeenCalled();

View File

@ -0,0 +1,93 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TreeSelect TreeSelect Custom Icons should support customized icons 1`] = `
<span
aria-haspopup="listbox"
class="ant-select ant-select-enabled ant-select-allow-clear"
role="combobox"
tabindex="-1"
>
<span
class="ant-select-selection ant-select-selection--multiple"
>
<ul
class="ant-select-selection__rendered"
role="menubar"
>
<li
class="ant-select-selection__choice"
role="menuitem"
style="user-select: none;"
unselectable="unselectable"
>
<span
class="ant-select-selection__choice__remove"
>
<span>
remove
</span>
</span>
<span
class="ant-select-selection__choice__content"
>
leaf1
</span>
</li>
<li
class="ant-select-selection__choice"
role="menuitem"
style="user-select: none;"
unselectable="unselectable"
>
<span
class="ant-select-selection__choice__remove"
>
<span>
remove
</span>
</span>
<span
class="ant-select-selection__choice__content"
>
leaf2
</span>
</li>
<li
class="ant-select-search ant-select-search--inline"
>
<span
class="ant-select-search__field__wrap"
>
<input
aria-autocomplete="list"
aria-label="filter select"
aria-multiline="false"
class="ant-select-search__field"
style="width: 0px;"
type="text"
value=""
/>
<span
class="ant-select-search__field__mirror"
>
 
</span>
</span>
</li>
</ul>
<span
class="ant-select-selection__clear"
>
<span>
clear
</span>
</span>
<span
class="ant-select-search__field__placeholder"
style="display: none;"
>
Please select
</span>
</span>
</span>
`;

View File

@ -1,6 +1,6 @@
import React from 'react';
import { mount } from 'enzyme';
import TreeSelect from '..';
import TreeSelect, { TreeNode } from '..';
import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
@ -17,4 +17,30 @@ describe('TreeSelect', () => {
expect(multiple.find('.ant-select-search__field').length).toBeTruthy();
});
});
describe('TreeSelect Custom Icons', () => {
it('should support customized icons', () => {
const wrapper = mount(
<TreeSelect
showSearch
clearIcon={<span>clear</span>}
removeIcon={<span>remove</span>}
value={['leaf1', 'leaf2']}
placeholder="Please select"
multiple
allowClear
treeDefaultExpandAll
>
<TreeNode value="parent 1" title="parent 1" key="0-1">
<TreeNode value="parent 1-0" title="parent 1-0" key="0-1-1">
<TreeNode value="leaf1" title="my leaf" key="random" />
<TreeNode value="leaf2" title="your leaf" key="random1" />
</TreeNode>
</TreeNode>
</TreeSelect>,
);
expect(wrapper.render()).toMatchSnapshot();
});
});
});

View File

@ -7,6 +7,7 @@ import { Loading, CaretDown, Down, Close, CloseCircleFilled } from '@ant-design/
import { TreeSelectProps, TreeNodeValue } from './interface';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import warning from '../_util/warning';
import { cloneElement } from '../_util/reactNode';
import { AntTreeNodeProps } from '../tree';
export { TreeNode, TreeSelectProps } from './interface';
@ -75,6 +76,8 @@ export default class TreeSelect<T extends TreeNodeValue> extends React.Component
dropdownStyle,
dropdownClassName,
suffixIcon,
removeIcon,
clearIcon,
getPopupContainer,
...restProps
} = this.props;
@ -100,14 +103,23 @@ export default class TreeSelect<T extends TreeNodeValue> extends React.Component
checkable = <span className={`${prefixCls}-tree-checkbox-inner`} />;
}
const inputIcon = (suffixIcon &&
(React.isValidElement<{ className?: string }>(suffixIcon)
? React.cloneElement(suffixIcon)
: suffixIcon)) || <Down className={`${prefixCls}-arrow-icon`} />;
const inputIcon = suffixIcon ? (
cloneElement(suffixIcon)
) : (
<Down className={`${prefixCls}-arrow-icon`} />
);
const removeIcon = <Close className={`${prefixCls}-remove-icon`} />;
const finalRemoveIcon = removeIcon ? (
cloneElement(removeIcon)
) : (
<Close className={`${prefixCls}-remove-icon`} />
);
const clearIcon = <CloseCircleFilled className={`${prefixCls}-clear-icon`} />;
const finalClearIcon = clearIcon ? (
cloneElement(clearIcon)
) : (
<CloseCircleFilled className={`${prefixCls}-clear-icon`} />
);
return (
<RcTreeSelect
@ -115,8 +127,8 @@ export default class TreeSelect<T extends TreeNodeValue> extends React.Component
this.renderSwitcherIcon(prefixCls, nodeProps)
}
inputIcon={inputIcon}
removeIcon={removeIcon}
clearIcon={clearIcon}
removeIcon={finalRemoveIcon}
clearIcon={finalClearIcon}
{...rest}
showSearch={showSearch}
getPopupContainer={getPopupContainer || getContextPopupContainer}

View File

@ -50,6 +50,8 @@ export interface TreeSelectProps<T extends TreeNodeValue> extends Omit<SelectPro
searchValue?: string;
showCheckedStrategy?: 'SHOW_ALL' | 'SHOW_PARENT' | 'SHOW_CHILD';
suffixIcon?: React.ReactNode;
removeIcon?: React.ReactNode;
clearIcon?: React.ReactNode;
treeCheckable?: boolean | React.ReactNode;
treeCheckStrictly?: boolean;
treeData?: Array<TreeNode>;

View File

@ -196,24 +196,26 @@ export default class Tree extends React.Component<TreeProps, any> {
if (loading) {
return <Loading className={`${prefixCls}-switcher-loading-icon`} />;
}
if (showLine) {
if (isLeaf) {
if (isLeaf) {
if (showLine) {
return <File className={`${prefixCls}-switcher-line-icon`} />;
}
return React.createElement(expanded ? MinusSquare : PlusSquare, {
className: `${prefixCls}-switcher-line-icon`,
});
}
const switcherCls = `${prefixCls}-switcher-icon`;
if (isLeaf) {
return null;
}
const switcherCls = `${prefixCls}-switcher-icon`;
if (switcherIcon) {
const switcherOriginCls = switcherIcon.props.className || '';
return React.cloneElement(switcherIcon, {
className: classNames(switcherOriginCls, switcherCls),
});
}
if (showLine) {
return expanded ? (
<MinusSquare className={`${prefixCls}-switcher-line-icon`} />
) : (
<PlusSquare className={`${prefixCls}-switcher-line-icon`} />
);
}
return <CaretDownFilled className={switcherCls} />;
};

View File

@ -2285,3 +2285,267 @@ exports[`renders ./components/tree/demo/search.md correctly 1`] = `
</div>
</div>
`;
exports[`renders ./components/tree/demo/switcher-icon.md correctly 1`] = `
Array [
<div
role="tree"
>
<input
style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
tabindex="0"
value=""
/>
</div>,
<div
class="ant-tree-list ant-tree ant-tree-icon-hide ant-tree-show-line"
>
<div>
<div
class="ant-tree-list-holder-inner"
style="display:flex;flex-direction:column"
>
<div
class="ant-tree-treenode ant-tree-treenode-switcher-open"
>
<span
class="ant-tree-switcher ant-tree-switcher_open"
/>
<span
class="ant-tree-node-content-wrapper ant-tree-node-content-wrapper-open"
title="parent 1"
>
<span
class="ant-tree-title"
>
parent 1
</span>
</span>
</div>
<div
class="ant-tree-treenode ant-tree-treenode-switcher-open"
>
<span
aria-hidden="true"
class="ant-tree-indent"
>
<span
class="ant-tree-indent-unit ant-tree-indent-unit-start"
/>
</span>
<span
class="ant-tree-switcher ant-tree-switcher_open"
/>
<span
class="ant-tree-node-content-wrapper ant-tree-node-content-wrapper-open"
title="parent 1-0"
>
<span
class="ant-tree-title"
>
parent 1-0
</span>
</span>
</div>
<div
class="ant-tree-treenode ant-tree-treenode-switcher-close"
>
<span
aria-hidden="true"
class="ant-tree-indent"
>
<span
class="ant-tree-indent-unit ant-tree-indent-unit-start"
/>
<span
class="ant-tree-indent-unit ant-tree-indent-unit-start"
/>
</span>
<span
class="ant-tree-switcher ant-tree-switcher-noop"
>
<span
aria-label="file"
class="anticon anticon-file ant-tree-switcher-line-icon"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="file"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494z"
/>
</svg>
</span>
</span>
<span
class="ant-tree-node-content-wrapper ant-tree-node-content-wrapper-normal"
title="leaf"
>
<span
class="ant-tree-title"
>
leaf
</span>
</span>
</div>
<div
class="ant-tree-treenode ant-tree-treenode-switcher-close"
>
<span
aria-hidden="true"
class="ant-tree-indent"
>
<span
class="ant-tree-indent-unit ant-tree-indent-unit-start"
/>
<span
class="ant-tree-indent-unit"
/>
</span>
<span
class="ant-tree-switcher ant-tree-switcher-noop"
>
<span
aria-label="file"
class="anticon anticon-file ant-tree-switcher-line-icon"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="file"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494z"
/>
</svg>
</span>
</span>
<span
class="ant-tree-node-content-wrapper ant-tree-node-content-wrapper-normal"
title="leaf"
>
<span
class="ant-tree-title"
>
leaf
</span>
</span>
</div>
<div
class="ant-tree-treenode ant-tree-treenode-switcher-close"
>
<span
aria-hidden="true"
class="ant-tree-indent"
>
<span
class="ant-tree-indent-unit ant-tree-indent-unit-start"
/>
<span
class="ant-tree-indent-unit ant-tree-indent-unit-end"
/>
</span>
<span
class="ant-tree-switcher ant-tree-switcher-noop"
>
<span
aria-label="file"
class="anticon anticon-file ant-tree-switcher-line-icon"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="file"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494z"
/>
</svg>
</span>
</span>
<span
class="ant-tree-node-content-wrapper ant-tree-node-content-wrapper-normal"
title="leaf"
>
<span
class="ant-tree-title"
>
leaf
</span>
</span>
</div>
<div
class="ant-tree-treenode ant-tree-treenode-switcher-close"
>
<span
aria-hidden="true"
class="ant-tree-indent"
>
<span
class="ant-tree-indent-unit"
/>
</span>
<span
class="ant-tree-switcher ant-tree-switcher_close"
/>
<span
class="ant-tree-node-content-wrapper ant-tree-node-content-wrapper-close"
title="parent 1-1"
>
<span
class="ant-tree-title"
>
parent 1-1
</span>
</span>
</div>
<div
class="ant-tree-treenode ant-tree-treenode-switcher-close"
>
<span
aria-hidden="true"
class="ant-tree-indent"
>
<span
class="ant-tree-indent-unit ant-tree-indent-unit-end"
/>
</span>
<span
class="ant-tree-switcher ant-tree-switcher_close"
/>
<span
class="ant-tree-node-content-wrapper ant-tree-node-content-wrapper-close"
title="parent 1-2"
>
<span
class="ant-tree-title"
>
parent 1-2
</span>
</span>
</div>
</div>
</div>
</div>,
]
`;

View File

@ -0,0 +1,54 @@
---
order: 8
title:
zh-CN: 自定义展开/折叠图标
en-US: Customize collapse/expand icon
---
## zh-CN
自定义展开/折叠图标。
## en-US
customize collapse/expand icon of tree node
```jsx
import { Tree, Icon } from 'antd';
const { TreeNode } = Tree;
class Demo extends React.Component {
onSelect = (selectedKeys, info) => {
console.log('selected', selectedKeys, info);
};
render() {
return (
<Tree
showLine
switcherIcon={<Icon type="down" />}
defaultExpandedKeys={['0-0-0']}
onSelect={this.onSelect}
>
<TreeNode title="parent 1" key="0-0">
<TreeNode title="parent 1-0" key="0-0-0">
<TreeNode title="leaf" key="0-0-0-0" />
<TreeNode title="leaf" key="0-0-0-1" />
<TreeNode title="leaf" key="0-0-0-2" />
</TreeNode>
<TreeNode title="parent 1-1" key="0-0-1">
<TreeNode title="leaf" key="0-0-1-0" />
</TreeNode>
<TreeNode title="parent 1-2" key="0-0-2">
<TreeNode title="leaf" key="0-0-2-0" />
<TreeNode title="leaf" key="0-0-2-1" />
</TreeNode>
</TreeNode>
</Tree>
);
}
}
ReactDOM.render(<Demo />, mountNode);
```

View File

@ -161,6 +161,8 @@
> span {
display: block;
width: 100%;
height: 100%;
}
.@{iconfont-css-prefix}-loading,
@ -378,6 +380,7 @@
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.@{upload-item}-name {

View File

@ -7,6 +7,7 @@ title: Third-Party Libraries
| Category | Recommended Components |
| --- | --- |
| Visualization and charts | [🔥 AntV 数据可视化解决方案](https://antv.alipay.com) [BizCharts](https://github.com/alibaba/BizCharts) [recharts](https://github.com/recharts/recharts/) [viser](https://viserjs.github.io/) |
| Router | [react-router](https://github.com/ReactTraining/react-router) |
| Layout | [@rebass/grid](https://github.com/rebassjs/grid) [react-blocks](http://whoisandy.github.io/react-blocks/) [react-flexbox-grid](https://github.com/roylee0704/react-flexbox-grid) |
| Drag and drop | [react-beautiful-dnd](https://github.com/atlassian/react-beautiful-dnd/) [react-dnd](https://github.com/gaearon/react-dnd) [react-sortable-hoc](https://github.com/clauderic/react-sortable-hoc) |
@ -19,7 +20,6 @@ title: Third-Party Libraries
| Document head manager | [react-helmet](https://github.com/nfl/react-helmet) [react-document-title](https://github.com/gaearon/react-document-title) |
| Icons | [react-fa](https://github.com/andreypopp/react-fa) [react-icons](https://github.com/gorangajic/react-icons) |
| QR Code | [qrcode.react](https://github.com/zpao/qrcode.react) |
| Charts | [BizCharts](https://github.com/alibaba/BizCharts) [recharts](https://github.com/recharts/recharts/) [victory](https://github.com/FormidableLabs/victory) |
| Visual Graph Editor | [GGEditor](https://github.com/gaoli/GGEditor) |
| Top Progress Bar | [nprogress](https://github.com/rstacruz/nprogress) |
| i18n | [react-intl](https://github.com/yahoo/react-intl) |
@ -27,6 +27,7 @@ title: Third-Party Libraries
| Markdown renderer | [react-markdown](http://rexxars.github.io/react-markdown/) |
| Infinite Scroll | [react-virtualized](https://github.com/bvaughn/react-virtualized) [antd-table-infinity](https://github.com/Leonard-Li777/antd-table-infinity) |
| Map | [react-google-maps](https://github.com/tomchentw/react-google-maps) [google-map-react](https://github.com/istarkov/google-map-react) [react-amap](https://github.com/ElemeFE/react-amap) |
| Video | [react-player](https://github.com/CookPete/react-player) [video-react](https://github.com/video-react/video-react) [video.js](http://docs.videojs.com/tutorial-react.html) |
| Context Menu | [react-contextmenu](https://github.com/vkbansal/react-contextmenu/) [react-contexify](https://github.com/fkhadra/react-contexify) |
| Emoji | [emoji-mart](https://github.com/missive/emoji-mart) |
| Split View | [react-split-pane](https://github.com/tomkp/react-split-pane) |

View File

@ -7,6 +7,7 @@ title: 社区精选组件
| 类型 | 推荐组件 |
| --- | --- |
| 可视化图表 | [🔥 AntV 数据可视化解决方案](https://antv.alipay.com) [BizCharts](https://github.com/alibaba/BizCharts) [recharts](https://github.com/recharts/recharts/) [viser](https://viserjs.github.io/) |
| 路由 | [react-router](https://github.com/ReactTraining/react-router) |
| 布局 | [@rebass/grid](https://github.com/rebassjs/grid) [react-blocks](https://github.com/whoisandy/react-blocks) [react-flexbox-grid](https://github.com/roylee0704/react-flexbox-grid) |
| 拖拽 | [react-beautiful-dnd](https://github.com/atlassian/react-beautiful-dnd/) [react-dnd](https://github.com/gaearon/react-dnd) [react-sortable-hoc](https://github.com/clauderic/react-sortable-hoc) |
@ -19,7 +20,6 @@ title: 社区精选组件
| 页面 meta 属性 | [react-helmet](https://github.com/nfl/react-helmet) [react-document-title](https://github.com/gaearon/react-document-title) |
| 图标 | [react-fa](https://github.com/andreypopp/react-fa) [react-icons](https://github.com/gorangajic/react-icons) |
| 二维码 | [qrcode.react](https://github.com/zpao/qrcode.react) |
| 可视化图表 | [BizCharts](https://github.com/alibaba/BizCharts) [recharts](https://github.com/recharts/recharts/) [victory](https://github.com/FormidableLabs/victory) |
| 可视化图编辑器 | [GGEditor](https://github.com/gaoli/GGEditor) |
| 顶部进度条 | [nprogress](https://github.com/rstacruz/nprogress) |
| 应用国际化 | [react-intl](https://github.com/yahoo/react-intl) |
@ -27,6 +27,7 @@ title: 社区精选组件
| Markdown 渲染 | [react-markdown](http://rexxars.github.io/react-markdown/) |
| 无限滚动 | [react-virtualized](https://github.com/bvaughn/react-virtualized) [antd-table-infinity](https://github.com/Leonard-Li777/antd-table-infinity) |
| 地图 | [react-google-maps](https://github.com/tomchentw/react-google-maps) [google-map-react](https://github.com/istarkov/google-map-react) [react-amap 高德](https://github.com/ElemeFE/react-amap) |
| 视频播放 | [react-player](https://github.com/CookPete/react-player) [video-react](https://github.com/video-react/video-react) [video.js](http://docs.videojs.com/tutorial-react.html) |
| 右键菜单 | [react-contextmenu](https://github.com/vkbansal/react-contextmenu/) [react-contexify](https://github.com/fkhadra/react-contexify) |
| Emoji | [emoji-mart](https://github.com/missive/emoji-mart) |
| 分割面板 | [react-split-pane](https://github.com/tomkp/react-split-pane) |

View File

@ -23,10 +23,10 @@ As a part of nature, it will have deep influence on user behavior, and designers
- The visual system plays the most important role in human perception and cognition. By refining the objective laws in nature and applying it to the interface design, a more layered product experience is created. In addition, hearing systems or tactile systems could be added in future to bring more dimensions and more real product experience. See visual language.
- In the real product design, a series of methods such as behavior analysis, artificial intelligence and sensors could be applied to assist users to make effective decisions and reduce extra operations of users, so as to save users' mental and physical resources and make human-computer interaction more natural.
## Determinacy
## Certainty
<div>
<img src="https://gw.alipayobjects.com/zos/rmsportal/ZxgRAMzXNrxHTcvMLchq.png" alt="Determine" />
<img src="https://gw.alipayobjects.com/zos/rmsportal/ZxgRAMzXNrxHTcvMLchq.png" alt="Certainty" />
</div>
The designers needs to make better design decisions and create a high-definition and low-entropy atmosphere for developer team. Meanwhile, different designers could produce the same design output which fit business needs based on the same understanding of business requirements and design system.

View File

@ -150,9 +150,12 @@
"@sentry/browser": "^5.4.0",
"@types/classnames": "^2.2.8",
"@types/gtag.js": "^0.0.3",
"@types/lodash": "^4.14.139",
"@types/prop-types": "^15.7.1",
"@types/react": "~16.9.1",
"@types/raf": "^3.4.0",
"@types/react": "^16.9.0",
"@types/react-dom": "^16.8.4",
"@types/shallowequal": "^1.1.1",
"@types/warning": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^2.0.0",
"@typescript-eslint/parser": "^2.0.0",
@ -204,7 +207,7 @@
"prettier": "^1.17.1",
"pretty-quick": "^1.11.0",
"querystring": "^0.2.0",
"rc-footer": "^0.4.0",
"rc-footer": "^0.5.0",
"rc-queue-anim": "^1.6.12",
"rc-scroll-anim": "^2.5.8",
"rc-tween-one": "^2.4.1",
@ -230,7 +233,7 @@
"scrollama": "^2.0.0",
"simple-git": "^1.113.0",
"stylelint": "^11.0.0",
"stylelint-config-prettier": "^5.2.0",
"stylelint-config-prettier": "^6.0.0",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-config-standard": "^19.0.0",
"stylelint-declaration-block-no-ignored-properties": "^2.1.0",

View File

@ -111,7 +111,7 @@
padding: 0 24px;
font-size: 16px;
font-family: Avenir, @font-family, sans-serif;
line-height: 38px;
line-height: 40px;
text-align: center;
text-decoration: none;
border: 1px solid #2f54eb;

View File

@ -4,7 +4,7 @@ declare module 'rc-calendar*';
declare module 'rc-time-picker*';
declare module 'rc-pagination*';
declare module 'rc-pagination/*';
declare module 'omit.js';
@ -12,14 +12,10 @@ declare module 'rc-animate*';
declare module 'rc-util*';
declare module 'shallowequal';
declare module 'css-animation*';
declare module 'rc-cascader';
declare module 'array-tree-filter';
declare module 'rc-checkbox';
declare module 'rc-radio';
@ -45,7 +41,7 @@ declare module 'rc-calendar';
declare module 'rc-input-number';
declare module 'rc-pagination';
declare module 'rc-collapse';
declare module 'rc-notification';
@ -73,8 +69,6 @@ declare module 'rc-tree-select';
declare module 'rc-upload';
declare module 'rc-collapse';
declare module 'rc-form*';
declare module 'react-lazy-load';
@ -89,22 +83,6 @@ declare module '*.json' {
export default value;
}
declare module 'lodash/isEqual';
declare module 'lodash/debounce';
declare module 'lodash/padStart';
declare module 'lodash/padEnd';
declare module 'lodash/repeat';
declare module 'lodash/uniqBy';
declare module 'lodash/findIndex';
declare module 'raf';
declare module 'react-lifecycles-compat';
declare module 'react-copy-to-clipboard';