import React, { useEffect, useMemo, useRef } from 'react'; import { Field, FieldProps } from '../field'; import { FieldHint } from '../field-hint'; import { FieldLabel } from '../field-label'; import { Textarea } from '../textarea'; import { buildClassNameObject } from '../utils/misc'; import { useCombinedRef } from '../utils/hooks'; import { TextareaFieldCounter } from './textarea-field-counter'; import * as css from './textarea-field.css'; /*-- Types --*/ type TextareaProps = React.ComponentProps; export interface TextareaFieldProps extends Omit { label?: React.ReactNode; textareaRef?: React.ForwardedRef; error?: boolean | React.ReactNode; hint?: React.ReactNode; /** * If `true`, it adds some space below the textarea so a layout doesn't jump * when a helper text (`error`, `hint`) appears. */ hasHelperTextSpace?: boolean; /** * If `true`, it shows a counter with the current length of the textarea. */ hasCounter?: boolean; width?: FieldProps['width']; className?: | string | { field?: string; label?: string; textarea?: string; hint?: string; }; } /*-- Main --*/ export const TextareaField = React.forwardRef< HTMLDivElement, TextareaFieldProps >(function TextareaField( { id, value, onChange: userOnChange, textareaRef, label, error, hint, hasHelperTextSpace, hasCounter, className, width, maxLength, ...rest }, ref, ) { const cn = useMemo( () => buildClassNameObject(className, 'textarea'), [className], ); const isInvalid = !!error; const hintText = error && typeof error !== 'boolean' ? error : hint; const refTarget = useRef(null); const combinedTextareaRef = useCombinedRef(textareaRef, refTarget); const isControlled = value !== undefined; const [currentLength, setCurrentLength] = React.useState(0); const onChange = (event: React.ChangeEvent) => { if (!isControlled) { setCurrentLength(event.target.value.length); } userOnChange?.(event); }; useEffect(() => { if (!isControlled) { const valueLength = refTarget.current?.value.length || 0; setCurrentLength(valueLength); } }, [isControlled]); const valueLength = isControlled ? String(value).length : currentLength; const shouldShowFieldHint = hasHelperTextSpace || hasCounter || hintText; const shouldShowFieldHintContent = hintText || (hasCounter && typeof maxLength === 'number'); return ( {label && ( {label} )}