UNPKG

react-native-really-awesome-button

Version:

React Native Button UI component that renders an 60fps animated set of progress enabled 3D performant buttons.

383 lines (382 loc) 13.3 kB
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } import React, { useCallback, useMemo, useRef, useState } from 'react'; import { ActivityIndicator, Pressable, View, Animated, Text } from 'react-native'; import { animateTiming, animateElastic, animateSpring } from './helpers'; import debounce from 'lodash.debounce'; import { styles, getStyles } from './styles'; import { ANIMATED_TIMING_LOADING, ANIMATED_TIMING_OFF, DEFAULT_ACTIVITY_COLOR, DEFAULT_ACTIVE_OPACITY, DEFAULT_BACKGROUND_ACTIVE, DEFAULT_BACKGROUND_COLOR, DEFAULT_BACKGROUND_DARKER, DEFAULT_BACKGROUND_SHADOW, DEFAULT_BORDER_RADIUS, DEFAULT_BORDER_WIDTH, DEFAULT_DEBOUNCED_PRESS_TIME, DEFAULT_HEIGHT, DEFAULT_HORIZONTAL_PADDING, DEFAULT_LINE_HEIGHT, DEFAULT_RAISE_LEVEL, DEFAULT_TEXT_COLOR, DEFAULT_TEXT_SIZE, DEFAULT_WIDTH } from './constants'; import Placeholder from './Placeholder'; const AwesomeButton = _ref => { let { activityColor = DEFAULT_ACTIVITY_COLOR, activeOpacity = DEFAULT_ACTIVE_OPACITY, animatedPlaceholder = true, backgroundActive = DEFAULT_BACKGROUND_ACTIVE, backgroundColor = DEFAULT_BACKGROUND_COLOR, backgroundDarker = DEFAULT_BACKGROUND_DARKER, backgroundPlaceholder = DEFAULT_BACKGROUND_SHADOW, backgroundProgress = DEFAULT_BACKGROUND_SHADOW, backgroundShadow = DEFAULT_BACKGROUND_SHADOW, borderColor, borderRadius = DEFAULT_BORDER_RADIUS, borderBottomLeftRadius, borderBottomRightRadius, borderTopLeftRadius, borderTopRightRadius, borderWidth = DEFAULT_BORDER_WIDTH, children = null, before = null, after = null, disabled = false, height = DEFAULT_HEIGHT, hitSlop = null, debouncedPressTime = DEFAULT_DEBOUNCED_PRESS_TIME, paddingHorizontal = DEFAULT_HORIZONTAL_PADDING, onPress = () => null, onPressIn = () => null, onPressedIn = () => null, onPressOut = () => null, onPressedOut = () => null, onProgressStart = () => null, onProgressEnd = () => null, onLongPress = null, dangerouslySetPressableProps = {}, progress = false, paddingBottom = 0, paddingTop = 0, progressLoadingTime = ANIMATED_TIMING_LOADING, raiseLevel = DEFAULT_RAISE_LEVEL, springRelease = true, stretch = false, style = null, textColor = DEFAULT_TEXT_COLOR, textLineHeight = DEFAULT_LINE_HEIGHT, textSize = DEFAULT_TEXT_SIZE, textFontFamily, width = DEFAULT_WIDTH, extra = null } = _ref; const loadingOpacity = useRef(new Animated.Value(1)).current; const textOpacity = useRef(new Animated.Value(1)).current; const activityOpacity = useRef(new Animated.Value(0)).current; const animatedActive = useRef(new Animated.Value(0)).current; const animatedValue = useRef(new Animated.Value(0)).current; const animatedLoading = useRef(new Animated.Value(0)).current; const animatedOpacity = useRef(new Animated.Value(width === null && !stretch === true ? 0 : 1)).current; const pressing = useRef(false); const pressed = useRef(false); const actioned = useRef(false); const progressing = useRef(false); const timeout = useRef(null); const containerWidth = useRef(null); const pressAnimation = useRef(null); const [activity, setActivity] = useState(false); const [stateWidth, setStateWidth] = useState(null); const debouncedPress = debouncedPressTime ? debounce(animateProgressEnd => onPress(animateProgressEnd), debouncedPressTime, { trailing: false, leading: true }) : onPress; const layout = { backgroundActive, backgroundColor, backgroundDarker, backgroundPlaceholder, backgroundProgress, backgroundShadow, borderColor, borderRadius, borderBottomLeftRadius, borderBottomRightRadius, borderTopLeftRadius, borderTopRightRadius, borderWidth, height, paddingBottom, paddingHorizontal, paddingTop, raiseLevel, stateWidth, stretch, textColor, textFontFamily, textLineHeight, textSize, width }; const dynamicStyles = useMemo(() => { return getStyles(layout); }, [backgroundActive, backgroundColor, backgroundDarker, backgroundPlaceholder, backgroundProgress, backgroundShadow, borderColor, borderRadius, borderBottomLeftRadius, borderBottomRightRadius, borderTopLeftRadius, borderTopRightRadius, borderWidth, height, paddingBottom, paddingHorizontal, paddingTop, raiseLevel, stateWidth, stretch, textColor, textFontFamily, textLineHeight, textSize, width]); const getAnimatedValues = () => { let width = containerWidth.current ? containerWidth.current * -1 : 0; return { animatedContainer: { opacity: animatedOpacity }, animatedShadow: { transform: [{ translateY: animatedValue.interpolate({ inputRange: [0, 1], outputRange: [0, -raiseLevel / 2] }) }] }, animatedContent: { transform: [{ translateY: animatedValue.interpolate({ inputRange: [0, 1], outputRange: [0, raiseLevel] }) }] }, animatedActive: { opacity: animatedActive }, animatedActivity: { opacity: activityOpacity, transform: [{ scale: activityOpacity }] }, animatedProgress: { opacity: loadingOpacity, transform: [{ translateX: animatedLoading.interpolate({ inputRange: [0, 1], outputRange: [width, 0] }) }] } }; }; const onTextLayout = event => { containerWidth.current = event.nativeEvent.layout.width; animatedOpacity.setValue(1); if (width !== null && stretch === false) { return; } if (stateWidth !== event.nativeEvent.layout.width && (stateWidth === null || stateWidth < event.nativeEvent.layout.width)) { setStateWidth(event.nativeEvent.layout.width); } }; const animatePressIn = useCallback(() => { pressing.current = true; pressAnimation.current = Animated.parallel([animateTiming({ variable: animatedValue, toValue: 1, duration: ANIMATED_TIMING_OFF }), animateTiming({ variable: animatedActive, toValue: 1, duration: ANIMATED_TIMING_OFF }), animateTiming({ variable: animatedOpacity, toValue: progress ? 1 : activeOpacity, duration: ANIMATED_TIMING_OFF })]); pressAnimation.current.start(() => { pressed.current = true; onPressedIn && onPressedIn(); }); }, []); const animateLoadingStart = () => { animatedLoading.setValue(0); animateTiming({ variable: animatedLoading, toValue: 1, duration: progressLoadingTime }).start(); }; const animateContentOut = () => { Animated.parallel([animateTiming({ variable: loadingOpacity, toValue: 1 }), animateElastic({ variable: textOpacity, toValue: 0 }), animateElastic({ variable: activityOpacity, toValue: 1 })]).start(); }; const animateProgressEnd = callback => { if (progress !== true) { return; } if (timeout !== null && timeout !== void 0 && timeout.current) { clearTimeout(timeout.current); } requestAnimationFrame(() => { animateTiming({ variable: animatedLoading, toValue: 1 }).start(() => { Animated.parallel([animateElastic({ variable: textOpacity, toValue: 1 }), animateElastic({ variable: activityOpacity, toValue: 0 }), animateTiming({ variable: loadingOpacity, toValue: 0, delay: 100 })]).start(() => { animateRelease(() => { progressing.current = false; callback && callback(); onProgressEnd && onProgressEnd(); }); }); }); }); }; const animateRelease = callback => { if (pressAnimation.current) { pressAnimation.current.stop(); } pressed.current = false; pressing.current = false; const end = () => { pressed.current = false; pressing.current = false; callback && callback(); onPressedOut && onPressedOut(); }; if (springRelease === true) { Animated.parallel([animateSpring({ variable: animatedActive, toValue: 0 }), animateSpring({ variable: animatedValue, toValue: 0 }), animateTiming({ variable: animatedOpacity, toValue: 1 })]).start(end); return; } Animated.parallel([animateTiming({ variable: animatedActive, toValue: 0, duration: ANIMATED_TIMING_OFF }), animateTiming({ variable: animatedValue, toValue: 0, duration: ANIMATED_TIMING_OFF }), animateTiming({ variable: animatedOpacity, toValue: 1 })]).start(end); }; const startProgress = () => { progressing.current = true; onProgressStart && onProgressStart(); setActivity(true); animateLoadingStart(); animateContentOut(); }; const press = () => { actioned.current = true; if (progressing.current === true) { return; } if (progress === true) { requestAnimationFrame(startProgress); } debouncedPress(animateProgressEnd); }; const handlePressIn = useCallback(event => { if (disabled === true || !children || progressing.current === true || pressed.current === true) { return; } onPressIn && onPressIn(event); animatePressIn(); }, [disabled, children, onPressIn]); const handlePressOut = useCallback(event => { var _event$nativeEvent; if (disabled === true || !children || progressing.current === true) { return; } onPressOut && onPressOut(event); // @ts-ignore if (event !== null && event !== void 0 && (_event$nativeEvent = event.nativeEvent) !== null && _event$nativeEvent !== void 0 && _event$nativeEvent.contentOffset) { animateRelease(); return; } if (pressing.current === true || raiseLevel === 0) { press(); if (progress === true) { return; } } animateRelease(); }, [raiseLevel, children, progress, onPress]); const animatedValues = getAnimatedValues(); const renderActivity = useMemo(() => { if (activity === false) { return null; } return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Animated.View, { testID: "aws-btn-progress", style: [styles.progress, dynamicStyles.progress, animatedValues.animatedProgress] }), /*#__PURE__*/React.createElement(Animated.View, { testID: "aws-btn-activity-indicator", style: [styles.container__activity, animatedValues.animatedActivity] }, /*#__PURE__*/React.createElement(ActivityIndicator, { color: activityColor }))); }, [activity]); const renderContent = useMemo(() => { const animatedStyles = { opacity: textOpacity, transform: [{ scale: textOpacity }] }; if (!children) { return /*#__PURE__*/React.createElement(Placeholder, { animated: animatedPlaceholder, style: [dynamicStyles.container__placeholder] }); } let content = children; if (typeof children === 'string') { content = /*#__PURE__*/React.createElement(Text, { testID: "aws-btn-content-text", style: [styles.container__text, dynamicStyles.container__text] }, children); } return /*#__PURE__*/React.createElement(Animated.View, { style: [styles.container__view, dynamicStyles.container__view, animatedStyles] }, before, content, after); }, [children, before, after, textColor]); return /*#__PURE__*/React.createElement(Pressable, _extends({ testID: "aws-btn-content-view", hitSlop: hitSlop, onLongPress: onLongPress }, dangerouslySetPressableProps, { onPressIn: handlePressIn, onPressOut: handlePressOut }), /*#__PURE__*/React.createElement(Animated.View, { testID: "aws-btn-content-2", style: [styles.container, dynamicStyles.container, animatedValues.animatedContainer, style] }, /*#__PURE__*/React.createElement(Animated.View, { testID: "aws-btn-shadow", style: [styles.shadow, dynamicStyles.shadow, animatedValues.animatedShadow] }), /*#__PURE__*/React.createElement(View, { testID: "aws-btn-bottom", style: [styles.bottom, dynamicStyles.bottom] }), /*#__PURE__*/React.createElement(Animated.View, { testID: "aws-btn-content", style: [styles.content, dynamicStyles.content, animatedValues.animatedContent] }, /*#__PURE__*/React.createElement(View, { testID: "aws-btn-text", style: [styles.text, dynamicStyles.text], onLayout: onTextLayout }, extra, /*#__PURE__*/React.createElement(Animated.View, { testID: "aws-btn-active-background", style: [styles.activeBackground, dynamicStyles.activeBackground, animatedValues.animatedActive] }), renderActivity, renderContent)))); }; export default AwesomeButton; //# sourceMappingURL=Button.js.map