chore: add animation to countdown timer

This commit is contained in:
MAZE 2024-06-16 22:12:12 +04:30
parent cfd2744e92
commit 73a5c21be9
2 changed files with 33 additions and 7 deletions

View file

@ -5,6 +5,7 @@ import {
IoRefresh, IoRefresh,
IoTrashOutline, IoTrashOutline,
} from 'react-icons/io5/index'; } from 'react-icons/io5/index';
import { motion } from 'framer-motion';
import { ReverseTimer } from './reverse-timer'; import { ReverseTimer } from './reverse-timer';
@ -29,13 +30,20 @@ export function Timer({ id }: TimerProps) {
const { name, spent, total } = useCountdownTimers(state => const { name, spent, total } = useCountdownTimers(state =>
state.getTimer(id), state.getTimer(id),
); ) || { name: '', spent: 0, total: 0 };
const [isDeleting, setIsDeleting] = useState(false);
const [snapshot, setSnapshot] = useState({ spent: 0, total: 0 });
const tick = useCountdownTimers(state => state.tick); const tick = useCountdownTimers(state => state.tick);
const rename = useCountdownTimers(state => state.rename); const rename = useCountdownTimers(state => state.rename);
const reset = useCountdownTimers(state => state.reset); const reset = useCountdownTimers(state => state.reset);
const deleteTimer = useCountdownTimers(state => state.delete); const deleteTimer = useCountdownTimers(state => state.delete);
const left = useMemo(() => total - spent, [total, spent]); const left = useMemo(
() => (isDeleting ? snapshot.total - snapshot.spent : total - spent),
[total, spent, isDeleting, snapshot],
);
const hours = useMemo(() => Math.floor(left / 3600), [left]); const hours = useMemo(() => Math.floor(left / 3600), [left]);
const minutes = useMemo(() => Math.floor((left % 3600) / 60), [left]); const minutes = useMemo(() => Math.floor((left % 3600) / 60), [left]);
@ -68,6 +76,9 @@ export function Timer({ id }: TimerProps) {
const handleDelete = () => { const handleDelete = () => {
if (isRunning) return showSnackbar('Please first stop the timer.'); if (isRunning) return showSnackbar('Please first stop the timer.');
setIsDeleting(true);
setSnapshot({ spent, total });
deleteTimer(id); deleteTimer(id);
}; };
@ -128,8 +139,20 @@ export function Timer({ id }: TimerProps) {
}; };
}, [isRunning, tick, id, spent, total, left]); }, [isRunning, tick, id, spent, total, left]);
const variants = {
enter: { opacity: 1 },
exit: { opacity: 0 },
initial: { opacity: 0 },
};
return ( return (
<div className={styles.timer}> <motion.div
animate="enter"
className={styles.timer}
exit="exit"
initial="initial"
variants={variants}
>
<header className={styles.header}> <header className={styles.header}>
<div className={styles.bar}> <div className={styles.bar}>
<div <div
@ -188,6 +211,6 @@ export function Timer({ id }: TimerProps) {
<IoTrashOutline /> <IoTrashOutline />
</button> </button>
</footer> </footer>
</div> </motion.div>
); );
} }

View file

@ -1,4 +1,5 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { AnimatePresence } from 'framer-motion';
import { Timer } from './timer'; import { Timer } from './timer';
import { Notice } from './notice'; import { Notice } from './notice';
@ -30,9 +31,11 @@ export function Timers() {
)} )}
</header> </header>
{timers.map(timer => ( <AnimatePresence>
<Timer id={timer.id} key={timer.id} /> {timers.map(timer => (
))} <Timer id={timer.id} key={timer.id} />
))}
</AnimatePresence>
<Notice /> <Notice />
</div> </div>