99 lines
2.9 KiB
TypeScript
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>
|
|
);
|
|
}
|