Skip to content

Commit 07914dd

Browse files
committed
feat(Textarea): support count API
1 parent 79aeee5 commit 07914dd

File tree

7 files changed

+86
-45
lines changed

7 files changed

+86
-45
lines changed

packages/components/textarea/Textarea.tsx

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1-
import React, { forwardRef, useState, useEffect, useMemo, useRef, useImperativeHandle } from 'react';
1+
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
22
import classNames from 'classnames';
3-
import { getCharacterLength, getUnicodeLength, limitUnicodeMaxLength } from '@tdesign/common-js/utils/helper';
3+
44
import calcTextareaHeight from '@tdesign/common-js/utils/calcTextareaHeight';
5-
import useConfig from '../hooks/useConfig';
6-
import { TdTextareaProps } from './type';
7-
import { StyledProps } from '../common';
5+
import { getCharacterLength, getUnicodeLength, limitUnicodeMaxLength } from '@tdesign/common-js/utils/helper';
86
import noop from '../_util/noop';
7+
import parseTNode from '../_util/parseTNode';
8+
import useConfig from '../hooks/useConfig';
99
import useControlled from '../hooks/useControlled';
10-
import { textareaDefaultProps } from './defaultProps';
1110
import useDefaultProps from '../hooks/useDefaultProps';
12-
import useIsomorphicLayoutEffect from '../hooks/useLayoutEffect';
1311
import useEventCallback from '../hooks/useEventCallback';
12+
import useIsomorphicLayoutEffect from '../hooks/useLayoutEffect';
13+
import { textareaDefaultProps } from './defaultProps';
14+
15+
import type { StyledProps } from '../common';
16+
import type { TdTextareaProps } from './type';
1417

1518
const DEFAULT_TEXTAREA_STYLE = { height: 'auto', minHeight: 'auto' };
1619

