1- import React , { useState , useCallback } from 'react' ;
1+ import React , { useCallback } from 'react' ;
22import styled from 'styled-components' ;
33import 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
68interface ITextareaProps
79 extends React . TextareaHTMLAttributes < HTMLTextAreaElement > {
810 maxLength ?: number ;
911 autosize ?: boolean ;
1012 value ?: string ;
13+ defaultValue ?: string ;
1114}
1215
1316export const TextareaComponent : any = styled ( Input ) . attrs ( {
@@ -36,17 +39,29 @@ const Count = styled.div<{ limit: boolean }>(({ limit }) =>
3639
3740export 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