UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

68 lines (62 loc) 3.18 kB
import { useState, useCallback, useEffect } from 'react'; import { getCharacterCoordinates } from '../utils/character-coordinates.js'; import useIsomorphicLayoutEffect from '../../utils/useIsomorphicLayoutEffect.js'; /** * Calculates the optimal height of the textarea according to its content, automatically * resizing it as the user types. If the user manually resizes the textarea, their setting * will be respected. * * Returns an object to spread to the component's `sx` prop. If you are using `Textarea`, * apply this to the child `textarea` element: `<Textarea sx={{'& textarea': resultOfThisHook}} />`. * * NOTE: for the most accurate results, be sure that the `lineHeight` of the element is * explicitly set in CSS. * * @deprecated Will be removed in v37 (https://github.com/primer/react/issues/3604) */ const useDynamicTextareaHeight = ({ disabled, minHeightLines, maxHeightLines, elementRef, value }) => { const [height, setHeight] = useState(undefined); const [minHeight, setMinHeight] = useState(undefined); const [maxHeight, setMaxHeight] = useState(undefined); const refreshHeight = useCallback(() => { if (disabled) return; const element = elementRef.current; if (!element) return; const computedStyles = getComputedStyle(element); const pt = computedStyles.paddingTop; // The calculator gives us the distance from the top border to the bottom of the caret, including // any top padding, so we need to delete the top padding to accurately get the height // We could also parse and subtract the top padding, but this is more reliable (no chance of NaN) element.style.paddingTop = '0'; const lastCharacterCoords = getCharacterCoordinates(element, element.value.length); // Somehow we come up 1 pixel too short and the scrollbar appears, so just add one setHeight(`${lastCharacterCoords.top + lastCharacterCoords.height + 1}px`); element.style.paddingTop = pt; const lineHeight = computedStyles.lineHeight === 'normal' ? `1.2 * ${computedStyles.fontSize}` : computedStyles.lineHeight; // Using CSS calculations is fast and prevents us from having to parse anything if (minHeightLines !== undefined) setMinHeight(`calc(${minHeightLines} * ${lineHeight})`); if (maxHeightLines !== undefined) setMaxHeight(`calc(${maxHeightLines} * ${lineHeight})`); // `value` is an unnecessary dependency but it enables us to recalculate as the user types // eslint-disable-next-line react-hooks/exhaustive-deps }, [minHeightLines, maxHeightLines, value, elementRef, disabled]); useIsomorphicLayoutEffect(refreshHeight, [refreshHeight]); // With Slots, initial render of the component is delayed and so the initial layout effect can occur // before the target element has actually been calculated in the DOM. But if we only use regular effects, // there will be a visible flash on initial render when not using slots // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(refreshHeight, []); if (disabled) return {}; return { height, minHeight, maxHeight, boxSizing: 'content-box' }; }; export { useDynamicTextareaHeight };