UNPKG

react-native-floating-action-circle-button

Version:

Simple floating action button component with circle button for react native

229 lines (204 loc) 7.7 kB
import React, { useRef, useState } from 'react'; import { View, StyleSheet, Animated, StyleProp, ViewStyle, Pressable, Dimensions, ImageSourcePropType, ImageStyle } from 'react-native'; import ActionButton from './ActionButton'; import Assets from '../assets/Assets'; /** * Place it inside the biggest component of screens for floating. You must pass at less 1 action to actions property * @author hieu * @typedef Props * @property {Object[]} actions: a list of actions for floating action button * @property {Boolean} actions[].disable: actions status (active/disable - default: false) * @property {()=>void=} actions[].onPress: event when actions is pressed * @property {String} actions[].label: label for action * @property {ImageSourcePropType} actions[].icon: custom Icon for CircleButton * @property {StyleProp<ImageStyle>} actions[].iconStyle: custom style for button's icon * @property {StyleProp<ViewStyle>} actions[].buttonStyle: custom style for button * @property {StyleProp<ViewStyle>} containerStyle: custom style for <View/> that wrap floating button * @property {StyleProp<ViewStyle>} actionButtonStyle: custom style for action button * @property {StyleProp<ViewStyle>} backgroundStyle: custom style for background * @property {StyleProp<ViewStyle>} anchorButtonStyle: custom style for anchor button * @property {ImageSourcePropType} anchorInactiveIcon: custom icon for anchor button * @property {ImageSourcePropType} anchorActiveIcon: custom icon for anchor button * @property {StyleProp<ImageStyle>} anchorIconStyle: custom style for icon inside anchor button * @param {Props} props * @returns {JSX.Element} */ const fullScreenHeight = Dimensions.get('screen').height; const fullScreenWidth = Dimensions.get('screen').width; const FloatingActionButton = ({ actions, containerStyle, actionButtonStyle, backgroundStyle, anchorButtonStyle, anchorActiveIcon, anchorInactiveIcon, anchorIconStyle }) => { const activeAnchor = anchorActiveIcon ?? Assets.Icons.cancelFloating; const inactiveAnchor = anchorInactiveIcon ?? Assets.Icons.threeDots; const animation = useRef(new Animated.Value(0)).current; const [floatingCheck, setFloatingCheck] = useState(false); const [open, setOpen] = useState(0); const toggleMenu = () => { const toValue = open ? 0 : 1; Animated.timing(animation, { toValue, duration: 500, // friction: 5, useNativeDriver: true, }).start(); setFloatingCheck(!floatingCheck); setOpen(!open); }; const labelPositionInterpolate = animation.interpolate({ inputRange: [0, 1], outputRange: [-30, -13], }); const opacityInterpolate = animation.interpolate({ inputRange: [0, 0.8, 1], outputRange: [0, 0, 1], }); const labelStyle = { opacity: opacityInterpolate, transform: [ { translateX: labelPositionInterpolate, }, ], }; const scaleInterPolate = animation.interpolate({ inputRange: [0, 1], outputRange: [0, 100], }); const bgStyle = { transform: [ { scale: scaleInterPolate, }, ], }; const AnimatedTouchable = Animated.createAnimatedComponent(Pressable); return ( <View style={[ styles.container, containerStyle, { width: open == 1 ? fullScreenWidth : 56, height: open == 1 ? fullScreenHeight : 56, borderRadius: open == 1 ? 0 : 28 }, ]} > <AnimatedTouchable style={[styles.background, bgStyle, backgroundStyle]} onPress={toggleMenu} /> {actions.map((item) => { const itemStyle = { transform: [ { scale: animation }, { translateY: animation.interpolate({ inputRange: [0, 1], outputRange: [0, -62 * (actions.indexOf(item) + 1)], }), }, ], }; const inactiveStyle = { opacity: animation.interpolate({ inputRange: [0, 0.8, 1], outputRange: [0, 0, 0.5], }), transform: [ { translateY: animation.interpolate({ inputRange: [0, 1], outputRange: [0, -62 * (actions.indexOf(item) + 1)], }), }, ], }; return ( <Animated.View key={actions.indexOf(item)} pointerEvents="box-none"> <Animated.View style={[styles.button, itemStyle, actionButtonStyle]}> <Animated.Text style={[ { ...styles.label, color: item.disable ? Assets.Colors.gray : Assets.Colors.pureWhite, }, labelStyle, ]} > {item.label} </Animated.Text> <ActionButton icon={item.icon} iconStyle={item.iconStyle} buttonStyle={item.buttonStyle} onPress={item.onPress} /> </Animated.View> {item.disable && ( <Animated.View style={[styles.background, inactiveStyle, backgroundStyle]} /> )} </Animated.View> ); })} <ActionButton icon={floatingCheck ? activeAnchor : inactiveAnchor} onPress={toggleMenu} buttonStyle={[{ backgroundColor: Assets.Colors.azure }, anchorButtonStyle]} iconStyle={anchorIconStyle} /> </View> ); }; const styles = StyleSheet.create({ container: { position: 'absolute', bottom: 7, alignItems: 'flex-end', justifyContent: 'flex-end', backgroundColor: 'transparent', alignSelf: 'flex-end', }, button: { position: 'absolute', justifyContent: 'flex-end', right: -1, flexDirection: 'row', alignItems: 'center', }, label: { fontSize: 16, position: 'absolute', right: 60, }, background: { backgroundColor: Assets.Colors.black, opacity: 0.5, position: 'absolute', width: 56, height: 56, right: -1, borderRadius: 28, flex: 1, }, }); export default FloatingActionButton;