import type { ReactNode } from 'react' import { AlertCircle, AlertTriangle, CheckCircle2, Clock, Info } from 'lucide-react' export type BannerTone = 'info' | 'success' | 'warning' | 'danger' | 'neutral' interface Props { tone?: BannerTone title?: ReactNode children?: ReactNode /** Optional leading icon override. Falls back to a tone-appropriate icon. */ icon?: ReactNode /** Optional trailing actions (buttons, links). */ actions?: ReactNode /** ARIA role: `alert` for important conditions, `status` for soft notices. */ role?: 'alert' | 'status' /** Extra class names appended to the outer container. */ className?: string } const TONE_CLASS: Record = { info: 'border-indigo-200 bg-indigo-50 text-indigo-900 dark:border-indigo-500/30 dark:bg-indigo-500/10 dark:text-indigo-100', success: 'border-emerald-200 bg-emerald-50 text-emerald-900 dark:border-emerald-500/30 dark:bg-emerald-500/10 dark:text-emerald-100', warning: 'border-amber-300/70 bg-amber-50 text-amber-900 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-100', danger: 'border-rose-200 bg-rose-50 text-rose-900 dark:border-rose-500/30 dark:bg-rose-500/10 dark:text-rose-100', neutral: 'border-[rgb(var(--line))] bg-[rgb(var(--bg-muted))] text-[rgb(var(--text-strong))]', } function defaultIcon(tone: BannerTone): ReactNode { switch (tone) { case 'info': return case 'success': return case 'warning': return case 'danger': return default: return } } /** * Inline banner — single primitive for soft alerts, info notices, * warnings, and rejected-action states. Replaces ad-hoc colored cards * scattered across pages so tone, spacing, and accessibility are * defined in one place. */ export default function Banner({ tone = 'info', title, children, icon, actions, role, className, }: Props) { const resolvedRole = role ?? (tone === 'danger' || tone === 'warning' ? 'alert' : 'status') return (
{icon ?? defaultIcon(tone)}
{title &&
{title}
} {children && (
{children}
)}
{actions &&
{actions}
}
) }