import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, } from 'react'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { AnimationControls, motion, useAnimation } from 'framer-motion'; const Bar = styled.div>` height: ${({ barHeight }) => barHeight}px; overflow: hidden; width: 100%; `; const BarFilling = styled(motion.div)` height: 100%; width: 100%; `; export type ProgressBarProps = { duration?: number; delay?: number; easing?: string; barHeight?: number; barColor?: string; autoStart?: boolean; }; export type ProgressBarControls = AnimationControls & { start: () => Promise; pause: () => Promise; }; export const ProgressBar = forwardRef( ( { duration = 3, delay = 0, easing = 'easeInOut', barHeight = 24, barColor, autoStart = true, }, ref, ) => { const theme = useTheme(); const controls = useAnimation(); const startTimestamp = useRef(0); const remainingTime = useRef(duration); const start = useCallback(async () => { startTimestamp.current = Date.now(); return controls.start({ scaleX: 0, transition: { duration: remainingTime.current / 1000, // convert ms to s for framer-motion delay: delay / 1000, // likewise ease: easing, }, }); }, [controls, delay, easing]); useImperativeHandle(ref, () => ({ ...controls, start: async () => { return start(); }, pause: async () => { const elapsed = Date.now() - startTimestamp.current; remainingTime.current = remainingTime.current - elapsed; return controls.stop(); }, })); useEffect(() => { if (autoStart) { start(); } }, [controls, delay, duration, easing, autoStart, start]); return ( ); }, );