Skip to content

Commit

Permalink
feat(ui): add textarea component
Browse files Browse the repository at this point in the history
  • Loading branch information
xiejay97 committed Dec 14, 2021
1 parent 46dd985 commit 144776b
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 0 deletions.
3 changes: 3 additions & 0 deletions packages/ui/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ export { DSeparator } from './separator';
export type { DTabProps, DTabsProps } from './tabs';
export { DTab, DTabs } from './tabs';

export type { DTextareaProps } from './textarea';
export { DTextarea } from './textarea';

export type { DTooltipProps } from './tooltip';
export { DTooltip } from './tooltip';

Expand Down
32 changes: 32 additions & 0 deletions packages/ui/src/components/textarea/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
group: Data Entry
title: Textarea
---

Multi-line plain text edit control.

## When To Use

It is desirable to allow users to enter large amounts of free-form text, such as comments on comments or feedback forms.

## API

### DTextareaProps

Extend `React.InputHTMLAttributes<HTMLTextAreaElement>`, [DFormControl](/components/Form#DFormControl).

<!-- prettier-ignore-start -->
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| dValue | Bind value | [string, Updater\<string\>?] | - |
| dRows | Set the number of rows, `'auto'` means automatic change, and can also pass the maximum and minimum values | 'auto' \| { minRows?: number; maxRows?: number } | - |
| dResizable | Whether the size can be changed | boolean | true |
| dShowCount | Display the number of input words | boolean | `((num: number) => React.ReactNode)` | false |
| onValueChange | Callback for binding value change | `(value: string) => void` | - |
<!-- prettier-ignore-end -->

### DTextareaRef

```tsx
export type DTextareaRef = HTMLTextAreaElement;
```
31 changes: 31 additions & 0 deletions packages/ui/src/components/textarea/README.zh-Hant.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
title: 文本域
---

多行纯文本编辑控件。

## 何时使用

希望允许用户输入大量自由格式的文本,例如对评论或反馈表的评论。

## API

### DTextareaProps

继承 `React.InputHTMLAttributes<HTMLTextAreaElement>`[DFormControl](/components/Form#DFormControl)

<!-- prettier-ignore-start -->
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| dValue | 绑定值 | [string, Updater\<string\>?] | - |
| dRows | 设置行数,`'auto'` 代表自动变化,也可传递最大最小值 | 'auto' \| { minRows?: number; maxRows?: number } | - |
| dResizable | 尺寸是否可变化 | boolean | true |
| dShowCount | 显示输入字数 | boolean | `((num: number) => React.ReactNode)` | false |
| onValueChange | 绑定值改变的回调 | `(value: string) => void` | - |
<!-- prettier-ignore-end -->

### DTextareaRef

```tsx
export type DTextareaRef = HTMLTextAreaElement;
```
130 changes: 130 additions & 0 deletions packages/ui/src/components/textarea/Textarea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import type { Updater } from '../../hooks/immer';
import type { DFormControl } from '../form';

import { isFunction, isNumber, isUndefined } from 'lodash';
import React, { useEffect, useImperativeHandle, useMemo } from 'react';
import { useCallback } from 'react';

import { usePrefixConfig, useComponentConfig, useTwoWayBinding, useImmer, useRefCallback } from '../../hooks';
import { getClassName, mergeStyle } from '../../utils';

export type DTextareaRef = HTMLTextAreaElement;

export interface DTextareaProps extends React.InputHTMLAttributes<HTMLTextAreaElement>, DFormControl {
dValue?: [string, Updater<string>?];
dRows?: 'auto' | { minRows?: number; maxRows?: number };
dResizable?: boolean;
dShowCount?: boolean | ((num: number) => React.ReactNode);
onValueChange?: (value: string) => void;
}

export const DTextarea = React.forwardRef<DTextareaRef, DTextareaProps>((props, ref) => {
const {
dFormControlName,
dValue,
dRows,
dResizable = true,
dShowCount = false,
onValueChange,
className,
style,
maxLength,
disabled,
onClick,
onFocus,
onBlur,
onChange,
...restProps
} = useComponentConfig(DTextarea.name, props);

//#region Context
const dPrefix = usePrefixConfig();
//#endregion

//#region Ref
const [textareaEl, textareaRef] = useRefCallback<HTMLTextAreaElement>();
//#endregion

const [bindValue, changeBindValue] = useTwoWayBinding('', dValue, onValueChange, {
name: dFormControlName,
});

const [rowNum, setRowNum] = useImmer(1);

const resizable = dResizable && isUndefined(dRows);
const heightStyle = useMemo(() => {
let overflow: 'hidden' | undefined;
let height: number | undefined;
let minHeight: number | undefined;
let maxHeight: number | undefined;

if (!isUndefined(dRows)) {
height = rowNum * 24 + 8;
if (dRows === 'auto') {
overflow = 'hidden';
} else {
if (isNumber(dRows.minRows)) {
minHeight = dRows.minRows * 24 + 8;
}
if (isNumber(dRows.maxRows)) {
maxHeight = dRows.maxRows * 24 + 8;
if (maxHeight > height) {
overflow = 'hidden';
}
}
}
}
return { overflow, height, minHeight, maxHeight };
}, [dRows, rowNum]);

const handleChange = useCallback<React.ChangeEventHandler<HTMLTextAreaElement>>(
(e) => {
onChange?.(e);
changeBindValue(e.currentTarget.value);

const el = e.currentTarget;
const overflow = el.style.overflow;
const height = el.style.height;
el.style.overflow = 'hidden';
el.style.height = '32px';
setRowNum((el.scrollHeight - 6) / 24);
el.style.overflow = overflow;
el.style.height = height;
},
[changeBindValue, onChange, setRowNum]
);

useEffect(() => {
if (textareaEl) {
setRowNum((textareaEl.scrollHeight - 6) / 24);
}
}, [setRowNum, textareaEl]);

useImperativeHandle<HTMLTextAreaElement | null, HTMLTextAreaElement | null>(ref, () => textareaEl, [textareaEl]);

return (
<>
<textarea
{...restProps}
ref={textareaRef}
className={getClassName(className, `${dPrefix}textarea`)}
style={mergeStyle(style, {
resize: resizable ? undefined : 'none',
...heightStyle,
})}
maxLength={maxLength}
value={bindValue}
disabled={disabled}
aria-disabled={disabled}
onChange={handleChange}
/>
<div className={`${dPrefix}textarea__count`} style={{ display: dShowCount === false ? 'none' : undefined }}>
{isFunction(dShowCount)
? dShowCount(bindValue.length)
: isUndefined(maxLength)
? bindValue.length
: `${bindValue.length} / ${maxLength}`}
</div>
</>
);
});
21 changes: 21 additions & 0 deletions packages/ui/src/components/textarea/demos/1.Basic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
title:
en-US: Basic
zh-Hant: 基本
---

# en-US

The simplest usage.

# zh-Hant

最简单的用法。

```tsx
import { DTextarea } from '@react-devui/ui';

export default function Demo() {
return <DTextarea placeholder="Basic" rows="5" />;
}
```
27 changes: 27 additions & 0 deletions packages/ui/src/components/textarea/demos/2.Rows.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title:
en-US: Set the number of rows
zh-Hant: 设置行数
---

# en-US

You can set the number of rows in the textarea to automatically change or pass the maximum and minimum values.

# zh-Hant

可以设置文本域行数自动变化或者传递最大最小值。

```tsx
import { DTextarea } from '@react-devui/ui';

export default function Demo() {
return (
<>
<DTextarea placeholder="Auto rows" dRows="auto" />
<br />
<DTextarea placeholder="minRows: 3, maxRows: 5" dRows={{ minRows: 3, maxRows: 5 }} />
</>
);
}
```
30 changes: 30 additions & 0 deletions packages/ui/src/components/textarea/demos/3.Count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
title:
en-US: The number of input words
zh-Hant: 输入字数
---

# en-US

Display the number of input words.

# zh-Hant

显示输入字数。

```tsx
import { DTextarea } from '@react-devui/ui';
import { useImmer } from '@react-devui/ui/hooks';

export default function Demo() {
const [value, setValue] = useImmer('Show count');

return (
<>
<DTextarea placeholder="Show count" dValue={[value, setValue]} dShowCount />
<br />
<DTextarea placeholder="Show count" maxLength={100} dValue={[value, setValue]} dShowCount />
</>
);
}
```
21 changes: 21 additions & 0 deletions packages/ui/src/components/textarea/demos/4.Disabled.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
title:
en-US: Disable
zh-Hant: 禁用
---

# en-US

Disable the textarea.

# zh-Hant

禁用文本域。

```tsx
import { DTextarea } from '@react-devui/ui';

export default function Demo() {
return <DTextarea placeholder="Show count" maxLength={100} disabled dShowCount />;
}
```
1 change: 1 addition & 0 deletions packages/ui/src/components/textarea/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Textarea';

0 comments on commit 144776b

Please sign in to comment.