diff --git a/projects/demo-ui-essentials/src/app/demos/demos.routes.ts b/projects/demo-ui-essentials/src/app/demos/demos.routes.ts
index b772630..a03f87d 100644
--- a/projects/demo-ui-essentials/src/app/demos/demos.routes.ts
+++ b/projects/demo-ui-essentials/src/app/demos/demos.routes.ts
@@ -43,6 +43,12 @@ import { ProgressCircleDemoComponent } from './progress-circle-demo/progress-cir
import { RangeSliderDemoComponent } from './range-slider-demo/range-slider-demo.component';
import { DividerDemoComponent } from './divider-demo/divider-demo.component';
import { TooltipDemoComponent } from './tooltip-demo/tooltip-demo.component';
+import { AccordionDemoComponent } from './accordion-demo/accordion-demo.component';
+import { PopoverDemoComponent } from './popover-demo/popover-demo.component';
+import { AlertDemoComponent } from './alert-demo/alert-demo.component';
+import { ToastDemoComponent } from './toast-demo/toast-demo.component';
+import { TreeViewDemoComponent } from './tree-view-demo/tree-view-demo.component';
+import { TimelineDemoComponent } from './timeline-demo/timeline-demo.component';
@Component({
@@ -213,6 +219,30 @@ import { TooltipDemoComponent } from './tooltip-demo/tooltip-demo.component';
}
+ @case ("accordion") {
+
+ }
+
+ @case ("popover") {
+
+ }
+
+ @case ("alert") {
+
+ }
+
+ @case ("toast") {
+
+ }
+
+ @case ("tree-view") {
+
+ }
+
+ @case ("timeline") {
+
+ }
+
}
`,
@@ -227,7 +257,8 @@ import { TooltipDemoComponent } from './tooltip-demo/tooltip-demo.component';
GridSystemDemoComponent, SpacerDemoComponent, ContainerDemoComponent, PaginationDemoComponent,
SkeletonLoaderDemoComponent, EmptyStateDemoComponent, FileUploadDemoComponent, FormFieldDemoComponent,
AutocompleteDemoComponent, BackdropDemoComponent, OverlayContainerDemoComponent, LoadingSpinnerDemoComponent,
- ProgressCircleDemoComponent, RangeSliderDemoComponent, DividerDemoComponent, TooltipDemoComponent]
+ ProgressCircleDemoComponent, RangeSliderDemoComponent, DividerDemoComponent, TooltipDemoComponent, AccordionDemoComponent,
+ PopoverDemoComponent, AlertDemoComponent, ToastDemoComponent, TreeViewDemoComponent, TimelineDemoComponent]
})
diff --git a/projects/demo-ui-essentials/src/app/demos/toast-demo/toast-demo.component.scss b/projects/demo-ui-essentials/src/app/demos/toast-demo/toast-demo.component.scss
new file mode 100644
index 0000000..194602e
--- /dev/null
+++ b/projects/demo-ui-essentials/src/app/demos/toast-demo/toast-demo.component.scss
@@ -0,0 +1,233 @@
+@use '../../../../../../../../shared-ui/src/styles/semantic' as *;
+
+.demo-container {
+ padding: $semantic-spacing-layout-section-md;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.demo-section {
+ margin-bottom: $semantic-spacing-layout-section-lg;
+
+ h3 {
+ font-family: map-get($semantic-typography-heading-h3, font-family);
+ font-size: map-get($semantic-typography-heading-h3, font-size);
+ font-weight: map-get($semantic-typography-heading-h3, font-weight);
+ line-height: map-get($semantic-typography-heading-h3, line-height);
+ color: $semantic-color-text-primary;
+ margin-bottom: $semantic-spacing-content-heading;
+ }
+
+ h4 {
+ font-family: map-get($semantic-typography-heading-h4, font-family);
+ font-size: map-get($semantic-typography-heading-h4, font-size);
+ font-weight: map-get($semantic-typography-heading-h4, font-weight);
+ line-height: map-get($semantic-typography-heading-h4, line-height);
+ color: $semantic-color-text-primary;
+ margin-bottom: $semantic-spacing-content-heading;
+ }
+
+ p {
+ font-family: map-get($semantic-typography-body-medium, font-family);
+ font-size: map-get($semantic-typography-body-medium, font-size);
+ font-weight: map-get($semantic-typography-body-medium, font-weight);
+ line-height: map-get($semantic-typography-body-medium, line-height);
+ color: $semantic-color-text-secondary;
+ margin-bottom: $semantic-spacing-content-paragraph;
+ }
+}
+
+.demo-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: $semantic-spacing-grid-gap-md;
+ align-items: flex-start;
+}
+
+.toast-wrapper {
+ flex: 0 0 auto;
+}
+
+.controls-section {
+ margin-bottom: $semantic-spacing-layout-section-md;
+}
+
+.button-group {
+ display: flex;
+ flex-wrap: wrap;
+ gap: $semantic-spacing-component-sm;
+ margin-bottom: $semantic-spacing-component-lg;
+}
+
+.demo-button {
+ display: inline-flex;
+ align-items: center;
+ gap: $semantic-spacing-component-xs;
+ padding: $semantic-spacing-interactive-button-padding-y $semantic-spacing-interactive-button-padding-x;
+ background: $semantic-color-surface-primary;
+ border: $semantic-border-width-1 solid $semantic-color-border-primary;
+ border-radius: $semantic-border-button-radius;
+ color: $semantic-color-text-primary;
+ font-family: map-get($semantic-typography-button-medium, font-family);
+ font-size: map-get($semantic-typography-button-medium, font-size);
+ font-weight: map-get($semantic-typography-button-medium, font-weight);
+ line-height: map-get($semantic-typography-button-medium, line-height);
+ cursor: pointer;
+ transition: all $semantic-motion-duration-fast $semantic-motion-easing-ease;
+ min-height: $semantic-sizing-button-height-md;
+
+ &:hover {
+ box-shadow: $semantic-shadow-button-hover;
+ background: $semantic-color-surface-secondary;
+ }
+
+ &:focus-visible {
+ outline: 2px solid $semantic-color-focus;
+ outline-offset: 2px;
+ }
+
+ &:active {
+ box-shadow: $semantic-shadow-button-rest;
+ transform: translateY(1px);
+ }
+
+ &--primary {
+ background: $semantic-color-primary;
+ color: $semantic-color-on-primary;
+ border-color: $semantic-color-primary;
+
+ &:hover {
+ background: $semantic-color-primary;
+ opacity: $semantic-opacity-hover;
+ }
+ }
+
+ &--secondary {
+ background: $semantic-color-secondary;
+ color: $semantic-color-on-secondary;
+ border-color: $semantic-color-secondary;
+
+ &:hover {
+ background: $semantic-color-secondary;
+ opacity: $semantic-opacity-hover;
+ }
+ }
+
+ &--warning {
+ background: $semantic-color-warning;
+ color: $semantic-color-on-warning;
+ border-color: $semantic-color-warning;
+
+ &:hover {
+ background: $semantic-color-warning;
+ opacity: $semantic-opacity-hover;
+ }
+ }
+
+ &--danger {
+ background: $semantic-color-danger;
+ color: $semantic-color-on-danger;
+ border-color: $semantic-color-danger;
+
+ &:hover {
+ background: $semantic-color-danger;
+ opacity: $semantic-opacity-hover;
+ }
+ }
+}
+
+.toast-container {
+ position: fixed;
+ top: $semantic-spacing-layout-section-md;
+ right: $semantic-spacing-layout-section-md;
+ z-index: $semantic-z-index-modal;
+ display: flex;
+ flex-direction: column;
+ gap: $semantic-spacing-component-sm;
+ max-width: 400px;
+ width: 100%;
+ pointer-events: none;
+
+ .ui-toast {
+ pointer-events: auto;
+ }
+}
+
+.stats-section {
+ margin-top: $semantic-spacing-layout-section-md;
+ padding: $semantic-spacing-component-lg;
+ background: $semantic-color-surface-elevated;
+ border: $semantic-border-width-1 solid $semantic-color-border-subtle;
+ border-radius: $semantic-border-card-radius;
+}
+
+.stats-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: $semantic-spacing-grid-gap-md;
+ margin-bottom: $semantic-spacing-component-lg;
+}
+
+.stat-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: $semantic-spacing-component-sm;
+ background: $semantic-color-surface-primary;
+ border: $semantic-border-width-1 solid $semantic-color-border-subtle;
+ border-radius: $semantic-border-radius-sm;
+}
+
+.stat-label {
+ font-family: map-get($semantic-typography-body-medium, font-family);
+ font-size: map-get($semantic-typography-body-medium, font-size);
+ font-weight: map-get($semantic-typography-body-medium, font-weight);
+ line-height: map-get($semantic-typography-body-medium, line-height);
+ color: $semantic-color-text-secondary;
+}
+
+.stat-value {
+ font-family: map-get($semantic-typography-label, font-family);
+ font-size: map-get($semantic-typography-label, font-size);
+ font-weight: $semantic-typography-font-weight-semibold;
+ line-height: map-get($semantic-typography-label, line-height);
+ color: $semantic-color-primary;
+}
+
+// Responsive Design
+@media (max-width: $semantic-breakpoint-md - 1) {
+ .demo-container {
+ padding: $semantic-spacing-layout-section-sm;
+ }
+
+ .demo-row {
+ flex-direction: column;
+ }
+
+ .toast-container {
+ top: $semantic-spacing-layout-section-sm;
+ right: $semantic-spacing-layout-section-sm;
+ left: $semantic-spacing-layout-section-sm;
+ max-width: none;
+ }
+
+ .button-group {
+ flex-direction: column;
+ }
+
+ .stats-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: $semantic-breakpoint-sm - 1) {
+ .demo-container {
+ padding: $semantic-spacing-layout-section-xs;
+ }
+
+ .toast-container {
+ top: $semantic-spacing-layout-section-xs;
+ right: $semantic-spacing-layout-section-xs;
+ left: $semantic-spacing-layout-section-xs;
+ }
+}
\ No newline at end of file
diff --git a/projects/demo-ui-essentials/src/app/demos/toast-demo/toast-demo.component.ts b/projects/demo-ui-essentials/src/app/demos/toast-demo/toast-demo.component.ts
new file mode 100644
index 0000000..bfa30ab
--- /dev/null
+++ b/projects/demo-ui-essentials/src/app/demos/toast-demo/toast-demo.component.ts
@@ -0,0 +1,247 @@
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { ToastComponent } from '../../../../../../../ui-essentials/src/lib/components/feedback/toast/toast.component';
+import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
+import { faPlay, faStop, faRedo } from '@fortawesome/free-solid-svg-icons';
+
+@Component({
+ selector: 'ui-toast-demo',
+ standalone: true,
+ imports: [CommonModule, ToastComponent, FontAwesomeModule],
+ template: `
+
+
Toast Demo
+
Toast notifications provide contextual feedback messages for user actions.
+
+
+
+ Sizes
+
+ @for (size of sizes; track size) {
+
+
+ This is a {{ size }} toast message.
+
+
+ }
+
+
+
+
+
+ Variants
+
+ @for (variant of variants; track variant) {
+
+
+ {{ getVariantMessage(variant) }}
+
+
+ }
+
+
+
+
+
+ Configuration Options
+
+
+
+ This toast cannot be manually dismissed.
+
+
+
+
+
+ This toast has no icon displayed.
+
+
+
+
+
+ This toast shows a progress indicator.
+
+
+
+
+
+
+
+ Interactive Examples
+
+
+
Toast Controls
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @for (toast of activeToasts; track toast.id) {
+
+ {{ toast.message }}
+
+ }
+
+
+
+
+
Demo Statistics
+
+
+ Toasts Shown:
+ {{ toastCount }}
+
+
+ Currently Active:
+ {{ activeToasts.length }}
+
+
+ Auto Dismissed:
+ {{ expiredCount }}
+
+
+ Manually Dismissed:
+ {{ dismissedCount }}
+
+
+
+
+
+
+
+ `,
+ styleUrl: './toast-demo.component.scss'
+})
+export class ToastDemoComponent {
+ sizes = ['sm', 'md', 'lg'] as const;
+ variants = ['primary', 'success', 'warning', 'danger', 'info'] as const;
+
+ // Icons
+ readonly faPlay = faPlay;
+ readonly faStop = faStop;
+ readonly faRedo = faRedo;
+
+ // Toast management
+ activeToasts: any[] = [];
+ private nextToastId = 1;
+
+ // Statistics
+ toastCount = 0;
+ expiredCount = 0;
+ dismissedCount = 0;
+
+ getVariantTitle(variant: string): string {
+ const titles = {
+ primary: 'Primary Toast',
+ success: 'Success Toast',
+ warning: 'Warning Toast',
+ danger: 'Error Toast',
+ info: 'Information Toast'
+ };
+ return titles[variant as keyof typeof titles] || 'Toast';
+ }
+
+ getVariantMessage(variant: string): string {
+ const messages = {
+ primary: 'This is a primary notification message.',
+ success: 'Operation completed successfully!',
+ warning: 'Please review your input carefully.',
+ danger: 'An error occurred during processing.',
+ info: 'Here is some helpful information.'
+ };
+ return messages[variant as keyof typeof messages] || 'Toast message';
+ }
+
+ showToast(variant: any, title: string, message: string): void {
+ const toast = {
+ id: this.nextToastId++,
+ variant,
+ title,
+ message,
+ size: 'md' as const,
+ duration: 4000,
+ autoDismiss: true,
+ showProgress: true
+ };
+
+ this.activeToasts.push(toast);
+ this.toastCount++;
+ }
+
+ removeToast(id: number): void {
+ const index = this.activeToasts.findIndex(toast => toast.id === id);
+ if (index > -1) {
+ this.activeToasts.splice(index, 1);
+ }
+ }
+
+ clearToasts(): void {
+ this.dismissedCount += this.activeToasts.length;
+ this.activeToasts = [];
+ }
+
+ resetStats(): void {
+ this.toastCount = 0;
+ this.expiredCount = 0;
+ this.dismissedCount = 0;
+ this.activeToasts = [];
+ this.nextToastId = 1;
+ }
+}
\ No newline at end of file
diff --git a/projects/demo-ui-essentials/src/app/features/dashboard/dashboard.component.ts b/projects/demo-ui-essentials/src/app/features/dashboard/dashboard.component.ts
index 0f1e9de..635ad54 100644
--- a/projects/demo-ui-essentials/src/app/features/dashboard/dashboard.component.ts
+++ b/projects/demo-ui-essentials/src/app/features/dashboard/dashboard.component.ts
@@ -9,7 +9,8 @@ import {
faVideo, faComment, faMousePointer, faLayerGroup, faSquare, faCalendarDays, faClock,
faGripVertical, faArrowsAlt, faBoxOpen, faChevronLeft, faSpinner, faExclamationTriangle,
faCloudUploadAlt, faFileText, faListAlt, faCircle, faExpandArrowsAlt, faCircleNotch, faSliders,
- faMinus, faInfoCircle
+ faMinus, faInfoCircle, faChevronDown, faCaretUp, faExclamationCircle, faSitemap, faStream,
+ faBell
} from '@fortawesome/free-solid-svg-icons';
import { DemoRoutes } from '../../demos';
import { ScrollContainerComponent } from '../../../../../ui-essentials/src/lib/layouts/scroll-container.component';
@@ -94,6 +95,11 @@ export class DashboardComponent {
faSliders = faSliders;
faMinus = faMinus;
faInfoCircle = faInfoCircle;
+ faChevronDown = faChevronDown;
+ faCaretUp = faCaretUp;
+ faSitemap = faSitemap;
+ faStream = faStream;
+ faBell = faBell;
menuItems: any = []
@@ -145,6 +151,7 @@ export class DashboardComponent {
// Data Display category
const dataDisplayChildren = [
+ this.createChildItem("accordion", "Accordion", this.faChevronDown),
this.createChildItem("table", "Tables", this.faTable),
this.createChildItem("lists", "Lists", this.faList),
this.createChildItem("progress", "Progress Bars", this.faCheckSquare),
@@ -152,7 +159,9 @@ export class DashboardComponent {
this.createChildItem("avatar", "Avatars", this.faUserCircle),
this.createChildItem("cards", "Cards", this.faIdCard),
this.createChildItem("divider", "Divider", this.faMinus),
- this.createChildItem("tooltip", "Tooltip", this.faInfoCircle)
+ this.createChildItem("timeline", "Timeline", this.faStream),
+ this.createChildItem("tooltip", "Tooltip", this.faInfoCircle),
+ this.createChildItem("tree-view", "Tree View", this.faSitemap)
];
this.addMenuItem("data-display", "Data Display", this.faEye, dataDisplayChildren);
@@ -180,6 +189,8 @@ export class DashboardComponent {
// Feedback category
const feedbackChildren = [
+ this.createChildItem("alert", "Alert", faExclamationCircle),
+ this.createChildItem("toast", "Toast", this.faBell),
this.createChildItem("chips", "Chips", this.faTags),
this.createChildItem("loading-spinner", "Loading Spinner", this.faSpinner),
this.createChildItem("skeleton-loader", "Skeleton Loader", this.faSpinner),
@@ -201,6 +212,7 @@ export class DashboardComponent {
const overlaysChildren = [
this.createChildItem("modal", "Modal/Dialog", this.faSquare),
this.createChildItem("drawer", "Drawer/Sidebar", this.faBars),
+ this.createChildItem("popover", "Popover/Dropdown", this.faCaretUp),
this.createChildItem("backdrop", "Backdrop", this.faCircle),
this.createChildItem("overlay-container", "Overlay Container", this.faExpandArrowsAlt)
];
diff --git a/projects/ui-essentials/src/lib/components/feedback/index.ts b/projects/ui-essentials/src/lib/components/feedback/index.ts
index bb66b99..66d6892 100644
--- a/projects/ui-essentials/src/lib/components/feedback/index.ts
+++ b/projects/ui-essentials/src/lib/components/feedback/index.ts
@@ -1,6 +1,8 @@
+export * from "./alert";
export * from "./status-badge.component";
export * from "./skeleton-loader";
export * from "./empty-state";
export * from "./loading-spinner";
export * from "./progress-circle";
+export * from "./toast";
// export * from "./theme-switcher.component"; // Temporarily disabled due to CSS variable issues
diff --git a/projects/ui-essentials/src/lib/components/feedback/toast/index.ts b/projects/ui-essentials/src/lib/components/feedback/toast/index.ts
new file mode 100644
index 0000000..dab40b7
--- /dev/null
+++ b/projects/ui-essentials/src/lib/components/feedback/toast/index.ts
@@ -0,0 +1 @@
+export * from './toast.component';
\ No newline at end of file
diff --git a/projects/ui-essentials/src/lib/components/feedback/toast/toast.component.scss b/projects/ui-essentials/src/lib/components/feedback/toast/toast.component.scss
new file mode 100644
index 0000000..fa2fe03
--- /dev/null
+++ b/projects/ui-essentials/src/lib/components/feedback/toast/toast.component.scss
@@ -0,0 +1,289 @@
+@use "../../../../../../shared-ui/src/styles/semantic/index" as *;
+
+.ui-toast {
+ // Core Structure
+ display: flex;
+ position: relative;
+ align-items: flex-start;
+ width: 100%;
+ max-width: 400px;
+
+ // Layout & Spacing
+ padding: $semantic-spacing-component-md;
+ gap: $semantic-spacing-component-sm;
+
+ // Visual Design
+ background: $semantic-color-surface-elevated;
+ border: $semantic-border-width-1 solid $semantic-color-border-primary;
+ border-radius: $semantic-border-card-radius;
+ box-shadow: $semantic-shadow-elevation-3;
+
+ // Typography
+ font-family: map-get($semantic-typography-body-medium, font-family);
+ font-size: map-get($semantic-typography-body-medium, font-size);
+ font-weight: map-get($semantic-typography-body-medium, font-weight);
+ line-height: map-get($semantic-typography-body-medium, line-height);
+ color: $semantic-color-text-primary;
+
+ // Transitions
+ transition: all $semantic-motion-duration-normal $semantic-motion-easing-ease;
+
+ // Size Variants
+ &--sm {
+ padding: $semantic-spacing-component-sm;
+ gap: $semantic-spacing-component-xs;
+ max-width: 320px;
+ font-family: map-get($semantic-typography-body-small, font-family);
+ font-size: map-get($semantic-typography-body-small, font-size);
+ font-weight: map-get($semantic-typography-body-small, font-weight);
+ line-height: map-get($semantic-typography-body-small, line-height);
+ }
+
+ &--md {
+ // Default styles already applied above
+ }
+
+ &--lg {
+ padding: $semantic-spacing-component-lg;
+ gap: $semantic-spacing-component-md;
+ max-width: 480px;
+ font-family: map-get($semantic-typography-body-large, font-family);
+ font-size: map-get($semantic-typography-body-large, font-size);
+ font-weight: map-get($semantic-typography-body-large, font-weight);
+ line-height: map-get($semantic-typography-body-large, line-height);
+ }
+
+ // Color Variants
+ &--primary {
+ border-color: $semantic-color-primary;
+ border-left-width: 4px;
+
+ .ui-toast__icon {
+ color: $semantic-color-primary;
+ }
+
+ .ui-toast__title {
+ color: $semantic-color-primary;
+ }
+ }
+
+ &--success {
+ border-color: $semantic-color-success;
+ border-left-width: 4px;
+
+ .ui-toast__icon {
+ color: $semantic-color-success;
+ }
+
+ .ui-toast__title {
+ color: $semantic-color-success;
+ }
+ }
+
+ &--warning {
+ border-color: $semantic-color-warning;
+ border-left-width: 4px;
+
+ .ui-toast__icon {
+ color: $semantic-color-warning;
+ }
+
+ .ui-toast__title {
+ color: $semantic-color-warning;
+ }
+ }
+
+ &--danger {
+ border-color: $semantic-color-danger;
+ border-left-width: 4px;
+
+ .ui-toast__icon {
+ color: $semantic-color-danger;
+ }
+
+ .ui-toast__title {
+ color: $semantic-color-danger;
+ }
+ }
+
+ &--info {
+ border-color: $semantic-color-info;
+ border-left-width: 4px;
+
+ .ui-toast__icon {
+ color: $semantic-color-info;
+ }
+
+ .ui-toast__title {
+ color: $semantic-color-info;
+ }
+ }
+
+ // BEM Elements
+ &__icon {
+ flex-shrink: 0;
+ color: $semantic-color-text-secondary;
+ font-size: $semantic-sizing-icon-inline;
+ margin-top: 2px; // Slight optical alignment
+ }
+
+ &__content {
+ flex: 1;
+ min-width: 0; // Prevents flex overflow
+ }
+
+ &__title {
+ margin: 0 0 $semantic-spacing-content-line-tight 0;
+ font-family: map-get($semantic-typography-label, font-family);
+ font-size: map-get($semantic-typography-label, font-size);
+ font-weight: map-get($semantic-typography-label, font-weight);
+ line-height: map-get($semantic-typography-label, line-height);
+ color: $semantic-color-text-primary;
+
+ &--bold {
+ font-weight: $semantic-typography-font-weight-semibold;
+ }
+ }
+
+ &__message {
+ margin: 0;
+ color: $semantic-color-text-secondary;
+ }
+
+ &__actions {
+ margin-top: $semantic-spacing-component-sm;
+ display: flex;
+ gap: $semantic-spacing-component-xs;
+ flex-wrap: wrap;
+ }
+
+ &__dismiss {
+ position: absolute;
+ top: $semantic-spacing-component-xs;
+ right: $semantic-spacing-component-xs;
+ background: transparent;
+ border: none;
+ color: $semantic-color-text-tertiary;
+ cursor: pointer;
+ padding: $semantic-spacing-component-xs;
+ border-radius: $semantic-border-radius-sm;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-width: $semantic-sizing-touch-minimum;
+ min-height: $semantic-sizing-touch-minimum;
+
+ &:hover {
+ background: $semantic-color-surface-secondary;
+ color: $semantic-color-text-secondary;
+ }
+
+ &:focus-visible {
+ outline: 2px solid $semantic-color-focus;
+ outline-offset: 2px;
+ }
+
+ &:active {
+ background: $semantic-color-surface-primary;
+ }
+ }
+
+ // State Variants
+ &--dismissible {
+ padding-right: calc($semantic-spacing-component-lg + $semantic-sizing-touch-minimum);
+ }
+
+ // Toast-specific positioning and animations
+ &--entering {
+ animation: toast-slide-in $semantic-motion-duration-normal $semantic-motion-easing-ease forwards;
+ }
+
+ &--exiting {
+ animation: toast-slide-out $semantic-motion-duration-normal $semantic-motion-easing-ease forwards;
+ }
+
+ // Progress bar for auto-dismiss
+ &__progress {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 3px;
+ background: $semantic-color-surface-secondary;
+ border-radius: 0 0 $semantic-border-card-radius $semantic-border-card-radius;
+ overflow: hidden;
+
+ &-bar {
+ height: 100%;
+ background: currentColor;
+ width: 100%;
+ transform-origin: left;
+ animation: toast-progress var(--duration) linear forwards;
+ }
+ }
+
+ &--with-progress {
+ padding-bottom: calc($semantic-spacing-component-md + 3px);
+ }
+
+ // Responsive Design
+ @media (max-width: $semantic-breakpoint-sm - 1) {
+ padding: $semantic-spacing-component-sm;
+ gap: $semantic-spacing-component-xs;
+ max-width: 100%;
+
+ &--lg {
+ padding: $semantic-spacing-component-md;
+ }
+
+ &__title {
+ font-family: map-get($semantic-typography-body-small, font-family);
+ font-size: map-get($semantic-typography-body-small, font-size);
+ font-weight: map-get($semantic-typography-body-small, font-weight);
+ line-height: map-get($semantic-typography-body-small, line-height);
+ }
+
+ &__message {
+ font-family: map-get($semantic-typography-caption, font-family);
+ font-size: map-get($semantic-typography-caption, font-size);
+ font-weight: map-get($semantic-typography-caption, font-weight);
+ line-height: map-get($semantic-typography-caption, line-height);
+ }
+
+ &--dismissible {
+ padding-right: calc($semantic-spacing-component-md + $semantic-sizing-touch-minimum);
+ }
+ }
+}
+
+// Keyframe animations
+@keyframes toast-slide-in {
+ from {
+ opacity: 0;
+ transform: translateX(100%);
+ }
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+@keyframes toast-slide-out {
+ from {
+ opacity: 1;
+ transform: translateX(0);
+ }
+ to {
+ opacity: 0;
+ transform: translateX(100%);
+ }
+}
+
+@keyframes toast-progress {
+ from {
+ transform: scaleX(1);
+ }
+ to {
+ transform: scaleX(0);
+ }
+}
\ No newline at end of file
diff --git a/projects/ui-essentials/src/lib/components/feedback/toast/toast.component.ts b/projects/ui-essentials/src/lib/components/feedback/toast/toast.component.ts
new file mode 100644
index 0000000..c6c1eb0
--- /dev/null
+++ b/projects/ui-essentials/src/lib/components/feedback/toast/toast.component.ts
@@ -0,0 +1,238 @@
+import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, ViewEncapsulation, OnInit, OnDestroy, ElementRef, inject } 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 ToastSize = 'sm' | 'md' | 'lg';
+type ToastVariant = 'primary' | 'success' | 'warning' | 'danger' | 'info';
+type ToastPosition = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center' | 'bottom-center';
+
+@Component({
+ selector: 'ui-toast',
+ standalone: true,
+ imports: [CommonModule, FontAwesomeModule],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ encapsulation: ViewEncapsulation.None,
+ template: `
+
+
+ @if (showIcon && toastIcon) {
+
+
+ }
+
+
+ @if (title) {
+
+ {{ title }}
+
+ }
+
+
+
+
+
+ @if (actions && actions.length > 0) {
+
+
+
+ }
+
+
+ @if (dismissible) {
+
+ }
+
+ @if (showProgress && autoDismiss && duration > 0) {
+
+ }
+
+ `,
+ styleUrl: './toast.component.scss'
+})
+export class ToastComponent implements OnInit, OnDestroy {
+ @Input() size: ToastSize = 'md';
+ @Input() variant: ToastVariant = 'primary';
+ @Input() title?: string;
+ @Input() boldTitle = false;
+ @Input() showIcon = true;
+ @Input() dismissible = true;
+ @Input() autoDismiss = true;
+ @Input() duration = 5000; // 5 seconds
+ @Input() showProgress = true;
+ @Input() position: ToastPosition = 'top-right';
+ @Input() dismissLabel = 'Dismiss toast';
+ @Input() role = 'alert';
+ @Input() ariaLive: 'polite' | 'assertive' | 'off' = 'assertive';
+ @Input() actions: any[] = [];
+
+ @Output() dismissed = new EventEmitter();
+ @Output() expired = new EventEmitter();
+ @Output() shown = new EventEmitter();
+ @Output() hidden = new EventEmitter();
+
+ // Icons
+ readonly faCheckCircle = faCheckCircle;
+ readonly faExclamationTriangle = faExclamationTriangle;
+ readonly faExclamationCircle = faExclamationCircle;
+ readonly faInfoCircle = faInfoCircle;
+ readonly faTimes = faTimes;
+
+ // Generate unique ID for accessibility
+ readonly toastId = Math.random().toString(36).substr(2, 9);
+
+ // State management
+ isEntering = false;
+ isExiting = false;
+ private autoDismissTimeout?: any;
+ private enterTimeout?: any;
+ private exitTimeout?: any;
+ private elementRef = inject(ElementRef);
+
+ get toastIcon(): 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;
+ }
+ }
+
+ ngOnInit(): void {
+ this.show();
+ }
+
+ ngOnDestroy(): void {
+ this.clearTimeouts();
+ }
+
+ show(): void {
+ this.isEntering = true;
+
+ this.enterTimeout = setTimeout(() => {
+ this.isEntering = false;
+ this.shown.emit();
+
+ if (this.autoDismiss && this.duration > 0) {
+ this.startAutoDismissTimer();
+ }
+ }, 300); // Animation duration
+ }
+
+ hide(): void {
+ this.clearTimeouts();
+ this.isExiting = true;
+
+ this.exitTimeout = setTimeout(() => {
+ this.isExiting = false;
+ this.hidden.emit();
+ }, 300); // Animation duration
+ }
+
+ handleDismiss(): void {
+ this.hide();
+ this.dismissed.emit();
+ }
+
+ handleDismissKeydown(event: KeyboardEvent): void {
+ if (event.key === 'Enter' || event.key === ' ') {
+ event.preventDefault();
+ this.handleDismiss();
+ }
+ }
+
+ private startAutoDismissTimer(): void {
+ if (this.autoDismissTimeout) {
+ clearTimeout(this.autoDismissTimeout);
+ }
+
+ this.autoDismissTimeout = setTimeout(() => {
+ this.hide();
+ this.expired.emit();
+ }, this.duration);
+ }
+
+ private clearTimeouts(): void {
+ if (this.autoDismissTimeout) {
+ clearTimeout(this.autoDismissTimeout);
+ this.autoDismissTimeout = undefined;
+ }
+
+ if (this.enterTimeout) {
+ clearTimeout(this.enterTimeout);
+ this.enterTimeout = undefined;
+ }
+
+ if (this.exitTimeout) {
+ clearTimeout(this.exitTimeout);
+ this.exitTimeout = undefined;
+ }
+ }
+
+ // Pause auto-dismiss on mouse enter
+ onMouseEnter(): void {
+ if (this.autoDismissTimeout) {
+ clearTimeout(this.autoDismissTimeout);
+ }
+ }
+
+ // Resume auto-dismiss on mouse leave
+ onMouseLeave(): void {
+ if (this.autoDismiss && this.duration > 0 && !this.isExiting) {
+ this.startAutoDismissTimer();
+ }
+ }
+}
\ No newline at end of file