diff --git a/apps/mobile/src/components/home/section-header.mounted.test.tsx b/apps/mobile/src/components/home/section-header.mounted.test.tsx index 67ca432cab..448a1221ca 100644 --- a/apps/mobile/src/components/home/section-header.mounted.test.tsx +++ b/apps/mobile/src/components/home/section-header.mounted.test.tsx @@ -60,6 +60,8 @@ describe('SectionHeader mounted layout', () => { node => Object.is(node.type, 'Text') && node.children.includes('Live now') ); + // The Latin display treatment is LTR-only (home-ar-loading) and is + // asserted per direction by the letterspacing test below. expect((label.props.className as string).split(' ')).toEqual( expect.arrayContaining([ 'grow', @@ -81,8 +83,11 @@ describe('SectionHeader mounted layout', () => { expect((action.parent?.props.className as string | undefined)?.split(' ')).toContain( 'flex-wrap' ); - // The row's outer edge comes from the action box's flex direction, never - // from a physical text alignment. + // The action copy must sit at the row's end in both directions, so the box + // is a row that places its content at the main-axis end, and the layout is + // direction-relative and identical under RTL. The row's outer edge comes + // from the action box's flex direction, never from a physical text + // alignment. expect((action.props.className as string).split(' ')).toEqual( expect.arrayContaining(ACTION_BOX_CLASSES) ); @@ -95,6 +100,31 @@ describe('SectionHeader mounted layout', () => { expect(text.children).toEqual(['See all']); }); + it.each([{ isRTL: false }, { isRTL: true }])( + 'aligns the action with the row edges, never with a physical text align, with RTL=$isRTL', + ({ isRTL }) => { + // React Native swaps `textAlign: 'left'` and `'right'` under RTL (Android + // maps 'left' to Gravity.RIGHT), so a physical alignment would pin the + // action to the inner edge of its box and float "See all" away from the + // row end in Arabic. + i18nManager.isRTL = isRTL; + const root = mount( + createElement(SectionHeader, { + label: 'Live now', + actionLabel: 'See all', + onActionPress: () => undefined, + }) + ); + const classes = root + .findAll(node => typeof node.props.className === 'string') + .flatMap(node => (node.props.className as string).split(' ')); + + expect(classes.filter(name => PHYSICAL_ALIGNMENT_CLASSES.has(name))).toEqual([]); + expect(classes).not.toContain('text-left'); + expect(classes).not.toContain('text-right'); + } + ); + // Finding home-ar-loading: the Arabic section labels carried the Latin // uppercase letter-spacing, whose glyph gaps break a cursive script's joins // ('ال جلسا ت'). The display treatment is LTR-only. diff --git a/apps/mobile/src/components/home/section-header.tsx b/apps/mobile/src/components/home/section-header.tsx index 46a4ca0019..8bc909e23c 100644 --- a/apps/mobile/src/components/home/section-header.tsx +++ b/apps/mobile/src/components/home/section-header.tsx @@ -5,7 +5,10 @@ import { cn } from '@/lib/utils'; type SectionHeaderProps = { label: string; - /** Optional link at the row's outer edge (physical right in LTR, physical left in RTL). */ + /** + * Optional link at the row's outer edge (physical right in LTR, physical left + * in RTL), e.g. "SEE ALL". + */ actionLabel?: string; onActionPress?: () => void; }; @@ -22,6 +25,12 @@ export function SectionHeader({ label, actionLabel, onActionPress }: Readonly