Merge pull request #15754 from ant-design/merge-master

Feature merge master
This commit is contained in:
zombieJ 2019-03-30 16:26:19 +08:00 committed by GitHub
commit c91d7d1a1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 319 additions and 135 deletions

View File

@ -13,9 +13,11 @@ export default class InputElement extends React.Component<InputElementProps, any
? this.ele.focus()
: (ReactDOM.findDOMNode(this.ele) as HTMLInputElement).focus();
};
blur = () => {
this.ele.blur ? this.ele.blur() : (ReactDOM.findDOMNode(this.ele) as HTMLInputElement).blur();
};
saveRef = (ele: HTMLInputElement) => {
this.ele = ele;
const { ref: childRef } = this.props.children as any;
@ -23,6 +25,7 @@ export default class InputElement extends React.Component<InputElementProps, any
childRef(ele);
}
};
render() {
return React.cloneElement(
this.props.children,

View File

@ -0,0 +1,50 @@
import React from 'react';
import { mount } from 'enzyme';
import AutoComplete from '..';
import focusTest from '../../../tests/shared/focusTest';
describe('AutoComplete could be focus', () => {
focusTest(AutoComplete);
});
describe('AutoComplete children could be focus', () => {
beforeAll(() => {
jest.useFakeTimers();
});
let container;
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});
afterAll(() => {
jest.useRealTimers();
});
afterEach(() => {
document.body.removeChild(container);
});
it('focus() and onFocus', () => {
const handleFocus = jest.fn();
const wrapper = mount(
<AutoComplete onFocus={handleFocus} />
, { attachTo: container });
wrapper.find('input').instance().focus();
jest.runAllTimers();
expect(handleFocus).toBeCalled();
});
it('blur() and onBlur', () => {
const handleBlur = jest.fn();
const wrapper = mount(
<AutoComplete onBlur={handleBlur} />
, { attachTo: container });
wrapper.find('input').instance().focus();
jest.runAllTimers();
wrapper.find('input').instance().blur();
jest.runAllTimers();
expect(handleBlur).toBeCalled();
});
});

View File

