import { useState, useCallback } from 'react';
/**
* Toast message type
*/
export type ToastType = 'success' | 'error' | 'warning' | 'info';
/**
* Toast message object
*/
export interface Toast {
id: string;
type: ToastType;
message: string;
duration?: number;
}
/**
* Toast configuration options
*/
export interface ToastOptions {
/**
* Duration in milliseconds before auto-dismiss
* @default 5000
*/
duration?: number;
/**
* Maximum number of toasts to show at once
* @default 5
*/
maxToasts?: number;
}
/**
* Return type for useToast hook
*/
export interface UseToastReturn {
/**
* Array of active toasts
*/
toasts: Toast[];
/**
* Show a success toast
*/
success: (message: string, duration?: number) => void;
/**
* Show an error toast
*/
error: (message: string, duration?: number) => void;
/**
* Show a warning toast
*/
warning: (message: string, duration?: number) => void;
/**
* Show an info toast
*/
info: (message: string, duration?: number) => void;
/**
* Show a toast with custom type
*/
show: (type: ToastType, message: string, duration?: number) => void;
/**
* Dismiss a toast by ID
*/
dismiss: (id: string) => void;
/**
* Dismiss all toasts
*/
dismissAll: () => void;
}
/**
* Hook for managing toast notifications
*
* Provides a simple API for showing and managing toast messages with
* automatic dismissal and maximum toast limits.
*
* @example
* ```typescript
* function MyComponent() {
* const toast = useToast({ duration: 3000 });
*
* const handleClick = () => {
* toast.success('Operation successful!');
* };
*
* return (
*
*
* {toast.toasts.map((t) => (
* toast.dismiss(t.id)}
* />
* ))}
*
* );
* }
* ```
*/
export function useToast(options: ToastOptions = {}): UseToastReturn {
const { duration: defaultDuration = 5000, maxToasts = 5 } = options;
const [toasts, setToasts] = useState([]);
const show = useCallback(
(type: ToastType, message: string, duration: number = defaultDuration) => {
const id = `toast-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const newToast: Toast = {
id,
type,
message,
duration,
};
setToasts((prev) => {
// Add new toast and enforce maxToasts limit
const updated = [...prev, newToast];
if (updated.length > maxToasts) {
return updated.slice(updated.length - maxToasts);
}
return updated;
});
// Auto-dismiss after duration
if (duration > 0) {
setTimeout(() => {
setToasts((prev) => prev.filter((t) => t.id !== id));
}, duration);
}
},
[defaultDuration, maxToasts]
);
const success = useCallback(
(message: string, duration?: number) => {
show('success', message, duration);
},
[show]
);
const error = useCallback(
(message: string, duration?: number) => {
show('error', message, duration);
},
[show]
);
const warning = useCallback(
(message: string, duration?: number) => {
show('warning', message, duration);
},
[show]
);
const info = useCallback(
(message: string, duration?: number) => {
show('info', message, duration);
},
[show]
);
const dismiss = useCallback((id: string) => {
setToasts((prev) => prev.filter((t) => t.id !== id));
}, []);
const dismissAll = useCallback(() => {
setToasts([]);
}, []);
return {
toasts,
success,
error,
warning,
info,
show,
dismiss,
dismissAll,
};
}