Skip to content

Commit

Permalink
chore(Textarea): complete refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
anlyyao committed Sep 20, 2024
1 parent 4c45fc1 commit 3236c2f
Show file tree
Hide file tree
Showing 22 changed files with 347 additions and 75 deletions.
2 changes: 1 addition & 1 deletion site/mobile/mobile.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ export default {
{
title: 'Textarea 多行文本框',
name: 'textarea',
component: () => import('tdesign-mobile-react/textarea/_example/index.jsx'),
component: () => import('tdesign-mobile-react/textarea/_example/index.tsx'),
},
{
title: 'Steps 步骤条',
Expand Down
15 changes: 15 additions & 0 deletions src/_util/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,18 @@ export function getCharacterLength(str: string, maxCharacter?: number) {
export function pxCompat(param: string | number) {
return typeof param === 'number' ? `${param}px` : param;
}

/**
* 修正 Unicode 最大字符长度
* '👨👨👨'.slice(0, 2) === '👨'
* limitUnicodeMaxLength('👨👨👨', 2) === '👨👨'
* @param str
* @param maxLength
* @param oldStr
* @returns {string}
*/
export function limitUnicodeMaxLength(str?: string, maxLength?: number, oldStr?: string): string {
// 旧字符满足字数要求则返回
if ([...(oldStr ?? '')].slice().length === maxLength) return oldStr || '';
return [...(str ?? '')].slice(0, maxLength).join('');
}
117 changes: 92 additions & 25 deletions src/textarea/Textarea.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,53 @@
import React, { forwardRef, useState, useEffect, useMemo, useRef, useCallback, useImperativeHandle } from 'react';
import classNames from 'classnames';
import useConfig from '../_util/useConfig';
import parseTNode from '../_util/parseTNode';
import useDefault from '../_util/useDefault';
import { getCharacterLength } from '../_util/helper';
import { getCharacterLength, limitUnicodeMaxLength } from '../_util/helper';
import calcTextareaHeight from '../_common/js/utils/calcTextareaHeight';
import { textareaDefaultProps } from './defaultProps';
import { TdTextareaProps } from './type';
import { StyledProps } from '../common';
import { usePrefixClass } from '../hooks/useClass';
import useDefaultProps from '../hooks/useDefaultProps';

export interface TextareaProps
extends Omit<
React.TextareaHTMLAttributes<HTMLTextAreaElement>,
'value' | 'defaultValue' | 'onBlur' | 'onChange' | 'onFocus'
>,
TdTextareaProps,
StyledProps {}

export interface TextareaProps extends TdTextareaProps, StyledProps {}
export interface TextareaRefInterface extends React.RefObject<unknown> {
currentElement: HTMLDivElement;
textareaElement: HTMLTextAreaElement;
}

const Textarea = forwardRef((props: TextareaProps, ref: TextareaRefInterface) => {
const { disabled, maxlength, maxcharacter, autofocus, defaultValue, autosize = false, label, ...otherProps } = props;
const { classPrefix } = useConfig();
const baseClass = `${classPrefix}-textarea`;
const Textarea = forwardRef((originProps: TextareaProps, ref: TextareaRefInterface) => {
const props = useDefaultProps<TextareaProps>(originProps, textareaDefaultProps);
const {
className,
style,
allowInputOverMax,
autofocus,
bordered,
disabled,
defaultValue,
maxlength,
maxcharacter,
layout,
autosize,
label,
indicator,
readonly,
...otherProps
} = props;

const textareaClass = usePrefixClass('textarea');

const [value = '', setValue] = useDefault(props.value, defaultValue, props.onChange);
const [textareaStyle, setTextareaStyle] = useState({});
const composingRef = useRef(false);
const textareaRef: React.RefObject<HTMLTextAreaElement> = useRef();
const wrapperRef: React.RefObject<HTMLDivElement> = useRef();

Expand Down Expand Up @@ -50,30 +78,56 @@ const Textarea = forwardRef((props: TextareaProps, ref: TextareaRefInterface) =>
return eventProps;
}, {});

const textareaClassNames = classNames(`${baseClass}__wrapper`, {
[`${baseClass}-is-disabled`]: disabled,
const textareaClasses = classNames(
`${textareaClass}`,
{
[`${textareaClass}--layout-${layout}`]: layout,
[`${textareaClass}--border`]: bordered,
},
className,
);

const textareaInnerClasses = classNames(`${textareaClass}__wrapper-inner`, {
[`${textareaClass}--disabled`]: disabled,
[`${textareaClass}--readonly`]: readonly,
});

const adjustTextareaHeight = useCallback(() => {
if (autosize === true) {
setTextareaStyle(calcTextareaHeight(textareaRef.current as HTMLTextAreaElement));
} else if (autosize === false) {
props.rows
? setTextareaStyle({ height: 'auto', minHeight: 'auto' })
: setTextareaStyle(calcTextareaHeight(textareaRef.current as HTMLTextAreaElement, 1, 1));
} else if (typeof autosize === 'object') {
const { minRows, maxRows } = autosize;
setTextareaStyle(calcTextareaHeight(textareaRef.current as HTMLTextAreaElement, minRows, maxRows));
} else if (autosize === false) {
setTextareaStyle({ height: 'auto', minHeight: '96px' });
}
}, [autosize]);
}, [autosize, props.rows]);

function inputValueChangeHandle(e: React.FormEvent<HTMLTextAreaElement>) {
const inputValueChangeHandle = (e: React.FormEvent<HTMLTextAreaElement>) => {
const { target } = e;
let val = (target as HTMLInputElement).value;
if (maxcharacter && maxcharacter >= 0) {
const stringInfo = getCharacterLength(val, maxcharacter);
val = typeof stringInfo === 'object' && stringInfo.characters;
if (!allowInputOverMax && !composingRef.current) {
val = limitUnicodeMaxLength(val, maxlength);
if (maxcharacter && maxcharacter >= 0) {
const stringInfo = getCharacterLength(val, maxcharacter);
val = typeof stringInfo === 'object' && stringInfo.characters;
}
}
setValue(val, { e });
}
};

const handleCompositionStart = () => {
composingRef.current = true;
};

const handleCompositionEnd = (e) => {
if (composingRef.current) {
composingRef.current = false;
inputValueChangeHandle(e);
}
};

useEffect(() => {
adjustTextareaHeight();
Expand All @@ -84,27 +138,40 @@ const Textarea = forwardRef((props: TextareaProps, ref: TextareaRefInterface) =>
textareaElement: textareaRef.current,
}));

const renderLabel = () => label && <div className={`${textareaClass}__label`}> {parseTNode(label)} </div>;

const renderIndicator = () => {
const isShowIndicator = indicator && (maxcharacter || maxlength);
if (!isShowIndicator) {
return null;
}
return <div className={`${textareaClass}__indicator`}>{`${textareaLength}/${maxcharacter || maxlength}`}</div>;
};

return (
<div ref={wrapperRef} className={baseClass}>
{label && <div className={`${baseClass}__name`}> {label} </div>}
<div className={textareaClassNames}>
<div ref={wrapperRef} className={textareaClasses} style={style}>
{renderLabel()}
<div className={`${textareaClass}__wrapper`}>
<textarea
{...textareaProps}
{...eventProps}
value={value}
className={textareaInnerClasses}
style={textareaStyle}
value={value}
readOnly={readonly}
autoFocus={autofocus}
disabled={disabled}
maxLength={maxlength}
onChange={inputValueChangeHandle}
onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd}
ref={textareaRef}
/>
{(maxcharacter || maxlength) && (
<div className={`${baseClass}__count`}> {`${textareaLength}/${maxcharacter || maxlength}`} </div>
)}
{renderIndicator()}
</div>
</div>
);
});

Textarea.displayName = 'Textarea';

export default Textarea;
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import React from 'react';
import { Textarea } from 'tdesign-mobile-react';

export default function Autosize() {
return <Textarea autosize placeholder="请输入文字" />;
return <Textarea placeholder="请输入文字" autosize />;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useState } from 'react';
import { Textarea } from 'tdesign-mobile-react';
import type { TextareaProps } from 'tdesign-mobile-react';

export default function Events() {
const [value, onChange] = useState('');
export default function Base() {
const [value, onChange] = useState<TextareaProps['value']>('');

function onFocus(value, { e }) {
console.log('onFocus: ', value, e);
Expand All @@ -13,13 +14,13 @@ export default function Events() {

return (
<Textarea
label="标签文字"
placeholder="请输入文字"
className="textarea-example"
value={value}
placeholder="请输入文字"
onFocus={onFocus}
onBlur={onBlur}
onChange={(value) => {
console.log('==value===', value);
console.log('value', value);
onChange(value);
}}
/>
Expand Down
24 changes: 24 additions & 0 deletions src/textarea/_example/card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import { Textarea } from 'tdesign-mobile-react';

export default function TextareaCard() {
return (
<>
<div className="card textarea-example__card">
<Textarea
className="textarea"
label="标签文字"
placeholder="请输入文字"
maxlength={500}
indicator
layout="vertical"
/>
</div>

<div className="card textarea-example__card card">
<div className="textarea-example__summary">卡片样式</div>
<Textarea className="textarea" label="标签文字" placeholder="请输入文字" maxlength={500} indicator />
</div>
</>
);
}
13 changes: 13 additions & 0 deletions src/textarea/_example/custom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';
import { Textarea } from 'tdesign-mobile-react';

export default function Events() {
return (
<>
<div className="custom textarea-example__custom ">
<text className="textarea-example__label">标签文字</text>
<Textarea className="textarea" placeholder="请输入文字" bordered maxlength={100} indicator />
</div>
</>
);
}
8 changes: 8 additions & 0 deletions src/textarea/_example/disable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react';
import { Textarea } from 'tdesign-mobile-react';

export default function Type() {
return (
<Textarea className="textarea-example" label="标签文字" placeholder="请输入文字" value="不可编辑文字" disabled />
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import TDemoBlock from '../../../site/mobile/components/DemoBlock';
import Base from './base';
import Label from './label';
import Autosize from './autosize';
import Events from './events';
import Status from './status';
import Events from './custom';
import Disable from './disable';
import Maxlength from './maxlength';
import Maxcharacter from './maxcharacter';
import Card from './card';

import './style/index.less';

export default function () {
return (
Expand All @@ -22,16 +25,19 @@ export default function () {
<TDemoBlock summary="自动增高多行文本框">
<Autosize />
</TDemoBlock>
<TDemoBlock title="02 状态" summary="禁用多行文本框">
<Status />
</TDemoBlock>
<TDemoBlock title="03 字符限制" summary="设置最大字符个数">
<TDemoBlock summary="设置字符数限制">
<Maxlength />
</TDemoBlock>
<TDemoBlock summary="设置最大字符个数,一个汉字表示两个字符">
<TDemoBlock>
<Maxcharacter />
</TDemoBlock>
<TDemoBlock title="04 事件" summary="带事件文本框">
<TDemoBlock title="02 状态" summary="禁用状态">
<Disable />
</TDemoBlock>
<TDemoBlock title="03 组件样式" summary="竖排样式">
<Card />
</TDemoBlock>
<TDemoBlock title="04 特殊样式" summary="标签外置输入框">
<Events />
</TDemoBlock>
</div>
Expand Down
6 changes: 0 additions & 6 deletions src/textarea/_example/label.jsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import React from 'react';
import { Textarea } from 'tdesign-mobile-react';

export default function Base() {
return <Textarea autofocus placeholder="请输入文字" />;
return <Textarea className="textarea-example" label="标签文字" placeholder="请输入文字" />;
}
6 changes: 0 additions & 6 deletions src/textarea/_example/maxcharacter.jsx

This file was deleted.

20 changes: 20 additions & 0 deletions src/textarea/_example/maxcharacter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React, { useState } from 'react';
import { Textarea } from 'tdesign-mobile-react';
import type { TextareaProps } from 'tdesign-mobile-react';

export default function Maxlength() {
const [value, onChange] = useState<TextareaProps['value']>('');
return (
<Textarea
placeholder="请输入文字"
label="标签文字"
maxcharacter={100}
indicator
value={value}
onChange={(value) => {
console.log(value);
onChange(value);
}}
/>
);
}
6 changes: 0 additions & 6 deletions src/textarea/_example/maxlength.jsx

This file was deleted.

Loading

0 comments on commit 3236c2f

Please sign in to comment.