@ -1,11 +1,8 @@
import React from 'react';
import { mount } from 'enzyme';
import AutoComplete from '..';
import focusTest from '../../../tests/shared/focusTest';
describe('AutoComplete with Custom Input Element Render', () => {
focusTest(AutoComplete);
it('AutoComplete with custom Input render perfectly', () => {
const wrapper = mount(
<AutoComplete dataSource={['12345', '23456', '34567']}>

View File

@ -7,7 +7,7 @@ import omit from 'omit.js';
import Icon from '../icon';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import Wave from '../_util/wave';
import { tuple } from '../_util/type';
import { Omit, tuple } from '../_util/type';
const rxTwoCNChar = /^[\u4e00-\u9fa5]{2}$/;
const isTwoCNChar = rxTwoCNChar.test.bind(rxTwoCNChar);
@ -67,13 +67,13 @@ export type AnchorButtonProps = {
target?: string;
onClick?: React.MouseEventHandler<HTMLAnchorElement>;
} & BaseButtonProps &
React.AnchorHTMLAttributes<HTMLAnchorElement>;
Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'type'>;
export type NativeButtonProps = {
htmlType?: ButtonHTMLType;
onClick?: React.MouseEventHandler<HTMLButtonElement>;
} & BaseButtonProps &
React.ButtonHTMLAttributes<HTMLButtonElement>;
Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'type'>;
export type ButtonProps = AnchorButtonProps | NativeButtonProps;
@ -90,6 +90,7 @@ class Button extends React.Component<ButtonProps, ButtonState> {
loading: false,
ghost: false,
block: false,
htmlType: 'button',
};
static propTypes = {
@ -219,6 +220,7 @@ class Button extends React.Component<ButtonProps, ButtonState> {
break;
case 'small':
sizeCls = 'sm';
break;
default:
break;
}
@ -265,7 +267,7 @@ class Button extends React.Component<ButtonProps, ButtonState> {
<Wave>
<button
{...otherProps as NativeButtonProps}
type={htmlType || 'button'}
type={htmlType}
className={classes}
onClick={this.handleClick}
ref={this.saveButtonRef}

View File

@ -17,7 +17,7 @@ To get a customized button, just set `type`/`shape`/`size`/`loading`/`disabled`.
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| disabled | disabled state of button | boolean | `false` |
| ghost | make background transparent and invert text and border colors, added in 2.7 | boolean | false |
| ghost | make background transparent and invert text and border colors, added in 2.7 | boolean | `false` |
| href | redirect url of link button | string | - |
| htmlType | set the original html `type` of `button`, see: [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-type) | string | `button` |
| icon | set the icon of button, see: Icon component | string | - |

View File

@ -25,6 +25,7 @@ export interface AbstractCheckboxGroupProps {
}
export interface CheckboxGroupProps extends AbstractCheckboxGroupProps {
name?: string;
defaultValue?: Array<CheckboxValueType>;
value?: Array<CheckboxValueType>;
onChange?: (checkedValue: Array<CheckboxValueType>) => void;

View File

@ -11113,10 +11113,18 @@ exports[`ConfigProvider components Spin configProvider 1`] = `
<span
class="config-spin-dot config-spin-dot-spin"
>
<i />
<i />
<i />
<i />
<i
class="config-spin-dot-item"
/>
<i
class="config-spin-dot-item"
/>
<i
class="config-spin-dot-item"
/>
<i
class="config-spin-dot-item"
/>
</span>
</div>
`;
@ -11128,10 +11136,18 @@ exports[`ConfigProvider components Spin normal 1`] = `
<span
class="ant-spin-dot ant-spin-dot-spin"
>
<i />
<i />
<i />
<i />
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
</span>
</div>
`;
@ -11143,10 +11159,18 @@ exports[`ConfigProvider components Spin prefixCls 1`] = `
<span
class="prefix-Spin-dot prefix-Spin-dot-spin"
>
<i />
<i />
<i />
<i />
<i
class="prefix-Spin-dot-item"
/>
<i
class="prefix-Spin-dot-item"
/>
<i
class="prefix-Spin-dot-item"
/>
<i
class="prefix-Spin-dot-item"
/>
</span>
</div>
`;

View File

@ -999,6 +999,32 @@ exports[`renders ./components/input/demo/presuffix.md correctly 1`] = `
type="text"
value=""
/>
<span
class="ant-input-suffix"
>
<i
aria-label="icon: info-circle"
class="anticon anticon-info-circle"
style="color:rgba(0,0,0,.45)"
>
<svg
aria-hidden="true"
class=""
data-icon="info-circle"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"
/>
<path
d="M464 336a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm72 112h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V456c0-4.4-3.6-8-8-8z"
/>
</svg>
</i>
</span>
</span>
`;

View File

@ -14,55 +14,17 @@ title:
Add prefix or suffix icons inside input.
````jsx
import { Input, Icon } from 'antd';
import { Input, Tooltip, Icon } from 'antd';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
userName: '',
};
}
emitEmpty = () => {
this.userNameInput.focus();
this.setState({ userName: '' });
}
onChangeUserName = (e) => {
this.setState({ userName: e.target.value });
}
render() {
const { userName } = this.state;
const suffix = userName ? <Icon type="close-circle" onClick={this.emitEmpty} /> : null;
return (
<Input
placeholder="Enter your username"
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
suffix={suffix}
value={userName}
onChange={this.onChangeUserName}
ref={node => this.userNameInput = node}
/>
);
}
}
ReactDOM.render(<App />, mountNode);
````
````css
.anticon-close-circle {
cursor: pointer;
color: #ccc;
transition: color 0.3s;
font-size: 12px;
}
.anticon-close-circle:hover {
color: #999;
}
.anticon-close-circle:active {
color: #666;
}
ReactDOM.render(
<Input
placeholder="Enter your username"
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
suffix={
<Tooltip title="Extra information">
<Icon type="info-circle" style={{ color: 'rgba(0,0,0,.45)' }} />
</Tooltip>
}
/>
, mountNode);
````

View File

@ -32,11 +32,33 @@
}
.@{ant-prefix}-input-password-icon {
color: @text-color-secondary;
cursor: pointer;
transition: all 0.3s;
&:hover {
color: #333;
}
}
.@{ant-prefix}-input-clear-icon {
color: @disabled-color;
font-size: @font-size-sm;
vertical-align: top;
cursor: pointer;
transition: color 0.3s;
&:hover {
color: @text-color-secondary;
}
&:active {
color: @text-color;
}
+ i {
margin-left: 6px;
}
}
@import './search-input';

View File

@ -377,10 +377,18 @@ exports[`renders ./components/list/demo/loadmore.md correctly 1`] = `
<span
class="ant-spin-dot ant-spin-dot-spin"
>
<i />
<i />
<i />
<i />
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
</span>
</div>
</div>

View File

@ -31,11 +31,11 @@ export type ListSize = 'small' | 'default' | 'large';
export type ListItemLayout = 'horizontal' | 'vertical';
export interface ListProps {
export interface ListProps<T> {
bordered?: boolean;
className?: string;
children?: React.ReactNode;
dataSource: any;
dataSource: T[];
extra?: React.ReactNode;
grid?: ListGridType;
id?: string;
@ -45,7 +45,7 @@ export interface ListProps {
pagination?: PaginationConfig | false;
prefixCls?: string;
rowKey?: any;
renderItem: any;
renderItem: (item: T, index: number) => React.ReactNode;
size?: ListSize;
split?: boolean;
header?: React.ReactNode;
@ -57,7 +57,7 @@ export interface ListLocale {
emptyText: React.ReactNode | (() => React.ReactNode);
}
export default class List extends React.Component<ListProps> {
export default class List<T> extends React.Component<ListProps<T>> {
static Item: typeof Item = Item;
static childContextTypes = {
@ -70,7 +70,7 @@ export default class List extends React.Component<ListProps> {
bordered: false,
split: true,
loading: false,
pagination: false as ListProps['pagination'],
pagination: false as ListProps<any>['pagination'],
};
state = {

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import { Item } from 'rc-menu';
import Tooltip from '../tooltip';
import { ClickParam } from './index';
import { ClickParam } from '.';
export interface MenuItemProps {
rootPrefixCls?: string;
@ -16,33 +16,29 @@ export interface MenuItemProps {
onMouseLeave?: (e: { key: string; domEvent: MouseEvent }) => void;
}
class MenuItem extends React.Component<MenuItemProps> {
static isMenuItem = 1;
export default class MenuItem extends React.Component<MenuItemProps> {
static isMenuItem = true;
private menuItem: this;
onKeyDown = (e: React.MouseEvent<HTMLElement>) => {
this.menuItem.onKeyDown(e);
};
saveMenuItem = (menuItem: this) => {
this.menuItem = menuItem;
};
render() {
const { level, children, rootPrefixCls } = this.props;
const { title, ...rest } = this.props;
let titleNode;
titleNode = title || (level === 1 ? children : '');
render() {
const { rootPrefixCls, title, ...rest } = this.props;
return (
<Tooltip
title={titleNode}
title={title}
placement="right"
overlayClassName={`${rootPrefixCls}-inline-collapsed-tooltip`}
>
<Item {...rest} title={title} ref={this.saveMenuItem} />
<Item {...rest} rootPrefixCls={rootPrefixCls} title={title} ref={this.saveMenuItem} />
</Tooltip>
);
}
}
export default MenuItem;

View File

@ -588,4 +588,31 @@ describe('Menu', () => {
wrapper.update();
expect(wrapper.instance().contextSiderCollapsed).toBe(false);
});
it('MenuItem should not render Tooltip when inlineCollapsed is false', () => {
const wrapper = mount(
<Menu defaultSelectedKeys={['mail']} defaultOpenKeys={['mail']} mode="horizontal">
<Menu.Item key="mail">
<Icon type="mail" />
Navigation One
</Menu.Item>
<Menu.Item key="app">
<Icon type="appstore" />
Navigation Two
</Menu.Item>
<Menu.Item key="alipay">
<a href="https://ant.design" target="_blank" rel="noopener noreferrer">
Navigation Four - Link
</a>
</Menu.Item>
</Menu>,
);
wrapper
.find('MenuItem')
.first()
.simulate('mouseenter');
jest.runAllTimers();
wrapper.update();
expect(wrapper.find('.ant-tooltip-inner').length).toBe(0);
});
});

View File

@ -172,10 +172,19 @@ class Menu extends React.Component<MenuProps, MenuState> {
// when inlineCollapsed menu width animation finished
// https://github.com/ant-design/ant-design/issues/12864
const widthCollapsed = e.propertyName === 'width' && e.target === e.currentTarget;
// Fix SVGElement e.target.className.indexOf is not a function
// https://github.com/ant-design/ant-design/issues/15699
const { className } = e.target as (HTMLElement | SVGElement);
// SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during an animation.
const classNameValue =
Object.prototype.toString.call(className) === '[object SVGAnimatedString]'
? className.animVal
: className;
// Fix for <Menu style={{ width: '100%' }} />, the width transition won't trigger when menu is collapsed
// https://github.com/ant-design/ant-design-pro/issues/2783
const iconScaled =
e.propertyName === 'font-size' && (e.target as HTMLElement).className.indexOf('anticon') >= 0;
const iconScaled = e.propertyName === 'font-size' && classNameValue.indexOf('anticon') >= 0;
if (widthCollapsed || iconScaled) {
this.restoreModeVerticalFromInline();
}

View File

@ -51,7 +51,7 @@ const renderBreadcrumb = (breadcrumb: BreadcrumbProps) => {
const renderHeader = (prefixCls: string, props: PageHeaderProps) => {
const { breadcrumb, backIcon, onBack } = props;
if (breadcrumb && breadcrumb.routes && breadcrumb.routes.length > 2) {
if (breadcrumb && breadcrumb.routes && breadcrumb.routes.length >= 2) {
return renderBreadcrumb(breadcrumb);
}
return renderBack(prefixCls, backIcon, onBack);

View File

@ -7,10 +7,18 @@ exports[`renders ./components/spin/demo/basic.md correctly 1`] = `
<span
class="ant-spin-dot ant-spin-dot-spin"
>
<i />
<i />
<i />
<i />
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
</span>
</div>
`;
@ -94,10 +102,18 @@ exports[`renders ./components/spin/demo/inside.md correctly 1`] = `
<span
class="ant-spin-dot ant-spin-dot-spin"
>
<i />
<i />
<i />
<i />
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
</span>
</div>
</div>
@ -154,10 +170,18 @@ exports[`renders ./components/spin/demo/size.md correctly 1`] = `
<span
class="ant-spin-dot ant-spin-dot-spin"
>
<i />
<i />
<i />
<i />
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
</span>
</div>
<div
@ -166,10 +190,18 @@ exports[`renders ./components/spin/demo/size.md correctly 1`] = `
<span
class="ant-spin-dot ant-spin-dot-spin"
>
<i />
<i />
<i />
<i />
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
</span>
</div>
<div
@ -178,10 +210,18 @@ exports[`renders ./components/spin/demo/size.md correctly 1`] = `
<span
class="ant-spin-dot ant-spin-dot-spin"
>
<i />
<i />
<i />
<i />
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
</span>
</div>
</div>
@ -198,10 +238,18 @@ exports[`renders ./components/spin/demo/tip.md correctly 1`] = `
<span
class="ant-spin-dot ant-spin-dot-spin"
>
<i />
<i />
<i />
<i />
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
</span>
<div
class="ant-spin-text"

View File

@ -47,10 +47,10 @@ function renderIndicator(prefixCls: string, props: SpinProps): React.ReactNode {
return (
<span className={classNames(dotClassName, `${prefixCls}-dot-spin`)}>
<i />
<i />
<i />
<i />
<i className={`${prefixCls}-dot-item`} />
<i className={`${prefixCls}-dot-item`} />
<i className={`${prefixCls}-dot-item`} />
<i className={`${prefixCls}-dot-item`} />
</span>
);
}

View File

@ -125,7 +125,7 @@
.square(@spin-dot-size);
i {
&-item {
position: absolute;
display: block;
width: 9px;
@ -136,6 +136,7 @@
transform-origin: 50% 50%;
opacity: 0.3;
animation: antSpinMove 1s infinite linear alternate;
&:nth-child(1) {
top: 0;
left: 0;

View File

@ -219,7 +219,7 @@
@screen-xl: 1200px;
@screen-xl-min: @screen-xl;
// Extra extra large screen / large descktop
// Extra extra large screen / large desktop
@screen-xxl: 1600px;
@screen-xxl-min: @screen-xxl;

View File

@ -499,10 +499,18 @@ exports[`Table renders empty table without emptyText when loading 1`] = `
<span
class="ant-spin-dot ant-spin-dot-spin"
>
<i />
<i />
<i />
<i />
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
</span>
</div>
</div>

View File

@ -89,11 +89,11 @@ Same as `onRow` `onHeaderRow` `onCell` `onHeaderCell`
<Table
onRow={(record, rowIndex) => {
return {
onClick: (event) => {}, // click row
onDoubleClick: (event) => {}, // double click row
onContextMenu: (event) => {} // right button click row
onMouseEnter: (event) => {} // mouse enter row
onMouseLeave: (event) => {} // mouse leave row
onClick: (event) => {}, // click row
onDoubleClick: (event) => {}, // double click row
onContextMenu: (event) => {}, // right button click row
onMouseEnter: (event) => {}, // mouse enter row
onMouseLeave: (event) => {}, // mouse leave row
};
}}
onHeaderRow={(column) => {

View File

@ -11,7 +11,7 @@
padding: 0 7px;
font-size: @tag-font-size;
line-height: 20px;
white-space: normal;
white-space: nowrap;
background: @tag-default-bg;
border: @border-width-base @border-style-base @border-color-base;
border-radius: @border-radius-base;

View File

@ -128,7 +128,7 @@ export interface TreeProps {
/** 异步加载数据 */
loadData?: (node: AntTreeNode) => PromiseLike<any>;
loadedKeys?: string[];
onLoaded?: (loadedKeys: string[], info: { event: 'load'; node: AntTreeNode }) => void;
onLoad?: (loadedKeys: string[], info: { event: 'load'; node: AntTreeNode }) => void;
/** 响应右键点击 */
onRightClick?: (options: AntTreeNodeMouseEvent) => void;
/** 设置节点可拖拽IE>8*/