Skip to content

Commit 196a792

Browse files
authored
Fix useDynamicTextareaHeight not taking into account padding (#2848)
* Fix `useDynamicTextareaHeight` not taking into account padding * Create .changeset/ten-peaches-breathe.md
1 parent 9ee3805 commit 196a792

File tree

3 files changed

+22
-11
lines changed

3 files changed

+22
-11
lines changed

.changeset/ten-peaches-breathe.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@primer/react": patch
3+
---
4+
5+
Fix `useDynamicTextareaHeight` not taking into account top padding of `textarea`
6+
7+
Also makes the hook accept a `RefObject` instead of an element instance

src/drafts/MarkdownEditor/_MarkdownInput.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export const MarkdownInput = forwardRef<HTMLTextAreaElement, MarkdownInputProps>
9292
return subscription?.unsubscribe
9393
}, [pasteUrlsAsPlainText])
9494

95-
const dynamicHeightStyles = useDynamicTextareaHeight({maxHeightLines, minHeightLines, element: ref.current, value})
95+
const dynamicHeightStyles = useDynamicTextareaHeight({maxHeightLines, minHeightLines, elementRef: ref, value})
9696
const heightStyles = fullHeight ? {} : dynamicHeightStyles
9797

9898
return (
Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import {useLayoutEffect, useState} from 'react'
1+
import {RefObject, useLayoutEffect, useState} from 'react'
22

33
import {SxProp} from '../../sx'
44
import {getCharacterCoordinates} from '../utils/character-coordinates'
55

66
type UseDynamicTextareaHeightSettings = {
7-
minHeightLines: number
8-
maxHeightLines: number
9-
element: HTMLTextAreaElement | null
7+
minHeightLines?: number
8+
maxHeightLines?: number
9+
elementRef: RefObject<HTMLTextAreaElement | null>
1010
/** The current value of the input. */
1111
value: string
1212
}
@@ -16,43 +16,47 @@ type UseDynamicTextareaHeightSettings = {
1616
* resizing it as the user types. If the user manually resizes the textarea, their setting
1717
* will be respected.
1818
*
19-
* Returns an object to spread to the component's `sx` prop.
19+
* Returns an object to spread to the component's `sx` prop. If you are using `Textarea`,
20+
* apply this to the child `textarea` element: `<Textarea sx={{'& textarea': resultOfThisHook}} />`.
2021
*
2122
* NOTE: for the most accurate results, be sure that the `lineHeight` of the element is
2223
* explicitly set in CSS.
2324
*/
2425
export const useDynamicTextareaHeight = ({
2526
minHeightLines,
2627
maxHeightLines,
27-
element,
28+
elementRef,
2829
value,
2930
}: UseDynamicTextareaHeightSettings): SxProp['sx'] => {
3031
const [height, setHeight] = useState<string | undefined>(undefined)
3132
const [minHeight, setMinHeight] = useState<string | undefined>(undefined)
3233
const [maxHeight, setMaxHeight] = useState<string | undefined>(undefined)
3334

3435
useLayoutEffect(() => {
36+
const element = elementRef.current
3537
if (!element) return
3638

3739
const computedStyles = getComputedStyle(element)
3840
const pt = computedStyles.paddingTop
39-
const lastCharacterCoords = getCharacterCoordinates(element, element.value.length)
4041

4142
// The calculator gives us the distance from the top border to the bottom of the caret, including
4243
// any top padding, so we need to delete the top padding to accurately get the height
4344
// We could also parse and subtract the top padding, but this is more reliable (no chance of NaN)
4445
element.style.paddingTop = '0'
46+
47+
const lastCharacterCoords = getCharacterCoordinates(element, element.value.length)
48+
4549
// Somehow we come up 1 pixel too short and the scrollbar appears, so just add one
4650
setHeight(`${lastCharacterCoords.top + lastCharacterCoords.height + 1}px`)
4751
element.style.paddingTop = pt
4852

4953
const lineHeight =
5054
computedStyles.lineHeight === 'normal' ? `1.2 * ${computedStyles.fontSize}` : computedStyles.lineHeight
5155
// Using CSS calculations is fast and prevents us from having to parse anything
52-
setMinHeight(`calc(${minHeightLines} * ${lineHeight})`)
53-
setMaxHeight(`calc(${maxHeightLines} * ${lineHeight})`)
56+
if (minHeightLines !== undefined) setMinHeight(`calc(${minHeightLines} * ${lineHeight})`)
57+
if (maxHeightLines !== undefined) setMaxHeight(`calc(${maxHeightLines} * ${lineHeight})`)
5458
// `value` is an unnecessary dependency but it enables us to recalculate as the user types
55-
}, [minHeightLines, maxHeightLines, element, value])
59+
}, [minHeightLines, maxHeightLines, value, elementRef])
5660

5761
return {height, minHeight, maxHeight, boxSizing: 'content-box'}
5862
}

0 commit comments

Comments
 (0)