recipe-manager/frontend/src/components/Toast.tsx

98 lines
2.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Toast notification component
* Displays temporary success/error/info messages
*/
import { useEffect } from 'react';
import { colors, radius, shadows, spacing, typography } from '../theme';
export type ToastType = 'success' | 'error' | 'info' | 'warning';
export interface ToastMessage {
id: string;
message: string;
type: ToastType;
duration?: number;
}
interface ToastProps {
message: ToastMessage;
onClose: (id: string) => void;
}
/**
* Single toast notification
*/
export function Toast({ message, onClose }: ToastProps) {
useEffect(() => {
const timer = setTimeout(() => {
onClose(message.id);
}, message.duration || 5000);
return () => clearTimeout(timer);
}, [message.id, message.duration, onClose]);
const backgroundColor = {
success: colors.success,
error: colors.error,
info: colors.primary,
warning: colors.warning,
}[message.type];
const icon = {
success: '✓',
error: '✕',
info: '',
warning: '⚠',
}[message.type];
return (
<div
className="animate-slide-in flex min-w-[300px] max-w-[500px] items-center justify-between gap-4 px-6 py-4 text-white"
style={{
backgroundColor,
borderRadius: radius.lg,
boxShadow: shadows.card,
}}
>
<div className="flex items-center gap-3">
<span className="text-xl font-bold">{icon}</span>
<span
className="font-medium"
style={{ fontSize: typography.fontSize.base, lineHeight: typography.lineHeight.normal }}
>
{message.message}
</span>
</div>
<button
onClick={() => onClose(message.id)}
className="text-xl font-bold leading-none text-white transition-colors hover:text-gray-200"
style={{ marginInlineStart: spacing.xs }}
aria-label="Close"
>
×
</button>
</div>
);
}
/**
* Toast container that displays all active toasts
*/
interface ToastContainerProps {
messages: ToastMessage[];
onClose: (id: string) => void;
}
export function ToastContainer({ messages, onClose }: ToastContainerProps) {
if (messages.length === 0) return null;
return (
<div className="fixed right-4 top-4 z-50 flex flex-col gap-2">
{messages.map((message) => (
<Toast key={message.id} message={message} onClose={onClose} />
))}
</div>
);
}