bsky-app/src/tours/Tooltip.tsx
Paul Frazee a3d4fb652b
Guided tour for new users (#4690)
* Add home guided tour (WIP)

* Add web handling of the tour

* Switch to our fork of rn-tourguide

* Bump guided-tour

* Fix alignment on android

* Implement home page tour trigger after account creation

* Add new_user_guided_tour gate

* Add a title line to the tour tooltips

* A11y improvements: proper labels, focus capture, scroll capture

* Silence type error

* Native a11y

* Use FocusScope

* Switch to useWebBodyScrollLock()

---------

Co-authored-by: Eric Bailey <git@esb.lol>
2024-07-02 21:25:19 -07:00

168 lines
4.7 KiB
TypeScript

import * as React from 'react'
import {
AccessibilityInfo,
findNodeHandle,
Pressable,
Text as RNText,
View,
} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {FocusScope} from '@tamagui/focus-scope'
import {IStep, Labels} from 'rn-tourguide'
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
import {useA11y} from '#/state/a11y'
import {Logo} from '#/view/icons/Logo'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import {leading, Text} from '#/components/Typography'
const stopPropagation = (e: any) => e.stopPropagation()
export interface TooltipComponentProps {
isFirstStep?: boolean
isLastStep?: boolean
currentStep: IStep
labels?: Labels
handleNext?: () => void
handlePrev?: () => void
handleStop?: () => void
}
export function TooltipComponent({
isLastStep,
handleNext,
handleStop,
currentStep,
labels,
}: TooltipComponentProps) {
const t = useTheme()
const {_} = useLingui()
const btnRef = React.useRef<View>(null)
const textRef = React.useRef<RNText>(null)
const {screenReaderEnabled} = useA11y()
useWebBodyScrollLock(true)
const focusTextNode = () => {
const node = textRef.current ? findNodeHandle(textRef.current) : undefined
if (node) {
AccessibilityInfo.setAccessibilityFocus(node)
}
}
// handle initial focus immediately on mount
React.useLayoutEffect(() => {
focusTextNode()
}, [])
// handle focus between steps
const innerHandleNext = () => {
handleNext?.()
setTimeout(() => focusTextNode(), 200)
}
return (
<FocusScope loop enabled trapped>
<View
role="alert"
aria-role="alert"
aria-label={_(msg`A help tooltip`)}
accessibilityLiveRegion="polite"
// iOS
accessibilityViewIsModal
// Android
importantForAccessibility="yes"
// @ts-ignore web only
onClick={stopPropagation}
onStartShouldSetResponder={_ => true}
onTouchEnd={stopPropagation}
style={[
t.atoms.bg,
a.px_lg,
a.py_lg,
a.flex_col,
a.gap_md,
a.rounded_sm,
a.shadow_md,
{maxWidth: 300},
]}>
{screenReaderEnabled && (
<Pressable
style={[
a.absolute,
a.inset_0,
a.z_10,
{height: 10, bottom: 'auto'},
]}
accessibilityLabel={_(
msg`Start of onboarding tour window. Do not move backward. Instead, go forward for more options, or press to skip.`,
)}
accessibilityHint={undefined}
onPress={handleStop}
/>
)}
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
<Logo width={16} style={{position: 'relative', top: 0}} />
<Text
accessible={false}
style={[a.text_sm, a.font_semibold, t.atoms.text_contrast_medium]}>
<Trans>Quick tip</Trans>
</Text>
</View>
<RNText
ref={textRef}
testID="stepDescription"
accessibilityLabel={_(
msg`Onboarding tour step ${currentStep.name}: ${currentStep.text}`,
)}
accessibilityHint={undefined}
style={[
a.text_md,
t.atoms.text,
a.pb_sm,
{
lineHeight: leading(a.text_md, a.leading_snug),
},
]}>
{currentStep.text}
</RNText>
{!isLastStep ? (
<Button
ref={btnRef}
variant="gradient"
color="gradient_sky"
size="medium"
onPress={innerHandleNext}
label={labels?.next || _(msg`Go to the next step of the tour`)}>
<ButtonText>{labels?.next || _(msg`Next`)}</ButtonText>
</Button>
) : (
<Button
variant="gradient"
color="gradient_sky"
size="medium"
onPress={handleStop}
label={
labels?.finish ||
_(msg`Finish tour and begin using the application`)
}>
<ButtonText>{labels?.finish || _(msg`Let's go!`)}</ButtonText>
</Button>
)}
{screenReaderEnabled && (
<Pressable
style={[a.absolute, a.inset_0, a.z_10, {height: 10, top: 'auto'}]}
accessibilityLabel={_(
msg`End of onboarding tour window. Do not move forward. Instead, go backward for more options, or press to skip.`,
)}
accessibilityHint={undefined}
onPress={handleStop}
/>
)}
</View>
</FocusScope>
)
}