Skip to content

Commit

Permalink
feat(Input): TextArea support hasClear, close #4334 (#4714)
Browse files Browse the repository at this point in the history
* feat(Input): textarea support hasClear, close #4334

* feat(Input): textarea code optimization , close #4334

* feat(Input): modify the class name of the clear button close #4334

* feat(Input): Internationalization

---------

Co-authored-by: WB01081293 <gym01081293@alibaba-inc.com>
  • Loading branch information
2 people authored and eternalsky committed Jul 16, 2024
1 parent abf9683 commit 08dc27d
Show file tree
Hide file tree
Showing 22 changed files with 430 additions and 216 deletions.
18 changes: 18 additions & 0 deletions components/input/__docs__/demo/clear/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,24 @@ ReactDOM.render(
/>
<br />
<br />
<Input.TextArea
hasClear
defaultValue="clear by click"
aria-label="input with config of hasClear"
onChange={onChange}
/>
<br />
<br />
<Input.TextArea
hasClear
defaultValue="clear by click"
aria-label="input with config of hasClear"
onChange={onChange}
maxLength={100}
showLimitHint
/>
<br />
<br />
</div>,
mountNode
);
139 changes: 70 additions & 69 deletions components/input/__docs__/index.en-us.md

Large diffs are not rendered by default.

155 changes: 78 additions & 77 deletions components/input/__docs__/index.md

Large diffs are not rendered by default.

