| import cn from "@utils/classnames"; | |
| import { AlertTriangle, CheckCircle, Info, X, XCircle } from "lucide-react"; | |
| import type { ReactNode } from "react"; | |
| interface MessageProps { | |
| type: "success" | "error" | "warning" | "info"; | |
| title: string; | |
| message?: string; | |
| children?: ReactNode; | |
| onClose?: () => void; | |
| className?: string; | |
| } | |
| const messageStyles = { | |
| success: { | |
| container: | |
| "border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-900/20", | |
| icon: "text-green-600 dark:text-green-500", | |
| title: "text-green-800 dark:text-green-400", | |
| text: "text-green-700 dark:text-green-300", | |
| closeButton: | |
| "text-green-600 hover:text-green-800 dark:text-green-400 dark:hover:text-green-200", | |
| }, | |
| error: { | |
| container: | |
| "border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-900/20", | |
| icon: "text-red-600 dark:text-red-500", | |
| title: "text-red-800 dark:text-red-400", | |
| text: "text-red-700 dark:text-red-300", | |
| closeButton: | |
| "text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-200", | |
| }, | |
| warning: { | |
| container: | |
| "border-yellow-200 bg-yellow-50 dark:border-yellow-800 dark:bg-yellow-900/20", | |
| icon: "text-yellow-600 dark:text-yellow-500", | |
| title: "text-yellow-800 dark:text-yellow-400", | |
| text: "text-yellow-700 dark:text-yellow-300", | |
| closeButton: | |
| "text-yellow-600 hover:text-yellow-800 dark:text-yellow-400 dark:hover:text-yellow-200", | |
| }, | |
| info: { | |
| container: | |
| "border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-900/20", | |
| icon: "text-blue-600 dark:text-blue-500", | |
| title: "text-blue-800 dark:text-blue-400", | |
| text: "text-blue-700 dark:text-blue-300", | |
| closeButton: | |
| "text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-200", | |
| }, | |
| }; | |
| const icons = { | |
| success: CheckCircle, | |
| error: XCircle, | |
| warning: AlertTriangle, | |
| info: Info, | |
| }; | |
| export default function Message({ | |
| type, | |
| title, | |
| message, | |
| children, | |
| onClose, | |
| className = "", | |
| }: MessageProps) { | |
| const styles = messageStyles[type]; | |
| const Icon = icons[type]; | |
| return ( | |
| <div className={cn("rounded-lg border p-4", styles.container, className)}> | |
| <div className="flex items-start gap-3"> | |
| <Icon className={cn("mt-0.5 h-5 w-5 flex-shrink-0", styles.icon)} /> | |
| <div className="flex-1"> | |
| <h3 className={cn("font-semibold", styles.title)}>{title}</h3> | |
| {message && ( | |
| <p className={cn("mt-1 text-sm", styles.text)}>{message}</p> | |
| )} | |
| {children && ( | |
| <div className={cn("mt-2 text-sm", styles.text)}>{children}</div> | |
| )} | |
| </div> | |
| {onClose && ( | |
| <button | |
| onClick={onClose} | |
| className={cn( | |
| "flex-shrink-0 rounded p-1 transition-colors", | |
| styles.closeButton | |
| )} | |
| aria-label="Close message" | |
| > | |
| <X className="h-4 w-4" /> | |
| </button> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |