import React, { useEffect, useRef, CSSProperties } from 'react'; import { useAtomValue, useSetAtom } from 'jotai'; import { toastQueueAtom, dismissToastAtom, ToastNotif } from '../../state/toast'; // Inject the keyframe animation once const STYLE_ID = 'lotus-toast-keyframes'; function ensureKeyframes() { if (document.getElementById(STYLE_ID)) return; const style = document.createElement('style'); style.id = STYLE_ID; style.textContent = ` @keyframes lotusToastIn { from { transform: translateX(120%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @media (prefers-reduced-motion: reduce) { @keyframes lotusToastIn { from { opacity: 0; } to { opacity: 1; } } } `; document.head.appendChild(style); } type ToastCardProps = { toast: ToastNotif; }; function ToastCard({ toast }: ToastCardProps) { const dismiss = useSetAtom(dismissToastAtom); const timerRef = useRef | null>(null); useEffect(() => { if (toast.sticky) return; timerRef.current = setTimeout(() => { dismiss(toast.id); }, 4000); return () => { if (timerRef.current !== null) clearTimeout(timerRef.current); }; }, [dismiss, toast.id, toast.sticky]); const handleCardClick = () => { if (toast.onClick) { toast.onClick(); } else { // window.location.hash setter auto-prepends '#', so values must not include it window.location.hash = toast.hashPath ?? `/room/${toast.roomId}`; } dismiss(toast.id); }; const handleDismiss = (e: React.MouseEvent) => { e.stopPropagation(); dismiss(toast.id); }; const cardStyle: CSSProperties = { position: 'relative', background: 'var(--lt-bg-card)', border: toast.sticky ? '1px solid var(--lt-accent-cyan-border)' : '1px solid var(--lt-border-color)', borderRadius: '12px', padding: '12px 14px', minWidth: '280px', maxWidth: '340px', boxShadow: toast.sticky ? 'var(--lt-box-glow-cyan)' : 'var(--lt-box-glow-orange)', cursor: 'pointer', animation: 'lotusToastIn 0.2s ease-out both', userSelect: 'none', }; const rowStyle: CSSProperties = { display: 'flex', alignItems: 'center', gap: '8px', marginRight: '20px', }; const avatarStyle: CSSProperties = { width: '24px', height: '24px', borderRadius: '50%', objectFit: 'cover', flexShrink: 0, }; const initialsStyle: CSSProperties = { width: '24px', height: '24px', borderRadius: '50%', background: 'var(--lt-accent-orange-dim)', border: '1px solid var(--lt-accent-orange-border)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '10px', fontWeight: 700, color: 'var(--lt-accent-orange)', flexShrink: 0, }; const nameStyle: CSSProperties = { color: toast.sticky ? 'var(--lt-accent-cyan)' : 'var(--lt-accent-orange)', fontWeight: 600, fontSize: '0.85rem', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', }; const dismissBtnStyle: CSSProperties = { position: 'absolute', top: '8px', right: '10px', background: 'none', border: 'none', color: 'var(--lt-text-secondary)', cursor: 'pointer', fontSize: '14px', lineHeight: 1, padding: '2px 4px', borderRadius: '4px', }; const bodyStyle: CSSProperties = { color: 'var(--lt-text-primary)', fontSize: '0.82rem', margin: '4px 0 2px', overflow: 'hidden', ...(toast.sticky ? { whiteSpace: 'normal', lineHeight: 1.4 } : { textOverflow: 'ellipsis', whiteSpace: 'nowrap' }), }; const roomNameStyle: CSSProperties = { color: 'var(--lt-text-secondary)', fontSize: '0.75rem', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', }; const initials = toast.displayName .split(' ') .slice(0, 2) .map((w) => w[0] ?? '') .join('') .toUpperCase(); return (
{ if (e.key === 'Enter' || e.key === ' ') handleCardClick(); }} aria-label={`Notification from ${toast.displayName} in ${toast.roomName}`} >
{toast.avatarUrl ? ( ) : ( )} {toast.displayName}
{toast.body}
{toast.roomName}
); } export function LotusToastContainer() { useEffect(() => { ensureKeyframes(); }, []); const toasts = useAtomValue(toastQueueAtom); if (toasts.length === 0) return null; const containerStyle: CSSProperties = { position: 'fixed', bottom: '1.5rem', right: '1.5rem', zIndex: 10001, display: 'flex', flexDirection: 'column', gap: '8px', pointerEvents: 'auto', }; return (
{toasts.map((toast) => ( ))}
); }