|
| 1 | +import React, { RefObject, useCallback, useRef } from 'react'; |
| 2 | +import { |
| 3 | + TextFieldBase, |
| 4 | + AriaTextFieldProps, |
| 5 | + TextFieldRef, |
| 6 | +} from './TextFieldBase'; |
| 7 | +import { useTextField } from '@react-aria/textfield'; |
| 8 | +import { chain, useLayoutEffect } from '@react-aria/utils'; |
| 9 | +import { useControlledState } from '@react-stately/utils'; |
| 10 | +import { useProviderProps } from '../provider'; |
| 11 | +import { AddonableProps } from '../types'; |
| 12 | + |
| 13 | +export interface TextAreaProps extends AriaTextFieldProps, AddonableProps { |
| 14 | + className?: string; |
| 15 | + variant?: 'default' | 'quiet'; |
| 16 | + /** The height of the text area */ |
| 17 | + height?: number; |
| 18 | +} |
| 19 | +function TextArea(props: TextAreaProps, ref: RefObject<TextFieldRef>) { |
| 20 | + props = useProviderProps(props); |
| 21 | + const { |
| 22 | + isDisabled = false, |
| 23 | + isReadOnly = false, |
| 24 | + isRequired = false, |
| 25 | + onChange, |
| 26 | + height, |
| 27 | + ...otherProps |
| 28 | + } = props; |
| 29 | + |
| 30 | + // not in stately because this is so we know when to re-measure, which is a spectrum design |
| 31 | + const [inputValue, setInputValue] = useControlledState( |
| 32 | + props.value, |
| 33 | + props.defaultValue, |
| 34 | + () => {} |
| 35 | + ); |
| 36 | + let inputRef = useRef<HTMLTextAreaElement>(null); |
| 37 | + |
| 38 | + const onHeightChange = useCallback(() => { |
| 39 | + // Quiet textareas always grow based on their text content. |
| 40 | + // Standard textareas also grow by default, unless an explicit height is set. |
| 41 | + const input = inputRef.current; |
| 42 | + |
| 43 | + if (input && !height) { |
| 44 | + const prevAlignment = input.style.alignSelf; |
| 45 | + const prevOverflow = input.style.overflow; |
| 46 | + // Firefox scroll position is lost when overflow: 'hidden' is applied so we skip applying it. |
| 47 | + // The measure/applied height is also incorrect/reset if we turn on and off |
| 48 | + // overflow: hidden in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1787062 |
| 49 | + const isFirefox = 'MozAppearance' in input.style; |
| 50 | + if (!isFirefox) { |
| 51 | + input.style.overflow = 'hidden'; |
| 52 | + } |
| 53 | + input.style.alignSelf = 'start'; |
| 54 | + input.style.height = 'auto'; |
| 55 | + // offsetHeight - clientHeight accounts for the border/padding. |
| 56 | + input.style.height = `${input.scrollHeight + |
| 57 | + (input.offsetHeight - input.clientHeight)}px`; |
| 58 | + input.style.overflow = prevOverflow; |
| 59 | + input.style.alignSelf = prevAlignment; |
| 60 | + } |
| 61 | + }, [inputRef, height]); |
| 62 | + |
| 63 | + useLayoutEffect(() => { |
| 64 | + if (inputRef.current) { |
| 65 | + onHeightChange(); |
| 66 | + } |
| 67 | + }, [onHeightChange, inputValue, inputRef]); |
| 68 | + |
| 69 | + let { |
| 70 | + labelProps, |
| 71 | + inputProps, |
| 72 | + descriptionProps, |
| 73 | + errorMessageProps, |
| 74 | + } = useTextField( |
| 75 | + { |
| 76 | + ...props, |
| 77 | + onChange: chain(onChange, setInputValue), |
| 78 | + inputElementType: 'textarea', |
| 79 | + }, |
| 80 | + inputRef |
| 81 | + ); |
| 82 | + |
| 83 | + return ( |
| 84 | + <TextFieldBase |
| 85 | + {...otherProps} |
| 86 | + ref={ref} |
| 87 | + labelProps={labelProps} |
| 88 | + inputProps={inputProps} |
| 89 | + descriptionProps={descriptionProps} |
| 90 | + errorMessageProps={errorMessageProps} |
| 91 | + multiLine |
| 92 | + isDisabled={isDisabled} |
| 93 | + isReadOnly={isReadOnly} |
| 94 | + isRequired={isRequired} |
| 95 | + height={height} |
| 96 | + inputRef={inputRef} |
| 97 | + /> |
| 98 | + ); |
| 99 | +} |
| 100 | + |
| 101 | +/** |
| 102 | + * TextAreas are multiline text inputs, useful for cases where users have |
| 103 | + * a sizable amount of text to enter. They allow for all customizations that |
| 104 | + * are available to text fields. |
| 105 | + */ |
| 106 | +// @ts-ignore |
| 107 | +let _TextArea = React.forwardRef(TextArea); |
| 108 | +export { _TextArea as TextArea }; |
0 commit comments