diff options
author | Paul Frazee <pfrazee@gmail.com> | 2024-07-02 21:25:19 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-02 21:25:19 -0700 |
commit | a3d4fb652b888ba81aecbf0e81a954968ea65d39 (patch) | |
tree | e78df8bf670baee080fa77b198db30058a012589 /src/tours/Tooltip.tsx | |
parent | 6694a33603544511441474819216d51482d19827 (diff) | |
download | voidsky-a3d4fb652b888ba81aecbf0e81a954968ea65d39.tar.zst |
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>
Diffstat (limited to 'src/tours/Tooltip.tsx')
-rw-r--r-- | src/tours/Tooltip.tsx | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/src/tours/Tooltip.tsx b/src/tours/Tooltip.tsx new file mode 100644 index 000000000..e7727763b --- /dev/null +++ b/src/tours/Tooltip.tsx @@ -0,0 +1,168 @@ +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> + ) +} |