Skip to content

Commit ec8068e

Browse files
committed
feat(pressable-feedback): adjust ripple animation hook
1 parent 85df65b commit ec8068e

File tree

3 files changed

+87
-42
lines changed

3 files changed

+87
-42
lines changed

example/src/app/(home)/components/pressable-feedback.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ const DefaultContent = () => {
1414
<View className="flex-row items-center justify-center gap-4">
1515
<PressableFeedback
1616
className="bg-surface rounded-2xl h-[150px] w-full items-center justify-center"
17-
animation={false}
18-
>
19-
<StyledIonicons
20-
name="checkmark"
21-
size={32}
22-
className="text-surface-foreground"
23-
/>
24-
</PressableFeedback>
17+
variant="ripple"
18+
animation={{
19+
ripple: {
20+
backgroundColor: {
21+
value: 'pink',
22+
},
23+
},
24+
}}
25+
/>
2526
{/* <PressableFeedback className="bg-accent rounded-2xl h-24 w-24 items-center justify-center">
2627
<StyledIonicons
2728
name="heart"

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

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type {
1818
PressableFeedbackAnimation,
1919
PressableFeedbackAnimationContextValue,
2020
PressableFeedbackHighlightRootAnimation,
21+
PressableFeedbackRippleRootAnimation,
2122
PressableFeedbackVariant,
2223
} from './pressable-feedback.types';
2324

@@ -84,7 +85,7 @@ export function usePressableFeedbackRootAnimation(options: {
8485
isPressed.set(false);
8586
scale.set(withTiming(0, scaleTimingConfig));
8687

87-
if (variant === 'ripple') return;
88+
if (variant === 'highlight') return;
8889

8990
rippleProgress.set(withTiming(2, { duration: 400 }));
9091
});
@@ -198,7 +199,13 @@ export function usePressableFeedbackHighlightAnimation(options: {
198199
* Animation hook for PressableFeedback ripple effect
199200
* Handles ripple circle animation with radial gradient
200201
*/
201-
export function usePressableFeedbackRippleAnimation() {
202+
export function usePressableFeedbackRippleAnimation(options: {
203+
animation: PressableFeedbackRippleRootAnimation | undefined;
204+
}) {
205+
const { animation } = options;
206+
207+
const { theme } = useUniwind();
208+
202209
const {
203210
pressedCenterX,
204211
pressedCenterY,
@@ -207,32 +214,77 @@ export function usePressableFeedbackRippleAnimation() {
207214
rippleProgress,
208215
} = usePressableFeedbackAnimation();
209216

217+
const { animationConfig, isAnimationDisabled } = getAnimationState(animation);
218+
219+
// Background color
220+
const defaultColor = theme === 'dark' ? 'white' : 'black';
221+
222+
const backgroundColor = getAnimationValueProperty({
223+
animationValue: animationConfig?.ripple?.backgroundColor,
224+
property: 'value',
225+
defaultValue: defaultColor,
226+
});
227+
228+
// Opacity animation
229+
const opacityValue = getAnimationValueProperty({
230+
animationValue: animationConfig?.ripple?.opacity,
231+
property: 'value',
232+
defaultValue: [0, 1, 0] as [number, number, number],
233+
});
234+
235+
const opacityTimingConfig = getAnimationValueMergedConfig({
236+
animationValue: animationConfig?.ripple?.opacity,
237+
property: 'timingConfig',
238+
defaultValue: { duration: 30 },
239+
});
240+
241+
// Scale animation
242+
const scaleValue = getAnimationValueProperty({
243+
animationValue: animationConfig?.ripple?.scale,
244+
property: 'value',
245+
defaultValue: [0, 1, 1] as [number, number, number],
246+
});
247+
248+
const scaleTimingConfig = getAnimationValueMergedConfig({
249+
animationValue: animationConfig?.ripple?.scale,
250+
property: 'timingConfig',
251+
defaultValue: { duration: 30 },
252+
});
253+
210254
const rContainerStyle = useAnimatedStyle(() => {
211255
const circleRadius =
212256
Math.sqrt(containerWidth.get() ** 2 + containerHeight.get() ** 2) * 1.25;
213257

214258
const translateX = pressedCenterX.get() - circleRadius;
215259
const translateY = pressedCenterY.get() - circleRadius;
216260

261+
if (isAnimationDisabled) {
262+
return {};
263+
}
264+
217265
return {
218266
width: circleRadius * 2,
219267
height: circleRadius * 2,
220268
borderRadius: circleRadius,
221269
opacity: withTiming(
222-
interpolate(rippleProgress.get(), [0, 1, 2], [0, 1, 0]),
223-
{ duration: 30 }
270+
interpolate(
271+
rippleProgress.get(),
272+
[0, 1, 2],
273+
[opacityValue[0], opacityValue[1], opacityValue[2]]
274+
),
275+
opacityTimingConfig
224276
),
225277
transform: [
226-
{
227-
translateX,
228-
},
229-
{
230-
translateY,
231-
},
278+
{ translateX },
279+
{ translateY },
232280
{
233281
scale: withTiming(
234-
interpolate(rippleProgress.get(), [0, 1, 2], [0, 1, 1]),
235-
{ duration: 30 }
282+
interpolate(
283+
rippleProgress.get(),
284+
[0, 1, 2],
285+
[scaleValue[0], scaleValue[1], scaleValue[2]]
286+
),
287+
scaleTimingConfig
236288
),
237289
},
238290
],
@@ -241,5 +293,6 @@ export function usePressableFeedbackRippleAnimation() {
241293

242294
return {
243295
rContainerStyle,
296+
backgroundColor,
244297
};
245298
}

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

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import Animated from 'react-native-reanimated';
1010
import Svg, { Defs, RadialGradient, Rect, Stop } from 'react-native-svg';
1111

1212
import { GestureDetector } from 'react-native-gesture-handler';
13-
import { useThemeColor } from '../../helpers/theme';
1413
import type { PressableRef } from '../../helpers/types';
1514
import {
1615
PressableFeedbackAnimationProvider,
@@ -23,7 +22,7 @@ import pressableFeedbackStyles from './pressable-feedback.styles';
2322
import type {
2423
PressableFeedbackHighlightRootAnimation,
2524
PressableFeedbackProps,
26-
PressableFeedbackRippleAnimation,
25+
PressableFeedbackRippleRootAnimation,
2726
} from './pressable-feedback.types';
2827

2928
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
@@ -105,6 +104,11 @@ const PressableFeedback = forwardRef<PressableRef, PressableFeedbackProps>(
105104
animation={animation as PressableFeedbackHighlightRootAnimation}
106105
/>
107106
)}
107+
{variant === 'ripple' && (
108+
<PressableFeedbackRipple
109+
animation={animation as PressableFeedbackRippleRootAnimation}
110+
/>
111+
)}
108112
{children}
109113
</AnimatedPressable>
110114
</GestureDetector>
@@ -133,11 +137,10 @@ const PressableFeedbackHighlight: FC<{
133137
// --------------------------------------------------
134138

135139
const PressableFeedbackRipple: FC<{
136-
animation: PressableFeedbackRippleAnimation;
137-
}> = () => {
138-
const { rContainerStyle } = usePressableFeedbackRippleAnimation();
139-
140-
const themeColorSurfaceSecondary = useThemeColor('on-surface-hover');
140+
animation: PressableFeedbackRippleRootAnimation | undefined;
141+
}> = ({ animation }) => {
142+
const { rContainerStyle, backgroundColor } =
143+
usePressableFeedbackRippleAnimation({ animation });
141144

142145
return (
143146
<Animated.View
@@ -148,21 +151,9 @@ const PressableFeedbackRipple: FC<{
148151
<Svg width="100%" height="100%">
149152
<Defs>
150153
<RadialGradient id="rippleGradient" cx="50%" cy="50%" r="50%">
151-
<Stop
152-
offset="0%"
153-
stopOpacity="1"
154-
stopColor={themeColorSurfaceSecondary}
155-
/>
156-
<Stop
157-
offset="80%"
158-
stopOpacity="0.8"
159-
stopColor={themeColorSurfaceSecondary}
160-
/>
161-
<Stop
162-
offset="100%"
163-
stopOpacity="0"
164-
stopColor={themeColorSurfaceSecondary}
165-
/>
154+
<Stop offset="0%" stopOpacity="1" stopColor={backgroundColor} />
155+
<Stop offset="80%" stopOpacity="0.8" stopColor={backgroundColor} />
156+
<Stop offset="100%" stopOpacity="0" stopColor={backgroundColor} />
166157
</RadialGradient>
167158
</Defs>
168159
<Rect

0 commit comments

Comments
 (0)