index.tsx56 lines · main
1'use client'
2
3import React, { forwardRef, useImperativeHandle, useLayoutEffect, useRef } from 'react'
4
5import { cn } from '../../lib/utils'
6import { Textarea } from '../shadcn/ui/textarea'
7
8export interface ExpandingTextAreaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
9 /* The value of the textarea. Required to calculate the height of the textarea. */
10 value: string
11}
12
13/**
14 * This is a custom TextArea component that expands based on the content.
15 */
16const ExpandingTextArea = forwardRef<HTMLTextAreaElement | null, ExpandingTextAreaProps>(
17 ({ className, value, ...props }, ref) => {
18 const internalRef = useRef<HTMLTextAreaElement | null>(null)
19
20 useImperativeHandle(ref, () => internalRef.current as HTMLTextAreaElement, [])
21
22 const updateTextAreaHeight = (element: HTMLTextAreaElement | null) => {
23 if (!element) return
24
25 // Match single-line input height (h-10 = 40px) so we don't shrink when typing; grow only when content wraps
26 const singleLineHeightPx = 40
27 element.style.height = 'auto'
28 const contentHeight = element.scrollHeight
29 element.style.height = Math.max(singleLineHeightPx, contentHeight) + 'px'
30 }
31
32 useLayoutEffect(() => {
33 updateTextAreaHeight(internalRef.current)
34 }, [value])
35
36 return (
37 <Textarea
38 ref={(element) => {
39 if (element) {
40 internalRef.current = element
41 updateTextAreaHeight(element)
42 }
43 }}
44 rows={1}
45 aria-expanded={false}
46 className={cn('h-auto resize-none box-border', className)}
47 value={value}
48 {...props}
49 />
50 )
51 }
52)
53
54ExpandingTextArea.displayName = 'ExpandingTextArea'
55
56export { ExpandingTextArea }