All files / apps/chatbot/src/components/Indicators BouncingDots.tsx

100% Statements 15/15
50% Branches 1/2
100% Functions 6/6
100% Lines 13/13

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72                            2x   2x 3x 3x   3x 3x                           3x       3x       2x 1x 1x     3x           4x                                  
import React, { useEffect } from "react";
import Animated, {
  Easing,
  useAnimatedStyle,
  useSharedValue,
  withDelay,
  withRepeat,
  withSequence,
  withTiming,
} from "react-native-reanimated";
import { View } from "react-native";
 
import { makeStyles } from "@repo/ui/themes/makeStyles";
 
const DOT_DELAYS_MS = [0, 150, 300];
 
const Dot = ({ delay }: { delay: number }) => {
  const styles = useStyles();
  const translateY = useSharedValue(0);
 
  useEffect(() => {
    translateY.value = withDelay(
      delay,
      withRepeat(
        withSequence(
          withTiming(-5, { duration: 250, easing: Easing.inOut(Easing.ease) }),
          withTiming(0, { duration: 250, easing: Easing.inOut(Easing.ease) }),
          withTiming(0, { duration: 300 }),
        ),
        -1,
        false,
      ),
    );
  }, [delay, translateY]);
 
  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ translateY: translateY.value }],
  }));
 
  return <Animated.View style={[styles.bouncingDot, animatedStyle]} />;
};
 
/** Three dots bouncing in sequence, shown while a text block is still streaming. */
export const BouncingDots = () => {
  const styles = useStyles();
  return (
    <View style={styles.bouncingDotsContainer}>
      {DOT_DELAYS_MS.map((delay) => (
        <Dot key={delay} delay={delay} />
      ))}
    </View>
  );
};
 
const useStyles = makeStyles((theme) => ({
  bouncingDot: {
    width: 4,
    height: 4,
    borderRadius: 2,
    backgroundColor: theme.colors.text.secondary || "#666",
  },
  bouncingDotsContainer: {
    flexDirection: "row",
    alignItems: "center",
    gap: 4,
    paddingTop: 8,
    paddingBottom: 2,
    paddingHorizontal: 2,
    alignSelf: "flex-start",
  },
}));