diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index 44f234cfda..2bc72fb71a 100644 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -48,7 +48,7 @@ import { import {AutosizedTextarea} from '#/components/forms/AutosizedTextarea' import {useOnKeyboard} from '#/components/hooks/useOnKeyboard' import {Span, Text} from '#/components/Typography' -import {IS_WEB, IS_WEB_TOUCH_DEVICE} from '#/env' +import {IS_IOS, IS_WEB, IS_WEB_TOUCH_DEVICE} from '#/env' export type SubmitRequest = | { @@ -219,11 +219,27 @@ export function Composer({ transform: [{translateY: -inputScrollSharedValue.value}], })) const textStyle = useMemo(() => { - return normalizeTextStyles([a.leading_snug, rawTextStyle, t.atoms.text], { - fontScale: fonts.scaleMultiplier, - fontFamily: fonts.family, - flags: {}, - }) + const ts = normalizeTextStyles( + [a.leading_snug, rawTextStyle, t.atoms.text], + { + fontScale: fonts.scaleMultiplier, + fontFamily: fonts.family, + flags: {}, + }, + ) + /** + * On iOS, having a lineHeight on the Text component causes the text to be + * vertically misaligned with the TextInput. + * + * This only seems to be an issue on iOS, and not on Android or web. It's + * possible that this is a bug in React Native's Text component on iOS, + * but in the meantime, we'll just remove the lineHeight on iOS to ensure + * the text is properly aligned. + */ + if (IS_IOS) { + delete ts.lineHeight + } + return ts }, [rawTextStyle, fonts]) /* diff --git a/src/components/forms/AutosizedTextarea.tsx b/src/components/forms/AutosizedTextarea.tsx index 96e6ccc1e3..94e5fc44cb 100644 --- a/src/components/forms/AutosizedTextarea.tsx +++ b/src/components/forms/AutosizedTextarea.tsx @@ -1,4 +1,4 @@ -import {useCallback, useEffect, useMemo, useRef, useState} from 'react' +import {useEffect, useMemo, useRef, useState} from 'react' import { TextInput, type TextInputContentSizeChangeEvent, @@ -19,7 +19,6 @@ export function AutosizedTextarea({ onChangeText: onChangeTextOuter, onContentSizeChange: onContentSizeChangeOuter, - style, ...rest }: Omit & { @@ -33,11 +32,14 @@ export function AutosizedTextarea({ const {theme: t, fonts} = useAlf() const {processedStyle, minHeight, maxHeight} = useMemo(() => { const fs = flatten(style) - const ts = normalizeTextStyles([a.leading_snug, fs, t.atoms.text], { - fontScale: fonts.scaleMultiplier, - fontFamily: fonts.family, - flags: {}, - }) + const ts = normalizeTextStyles( + [a.text_md, a.leading_snug, t.atoms.text, fs], + { + fontScale: fonts.scaleMultiplier, + fontFamily: fonts.family, + flags: {}, + }, + ) const lineHeight = ts.lineHeight || 20 const padding = extractPadding(fs ?? {}) const verticalSpace = padding.paddingTop + padding.paddingBottom @@ -50,15 +52,11 @@ export function AutosizedTextarea({ * On iOS, minHeight/maxHeight works fine natively. */ const tas = IS_WEB - ? {height: lineHeight + verticalSpace} + ? {height: mh} : IS_ANDROID ? {height: mh} : {minHeight: mh, maxHeight: xh} - if (IS_IOS) { - delete ts.lineHeight - } - return { processedStyle: { ...ts, @@ -78,52 +76,46 @@ export function AutosizedTextarea({ const [androidInputHeight, setAndroidInputHeight] = useState(minHeight) const prevHeight = useRef(0) - const onChangeText = useCallback( - (text: string) => { - if (IS_WEB) { - const el = textInputRef.current as unknown as HTMLTextAreaElement - if (el) { - el.style.height = '0px' - const scrollHeight = el.scrollHeight - const nextHeight = Math.min( - Math.max(scrollHeight, minHeight), - maxHeight, - ) - el.style.height = `${nextHeight}px` - el.style.overflowY = scrollHeight > maxHeight ? 'auto' : 'hidden' - if (nextHeight !== prevHeight.current) { - prevHeight.current = nextHeight - onUpdateHeight?.(nextHeight) - } - } - } else if (IS_IOS) { - textInputRef.current?.measure((_x, _y, _w, h) => { - if (h !== prevHeight.current) { - prevHeight.current = h - onUpdateHeight?.(h) - } - }) - } - - onChangeTextOuter?.(text) - }, - [onChangeTextOuter, minHeight, maxHeight, onUpdateHeight], - ) - - const onContentSizeChange = useCallback( - (e: TextInputContentSizeChangeEvent) => { - if (IS_ANDROID) { - const h = Math.ceil(e.nativeEvent.contentSize.height) - const nextHeight = Math.min(Math.max(h, minHeight), maxHeight) - if (nextHeight !== androidInputHeight) { - setAndroidInputHeight(nextHeight) + const onChangeText = (text: string) => { + if (IS_WEB) { + const el = textInputRef.current as unknown as HTMLTextAreaElement + if (el) { + el.style.height = '0px' + const scrollHeight = el.scrollHeight + const nextHeight = Math.min( + Math.max(scrollHeight, minHeight), + maxHeight, + ) + el.style.height = `${nextHeight}px` + el.style.overflowY = scrollHeight > maxHeight ? 'auto' : 'hidden' + if (nextHeight !== prevHeight.current) { + prevHeight.current = nextHeight + onUpdateHeight?.(nextHeight) } } + } else if (IS_IOS) { + textInputRef.current?.measure((_x, _y, _w, h) => { + if (h !== prevHeight.current) { + prevHeight.current = h + onUpdateHeight?.(h) + } + }) + } - onContentSizeChangeOuter?.(e) - }, - [onContentSizeChangeOuter, minHeight, maxHeight, androidInputHeight], - ) + onChangeTextOuter?.(text) + } + + const onContentSizeChange = (e: TextInputContentSizeChangeEvent) => { + if (IS_ANDROID) { + const h = Math.ceil(e.nativeEvent.contentSize.height) + const nextHeight = Math.min(Math.max(h, minHeight), maxHeight) + if (nextHeight !== androidInputHeight) { + setAndroidInputHeight(nextHeight) + } + } + + onContentSizeChangeOuter?.(e) + } /* * On Android, height is driven by onContentSizeChange (see the TextInput