React Native Developer

Wednesday, March 24, 2021

Ripple effect in React Native

RIPPLE.TSX      import React from 'react';
import {Alert, Dimensions, StyleSheet, Text, View} from 'react-native';
import {Feather as Icon} from '@expo/vector-icons';

import RippleButton from './RippleButton';
import {StyleGuide} from '../components';

const {width} = Dimensions.get('window');
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#ecf0f1',
padding: 8,
},
touchable: {
marginVertical: 16,
width: width - 63,
height: 45,
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
},
text: {
alignSelf: 'center',
fontSize: 24,
fontWeight: '400',
marginLeft: 16,
},
});

const App = () => {
return (
<View style={styles.container}>
<RippleButton color={StyleGuide.palette.primary}>
<View style={styles.touchable}>
<Icon name="trash" color="white" size={24} />
<Text style={[styles.text, {color: 'white'}]}>Delete</Text>
</View>
</RippleButton>
<RippleButton color="#DC034E">
<View style={styles.touchable}>
<Icon name="send" color="white" size={24} />
<Text style={[styles.text, {color: 'white'}]}>Send</Text>
</View>
</RippleButton>
<RippleButton color="#E0E0E0">
<View style={styles.touchable}>
<Icon name="share" size={24} />
<Text style={styles.text}>Upload</Text>
</View>
</RippleButton>
</View>
);
};

export default App;

RippleButton.tsx

import React, {Children, ReactElement, ReactNode, useState} from 'react';
import {State, TapGestureHandler} from 'react-native-gesture-handler';
import Animated, {
and,
call,
cond,
diff,
eq,
greaterOrEq,
greaterThan,
neq,
onChange,
or,
useCode,
} from 'react-native-reanimated';
import {StyleSheet, View, ViewProps} from 'react-native';
import {
mix,
translate,
useDebug,
useTapGestureHandler,
vec,
withTransition,
} from 'react-native-redash';
import Color from 'color';

interface RippleButtonProps {
children: ReactElement<ViewProps>;
color: string;
onPress?: () => void;
}

const RippleButton = ({children, color, onPress}: RippleButtonProps) => {
const [radius, setRadius] = useState(-1);
const {gestureHandler, position, state} = useTapGestureHandler();
const child = Children.only(children);
const progress = withTransition(eq(state, State.BEGAN));
const isGoingUp = or(greaterThan(diff(progress), 0), eq(progress, 1));
const scale = mix(progress, 0.001, 1);
const opacity = isGoingUp;
const backgroundColor = Color(color).lighten(0.1);
useCode(
() => [
onChange(
state,
cond(eq(state, State.END), [call([], onPress || (() => null))]),
),
],
[onPress],
);
return (
<TapGestureHandler {...gestureHandler}>
<Animated.View {...child.props} style={[child.props.style]}>
<View
style={{
...StyleSheet.absoluteFillObject,
borderRadius: 14,
backgroundColor: color,
overflow: 'hidden',
}}
onLayout={({
nativeEvent: {
layout: {height, width},
},
}) => setRadius(Math.sqrt(width ** 2 + height ** 2))}>
{radius !== -1 && (
<Animated.View
style={{
opacity,
backgroundColor,
borderRadius: radius,
width: radius * 2,
height: radius * 2,
transform: [
...translate(vec.create(-radius)),
...translate(position),
{scale},
],
}}
/>
)}
</View>
{child.props.children}
</Animated.View>
</TapGestureHandler>
);
};

export default RippleButton;

 

No comments:

Post a Comment