Files
ui-essentials/src/lib/components/feedback/alert/alert.component.ts
Jules 0a0cade343 Initial commit - Angular library: ui-essentials
🎯 Implementation Complete!

This library has been extracted from the monorepo and is ready for Git submodule distribution.

Features:
- Standardized SCSS imports (no relative paths)
- Optimized public-api.ts exports
- Independent Angular library structure
- Ready for consumer integration as submodule

🚀 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-11 21:12:46 +10:00

136 lines
4.0 KiB
TypeScript

import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { IconDefinition } from '@fortawesome/free-solid-svg-icons';
import {
faCheckCircle,
faExclamationTriangle,
faExclamationCircle,
faInfoCircle,
faTimes
} from '@fortawesome/free-solid-svg-icons';
type AlertSize = 'sm' | 'md' | 'lg';
type AlertVariant = 'primary' | 'success' | 'warning' | 'danger' | 'info';
@Component({
selector: 'ui-alert',
standalone: true,
imports: [CommonModule, FontAwesomeModule],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
template: `
<div
class="ui-alert"
[class.ui-alert--sm]="size === 'sm'"
[class.ui-alert--md]="size === 'md'"
[class.ui-alert--lg]="size === 'lg'"
[class.ui-alert--primary]="variant === 'primary'"
[class.ui-alert--success]="variant === 'success'"
[class.ui-alert--warning]="variant === 'warning'"
[class.ui-alert--danger]="variant === 'danger'"
[class.ui-alert--info]="variant === 'info'"
[class.ui-alert--dismissible]="dismissible"
[attr.role]="role"
[attr.aria-live]="ariaLive"
[attr.aria-labelledby]="title ? 'alert-title-' + alertId : null"
[attr.aria-describedby]="'alert-content-' + alertId">
@if (showIcon && alertIcon) {
<fa-icon
class="ui-alert__icon"
[icon]="alertIcon"
[attr.aria-hidden]="true">
</fa-icon>
}
<div class="ui-alert__content">
@if (title) {
<h4
class="ui-alert__title"
[class.ui-alert__title--bold]="boldTitle"
[id]="'alert-title-' + alertId">
{{ title }}
</h4>
}
<div
class="ui-alert__message"
[id]="'alert-content-' + alertId">
<ng-content></ng-content>
</div>
@if (actions && actions.length > 0) {
<div class="ui-alert__actions">
<ng-content select="[slot=actions]"></ng-content>
</div>
}
</div>
@if (dismissible) {
<button
type="button"
class="ui-alert__dismiss"
[attr.aria-label]="dismissLabel"
(click)="handleDismiss()"
(keydown)="handleDismissKeydown($event)">
<fa-icon [icon]="faTimes" [attr.aria-hidden]="true"></fa-icon>
</button>
}
</div>
`,
styleUrl: './alert.component.scss'
})
export class AlertComponent {
@Input() size: AlertSize = 'md';
@Input() variant: AlertVariant = 'primary';
@Input() title?: string;
@Input() boldTitle = false;
@Input() showIcon = true;
@Input() dismissible = false;
@Input() dismissLabel = 'Dismiss alert';
@Input() role = 'alert';
@Input() ariaLive: 'polite' | 'assertive' | 'off' = 'polite';
@Input() actions: any[] = [];
@Output() dismissed = new EventEmitter<void>();
// Icons
readonly faCheckCircle = faCheckCircle;
readonly faExclamationTriangle = faExclamationTriangle;
readonly faExclamationCircle = faExclamationCircle;
readonly faInfoCircle = faInfoCircle;
readonly faTimes = faTimes;
// Generate unique ID for accessibility
readonly alertId = Math.random().toString(36).substr(2, 9);
get alertIcon(): IconDefinition | null {
if (!this.showIcon) return null;
switch (this.variant) {
case 'success':
return this.faCheckCircle;
case 'warning':
return this.faExclamationTriangle;
case 'danger':
return this.faExclamationCircle;
case 'info':
return this.faInfoCircle;
case 'primary':
default:
return this.faInfoCircle;
}
}
handleDismiss(): void {
this.dismissed.emit();
}
handleDismissKeydown(event: KeyboardEvent): void {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
this.handleDismiss();
}
}
}