import './index.css' import { useVisuallyHidden } from '@react-aria/visually-hidden' import { forwardRef, useCallback, useEffect, useRef, useState } from 'react' import FieldLabel from '../FieldLabel' import { countCodePointsInString, mergeRefs } from '../../_lib' import { useFocusWithClick } from '../TextField/useFocusWithClick' import { useId } from '@react-aria/utils' import { AssistiveText } from '../TextField/AssistiveText' import { useClassNames } from '../../_lib/useClassNames' export type TextAreaProps = { value?: string onChange?: (value: string) => void showCount?: boolean showLabel?: boolean assistiveText?: string invalid?: boolean label?: string requiredText?: string disabled?: boolean subLabel?: React.ReactNode autoHeight?: boolean getCount?: (value: string) => number } & Omit, 'onChange'> const TextArea = forwardRef( function TextAreaInner( { onChange, className, value, showCount = false, showLabel = false, label = '', requiredText, subLabel, disabled = false, required, assistiveText, maxLength, autoHeight = false, rows: initialRows = 4, invalid, getCount = countCodePointsInString, ...props }, forwardRef, ) { const { visuallyHiddenProps } = useVisuallyHidden() const textareaRef = useRef(null) const [count, setCount] = useState(getCount(value ?? '')) const [rows, setRows] = useState(initialRows) const syncHeight = useCallback( (textarea: HTMLTextAreaElement) => { const rows = (`${textarea.value}\n`.match(/\n/gu)?.length ?? 0) || 1 setRows(initialRows <= rows ? rows : initialRows) }, [initialRows], ) const nonControlled = value === undefined const handleChange = useCallback( (e: React.ChangeEvent) => { const value = e.target.value const count = getCount(value) if (maxLength !== undefined && count > maxLength) { return } if (nonControlled) { setCount(count) } if (autoHeight && textareaRef.current !== null) { syncHeight(textareaRef.current) } onChange?.(value) }, [autoHeight, getCount, maxLength, nonControlled, onChange, syncHeight], ) useEffect(() => { setCount(getCount(value ?? '')) }, [getCount, value]) useEffect(() => { if (autoHeight && textareaRef.current !== null) { syncHeight(textareaRef.current) } }, [autoHeight, syncHeight]) const containerRef = useRef(null) useFocusWithClick(containerRef, textareaRef) const textAreaId = useId(props.id) const describedbyId = useId() const labelledbyId = useId() const classNames = useClassNames('charcoal-text-area-root', className) const showAssistiveText = assistiveText != null && assistiveText.length !== 0 return (