import { css, html } from "lit"; import { classMap } from "lit/directives/class-map.js"; import { BaseElement } from "../../core/base-element.ts"; /** * CTAlert - Alert message display component with variants and dismissible option * * @element ct-alert * * @attr {string} variant - Visual style variant: "default" | "destructive" | "warning" | "success" | "info" * @attr {boolean} dismissible - Whether the alert can be dismissed with an X button * * @slot icon - Alert icon * @slot title - Alert title * @slot description - Alert description * @slot - Default slot for alert content * * @fires ct-dismiss - Fired when alert is dismissed * * @example * * ⚠️ *

Error

*

Something went wrong

*
*/ export type AlertVariant = | "default" | "destructive" | "warning" | "success" | "info"; export class CTAlert extends BaseElement { static override styles = css` :host { display: block; /* Default color values if not provided */ --background: #ffffff; --foreground: #0f172a; --muted: #f8fafc; --muted-foreground: #64748b; --primary: #0f172a; --primary-foreground: #f8fafc; --destructive: #dc2626; --destructive-foreground: #fef2f2; --warning: #f59e0b; --warning-foreground: #451a03; --success: #10b981; --success-foreground: #f0fdf4; --info: #3b82f6; --info-foreground: #eff6ff; --border: #e2e8f0; --ring: #94a3b8; } .alert { position: relative; display: flex; width: 100%; border-radius: 0.5rem; border: 1px solid; padding: 1rem; gap: 0.75rem; font-family: inherit; transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1); } /* Alert icon */ .alert-icon { flex-shrink: 0; width: 1rem; height: 1rem; } .alert-icon:empty { display: none; } /* Alert content */ .alert-content { flex: 1; display: flex; flex-direction: column; gap: 0.125rem; } /* Alert title */ .alert-title { font-size: 0.875rem; font-weight: 500; line-height: 1; letter-spacing: -0.025em; } .alert-title:empty { display: none; } /* Alert description */ .alert-description { font-size: 0.875rem; line-height: 1.5; opacity: 0.9; } .alert-description:empty { display: none; } /* Dismiss button */ .dismiss-button { all: unset; box-sizing: border-box; position: absolute; right: 0.5rem; top: 0.5rem; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; opacity: 0.7; transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1); border-radius: 0.25rem; padding: 0.25rem; width: 1.5rem; height: 1.5rem; } .dismiss-button:hover { opacity: 1; } .dismiss-button:focus-visible { outline: 2px solid transparent; outline-offset: 2px; box-shadow: 0 0 0 2px var(--ring, #94a3b8); } .dismiss-button svg { width: 1rem; height: 1rem; } /* Default variant */ .alert.variant-default { background-color: var(--background, #ffffff); color: var(--foreground, #0f172a); border-color: var(--border, #e2e8f0); } .alert.variant-default .alert-icon { color: var(--foreground, #0f172a); } /* Destructive variant */ .alert.variant-destructive { background-color: var(--destructive-foreground, #fef2f2); color: var(--destructive, #dc2626); border-color: var(--destructive, #dc2626); } .alert.variant-destructive .alert-icon { color: var(--destructive, #dc2626); } .alert.variant-destructive .alert-title { color: var(--destructive, #dc2626); } .alert.variant-destructive .alert-description { color: var(--destructive, #dc2626); opacity: 0.8; } /* Warning variant */ .alert.variant-warning { background-color: #fef3c7; color: var(--warning-foreground, #451a03); border-color: var(--warning, #f59e0b); } .alert.variant-warning .alert-icon { color: var(--warning, #f59e0b); } .alert.variant-warning .alert-title { color: var(--warning-foreground, #451a03); } .alert.variant-warning .alert-description { color: var(--warning-foreground, #451a03); opacity: 0.8; } /* Success variant */ .alert.variant-success { background-color: var(--success-foreground, #f0fdf4); color: #065f46; border-color: var(--success, #10b981); } .alert.variant-success .alert-icon { color: var(--success, #10b981); } .alert.variant-success .alert-title { color: #065f46; } .alert.variant-success .alert-description { color: #065f46; opacity: 0.8; } /* Info variant */ .alert.variant-info { background-color: var(--info-foreground, #eff6ff); color: #1e3a8a; border-color: var(--info, #3b82f6); } .alert.variant-info .alert-icon { color: var(--info, #3b82f6); } .alert.variant-info .alert-title { color: #1e3a8a; } .alert.variant-info .alert-description { color: #1e3a8a; opacity: 0.8; } /* Slot styles */ ::slotted(*) { margin: 0; } ::slotted([slot="icon"]) { width: 1rem; height: 1rem; } /* Adjust padding when dismissible */ :host([dismissible]) .alert { padding-right: 2.5rem; } `; static override properties = { variant: { type: String }, dismissible: { type: Boolean, reflect: true }, }; declare variant: AlertVariant; declare dismissible: boolean; constructor() { super(); this.variant = "default"; this.dismissible = false; } override render() { const classes = { alert: true, [`variant-${this.variant}`]: true, }; return html` `; } private _handleDismiss = (event: Event): void => { event.preventDefault(); event.stopPropagation(); // Emit ct-dismiss event this.emit("ct-dismiss", { variant: this.variant, }); }; } globalThis.customElements.define("ct-alert", CTAlert);