Skip to content

Commit 2b379e2

Browse files
committed
feat(pressable-feedback): add usePressableFeedbackRippleAnimation hook
1 parent 16fc67a commit 2b379e2

File tree

2 files changed

+63
-50
lines changed

2 files changed

+63
-50
lines changed

src/components/pressable-feedback/pressable-feedback.animation.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Gesture } from 'react-native-gesture-handler';
22
import {
33
Easing,
4+
interpolate,
45
useAnimatedStyle,
56
useSharedValue,
67
withTiming,
@@ -45,7 +46,9 @@ export function usePressableFeedbackRootAnimation() {
4546
pressedCenterX.set(event.x);
4647
pressedCenterY.set(event.y);
4748
isPressed.set(true);
48-
rippleProgress.set(withTiming(1, { duration: 200 }));
49+
if (rippleProgress.get() === 0) {
50+
rippleProgress.set(withTiming(1, { duration: 250 }));
51+
}
4952
})
5053
.onFinalize(() => {
5154
isPressed.set(false);
@@ -128,3 +131,55 @@ export function usePressableFeedbackHighlightAnimation(options: {
128131
isPressed,
129132
};
130133
}
134+
135+
// --------------------------------------------------
136+
137+
/**
138+
* Animation hook for PressableFeedback ripple effect
139+
* Handles ripple circle animation with radial gradient
140+
*/
141+
export function usePressableFeedbackRippleAnimation() {
142+
const {
143+
pressedCenterX,
144+
pressedCenterY,
145+
containerWidth,
146+
containerHeight,
147+
rippleProgress,
148+
} = usePressableFeedbackAnimation();
149+
150+
const rContainerStyle = useAnimatedStyle(() => {
151+
const circleRadius =
152+
Math.sqrt(containerWidth.get() ** 2 + containerHeight.get() ** 2) * 1.25;
153+
154+
const translateX = pressedCenterX.get() - circleRadius;
155+
const translateY = pressedCenterY.get() - circleRadius;
156+
157+
return {
158+
width: circleRadius * 2,
159+
height: circleRadius * 2,
160+
borderRadius: circleRadius,
161+
opacity: withTiming(
162+
interpolate(rippleProgress.get(), [0, 1, 2], [0, 1, 0]),
163+
{ duration: 40 }
164+
),
165+
transform: [
166+
{
167+
translateX,
168+
},
169+
{
170+
translateY,
171+
},
172+
{
173+
scale: withTiming(
174+
interpolate(rippleProgress.get(), [0, 1, 2], [0, 1, 1]),
175+
{ duration: 40 }
176+
),
177+
},
178+
],
179+
};
180+
});
181+
182+
return {
183+
rContainerStyle,
184+
};
185+
}

src/components/pressable-feedback/pressable-feedback.tsx

Lines changed: 7 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
import { forwardRef, useCallback, useMemo, type FC } from 'react';
22
import { Pressable, StyleSheet, type LayoutChangeEvent } from 'react-native';
33

4-
import Animated, {
5-
interpolate,
6-
useAnimatedStyle,
7-
withTiming,
8-
} from 'react-native-reanimated';
4+
import Animated from 'react-native-reanimated';
95
import Svg, { Defs, RadialGradient, Rect, Stop } from 'react-native-svg';
106

117
import { GestureDetector } from 'react-native-gesture-handler';
128
import { useThemeColor } from '../../helpers/theme';
139
import type { PressableRef } from '../../helpers/types';
1410
import {
1511
PressableFeedbackAnimationProvider,
16-
usePressableFeedbackAnimation,
1712
usePressableFeedbackHighlightAnimation,
13+
usePressableFeedbackRippleAnimation,
1814
usePressableFeedbackRootAnimation,
1915
} from './pressable-feedback.animation';
2016
import { DISPLAY_NAME } from './pressable-feedback.constants';
@@ -116,48 +112,10 @@ const PressableFeedbackHighlight: FC<{
116112
// --------------------------------------------------
117113

118114
const PressableFeedbackRipple: FC<{}> = () => {
119-
const {
120-
pressedCenterX,
121-
pressedCenterY,
122-
containerWidth,
123-
containerHeight,
124-
rippleProgress,
125-
} = usePressableFeedbackAnimation();
115+
const { rContainerStyle } = usePressableFeedbackRippleAnimation();
126116

127117
const themeColorSurfaceSecondary = useThemeColor('on-surface-hover');
128118

129-
const rContainerStyle = useAnimatedStyle(() => {
130-
const circleRadius =
131-
Math.sqrt(containerWidth.get() ** 2 + containerHeight.get() ** 2) * 1.25;
132-
133-
const translateX = pressedCenterX.get() - circleRadius;
134-
const translateY = pressedCenterY.get() - circleRadius;
135-
136-
return {
137-
width: circleRadius * 2,
138-
height: circleRadius * 2,
139-
borderRadius: circleRadius,
140-
opacity: withTiming(
141-
interpolate(rippleProgress.get(), [0, 1, 2], [0, 1, 0]),
142-
{ duration: 40 }
143-
),
144-
transform: [
145-
{
146-
translateX,
147-
},
148-
{
149-
translateY,
150-
},
151-
{
152-
scale: withTiming(
153-
interpolate(rippleProgress.get(), [0, 1, 2], [0, 1, 1]),
154-
{ duration: 40 }
155-
),
156-
},
157-
],
158-
};
159-
});
160-
161119
return (
162120
<Animated.View
163121
pointerEvents="none"
@@ -169,18 +127,18 @@ const PressableFeedbackRipple: FC<{}> = () => {
169127
<RadialGradient id="rippleGradient" cx="50%" cy="50%" r="50%">
170128
<Stop
171129
offset="0%"
172-
stopColor={themeColorSurfaceSecondary}
173130
stopOpacity="1"
131+
stopColor={themeColorSurfaceSecondary}
174132
/>
175133
<Stop
176-
offset="75%"
134+
offset="80%"
135+
stopOpacity="0.8"
177136
stopColor={themeColorSurfaceSecondary}
178-
stopOpacity="0.75"
179137
/>
180138
<Stop
181139
offset="100%"
182-
stopColor={themeColorSurfaceSecondary}
183140
stopOpacity="0"
141+
stopColor={themeColorSurfaceSecondary}
184142
/>
185143
</RadialGradient>
186144
</Defs>

0 commit comments

Comments
 (0)