68 lines
1.8 KiB
TypeScript
68 lines
1.8 KiB
TypeScript
import { useEffect } from 'react'
|
||
import type { ReactNode } from 'react'
|
||
|
||
export type ToastVariant = 'info' | 'success' | 'warning' | 'error'
|
||
|
||
interface ToastProps {
|
||
children: ReactNode
|
||
variant?: ToastVariant
|
||
duration?: number
|
||
onClose: () => void
|
||
'aria-label'?: string
|
||
}
|
||
|
||
function getVariantClasses(variant: ToastVariant): string {
|
||
switch (variant) {
|
||
case 'info':
|
||
return 'bg-neon-cyan/20 text-neon-cyan border-neon-cyan/50'
|
||
case 'success':
|
||
return 'bg-neon-green/20 text-neon-green border-neon-green/50'
|
||
case 'warning':
|
||
return 'bg-yellow-500/20 text-yellow-400 border-yellow-500/50'
|
||
case 'error':
|
||
return 'bg-red-500/20 text-red-400 border-red-500/50'
|
||
default:
|
||
return 'bg-neon-cyan/20 text-neon-cyan border-neon-cyan/50'
|
||
}
|
||
}
|
||
|
||
export function Toast({
|
||
children,
|
||
variant = 'info',
|
||
duration = 5000,
|
||
onClose,
|
||
'aria-label': ariaLabel,
|
||
}: ToastProps): React.ReactElement {
|
||
useEffect(() => {
|
||
if (duration > 0) {
|
||
const timer = setTimeout(() => {
|
||
onClose()
|
||
}, duration)
|
||
return () => clearTimeout(timer)
|
||
}
|
||
return undefined
|
||
}, [duration, onClose])
|
||
|
||
const variantClasses = getVariantClasses(variant)
|
||
const baseClasses = 'border rounded-lg p-4 shadow-lg flex items-center justify-between min-w-[300px] max-w-md'
|
||
const combinedClasses = `${baseClasses} ${variantClasses}`.trim()
|
||
|
||
return (
|
||
<div
|
||
className={combinedClasses}
|
||
role="alert"
|
||
aria-live="polite"
|
||
aria-label={ariaLabel}
|
||
>
|
||
<div className="flex-1">{children}</div>
|
||
<button
|
||
onClick={onClose}
|
||
className="ml-4 text-current hover:opacity-70 transition-opacity focus:outline-none focus:ring-2 focus:ring-current rounded"
|
||
aria-label="Close notification"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
)
|
||
}
|