mirror of
https://github.com/ant-design/ant-design.git
synced 2025-06-07 09:26:06 +08:00
feat: input support show count (#32522)
Co-authored-by: machixian <machixian@myweimai.com>
This commit is contained in:
parent
ec625a00db
commit
e0de0249b2
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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 />);
|
||||
|
34
components/input/demo/show-count.md
Normal file
34
components/input/demo/show-count.md
Normal 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,
|
||||
);
|
||||
```
|
@ -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);
|
||||
```
|
@ -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 | - | |
|
||||
|
@ -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 | - | |
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user