Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions apps/mobile/src/components/home/section-header.mounted.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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)
);
Expand All @@ -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.
Expand Down
11 changes: 10 additions & 1 deletion apps/mobile/src/components/home/section-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand All @@ -22,6 +25,12 @@ export function SectionHeader({ label, actionLabel, onActionPress }: Readonly<Se
hitSlop={8}
accessibilityRole="button"
accessibilityLabel={actionLabel}
// The copy sits at the end of the row, so it is placed with the box's
// own direction (`justify-end` on a `flex-row`) and never with a
// physical `text-left`/`text-right`: React Native swaps those two
// under RTL (Android maps `textAlign: 'left'` to `Gravity.RIGHT` when
// the layout is RTL), which floated the action onto the inner edge of
// its box instead of the row's end in Arabic.
className="max-w-full grow flex-row justify-end active:opacity-70"
>
<Text
Expand Down
Loading