@@ -12,13 +12,18 @@ import {
1212 type ForwardedRef ,
1313 type KeyboardEventHandler ,
1414 type ReactElement ,
15+ type ReactNode ,
1516 type SyntheticEvent ,
1617} from 'react' ;
1718
1819import { useSyncRefs } from '#/hooks/useSyncRefs' ;
20+ import { cn } from '#/utilities/styleUtilities' ;
1921
2022import styles from './styles/textarea.module.scss' ;
2123
24+ /**
25+ * @deprecated Use Textarea.Slot instead for custom actions
26+ */
2227export type ExtraAction = {
2328 icon : ReactElement ;
2429 title : string ;
@@ -32,6 +37,10 @@ type TextareaProps = {
3237 * The id of the textarea
3338 */
3439 id ?: string ;
40+ /**
41+ * The place where the textarea slots are placed
42+ */
43+ children ?: ReactNode ;
3544 /**
3645 * If `true`, Textarea will have `autoComplete` functionality
3746 */
@@ -55,6 +64,7 @@ type TextareaProps = {
5564 disabled ?: boolean ;
5665 /**
5766 * Collection of extra actions the input can preform
67+ * @deprecated Use Textarea.Slot instead for custom actions
5868 */
5969 extraActions ?: ExtraAction [ ] ;
6070 /**
@@ -118,11 +128,12 @@ type TextareaProps = {
118128 value ?: string ;
119129} ;
120130
121- const TextareaComponent = (
131+ export const TextareaRoot = (
122132 {
123133 'data-test-id' : dataTestId = 'fondue-textarea' ,
124134 autocomplete,
125135 autosize,
136+ children,
126137 clearable,
127138 decorator,
128139 defaultValue,
@@ -179,46 +190,47 @@ const TextareaComponent = (
179190 data-disabled = { disabled || readOnly }
180191 data-has-decorator = { decorator ? true : false }
181192 data-has-tools = { hasTools }
182- data-replicated-value = { value }
183193 data-resizable = { resizable }
184194 data-status = { status }
185195 data-max-rows = { ! ! maxRows }
186196 data-test-id = { dataTestId }
187197 style = { { '--max-rows' : `${ maxRows } ` } as CSSProperties }
188198 >
189199 { decorator ? < div className = { styles . decorator } > { decorator } </ div > : null }
190- < textarea
191- { ...props }
192- onMouseDown = { ( mouseEvent ) => {
193- wasClicked . current = true ;
194- mouseEvent . currentTarget . dataset . showFocusRing = 'false' ;
195- } }
196- onFocus = { ( focusEvent ) => {
197- if ( ! wasClicked . current ) {
198- focusEvent . target . dataset . showFocusRing = 'true' ;
199- }
200- props . onFocus ?.( focusEvent ) ;
201- } }
202- onBlur = { ( blurEvent ) => {
203- blurEvent . target . dataset . showFocusRing = 'false' ;
204- wasClicked . current = false ;
205- props . onBlur ?.( blurEvent ) ;
206- } }
207- autoComplete = { autocomplete ? 'on' : 'off' }
208- className = { styles . textarea }
209- disabled = { disabled }
210- onKeyDown = { handleKeyDown }
211- onInput = { ( event ) => setValue ( event . currentTarget . value ) }
212- onSelect = { ( event ) => {
213- if ( ! selectable ) {
214- event . currentTarget . selectionStart = event . currentTarget . selectionEnd ;
215- }
216- } }
217- readOnly = { readOnly }
218- ref = { ref }
219- rows = { rows }
220- value = { value }
221- > </ textarea >
200+ < div className = { styles . textareaWrapper } data-replicated-value = { value } >
201+ < textarea
202+ { ...props }
203+ onMouseDown = { ( mouseEvent ) => {
204+ wasClicked . current = true ;
205+ mouseEvent . currentTarget . dataset . showFocusRing = 'false' ;
206+ } }
207+ onFocus = { ( focusEvent ) => {
208+ if ( ! wasClicked . current ) {
209+ focusEvent . target . dataset . showFocusRing = 'true' ;
210+ }
211+ props . onFocus ?.( focusEvent ) ;
212+ } }
213+ onBlur = { ( blurEvent ) => {
214+ blurEvent . target . dataset . showFocusRing = 'false' ;
215+ wasClicked . current = false ;
216+ props . onBlur ?.( blurEvent ) ;
217+ } }
218+ autoComplete = { autocomplete ? 'on' : 'off' }
219+ className = { styles . textarea }
220+ disabled = { disabled }
221+ onKeyDown = { handleKeyDown }
222+ onInput = { ( event ) => setValue ( event . currentTarget . value ) }
223+ onSelect = { ( event ) => {
224+ if ( ! selectable ) {
225+ event . currentTarget . selectionStart = event . currentTarget . selectionEnd ;
226+ }
227+ } }
228+ readOnly = { readOnly }
229+ ref = { ref }
230+ rows = { rows }
231+ value = { value }
232+ > </ textarea >
233+ </ div >
222234 { status === 'loading' && < div className = { styles . loadingStatus } data-test-id = { `${ dataTestId } -loader` } /> }
223235 { hasTools && (
224236 < div className = { styles . tools } >
@@ -250,9 +262,34 @@ const TextareaComponent = (
250262 ) }
251263 </ div >
252264 ) }
265+ { children }
253266 </ div >
254267 ) ;
255268} ;
269+ TextareaRoot . displayName = 'Textarea.Root' ;
270+
271+ export type TextareaSlotProps = {
272+ children : ReactNode ;
273+ name ?: 'left' | 'right' ;
274+ className ?: string ;
275+ } ;
276+
277+ export const TextareaSlot = (
278+ { name, className, ...slotProps } : TextareaSlotProps ,
279+ forwardedRef : ForwardedRef < HTMLDivElement > ,
280+ ) => {
281+ return < div data-slot data-name = { name } { ...slotProps } ref = { forwardedRef } className = { cn ( styles . slot , className ) } /> ;
282+ } ;
283+
284+ TextareaSlot . displayName = 'Textarea.Slot' ;
285+
286+ const ForwardedRefTextareaRoot = forwardRef < HTMLTextAreaElement , TextareaProps > ( TextareaRoot ) ;
287+ const ForwardedRefTextareaSlot = forwardRef < HTMLDivElement , TextareaSlotProps > ( TextareaSlot ) ;
256288
257- export const Textarea = forwardRef < HTMLTextAreaElement , TextareaProps > ( TextareaComponent ) ;
258- Textarea . displayName = 'Textarea' ;
289+ // @ts -expect-error We support both single component (without slots) and compound components (with slots)
290+ export const Textarea : typeof TextareaRoot & {
291+ Root : typeof ForwardedRefTextareaRoot ;
292+ Slot : typeof ForwardedRefTextareaSlot ;
293+ } = ForwardedRefTextareaRoot ;
294+ Textarea . Root = ForwardedRefTextareaRoot ;
295+ Textarea . Slot = ForwardedRefTextareaSlot ;
0 commit comments