@@ -29,6 +32,7 @@ export interface TextareaRefInterface {
2932
const Textarea = forwardRef<TextareaRefInterface, TextareaProps>((originalProps, ref) => {
3033
const props = useDefaultProps<TextareaProps>(originalProps, textareaDefaultProps);
3134
const {
35+
count,
3236
disabled,
3337
maxlength,
3438
maxcharacter,
@@ -46,16 +50,19 @@ const Textarea = forwardRef<TextareaRefInterface, TextareaProps>((originalProps,
4650
rows,
4751
...otherProps
4852
} = props;
53+
const hasMaxcharacter = typeof maxcharacter !== 'undefined';
4954

5055
const [value = '', setValue] = useControlled(props, 'value', props.onChange);
56+
5157
const [isFocused, setIsFocused] = useState(false);
5258
const [isOvermax, setIsOvermax] = useState(false);
5359
const [textareaStyle, setTextareaStyle] = useState<Partial<typeof DEFAULT_TEXTAREA_STYLE>>(DEFAULT_TEXTAREA_STYLE);
54-
const composingRef = useRef(false);
5560
const [composingValue, setComposingValue] = useState<string>('');
56-
const hasMaxcharacter = typeof maxcharacter !== 'undefined';
61+
62+
const composingRef = useRef(false);
5763
const textareaRef = useRef<HTMLTextAreaElement>(null);
5864
const wrapperRef = useRef<HTMLDivElement>(null);
65+
5966
const currentLength = useMemo(() => getUnicodeLength(value), [value]);
6067
const characterLength = useMemo(() => {
6168
const characterInfo = getCharacterLength(String(value), allowInputOverMax ? Infinity : maxcharacter);
@@ -141,16 +148,28 @@ const Textarea = forwardRef<TextareaRefInterface, TextareaProps>((originalProps,
141148
}
142149
}
143150

144-
const renderLimitText = (current: number, max: number) => (
145-
<span className={`${classPrefix}-textarea__limit`}>
146-
{isOvermax && allowInputOverMax ? (
147-
<span className={`${classPrefix}-textarea__tips--warning`}> {current}</span>
148-
) : (
149-
`${current}`
150-
)}
151-
{`/${max}`}
152-
</span>
153-
);
151+
const renderLimitText = (current: number, max: number) => {
152+
if (count === false) return null;
153+
154+
if (typeof count === 'function') {
155+
return parseTNode(count, {
156+
value,
157+
count: current,
158+
maxLength: hasMaxcharacter ? undefined : maxlength,
159+
maxCharacter: hasMaxcharacter ? maxcharacter : undefined,
160+
});
161+
}
162+
return (
163+
<span className={`${classPrefix}-textarea__limit`}>
164+
{isOvermax && allowInputOverMax ? (
165+
<span className={`${classPrefix}-textarea__tips--warning`}> {current}</span>
166+
) : (
167+
`${current}`
168+
)}
169+
{`/${max}`}
170+
</span>
171+
);
172+
};
154173

155174
useIsomorphicLayoutEffect(() => {
156175
if (autosize === false) {
@@ -187,9 +206,13 @@ const Textarea = forwardRef<TextareaRefInterface, TextareaProps>((originalProps,
187206
</div>
188207
);
189208

190-
const limitText =
191-
(hasMaxcharacter && renderLimitText(characterLength, maxcharacter)) ||
192-
(!hasMaxcharacter && maxlength && renderLimitText(currentLength, maxlength));
209+
const showCount =
210+
count === true || typeof count === 'function' || (count === undefined && (maxlength || maxcharacter));
211+
212+
const limitText = showCount
213+
? (hasMaxcharacter && renderLimitText(characterLength, maxcharacter)) ||
214+
(!hasMaxcharacter && maxlength && renderLimitText(currentLength, maxlength))
215+
: null;
193216

194217
return (
195218
<div style={style} ref={wrapperRef} className={classNames(`${classPrefix}-textarea`, className)}>

packages/components/textarea/_example/maxlength.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
import React from 'react';
2-
import { Textarea, Space } from 'tdesign-react';
2+
import { Space, Textarea } from 'tdesign-react';
33

44
export default function InputExample() {
55
return (
66
<Space direction="vertical" style={{ width: '100%' }}>
7-
<Textarea placeholder="请输入内容,超出限制无法输入" tips="这里可以放一些提示文字" maxlength={20} />
7+
<Textarea
8+
placeholder="请输入内容,超出限制无法输入"
9+
tips="这里可以放一些提示文字"
10+
maxlength={20}
11+
count={(ctx) => (
12+
<div style={{ fontSize: '12px', color: 'gray' }}>
13+
<span>{ctx.count}</span>/<span style={{ color: 'mediumblue' }}>{ctx.maxLength}</span>
14+
</div>
15+
)}
16+
/>
817
<Textarea placeholder="请输入内容,超出限制可以输入" maxlength={20} allowInputOverMax />
918
<Textarea placeholder="请输入内容,一个中文汉字表示两个字符长度,超出限制无法输入" maxcharacter={20} />
1019
<Textarea

packages/components/textarea/textarea.en-US.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
:: BASE_DOC ::
22

33
## API
4+
45
### Textarea Props
56

67
name | type | default | description | required
78
-- | -- | -- | -- | --
89
className | String | - | className of component | N
910
style | Object | - | CSS(Cascading Style Sheets),Typescript: `React.CSSProperties` | N
10-
allowInputOverMax | Boolean | false | \- | N
11+
allowInputOverMax | Boolean | false | Allow input after exceeding `maxlength` or `maxcharacter` | N
1112
autofocus | Boolean | false | \- | N
1213
autosize | Boolean / Object | false | Typescript: `boolean \| { minRows?: number; maxRows?: number }` | N
13-
disabled | Boolean | false | \- | N
14-
label | TNode | - | Typescript: `string \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
14+
count | Boolean / Function | - | Character counter. It is enabled by default when `maxLength` or `maxCharacter` is set.。Typescript: `boolean \| ((ctx: { value: string; count: number; maxLength?: number; maxCharacter?: number }) => TNode)`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
15+
disabled | Boolean | undefined | \- | N
1516
maxcharacter | Number | - | \- | N
16-
maxlength | Number | - | \- | N
17+
maxlength | Number | - | Typescript: `number` | N
1718
name | String | - | \- | N
1819
placeholder | String | undefined | \- | N
1920
readonly | Boolean | false | \- | N
20-
status | String | - | optionsdefault/success/warning/error | N
21+
status | String | - | options: default/success/warning/error | N
2122
tips | TNode | - | Typescript: `string \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
22-
value | String / Number | - | Typescript: `TextareaValue` `type TextareaValue = string`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/textarea/type.ts) | N
23-
defaultValue | String / Number | - | uncontrolled property。Typescript: `TextareaValue` `type TextareaValue = string`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/textarea/type.ts) | N
23+
value | String / Number | - | Typescript: `TextareaValue` `type TextareaValue = string \| number`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/textarea/type.ts) | N
24+
defaultValue | String / Number | - | uncontrolled property。Typescript: `TextareaValue` `type TextareaValue = string \| number`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/textarea/type.ts) | N
2425
onBlur | Function | | Typescript: `(value: TextareaValue, context: { e: FocusEvent }) => void`<br/> | N
2526
onChange | Function | | Typescript: `(value: TextareaValue, context?: { e?: InputEvent }) => void`<br/> | N
2627
onFocus | Function | | Typescript: `(value: TextareaValue, context : { e: FocusEvent }) => void`<br/> | N

packages/components/textarea/textarea.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
:: BASE_DOC ::
22

33
## API
4+
45
### Textarea Props
56

67
名称 | 类型 | 默认值 | 描述 | 必传
78
-- | -- | -- | -- | --
89
className | String | - | 类名 | N
910
style | Object | - | 样式,TS 类型:`React.CSSProperties` | N
10-
allowInputOverMax | Boolean | false | 超出maxlength或maxcharacter之后是否还允许输入 | N
11+
allowInputOverMax | Boolean | false | 超出 `maxlength``maxcharacter` 之后是否还允许输入 | N
1112
autofocus | Boolean | false | 自动聚焦,拉起键盘 | N
1213
autosize | Boolean / Object | false | 高度自动撑开。 autosize = true 表示组件高度自动撑开,同时,依旧允许手动拖高度。如果设置了 autosize.maxRows 或者 autosize.minRows 则不允许手动调整高度。TS 类型:`boolean \| { minRows?: number; maxRows?: number }` | N
14+
count | Boolean / Function | - | 文字计数元素。设置 `maxlength``maxchanacter` 时,默认为 true。TS 类型:`boolean \| ((ctx: { value: string; count: number; maxLength?: number; maxCharacter?: number }) => TNode)`[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
1315
disabled | Boolean | false | 是否禁用文本框 | N
14-
label | TNode | - | 左侧文本。TS 类型:`string \| TNode`[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
1516
maxcharacter | Number | - | 用户最多可以输入的字符个数,一个中文汉字表示两个字符长度 | N
1617
maxlength | Number | - | 用户最多可以输入的字符个数 | N
1718
name | String | - | 名称,HTML 元素原生属性 | N

packages/components/textarea/type.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { KeyboardEvent, FocusEvent, FormEvent } from 'react';
99

1010
export interface TdTextareaProps {
1111
/**
12-
* 超出maxlength或maxcharacter之后是否还允许输入
12+
* 超出 `maxlength` 或 `maxcharacter` 之后是否还允许输入
1313
* @default false
1414
*/
1515
allowInputOverMax?: boolean;
@@ -23,15 +23,15 @@ export interface TdTextareaProps {
2323
* @default false
2424
*/
2525
autosize?: boolean | { minRows?: number; maxRows?: number };
26+
/**
27+
* 文字计数元素。设置 `maxlength` 或 `maxchanacter` 时,默认为 true
28+
*/
29+
count?: boolean | ((ctx: { value: string; count: number; maxLength?: number; maxCharacter?: number }) => TNode);
2630
/**
2731
* 是否禁用文本框
2832
* @default false
2933
*/
3034
disabled?: boolean;
31-
/**
32-
* 左侧文本
33-
*/
34-
label?: TNode;
3535
/**
3636
* 用户最多可以输入的字符个数,一个中文汉字表示两个字符长度
3737
*/

test/snap/__snapshots__/csr.test.jsx.snap

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135394,12 +135394,19 @@ exports[`csr snapshot test > csr test packages/components/textarea/_example/maxl
135394135394
>
135395135395
这里可以放一些提示文字
135396135396
</div>
135397-
<span
135398-
class="t-textarea__limit"
135397+
<div
135398+
style="font-size: 12px; color: gray;"
135399135399
>
135400-
0
135401-
/20
135402-
</span>
135400+
<span>
135401+
0
135402+
</span>
135403+
/
135404+
<span
135405+
style="color: mediumblue;"
135406+
>
135407+
20
135408+
</span>
135409+
</div>
135403135410
</div>
135404135411
</div>
135405135412
</div>
@@ -150888,7 +150895,7 @@ exports[`ssr snapshot test > ssr test packages/components/textarea/_example/base
150888150895

150889150896
exports[`ssr snapshot test > ssr test packages/components/textarea/_example/events.tsx 1`] = `"<div class="t-textarea"><textarea placeholder="请输入内容" style="height:auto;min-height:auto" class="t-textarea__inner"></textarea></div>"`;
150890150897

150891-
exports[`ssr snapshot test > ssr test packages/components/textarea/_example/maxlength.tsx 1`] = `"<div style="width:100%;gap:16px" class="t-space t-space-vertical"><div class="t-space-item"><div class="t-textarea"><textarea placeholder="请输入内容,超出限制无法输入" style="height:auto;min-height:auto" class="t-textarea__inner"></textarea><div class="t-textarea__info_wrapper"><div class="t-textarea__tips t-textarea__tips--normal">这里可以放一些提示文字</div><span class="t-textarea__limit">0<!-- -->/20</span></div></div></div><div class="t-space-item"><div class="t-textarea"><textarea placeholder="请输入内容,超出限制可以输入" style="height:auto;min-height:auto" class="t-textarea__inner"></textarea><div class="t-textarea__info_wrapper t-textarea__info_wrapper_align"><span class="t-textarea__limit">0<!-- -->/20</span></div></div></div><div class="t-space-item"><div class="t-textarea"><textarea placeholder="请输入内容,一个中文汉字表示两个字符长度,超出限制无法输入" style="height:auto;min-height:auto" class="t-textarea__inner"></textarea><div class="t-textarea__info_wrapper t-textarea__info_wrapper_align"><span class="t-textarea__limit">0<!-- -->/20</span></div></div></div><div class="t-space-item"><div class="t-textarea"><textarea placeholder="请输入内容,一个中文汉字表示两个字符长度,超出限制可以输入" style="height:auto;min-height:auto" class="t-textarea__inner"></textarea><div class="t-textarea__info_wrapper t-textarea__info_wrapper_align"><span class="t-textarea__limit">0<!-- -->/20</span></div></div></div></div>"`;
150898+
exports[`ssr snapshot test > ssr test packages/components/textarea/_example/maxlength.tsx 1`] = `"<div style="width:100%;gap:16px" class="t-space t-space-vertical"><div class="t-space-item"><div class="t-textarea"><textarea placeholder="请输入内容,超出限制无法输入" style="height:auto;min-height:auto" class="t-textarea__inner"></textarea><div class="t-textarea__info_wrapper"><div class="t-textarea__tips t-textarea__tips--normal">这里可以放一些提示文字</div><div style="font-size:12px;color:gray"><span>0</span>/<span style="color:mediumblue">20</span></div></div></div></div><div class="t-space-item"><div class="t-textarea"><textarea placeholder="请输入内容,超出限制可以输入" style="height:auto;min-height:auto" class="t-textarea__inner"></textarea><div class="t-textarea__info_wrapper t-textarea__info_wrapper_align"><span class="t-textarea__limit">0<!-- -->/20</span></div></div></div><div class="t-space-item"><div class="t-textarea"><textarea placeholder="请输入内容,一个中文汉字表示两个字符长度,超出限制无法输入" style="height:auto;min-height:auto" class="t-textarea__inner"></textarea><div class="t-textarea__info_wrapper t-textarea__info_wrapper_align"><span class="t-textarea__limit">0<!-- -->/20</span></div></div></div><div class="t-space-item"><div class="t-textarea"><textarea placeholder="请输入内容,一个中文汉字表示两个字符长度,超出限制可以输入" style="height:auto;min-height:auto" class="t-textarea__inner"></textarea><div class="t-textarea__info_wrapper t-textarea__info_wrapper_align"><span class="t-textarea__limit">0<!-- -->/20</span></div></div></div></div>"`;
150892150899

150893150900
exports[`ssr snapshot test > ssr test packages/components/textarea/_example/type.tsx 1`] = `"<div style="width:100%;gap:16px" class="t-space t-space-vertical"><div class="t-space-item"><div class="t-textarea"><textarea placeholder="请输入内容" style="height:auto;min-height:auto" class="t-textarea__inner" readonly="">只读状态</textarea></div></div><div class="t-space-item"><div class="t-textarea"><textarea placeholder="请输入内容" style="height:auto;min-height:auto" class="t-textarea__inner t-is-disabled" disabled="">禁用状态</textarea></div></div><div class="t-space-item"><div class="t-textarea"><textarea placeholder="normal" style="height:auto;min-height:auto" class="t-textarea__inner">普通状态</textarea><div class="t-textarea__info_wrapper"><div class="t-textarea__tips t-textarea__tips--normal">正常提示</div></div></div></div><div class="t-space-item"><div class="t-textarea"><textarea placeholder="success" style="height:auto;min-height:auto" class="t-textarea__inner t-is-success">成功状态</textarea><div class="t-textarea__info_wrapper"><div class="t-textarea__tips t-textarea__tips--success">成功提示</div></div></div></div><div class="t-space-item"><div class="t-textarea"><textarea placeholder="warning" style="height:auto;min-height:auto" class="t-textarea__inner t-is-warning">警告状态</textarea><div class="t-textarea__info_wrapper"><div class="t-textarea__tips t-textarea__tips--warning">警告提示</div></div></div></div><div class="t-space-item"><div class="t-textarea"><textarea placeholder="error" style="height:auto;min-height:auto" class="t-textarea__inner t-is-error">错误状态</textarea><div class="t-textarea__info_wrapper"><div class="t-textarea__tips t-textarea__tips--error">错误提示</div></div></div></div></div>"`;
150894150901

0 commit comments

Comments
 (0)