Skip to content

Commit 3b6f9a2

Browse files
Contolled autosizing textarea (codesandbox#3734)
* contolled autosizing textarea * fix Co-authored-by: Sara Vieira <[email protected]>
1 parent 7fcc728 commit 3b6f9a2

File tree

2 files changed

+81
-21
lines changed

2 files changed

+81
-21
lines changed

packages/components/src/components/Textarea/index.stories.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,14 @@ export const Placeholder = () => (
1818

1919
export const MaxLength = () => (
2020
<Wrapper>
21-
<Textarea maxLength={10} label="Your full name" placeholder="John Doe" />
21+
<Textarea maxLength={10} placeholder="John Doe" />
2222
</Wrapper>
2323
);
2424

2525
export const onChange = () => (
2626
<Wrapper>
2727
<Textarea
2828
maxLength={10}
29-
label="Your full name"
3029
placeholder="John Doe"
3130
onChange={action('textarea change')}
3231
/>
@@ -38,3 +37,23 @@ export const autoResize = () => (
3837
<Textarea autosize placeholder="Write a lot of lines here" />
3938
</Wrapper>
4039
);
40+
41+
export const Controlled = () => {
42+
const [value, setValue] = React.useState('');
43+
return (
44+
<>
45+
<Wrapper>
46+
<Textarea
47+
autosize
48+
maxLength={20}
49+
value={value}
50+
onChange={event => setValue(event.target.value)}
51+
placeholder="Write a lot of lines here"
52+
/>
53+
</Wrapper>
54+
<button type="button" onClick={() => setValue('')}>
55+
clear value
56+
</button>
57+
</>
58+
);
59+
};

packages/components/src/components/Textarea/index.tsx

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
import React, { useState, useCallback } from 'react';
1+
import React, { useCallback } from 'react';
22
import styled from 'styled-components';
33
import css from '@styled-system/css';
4-
import { Stack, Input } from '../..';
4+
import Rect from '@reach/rect';
5+
import VisuallyHidden from '@reach/visually-hidden';
6+
import { Stack, Input, Text } from '../..';
57

68
interface ITextareaProps
79
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
810
maxLength?: number;
911
autosize?: boolean;
1012
value?: string;
13+
defaultValue?: string;
1114
}
1215

1316
export const TextareaComponent: any = styled(Input).attrs({
@@ -36,17 +39,29 @@ const Count = styled.div<{ limit: boolean }>(({ limit }) =>
3639

3740
export const Textarea: React.FC<ITextareaProps> = ({
3841
maxLength,
42+
defaultValue = '',
43+
value = '',
3944
onChange,
4045
onKeyPress,
4146
autosize,
4247
...props
4348
}) => {
44-
const [value, setValue] = useState(props.value || '');
49+
const [innerValue, setInnerValue] = React.useState<string>(defaultValue);
50+
51+
/**
52+
* To support both contolled and uncontrolled components
53+
* We sync props.value with internalValue
54+
*/
55+
React.useEffect(
56+
function syncValue() {
57+
setInnerValue(value);
58+
},
59+
[value]
60+
);
4561

4662
const internalOnChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
4763
if (onChange) onChange(event);
48-
setValue(event.target.value);
49-
if (autosize) resize(event.target);
64+
setInnerValue(event.target.value);
5065
};
5166

5267
const Wrapper = useCallback(
@@ -61,27 +76,53 @@ export const Textarea: React.FC<ITextareaProps> = ({
6176
[maxLength]
6277
);
6378

64-
const resize = (element: HTMLTextAreaElement) => {
65-
const offset = 2; // for borders on both sides
66-
element.style.height = ''; // reset before setting again
67-
element.style.height = element.scrollHeight + offset + 'px';
68-
};
69-
7079
return (
7180
<>
7281
<Wrapper>
73-
<TextareaComponent
74-
value={value}
75-
onChange={internalOnChange}
76-
maxLength={maxLength}
77-
{...props}
78-
/>
82+
<Autosize value={innerValue}>
83+
{(height: number) => (
84+
<TextareaComponent
85+
value={innerValue}
86+
onChange={internalOnChange}
87+
maxLength={maxLength}
88+
style={{ height }}
89+
{...props}
90+
/>
91+
)}
92+
</Autosize>
93+
7994
{maxLength ? (
80-
<Count limit={maxLength <= value.length}>
81-
{value.length}/{maxLength}
95+
<Count limit={maxLength <= innerValue.length}>
96+
{innerValue.length}/{maxLength}
8297
</Count>
8398
) : null}
8499
</Wrapper>
85100
</>
86101
);
87102
};
103+
104+
const Autosize = ({ value, ...props }) => (
105+
<Rect>
106+
{({ rect, ref }) => (
107+
<>
108+
<VisuallyHidden>
109+
<Text
110+
block
111+
ref={ref}
112+
size={3}
113+
style={{
114+
// match textarea styles
115+
whiteSpace: 'pre',
116+
lineHeight: 1,
117+
minHeight: 64,
118+
padding: 8,
119+
}}
120+
>
121+
{value + ' '}
122+
</Text>
123+
</VisuallyHidden>
124+
{props.children(rect ? rect.height + 8 : 0)}
125+
</>
126+
)}
127+
</Rect>
128+
);

0 commit comments

Comments
 (0)