react-native-reanimated#useAnimatedProps TypeScript Examples

The following examples show how to use react-native-reanimated#useAnimatedProps. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: useAnimatedPath.ts    From react-native-wagmi-charts with MIT License 6 votes vote down vote up
export default function useAnimatedPath({
  enabled = true,
  path,
}: {
  enabled?: boolean;
  path: string;
}) {
  const transition = useSharedValue(0);

  const previousPath = usePrevious(path);

  useAnimatedReaction(
    () => {
      return path;
    },
    (_, previous) => {
      if (previous) {
        transition.value = 0;
        transition.value = withTiming(1);
      }
    },
    [path]
  );

  const animatedProps = useAnimatedProps(() => {
    let d = path || '';
    if (previousPath && enabled) {
      const pathInterpolator = interpolatePath(previousPath, path, null);
      d = pathInterpolator(transition.value);
    }
    return {
      d,
    };
  });

  return { animatedProps };
}
Example #2
Source File: AnimatedText.tsx    From react-native-wagmi-charts with MIT License 6 votes vote down vote up
AnimatedText = ({ text, style }: AnimatedTextProps) => {
  const inputRef = React.useRef<any>(null); // eslint-disable-line @typescript-eslint/no-explicit-any

  if (Platform.OS === 'web') {
    // For some reason, the worklet reaction evaluates upfront regardless of any
    // conditionals within it, causing Android to crash upon the invokation of `setNativeProps`.
    // We are going to break the rules of hooks here so it doesn't invoke `useAnimatedReaction`
    // for platforms outside of the web.

    // eslint-disable-next-line react-hooks/rules-of-hooks
    useAnimatedReaction(
      () => {
        return text.value;
      },
      (data, prevData) => {
        if (data !== prevData && inputRef.current) {
          inputRef.current.value = data;
        }
      }
    );
  }
  const animatedProps = useAnimatedProps(() => {
    return {
      text: text.value,
      // Here we use any because the text prop is not available in the type
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
    } as any;
  });
  return (
    <AnimatedTextInput
      underlineColorAndroid="transparent"
      editable={false}
      ref={Platform.select({ web: inputRef })}
      value={text.value}
      style={[styles.text, style]}
      animatedProps={animatedProps}
    />
  );
}
Example #3
Source File: animated-stroke.tsx    From react-native-checkbox-reanimated with MIT License 6 votes vote down vote up
AnimatedStroke = ({ progress, ...pathProps }: AnimatedStrokeProps) => {
  const [length, setLength] = useState(0)
  const ref = useRef<typeof AnimatedPath>(null)
  const animatedProps = useAnimatedProps(() => ({
    strokeDashoffset: Math.max(
      0,
      length - length * Easing.bezierFn(0.37, 0, 0.63, 1)(progress.value) - 0.1
    )
  }))

  return (
    <AnimatedPath
      animatedProps={animatedProps}
      // @ts-ignore
      onLayout={() => setLength(ref.current!.getTotalLength())}
      // @ts-ignore
      ref={ref}
      strokeDasharray={length}
      {...pathProps}
    />
  )
}
Example #4
Source File: ReText.tsx    From jellyfin-audio-player with MIT License 6 votes vote down vote up
ReText = (props: TextProps) => {
    const { text, style } = { style: {}, ...props };
    const defaultStyles = useDefaultStyles();

    const animatedProps = useAnimatedProps(() => {
        return {
            text: text.value,
            // Here we use any because the text prop is not available in the type
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
        } as any;
    });
    
    return (
        <AnimatedTextInput
            underlineColorAndroid="transparent"
            editable={false}
            value={text.value}
            style={[styles.baseStyle, defaultStyles.text, style]}
            {...{ animatedProps }}
        />
    );
}
Example #5
Source File: HorizontalLine.tsx    From react-native-wagmi-charts with MIT License 5 votes vote down vote up
export function LineChartHorizontalLine({
  color = 'gray',
  lineProps = {},
  at = { index: 0 },
  offsetY = 0,
}: HorizontalLineProps) {
  const { width, path, height, gutter } = React.useContext(
    LineChartDimensionsContext
  );
  const { data, yDomain } = useLineChart();

  const parsedPath = React.useMemo(() => parse(path), [path]);
  const pointWidth = React.useMemo(
    () => width / data.length,
    [data.length, width]
  );

  const y = useDerivedValue(() => {
    if (typeof at === 'number' || at.index != null) {
      const index = typeof at === 'number' ? at : at.index;
      const yForX = getYForX(parsedPath!, pointWidth * index) || 0;
      return withTiming(yForX + offsetY);
    }
    /**
     * <gutter>
     * | ---------- | <- yDomain.max  |
     * |            |                 | offsetTop
     * |            | <- value        |
     * |            |
     * |            | <- yDomain.min
     * <gutter>
     */

    const offsetTop = yDomain.max - at.value;
    const percentageOffsetTop = offsetTop / (yDomain.max - yDomain.min);

    const heightBetweenGutters = height - gutter * 2;

    const offsetTopPixels = gutter + percentageOffsetTop * heightBetweenGutters;

    return withTiming(offsetTopPixels + offsetY);
  });

  const lineAnimatedProps = useAnimatedProps(() => ({
    x1: 0,
    x2: width,
    y1: y.value,
    y2: y.value,
  }));

  return (
    <AnimatedLine
      animatedProps={lineAnimatedProps}
      strokeWidth={2}
      stroke={color}
      strokeDasharray="3 3"
      {...lineProps}
    />
  );
}
Example #6
Source File: index.tsx    From react-native-checkbox-reanimated with MIT License 5 votes vote down vote up
AnimatedCheckbox = (props: Props) => {
  const { checked, checkmarkColor, highlightColor, boxOutlineColor } = props

  const progress = useSharedValue(0)

  useEffect(() => {
    progress.value = withTiming(checked ? 1 : 0, {
      duration: checked ? 300 : 100,
      easing: Easing.linear
    })
  }, [checked])

  const animatedBoxProps = useAnimatedProps(
    () => ({
      stroke: interpolateColor(
        Easing.bezierFn(0.16, 1, 0.3, 1)(progress.value),
        [0, 1],
        [boxOutlineColor, highlightColor],
        'RGB'
      ),
      fill: interpolateColor(
        Easing.bezierFn(0.16, 1, 0.3, 1)(progress.value),
        [0, 1],
        ['#00000000', highlightColor],
        'RGB'
      )
    }),
    [highlightColor, boxOutlineColor]
  )

  return (
    <Svg
      viewBox={[-MARGIN, -MARGIN, vWidth + MARGIN, vHeight + MARGIN].join(' ')}
    >
      <Defs>
        <ClipPath id="clipPath">
          <Path
            fill="white"
            stroke="gray"
            strokeLinejoin="round"
            strokeLinecap="round"
            d={outlineBoxPath}
          />
        </ClipPath>
      </Defs>
      <AnimatedStroke
        progress={progress}
        d={checkMarkPath}
        stroke={highlightColor}
        strokeWidth={10}
        strokeLinejoin="round"
        strokeLinecap="round"
        strokeOpacity={checked || false ? 1 : 0}
      />
      <AnimatedPath
        d={outlineBoxPath}
        strokeWidth={7}
        strokeLinejoin="round"
        strokeLinecap="round"
        animatedProps={animatedBoxProps}
      />
      <G clipPath="url(#clipPath)">
        <AnimatedStroke
          progress={progress}
          d={checkMarkPath}
          stroke={checkmarkColor}
          strokeWidth={10}
          strokeLinejoin="round"
          strokeLinecap="round"
          strokeOpacity={checked || false ? 1 : 0}
        />
      </G>
    </Svg>
  )
}
Example #7
Source File: CurvedTabBar.tsx    From curved-bottom-navigation-bar with MIT License 4 votes vote down vote up
CurvedTabBarComponent = (props: TabBarViewProps) => {
  // props
  const {
    routes,
    selectedIndex,
    barWidth,
    duration,
    dotColor,
    tabBarColor,
    titleShown,
    isRtl,
    navigationIndex,
    dotSize: SIZE_DOT,
    barHeight = TAB_BAR_HEIGHT,
  } = props;
  // state
  const {bottom} = useSafeAreaInsets();
  const {width} = useSafeAreaFrame();
  const actualBarWidth = useMemo<number>(
    () => barWidth || width,
    [barWidth, width],
  );
  const widthTab = useMemo(
    () => actualBarWidth / routes.length,
    [routes, actualBarWidth],
  );
  const inputRange = useMemo(
    () =>
      isRtl
        ? routes.map((_: any, index: number) => index).reverse()
        : routes.map((_: any, index: number) => index),
    [isRtl, routes],
  );

  const outputRange = useMemo(
    () =>
      routes.map(
        (_: any, index: number) => (index / routes.length) * actualBarWidth,
      ),
    [routes, actualBarWidth],
  );
  const actualBarHeight = useMemo<number>(
    () => barHeight + bottom,
    [barHeight, bottom],
  );
  const indexAnimated = useDerivedValue(() =>
    sharedTiming(selectedIndex.value, {duration}),
  );

  // func
  const renderButtonTab = useCallback(
    ({key, title, ...configs}: TabRoute, index: number) => {
      return (
        <ButtonTab
          focused={index === selectedIndex.value}
          width={actualBarWidth}
          key={key}
          title={title}
          titleShown={titleShown}
          indexAnimated={indexAnimated}
          countTab={routes.length}
          selectedIndex={selectedIndex}
          index={index}
          {...configs}
        />
      );
    },
    [indexAnimated, routes.length, selectedIndex, titleShown, actualBarWidth],
  );

  // reanimated

  const progress = withSharedTransition(sharedEq(selectedIndex, indexAnimated));

  const xPath = useInterpolate(indexAnimated, inputRange, outputRange);

  // path
  const pathProps = useAnimatedProps<PathProps>(() => {
    const centerHoleX = xPath.value + widthTab / 2;
    return {
      d: `M0,0 L${centerHoleX - SIZE_DOT},0
      C${centerHoleX - SIZE_DOT * 0.5},0 ${
        centerHoleX - SIZE_DOT * 0.75
      },${HEIGHT_HOLE} ${centerHoleX},${HEIGHT_HOLE} 
      C${centerHoleX + SIZE_DOT * 0.75},${HEIGHT_HOLE} ${
        centerHoleX + SIZE_DOT * 0.5
      },0 ${centerHoleX + SIZE_DOT} 0 
      L${actualBarWidth * 2},0 L ${
        actualBarWidth * 2
      },${actualBarHeight} L 0,${actualBarHeight} Z
      `,
    };
  }, [actualBarWidth, widthTab, SIZE_DOT, actualBarHeight]);

  // style
  const containerStyle = useMemo<StyleProp<ViewStyle>>(
    () => [
      {
        height: actualBarHeight,
        width: actualBarWidth,
      },
    ],
    [actualBarHeight, actualBarWidth],
  );
  const rowTab = useMemo<StyleProp<ViewStyle>>(
    () => [
      {
        width: actualBarWidth,
        height: actualBarHeight,
      },
    ],
    [actualBarHeight, actualBarWidth],
  );

  return (
    <>
      <RNShadow style={[styles.container, containerStyle]}>
        <Svg
          width={actualBarWidth}
          height={actualBarHeight}
          style={[styles.svg]}>
          <AnimatedPath
            animatedProps={pathProps}
            translateY={3}
            fill={tabBarColor}
            stroke={'transparent'}
            strokeWidth={0}
          />
        </Svg>
      </RNShadow>
      <View style={[styles.rowTab, rowTab]}>
        <Dot
          navigationIndex={navigationIndex}
          isRtl={isRtl}
          dotColor={dotColor}
          dotSize={SIZE_DOT}
          barHeight={actualBarHeight}
          width={actualBarWidth}
          selectedIndex={indexAnimated}
          routes={routes}
          progress={progress}
        />
        {routes.map(renderButtonTab)}
      </View>
    </>
  );
}
Example #8
Source File: CurvedTabBar.tsx    From curved-bottom-navigation-bar with MIT License 4 votes vote down vote up
CurvedTabBarComponent = (props: TabBarViewProps) => {
  // props
  const {
    routes,
    selectedIndex,
    barWidth,
    duration,
    dotColor,
    tabBarColor,
    titleShown,
    isRtl,
    navigationIndex,
    dotSize: SIZE_DOT,
    barHeight = TAB_BAR_HEIGHT,
  } = props;
  // state
  const { bottom } = useSafeAreaInsets();
  const { width } = useSafeAreaFrame();
  const actualBarWidth = useMemo<number>(
    () => barWidth || width,
    [barWidth, width]
  );
  const widthTab = useMemo(
    () => actualBarWidth / routes.length,
    [routes, actualBarWidth]
  );
  const inputRange = useMemo(
    () =>
      isRtl
        ? routes.map((_: any, index: number) => index).reverse()
        : routes.map((_: any, index: number) => index),
    [isRtl, routes]
  );

  const outputRange = useMemo(
    () =>
      routes.map(
        (_: any, index: number) => (index / routes.length) * actualBarWidth
      ),
    [routes, actualBarWidth]
  );
  const actualBarHeight = useMemo<number>(
    () => barHeight + bottom,
    [barHeight, bottom]
  );
  const indexAnimated = useDerivedValue(() =>
    sharedTiming(selectedIndex.value, { duration })
  );

  // func
  const renderButtonTab = useCallback(
    ({ key, title, ...configs }: TabRoute, index: number) => {
      return (
        <ButtonTab
          focused={index === selectedIndex.value}
          width={actualBarWidth}
          key={key}
          title={title}
          titleShown={titleShown}
          indexAnimated={indexAnimated}
          countTab={routes.length}
          selectedIndex={selectedIndex}
          index={index}
          {...configs}
        />
      );
    },
    [indexAnimated, routes.length, selectedIndex, titleShown, actualBarWidth]
  );

  // reanimated

  const progress = withSharedTransition(sharedEq(selectedIndex, indexAnimated));

  const xPath = useInterpolate(indexAnimated, inputRange, outputRange);

  // path
  const pathProps = useAnimatedProps<PathProps>(() => {
    const centerHoleX = xPath.value + widthTab / 2;
    return {
      d: `M0,0 L${centerHoleX - SIZE_DOT},0
      C${centerHoleX - SIZE_DOT * 0.5},0 ${
        centerHoleX - SIZE_DOT * 0.75
      },${HEIGHT_HOLE} ${centerHoleX},${HEIGHT_HOLE} 
      C${centerHoleX + SIZE_DOT * 0.75},${HEIGHT_HOLE} ${
        centerHoleX + SIZE_DOT * 0.5
      },0 ${centerHoleX + SIZE_DOT} 0 
      L${actualBarWidth * 2},0 L ${
        actualBarWidth * 2
      },${actualBarHeight} L 0,${actualBarHeight} Z
      `,
    };
  }, [actualBarWidth, widthTab, SIZE_DOT, actualBarHeight]);

  // style
  const containerStyle = useMemo<StyleProp<ViewStyle>>(
    () => [
      {
        height: actualBarHeight,
        width: actualBarWidth,
      },
    ],
    [actualBarHeight, actualBarWidth]
  );
  const rowTab = useMemo<StyleProp<ViewStyle>>(
    () => [
      {
        width: actualBarWidth,
        height: actualBarHeight,
      },
    ],
    [actualBarHeight, actualBarWidth]
  );

  return (
    <>
      <RNShadow style={[styles.container, containerStyle]}>
        <Svg
          width={actualBarWidth}
          height={actualBarHeight}
          style={[styles.svg]}
        >
          <AnimatedPath
            animatedProps={pathProps}
            translateY={3}
            fill={tabBarColor}
            stroke={'transparent'}
            strokeWidth={0}
          />
        </Svg>
      </RNShadow>
      <View style={[styles.rowTab, rowTab]}>
        <Dot
          navigationIndex={navigationIndex}
          isRtl={isRtl}
          dotColor={dotColor}
          dotSize={SIZE_DOT}
          barHeight={actualBarHeight}
          width={actualBarWidth}
          selectedIndex={indexAnimated}
          routes={routes}
          progress={progress}
        />
        {routes.map(renderButtonTab)}
      </View>
    </>
  );
}
Example #9
Source File: Candle.tsx    From react-native-wagmi-charts with MIT License 4 votes vote down vote up
CandlestickChartCandle = ({
  candle,
  maxHeight,
  domain,
  margin = 2,
  positiveColor = '#10b981',
  negativeColor = '#ef4444',
  rectProps: overrideRectProps,
  lineProps: overrideLineProps,
  index,
  width,
  useAnimations = true,
  renderLine = (props) =>
    props.useAnimations ? <AnimatedLine {...props} /> : <Line {...props} />,
  renderRect = (props) =>
    props.useAnimations ? <AnimatedRect {...props} /> : <Rect {...props} />,
}: CandlestickChartCandleProps) => {
  const { close, open, high, low } = candle;
  const isPositive = close > open;
  const fill = isPositive ? positiveColor : negativeColor;
  const x = index * width;
  const max = Math.max(open, close);
  const min = Math.min(open, close);

  const lineProps = React.useMemo(
    () => ({
      stroke: fill,
      strokeWidth: 1,
      direction: isPositive ? 'positive' : 'negative',
      x1: x + width / 2,
      y1: getY({ maxHeight, value: low, domain }),
      x2: x + width / 2,
      y2: getY({ maxHeight, value: high, domain }),
      ...overrideLineProps,
    }),
    [
      domain,
      fill,
      high,
      isPositive,
      low,
      maxHeight,
      overrideLineProps,
      width,
      x,
    ]
  );
  const animatedLineProps = useAnimatedProps(() => ({
    x1: withTiming(x + width / 2),
    y1: withTiming(getY({ maxHeight, value: low, domain })),
    x2: withTiming(x + width / 2),
    y2: withTiming(getY({ maxHeight, value: high, domain })),
  }));

  const rectProps = React.useMemo(
    () => ({
      width: width - margin * 2,
      fill: fill,
      direction: isPositive ? 'positive' : 'negative',
      x: x + margin,
      y: getY({ maxHeight, value: max, domain }),
      height: getHeight({ maxHeight, value: max - min, domain }),
      ...overrideRectProps,
    }),
    [
      domain,
      fill,
      isPositive,
      margin,
      max,
      maxHeight,
      min,
      overrideRectProps,
      width,
      x,
    ]
  );
  const animatedRectProps = useAnimatedProps(() => ({
    x: withTiming(x + margin),
    y: withTiming(getY({ maxHeight, value: max, domain })),
    height: withTiming(getHeight({ maxHeight, value: max - min, domain })),
  }));

  return (
    <>
      {renderLine({
        ...lineProps,
        useAnimations,
        ...(useAnimations ? { animatedProps: animatedLineProps } : {}),
      })}
      {renderRect({
        ...rectProps,
        useAnimations,
        ...(useAnimations ? { animatedProps: animatedRectProps } : {}),
      })}
    </>
  );
}
Example #10
Source File: ChartPath.tsx    From react-native-wagmi-charts with MIT License 4 votes vote down vote up
export function LineChartPathWrapper({
  animationDuration = 300,
  animationProps = {},
  children,
  color = 'black',
  inactiveColor,
  width: strokeWidth = 3,
  widthOffset = 20,
  pathProps = {},
  showInactivePath = true,
  animateOnMount,
  mountAnimationDuration = animationDuration,
  mountAnimationProps = animationProps,
}: LineChartPathWrapperProps) {
  const { height, pathWidth, width } = React.useContext(
    LineChartDimensionsContext
  );
  const { currentX, isActive } = useLineChart();
  const isMounted = useSharedValue(false);
  const hasMountedAnimation = useSharedValue(false);

  React.useEffect(() => {
    isMounted.value = true;
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  ////////////////////////////////////////////////

  const svgProps = useAnimatedProps(() => {
    const shouldAnimateOnMount = animateOnMount === 'foreground';
    const inactiveWidth =
      !isMounted.value && shouldAnimateOnMount ? 0 : pathWidth;

    let duration =
      shouldAnimateOnMount && !hasMountedAnimation.value
        ? mountAnimationDuration
        : animationDuration;
    const props =
      shouldAnimateOnMount && !hasMountedAnimation.value
        ? mountAnimationProps
        : animationProps;

    if (isActive.value) {
      duration = 0;
    }

    return {
      width: withTiming(
        isActive.value
          ? // on Web, <svg /> elements don't support negative widths
            // https://github.com/coinjar/react-native-wagmi-charts/issues/24#issuecomment-955789904
            Math.max(currentX.value, 0)
          : inactiveWidth + widthOffset,
        Object.assign({ duration }, props),
        () => {
          hasMountedAnimation.value = true;
        }
      ),
    };
  });

  const viewSize = React.useMemo(() => ({ width, height }), [width, height]);

  ////////////////////////////////////////////////

  let backgroundChildren;
  let foregroundChildren;
  if (children) {
    const iterableChildren = flattenChildren(children);
    backgroundChildren = iterableChildren.filter((child) =>
      // @ts-ignore
      BACKGROUND_COMPONENTS.includes(child?.type?.displayName)
    );
    foregroundChildren = iterableChildren.filter((child) =>
      // @ts-ignore
      FOREGROUND_COMPONENTS.includes(child?.type?.displayName)
    );
  }

  ////////////////////////////////////////////////

  return (
    <>
      <LineChartPathContext.Provider
        value={{
          color,
          isInactive: showInactivePath,
          isTransitionEnabled: pathProps.isTransitionEnabled ?? true,
        }}
      >
        <View style={viewSize}>
          <Svg width={width} height={height}>
            <LineChartPath
              color={color}
              inactiveColor={inactiveColor}
              width={strokeWidth}
              {...pathProps}
            />
            {backgroundChildren}
          </Svg>
        </View>
      </LineChartPathContext.Provider>
      <LineChartPathContext.Provider
        value={{
          color,
          isInactive: false,
          isTransitionEnabled: pathProps.isTransitionEnabled ?? true,
        }}
      >
        <View style={StyleSheet.absoluteFill}>
          <AnimatedSVG animatedProps={svgProps} height={height}>
            <LineChartPath color={color} width={strokeWidth} {...pathProps} />
            {foregroundChildren}
          </AnimatedSVG>
        </View>
      </LineChartPathContext.Provider>
    </>
  );
}
Example #11
Source File: Dot.tsx    From react-native-wagmi-charts with MIT License 4 votes vote down vote up
export function LineChartDot({
  at,
  color: defaultColor = 'black',
  dotProps,
  hasOuterDot: defaultHasOuterDot = false,
  hasPulse = false,
  inactiveColor,
  outerDotProps,
  pulseBehaviour = 'while-inactive',
  pulseDurationMs = 800,
  showInactiveColor = true,
  size = 4,
  outerSize = size * 4,
}: LineChartDotProps) {
  const { data, isActive } = useLineChart();
  const { path, pathWidth: width } = React.useContext(
    LineChartDimensionsContext
  );

  ////////////////////////////////////////////////////////////

  const { isInactive: _isInactive } = React.useContext(LineChartPathContext);
  const isInactive = showInactiveColor && _isInactive;
  const color = isInactive ? inactiveColor || defaultColor : defaultColor;
  const opacity = isInactive && !inactiveColor ? 0.5 : 1;
  const hasOuterDot = defaultHasOuterDot || hasPulse;

  ////////////////////////////////////////////////////////////

  const parsedPath = React.useMemo(() => parse(path), [path]);

  ////////////////////////////////////////////////////////////

  const pointWidth = React.useMemo(
    () => width / (data.length - 1),
    [data.length, width]
  );

  ////////////////////////////////////////////////////////////

  const x = useDerivedValue(() => withTiming(pointWidth * at));
  const y = useDerivedValue(() =>
    withTiming(getYForX(parsedPath!, x.value) || 0)
  );

  ////////////////////////////////////////////////////////////

  const animatedDotProps = useAnimatedProps(() => ({
    cx: x.value,
    cy: y.value,
  }));

  const animatedOuterDotProps = useAnimatedProps(() => {
    let defaultProps = {
      cx: x.value,
      cy: y.value,
      opacity: 0.1,
      r: outerSize,
    };

    if (!hasPulse) {
      return defaultProps;
    }

    if (isActive.value && pulseBehaviour === 'while-inactive') {
      return {
        ...defaultProps,
        r: 0,
      };
    }

    const easing = Easing.out(Easing.sin);
    const animatedOpacity = withRepeat(
      withSequence(
        withTiming(0.8),
        withTiming(0, {
          duration: pulseDurationMs,
          easing,
        })
      ),
      -1,
      false
    );
    const scale = withRepeat(
      withSequence(
        withTiming(0),
        withTiming(outerSize, {
          duration: pulseDurationMs,
          easing,
        })
      ),
      -1,
      false
    );

    if (pulseBehaviour === 'while-inactive') {
      return {
        ...defaultProps,
        opacity: isActive.value ? withTiming(0) : animatedOpacity,
        r: isActive.value ? withTiming(0) : scale,
      };
    }
    return {
      ...defaultProps,
      opacity: animatedOpacity,
      r: scale,
    };
  }, [outerSize]);

  ////////////////////////////////////////////////////////////

  return (
    <>
      <AnimatedCircle
        animatedProps={animatedDotProps}
        r={size}
        fill={color}
        opacity={opacity}
        {...dotProps}
      />
      {hasOuterDot && (
        <AnimatedCircle
          animatedProps={animatedOuterDotProps}
          fill={color}
          {...outerDotProps}
        />
      )}
    </>
  );
}