feat(components): PressableSurface with refined press feedback

This commit is contained in:
fightrate-bot committed 2026-06-16 01:47:37 -03:00
1 parent 9b02c10926
commit fc42e1da43
2 files changed
+113

No files matched your search

+67
View File
@@ -0,0 +1,67 @@
import React, { useCallback, type ReactNode } from 'react';
import { Pressable, StyleSheet, type StyleProp, type ViewStyle } from 'react-native';
import Animated, {
Easing,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated';
type Props = {
readonly children: ReactNode;
readonly onPress?: () => void;
readonly disabled?: boolean;
readonly style?: StyleProp<ViewStyle>;
readonly accessibilityLabel?: string;
readonly testID?: string;
};
export function PressableSurface({
children,
onPress,
disabled,
style,
accessibilityLabel,
testID,
}: Props) {
const scale = useSharedValue(1);
const opacity = useSharedValue(1);
const onPressIn = useCallback(() => {
scale.value = withTiming(0.96, { duration: 80, easing: Easing.out(Easing.quad) });
opacity.value = withTiming(0.82, { duration: 80, easing: Easing.out(Easing.quad) });
}, [scale, opacity]);
const onPressOut = useCallback(() => {
scale.value = withTiming(1, { duration: 160, easing: Easing.out(Easing.quad) });
opacity.value = withTiming(1, { duration: 160, easing: Easing.out(Easing.quad) });
}, [scale, opacity]);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
opacity: opacity.value,
}));
return (
<Animated.View style={animatedStyle}>
<Pressable
accessibilityLabel={accessibilityLabel}
accessibilityRole={accessibilityLabel ? 'button' : undefined}
disabled={disabled}
onPress={onPress}
onPressIn={onPressIn}
onPressOut={onPressOut}
style={[styles.base, style]}
testID={testID}
>
{children}
</Pressable>
</Animated.View>
);
}
const styles = StyleSheet.create({
base: {
alignSelf: 'stretch',
},
});