Files
timeline-elements-ui/src/components/tl-horizontal-timeline/tl-horizontal-timeline.component.ts
Giuliano Silvestro a07edd485b Initial commit: timeline-elements-ui library
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>
2026-02-13 21:58:25 +10:00

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);
}
}