feat(mui): migrate AlertContainer off admin-* CSS

Replaces framer-motion animation + custom SVG icons + admin-toast markup
with MUI Snackbar + MUI Alert (filled variant). useAlertState() consumption
(alerts array, removeAlert) is preserved verbatim — alert lifecycle
(timing, types, auto-dismiss) is owned by AlertContext and unchanged.
Alerts stack bottom-right with 72px vertical offset per slot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-06-07 08:37:32 +02:00
parent 67f98c1f66
commit 6f428b2297

View File

@@ -1,102 +1,29 @@
import React from "react"; import Snackbar from "@mui/material/Snackbar";
import { motion, AnimatePresence } from "framer-motion"; import MuiAlert from "@mui/material/Alert";
import { useAlertState } from "../context/AlertContext"; import { useAlertState } from "../context/AlertContext";
const icons: Record<string, React.ReactNode> = {
success: (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
<polyline points="22 4 12 14.01 9 11.01" />
</svg>
),
error: (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<circle cx="12" cy="12" r="10" />
<line x1="15" y1="9" x2="9" y2="15" />
<line x1="9" y1="9" x2="15" y2="15" />
</svg>
),
warning: (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
<line x1="12" y1="9" x2="12" y2="13" />
<line x1="12" y1="17" x2="12.01" y2="17" />
</svg>
),
info: (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<circle cx="12" cy="12" r="10" />
<line x1="12" y1="16" x2="12" y2="12" />
<line x1="12" y1="8" x2="12.01" y2="8" />
</svg>
),
};
export default function AlertContainer() { export default function AlertContainer() {
const { alerts, removeAlert } = useAlertState(); const { alerts, removeAlert } = useAlertState();
return ( return (
<div className="admin-alert-container" role="status" aria-live="polite"> <>
<AnimatePresence> {alerts.map((alert, index) => (
{alerts.map((alert) => ( <Snackbar
<motion.div key={alert.id}
key={alert.id} open
className={`admin-toast admin-toast-${alert.type}`} anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
initial={{ opacity: 0, y: 20, scale: 0.95 }} sx={{ bottom: { xs: 16 + index * 72 } }}
animate={{ opacity: 1, y: 0, scale: 1 }} >
exit={{ opacity: 0, y: 20, scale: 0.95 }} <MuiAlert
transition={{ duration: 0.2 }} severity={alert.type}
onClose={() => removeAlert(alert.id)}
variant="filled"
sx={{ width: "100%", minWidth: 280 }}
> >
<span className="admin-toast-icon">{icons[alert.type]}</span> {alert.message}
<span className="admin-toast-message">{alert.message}</span> </MuiAlert>
<button </Snackbar>
className="admin-toast-close" ))}
onClick={() => removeAlert(alert.id)} </>
aria-label="Zavřít"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</motion.div>
))}
</AnimatePresence>
</div>
); );
} }