import { FC, useEffect, useRef } from "react"; import { Animated, Pressable, StyleSheet, Text, View, Easing, } from "react-native"; import { gameUIColors, dialColors } from "@react-buoy/shared-ui"; import { calculateTooltipPosition, ARROW_BOTTOM_OFFSET, ARROW_HEIGHT } from "./onboardingConstants"; interface OnboardingTooltipProps { visible: boolean; onDismiss: () => void; /** Optional custom title. Defaults to "Welcome to Buoy Dev Tools!" */ title?: string; /** Optional custom message. Defaults to dial menu instruction */ message?: string; } export const OnboardingTooltip: FC = ({ visible, onDismiss, title = "Welcome to Buoy Dev Tools!", message = "Tap the center button to configure and add developer tools to your dial menu.", }) => { const fadeAnim = useRef(new Animated.Value(0)).current; const scaleAnim = useRef(new Animated.Value(0.8)).current; const pulseAnim = useRef(new Animated.Value(1)).current; const arrowBounceAnim = useRef(new Animated.Value(0)).current; useEffect(() => { if (visible) { // Entrance animation Animated.parallel([ Animated.timing(fadeAnim, { toValue: 1, duration: 300, easing: Easing.out(Easing.ease), useNativeDriver: true, }), Animated.spring(scaleAnim, { toValue: 1, damping: 12, stiffness: 150, useNativeDriver: true, }), ]).start(); // Continuous pulse animation for the tooltip Animated.loop( Animated.sequence([ Animated.timing(pulseAnim, { toValue: 1.02, duration: 1500, easing: Easing.inOut(Easing.ease), useNativeDriver: true, }), Animated.timing(pulseAnim, { toValue: 1, duration: 1500, easing: Easing.inOut(Easing.ease), useNativeDriver: true, }), ]) ).start(); // Bouncing arrow animation Animated.loop( Animated.sequence([ Animated.timing(arrowBounceAnim, { toValue: -8, duration: 800, easing: Easing.inOut(Easing.ease), useNativeDriver: true, }), Animated.timing(arrowBounceAnim, { toValue: 0, duration: 800, easing: Easing.inOut(Easing.ease), useNativeDriver: true, }), ]) ).start(); } else { // Exit animation Animated.parallel([ Animated.timing(fadeAnim, { toValue: 0, duration: 200, easing: Easing.in(Easing.ease), useNativeDriver: true, }), Animated.timing(scaleAnim, { toValue: 0.8, duration: 200, easing: Easing.in(Easing.ease), useNativeDriver: true, }), ]).start(); } }, [visible]); if (!visible) { return null; } return ( {/* Tooltip content positioned above the center button */} {/* Glow effect */} {/* Content */} {title} {message} {/* Got it button */} [ styles.button, pressed && styles.buttonPressed, ]} > Got it! {/* Arrow pointing down to center button */} ); }; const styles = StyleSheet.create({ container: { ...StyleSheet.absoluteFillObject, alignItems: "center", justifyContent: "center", pointerEvents: "box-none", }, tooltipContainer: { position: "absolute", bottom: calculateTooltipPosition(), alignItems: "center", maxWidth: 280, }, tooltip: { backgroundColor: dialColors.dialBackground, borderRadius: 16, borderWidth: 2, borderColor: dialColors.dialBorder, padding: 20, shadowColor: gameUIColors.info, shadowOffset: { width: 0, height: 0 }, shadowOpacity: 0.6, shadowRadius: 20, elevation: 10, }, glowEffect: { position: "absolute", top: -4, left: -4, right: -4, bottom: -4, borderRadius: 18, backgroundColor: "#FFFFFF", opacity: 0.15, }, content: { alignItems: "center", gap: 12, }, title: { color: gameUIColors.primary, fontSize: 16, fontWeight: "900", fontFamily: "monospace", letterSpacing: 1, textAlign: "center", textTransform: "uppercase", textShadowColor: gameUIColors.info, textShadowOffset: { width: 0, height: 0 }, textShadowRadius: 4, }, message: { color: "#FFFFFF", fontSize: 13, lineHeight: 20, textAlign: "center", fontFamily: "monospace", }, button: { marginTop: 8, borderRadius: 8, overflow: "hidden", borderWidth: 2, borderColor: "#FFFFFF", }, buttonPressed: { opacity: 0.7, transform: [{ scale: 0.96 }], }, buttonGradient: { backgroundColor: "rgba(255, 255, 255, 0.1)", paddingHorizontal: 24, paddingVertical: 10, alignItems: "center", }, buttonText: { color: "#FFFFFF", fontSize: 14, fontWeight: "900", fontFamily: "monospace", letterSpacing: 1.5, textTransform: "uppercase", }, arrowContainer: { position: "absolute", bottom: ARROW_BOTTOM_OFFSET, left: 0, right: 0, alignItems: "center", }, arrow: { width: 0, height: 0, borderLeftWidth: 12, borderRightWidth: 12, borderTopWidth: ARROW_HEIGHT, borderLeftColor: "transparent", borderRightColor: "transparent", borderTopColor: gameUIColors.info, shadowColor: gameUIColors.info, shadowOffset: { width: 0, height: 0 }, shadowOpacity: 0.8, shadowRadius: 10, }, });