Skip to content

Commit

Permalink
spike/5010-roettger-ScreenReaderResponsiveTransitionHeader (#5047)
Browse files Browse the repository at this point in the history
* Ready for PR

* Hopeful fix for silly docusaurus failure

---------

Co-authored-by: Jon Bindbeutel <[email protected]>
  • Loading branch information
TimRoe and dumathane authored Mar 13, 2023
1 parent 63a6991 commit 0b8b9d3
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 33 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import HooksInfo from '../../../../src/components/HooksInfo'

export const exampleString = `const screenReaderEnabled = useIsScreenReaderEnabled()\n
const setFocus = useCallback(() => {
if (ref.current && screenReaderEnabled) {}
}`

<HooksInfo componentName="useIsScreenReaderEnabled" example={exampleString}/>
5 changes: 3 additions & 2 deletions VAMobile/src/components/Templates/CategoryLanding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'
import React, { FC, useState } from 'react'

import { CrisisLineCta, TextView, TextViewProps, VAIconProps } from 'components'
import { useRouteNavigation, useTheme } from 'utils/hooks'
import { useIsScreenReaderEnabled, useRouteNavigation, useTheme } from 'utils/hooks'
import HeaderBanner, { HeaderBannerProps } from './HeaderBanner'
import VAScrollView, { VAScrollViewProps } from 'components/VAScrollView'

Expand Down Expand Up @@ -34,6 +34,7 @@ export const CategoryLanding: FC<CategoryLandingProps> = ({ title, headerButton,
const fontScale = useWindowDimensions().fontScale
const theme = useTheme()
const navigateTo = useRouteNavigation()
const screenReaderEnabled = useIsScreenReaderEnabled(true)

const [scrollOffset, setScrollOffset] = useState(0)
const [trackScrollOffset, setTrackScrollOffset] = useState(true)
Expand Down Expand Up @@ -99,7 +100,7 @@ export const CategoryLanding: FC<CategoryLandingProps> = ({ title, headerButton,
<VAScrollView scrollEventThrottle={title ? 1 : 0} onScroll={onScroll} {...scrollViewProps}>
<View onLayout={getTransitionHeaderHeight}>
<CrisisLineCta onPress={navigateTo('VeteransCrisisLine')} />
{title ? <TextView {...subtitleProps}>{title}</TextView> : null}
{title && !screenReaderEnabled ? <TextView {...subtitleProps}>{title}</TextView> : null}
</View>
{children}
</VAScrollView>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'
import React, { FC, ReactNode, useState } from 'react'

import { TextView, TextViewProps, VAIconProps } from 'components'
import { useTheme } from 'utils/hooks'
import { useIsScreenReaderEnabled, useTheme } from 'utils/hooks'
import HeaderBanner, { HeaderBannerProps } from './HeaderBanner'
import VAScrollView, { VAScrollViewProps } from 'components/VAScrollView'

Expand Down Expand Up @@ -46,6 +46,7 @@ export const ChildTemplate: FC<ChildTemplateProps> = ({ backLabel, backLabelA11y
const insets = useSafeAreaInsets()
const fontScale = useWindowDimensions().fontScale
const theme = useTheme()
const screenReaderEnabled = useIsScreenReaderEnabled(true)

const [scrollOffset, setScrollOffset] = useState(0)
const [trackScrollOffset, setTrackScrollOffset] = useState(true)
Expand Down Expand Up @@ -108,9 +109,7 @@ export const ChildTemplate: FC<ChildTemplateProps> = ({ backLabel, backLabelA11y
transitionHeader(event.nativeEvent.contentOffset.y)
}}
{...scrollViewProps}>
<View onLayout={getTransitionHeaderHeight}>
<TextView {...subtitleProps}>{title}</TextView>
</View>
<View onLayout={getTransitionHeaderHeight}>{!screenReaderEnabled ? <TextView {...subtitleProps}>{title}</TextView> : null}</View>
{children}
</VAScrollView>
{footerContent}
Expand Down
21 changes: 13 additions & 8 deletions VAMobile/src/components/Templates/HeaderBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useFocusEffect } from '@react-navigation/native'
import React, { FC, useEffect, useReducer, useState } from 'react'

import { Box, BoxProps, DescriptiveBackButton, TextView, TextViewProps, VAIcon, VAIconProps } from 'components'
import { useAccessibilityFocus, useTheme } from 'utils/hooks'
import { useAccessibilityFocus, useIsScreenReaderEnabled, useTheme } from 'utils/hooks'
import MenuView, { MenuViewActionsType } from 'components/Menu'

export type HeaderLeftButtonProps = {
Expand Down Expand Up @@ -63,6 +63,7 @@ const HeaderBanner: FC<HeaderBannerProps> = ({ leftButton, title, rightButton, d
const [focusTitle, setFocusTitle] = useAccessibilityFocus<View>()
const focus = leftButton ? 'Left' : title ? 'Title' : 'Right'
useFocusEffect(focus === 'Title' ? setFocusTitle : setFocus)
const screenReaderEnabled = useIsScreenReaderEnabled(true)

const TEXT_CONSTRAINT_THRESHOLD = 30

Expand All @@ -79,6 +80,9 @@ const HeaderBanner: FC<HeaderBannerProps> = ({ leftButton, title, rightButton, d
* Reducer to swap between "VA" and title based on scroll
*/
const titleShowingReducer = (initTitleShowing: boolean) => {
if (screenReaderEnabled) {
return true
}
return transition ? title.scrollOffset >= title.transitionHeaderHeight : initTitleShowing
}

Expand Down Expand Up @@ -123,13 +127,14 @@ const HeaderBanner: FC<HeaderBannerProps> = ({ leftButton, title, rightButton, d
zIndex: 1,
}

const headerDropShadow: ShadowProps = titleShowing
? {
startColor: theme.colors.background.headerDropShadow,
distance: 4,
sides: { start: false, top: false, bottom: true, end: false },
}
: { disabled: true }
const headerDropShadow: ShadowProps =
titleShowing && !screenReaderEnabled
? {
startColor: theme.colors.background.headerDropShadow,
distance: 4,
sides: { start: false, top: false, bottom: true, end: false },
}
: { disabled: true }

const titleBannerProps: BoxProps = {
alignItems: 'center',
Expand Down
50 changes: 39 additions & 11 deletions VAMobile/src/utils/hooks.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
import { AccessibilityInfo, ActionSheetIOS, Alert, AlertButton, AppState, Dimensions, Linking, PixelRatio, ScrollView, UIManager, View, findNodeHandle } from 'react-native'
import {
AccessibilityInfo,
ActionSheetIOS,
Alert,
AlertButton,
AppState,
Dimensions,
EmitterSubscription,
Linking,
PixelRatio,
ScrollView,
UIManager,
View,
findNodeHandle,
} from 'react-native'
import { EventArg, useNavigation } from '@react-navigation/native'
import { ImagePickerResponse } from 'react-native-image-picker'
import { MutableRefObject, ReactNode, useCallback, useContext, useEffect, useRef, useState } from 'react'
Expand Down Expand Up @@ -147,10 +161,10 @@ export function useAccessibilityFocus<T>(): [MutableRefObject<T>, () => void] {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const ref: MutableRefObject<any> = useRef<T>(null)
const dispatch = useAppDispatch()
const screanReaderEnabled = useIsScreanReaderEnabled()
const screenReaderEnabled = useIsScreenReaderEnabled()

const setFocus = useCallback(() => {
if (ref.current && screanReaderEnabled) {
if (ref.current && screenReaderEnabled) {
/**
* There is a race condition during transition that causes the accessibility focus
* to intermittently fail to be set https://github.com/facebook/react-native/issues/30097
Expand Down Expand Up @@ -181,32 +195,46 @@ export function useAccessibilityFocus<T>(): [MutableRefObject<T>, () => void] {

return () => clearTimeout(timeOutPageFocus)
}
}, [ref, dispatch, screanReaderEnabled])
}, [ref, dispatch, screenReaderEnabled])

return [ref, setFocus]
}

/**
* Hook to check if the screan reader is enabled
* Hook to check if the screen reader is enabled
*
* withListener - True to add a listener to live update screen reader status, default false
* @returns boolean if the screen reader is on
*/
export function useIsScreanReaderEnabled(): boolean {
const [screanReaderEnabled, setScreanReaderEnabled] = useState(false)
export function useIsScreenReaderEnabled(withListener = false): boolean {
const [screenReaderEnabled, setScreenReaderEnabled] = useState(false)

useEffect(() => {
let isMounted = true
let screenReaderChangedSubscription: EmitterSubscription

if (withListener) {
screenReaderChangedSubscription = AccessibilityInfo.addEventListener('screenReaderChanged', (isScreenReaderEnabled) => {
if (isMounted) {
setScreenReaderEnabled(isScreenReaderEnabled)
}
})
}
AccessibilityInfo.isScreenReaderEnabled().then((isScreenReaderEnabled) => {
if (isMounted) {
setScreanReaderEnabled(isScreenReaderEnabled)
setScreenReaderEnabled(isScreenReaderEnabled)
}
})

return () => {
isMounted = false
if (withListener) {
screenReaderChangedSubscription.remove()
}
}
}, [screanReaderEnabled])
}, [screenReaderEnabled, withListener])

return screanReaderEnabled
return screenReaderEnabled
}

/**
Expand Down Expand Up @@ -301,7 +329,7 @@ export function useAutoScrollToElement(): [MutableRefObject<ScrollView>, Mutable
const scrollRef = useRef() as MutableRefObject<ScrollView>
const [viewRef, setFocus] = useAccessibilityFocus<View>()
const [shouldFocus, setShouldFocus] = useState(true)
const screenReaderEnabled = useIsScreanReaderEnabled()
const screenReaderEnabled = useIsScreenReaderEnabled()

const scrollToElement = useCallback(
(offset?: number) => {
Expand Down

0 comments on commit 0b8b9d3

Please sign in to comment.