"use client"; import { Field, FieldContent, FieldDescription, FieldError, FieldLabel, } from "@/components/ui/field"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; import type { LucideIcon } from "lucide-react"; import * as React from "react"; import type { FieldPath, FieldValues } from "react-hook-form"; import { Controller } from "react-hook-form"; type BaseProps = { id?: string; icon?: LucideIcon; label?: React.ReactNode; placeholder?: string; description?: React.ReactNode; className?: string; wrapperClassName?: string; textareaClassName?: string; rows?: number; minHeight?: string; maxLength?: number; showCount?: boolean; disabled?: boolean; required?: boolean; autoResize?: boolean; trimOnBlur?: boolean; readOnly?: boolean; value?: string; onValueChange?: (value: string) => void; onChange?: React.ChangeEventHandler; onKeyUp?: React.KeyboardEventHandler; onPaste?: React.ClipboardEventHandler; onBlur?: React.FocusEventHandler; defaultValue?: string; error?: React.ReactNode; }; export type TextareaFieldProps< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath > = BaseProps & { name?: TName }; const TextareaField = React.forwardRef< HTMLTextAreaElement, TextareaFieldProps >((props, ref) => { const { name, id, icon: Icon, label, placeholder, description, className, wrapperClassName, textareaClassName, rows = 4, minHeight, maxLength, showCount = false, disabled = false, required = false, autoResize = false, trimOnBlur = false, readOnly = false, value: externalValue, onValueChange, onChange, onKeyUp, onPaste, onBlur, defaultValue, error, } = props; const innerRef = React.useRef(null); const setMergedRef = React.useCallback( (el: HTMLTextAreaElement | null) => { innerRef.current = el; if (typeof ref === "function") ref(el); else if (ref) (ref as React.MutableRefObject).current = el; }, [ref] ); const autoId = React.useId(); const textareaId = id ?? autoId; const [internal, setInternal] = React.useState(defaultValue ?? ""); const standaloneValue = externalValue ?? internal; const resizeNow = React.useCallback(() => { if (!autoResize || !innerRef.current) return; const el = innerRef.current; el.style.height = "auto"; el.style.height = `${el.scrollHeight}px`; }, [autoResize]); React.useEffect(() => { resizeNow(); }, [resizeNow]); if (!name) { const value = standaloneValue ?? ""; const handleChange: React.ChangeEventHandler = (e) => { const next = e.target.value; void (onValueChange ? onValueChange(next) : setInternal(next)); onChange?.(e); resizeNow(); }; const handleBlur: React.FocusEventHandler = (e) => { if (trimOnBlur) { const trimmed = e.target.value.trim(); if (trimmed !== e.target.value) { void (onValueChange ? onValueChange(trimmed) : setInternal(trimmed)); } } onBlur?.(e); }; const count = typeof value === "string" ? value.length : 0; return (
{label ? ( {Icon && } {label} {required && *} ) : null}