-
{iconByType(n.type)}
-
+
+ {items.map((n) => (
+
+
+
+ {iconByType(n.type)}
+
+ {n.type === 'success' && 'Успех'}
+ {n.type === 'error' && 'Ошибка'}
+ {n.type === 'warning' && 'Внимание'}
+ {n.type === 'info' && 'Информация'}
+
+
+
+
{n.message}
- {n.details && (
-
- Подробнее
- {typeof n.details === 'string' ? n.details : JSON.stringify(n.details, null, 2)}
-
- )}
- {n.count > 1 && (
-
×{n.count}
- )}
+ {n.count > 1 &&
×{n.count}}
-
+ {n.details && (
+
+ Подробнее
+
+ {typeof n.details === 'string' ? n.details : JSON.stringify(n.details, null, 2)}
+
+
+ )}
- ))}
-
+
+ ))}
);
}
diff --git a/frontend/src/components/ToastContainer.jsx b/frontend/src/components/ToastContainer.jsx
index 0986adf..7bc1adf 100644
--- a/frontend/src/components/ToastContainer.jsx
+++ b/frontend/src/components/ToastContainer.jsx
@@ -3,12 +3,12 @@ import {
IconCheck,
IconX,
IconAlertTriangle,
- IconInfoCircle,
- IconRefresh
+ IconInfoCircle
} from '@tabler/icons-react'
/**
- * Контекст для глобального управления toast уведомлениями
+ * Контекст для глобального управления toast уведомлениями.
+ * Тосты в стиле Tabler Alerts, всплывают справа как Tabler Toasts.
*/
const ToastContext = createContext({
success: () => {},
@@ -19,24 +19,29 @@ const ToastContext = createContext({
export const useToast = () => useContext(ToastContext)
+const DEFAULT_TITLES = {
+ success: 'Выполнено успешно',
+ error: 'Произошла ошибка',
+ warning: 'Внимание',
+ info: 'Информация',
+}
+
/**
- * Toast уведомление (отдельный элемент)
+ * Один toast: разметка Tabler (toast + toast-header + toast-body), стиль Tabler Alerts.
*/
function Toast({ toast, onClose }) {
const [isLeaving, setIsLeaving] = useState(false)
useEffect(() => {
if (toast.duration && toast.duration > 0) {
- const timer = setTimeout(() => {
- handleClose()
- }, toast.duration)
+ const timer = setTimeout(() => handleClose(), toast.duration)
return () => clearTimeout(timer)
}
}, [toast.duration])
const handleClose = () => {
setIsLeaving(true)
- setTimeout(() => onClose(toast.id), 300) // анимация 300ms
+ setTimeout(() => onClose(toast.id), 300)
}
const getIcon = () => {
@@ -45,47 +50,53 @@ function Toast({ toast, onClose }) {
case 'error': return
case 'warning': return
case 'info': return
- default: return null
+ default: return
}
}
- const getColorClass = () => {
- switch (toast.type) {
- case 'success': return 'alert-success'
- case 'error': return 'alert-danger'
- case 'warning': return 'alert-warning'
- case 'info': return 'alert-info'
- default: return 'alert-info'
- }
- }
+ const alertClass = {
+ success: 'alert-success',
+ error: 'alert-danger',
+ warning: 'alert-warning',
+ info: 'alert-info',
+ }[toast.type] || 'alert-info'
+
+ const title = toast.title ?? DEFAULT_TITLES[toast.type]
return (
-
-
- {getIcon()}
+
+
+ {getIcon()}
+ {title}
+
-
- {toast.title && (
-
{toast.title}
- )}
-
{toast.message}
-
- {/* Действия */}
+
+
{toast.message}
{toast.actions && toast.actions.length > 0 && (
{toast.actions.map((action, idx) => (
)}
-
)
@@ -152,46 +157,73 @@ export function ToastContainer({ children }) {
return (
{children}
-
- {/* Контейнер с toast'ами */}
-
- {toasts.map(toast => (
-
+
+ {/* Контейнер тостов справа (как в Tabler) */}
+
+ {toasts.map((t) => (
+
))}
- {/* CSS анимации */}
+ {/* Стили: тосты в стиле Tabler Alerts, всплывают справа */}