127 lines
3.9 KiB
TypeScript
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>
|
|
);
|
|
}
|