uchill/front_material/components/common/Toast.tsx

99 lines
2.9 KiB
TypeScript

/**
* Компонент всплывающих уведомлений (Toast)
*/
'use client';
import React, { useEffect, useState } from 'react';
export interface ToastMessage {
id: string;
message: string;
type?: 'success' | 'error' | 'info';
}
interface ToastProps {
messages: ToastMessage[];
onRemove: (id: string) => void;
}
export function Toast({ messages, onRemove }: ToastProps) {
return (
<div
style={{
position: 'fixed',
bottom: '24px',
right: '24px',
zIndex: 9999,
display: 'flex',
flexDirection: 'column',
gap: '8px',
pointerEvents: 'none',
}}
>
{messages.map((msg) => (
<ToastItem key={msg.id} message={msg} onRemove={onRemove} />
))}
</div>
);
}
function ToastItem({ message, onRemove }: { message: ToastMessage; onRemove: (id: string) => void }) {
const [visible, setVisible] = useState(false);
useEffect(() => {
const timer = setTimeout(() => setVisible(true), 10);
const removeTimer = setTimeout(() => {
setVisible(false);
setTimeout(() => onRemove(message.id), 300);
}, 3000);
return () => {
clearTimeout(timer);
clearTimeout(removeTimer);
};
}, [message.id, onRemove]);
const getBgColor = () => {
switch (message.type) {
case 'success': return 'var(--md-sys-color-tertiary-container, #e8def8)';
case 'error': return 'var(--md-sys-color-error-container, #f9dedc)';
default: return 'var(--md-sys-color-secondary-container, #e8def8)';
}
};
const getTextColor = () => {
switch (message.type) {
case 'success': return 'var(--md-sys-color-on-tertiary-container, #1d192b)';
case 'error': return 'var(--md-sys-color-on-error-container, #410002)';
default: return 'var(--md-sys-color-on-secondary-container, #1d192b)';
}
};
return (
<div
style={{
padding: '12px 20px',
borderRadius: '12px',
background: getBgColor(),
color: getTextColor(),
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
fontSize: '14px',
fontWeight: 500,
pointerEvents: 'auto',
transform: visible ? 'translateX(0)' : 'translateX(120%)',
opacity: visible ? 1 : 0,
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
display: 'flex',
alignItems: 'center',
gap: '8px',
}}
>
{message.type === 'success' && <span className="material-symbols-outlined" style={{ fontSize: 20 }}>check_circle</span>}
{message.type === 'error' && <span className="material-symbols-outlined" style={{ fontSize: 20 }}>error</span>}
{message.type === 'info' && <span className="material-symbols-outlined" style={{ fontSize: 20 }}>info</span>}
{message.message}
</div>
);
}