Files
doormilxpress_astryx/src/components/ds/Timeline.jsx
2026-08-07 23:53:15 +05:30

100 lines
3.5 KiB
JavaScript

import * as React from 'react';
import { cn } from '@/lib/utils';
import { StatusBadge } from '@/components/ds/StatusBadge';
const TONE_DOT = {
brand: 'bg-krow-blue ring-krow-blue/20',
success: 'bg-success ring-success/20',
warning: 'bg-warning ring-warning/20',
destructive: 'bg-destructive ring-destructive/20',
neutral: 'bg-ink-4 ring-ink-4/20',
};
/**
* Timeline — a vertical sequence of events: a candidate's journey, an audit
* trail, a career history.
*
* Items:
* { id, title, description, timestamp, icon, tone, status, meta, current }
*
* The connecting rail is drawn per item rather than as one absolute line, so a
* timeline can be nested inside a scrolling card without the rail detaching.
*/
export function Timeline({ items = [], className, compact = false }) {
return (
<ol className={cn('relative', className)}>
{items.map((item, i) => {
const isLast = i === items.length - 1;
const Icon = item.icon;
const tone = item.tone || (item.current ? 'brand' : 'neutral');
return (
<li key={item.id ?? i} className="relative flex gap-3.5 pb-5 last:pb-0">
{/* Rail + node */}
<div className="relative flex flex-col items-center shrink-0">
{Icon ? (
<span
className={cn(
'grid place-items-center rounded-full ring-4 shrink-0 z-10',
compact ? 'w-6 h-6' : 'w-8 h-8',
tone === 'neutral' ? 'bg-surface-sunken text-ink-3 ring-white' : 'text-white ring-white',
tone !== 'neutral' && TONE_DOT[tone]
)}
>
<Icon className={compact ? 'w-3 h-3' : 'w-4 h-4'} aria-hidden="true" />
</span>
) : (
<span
className={cn(
'rounded-full ring-4 ring-white shrink-0 z-10 mt-1',
compact ? 'w-2 h-2' : 'w-2.5 h-2.5',
TONE_DOT[tone],
item.current && 'animate-pulse-ring'
)}
aria-hidden="true"
/>
)}
{!isLast && (
<span
aria-hidden="true"
className="w-px flex-1 bg-border mt-1.5 min-h-4"
/>
)}
</div>
{/* Content */}
<div className={cn('flex-1 min-w-0', compact ? '-mt-0.5' : '-mt-1')}>
<div className="flex items-start justify-between gap-2 flex-wrap">
<div className="min-w-0">
<p
className={cn(
'font-medium text-ink-1',
compact ? 'text-body-sm' : 'text-body'
)}
>
{item.title}
</p>
{item.description && (
<p className="text-body-sm text-ink-3 mt-0.5 leading-relaxed">{item.description}</p>
)}
{item.meta && <p className="text-caption text-ink-4 mt-1">{item.meta}</p>}
</div>
<div className="flex items-center gap-2 shrink-0">
{item.status && <StatusBadge status={item.status} size="sm" />}
{item.timestamp && (
<time className="text-caption text-ink-4 whitespace-nowrap">{item.timestamp}</time>
)}
</div>
</div>
{item.children}
</div>
</li>
);
})}
</ol>
);
}