1- import type { ReactNode , RefObject } from 'react' ;
2- import React , { useState } from 'react' ;
1+ import type { ReactNode } from 'react' ;
2+ import React , { useRef , useState } from 'react' ;
33import type { GestureResponderEvent , LayoutChangeEvent , View } from 'react-native' ;
44import Hoverable from '@components/Hoverable' ;
5+ import { useLHNTooltipContext } from '@components/LHNOptionsList/LHNTooltipContext' ;
6+ import useLHNRowProductTrainingTooltip from '@components/LHNOptionsList/OptionRowLHN/useLHNRowProductTrainingTooltip' ;
57import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction' ;
8+ import getContextMenuAccessibilityHint from '@components/utils/getContextMenuAccessibilityHint' ;
9+ import getContextMenuAccessibilityProps from '@components/utils/getContextMenuAccessibilityProps' ;
10+ import useEnvironment from '@hooks/useEnvironment' ;
11+ import useLocalize from '@hooks/useLocalize' ;
612import useResponsiveLayout from '@hooks/useResponsiveLayout' ;
713import useStyleUtils from '@hooks/useStyleUtils' ;
814import useTheme from '@hooks/useTheme' ;
@@ -14,35 +20,72 @@ import {startSpan} from '@libs/telemetry/activeSpans';
1420import { showContextMenu } from '@pages/inbox/report/ContextMenu/ReportActionContextMenu' ;
1521import variables from '@styles/variables' ;
1622import CONST from '@src/CONST' ;
17- import useLHNRowProductTrainingTooltip from './useLHNRowProductTrainingTooltip' ;
1823
19- type OptionRowPressableProps = {
24+ type PressableProps = {
25+ /** Option data for the row. Source of accessibility text and the report ID used by press/context-menu actions. */
2026 optionItem : OptionData ;
27+
28+ /** Whether the row is the currently focused/active option. Drives the focused background and accessibility metadata. */
2129 isOptionFocused : boolean ;
22- isScreenFocused : boolean ;
23- popoverAnchor : RefObject < View | null > ;
24- onSelectRow : ( optionItem : OptionData , popoverAnchor : RefObject < View | null > ) => void ;
30+
31+ /** Press handler invoked with the option data and the popover anchor ref. */
32+ onSelectRow : ( optionItem : OptionData , popoverAnchor : React . RefObject < View | null > ) => void ;
33+
34+ /** Layout handler forwarded to the underlying pressable. */
2535 onLayout ?: ( event : LayoutChangeEvent ) => void ;
26- accessibilityLabel : string ;
27- accessibilityHint ?: string ;
28- testID ?: string ;
29- children : ( hovered : boolean ) => ReactNode ;
36+
37+ /** Fires when the mouse enters the row. Hover state lives in the parent so leaves like Avatar can react. */
38+ onHoverIn ?: ( ) => void ;
39+
40+ /** Fires when the mouse leaves the row. */
41+ onHoverOut ?: ( ) => void ;
42+
43+ /** Row content. */
44+ children : ReactNode ;
3045} ;
3146
32- function OptionRowPressable ( {
33- optionItem,
34- isOptionFocused,
35- isScreenFocused,
36- popoverAnchor,
37- onSelectRow,
38- onLayout,
39- accessibilityLabel,
40- accessibilityHint,
41- testID,
42- children,
43- } : OptionRowPressableProps ) {
47+ function Pressable ( { optionItem, isOptionFocused, onSelectRow, onLayout, onHoverIn, onHoverOut, children} : PressableProps ) {
48+ const theme = useTheme ( ) ;
49+ const styles = useThemeStyles ( ) ;
50+ const StyleUtils = useStyleUtils ( ) ;
51+ const { translate} = useLocalize ( ) ;
52+ const { isProduction} = useEnvironment ( ) ;
53+ const { isScreenFocused} = useLHNTooltipContext ( ) ;
54+ const { shouldUseNarrowLayout} = useResponsiveLayout ( ) ;
4455 const { hideProductTrainingTooltip} = useLHNRowProductTrainingTooltip ( ) ;
56+
57+ const popoverAnchor = useRef < View > ( null ) ;
58+ const [ isContextMenuActive , setIsContextMenuActive ] = useState ( false ) ;
59+
4560 const reportID = optionItem . reportID ;
61+ const brickRoadIndicator = optionItem . brickRoadIndicator ;
62+ const actionBadgeText = ! isProduction && optionItem . actionBadge ? translate ( `common.actionBadge.${ optionItem . actionBadge } ` ) : '' ;
63+
64+ let accessibilityLabelForBadge = '' ;
65+ if ( brickRoadIndicator ) {
66+ accessibilityLabelForBadge = [ translate ( 'common.yourReviewIsRequired' ) , actionBadgeText ] . filter ( Boolean ) . join ( ', ' ) ;
67+ } else if ( optionItem . isPinned ) {
68+ accessibilityLabelForBadge = translate ( 'common.pinned' ) ;
69+ }
70+
71+ const accessibilityLabel = [
72+ `${ translate ( 'accessibilityHints.navigatesToChat' ) } ${ optionItem . text } ` ,
73+ optionItem . isUnread ? translate ( 'common.unread' ) : '' ,
74+ optionItem . alternateText ?? '' ,
75+ accessibilityLabelForBadge ,
76+ ]
77+ . filter ( Boolean )
78+ . join ( '. ' ) ;
79+ const contextMenuHint = getContextMenuAccessibilityHint ( { translate} ) ;
80+ const { accessibilityLabel : accessibilityLabelWithContextMenuHint , accessibilityHint} = getContextMenuAccessibilityProps ( {
81+ accessibilityLabel,
82+ nativeAccessibilityHint : accessibilityLabel ,
83+ contextMenuHint,
84+ } ) ;
85+
86+ // reportID may be a number contrary to the type definition
87+ const testID = typeof reportID === 'number' ? String ( reportID ) : reportID ;
88+
4689 const onPress = ( event : GestureResponderEvent | KeyboardEvent | undefined ) => {
4790 hideProductTrainingTooltip ( ) ;
4891 startSpan ( `${ CONST . TELEMETRY . SPAN_OPEN_REPORT } _${ reportID } ` , {
@@ -55,11 +98,6 @@ function OptionRowPressable({
5598 ReportActionComposeFocusManager . focus ( ) ;
5699 onSelectRow ( optionItem , popoverAnchor ) ;
57100 } ;
58- const theme = useTheme ( ) ;
59- const styles = useThemeStyles ( ) ;
60- const StyleUtils = useStyleUtils ( ) ;
61- const { shouldUseNarrowLayout} = useResponsiveLayout ( ) ;
62- const [ isContextMenuActive , setIsContextMenuActive ] = useState ( false ) ;
63101
64102 const showPopover = ( event : MouseEvent | GestureResponderEvent ) => {
65103 if ( ! isScreenFocused && shouldUseNarrowLayout ) {
@@ -86,7 +124,10 @@ function OptionRowPressable({
86124 } ;
87125
88126 return (
89- < Hoverable >
127+ < Hoverable
128+ onHoverIn = { onHoverIn }
129+ onHoverOut = { onHoverOut }
130+ >
90131 { ( hovered ) => (
91132 < PressableWithSecondaryInteraction
92133 ref = { popoverAnchor }
@@ -121,19 +162,19 @@ function OptionRowPressable({
121162 ( hovered || isContextMenuActive ) && ! isOptionFocused ? styles . sidebarLinkHover : null ,
122163 ] }
123164 role = { CONST . ROLE . BUTTON }
124- accessibilityLabel = { accessibilityLabel }
165+ accessibilityLabel = { accessibilityLabelWithContextMenuHint }
125166 accessibilityHint = { accessibilityHint }
126167 onLayout = { onLayout }
127168 needsOffscreenAlphaCompositing = { ( optionItem ?. icons ?. length ?? 0 ) >= 2 }
128169 sentryLabel = { CONST . SENTRY_LABEL . LHN . OPTION_ROW }
129170 >
130- { children ( hovered ) }
171+ { children }
131172 </ PressableWithSecondaryInteraction >
132173 ) }
133174 </ Hoverable >
134175 ) ;
135176}
136177
137- OptionRowPressable . displayName = 'OptionRowPressable ' ;
178+ Pressable . displayName = 'OptionRow.Pressable ' ;
138179
139- export default OptionRowPressable ;
180+ export default Pressable ;
0 commit comments