Fix RTL post alignment on native

This commit is contained in:
Eric Bailey
2026-08-28 13:53:57 -05:00
parent e39d308fb7
commit 5bde4cddb2
11 changed files with 56 additions and 3 deletions
+5
View File
@@ -326,6 +326,11 @@ export function QuoteEmbed({
{richText ? (
<RichText
value={richText}
language={
bsky.isType(app.bsky.feed.post, quote.record)
? quote.record.langs?.[0]
: undefined
}
style={a.text_md}
numberOfLines={20}
disableLinks
+8 -2
View File
@@ -2,8 +2,9 @@ import {useMemo} from 'react'
import {type StyleProp, type TextStyle} from 'react-native'
import {RichText as RichTextAPI} from '@bsky/sdk/richtext'
import {isRTL} from '#/lib/strings/bidi'
import {toShortUrl} from '#/lib/strings/url-helpers'
import {android, atoms as a, flatten, type TextStyleProp} from '#/alf'
import {android, atoms as a, flatten, native, type TextStyleProp} from '#/alf'
import {isOnlyEmoji} from '#/alf/typography'
import {InlineLinkText, type LinkProps} from '#/components/Link'
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
@@ -20,6 +21,7 @@ const URL_REGEX =
export type RichTextProps = TextStyleProp &
Pick<TextProps, 'selectable' | 'onLayout' | 'onTextLayout'> & {
value: RichTextAPI | string
language?: string
testID?: string
numberOfLines?: number
disableLinks?: boolean
@@ -56,6 +58,7 @@ export type RichTextProps = TextStyleProp &
export function RichText({
testID,
value,
language,
style,
numberOfLines,
disableLinks,
@@ -82,7 +85,10 @@ export function RichText({
}
}, [value])
const plainStyles = style
const plainStyles = [
style,
isRTL(language) ? native({textAlign: 'right'}) : null,
]
const suffixStyles =
suffix && suffixOffset
? android({paddingBottom: suffixOffset, marginBottom: -suffixOffset})
+19 -1
View File
@@ -11,7 +11,7 @@ jest.mock('#/env', () => ({
},
}))
import {forceLTR} from '../bidi'
import {forceLTR, isRTL} from '../bidi'
const LEFT_TO_RIGHT_EMBEDDING = '\u202A'
const POP_DIRECTIONAL_FORMATTING = '\u202C'
@@ -31,3 +31,21 @@ describe('forceLTR', () => {
expect(forceLTR('@alice.bsky.social')).toBe('@alice.bsky.social')
})
})
describe('isRTL', () => {
it('recognizes right-to-left languages and scripts', () => {
expect(isRTL('he')).toBe(true)
expect(isRTL('ar')).toBe(true)
expect(isRTL('az-Arab')).toBe(true)
})
it('recognizes left-to-right languages and scripts', () => {
expect(isRTL('en')).toBe(false)
expect(isRTL('az-Latn')).toBe(false)
})
it('handles missing and invalid language tags', () => {
expect(isRTL(undefined)).toBe(false)
expect(isRTL('not_a_language')).toBe(false)
})
})
+14
View File
@@ -17,3 +17,17 @@ export function forceLTR(str: string) {
if (IS_WEB) return str
return LEFT_TO_RIGHT_EMBEDDING + str + POP_DIRECTIONAL_FORMATTING
}
/**
* Determines whether a BCP 47 language tag uses a right-to-left script.
*/
export function isRTL(language: string | undefined) {
if (!language) return false
try {
return new Intl.Locale(language).getTextInfo().direction === 'rtl'
} catch (error) {
if (error instanceof RangeError) return false
throw error
}
}
@@ -239,6 +239,7 @@ function MessageInputPostEmbed({
enableTags
testID="postText"
value={rt}
language={record.langs?.[0]}
style={[a.text_sm, t.atoms.text_contrast_high]}
authorHandle={post.author.handle}
numberOfLines={3}
@@ -405,6 +405,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
enableTags
selectable
value={richText}
language={record.langs?.[0]}
style={[a.flex_1, a.text_lg]}
authorHandle={post.author.handle}
shouldProxyLinks={true}
@@ -327,6 +327,7 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({
<RichText
enableTags
value={richText}
language={record.langs?.[0]}
style={[a.flex_1, a.text_md]}
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
authorHandle={post.author.handle}
@@ -355,6 +355,7 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({
<RichText
enableTags
value={richText}
language={record.langs?.[0]}
style={[a.flex_1, a.text_md]}
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
authorHandle={post.author.handle}
+4
View File
@@ -963,6 +963,7 @@ function Overlay({
{record?.text?.trim() && (
<ExpandableRichTextView
value={richText}
language={record.langs?.[0]}
authorHandle={post.author.handle}
/>
)}
@@ -1020,9 +1021,11 @@ function Overlay({
function ExpandableRichTextView({
value,
language,
authorHandle,
}: {
value: RichTextAPI
language?: string
authorHandle?: string
}) {
const {height: screenHeight} = useSafeAreaFrame()
@@ -1057,6 +1060,7 @@ function ExpandableRichTextView({
]}>
<RichText
value={value}
language={language}
style={[a.text_sm, a.flex_1, a.leading_relaxed]}
authorHandle={authorHandle}
enableTags
+1
View File
@@ -226,6 +226,7 @@ function PostInner({
enableTags
testID="postText"
value={richText}
language={record?.langs?.[0]}
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
style={[a.flex_1, a.text_md]}
authorHandle={post.author.handle}
+1
View File
@@ -520,6 +520,7 @@ let PostContent = ({
enableTags
testID="postText"
value={richText}
language={record?.langs?.[0]}
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
style={[a.flex_1, a.text_md]}
authorHandle={postAuthor.handle}