Skip to content

Commit 2fb52d6

Browse files
authored
feat: textarea support word count (#3009)
1 parent 9ac18c5 commit 2fb52d6

File tree

6 files changed

+61
-7
lines changed

6 files changed

+61
-7
lines changed

components/input/ClearableLabeledInput.jsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ const ClearableLabeledInput = {
3434
addonAfter: PropTypes.any,
3535
readonly: PropTypes.bool,
3636
isFocused: PropTypes.bool,
37-
style: PropTypes.object,
3837
},
3938
methods: {
4039
renderClearIcon(prefixCls) {
@@ -74,6 +73,7 @@ const ClearableLabeledInput = {
7473

7574
renderLabeledIcon(prefixCls, element) {
7675
const props = this.$props;
76+
const { style } = this.$attrs;
7777
const suffix = this.renderSuffix(prefixCls);
7878
if (!hasPrefixSuffix(this)) {
7979
return cloneElement(element, {
@@ -94,7 +94,7 @@ const ClearableLabeledInput = {
9494
props.suffix && props.allowClear && this.$props.value,
9595
});
9696
return (
97-
<span class={affixWrapperCls} style={props.style}>
97+
<span class={affixWrapperCls} style={style}>
9898
{prefix}
9999
{cloneElement(element, {
100100
style: null,

components/input/TextArea.jsx

+35-4
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import { hasProp, getOptionProps } from '../_util/props-util';
66
import { ConfigConsumerProps } from '../config-provider';
77
import { fixControlledValue, resolveOnChange } from './Input';
88
import PropTypes from '../_util/vue-types';
9+
import classNames from '../_util/classNames';
910

1011
const TextAreaProps = {
1112
...inputProps,
1213
autosize: PropTypes.oneOfType([Object, Boolean]),
1314
autoSize: PropTypes.oneOfType([Object, Boolean]),
15+
showCount: PropTypes.bool,
1416
};
1517

1618
export default {
@@ -100,9 +102,13 @@ export default {
100102

101103
renderTextArea(prefixCls) {
102104
const props = getOptionProps(this);
105+
const { style, class: customClass } = this.$attrs;
103106
const resizeProps = {
104107
...props,
105108
...this.$attrs,
109+
style: style && !props.showCount,
110+
class: customClass && !props.showCount,
111+
showCount: null,
106112
prefixCls,
107113
onInput: this.handleChange,
108114
onChange: this.handleChange,
@@ -112,19 +118,44 @@ export default {
112118
},
113119
},
114120
render() {
115-
const { stateValue, prefixCls: customizePrefixCls } = this;
121+
const { stateValue, prefixCls: customizePrefixCls, maxlength, showCount } = this;
122+
const { style, class: customClass } = this.$attrs;
116123
const getPrefixCls = this.configProvider.getPrefixCls;
117124
const prefixCls = getPrefixCls('input', customizePrefixCls);
118-
125+
let value = fixControlledValue(stateValue);
126+
// Max length value
127+
const hasMaxlength = Number(maxlength) > 0;
128+
value = hasMaxlength ? value.slice(0, maxlength) : value;
119129
const props = {
120130
...getOptionProps(this),
121131
...this.$attrs,
122132
prefixCls,
123133
inputType: 'text',
124-
value: fixControlledValue(stateValue),
125134
element: this.renderTextArea(prefixCls),
126135
handleReset: this.handleReset,
127136
};
128-
return <ClearableLabeledInput {...props} ref={this.saveClearableInput} />;
137+
138+
let textareaNode = (
139+
<ClearableLabeledInput {...props} value={value} ref={this.saveClearableInput} />
140+
);
141+
142+
if (showCount) {
143+
const valueLength = [...value].length;
144+
const dataCount = `${valueLength}${hasMaxlength ? ` / ${maxlength}` : ''}`;
145+
textareaNode = (
146+
<div
147+
className={classNames(
148+
`${prefixCls}-textarea`,
149+
`${prefixCls}-textarea-show-count`,
150+
customClass,
151+
)}
152+
style={style}
153+
data-count={dataCount}
154+
>
155+
{textareaNode}
156+
</div>
157+
);
158+
}
159+
return textareaNode;
129160
},
130161
};

components/input/__tests__/__snapshots__/index.test.js.snap

+2
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ exports[`Input.Search should support suffix 1`] = `<span class="ant-input-search
77
exports[`TextArea should support disabled 1`] = `<textarea disabled="" class="ant-input ant-input-disabled"></textarea>`;
88
99
exports[`TextArea should support maxlength 1`] = `<textarea maxlength="10" class="ant-input"></textarea>`;
10+
11+
exports[`TextArea should support showCount 1`] = `<div class="ant-input-textarea ant-input-textarea-show-count" data-count="3 / 10"><textarea maxlength="10" class="ant-input"></textarea></div>`;

components/input/__tests__/index.test.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe('Input', () => {
2929
props: { allowClear: true, defaultValue: '111', disabled: true },
3030
sync: false,
3131
});
32-
expect(wrapper.findAll('.ant-input-clear-icon').length).toBe(0);
32+
expect(wrapper.findAll('.ant-input-clear-icon-hidden').length).toBeTruthy();
3333
});
3434
});
3535

@@ -68,6 +68,17 @@ describe('TextArea', () => {
6868
expect(wrapper.html()).toMatchSnapshot();
6969
});
7070
});
71+
72+
it('should support showCount', async () => {
73+
const wrapper = mount(TextArea, {
74+
props: { showCount: true, defaultValue: '111', maxlength: 10 },
75+
sync: false,
76+
});
77+
expect(wrapper.find('.ant-input-textarea-show-count')).toBeTruthy();
78+
await asyncExpect(() => {
79+
expect(wrapper.html()).toMatchSnapshot();
80+
});
81+
});
7182
});
7283

7384
// describe('As Form Control', () => {

components/input/style/index.less

+9
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,13 @@
5757
margin: 8px 8px 0 0;
5858
}
5959

60+
.@{ant-prefix}-input-textarea {
61+
&-show-count::after {
62+
display: block;
63+
color: @text-color-secondary;
64+
text-align: right;
65+
content: attr(data-count);
66+
}
67+
}
68+
6069
@import './search-input';

types/input/textarea.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@ export declare class TextArea extends AntdComponent {
3131
* @type boolean
3232
*/
3333
allowClear?: boolean;
34+
showCount?: boolean;
3435
};
3536
}

0 commit comments

Comments
 (0)