support allowClear for Input

This commit is contained in:
afc163 2018-12-26 22:34:29 +08:00 committed by 偏右
parent 144b0f4c60
commit d61ad7445a
5 changed files with 218 additions and 7 deletions

View File

@ -7,6 +7,7 @@ import Search from './Search';
import TextArea from './TextArea';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import Password from './Password';
import Icon from '../icon';
import { Omit, tuple } from '../_util/type';
function fixControlledValue<T>(value: T) {
@ -27,6 +28,7 @@ export interface InputProps
addonAfter?: React.ReactNode;
prefix?: React.ReactNode;
suffix?: React.ReactNode;
allowClear?: Boolean;
}
export default class Input extends React.Component<InputProps, any> {
@ -38,6 +40,7 @@ export default class Input extends React.Component<InputProps, any> {
static defaultProps = {
type: 'text',
disabled: false,
allowClear: false,
};
static propTypes = {
@ -59,6 +62,11 @@ export default class Input extends React.Component<InputProps, any> {
onBlur: PropTypes.func,
prefix: PropTypes.node,
suffix: PropTypes.node,
allowClear: PropTypes.bool,
};
state = {
value: '',
};
input: HTMLInputElement;
@ -98,6 +106,64 @@ export default class Input extends React.Component<InputProps, any> {
this.input = node;
};
setValue(
value: string,
e: React.ChangeEvent<HTMLInputElement> | React.MouseEvent<HTMLElement, MouseEvent>,
) {
const { onChange } = this.props;
if (!('value' in this.props)) {
this.setState({ value });
}
if (onChange) {
onChange(
e.target
? (e as React.ChangeEvent<HTMLInputElement>)
: {
...e,
target: this.input,
currentTarget: this.input,
},
);
}
}
handleReset = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
this.setValue('', e);
};
handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setValue(e.target.value, e);
};
renderClearIcon(prefixCls: string) {
const { allowClear } = this.props;
const { value } = this.state;
if (!allowClear || value === undefined || value === '') {
return null;
}
return (
<Icon
type="close-circle"
theme="filled"
onClick={this.handleReset}
className={`${prefixCls}-clear-icon`}
/>
);
}
renderSuffix(prefixCls: string) {
const { suffix, allowClear } = this.props;
if (suffix || allowClear) {
return (
<span className={`${prefixCls}-suffix`}>
{this.renderClearIcon(prefixCls)}
{suffix}
</span>
);
}
return null;
}
renderLabeledInput(prefixCls: string, children: React.ReactElement<any>) {
const props = this.props;
// Not wrap when there is not addons
@ -110,7 +176,6 @@ export default class Input extends React.Component<InputProps, any> {
const addonBefore = props.addonBefore ? (
<span className={addonClassName}>{props.addonBefore}</span>
) : null;
const addonAfter = props.addonAfter ? (
<span className={addonClassName}>{props.addonAfter}</span>
) : null;
@ -139,7 +204,9 @@ export default class Input extends React.Component<InputProps, any> {
renderLabeledIcon(prefixCls: string, children: React.ReactElement<any>) {
const { props } = this;
if (!('prefix' in props || 'suffix' in props)) {
const suffix = this.renderSuffix(prefixCls);
if (!('prefix' in props) && !suffix) {
return children;
}
@ -147,10 +214,6 @@ export default class Input extends React.Component<InputProps, any> {
<span className={`${prefixCls}-prefix`}>{props.prefix}</span>
) : null;
const suffix = props.suffix ? (
<span className={`${prefixCls}-suffix`}>{props.suffix}</span>
) : null;
const affixWrapperCls = classNames(props.className, `${prefixCls}-affix-wrapper`, {
[`${prefixCls}-affix-wrapper-sm`]: props.size === 'small',
[`${prefixCls}-affix-wrapper-lg`]: props.size === 'large',
@ -168,7 +231,8 @@ export default class Input extends React.Component<InputProps, any> {
}
renderInput(prefixCls: string) {
const { value, className } = this.props;
const { className } = this.props;
const { value } = this.state;
// Fix https://fb.me/react-unknown-prop
const otherProps = omit(this.props, [
'prefixCls',
@ -177,6 +241,7 @@ export default class Input extends React.Component<InputProps, any> {
'addonAfter',
'prefix',
'suffix',
'allowClear',
]);
if ('value' in this.props) {
@ -189,6 +254,8 @@ export default class Input extends React.Component<InputProps, any> {
prefixCls,
<input
{...otherProps}
value={value}
onChange={this.handleChange}
className={classNames(this.getInputClassName(prefixCls), className)}
onKeyDown={this.handleKeyDown}
ref={this.saveInput}

View File

@ -195,6 +195,22 @@ exports[`renders ./components/input/demo/addon.md correctly 1`] = `
</div>
`;
exports[`renders ./components/input/demo/allowClear.md correctly 1`] = `
<span
class="ant-input-affix-wrapper"
>
<input
class="ant-input"
placeholder="input with clear icon"
type="text"
value=""
/>
<span
class="ant-input-suffix"
/>
</span>
`;
exports[`renders ./components/input/demo/autosize-textarea.md correctly 1`] = `
<div>
<textarea

View File

@ -1,5 +1,92 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Input allowClear should change type when click 1`] = `
<Input
allowClear={true}
disabled={false}
type="text"
>
<Consumer>
<span
className="ant-input-affix-wrapper"
>
<input
className="ant-input"
disabled={false}
onChange={[Function]}
onKeyDown={[Function]}
style={null}
type="text"
value="111"
/>
<span
className="ant-input-suffix"
>
<Icon
className="ant-input-clear-icon"
onClick={[Function]}
theme="filled"
type="close-circle"
>
<i
className="anticon anticon-close-circle ant-input-clear-icon"
onClick={[Function]}
>
<IconReact
className=""
type="close-circle-fill"
>
<svg
aria-hidden="true"
className=""
data-icon="close-circle"
fill="currentColor"
height="1em"
key="svg-close-circle"
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 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 0 1-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
key="svg-close-circle-svg-0"
/>
</svg>
</IconReact>
</i>
</Icon>
</span>
</span>
</Consumer>
</Input>
`;
exports[`Input allowClear should change type when click 2`] = `
<Input
allowClear={true}
disabled={false}
type="text"
>
<Consumer>
<span
className="ant-input-affix-wrapper"
>
<input
className="ant-input"
disabled={false}
onChange={[Function]}
onKeyDown={[Function]}
style={null}
type="text"
value=""
/>
<span
className="ant-input-suffix"
/>
</span>
</Consumer>
</Input>
`;
exports[`Input should support maxLength 1`] = `
<Input
disabled={false}

View File

@ -119,3 +119,18 @@ describe('Input.Password', () => {
expect(wrapper.find('.anticon-eye').length).toBe(1);
});
});
describe('Input allowClear', () => {
it('should change type when click', () => {
const wrapper = mount(<Input allowClear />);
wrapper.find('input').simulate('change', { target: { value: '111' } });
expect(wrapper.find('input').getDOMNode().value).toEqual('111');
expect(wrapper).toMatchSnapshot();
wrapper
.find('.ant-input-clear-icon')
.at(0)
.simulate('click');
expect(wrapper).toMatchSnapshot();
expect(wrapper.find('input').getDOMNode().value).toEqual('');
});
});

View File

@ -0,0 +1,26 @@
---
order: 11
title:
zh-CN: 带移除图标
en-US: With clear icon
---
## zh-CN
带移除图标的输入框,点击图标删除所有内容。
## en-US
Input type of password.
````jsx
import { Input } from 'antd';
const onChange = (e) => {
console.log(e);
};
ReactDOM.render(
<Input placeholder="input with clear icon" allowClear onChange={onChange} />
, mountNode);
````