@@ -3,6 +3,10 @@ import {
33 forwardRef ,
44 TextareaHTMLAttributes ,
55 ForwardedRef ,
6+ useEffect ,
7+ useRef ,
8+ useImperativeHandle ,
9+ useCallback ,
610} from 'react' ;
711import styled , { css } from 'styled-components' ;
812import { spacing } from '../../spacing' ;
@@ -12,13 +16,19 @@ type Props = TextareaHTMLAttributes<HTMLTextAreaElement> & {
1216 variant ?: TextAreaVariant ;
1317 width ?: CSSProperties [ 'width' ] ;
1418 height ?: CSSProperties [ 'height' ] ;
19+ /**
20+ * Automatically adjust height to fit content
21+ * When enabled, the textarea will grow/shrink to show all content
22+ */
23+ autoGrow ?: boolean ;
1524} ;
1625type RefType = HTMLTextAreaElement | null ;
1726
1827const TextAreaContainer = styled . textarea < {
1928 variant : TextAreaVariant ;
2029 width ?: CSSProperties [ 'width' ] ;
2130 height ?: CSSProperties [ 'height' ] ;
31+ autoGrow ?: boolean ;
2232} > `
2333 padding: ${ spacing . r12 } ${ spacing . r8 } ;
2434 border-radius: 4px;
@@ -46,6 +56,13 @@ const TextAreaContainer = styled.textarea<{
4656 height : ${ props . height } ;
4757 ` }
4858
59+ ${ ( props ) =>
60+ props . autoGrow &&
61+ css `
62+ resize : none;
63+ overflow : hidden;
64+ ` }
65+
4966 &:placeholder-shown {
5067 font-style: italic;
5168 }
@@ -77,18 +94,67 @@ const TextAreaContainer = styled.textarea<{
7794` ;
7895
7996function TextAreaElement (
80- { rows = 3 , cols = 20 , width, height, variant = 'code' , ...rest } : Props ,
97+ {
98+ rows = 3 ,
99+ cols = 20 ,
100+ width,
101+ height,
102+ variant = 'code' ,
103+ autoGrow = false ,
104+ value,
105+ defaultValue,
106+ onChange,
107+ ...rest
108+ } : Props ,
81109 ref : ForwardedRef < RefType > ,
82110) {
111+ const internalRef = useRef < HTMLTextAreaElement > ( null ) ;
112+
113+ // Expose the textarea element to parent components via forwarded ref
114+ useImperativeHandle ( ref , ( ) => internalRef . current as HTMLTextAreaElement ) ;
115+
116+ const adjustHeight = useCallback ( ( ) => {
117+ const textarea = internalRef . current ;
118+ if ( ! textarea || ! autoGrow ) return ;
119+
120+ // Reset height to auto to get the correct scrollHeight
121+ textarea . style . height = 'auto' ;
122+
123+ // Set the height to match the content
124+ const newHeight = textarea . scrollHeight ;
125+ textarea . style . height = `${ newHeight } px` ;
126+ } , [ autoGrow ] ) ;
127+
128+ // Adjust height when content changes
129+ useEffect ( ( ) => {
130+ adjustHeight ( ) ;
131+ } , [ value , defaultValue , autoGrow , adjustHeight ] ) ;
132+
133+ // Adjust height on mount
134+ useEffect ( ( ) => {
135+ adjustHeight ( ) ;
136+ } , [ adjustHeight ] ) ;
137+
138+ const handleChange = ( e : React . ChangeEvent < HTMLTextAreaElement > ) => {
139+ if ( autoGrow ) {
140+ adjustHeight ( ) ;
141+ }
142+ onChange ?.( e ) ;
143+ } ;
144+
83145 if ( width || height ) {
84146 return (
85147 < TextAreaContainer
86148 className = "sc-textarea"
87149 width = { width }
88150 height = { height }
89151 variant = { variant }
152+ autoGrow = { autoGrow }
153+ value = { value }
154+ defaultValue = { defaultValue }
155+ onChange = { handleChange }
90156 { ...rest }
91- ref = { ref }
157+ ref = { internalRef }
92158 />
93159 ) ;
94160 }
@@ -99,8 +165,12 @@ function TextAreaElement(
99165 rows = { rows }
100166 cols = { cols }
101167 variant = { variant }
168+ autoGrow = { autoGrow }
169+ value = { value }
170+ defaultValue = { defaultValue }
171+ onChange = { handleChange }
102172 { ...rest }
103- ref = { ref }
173+ ref = { internalRef }
104174 />
105175 ) ;
106176}
0 commit comments