Skip to content

Commit

Permalink
Split out util hooks for accessibility, OSfunctions, and storybook utils
Browse files Browse the repository at this point in the history
  • Loading branch information
TimRoe committed Sep 13, 2024
1 parent 1c6a099 commit 66a4aa3
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 110 deletions.
1 change: 1 addition & 0 deletions packages/components/.npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ tsconfig.test.json
# Source code
src/components/*/*.stories.tsx
src/components/*/*.test.tsx
src/utils/hooks/useWebStorybookColorScheme.ts
src/utils/storybook.tsx
src/App.tsx
2 changes: 1 addition & 1 deletion packages/components/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ export {
export { Spacer } from './components/Spacer/Spacer'

// Export consumer available utilities here so they are exported through npm
export { useIsScreenReaderEnabled, useTheme } from './utils'
export { useSnackbar } from './components/Snackbar/useSnackbar'
export { useTheme } from './utils'
57 changes: 4 additions & 53 deletions packages/components/src/utils/OSfunctions.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/** A file for functions that leverage OS functionality */

import { Alert, Linking, Platform } from 'react-native'
import { LinkAnalytics } from '../components/Link/Link'
import { useTranslation } from 'react-i18next'
import { Platform } from 'react-native'

// Export related hooks
export { useExternalLink } from './hooks/useExternalLink'

/** True if iOS; a function to pull real time for unit tests so it can be changed */
export const isIOS = () => Platform.OS === 'ios'
Expand Down Expand Up @@ -63,53 +64,3 @@ export type leaveAppPromptText = {
confirm?: string
title?: string
}

/**
* Hook to handle a link that leaves the app; conditionally displays alert prior to leaving
*/
export function useExternalLink(): (
url: string,
analytics?: LinkAnalytics,
text?: leaveAppPromptText,
) => void {
const { t } = useTranslation()

return (
url: string,
analytics?: LinkAnalytics,
text?: leaveAppPromptText,
) => {
if (analytics?.onPress) analytics.onPress()

const onCancelPress = () => {
if (analytics?.onCancel) analytics.onCancel()
}

const onOKPress = () => {
if (analytics?.onConfirm) analytics.onConfirm()
return Linking.openURL(url)
}

const body = text?.body ? text.body : t('leaveAppAlert.body')
const cancel = text?.cancel ? text.cancel : t('goBack')
const confirm = text?.confirm ? text.confirm : t('leave')
const title = text?.title ? text.title : t('leaveAppAlert.title')

if (url.startsWith('http')) {
Alert.alert(title, body, [
{
text: cancel,
style: 'cancel',
onPress: onCancelPress,
},
{
text: confirm,
onPress: (): Promise<void> => onOKPress(),
style: 'default',
},
])
} else {
Linking.openURL(url)
}
}
}
33 changes: 2 additions & 31 deletions packages/components/src/utils/accessibility.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,2 @@
import { AccessibilityInfo } from 'react-native'
import { useEffect, useState } from 'react'

/**
* Hook to monitor screen reader status
* @returns True when the screen reader is on, else false
*/
export function useIsScreenReaderEnabled(): boolean {
const [screenReaderEnabled, setScreenReaderEnabled] = useState(false)

useEffect(() => {
// Function to update state based on the screen reader status
const updateScreenReaderStatus = (isEnabled: boolean) => {
setScreenReaderEnabled(isEnabled)
}

// Initiate with current screen reader status
AccessibilityInfo.isScreenReaderEnabled().then(updateScreenReaderStatus)

// Subscribe to screen reader status changes
const subscription = AccessibilityInfo.addEventListener(
'screenReaderChanged',
updateScreenReaderStatus,
)

// Cleanup subscription on component unmount
return () => subscription.remove()
}, [screenReaderEnabled])

return screenReaderEnabled
}
// Export related hooks
export { useIsScreenReaderEnabled } from './hooks/useIsScreenReaderEnabled'
2 changes: 1 addition & 1 deletion packages/components/src/utils/hooks/useColorScheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function useColorScheme(): ColorSchemeName {
} else {
try {
const webStorybookColorScheme =
require('./storybook').webStorybookColorScheme // eslint-disable-line
require('./useWebStorybookColorScheme').useWebStorybookColorScheme // eslint-disable-line
return webStorybookColorScheme()
} catch (error) {
return null
Expand Down
55 changes: 55 additions & 0 deletions packages/components/src/utils/hooks/useExternalLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Alert, Linking } from 'react-native'
import { useTranslation } from 'react-i18next'

import { LinkAnalytics } from '../../components/Link/Link'
import { leaveAppPromptText } from '../OSfunctions'

/**
* Hook to handle a link that leaves the app; conditionally displays alert prior to leaving
*/
export function useExternalLink(): (
url: string,
analytics?: LinkAnalytics,
text?: leaveAppPromptText,
) => void {
const { t } = useTranslation()

return (
url: string,
analytics?: LinkAnalytics,
text?: leaveAppPromptText,
) => {
if (analytics?.onPress) analytics.onPress()

const onCancelPress = () => {
if (analytics?.onCancel) analytics.onCancel()
}

const onOKPress = () => {
if (analytics?.onConfirm) analytics.onConfirm()
return Linking.openURL(url)
}

const body = text?.body ? text.body : t('leaveAppAlert.body')
const cancel = text?.cancel ? text.cancel : t('goBack')
const confirm = text?.confirm ? text.confirm : t('leave')
const title = text?.title ? text.title : t('leaveAppAlert.title')

if (url.startsWith('http')) {
Alert.alert(title, body, [
{
text: cancel,
style: 'cancel',
onPress: onCancelPress,
},
{
text: confirm,
onPress: (): Promise<void> => onOKPress(),
style: 'default',
},
])
} else {
Linking.openURL(url)
}
}
}
31 changes: 31 additions & 0 deletions packages/components/src/utils/hooks/useIsScreenReaderEnabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { AccessibilityInfo } from 'react-native'
import { useEffect, useState } from 'react'

/**
* Hook to monitor screen reader status
* @returns True when the screen reader is on, else false
*/
export function useIsScreenReaderEnabled(): boolean {
const [screenReaderEnabled, setScreenReaderEnabled] = useState(false)

useEffect(() => {
// Function to update state based on the screen reader status
const updateScreenReaderStatus = (isEnabled: boolean) => {
setScreenReaderEnabled(isEnabled)
}

// Initiate with current screen reader status
AccessibilityInfo.isScreenReaderEnabled().then(updateScreenReaderStatus)

// Subscribe to screen reader status changes
const subscription = AccessibilityInfo.addEventListener(
'screenReaderChanged',
updateScreenReaderStatus,
)

// Cleanup subscription on component unmount
return () => subscription.remove()
}, [screenReaderEnabled])

return screenReaderEnabled
}
2 changes: 1 addition & 1 deletion packages/components/src/utils/hooks/useTheme.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ColorSchemeName } from 'react-native'
import { Theme, themes } from '@department-of-veterans-affairs/mobile-tokens'

import { useColorScheme } from '../style'
import { useColorScheme } from './useColorScheme'

/** Returns light/dark theme based on useColorScheme */
export function useTheme(): Theme {
Expand Down
21 changes: 21 additions & 0 deletions packages/components/src/utils/hooks/useWebStorybookColorScheme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ColorSchemeName } from 'react-native'
import { useSyncExternalStore } from 'react'

/** Function to initialize listening to light/dark mode toggle in Web Storybook to set color scheme */
export const useWebStorybookColorScheme = () => {
// Mimics RN useColorScheme hook, but listens to the parent body's class ('light'/'dark' from storybook-dark-mode)
return useSyncExternalStore(
(callback: () => void) => {
window.parent.addEventListener('click', callback)
return () => window.parent.removeEventListener('click', callback)
},
(): ColorSchemeName => {
const colorScheme = window.parent.document.body.className
if (colorScheme !== 'light' && colorScheme !== 'dark') {
return null
}

return colorScheme
},
)
}
35 changes: 12 additions & 23 deletions packages/components/src/utils/storybook.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ColorSchemeName, Linking, Text, View } from 'react-native'
import {
Controls,
Description,
Expand All @@ -7,7 +6,11 @@ import {
Subtitle,
Title,
} from '@storybook/addon-docs'
import React, { useSyncExternalStore } from 'react'
import { Linking, Text, View } from 'react-native'
import React from 'react'

// Export related hooks
export { useWebStorybookColorScheme } from './hooks/useWebStorybookColorScheme'

type DocProps = {
name?: string
Expand All @@ -28,11 +31,16 @@ export const DocLink = ({ name, docUrl }: DocProps) => {
return (
<View style={{ marginVertical: 10 }}>
<Text
style={{ color: 'blue', textDecorationLine: 'underline', lineHeight: 20 }}
style={{
color: 'blue',
textDecorationLine: 'underline',
lineHeight: 20,
}}
onPress={() => {
Linking.openURL(docUrl)
}}>
View guidance for the {name} component on the VA Mobile Documentation Site
View guidance for the {name} component on the VA Mobile Documentation
Site
</Text>
</View>
)
Expand All @@ -59,22 +67,3 @@ export const generateDocs = ({ name, docUrl, icons }: DocProps) => ({
</>
),
})

/** Function to initialize listening to light/dark mode toggle in Web Storybook to set color scheme */
export const webStorybookColorScheme = () => {
// Mimics RN useColorScheme hook, but listens to the parent body's class ('light'/'dark' from storybook-dark-mode)
return useSyncExternalStore(
(callback: () => void) => {
window.parent.addEventListener('click', callback)
return () => window.parent.removeEventListener('click', callback)
},
(): ColorSchemeName => {
const colorScheme = window.parent.document.body.className
if (colorScheme !== 'light' && colorScheme !== 'dark') {
return null
}

return colorScheme
},
)
}

0 comments on commit 66a4aa3

Please sign in to comment.