109 lines
2.6 KiB
TypeScript
Raw Normal View History

"use client";
import React from "react";
import {
CheckCircleIcon,
InformationCircleIcon,
ExclamationTriangleIcon,
XCircleIcon,
} from "@heroicons/react/24/outline";
type Variant = "success" | "info" | "warning" | "error";
type IconType = React.ComponentType<React.SVGProps<SVGSVGElement>>;
const variantClasses: Record<
Variant,
{ bg: string; border: string; text: string; icon: string; Icon: IconType }
> = {
success: {
bg: "bg-success-soft",
border: "border-success/30",
text: "text-success",
icon: "text-success",
Icon: CheckCircleIcon,
},
info: {
bg: "bg-info-soft",
border: "border-info/30",
text: "text-info",
icon: "text-info",
Icon: InformationCircleIcon,
},
warning: {
bg: "bg-warning-soft",
border: "border-warning/35",
text: "text-foreground",
icon: "text-warning",
Icon: ExclamationTriangleIcon,
},
error: {
bg: "bg-danger-soft",
border: "border-danger/30",
text: "text-danger",
icon: "text-danger",
Icon: XCircleIcon,
},
};
interface AlertBannerProps extends React.HTMLAttributes<HTMLDivElement> {
variant?: Variant;
title?: string;
children?: React.ReactNode;
icon?: React.ReactNode;
size?: "sm" | "md";
elevated?: boolean;
onClose?: () => void;
}
export function AlertBanner({
variant = "info",
title,
children,
icon,
size = "md",
elevated = false,
onClose,
className,
...rest
}: AlertBannerProps) {
const styles = variantClasses[variant];
const Icon = styles.Icon;
const padding = size === "sm" ? "p-3" : "p-4";
const radius = "rounded-xl";
const shadow = elevated ? "shadow-sm" : "";
const role = variant === "error" || variant === "warning" ? "alert" : "status";
return (
<div
className={[radius, padding, "border", shadow, styles.bg, styles.border, className]
.filter(Boolean)
.join(" ")}
role={role}
{...rest}
>
<div className="flex items-start gap-3">
<div className="mt-0.5 flex-shrink-0">
{icon ? icon : <Icon className={["h-5 w-5", styles.icon].join(" ")} />}
</div>
<div className="flex-1">
{title && <p className={["font-medium", styles.text].join(" ")}>{title}</p>}
{children && (
<div className={["text-sm mt-1 text-foreground/80"].join(" ")}>{children}</div>
)}
</div>
{onClose && (
<button
onClick={onClose}
aria-label="Close alert"
className="text-muted-foreground hover:text-foreground transition-colors"
>
×
</button>
)}
</div>
</div>
);
}
export type { AlertBannerProps };