UNPKG

@baby-journey/rn-segmented-progress-bar

Version:
203 lines (199 loc) 9 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, { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef } from 'react'; import { Animated, Easing, StyleSheet, View } from 'react-native'; import Svg, { Circle, G, TSpan } from 'react-native-svg'; import { getArcEndCoordinates, getPathValues } from './helpers'; const IndicatorCircle = Animated.createAnimatedComponent(Circle); const ProgressCircle = Animated.createAnimatedComponent(Circle); const max = 100; const duration = 1200; const RNSegmentedProgressBar = (props, ref) => { const { radius, strokeWidth = 10, baseColor = '#ffede1', progressColor = '#F39E93', segments = 3, segmentsGap = 0, indicator, centerComponent } = props; const circleRef = useRef([]); const animatedValue = useRef(new Animated.Value(0)).current; const progressAnimatedValues = useRef([...Array(segments)].map(() => new Animated.Value(0))).current; const indicatorCircleRef = useRef(null); const tSpanRef = useRef(null); const indicatorSegmentsGap = (indicator === null || indicator === void 0 ? void 0 : indicator.radius) ?? 0; const halfCircle = radius + strokeWidth + indicatorSegmentsGap; const circleCircumference = 2 * Math.PI * radius; const rotation = -90 + 180 * (segmentsGap / 2 / radius) / Math.PI; const getProgressValues = useCallback(progress => getPathValues(progress, max, segments), [segments]); const progressDelay = 10; const animation = useCallback((animatedVal, toValue, delay, durationValue) => { return Animated.timing(animatedVal, { toValue, duration: durationValue, delay, useNativeDriver: true, easing: Easing.linear }); }, []); useEffect(() => { () => { animatedValue.removeAllListeners(); progressAnimatedValues.forEach(progressAnimatedValue => progressAnimatedValue.removeAllListeners()); }; }, [animatedValue, progressAnimatedValues]); const getMeanSegmentsGap = useCallback(progress => { const pathValues = getProgressValues(progress); return (progress / pathValues.filter(val => val > 0).length || 1) * segments * segmentsGap / 100; }, [getProgressValues, segments, segmentsGap]); const runIndicator = useCallback((calculatedStrokeDashoffset, val) => { const { x: cx, y: cy } = getArcEndCoordinates(radius, calculatedStrokeDashoffset, halfCircle, halfCircle, rotation); if (!calculatedStrokeDashoffset) { return; } const calculatedProgress = `${Math.round(val)}%`; if (indicatorCircleRef !== null && indicatorCircleRef !== void 0 && indicatorCircleRef.current && tSpanRef !== null && tSpanRef !== void 0 && tSpanRef.current) { //@ts-ignore indicatorCircleRef.current.setNativeProps({ r: (indicator === null || indicator === void 0 ? void 0 : indicator.radius) || 0, strokeWidth: (indicator === null || indicator === void 0 ? void 0 : indicator.strokeWidth) || 0, cx, cy }); //@ts-ignore tSpanRef === null || tSpanRef === void 0 ? void 0 : tSpanRef.current.setNativeProps({ children: calculatedProgress, dx: cx, dy: cy + 5, font: { textAnchor: 'middle', fontSize: 18 } }); } }, [radius, halfCircle, rotation, indicator === null || indicator === void 0 ? void 0 : indicator.radius, indicator === null || indicator === void 0 ? void 0 : indicator.strokeWidth]); const run = useCallback(_ref => { let { progress } = _ref; const circleProgressValues = getProgressValues(progress); progressAnimatedValues.forEach((progressAnimated, index) => { progressAnimated.addListener(v => { if (circleRef !== null && circleRef !== void 0 && circleRef.current[index]) { var _circleRef$current$in; var strokeDashoffset = circleCircumference; var val = v.value <= (circleProgressValues[index] ?? 0) ? v.value : circleProgressValues[index] ?? 0; strokeDashoffset = circleProgressValues[index] ? circleCircumference - circleCircumference * val / 100 : circleCircumference; const paintedLength = circleCircumference - strokeDashoffset - segments * (circleProgressValues[index] ?? 0) * segmentsGap / 100; //@ts-ignore circleRef === null || circleRef === void 0 ? void 0 : (_circleRef$current$in = circleRef.current[index]) === null || _circleRef$current$in === void 0 ? void 0 : _circleRef$current$in.setNativeProps({ strokeDashoffset: circleCircumference - paintedLength > circleCircumference ? circleCircumference : circleCircumference - paintedLength }); } }); }); if (indicator !== null && indicator !== void 0 && indicator.show) { animatedValue.addListener(v => { var strokeDashoffset = circleCircumference; var val = v.value <= progress ? v.value : progress; strokeDashoffset = progress ? circleCircumference - circleCircumference * val / 100 : circleCircumference; const paintedLength = circleCircumference - strokeDashoffset; const meanSegmentsGap = getMeanSegmentsGap(progress); const calculatedStrokeDashoffset = paintedLength - meanSegmentsGap; runIndicator(calculatedStrokeDashoffset, progress); }); } // Animate circles sequentially const progressAnimations = Animated.sequence(progressAnimatedValues.map((tav, index) => animation(tav, // Animated value circleProgressValues[index] ?? 0, // To value index === 0 ? progressDelay : 0, // Delay duration * (circleProgressValues[index] ?? 0) / max // Duration ))); if (indicator !== null && indicator !== void 0 && indicator.show) { // Animate percentage circle const percentageAnim = animation(animatedValue, // Animated value progress, // To value progressDelay, // Delay duration * progress / max // Duration ); // Progress Animations run parallelly with percentage circle Animated.parallel([progressAnimations, percentageAnim]).start(); } else { progressAnimations.start(); } }, [animatedValue, animation, segments, circleCircumference, segmentsGap, getMeanSegmentsGap, indicator === null || indicator === void 0 ? void 0 : indicator.show, getProgressValues, runIndicator, progressAnimatedValues]); const getProgress = useMemo(() => { const progressConfig = { stroke: progressColor, cx: halfCircle, cy: halfCircle, r: radius, origin: `${halfCircle}, ${halfCircle}`, strokeWidth: strokeWidth, strokeDasharray: circleCircumference, strokeDashoffset: circleCircumference }; return progressAnimatedValues.map((_, key) => /*#__PURE__*/React.createElement(ProgressCircle, _extends({ key: key //@ts-ignore , ref: el => circleRef.current[key] = el }, progressConfig, { rotation: rotation + key * 360 / segments, strokeLinecap: "round" }))); }, [segments, circleCircumference, halfCircle, progressColor, radius, rotation, strokeWidth, progressAnimatedValues]); useImperativeHandle(ref, () => ({ run })); return /*#__PURE__*/React.createElement(Svg, { viewBox: `0 0 ${halfCircle * 2} ${halfCircle * 2}`, width: '100%', fill: "none", height: radius * 2 }, centerComponent && /*#__PURE__*/React.createElement(View, { style: styles.centerComponent }, centerComponent), /*#__PURE__*/React.createElement(G, null, [...Array(segments)].map((_, key) => { return /*#__PURE__*/React.createElement(Circle, { key: key, cx: halfCircle, cy: halfCircle, r: radius, stroke: baseColor, rotation: rotation + key * 360 / segments, origin: `${halfCircle}, ${halfCircle}`, strokeWidth: strokeWidth, strokeDasharray: circleCircumference, strokeDashoffset: circleCircumference - circleCircumference / segments + segmentsGap, strokeLinecap: "round" }); }), getProgress, (indicator === null || indicator === void 0 ? void 0 : indicator.show) === true && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(IndicatorCircle, { stroke: progressColor, ref: indicatorCircleRef, fill: "white" }), /*#__PURE__*/React.createElement(TSpan, { stroke: progressColor, fill: progressColor, ref: tSpanRef })))); }; export default /*#__PURE__*/memo( /*#__PURE__*/forwardRef(RNSegmentedProgressBar)); const styles = StyleSheet.create({ centerComponent: { height: '100%', justifyContent: 'center', alignItems: 'center' } }); //# sourceMappingURL=index.js.map