import { useState, useEffect, useCallback } from "react"; // ============================================= // Types // ============================================= type ToastType = "success" | "error" | "warning" | "info"; interface Toast { id: string; type: ToastType; message: string; duration: number; } // ============================================= // TODO: Implement a Toast / Notification System // ============================================= // // Requirements: // 1. Show toast notifications with 4 types: success, error, warning, info // 2. Each toast auto-dismisses after its duration (default 3000ms) // 3. Support stacking multiple toasts (newest at the top) // 4. Each toast has a close (×) button for manual dismissal // 5. Different styling per type (colors, icons) // 6. Smooth enter/exit animations // // Bonus: // - Pause auto-dismiss on hover // - Progress bar showing remaining time // - Limit max visible toasts (e.g., 5) // // Hints: // - Use useState<Toast[]> to manage the toast array // - addToast: generate a unique id (crypto.randomUUID()), push to array // - removeToast: filter by id // - Each toast component should run a useEffect with setTimeout // that calls removeToast after the duration // - Clean up the timeout in the useEffect return // - For animations, consider CSS transitions with a "slide in" effect // ============================================= // TODO: Implement the individual Toast component // ============================================= // Props: toast (Toast), onDismiss (id: string) => void // // This component should: // - Render the toast with appropriate styling based on type // - Show an icon and the message text // - Include a close button // - Start an auto-dismiss timer on mount // - Clean up the timer on unmount function ToastItem({ toast, onDismiss }: { toast: Toast; onDismiss: (id: string) => void }) { // TODO: Set up auto-dismiss timer // useEffect(() => { // const timer = setTimeout(() => onDismiss(toast.id), toast.duration); // return () => clearTimeout(timer); // }, [toast.id, toast.duration, onDismiss]); // TODO: Define styles/colors per toast type // const styles: Record<ToastType, { bg: string; border: string; icon: string }> = { ... }; return ( <div style={{ padding: "12px 16px", borderRadius: 8, border: "1px solid #e5e7eb", background: "#fff", display: "flex", alignItems: "center", gap: 12, boxShadow: "0 4px 12px rgba(0,0,0,0.08)", minWidth: 320, }}> {/* TODO: Add icon based on toast.type */} <span style={{ fontSize: 18 }}>💬</span> <span style={{ flex: 1, fontSize: 14 }}>{toast.message}</span> {/* TODO: Add close button */} <button onClick={() => onDismiss(toast.id)} style={{ color: "#999", fontSize: 18, lineHeight: 1 }} > × </button> </div> ); } export default function App() { const [toasts, setToasts] = useState<Toast[]>([]); // TODO: Implement addToast const addToast = (type: ToastType, message: string, duration = 3000) => { // Generate unique id, append to toasts array }; // TODO: Implement removeToast const removeToast = useCallback((id: string) => { // Filter out the toast with the given id }, []); return ( <div style={{ maxWidth: 600, margin: "0 auto", padding: 24, fontFamily: "system-ui" }}> <h2 style={{ fontSize: 20, fontWeight: 700, marginBottom: 4 }}> Toast / Notification System </h2> <p style={{ fontSize: 14, color: "#666", marginBottom: 24 }}> Implement the toast system. Click the buttons below to trigger notifications. They should auto-dismiss and support manual close. </p> {/* Control buttons */} <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginBottom: 32 }}> <button onClick={() => addToast("success", "Item saved successfully!")} style={{ padding: "8px 16px", borderRadius: 6, background: "#22c55e", color: "#fff", fontSize: 13, fontWeight: 600, cursor: "pointer", }} > ✓ Success </button> <button onClick={() => addToast("error", "Something went wrong!")} style={{ padding: "8px 16px", borderRadius: 6, background: "#ef4444", color: "#fff", fontSize: 13, fontWeight: 600, cursor: "pointer", }} > ✕ Error </button> <button onClick={() => addToast("warning", "Check your input")} style={{ padding: "8px 16px", borderRadius: 6, background: "#f59e0b", color: "#fff", fontSize: 13, fontWeight: 600, cursor: "pointer", }} > ⚠ Warning </button> <button onClick={() => addToast("info", "New update available")} style={{ padding: "8px 16px", borderRadius: 6, background: "#3b82f6", color: "#fff", fontSize: 13, fontWeight: 600, cursor: "pointer", }} > ℹ Info </button> </div> {/* ============================================= */} {/* TODO: Toast container */} {/* ============================================= */} {/* Position this fixed to the top-right of the viewport. Map over the toasts array and render a ToastItem for each. Pass removeToast as the onDismiss callback. */} <div style={{ position: "fixed", top: 24, right: 24, display: "flex", flexDirection: "column", gap: 8, zIndex: 1000, }}> {toasts.map((toast) => ( <ToastItem key={toast.id} toast={toast} onDismiss={removeToast} /> ))} </div> {/* Status */} <div style={{ padding: "12px 16px", background: "#f9fafb", border: "1px solid #e5e7eb", borderRadius: 8, fontSize: 13, color: "#666", }}> Active toasts: <strong>{toasts.length}</strong> </div> {/* Hint */} <div style={{ marginTop: 24, padding: 16, background: "#f9fafb", borderLeft: "4px solid #111", borderRadius: "0 8px 8px 0", fontSize: 13, color: "#555", }}> <strong>Hint:</strong> Start with <code>addToast</code> and <code>removeToast</code>. Then add <code>useEffect</code> in ToastItem for auto-dismiss. Finally, style each type differently and add animations. </div> </div> ); }