Skip to content
Merged
4 changes: 2 additions & 2 deletions src/components/FormHelpMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import isEmpty from 'lodash/isEmpty';
import React, {useMemo} from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
import useAccessibilityAnnouncement from '@hooks/useAccessibilityAnnouncement';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
Expand All @@ -11,7 +12,6 @@ import CONST from '@src/CONST';
import Icon from './Icon';
import RenderHTML from './RenderHTML';
import Text from './Text';
import useFormHelpMessageAccessibilityAnnouncement from './utils/useFormHelpMessageAccessibilityAnnouncement';

type FormHelpMessageProps = {
/** Error or hint text. Ignored when children is not empty */
Expand Down Expand Up @@ -69,7 +69,7 @@ function FormHelpMessage({
return `<muted-text-label>${replacedText}</muted-text-label>`;
}, [isError, message, shouldRenderMessageAsHTML]);

useFormHelpMessageAccessibilityAnnouncement(message, shouldAnnounceError);
useAccessibilityAnnouncement(message, shouldAnnounceError);

const errorIconLabel = isError && shouldShowRedDotIndicator ? CONST.ACCESSIBILITY_LABELS.ERROR : undefined;

Expand Down
9 changes: 7 additions & 2 deletions src/components/SelectionList/components/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {TextInputOptions} from '@components/SelectionList/types';
import Text from '@components/Text';
import BaseTextInput from '@components/TextInput';
import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types';
import useAccessibilityAnnouncement from '@hooks/useAccessibilityAnnouncement';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import mergeRefs from '@libs/mergeRefs';
Expand Down Expand Up @@ -66,9 +67,13 @@ function TextInput({
const styles = useThemeStyles();
const {translate} = useLocalize();
const {label, value, onChangeText, errorText, headerMessage, hint, disableAutoFocus, placeholder, maxLength, inputMode, ref: optionsRef, style, disableAutoCorrect} = options ?? {};
const resultsFound = headerMessage !== translate('common.noResultsFound');
const noResultsFoundText = translate('common.noResultsFound');
const isNoResultsFoundMessage = headerMessage === noResultsFoundText;
const noData = dataLength === 0 && !shouldShowLoadingPlaceholder;
const shouldShowHeaderMessage = !!headerMessage && (!isLoadingNewOptions || resultsFound || noData);
const shouldShowHeaderMessage = !!shouldShowTextInput && !!headerMessage && (!isLoadingNewOptions || !isNoResultsFoundMessage || noData);
const shouldAnnounceNoResults = shouldShowHeaderMessage && isNoResultsFoundMessage;

useAccessibilityAnnouncement(headerMessage, shouldAnnounceNoResults, {shouldAnnounceOnNative: true});

const focusTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const mergedRef = mergeRefs<BaseTextInputRef>(ref, optionsRef);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {PressableWithFeedback} from '@components/Pressable';
import SectionList from '@components/SectionList';
import Text from '@components/Text';
import TextInput from '@components/TextInput';
import useAccessibilityAnnouncement from '@hooks/useAccessibilityAnnouncement';
import useActiveElementRole from '@hooks/useActiveElementRole';
import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager';
import useKeyboardShortcut from '@hooks/useKeyboardShortcut';
Expand Down Expand Up @@ -1006,9 +1007,15 @@ function BaseSelectionListWithSections<TItem extends ListItem>({
},
);

const noResultsFoundText = translate('common.noResultsFound');
const isNoResultsFoundMessage = headerMessage === noResultsFoundText;
const shouldShowHeaderMessage = !!headerMessage && (!isLoadingNewOptions || !isNoResultsFoundMessage || (flattenedSections.allOptions.length === 0 && !shouldShowLoadingPlaceholder));
const shouldAnnounceNoResults = shouldShowHeaderMessage && isNoResultsFoundMessage;

useAccessibilityAnnouncement(headerMessage, shouldAnnounceNoResults, {shouldAnnounceOnNative: true});

const headerMessageContent = () =>
(!isLoadingNewOptions || headerMessage !== translate('common.noResultsFound') || (flattenedSections.allOptions.length === 0 && !shouldShowLoadingPlaceholder)) &&
!!headerMessage && (
shouldShowHeaderMessage && (
<View style={headerMessageStyle ?? [styles.ph5, styles.pb5]}>
<Text style={[styles.textLabel, styles.colorMuted, styles.minHeight5]}>{headerMessage}</Text>
</View>
Expand Down

This file was deleted.

Comment thread
marufsharifi marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ import type {ReactNode} from 'react';
import {useEffect, useRef} from 'react';
import {AccessibilityInfo} from 'react-native';

type UseAccessibilityAnnouncementOptions = {
shouldAnnounceOnNative?: boolean;
};

const DELAY_FOR_ACCESSIBILITY_TREE_SYNC = 100;

function useFormHelpMessageAccessibilityAnnouncement(message: string | ReactNode, shouldAnnounceError: boolean) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function useAccessibilityAnnouncement(message: string | ReactNode, shouldAnnounceMessage: boolean, _options?: UseAccessibilityAnnouncementOptions) {
const previousAnnouncedMessageRef = useRef('');

useEffect(() => {
if (!shouldAnnounceError || typeof message !== 'string' || !message.trim()) {
if (!shouldAnnounceMessage || typeof message !== 'string' || !message.trim()) {
previousAnnouncedMessageRef.current = '';
return;
}
Expand All @@ -25,7 +30,7 @@ function useFormHelpMessageAccessibilityAnnouncement(message: string | ReactNode
}, DELAY_FOR_ACCESSIBILITY_TREE_SYNC);

return () => clearTimeout(timeout);
}, [message, shouldAnnounceError]);
}, [message, shouldAnnounceMessage]);
}

export default useFormHelpMessageAccessibilityAnnouncement;
export default useAccessibilityAnnouncement;
28 changes: 28 additions & 0 deletions src/hooks/useAccessibilityAnnouncement/index.native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type {ReactNode} from 'react';
import {useEffect, useRef} from 'react';
import {AccessibilityInfo} from 'react-native';

type UseAccessibilityAnnouncementOptions = {
shouldAnnounceOnNative?: boolean;
};

function useAccessibilityAnnouncement(message: string | ReactNode, shouldAnnounceMessage: boolean, options?: UseAccessibilityAnnouncementOptions) {
const previousAnnouncedMessageRef = useRef('');
const shouldAnnounceOnNative = options?.shouldAnnounceOnNative ?? false;

useEffect(() => {
if (!shouldAnnounceOnNative || !shouldAnnounceMessage || typeof message !== 'string' || !message.trim()) {
previousAnnouncedMessageRef.current = '';
return;
}

if (previousAnnouncedMessageRef.current === message) {
return;
}

previousAnnouncedMessageRef.current = message;
AccessibilityInfo.announceForAccessibility(message);
}, [message, shouldAnnounceMessage, shouldAnnounceOnNative]);
}

export default useAccessibilityAnnouncement;
10 changes: 10 additions & 0 deletions src/hooks/useAccessibilityAnnouncement/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type {ReactNode} from 'react';

type UseAccessibilityAnnouncementOptions = {
shouldAnnounceOnNative?: boolean;
};

// eslint-disable-next-line @typescript-eslint/no-unused-vars
function useAccessibilityAnnouncement(_message: string | ReactNode, _shouldAnnounceMessage: boolean, _options?: UseAccessibilityAnnouncementOptions) {}

export default useAccessibilityAnnouncement;
Loading