# Timeline

**When to use:** A vertical list of dated events, connected by a line, for audit logs, activity feeds, order status history, and deploy logs. Each entry can carry a title, timestamp, description, an icon or a colour, and arbitrary extra content.

**Keywords:** history log

**Import:** `import { Timeline, TimelineAlignment, ThemeColor } from '@ahrowe/ui'`
**Types:** `import type { TimelineItem, TimelineProps } from '@ahrowe/ui'`

**Enums:**
- `TimelineAlignment`: `Left` (default) | `Right` | `Alternate`

```tsx
import { Timeline, ThemeColor } from '@ahrowe/ui';
import type { TimelineItem } from '@ahrowe/ui';

const items: TimelineItem[] = [
  { title: 'Invoice created', description: 'by Jane Doe', timestamp: '09:12' },
  { title: 'Invoice sent to client', description: 'via email', timestamp: '09:15' },
  { title: 'Payment received', color: ThemeColor.Success, timestamp: '2 days later' },
  { title: 'Invoice closed', color: ThemeColor.Success, timestamp: 'Today' },
];

// Default: left-aligned line and markers, content to the right
<Timeline items={items} />

// Icon markers instead of plain dots
import { faUserPlus, faCheck } from '@fortawesome/free-solid-svg-icons';
<Timeline
  items={[
    { title: 'Account created', icon: faUserPlus, timestamp: 'Mon' },
    { title: 'Email verified', icon: faCheck, color: ThemeColor.Success, timestamp: 'Wed' },
  ]}
/>

// Right-aligned: line and markers on the right, content on the left
import { TimelineAlignment } from '@ahrowe/ui';
<Timeline items={items} alignment={TimelineAlignment.Right} />

// Alternate: entries alternate either side of a centered line
<Timeline items={items} alignment={TimelineAlignment.Alternate} />

// Extra content per entry: a diff, an attachment, an actor avatar
<Timeline
  items={[
    {
      title: 'Deploy #128 started',
      description: 'main @ a93d12d',
      timestamp: '14:02',
      content: <p>Triggered by Jon Snow.</p>,
    },
  ]}
/>

// Clickable entries: e.g. open the full audit log record in a Modal.
// An item without onClick stays a plain, non-interactive row.
import { Modal } from '@ahrowe/ui';

function AuditLog() {
  const [selected, setSelected] = useState<(typeof items)[number] | null>(null);
  return (
    <>
      <Timeline
        items={items.map((entry) => ({ ...entry, onClick: () => setSelected(entry) }))}
      />
      <Modal isOpen={selected != null} title={selected?.title} onClose={() => setSelected(null)}>
        {selected?.description}
      </Modal>
    </>
  );
}

// Lock an individual entry: dimmed, never clickable, even with onClick set
<Timeline
  items={[
    { title: 'Archived entry', onClick: () => openDetail(), disabled: true },
  ]}
/>
```

**TimelineItem:**

| Field | Type | Description |
|-------|------|-------------|
| `id` | `string \| number` | Stable identity used as the entry's React key. Falls back to its array index when omitted; pass one whenever `items` can be reordered or have entries prepended (e.g. a live "newest first" audit log) |
| `title` | `ReactNode` | Primary label for the entry |
| `description` | `ReactNode` | Secondary line under the title |
| `timestamp` | `ReactNode` | Rendered next to the title, e.g. a date or relative time ("2 hours ago") |
| `icon` | `IconDefinition \| ReactElement` | Icon shown in the marker instead of a plain dot — FontAwesome icon or any React element |
| `color` | `ColorValue` | The marker's colour: a `ThemeColor` or any CSS colour, see [ThemeColor.md](ThemeColor.md). Unset, it is `--timeline-dot-color` |
| `content` | `ReactNode` | Extra content rendered under the description, e.g. a diff, an attachment, an actor avatar |
| `onClick` | `(event: MouseEvent<HTMLDivElement>) => void` | Makes the whole entry clickable, rendering it as an accessible button (`role="button"`, focusable, click and Enter both fire it). Omit to render a plain, non-interactive row |
| `disabled` | `boolean` | Disables the entry: never clickable, dimmed. Only relevant alongside `onClick` |

**Key props:**

| Prop | Type | Description |
|------|------|-------------|
| `items` | `TimelineItem[]` | The entries to render, in display order (typically newest first for an audit log) |
| `alignment` | `TimelineAlignment` | Layout of entries relative to the connecting line (default `Left`) |

**Theming:** these are theme variables (typed on `ThemeVariables`). Set them theme-wide via `ThemeProvider`'s `variables`, or per instance via `style`, without fighting specificity. Each falls back to a built-in default when unset:

| Variable | Falls back to |
|----------|---------------|
| `--timeline-dot-size` | `14px` |
| `--timeline-dot-color` | `var(--primary-color)` |
| `--timeline-dot-border` | `var(--background)` |
| `--timeline-line-color` | `var(--border-color)` |
| `--timeline-line-thickness` | `2px` |
| `--timeline-gap` | `16px` (gap between marker column and content) |
| `--timeline-item-gap` | `20px` (vertical gap between entries) |
| `--timeline-icon-dot-size` | `28px` (marker size when an entry has `icon`) |

An entry's `color` replaces `--timeline-dot-color` for that marker, and an icon in it is black or white, whichever reads on the colour. A `ThemeColor` reads the theme's own colour, so it always matches the rest of the app.

```tsx
// Theme-wide, via ThemeProvider: thicker line, larger dots
const theme: Theme = {
  id: 'brand',
  variables: {
    '--timeline-line-thickness': '3px',
    '--timeline-dot-size': '18px',
  },
};
```

**Slots:** `root` `item` `marker` `dot` `connector` `content` `title` `description` `timestamp`

## Migration from `status`

Before 0.43 an entry's marker colour was `status`, typed `TimelineItemStatus`. It is now `color`,
typed `ColorValue`, and `TimelineItemStatus` is gone:

| Before | After |
|--------|-------|
| `status: TimelineItemStatus.Default` | nothing: an unset `color` is `--timeline-dot-color` |
| `status: TimelineItemStatus.Success` (and `Primary`, `Warn`, `Error`) | `color: ThemeColor.Success` |
