feat(hooks): usePulseAnimation for live indicators

This commit is contained in:
fightrate-bot committed 2026-06-16 01:46:23 -03:00
1 parent ea70ecbaa4
commit e8f828d90a
2 files changed
+93

No files matched your search

+49
View File
@@ -0,0 +1,49 @@
import React from 'react';
import TestRenderer from 'react-test-renderer';
jest.mock('react-native-reanimated', () => {
return {
Easing: { out: () => (() => 0), inOut: () => (() => 0), quad: () => 0 },
useSharedValue: (initial: number) => ({ value: initial }),
useAnimatedStyle: (fn: () => unknown) => fn(),
withTiming: (to: number) => to,
withRepeat: (to: number) => to,
cancelAnimation: () => undefined,
};
});
import { usePulseAnimation } from '../hooks/usePulseAnimation';
function Probe() {
const style = usePulseAnimation(false) as any;
return null;
}
describe('usePulseAnimation', () => {
it('returns initial opacity 0.5 and scale 1 when inactive', () => {
let captured: any = null;
function Capture() {
const style = usePulseAnimation(false) as any;
captured = style;
return null;
}
TestRenderer.act(() => {
TestRenderer.create(React.createElement(Capture));
});
expect(captured.transform[0].scale).toBe(1);
expect(captured.opacity).toBe(0.5);
});
it('returns opacity 1 when active', () => {
let captured: any = null;
function Capture() {
const style = usePulseAnimation(true) as any;
captured = style;
return null;
}
TestRenderer.act(() => {
TestRenderer.create(React.createElement(Capture));
});
expect(captured.opacity).toBe(1);
});
});
+44
View File
@@ -0,0 +1,44 @@
import { useEffect } from 'react';
import {
Easing,
cancelAnimation,
useAnimatedStyle,
useSharedValue,
withRepeat,
withTiming,
} from 'react-native-reanimated';
type AnimatedStyle = {
transform: Array<{ scale: number }>;
opacity: number;
};
export function usePulseAnimation(active: boolean, durationMs = 1600): AnimatedStyle {
const scale = useSharedValue(1);
const opacity = useSharedValue(active ? 1 : 0.5);
useEffect(() => {
if (active) {
scale.value = withRepeat(
withTiming(1.4, { duration: durationMs / 2, easing: Easing.out(Easing.quad) }),
-1,
true,
);
opacity.value = withRepeat(
withTiming(0.55, { duration: durationMs / 2, easing: Easing.inOut(Easing.quad) }),
-1,
true,
);
} else {
cancelAnimation(scale);
cancelAnimation(opacity);
scale.value = withTiming(1, { duration: 160 });
opacity.value = withTiming(0.5, { duration: 160 });
}
}, [active, durationMs, scale, opacity]);
return useAnimatedStyle<AnimatedStyle>(() => ({
transform: [{ scale: scale.value }],
opacity: opacity.value,
}));
}