94 changes: 76 additions & 18 deletions components/input/__tests__/textarea-spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import React, { useState } from 'react';
import ReactTestUtils from 'react-dom/test-utils';
import ReactDOM from 'react-dom';
import Enzyme, { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Expand All @@ -7,7 +8,10 @@ import assert from 'power-assert';
import Input from '../index';

Enzyme.configure({ adapter: new Adapter() });

function delay(duration) {
return new Promise(resolve => setTimeout(resolve, duration));
}
/* eslint-disable no-undef, react/jsx-filename-extension */
describe('TextArea', () => {
describe('render', () => {
let parent;
Expand All @@ -23,23 +27,55 @@ describe('TextArea', () => {
});

it('should textarea isPreview', () => {
ReactDOM.render(<Input.TextArea id="ispreview-input" isPreview defaultValue="abc" />, parent);
ReactDOM.render(
<Input.TextArea id="ispreview-input" isPreview defaultValue="abc" />,
parent
);
assert(document.querySelectorAll('#ispreview-input')[0].innerText === 'abc');
});

it('should textarea isPreview compatible value null', () => {
ReactDOM.render(<Input.TextArea id="ispreview-input-null" isPreview value={null} />, parent);
ReactDOM.render(
<Input.TextArea id="ispreview-input-null" isPreview value={null} />,
parent
);
assert(document.querySelectorAll('#ispreview-input-null')[0].innerText === '');
});

it('should textarea renderPreview', () => {
ReactDOM.render(
<Input.TextArea id="renderpreview-input" isPreview defaultValue="abc" renderPreview={() => 'ddd'} />,
<Input.TextArea
id="renderpreview-input"
isPreview
defaultValue="abc"
renderPreview={() => 'ddd'}
/>,
parent
);

assert(document.querySelectorAll('#renderpreview-input')[0].innerText === 'ddd');
});
it('should support hasClear ,close #4334', async () => {
const ref = { current: null };
function Demo() {
const [value, setValue] = useState('aaa');
ref.current = { value };
return (
<Input.TextArea
value={value}
placeholder="TextArea"
aria-label="TextArea"
hasClear
onChange={v => setValue(v)}
/>
);
}
mount(<Demo />, { attachTo: parent });
await delay(100);
const btn = parent.querySelector('.next-input-clear');
ReactTestUtils.Simulate.click(btn);
assert(ref.current.value === '');
});
});

describe('behavior', () => {
Expand All @@ -48,7 +84,9 @@ describe('TextArea', () => {
const onChange = sinon.spy();
const onFocus = sinon.spy();
const onBlur = sinon.spy();
const wrapper = mount(<Input.TextArea onChange={onChange} onFocus={onFocus} onBlur={onBlur} />);
const wrapper = mount(
<Input.TextArea onChange={onChange} onFocus={onFocus} onBlur={onBlur} />
);
wrapper.find('textarea').simulate('change', { target: { value: '20' } });
assert(onChange.calledOnce);
wrapper.find('textarea').simulate('focus');
Expand Down Expand Up @@ -125,7 +163,9 @@ describe('TextArea', () => {
});

it('should support maxLength & hasLimitHint', done => {
const wrapper = mount(<Input.TextArea defaultValue={'abcd'} maxLength={10} hasLimitHint />);
const wrapper = mount(
<Input.TextArea defaultValue={'abcd'} maxLength={10} hasLimitHint />
);
assert(!wrapper.find('.next-input-len').hasClass('next-error'));

wrapper.find('textarea').simulate('change', { target: { value: '12345678901' } });
Expand Down Expand Up @@ -176,7 +216,9 @@ describe('TextArea', () => {
<Input.TextArea
ref="textarea"
onFocus={e => {
assert(this.refs.textarea.getInstance().getInputNode() !== undefined);
assert(
this.refs.textarea.getInstance().getInputNode() !== undefined
);
}}
/>
);
Expand All @@ -191,7 +233,13 @@ describe('TextArea', () => {

it('should support getValueLength', done => {
const getValueLength = sinon.spy();
mount(<Input.TextArea defaultValue="abcdef" maxLength={10} getValueLength={getValueLength} />);
mount(
<Input.TextArea
defaultValue="abcdef"
maxLength={10}
getValueLength={getValueLength}
/>
);
assert(getValueLength.calledOnce);

let getValueLength2 = value => {
Expand All @@ -208,7 +256,12 @@ describe('TextArea', () => {
assert(wrapper.find('.next-input-len').text() === '1/10');

const wrapper2 = mount(
<Input.TextArea defaultValue="abcdef" maxLength={10} hasLimitHint getValueLength={getValueLength2} />
<Input.TextArea
defaultValue="abcdef"
maxLength={10}
hasLimitHint
getValueLength={getValueLength2}
/>
);
assert(wrapper2.find('.next-input-len').text() === '1/10');

Expand All @@ -219,15 +272,23 @@ describe('TextArea', () => {
const wrapper = mount(<Input.TextArea defaultValue="abcdef" autoHeight />);
// console.log(wrapper.find('textarea[data-real]').instance().clientHeight)
// let originHeight = wrapper.find('textarea[data-real]').instance().clientHeight;
wrapper.find('textarea[data-real]').simulate('change', { target: { value: '1\n2\n3\n4\n5\n' } });
wrapper
.find('textarea[data-real]')
.simulate('change', { target: { value: '1\n2\n3\n4\n5\n' } });

// assert(wrapper.find('textarea[data-real]').at(0).getElement().clientHeight > originHeight);

const wrapper2 = mount(<Input.TextArea defaultValue="abcdef" autoHeight={{ minRows: 2, maxRows: 4 }} />);
const wrapper2 = mount(
<Input.TextArea defaultValue="abcdef" autoHeight={{ minRows: 2, maxRows: 4 }} />
);
// console.log(wrapper2.find('textarea[data-real]').instance().clientHeight)
// let originHeight = wrapper2.find('textarea[data-real]').instance().clientHeight;
wrapper2.find('textarea[data-real]').simulate('change', { target: { value: '1\n2\n3\n4\n5\n' } });
wrapper2.find('textarea[data-real]').simulate('change', { target: { value: '1\n2\n3\n4' } });
wrapper2
.find('textarea[data-real]')
.simulate('change', { target: { value: '1\n2\n3\n4\n5\n' } });
wrapper2
.find('textarea[data-real]')
.simulate('change', { target: { value: '1\n2\n3\n4' } });

//

Expand All @@ -241,10 +302,7 @@ describe('TextArea', () => {
}
}
const wrapper = mount(<App />);
wrapper
.ref('textarea')
.getInstance()
.focus();
wrapper.ref('textarea').getInstance().focus();
wrapper.update();
// assert(wrapper.find('.next-input').hasClass('next-focus'));

Expand Down
31 changes: 21 additions & 10 deletions components/input/base.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { polyfill } from 'react-lifecycles-compat';

import ConfigProvider from '../config-provider';
import { func } from '../util';
import zhCN from '../locale/zh-cn';


class Base extends React.Component {
static propTypes = {
...ConfigProvider.propTypes,
Expand Down Expand Up @@ -240,7 +240,26 @@ class Base extends React.Component {
});
this.props.onBlur(e);
}

handleKeyDownFromClear = e => {
if (e.keyCode === 13) {
this.onClear(e);
}
};

onClear(e) {
if (this.props.disabled) {
return;
}
// 非受控模式清空内部数据
if (!('value' in this.props)) {
this.setState({
value: '',
});
}
this.props.onChange('', e, 'clear');
this.focus();
}
renderLength() {
const { maxLength, showLimitHint, prefix, rtl } = this.props;
const len = maxLength > 0 && this.state.value ? this.getValueLength(this.state.value) : 0;
Expand All @@ -252,18 +271,10 @@ class Base extends React.Component {

const content = rtl ? `${maxLength}/${len}` : `${len}/${maxLength}`;

return maxLength && showLimitHint ? <span className={classesLenWrap}>{content}</span> : null;
return maxLength && showLimitHint ? <span className={classesLenWrap}>{content}</span> : null
}

renderControl() {
const lenWrap = this.renderLength();

return lenWrap ? (
<span onClick={() => this.focus()} className={`${this.props.prefix}input-control`}>
{lenWrap}
</span>
) : null;
}

getClass() {
const { disabled, state, prefix } = this.props;
Expand Down
4 changes: 4 additions & 0 deletions components/input/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ export interface TextAreaProps extends HTMLAttributesWeak, CommonProps {
* 开启后会过滤输入法中间字母状态,文字输入完成后才会触发 onChange
*/
composition?: boolean;
/**
* 是否出现clear按钮
*/
hasClear?: boolean;
}

export class TextArea extends React.Component<TextAreaProps, any> {}
Expand Down
21 changes: 0 additions & 21 deletions components/input/input.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,27 +240,6 @@ class Input extends Base {
this.onKeyDown(e);
};

handleKeyDownFromClear = e => {
if (e.keyCode === 13) {
this.onClear(e);
}
};

onClear(e) {
if (this.props.disabled) {
return;
}

// 非受控模式清空内部数据
if (!('value' in this.props)) {
this.setState({
value: '',
});
}
this.props.onChange('', e, 'clear');
this.focus();
}

render() {
const {
size,
Expand Down
Loading

0 comments on commit 08dc27d

Please sign in to comment.