feat: input support show count (#32522)

Co-authored-by: machixian <machixian@myweimai.com>
This commit is contained in:
WeijieChen 2021-10-15 16:23:47 +08:00 committed by GitHub
parent ec625a00db
commit e0de0249b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 162 additions and 38 deletions

View File

@ -16,6 +16,10 @@ export interface InputFocusOptions extends FocusOptions {
cursor?: 'start' | 'end' | 'all';
}
export interface ShowCountProps {
formatter: (args: { count: number; maxLength?: number }) => React.ReactNode;
}
export interface InputProps
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'prefix' | 'type'> {
prefixCls?: string;
@ -52,6 +56,7 @@ export interface InputProps
prefix?: React.ReactNode;
suffix?: React.ReactNode;
allowClear?: boolean;
showCount?: boolean | ShowCountProps;
bordered?: boolean;
htmlSize?: number;
}
@ -344,12 +349,46 @@ class Input extends React.Component<InputProps, InputState> {
onKeyDown?.(e);
};
renderShowCountSuffix = (prefixCls: string) => {
const { value } = this.state;
const { maxLength, suffix, showCount } = this.props;
// Max length value
const hasMaxLength = Number(maxLength) > 0;
if (suffix || showCount) {
const valueLength = [...fixControlledValue(value)].length;
let dataCount = null;
if (typeof showCount === 'object') {
dataCount = showCount.formatter({ count: valueLength, maxLength });
} else {
dataCount = `${valueLength}${hasMaxLength ? ` / ${maxLength}` : ''}`;
}
return (
<>
{!!showCount && (
<span
className={classNames(`${prefixCls}-show-count-suffix`, {
[`${prefixCls}-show-count-has-suffix`]: !!suffix,
})}
>
{dataCount}
</span>
)}
{suffix}
</>
);
}
return null;
};
renderComponent = ({ getPrefixCls, direction, input }: ConfigConsumerProps) => {
const { value, focused } = this.state;
const { prefixCls: customizePrefixCls, bordered = true } = this.props;
const prefixCls = getPrefixCls('input', customizePrefixCls);
this.direction = direction;
const showCountSuffix = this.renderShowCountSuffix(prefixCls);
return (
<SizeContext.Consumer>
{size => (
@ -366,6 +405,7 @@ class Input extends React.Component<InputProps, InputState> {
focused={focused}
triggerFocus={this.focus}
bordered={bordered}
suffix={showCountSuffix}
/>
)}
</SizeContext.Consumer>

View File

@ -3147,6 +3147,41 @@ Array [
]
`;
exports[`renders ./components/input/demo/show-count.md correctly 1`] = `
Array [
<span
class="ant-input-affix-wrapper"
>
<input
class="ant-input"
maxlength="20"
type="text"
value=""
/>
<span
class="ant-input-suffix"
>
<span
class="ant-input-show-count-suffix"
>
0 / 20
</span>
</span>
</span>,
<br />,
<br />,
<div
class="ant-input-textarea ant-input-textarea-show-count"
data-count="0 / 100"
>
<textarea
class="ant-input"
maxlength="100"
/>
</div>,
]
`;
exports[`renders ./components/input/demo/size.md correctly 1`] = `
Array [
<span
@ -3310,18 +3345,6 @@ Array [
]
`;
exports[`renders ./components/input/demo/textarea-show-count.md correctly 1`] = `
<div
class="ant-input-textarea ant-input-textarea-show-count"
data-count="0 / 100"
>
<textarea
class="ant-input"
maxlength="100"
/>
</div>
`;
exports[`renders ./components/input/demo/tooltip.md correctly 1`] = `
<input
class="ant-input"

View File

@ -133,6 +133,49 @@ describe('As Form Control', () => {
});
});
describe('should support showCount', () => {
it('maxLength', () => {
const wrapper = mount(<Input maxLength={5} showCount value="12345" />);
expect(wrapper.find('input').prop('value')).toBe('12345');
expect(wrapper.find('.ant-input-show-count-suffix').getDOMNode().innerHTML).toBe('5 / 5');
});
it('control exceed maxLength', () => {
const wrapper = mount(<Input maxLength={5} showCount value="12345678" />);
expect(wrapper.find('input').prop('value')).toBe('12345678');
expect(wrapper.find('.ant-input-show-count-suffix').getDOMNode().innerHTML).toBe('8 / 5');
});
describe('emoji', () => {
it('should minimize value between emoji length and maxLength', () => {
const wrapper = mount(<Input maxLength={1} showCount value="👀" />);
expect(wrapper.find('input').prop('value')).toBe('👀');
expect(wrapper.find('.ant-input-show-count-suffix').getDOMNode().innerHTML).toBe('1 / 1');
const wrapper1 = mount(<Input maxLength={2} showCount value="👀" />);
expect(wrapper1.find('.ant-input-show-count-suffix').getDOMNode().innerHTML).toBe('1 / 2');
});
it('slice emoji', () => {
const wrapper = mount(<Input maxLength={5} showCount value="1234😂" />);
expect(wrapper.find('input').prop('value')).toBe('1234😂');
expect(wrapper.find('.ant-input-show-count-suffix').getDOMNode().innerHTML).toBe('5 / 5');
});
});
it('count formatter', () => {
const wrapper = mount(
<Input
maxLength={5}
showCount={{ formatter: ({ count, maxLength }) => `${count}, ${maxLength}` }}
value="12345"
/>,
);
expect(wrapper.find('input').prop('value')).toBe('12345');
expect(wrapper.find('.ant-input-show-count-suffix').getDOMNode().innerHTML).toBe('5, 5');
});
});
describe('Input allowClear', () => {
it('should change type when click', () => {
const wrapper = mount(<Input allowClear />);

View File

@ -0,0 +1,34 @@
---
order: 12
title:
zh-CN: 带字数提示
en-US: With character counting
---
## zh-CN
展示字数提示。
## en-US
Show character counting.
```jsx
import { Input } from 'antd';
const { TextArea } = Input;
const onChange = e => {
console.log('Change:', e.target.value);
};
ReactDOM.render(
<>
<Input showCount maxLength={20} onChange={onChange} />
<br />
<br />
<TextArea showCount maxLength={100} onChange={onChange} />
</>,
mountNode,
);
```

View File

@ -1,26 +0,0 @@
---
order: 12
title:
zh-CN: 带字数提示的文本域
en-US: Textarea with character counting
---
## zh-CN
展示字数提示。
## en-US
Show character counting.
```jsx
import { Input } from 'antd';
const { TextArea } = Input;
const onChange = e => {
console.log('Change:', e.target.value);
};
ReactDOM.render(<TextArea showCount maxLength={100} onChange={onChange} />, mountNode);
```

View File

@ -26,6 +26,7 @@ A basic widget for getting the user input is a text field. Keyboard and mouse ca
| disabled | Whether the input is disabled | boolean | false | |
| id | The ID for input | string | - | |
| maxLength | The max length | number | - | |
| showCount | Whether show text count | boolean \| { formatter: ({ count: number, maxLength?: number }) => ReactNode } | false | 4.17.0 |
| prefix | The prefix icon for the Input | ReactNode | - | |
| size | The size of the input box. Note: in the context of a form, the `large` size is used | `large` \| `middle` \| `small` | - | |
| suffix | The suffix icon for the Input | ReactNode | - | |

View File

@ -27,6 +27,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/xS9YEJhfe/Input.svg
| disabled | 是否禁用状态,默认为 false | boolean | false | |
| id | 输入框的 id | string | - | |
| maxLength | 最大长度 | number | - | |
| showCount | 是否展示字数 | boolean \| { formatter: ({ count: number, maxLength?: number }) => ReactNode } | false | 4.17.0 |
| prefix | 带有前缀图标的 input | ReactNode | - | |
| size | 控件大小。注:标准表单内的输入框大小限制为 `large` | `large` \| `middle` \| `small` | - | |
| suffix | 带有后缀图标的 input | ReactNode | - | |

View File

@ -52,6 +52,14 @@
align-items: center;
}
&-show-count-suffix {
color: @text-color-secondary;
}
&-show-count-has-suffix {
margin-right: 2px;
}
&-prefix {
margin-right: @input-affix-margin;
}