@primer/react
Version:
An implementation of GitHub's Primer Design System using React
54 lines (49 loc) • 2.41 kB
JavaScript
import { useState, useLayoutEffect } from 'react';
import { getCharacterCoordinates } from '../utils/character-coordinates.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.
*/
const useDynamicTextareaHeight = ({
minHeightLines,
maxHeightLines,
elementRef,
value
}) => {
const [height, setHeight] = useState(undefined);
const [minHeight, setMinHeight] = useState(undefined);
const [maxHeight, setMaxHeight] = useState(undefined);
useLayoutEffect(() => {
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
}, [minHeightLines, maxHeightLines, value, elementRef]);
return {
height,
minHeight,
maxHeight,
boxSizing: 'content-box'
};
};
export { useDynamicTextareaHeight };