From 0f15a0cc0ec1144902373b19ea5beacd7abb97b9 Mon Sep 17 00:00:00 2001 From: emmaccen Date: Thu, 20 Nov 2025 15:29:03 +0100 Subject: [PATCH] fix: PromptInputTextarea now actually handles its own damn resize logic --- components/elements/prompt-input.tsx | 152 ++++++++++++++++++--------- components/multimodal-input.tsx | 23 +--- 2 files changed, 103 insertions(+), 72 deletions(-) diff --git a/components/elements/prompt-input.tsx b/components/elements/prompt-input.tsx index d1928dd8f3..77b316e9e0 100644 --- a/components/elements/prompt-input.tsx +++ b/components/elements/prompt-input.tsx @@ -7,7 +7,7 @@ import type { HTMLAttributes, KeyboardEventHandler, } from "react"; -import { Children } from "react"; +import { Children, useCallback, useEffect, useRef } from "react"; import { Button } from "@/components/ui/button"; import { Select, @@ -18,6 +18,7 @@ import { } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; +import React from "react"; export type PromptInputProps = HTMLAttributes; @@ -38,60 +39,111 @@ export type PromptInputTextareaProps = ComponentProps & { resizeOnNewLinesOnly?: boolean; }; -export const PromptInputTextarea = ({ - onChange, - className, - placeholder = "What would you like to know?", - minHeight = 48, - maxHeight = 164, - disableAutoResize = false, - resizeOnNewLinesOnly = false, - ...props -}: PromptInputTextareaProps) => { - const handleKeyDown: KeyboardEventHandler = (e) => { - if (e.key === "Enter") { - // Don't submit if IME composition is in progress - if (e.nativeEvent.isComposing) { - return; - } - if (e.shiftKey) { - // Allow newline - return; +export const PromptInputTextarea = React.forwardRef< + HTMLTextAreaElement, + PromptInputTextareaProps +>( + ( + { + onChange, + className, + placeholder = "Hi, there! How can I help you today?", + minHeight = 48, + maxHeight = 164, + disableAutoResize = false, + resizeOnNewLinesOnly = false, + ...props + }, + forwardedRef + ) => { + const internalRef = useRef(null); + const textareaRef = + (forwardedRef as React.RefObject) || internalRef; + const prevLineCountRef = useRef(0); + + const adjustHeight = useCallback(() => { + const textarea = textareaRef.current; + if (!textarea || disableAutoResize) return; + + // Reset height to auto to get the correct scrollHeight + textarea.style.height = "auto"; + const scrollHeight = textarea.scrollHeight; + + const newHeight = Math.min(Math.max(scrollHeight, minHeight), maxHeight); + textarea.style.height = `${newHeight}px`; + }, [disableAutoResize, maxHeight, minHeight, textareaRef]); + + useEffect(() => { + adjustHeight(); + }, [disableAutoResize, minHeight, maxHeight]); + + useEffect(() => { + if (disableAutoResize) return; + + const currentValue = props.value?.toString() || ""; + + if (resizeOnNewLinesOnly) { + const currentLineCount = (currentValue.match(/\n/g) || []).length; + if (currentLineCount !== prevLineCountRef.current) { + adjustHeight(); + prevLineCountRef.current = currentLineCount; + } + } else { + adjustHeight(); } + }, [props.value, disableAutoResize, resizeOnNewLinesOnly]); + + const handleChange = (e: React.ChangeEvent) => { + onChange?.(e); + }; - // Submit on Enter (without Shift) - e.preventDefault(); - const form = e.currentTarget.form; - if (form) { - form.requestSubmit(); + const handleKeyDown: KeyboardEventHandler = (e) => { + if (e.key === "Enter") { + // Don't submit if IME composition is in progress + if (e.nativeEvent.isComposing) { + return; + } + if (e.shiftKey) { + // Allow newline + return; + } + e.preventDefault(); + const form = e.currentTarget.form; + if (form) { + form.requestSubmit(); + } } - } - }; + }; - return ( -