Angular 19 component library for timelines and event tracking: timeline, Gantt chart, event cards, date markers, connectors, and custom event templates. Includes signal-based services, SCSS design tokens with dark mode, and TypeScript type definitions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
93 lines
3.2 KiB
TypeScript
93 lines
3.2 KiB
TypeScript
import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { ButtonComponent, IconComponent, SkeletonComponent, TooltipDirective } from '@sda/base-ui';
|
|
import type { TlTimelineItem } from '../../types/timeline.types';
|
|
import type { TlMarkerSize, TlConnectorStyle, TlSortDirection } from '../../types/config.types';
|
|
import type { TlTimelineItemClickEvent } from '../../types/event.types';
|
|
import { formatSmartTime } from '../../utils/date.utils';
|
|
|
|
@Component({
|
|
selector: 'tl-horizontal-timeline',
|
|
standalone: true,
|
|
imports: [CommonModule, ButtonComponent, IconComponent, SkeletonComponent, TooltipDirective],
|
|
templateUrl: './tl-horizontal-timeline.component.html',
|
|
styleUrl: './tl-horizontal-timeline.component.scss',
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
})
|
|
export class TlHorizontalTimelineComponent implements AfterViewInit {
|
|
@ViewChild('scrollContainer') scrollContainer?: ElementRef<HTMLDivElement>;
|
|
|
|
@Input({ required: true }) items!: TlTimelineItem[];
|
|
@Input() markerSize: TlMarkerSize = 'md';
|
|
@Input() connectorStyle: TlConnectorStyle = 'solid';
|
|
@Input() sortDirection: TlSortDirection = 'desc';
|
|
@Input() scrollable = true;
|
|
@Input() showNavArrows = true;
|
|
@Input() loading = false;
|
|
@Input() emptyMessage = 'No timeline items';
|
|
|
|
@Output() itemClick = new EventEmitter<TlTimelineItemClickEvent>();
|
|
|
|
protected canScrollLeft = false;
|
|
protected canScrollRight = false;
|
|
|
|
protected get sortedItems(): TlTimelineItem[] {
|
|
const items = [...this.items];
|
|
const direction = this.sortDirection;
|
|
|
|
items.sort((a, b) => {
|
|
const aTime = new Date(a.date).getTime();
|
|
const bTime = new Date(b.date).getTime();
|
|
return direction === 'asc' ? aTime - bTime : bTime - aTime;
|
|
});
|
|
|
|
return items;
|
|
}
|
|
|
|
ngAfterViewInit(): void {
|
|
this.updateScrollState();
|
|
}
|
|
|
|
protected formatTime(item: TlTimelineItem): string {
|
|
return formatSmartTime(item.date);
|
|
}
|
|
|
|
protected getMarkerColor(item: TlTimelineItem): string {
|
|
if (item.iconColor) return item.iconColor;
|
|
if (item.status) return `var(--tl-status-${item.status})`;
|
|
return 'var(--tl-marker-bg)';
|
|
}
|
|
|
|
protected onItemClick(item: TlTimelineItem, index: number): void {
|
|
this.itemClick.emit({ item, index });
|
|
}
|
|
|
|
protected scrollLeft(): void {
|
|
const container = this.scrollContainer?.nativeElement;
|
|
if (container) {
|
|
container.scrollBy({ left: -200, behavior: 'smooth' });
|
|
setTimeout(() => this.updateScrollState(), 300);
|
|
}
|
|
}
|
|
|
|
protected scrollRight(): void {
|
|
const container = this.scrollContainer?.nativeElement;
|
|
if (container) {
|
|
container.scrollBy({ left: 200, behavior: 'smooth' });
|
|
setTimeout(() => this.updateScrollState(), 300);
|
|
}
|
|
}
|
|
|
|
protected updateScrollState(): void {
|
|
const container = this.scrollContainer?.nativeElement;
|
|
if (container) {
|
|
this.canScrollLeft = container.scrollLeft > 0;
|
|
this.canScrollRight = container.scrollLeft < container.scrollWidth - container.clientWidth - 1;
|
|
}
|
|
}
|
|
|
|
protected getLoadingItems(): number[] {
|
|
return Array.from({ length: 5 }, (_, i) => i);
|
|
}
|
|
}
|