UNPKG

react-progress-bar-timer

Version:

Customizable React progress bar with a labeled timer

367 lines (321 loc) 9.84 kB
import { jsx, jsxs } from 'react/jsx-runtime'; import { useState, useRef, useEffect, forwardRef, useImperativeHandle } from 'react'; import { ButtonBase, Box, Typography, Slide, alpha } from '@mui/material'; import { blue } from '@mui/material/colors'; import { makeStyles } from 'tss-react/mui'; import { keyframes } from '@emotion/react'; function _taggedTemplateLiteralLoose(strings, raw) { if (!raw) { raw = strings.slice(0); } strings.raw = raw; return strings; } var Direction; (function (Direction) { Direction["Left"] = "left"; Direction["Right"] = "right"; })(Direction || (Direction = {})); var Variant; (function (Variant) { Variant["Fill"] = "fill"; Variant["Empty"] = "empty"; })(Variant || (Variant = {})); var useTimer = function useTimer(_ref) { var duration = _ref.duration, _ref$onTick = _ref.onTick, onTick = _ref$onTick === void 0 ? function () {} : _ref$onTick, _ref$onFinish = _ref.onFinish, onFinish = _ref$onFinish === void 0 ? function () {} : _ref$onFinish; var _useState = useState(duration), time = _useState[0], setTime = _useState[1]; var _useState2 = useState(), timer = _useState2[0], setTimer = _useState2[1]; var isRunning = Boolean(timer && time); var callbackRef = useRef(); /** * Update callback ref on changes to callback props * to allow changes to reflect within setInterval's callback. */ useEffect(function () { callbackRef.current = { onTick: onTick, onFinish: onFinish }; }, [onTick, onFinish]); /** * Starts a stopped timer. */ var start = function start() { setTime(duration); var timer = setInterval(function () { var _callbackRef$current; (_callbackRef$current = callbackRef.current) == null ? void 0 : _callbackRef$current.onTick == null ? void 0 : _callbackRef$current.onTick(); setTime(function (prevTime) { var updatedTime = prevTime - 1; if (!updatedTime) { var _callbackRef$current2; clearInterval(timer); (_callbackRef$current2 = callbackRef.current) == null ? void 0 : _callbackRef$current2.onFinish == null ? void 0 : _callbackRef$current2.onFinish(); } return updatedTime; }); }, 1000); setTimer(timer); }; /** * Stops a running timer. */ var stop = function stop() { clearInterval(timer); setTimer(undefined); }; /** * Restarts a running or finished timer. */ var restart = function restart() { setTime(0); stop(); }; /** * Restarts the timer if time is 0. * This allows UI to reset visually prior to restarting. */ var handleRestart = function handleRestart() { if (time) { return; } start(); }; useEffect(handleRestart, [timer]); /** * Cleanup by clearing interval on unmount. */ useEffect(function () { return function () { return clearInterval(timer); }; }, []); return { time: time, timer: timer, isRunning: isRunning, start: start, stop: stop, restart: restart }; }; var _templateObject; var getRadius = function getRadius(rounded) { return rounded ? 4 : 0; }; var useStyles = /*#__PURE__*/makeStyles()(function (_theme, _ref) { var color = _ref.color, rootRounded = _ref.rootRounded, barRounded = _ref.barRounded; return { root: { width: '100%', borderRadius: getRadius(rootRounded) }, progressContainer: { flex: 1, position: 'relative', overflowX: 'hidden', borderRadius: getRadius(rootRounded), backgroundColor: alpha(color, 0.4) }, progress: { zIndex: 1, inset: 0, position: 'absolute', transformOrigin: 'left center', backgroundColor: color, borderRadius: getRadius(barRounded) }, textContainer: { boxSizing: 'border-box', position: 'relative', height: '4em', zIndex: 2, display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', gap: 4, margin: 8, overflowY: 'hidden', fontWeight: 500 }, label: { lineHeight: 'normal', letterSpacing: '0.0285em', fontWeight: 'inherit', fontSize: '0.9em', transition: 'transform 300ms cubic-bezier(0, 0, 0.2, 1) 0ms' }, time: { fontWeight: 'inherit', fontSize: '2em', lineHeight: 1 }, finished: { animation: keyframes(_templateObject || (_templateObject = _taggedTemplateLiteralLoose(["\n 0% {\n opacity: 0.8;\n background-color: orangered;\n }\n "]))) + " 1s 5" } }; }); var padTime = function padTime(num) { return ("" + num).padStart(2, '0'); }; var ProgressTimer = /*#__PURE__*/forwardRef(function (_ref2, ref) { var _cx, _cx2; var _ref2$direction = _ref2.direction, direction = _ref2$direction === void 0 ? Direction.Right : _ref2$direction, _ref2$variant = _ref2.variant, variant = _ref2$variant === void 0 ? Variant.Fill : _ref2$variant, _ref2$color = _ref2.color, color = _ref2$color === void 0 ? blue[700] : _ref2$color, _ref2$fontColor = _ref2.fontColor, fontColor = _ref2$fontColor === void 0 ? '#ffffffd9' : _ref2$fontColor, _ref2$duration = _ref2.duration, duration = _ref2$duration === void 0 ? 60 : _ref2$duration, _ref2$label = _ref2.label, label = _ref2$label === void 0 ? '' : _ref2$label, _ref2$buttonText = _ref2.buttonText, buttonText = _ref2$buttonText === void 0 ? '' : _ref2$buttonText, _ref2$classes = _ref2.classes, classes = _ref2$classes === void 0 ? {} : _ref2$classes, fontSize = _ref2.fontSize, _ref2$showDuration = _ref2.showDuration, showDuration = _ref2$showDuration === void 0 ? false : _ref2$showDuration, _ref2$rootRounded = _ref2.rootRounded, rootRounded = _ref2$rootRounded === void 0 ? true : _ref2$rootRounded, _ref2$barRounded = _ref2.barRounded, barRounded = _ref2$barRounded === void 0 ? false : _ref2$barRounded, started = _ref2.started, _ref2$onFinish = _ref2.onFinish, _onFinish = _ref2$onFinish === void 0 ? function () {} : _ref2$onFinish; var _useStyles = useStyles({ color: color, rootRounded: rootRounded, barRounded: barRounded }, { props: { classes: classes } }), styles = _useStyles.classes, cx = _useStyles.cx; var _useTimer = useTimer({ duration: duration, onFinish: function onFinish() { return _onFinish(label || buttonText); } }), time = _useTimer.time, timer = _useTimer.timer, isRunning = _useTimer.isRunning, start = _useTimer.start, stop = _useTimer.stop, restart = _useTimer.restart; /** * Controls timer via functions instead of "started" prop. */ useImperativeHandle(ref, function () { return { start: start, stop: stop, restart: restart }; }); /** * Formats the time to mm:ss. * * @returns {string} the formatted time */ var formatTime = function formatTime() { return padTime(Math.floor(time / 60)) + ":" + padTime(time % 60); }; /** * Gets the sign of the css translation that * determines if the bar moves left or right. * * @returns {string} the sign */ var getSign = function getSign() { var negativeDirection = timer ? Direction.Left : Direction.Right; return direction === negativeDirection ? '-' : ''; }; /** * Builds the progress transformation used * to move the progress bar left or right. * * @returns {string} the x translation css */ var buildProgressTransformation = function buildProgressTransformation() { var xPercentage = Boolean(timer) === (variant === Variant.Fill) ? '0%' : '100%'; return "translateX(" + getSign() + xPercentage + ")"; }; /** * Controls timer via "started" prop. */ var handleStartedChange = function handleStartedChange() { if (started == null) { return; } if (started) { if (timer) { restart(); } else { start(); } } else { stop(); } }; useEffect(handleStartedChange, [started]); return jsx(ButtonBase, { className: styles.root, onClick: timer ? stop : start, "aria-label": label, children: jsxs("div", { className: cx(styles.progressContainer, (_cx = {}, _cx[styles.finished] = !time && variant === Variant.Empty, _cx)), children: [jsxs(Box, { className: styles.textContainer, fontSize: fontSize, color: fontColor, children: [(label || buttonText && !timer || !time) && jsx(Typography, { className: styles.label, sx: { transform: isRunning || showDuration ? undefined : 'scale(1.86)' }, children: !isRunning && buttonText ? buttonText : label }), jsx(Slide, { direction: "up", timeout: { enter: 100, exit: 70 }, "in": showDuration || isRunning, mountOnEnter: true, unmountOnExit: true, children: jsx(Typography, { className: styles.time, children: formatTime() }) })] }), jsx("span", { className: cx(styles.progress, (_cx2 = {}, _cx2[styles.finished] = !time && variant === Variant.Fill, _cx2)), style: { transform: buildProgressTransformation(), transition: timer ? "transform " + duration + "s linear 0s" : undefined } })] }) }); }); export default ProgressTimer; export { Direction, Variant, useTimer }; //# sourceMappingURL=react-progress-bar-timer.esm.js.map