Spaces:
Paused
Paused
| import * as React from "react"; | |
| import { cn } from "@/lib/utils"; | |
| import { useImperativeHandle } from "react"; | |
| export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {} | |
| const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(({ className, ...props }, ref) => { | |
| return <textarea className={cn("flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", className)} ref={ref} {...props} />; | |
| }); | |
| Textarea.displayName = "Textarea"; | |
| export { Textarea }; | |
| interface UseAutosizeTextAreaProps { | |
| textAreaRef: React.MutableRefObject<HTMLTextAreaElement | null>; | |
| minHeight?: number; | |
| maxHeight?: number; | |
| triggerAutoSize: string; | |
| } | |
| export const useAutosizeTextArea = ({ textAreaRef, triggerAutoSize, maxHeight = Number.MAX_SAFE_INTEGER, minHeight = 0 }: UseAutosizeTextAreaProps) => { | |
| const [init, setInit] = React.useState(true); | |
| React.useEffect(() => { | |
| // We need to reset the height momentarily to get the correct scrollHeight for the textarea | |
| const offsetBorder = 0; | |
| const textAreaElement = textAreaRef.current; | |
| if (textAreaElement) { | |
| if (init) { | |
| textAreaElement.style.minHeight = `${minHeight + offsetBorder}px`; | |
| if (maxHeight > minHeight) { | |
| textAreaElement.style.maxHeight = `${maxHeight}px`; | |
| } | |
| setInit(false); | |
| } | |
| textAreaElement.style.height = `${minHeight + offsetBorder}px`; | |
| const scrollHeight = textAreaElement.scrollHeight; | |
| // We then set the height directly, outside of the render loop | |
| // Trying to set this with state or a ref will product an incorrect value. | |
| if (scrollHeight > maxHeight) { | |
| textAreaElement.style.height = `${maxHeight}px`; | |
| } else { | |
| textAreaElement.style.height = `${scrollHeight + offsetBorder}px`; | |
| } | |
| } | |
| }, [textAreaRef.current, triggerAutoSize]); | |
| }; | |
| export type AutosizeTextAreaRef = { | |
| textArea: HTMLTextAreaElement; | |
| maxHeight: number; | |
| minHeight: number; | |
| }; | |
| type AutosizeTextAreaProps = { | |
| maxHeight?: number; | |
| minHeight?: number; | |
| } & React.TextareaHTMLAttributes<HTMLTextAreaElement>; | |
| export const AutosizeTextarea = React.forwardRef<AutosizeTextAreaRef, AutosizeTextAreaProps>(({ maxHeight = Number.MAX_SAFE_INTEGER, minHeight = 52, className, onChange, value, ...props }: AutosizeTextAreaProps, ref: React.Ref<AutosizeTextAreaRef>) => { | |
| const textAreaRef = React.useRef<HTMLTextAreaElement | null>(null); | |
| const [triggerAutoSize, setTriggerAutoSize] = React.useState(""); | |
| useAutosizeTextArea({ | |
| textAreaRef, | |
| triggerAutoSize: triggerAutoSize, | |
| maxHeight, | |
| minHeight, | |
| }); | |
| useImperativeHandle(ref, () => ({ | |
| textArea: textAreaRef.current as HTMLTextAreaElement, | |
| focus: () => textAreaRef?.current?.focus(), | |
| maxHeight, | |
| minHeight, | |
| })); | |
| React.useEffect(() => { | |
| setTriggerAutoSize(value as string); | |
| }, [props?.defaultValue, value]); | |
| return ( | |
| <textarea | |
| {...props} | |
| value={value} | |
| ref={textAreaRef} | |
| className={cn("flex w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", className)} | |
| onChange={(e) => { | |
| setTriggerAutoSize(e.target.value); | |
| onChange?.(e); | |
| }} | |
| /> | |
| ); | |
| }); | |
| AutosizeTextarea.displayName = "AutosizeTextarea"; | |