Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions site/sidebar.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ export default [
path: '/components/switch',
component: () => import('tdesign-web-components/switch/README.md'),
},
{
title: 'Textarea 文本框',
name: 'textarea',
path: '/components/textarea',
component: () => import('tdesign-web-components/textarea/README.md'),
},
],
},
{
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './button';
export * from './icon';
export * from './textarea';
export * from './space';
export * from './switch';
59 changes: 59 additions & 0 deletions src/textarea/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
title: Textarea 文本框
description: 通过鼠标或键盘输入多行内容。
isComponent: true
usage: { title: '', description: '' }
spline: base
---

### 基础使用

高度自适应

{{ base }}

### 自定义事件

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

测试

{{ event }}

### 字数限制

支持中文和英文字符数限制

{{ limit }}

### 自定义状态

支持禁用、只读,和default/success/warning/error四种status状态

{{ status }}




## API

### Textarea Props

名称 | 类型 | 默认值 | 说明 | 必传
-- | -- | -- | -- | --
allowInputOverMax | Boolean | false | 超出`maxlength`或`maxcharacter`之后是否还允许输入 | N
autofocus | Boolean | false | 自动聚焦,拉起键盘 | N
autosize | Boolean/Object | false | 高度自动撑开。Object属性:`minRows:number,maxRows:number`。autosize = true 表示组件高度自动撑开,同时,依旧允许手动拖高度。如果设置了 autosize.maxRows 或者 autosize.minRows 则不允许手动调整高度 | N
disabled | Boolean | false | 是否禁用文本框 | N
label | TNode | - | 左侧文本 | N
maxcharacter | Number | - | 用户最多可以输入的字符个数,一个中文汉字表示两个字符长度 | N
maxlength | Number | - | 用户最多可以输入的字符个数 | N
name | String | - | 名称 | N
placeholder | String | '' | 占位符 | N
readonly | Boolean | false | 只读状态 | N
status | String | - | 文本框状态。可选项:`default/success/warning/error` | N
tips | TNode | - | 输入框下方提示文本 | N
value | String | - | 文本框值 | N
defaultValue | String | - | 文本框值,非受控属性 | N
onBlur | Function | | TS 类型:`(value: string, context: FocusEvent) => void`<br/>失去焦点时触发 | N
onFocus | Function | | TS 类型:`(value: string, context: FocusEvent) => void`<br/>获得焦点时触发 | N
onKeydown | Function | | TS 类型:`(value: string, context: KeyboardEvent) => void`<br/>键盘按下时触发 | N
onKeypress | Function | | TS 类型:`(value: string, context: KeyboardEvent) => void`<br/>按下字符键时触发 | N
onKeyup | Function | | TS 类型:`(value: string, context: KeyboardEvent) => void`<br/>释放键盘时触发 | N
onChange | Function | | TS 类型:`(value: string, context: Event) => void`<br/>输入内容变化时触发 | N
12 changes: 12 additions & 0 deletions src/textarea/_example/base.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'tdesign-web-components/textarea';

export default function Textarea() {

return (
<div style={{ gap: 16, display: 'flex', flexDirection: 'column' }}>
<t-textarea placeholder="请输入描述文案"></t-textarea>
<t-textarea placeholder="请输入文案,高度可自适应;autosize=true" autosize={true}></t-textarea>
<t-textarea placeholder="请输入文案,高度可自适应,最小3行,最大5行;autosize={minRows: 3, maxRows: 5}" autosize={{minRows: 3, maxRows: 5}}></t-textarea>
</div>
);
}
41 changes: 41 additions & 0 deletions src/textarea/_example/event.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import 'tdesign-web-components/textarea';

export default function Textarea() {
const value = '';

const onBlur = (value, { e }) => {
console.log('onBlur: ', value, e);
};

const onFocus = (value, { e }) => {
console.log('onFocus: ', value, e);
};

const onKeyup = (value, { e }) => {
console.log('onKeyup', value, e);
};

const onKeypress = (value, { e }) => {
console.log('onKeypress', value, e);
};

const onKeydown = (value, { e }) => {
console.log('onKeydown', value, e);
};
const onChange = (value, { e }) => {
console.log('onChange', value, e);
};

return (
<t-textarea
placeholder="请输入"
value={value}
onBlur={onBlur}
onFocus={onFocus}
onKeypress={onKeypress}
onKeydown={onKeydown}
onKeyup={onKeyup}
onChange={onChange}
></t-textarea>
);
}
14 changes: 14 additions & 0 deletions src/textarea/_example/limit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'tdesign-web-components/textarea';
import 'tdesign-web-components/space';

export default function Textarea() {
return (
<t-space direction="vertical" style={{ gap: 16, display: 'flex', flexDirection: 'column' }}>
<t-textarea placeholder="请输入描述文案,文本长度最多20,maxlength=20" maxlength="20"></t-textarea>
<t-textarea
placeholder="请输入描述文案,最多20字符(一个汉字占两个字符长度),maxcharacter=20"
maxcharacter="20"
></t-textarea>
</t-space>
);
}
15 changes: 15 additions & 0 deletions src/textarea/_example/status.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'tdesign-web-components/textarea';
import 'tdesign-web-components/space';

export default function Textarea() {
return (
<t-space direction="vertical" style={{ gap: 16, display: 'flex', flexDirection: 'column' }}>
<t-textarea placeholder="禁用状态" disabled="true"></t-textarea>
<t-textarea placeholder="只读状态" readonly="true"></t-textarea>
<t-textarea placeholder="普通状态" readonly="true" tips="这是普通文本提示"></t-textarea>
<t-textarea placeholder="成功状态" tips="校验通过文本提示" status="success"></t-textarea>
<t-textarea placeholder="警告状态" tips="校验不通过文本提示" status="warning"></t-textarea>
<t-textarea placeholder="错误状态" tips="校验存在严重问题文本提示" status="error"></t-textarea>
</t-space>
);
}
9 changes: 9 additions & 0 deletions src/textarea/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import './style/index.js';

import _Textarea from './textarea';

export type { TextareaProps } from './textarea';
export const Textarea = _Textarea;
export default Textarea;

export * from './type';
11 changes: 11 additions & 0 deletions src/textarea/style/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { css, globalCSS } from 'omi';

// 为了做主题切换
import styles from '../../_common/style/web/components/textarea/_index.less';

export const styleSheet = css`
${styles}
`;

globalCSS(styleSheet);

173 changes: 173 additions & 0 deletions src/textarea/textarea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { bind, classNames, Component, createRef, tag } from 'omi';

import calcTextareaHeight from '../_common/js/utils/calcTextareaHeight';
import { getCharacterLength, limitUnicodeMaxLength } from '../_common/js/utils/helper';
import { getClassPrefix } from '../_util/classname';
import { TdTextareaProps } from './type';

export type TextareaProps = TdTextareaProps;
@tag('t-textarea')
export default class Textarea extends Component<TdTextareaProps> {
static css = [];

constructor() {
super();
this.props = {
allowInputOverMax: false,
autofocus: false,
autosize: false,
disabled: false,
readonly: false,
value: '',
...this.props,
};
}

value = '';

isFocused = false;

eventPropsNames;

eventProps;

classPrefix = getClassPrefix();

installed() {
const { value, disabled, ...otherProps } = this.props;
this.value = value;
this.eventPropsNames = Object.keys(otherProps).filter((key) => /^on[A-Z]/.test(key));
this.eventProps = this.eventPropsNames.reduce((eventProps, key) => {
Object.assign(eventProps, {
[key]: (e) => {
if (disabled) return;
if (key === 'onFocus') {
this.isFocused = true;
this.update();
}
if (key === 'onBlur') {
this.isFocused = false;
this.update();
}
this.props[key](e.currentTarget.value, { e });
e.stopPropagation();
},
});
return eventProps;
}, {});

this.update();

const node = this.textArea.current;
this.value = node.value;
this.onInput();
}

countCharacters(text: string) {
// 按照一个中文汉字等于一个字符长度计算
const chineseCharacterRegex = /[\u4e00-\u9fa5]/g;
const chineseCharacters = text.match(chineseCharacterRegex) || [];
return text.length + chineseCharacters.length;
}

// textarea ref
textArea = createRef<any>();

getTextareaStatus(status: string) {
return `${this.classPrefix}-is-${status || ''}`;
}

getTipsStyle(status: string) {
return `${this.classPrefix}-textarea__tips--${status}`;
}

getTextareaIsDisabled(disabled: boolean) {
return `${this.classPrefix}-is-${disabled ? 'disabled' : ''}`;
}

textareaClassPrefix = `${this.classPrefix}-textarea`;

cls() {
return classNames(`${this.textareaClassPrefix}__inner`, {
[`${this.classPrefix}-is-${this.props.status}`]: this.props.status,
[`${this.classPrefix}-is-disabled`]: this.props.disabled,
[`${this.classPrefix}-is-focused`]: this.isFocused,
[`${this.classPrefix}-resize-none`]: typeof this.props.autosize === 'object',
});
}

setHeight(heightObj) {
const node = this.textArea.current;
const clacMinHeight = heightObj.minHeight;
const clacHeight = heightObj.height;
node.style.minHeight = clacMinHeight;
node.style.height = clacHeight;
}

@bind
onInput() {
const node = this.textArea.current;
const { autosize, maxcharacter } = this.props;
if (autosize === true) {
const heightObj = calcTextareaHeight(node);
this.setHeight(heightObj);
} else if (typeof autosize === 'object') {
const heightObj = calcTextareaHeight(node, autosize?.minRows, autosize?.maxRows);
this.setHeight(heightObj);
}
if (maxcharacter) {
const text = node.value;
const length = this.countCharacters(text);
if (length > maxcharacter) {
if (text[text.length - 1].match('/[\u4e00-\u9fa5]/g')) {
node.value = text.slice(0, maxcharacter - 1);
} else {
node.value = text.slice(0, maxcharacter);
}
}
}
}

onChange(e) {
const { target } = e;
let val = (target as HTMLInputElement).value;
if (!this.props?.allowInputOverMax && !this.textArea.current) {
val = limitUnicodeMaxLength(val, this.props?.maxlength);
if (this.props?.maxcharacter && this.props?.maxcharacter >= 0) {
const stringInfo = getCharacterLength(val, this.props?.maxcharacter);
val = typeof stringInfo === 'object' && stringInfo.characters;
}
}
// setValue(val, { e });
this.value = val;

this.props?.onChange(val, { e });
this.update();
}

render(props: TextareaProps) {
const { autofocus, placeholder, readonly, status, disabled, tips, maxlength, maxcharacter } = props;

return (
<>
<div class={classNames(`${this.classPrefix}-textarea`)}>
<textarea
{...this.eventProps}
class={this.cls()}
value={this.value}
placeholder={placeholder}
readonly={readonly}
disabled={disabled}
autofocus={autofocus}
maxlength={maxlength}
maxcharacter={maxcharacter}
onChange={(e) => this.onChange(e)}
onInput={this.onInput}
ref={this.textArea}
></textarea>
{tips && <div class={classNames(`${this.classPrefix}-tips`, this.getTipsStyle(status))}>{tips}</div>}
</div>
</>
);
}
}
Loading