github.com/quickfeed/quickfeed@v0.0.0-20240507093252-ed8ca812a09c/public/src/components/alerts/Alert.tsx (about) 1 import { useEffect, useRef } from "react"; 2 import { Alert as AlertType } from "../../overmind/state"; 3 import { useActions } from "../../overmind"; 4 import React from "react"; 5 6 /** Alert is a component that displays a single alert. 7 * 8 * If the alert has a delay property, the alert will be removed after the delay. 9 * In this case, an animated circle will be displayed to indicate the time remaining before the alert is removed. 10 * 11 * The alert is removed if the alert is clicked, or if the delay has passed. 12 * 13 * @param alert - The alert to be displayed 14 */ 15 const Alert = ({ alert }: { alert: AlertType }): JSX.Element => { 16 const circleRef = useRef<SVGCircleElement>(null); 17 const actions = useActions(); 18 19 useEffect(() => { 20 let id: ReturnType<typeof setTimeout>; 21 if (alert.delay) { 22 const circle = circleRef.current; 23 24 // Remove the alert after the delay 25 id = setTimeout(() => { 26 actions.popAlert(alert); 27 }, alert.delay); 28 29 if (circle) { 30 const delay: number = alert.delay 31 const circumference = circle.getTotalLength(); 32 33 circle.style.strokeDasharray = `${circumference}px`; 34 circle.style.strokeDashoffset = `${circumference}px`; 35 36 const start = Date.now(); 37 const animate = () => { 38 const elapsed = Date.now() - start; 39 const strokeDashoffset = (elapsed / delay) * circumference; 40 circle.style.strokeDashoffset = `${strokeDashoffset}px`; 41 if (elapsed < delay) { 42 requestAnimationFrame(animate); 43 } 44 }; 45 requestAnimationFrame(animate); 46 } 47 } 48 49 return () => { 50 if (id) { 51 // If the alert is removed (by clicking the alert) before 52 // the delay has passed, the timeout will be cleared. 53 clearTimeout(id); 54 } 55 }; 56 }, []); 57 58 return ( 59 <div className={`alert alert-${alert.color}`} role="button" style={{ marginTop: "20px", whiteSpace: "pre-wrap" }} onClick={() => actions.popAlert(alert)}> 60 {alert.delay && ( 61 <svg viewBox="0 0 50 50" style={{ width: 20, height: 20, marginRight: 20 }}> 62 <circle ref={circleRef} cx={25} cy={25} r={20} strokeWidth={5} fill="none" stroke="#000" /> 63 </svg> 64 )} 65 {alert.text} 66 </div> 67 ); 68 }; 69 70 export default Alert;