veza/apps/web/src/components/data/Timeline.tsx
2025-12-12 21:34:34 -05:00

127 lines
3.9 KiB
TypeScript

import { ReactNode } from 'react';
import { cn } from '@/lib/utils';
import { formatDate } from '@/utils/date';
export interface TimelineItem {
id: string;
title: string;
description?: string;
date: Date | string;
icon?: ReactNode;
}
export interface TimelineProps {
items: TimelineItem[];
orientation?: 'vertical' | 'horizontal';
className?: string;
}
/**
* Composant Timeline pour afficher des événements chronologiques.
*/
export function Timeline({
items,
orientation = 'vertical',
className,
}: TimelineProps) {
if (items.length === 0) {
return (
<div
className={cn(
'flex items-center justify-center py-8 text-muted-foreground',
className,
)}
>
Aucun événement à afficher
</div>
);
}
if (orientation === 'horizontal') {
return (
<div className={cn('relative', className)}>
<div className="flex items-start gap-4 overflow-x-auto pb-8">
{items.map((item, index) => (
<div
key={item.id}
className="relative flex min-w-[200px] flex-shrink-0 flex-col items-center"
>
{/* Ligne horizontale */}
{index < items.length - 1 && (
<div className="absolute left-[50%] top-6 h-0.5 w-full translate-x-1/2 bg-border" />
)}
{/* Icône */}
<div className="relative z-10 mb-2 flex h-12 w-12 items-center justify-center rounded-full border-2 border-background bg-primary text-primary-foreground">
{item.icon ? (
<div className="flex h-full w-full items-center justify-center">
{item.icon}
</div>
) : (
<div className="h-2 w-2 rounded-full bg-primary-foreground" />
)}
</div>
{/* Contenu */}
<div className="flex w-full flex-col items-center text-center">
<div className="mb-1 font-semibold text-foreground">
{item.title}
</div>
{item.description && (
<div className="mb-2 text-sm text-muted-foreground">
{item.description}
</div>
)}
<div className="text-xs text-muted-foreground">
{formatDate(item.date, 'short')}
</div>
</div>
</div>
))}
</div>
</div>
);
}
// Orientation verticale (par défaut)
return (
<div className={cn('relative', className)}>
<div className="flex flex-col">
{items.map((item, index) => (
<div key={item.id} className="relative flex gap-4 pb-8 last:pb-0">
{/* Ligne verticale */}
{index < items.length - 1 && (
<div className="absolute left-6 top-12 h-full w-0.5 bg-border" />
)}
{/* Icône */}
<div className="relative z-10 flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full border-2 border-background bg-primary text-primary-foreground">
{item.icon ? (
<div className="flex h-full w-full items-center justify-center">
{item.icon}
</div>
) : (
<div className="h-2 w-2 rounded-full bg-primary-foreground" />
)}
</div>
{/* Contenu */}
<div className="flex-1 pt-1">
<div className="mb-1 font-semibold text-foreground">
{item.title}
</div>
{item.description && (
<div className="mb-2 text-sm text-muted-foreground">
{item.description}
</div>
)}
<div className="text-xs text-muted-foreground">
{formatDate(item.date, 'short')}
</div>
</div>
</div>
))}
</div>
</div>
);
}