From e17be69d1ee173b94ec6b6af9c72b9196504b523 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Thu, 29 Jan 2026 14:36:04 +0430 Subject: [PATCH 1/7] fix(a11y): remove duplicate focusable elements --- .../implementation/index.native.tsx | 2 +- .../BaseTextInput/implementation/index.tsx | 23 ++++--- .../TextInput/TextInputLabel/index.native.tsx | 3 + .../TextInput/TextInputLabel/index.tsx | 4 ++ .../TextInput/TextInputMeasurement/index.tsx | 12 ++++ src/pages/signin/SignInPageLayout/Footer.tsx | 64 ++++++++++++++----- 6 files changed, 84 insertions(+), 24 deletions(-) diff --git a/src/components/TextInput/BaseTextInput/implementation/index.native.tsx b/src/components/TextInput/BaseTextInput/implementation/index.native.tsx index e8ea3cb0b2ff5..25009e250448b 100644 --- a/src/components/TextInput/BaseTextInput/implementation/index.native.tsx +++ b/src/components/TextInput/BaseTextInput/implementation/index.native.tsx @@ -289,10 +289,10 @@ function BaseTextInput({ role={CONST.ROLE.PRESENTATION} onPress={onPress} tabIndex={-1} + accessible={false} // When autoGrowHeight is true we calculate the width for the text input, so it will break lines properly // or if multiline is not supplied we calculate the text input height, using onLayout. onLayout={onLayout} - accessibilityLabel={accessibilityLabel} style={[ autoGrowHeight && !isAutoGrowHeightMarkdown && diff --git a/src/components/TextInput/BaseTextInput/implementation/index.tsx b/src/components/TextInput/BaseTextInput/implementation/index.tsx index 6fc2988bee930..338b1eb3a339f 100644 --- a/src/components/TextInput/BaseTextInput/implementation/index.tsx +++ b/src/components/TextInput/BaseTextInput/implementation/index.tsx @@ -297,11 +297,7 @@ function BaseTextInput({ // eslint-disable-next-line react/jsx-props-no-spreading {...(shouldInterceptSwipe && SwipeInterceptPanResponder.panHandlers)} > - + {!!suffixCharacter && ( @@ -518,7 +525,7 @@ function BaseTextInput({ )} - + {!!inputHelpText && ( {label} diff --git a/src/components/TextInput/TextInputMeasurement/index.tsx b/src/components/TextInput/TextInputMeasurement/index.tsx index 5de306879430c..30eec45eda62a 100644 --- a/src/components/TextInput/TextInputMeasurement/index.tsx +++ b/src/components/TextInput/TextInputMeasurement/index.tsx @@ -35,6 +35,10 @@ function TextInputMeasurement({ onSetTextInputWidth(e.nativeEvent.layout.width); onSetTextInputHeight(e.nativeEvent.layout.height); }} + accessible={false} + accessibilityElementsHidden + importantForAccessibility="no" + aria-hidden > {/* \u200B added to solve the issue of not expanding the text input enough when the value ends with '\n' (https://github.com/Expensify/App/issues/21271) */} {value ? `${value}${value.endsWith('\n') ? '\u200B' : ''}` : placeholder} @@ -66,6 +74,10 @@ function TextInputMeasurement({ styles.hiddenElementOutsideOfWindow, styles.visibilityHidden, ]} + accessible={false} + accessibilityElementsHidden + importantForAccessibility="no" + aria-hidden onLayout={(e) => { if (e.nativeEvent.layout.width === 0 && e.nativeEvent.layout.height === 0) { return; diff --git a/src/pages/signin/SignInPageLayout/Footer.tsx b/src/pages/signin/SignInPageLayout/Footer.tsx index 87bf16af1ebb8..3b4e42baee3f7 100644 --- a/src/pages/signin/SignInPageLayout/Footer.tsx +++ b/src/pages/signin/SignInPageLayout/Footer.tsx @@ -1,12 +1,14 @@ import React from 'react'; -import type {StyleProp, TextStyle} from 'react-native'; -import {View} from 'react-native'; +import type {GestureResponderEvent, StyleProp, TextStyle} from 'react-native'; +import {Platform, View} from 'react-native'; import SignInGradient from '@assets/images/home-fade-gradient--mobile.svg'; import Hoverable from '@components/Hoverable'; import ImageSVG from '@components/ImageSVG'; +import {PressableWithoutFeedback} from '@components/Pressable'; import Text from '@components/Text'; import type {LinkProps, PressProps} from '@components/TextLink'; import TextLink from '@components/TextLink'; +import useEnvironment from '@hooks/useEnvironment'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -16,6 +18,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Licenses from '@pages/signin/Licenses'; import Socials from '@pages/signin/Socials'; import variables from '@styles/variables'; +import {openLink as openLinkUtil} from '@userActions/Link'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import type {SignInPageLayoutProps} from './types'; @@ -147,6 +150,7 @@ function Footer({navigateFocus}: FooterProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); + const {environmentURL} = useEnvironment(); const {shouldUseNarrowLayout, isMediumScreenWidth} = useResponsiveLayout(); const icons = useMemoizedLazyExpensifyIcons(['ExpensifyFooterLogo', 'ExpensifyFooterLogoVertical']); const isVertical = shouldUseNarrowLayout; @@ -158,6 +162,7 @@ function Footer({navigateFocus}: FooterProps) { const footerColumn = isVertical ? [styles.p4] : [styles.p4, isMediumScreenWidth ? styles.w50 : styles.w25]; const footerWrapper = isVertical ? [StyleUtils.getBackgroundColorStyle(theme.signInPage), styles.overflowHidden] : []; const getTextLinkStyle: (hovered: boolean) => StyleProp = (hovered) => [styles.footerRow, hovered ? styles.textBlue : {}]; + const shouldUseAccessiblePressable = Platform.OS !== CONST.PLATFORM.WEB; return ( @@ -180,25 +185,54 @@ function Footer({navigateFocus}: FooterProps) { {column.rows.map(({href, onPress, translationPath}) => ( - {(hovered) => ( - - {onPress ? ( - { + if (shouldUseAccessiblePressable) { + return ( + { + if (onPress) { + onPress({} as GestureResponderEvent); + return; + } + if (href) { + openLinkUtil(href, environmentURL); + } + }} > - {translate(translationPath)} - - ) : ( + + {translate(translationPath)} + + + ); + } + + if (onPress) { + return ( {translate(translationPath)} - )} - - )} + ); + } + + return ( + + {translate(translationPath)} + + ); + }} ))} {i === 2 && ( From 448ff7a05b81320592f09d3d1ecfb1546ee5faea Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Sun, 1 Feb 2026 09:52:54 +0430 Subject: [PATCH 2/7] adjusted tests --- tests/ui/AddressPageTest.tsx | 7 ++++--- tests/ui/components/TextInputLabel.tsx | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/ui/AddressPageTest.tsx b/tests/ui/AddressPageTest.tsx index b10f1ebd181fa..f1b3263d71551 100644 --- a/tests/ui/AddressPageTest.tsx +++ b/tests/ui/AddressPageTest.tsx @@ -74,12 +74,13 @@ describe('AddressPageTest', () => { renderPage(SCREENS.SETTINGS.PROFILE.ADDRESS); await waitForBatchedUpdatesWithAct(); - const state = screen.getAllByLabelText('State / Province'); - expect(state.at(1)?.props.value).toEqual('Test'); + const stateInput = screen.getByLabelText('State / Province'); + expect(stateInput.props.value).toEqual('Test'); Navigation.setParams({ country: 'VN', }); await waitForBatchedUpdatesWithAct(); - expect(state?.at(1)?.props.value).toEqual('Test'); + const stateInputAfterParams = screen.getByLabelText('State / Province'); + expect(stateInputAfterParams.props.value).toEqual('Test'); }); }); diff --git a/tests/ui/components/TextInputLabel.tsx b/tests/ui/components/TextInputLabel.tsx index 93d542e2b824b..05fae399cd433 100644 --- a/tests/ui/components/TextInputLabel.tsx +++ b/tests/ui/components/TextInputLabel.tsx @@ -26,7 +26,7 @@ describe('TextInputLabel', () => { labelScale, }); // Find the Animated.Text component by its text content - const labelElement = screen.getByText(longLabel); + const labelElement = screen.getByText(longLabel, {includeHiddenElements: true}); // Verify the component renders the correct text expect(labelElement).toBeTruthy(); // Verify the props for shortening behavior @@ -44,7 +44,7 @@ describe('TextInputLabel', () => { labelScale, }); // Find the Animated.Text component by its text content - const labelElement = screen.getByText(label); + const labelElement = screen.getByText(label, {includeHiddenElements: true}); // Verify the component renders the correct text expect(labelElement).toBeTruthy(); // Verify that numberOfLines and ellipsizeMode are undefined From d444813fb1c8065ce354903d6669dfde5c1eb2b6 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Mon, 2 Feb 2026 14:40:05 +0430 Subject: [PATCH 3/7] fixed lint --- src/pages/signin/SignInPageLayout/Footer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/signin/SignInPageLayout/Footer.tsx b/src/pages/signin/SignInPageLayout/Footer.tsx index 3b4e42baee3f7..5e81e239f7c58 100644 --- a/src/pages/signin/SignInPageLayout/Footer.tsx +++ b/src/pages/signin/SignInPageLayout/Footer.tsx @@ -192,6 +192,7 @@ function Footer({navigateFocus}: FooterProps) { accessible accessibilityRole={CONST.ROLE.LINK} accessibilityLabel={translate(translationPath)} + sentryLabel={translationPath} onPress={() => { if (onPress) { onPress({} as GestureResponderEvent); From 8a88be1b47446dd3212d5f3d17efd576002acaa9 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Tue, 3 Feb 2026 12:44:21 +0430 Subject: [PATCH 4/7] fixed link error --- .../TextInput/BaseTextInput/implementation/index.native.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/TextInput/BaseTextInput/implementation/index.native.tsx b/src/components/TextInput/BaseTextInput/implementation/index.native.tsx index 25009e250448b..42db16aa19008 100644 --- a/src/components/TextInput/BaseTextInput/implementation/index.native.tsx +++ b/src/components/TextInput/BaseTextInput/implementation/index.native.tsx @@ -79,6 +79,7 @@ function BaseTextInput({ iconContainerStyle, shouldUseDefaultLineHeightForPrefix = true, ref, + sentryLabel, ...props }: BaseTextInputProps) { const InputComponent = InputComponentMap.get(type) ?? RNTextInput; @@ -290,6 +291,7 @@ function BaseTextInput({ onPress={onPress} tabIndex={-1} accessible={false} + sentryLabel={sentryLabel} // When autoGrowHeight is true we calculate the width for the text input, so it will break lines properly // or if multiline is not supplied we calculate the text input height, using onLayout. onLayout={onLayout} From c7893dca259bc292d3b245e7808f650916e10f09 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Wed, 4 Feb 2026 15:22:31 +0430 Subject: [PATCH 5/7] applied ai feedback --- src/pages/signin/SignInPageLayout/Footer.tsx | 86 +++++-------------- .../SignInPageLayout/FooterRow.native.tsx | 47 ++++++++++ .../signin/SignInPageLayout/FooterRow.tsx | 33 +++++++ src/pages/signin/SignInPageLayout/types.ts | 8 +- 4 files changed, 109 insertions(+), 65 deletions(-) create mode 100644 src/pages/signin/SignInPageLayout/FooterRow.native.tsx create mode 100644 src/pages/signin/SignInPageLayout/FooterRow.tsx diff --git a/src/pages/signin/SignInPageLayout/Footer.tsx b/src/pages/signin/SignInPageLayout/Footer.tsx index 5e81e239f7c58..4213f3a8f21b5 100644 --- a/src/pages/signin/SignInPageLayout/Footer.tsx +++ b/src/pages/signin/SignInPageLayout/Footer.tsx @@ -1,14 +1,10 @@ import React from 'react'; -import type {GestureResponderEvent, StyleProp, TextStyle} from 'react-native'; -import {Platform, View} from 'react-native'; +import type {StyleProp, TextStyle} from 'react-native'; +import {View} from 'react-native'; import SignInGradient from '@assets/images/home-fade-gradient--mobile.svg'; import Hoverable from '@components/Hoverable'; import ImageSVG from '@components/ImageSVG'; -import {PressableWithoutFeedback} from '@components/Pressable'; import Text from '@components/Text'; -import type {LinkProps, PressProps} from '@components/TextLink'; -import TextLink from '@components/TextLink'; -import useEnvironment from '@hooks/useEnvironment'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -18,17 +14,13 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Licenses from '@pages/signin/Licenses'; import Socials from '@pages/signin/Socials'; import variables from '@styles/variables'; -import {openLink as openLinkUtil} from '@userActions/Link'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; -import type {SignInPageLayoutProps} from './types'; +import FooterRow from './FooterRow'; +import type {FooterColumnRow, SignInPageLayoutProps} from './types'; type FooterProps = Pick; -type FooterColumnRow = (LinkProps | PressProps) & { - translationPath: TranslationPaths; -}; - type FooterColumnData = { translationPath: TranslationPaths; rows: FooterColumnRow[]; @@ -150,7 +142,6 @@ function Footer({navigateFocus}: FooterProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); - const {environmentURL} = useEnvironment(); const {shouldUseNarrowLayout, isMediumScreenWidth} = useResponsiveLayout(); const icons = useMemoizedLazyExpensifyIcons(['ExpensifyFooterLogo', 'ExpensifyFooterLogoVertical']); const isVertical = shouldUseNarrowLayout; @@ -162,7 +153,6 @@ function Footer({navigateFocus}: FooterProps) { const footerColumn = isVertical ? [styles.p4] : [styles.p4, isMediumScreenWidth ? styles.w50 : styles.w25]; const footerWrapper = isVertical ? [StyleUtils.getBackgroundColorStyle(theme.signInPage), styles.overflowHidden] : []; const getTextLinkStyle: (hovered: boolean) => StyleProp = (hovered) => [styles.footerRow, hovered ? styles.textBlue : {}]; - const shouldUseAccessiblePressable = Platform.OS !== CONST.PLATFORM.WEB; return ( @@ -183,57 +173,25 @@ function Footer({navigateFocus}: FooterProps) { > {translate(column.translationPath)} - {column.rows.map(({href, onPress, translationPath}) => ( - - {(hovered) => { - if (shouldUseAccessiblePressable) { - return ( - { - if (onPress) { - onPress({} as GestureResponderEvent); - return; - } - if (href) { - openLinkUtil(href, environmentURL); - } - }} - > - - {translate(translationPath)} - - - ); - } - - if (onPress) { - return ( - - {translate(translationPath)} - - ); - } - - return ( - ( + + {(hovered) => + row.onPress ? ( + + ) : ( + - {translate(translationPath)} - - ); - }} + /> + ) + } ))} {i === 2 && ( diff --git a/src/pages/signin/SignInPageLayout/FooterRow.native.tsx b/src/pages/signin/SignInPageLayout/FooterRow.native.tsx new file mode 100644 index 0000000000000..b39fa730dce8f --- /dev/null +++ b/src/pages/signin/SignInPageLayout/FooterRow.native.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import type {GestureResponderEvent, StyleProp, TextStyle} from 'react-native'; +import Text from '@components/Text'; +import {PressableWithoutFeedback} from '@components/Pressable'; +import useEnvironment from '@hooks/useEnvironment'; +import useThemeStyles from '@hooks/useThemeStyles'; +import CONST from '@src/CONST'; +import {openLink as openLinkUtil} from '@userActions/Link'; +import type {FooterColumnRow} from './types'; + +type FooterRowProps = FooterColumnRow & { + text: string; + style: StyleProp; +}; + +function FooterRow({href, onPress, translationPath, text, style}: FooterRowProps) { + const styles = useThemeStyles(); + const {environmentURL} = useEnvironment(); + + return ( + { + if (onPress) { + onPress({} as GestureResponderEvent); + return; + } + if (href) { + openLinkUtil(href, environmentURL); + } + }} + > + + {text} + + + ); +} + +export default FooterRow; diff --git a/src/pages/signin/SignInPageLayout/FooterRow.tsx b/src/pages/signin/SignInPageLayout/FooterRow.tsx new file mode 100644 index 0000000000000..d4a1911dc36b6 --- /dev/null +++ b/src/pages/signin/SignInPageLayout/FooterRow.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import type {StyleProp, TextStyle} from 'react-native'; +import TextLink from '@components/TextLink'; +import type {FooterColumnRow} from './types'; + +type FooterRowProps = FooterColumnRow & { + text: string; + style: StyleProp; +}; + +function FooterRow({href, onPress, text, style}: FooterRowProps) { + if (onPress) { + return ( + + {text} + + ); + } + + return ( + + {text} + + ); +} + +export default FooterRow; diff --git a/src/pages/signin/SignInPageLayout/types.ts b/src/pages/signin/SignInPageLayout/types.ts index 9c31cb9b486ad..2546335f483d6 100644 --- a/src/pages/signin/SignInPageLayout/types.ts +++ b/src/pages/signin/SignInPageLayout/types.ts @@ -1,5 +1,7 @@ import type React from 'react'; import type {ForwardedRef} from 'react'; +import type {LinkProps, PressProps} from '@components/TextLink'; +import type {TranslationPaths} from '@src/languages/types'; type SignInPageLayoutProps = { /** The children to show inside the layout */ @@ -35,4 +37,8 @@ type SignInPageLayoutRef = { scrollPageToTop: (animated?: boolean) => void; }; -export type {SignInPageLayoutRef, SignInPageLayoutProps}; +type FooterColumnRow = (LinkProps | PressProps) & { + translationPath: TranslationPaths; +}; + +export type {SignInPageLayoutRef, SignInPageLayoutProps, FooterColumnRow}; From 7278b3222de3938edb5ed9422b8521ae32ce2894 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Thu, 5 Feb 2026 12:10:44 +0430 Subject: [PATCH 6/7] fixed type, lint and prettier failure. --- .../TextInput/BaseTextInput/implementation/index.native.tsx | 1 - .../TextInput/BaseTextInput/implementation/index.tsx | 2 +- src/pages/signin/SignInPageLayout/FooterRow.native.tsx | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/TextInput/BaseTextInput/implementation/index.native.tsx b/src/components/TextInput/BaseTextInput/implementation/index.native.tsx index 4bed9759efd93..37b5276c0685f 100644 --- a/src/components/TextInput/BaseTextInput/implementation/index.native.tsx +++ b/src/components/TextInput/BaseTextInput/implementation/index.native.tsx @@ -308,7 +308,6 @@ function BaseTextInput({ !isMultiline && styles.componentHeightLarge, touchableInputWrapperStyle, ]} - sentryLabel={props.sentryLabel} > {!!suffixCharacter && ( diff --git a/src/pages/signin/SignInPageLayout/FooterRow.native.tsx b/src/pages/signin/SignInPageLayout/FooterRow.native.tsx index b39fa730dce8f..bd3033793a863 100644 --- a/src/pages/signin/SignInPageLayout/FooterRow.native.tsx +++ b/src/pages/signin/SignInPageLayout/FooterRow.native.tsx @@ -1,11 +1,11 @@ import React from 'react'; import type {GestureResponderEvent, StyleProp, TextStyle} from 'react-native'; -import Text from '@components/Text'; import {PressableWithoutFeedback} from '@components/Pressable'; +import Text from '@components/Text'; import useEnvironment from '@hooks/useEnvironment'; import useThemeStyles from '@hooks/useThemeStyles'; -import CONST from '@src/CONST'; import {openLink as openLinkUtil} from '@userActions/Link'; +import CONST from '@src/CONST'; import type {FooterColumnRow} from './types'; type FooterRowProps = FooterColumnRow & { From 8c407c6d2aa0ee233a7bc183e397b7f31dbda3f9 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Tue, 10 Feb 2026 07:31:39 +0430 Subject: [PATCH 7/7] consistency refactor file structure --- .../{FooterRow.native.tsx => FooterRow/index.native.tsx} | 2 +- .../SignInPageLayout/{FooterRow.tsx => FooterRow/index.tsx} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/pages/signin/SignInPageLayout/{FooterRow.native.tsx => FooterRow/index.native.tsx} (94%) rename src/pages/signin/SignInPageLayout/{FooterRow.tsx => FooterRow/index.tsx} (89%) diff --git a/src/pages/signin/SignInPageLayout/FooterRow.native.tsx b/src/pages/signin/SignInPageLayout/FooterRow/index.native.tsx similarity index 94% rename from src/pages/signin/SignInPageLayout/FooterRow.native.tsx rename to src/pages/signin/SignInPageLayout/FooterRow/index.native.tsx index bd3033793a863..a723a68fbe9c7 100644 --- a/src/pages/signin/SignInPageLayout/FooterRow.native.tsx +++ b/src/pages/signin/SignInPageLayout/FooterRow/index.native.tsx @@ -4,9 +4,9 @@ import {PressableWithoutFeedback} from '@components/Pressable'; import Text from '@components/Text'; import useEnvironment from '@hooks/useEnvironment'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {FooterColumnRow} from '@pages/signin/SignInPageLayout/types'; import {openLink as openLinkUtil} from '@userActions/Link'; import CONST from '@src/CONST'; -import type {FooterColumnRow} from './types'; type FooterRowProps = FooterColumnRow & { text: string; diff --git a/src/pages/signin/SignInPageLayout/FooterRow.tsx b/src/pages/signin/SignInPageLayout/FooterRow/index.tsx similarity index 89% rename from src/pages/signin/SignInPageLayout/FooterRow.tsx rename to src/pages/signin/SignInPageLayout/FooterRow/index.tsx index d4a1911dc36b6..4b2f167b212f5 100644 --- a/src/pages/signin/SignInPageLayout/FooterRow.tsx +++ b/src/pages/signin/SignInPageLayout/FooterRow/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import type {StyleProp, TextStyle} from 'react-native'; import TextLink from '@components/TextLink'; -import type {FooterColumnRow} from './types'; +import type {FooterColumnRow} from '@pages/signin/SignInPageLayout/types'; type FooterRowProps = FooterColumnRow & { text: string;