mattermost-mobile/app/components/option_box/animated.tsx

161 lines
5.3 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback, useEffect, useState} from 'react';
import {Pressable, StyleSheet, Text, View} from 'react-native';
import Animated, {Easing, interpolate, interpolateColor, runOnJS, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import CompassIcon from '@components/compass_icon';
import {useTheme} from '@context/theme';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
import {OPTIONS_HEIGHT} from '.';
type OptionBoxProps = {
animatedBackgroundColor: string;
animatedColor: string;
animatedIconName: string;
animatedText: string;
iconName: string;
onAnimationEnd?: () => void;
onPress: () => void;
testID?: string;
text: string;
}
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
container: {
borderRadius: 4,
flex: 1,
maxHeight: OPTIONS_HEIGHT,
minWidth: 60,
},
background: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.04),
},
center: {
alignItems: 'center',
justifyContent: 'center',
},
text: {
color: changeOpacity(theme.centerChannelColor, 0.56),
paddingHorizontal: 5,
textTransform: 'capitalize',
...typography('Body', 50, 'SemiBold'),
},
}));
const AnimatedOptionBox = ({
animatedBackgroundColor, animatedColor, animatedIconName, animatedText,
iconName, onAnimationEnd, onPress, testID, text,
}: OptionBoxProps) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
const [activated, setActivated] = useState(false);
const animate = useSharedValue(0);
const handleOnPress = useCallback(() => {
animate.value = withTiming(1, {duration: 150, easing: Easing.out(Easing.linear)});
setActivated(true);
onPress();
}, [onPress]);
const backgroundStyle = useAnimatedStyle(() => ({
backgroundColor: interpolateColor(
animate.value,
[0, 1],
['transparent', animatedBackgroundColor],
),
}));
const optionStyle = useAnimatedStyle(() => ({
opacity: interpolate(
animate.value,
[0, 1],
[1, 0],
),
}));
const scaleStyle = useAnimatedStyle(() => ({
transform: [{
scale: interpolate(
animate.value,
[0, 1],
[0.25, 1],
),
}],
opacity: animate.value,
}), [activated]);
useEffect(() => {
let t: NodeJS.Timeout|undefined;
const callback = () => {
'worklet';
if (onAnimationEnd) {
runOnJS(onAnimationEnd)();
}
};
if (activated) {
t = setTimeout(() => {
setActivated(false);
animate.value = withTiming(0, {duration: 150, easing: Easing.out(Easing.linear)}, callback);
}, 1200);
}
return () => {
if (t) {
clearTimeout(t);
}
};
}, [activated, animate.value, onAnimationEnd]);
return (
<AnimatedPressable
onPress={handleOnPress}
disabled={activated}
style={styles.container}
testID={testID}
>
{({pressed}) => (
<View style={[styles.container, styles.background, pressed && {backgroundColor: changeOpacity(theme.buttonBg, 0.16)}]}>
<Animated.View style={[styles.container, backgroundStyle]}>
<Animated.View style={[StyleSheet.absoluteFill, styles.center, optionStyle]}>
<CompassIcon
color={pressed ? theme.buttonBg : changeOpacity(theme.centerChannelColor, 0.56)}
name={iconName}
size={24}
/>
<Text
numberOfLines={1}
style={[styles.text, {color: pressed ? theme.buttonBg : changeOpacity(theme.centerChannelColor, 0.56)}]}
testID={`${testID}.label`}
>
{text}
</Text>
</Animated.View>
<Animated.View style={[StyleSheet.absoluteFill, styles.center, scaleStyle]}>
<CompassIcon
color={animatedColor}
name={animatedIconName}
size={24}
/>
<Text
numberOfLines={1}
style={[styles.text, {color: animatedColor}]}
testID={`${testID}.animated`}
>
{animatedText}
</Text>
</Animated.View>
</Animated.View>
</View>
)}
</AnimatedPressable>
);
};
export default AnimatedOptionBox;