UNPKG

react-native-fab

Version:

A FAB button component for Android and iOS, customizable, simple and as per material design specs.

209 lines (193 loc) 4.95 kB
import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { StyleSheet, Text, Animated, Easing, } from 'react-native'; import { Touchable } from './src'; import { noop } from './src/utils'; const sharpEasingValues = { entry: Easing.bezier(0.0, 0.0, 0.2, 1), exit: Easing.bezier(0.4, 0.0, 0.6, 1), }; const durationValues = { entry: 225, exit: 195, }; const moveEasingValues = { entry: Easing.bezier(0.0, 0.0, 0.2, 1), exit: Easing.bezier(0.4, 0.0, 1, 1), }; const styles = StyleSheet.create({ addButton: { borderRadius: 50, alignItems: 'stretch', shadowColor: '#000000', shadowOpacity: 0.8, shadowRadius: 2, shadowOffset: { height: 1, width: 0, }, elevation: 2, }, fabContainer: { position: 'absolute', bottom: 17, right: 17, height: 62, width: 62, alignItems: 'center', justifyContent: 'center', borderRadius: 50, }, addButtonInnerContainer: { flex: 1, borderRadius: 50, alignItems: 'center', justifyContent: 'center', }, }); export default class FAB extends Component { static propTypes = { buttonColor: PropTypes.string, iconTextColor: PropTypes.string, onClickAction: PropTypes.func, iconTextComponent: PropTypes.element, visible: PropTypes.bool, snackOffset: PropTypes.number, style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), }; static defaultProps = { buttonColor: 'red', iconTextColor: '#FFFFFF', onClickAction: noop, iconTextComponent: <Text>+</Text>, visible: true, snackOffset: 0, style: {}, }; state = { translateValue: new Animated.Value(0), shiftValue: new Animated.Value(0), }; componentDidMount() { const { translateValue, shiftValue } = this.state; const { visible, snackOffset } = this.props; if (visible) { translateValue.setValue(1); } else { translateValue.setValue(0); } if (snackOffset === 0) { shiftValue.setValue(20); } else { shiftValue.setValue(20 + snackOffset); } } // eslint-disable-next-line camelcase UNSAFE_componentWillReceiveProps(nextProps) { const { translateValue, shiftValue } = this.state; const { visible, snackOffset } = this.props; if (nextProps.visible && !visible) { Animated.timing( translateValue, { duration: durationValues.entry, toValue: 1, easing: sharpEasingValues.entry, useNativeDriver: false, }, ).start(); } else if (!nextProps.visible && visible) { Animated.timing( translateValue, { duration: durationValues.exit, toValue: 0, easing: sharpEasingValues.exit, useNativeDriver: false, }, ).start(); } if (nextProps.snackOffset !== snackOffset) { if (nextProps.snackOffset === 0) { Animated.timing( shiftValue, { duration: durationValues.exit, toValue: 20, easing: moveEasingValues.exit, useNativeDriver: false, }, ).start(); } else if (nextProps.snackOffset !== 0) { Animated.timing( shiftValue, { duration: durationValues.entry, toValue: 20 + nextProps.snackOffset, easing: moveEasingValues.entry, useNativeDriver: false, }, ).start(); } } } render() { const { translateValue, shiftValue, } = this.state; const { onClickAction, buttonColor, iconTextComponent, iconTextColor, style, } = this.props; const dimensionInterpolate = translateValue.interpolate({ inputRange: [0, 1], outputRange: [0, 56], }); const rotateInterpolate = translateValue.interpolate({ inputRange: [0, 1], outputRange: ['-90deg', '0deg'], }); return ( <Animated.View style={[styles.fabContainer, { bottom: shiftValue }]}> <Animated.View style={[ styles.addButton, { height: dimensionInterpolate, width: dimensionInterpolate, }, ]} > <Touchable onPress={onClickAction} style={[styles.addButtonInnerContainer, style]} buttonColor={buttonColor} > <Animated.View style={{ transform: [ { scaleX: translateValue }, { rotate: rotateInterpolate }, ], }} > {React.cloneElement(iconTextComponent, { style: { fontSize: 24, color: iconTextColor, }, })} </Animated.View> </Touchable> </Animated.View> </Animated.View> ); } }