mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-12-02 12:09:14 +08:00
⚡ support allowClear for Input
This commit is contained in:
parent
144b0f4c60
commit
d61ad7445a
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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('');
|
||||
});
|
||||
});
|
||||
|
26
components/input/demo/allowClear.md
Normal file
26
components/input/demo/allowClear.md
Normal 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);
|
||||
````
|
Loading…
Reference in New Issue
Block a user