mq-timeline-calendar
v0.5.0
Published
A flexible, framework-agnostic timeline/calendar component with smooth scrolling and zooming
Downloads
586
Readme
mq-timeline-calendar
A flexible, headless timeline/calendar component for React with smooth scrolling, zooming, and drag-and-drop capabilities.
Features
- 🎯 Headless by default - Full control over styling and rendering
- 📅 Smooth scrolling - Navigate through time seamlessly with mouse wheel
- 🔍 Continuous zoom - Smooth, cursor-relative zoom (Ctrl/Cmd + wheel)
- 🎨 Built-in themes - Light and dark themes included
- 🖱️ Drag & Drop - Move items horizontally (time) and vertically (rows)
- 📍 Pinpoint markers - Display events and milestones with automatic clustering
- 📊 Row grouping - Organize timeline items with collapsible row groups
- 🌍 Localization - Support for 13+ European languages
- ⏰ Availability overlay - Show working hours and available time periods
- 📊 Smart aggregation - Auto-aggregate items when zoomed out for better performance
- ⚛️ React first - Optimized React components with hooks
- 📦 TypeScript - Full type safety and IntelliSense support
- 🚀 Framework-agnostic core - Core engine can be used with any framework
Installation
npm install mq-timeline-calendarLive Demo
Try out the timeline calendar in StackBlitz:
Quick Start
import { TimelineCalendar, TimelineItem, TimelineRow, TimelineRowGroup } from 'mq-timeline-calendar/react';
function App() {
return (
<TimelineCalendar
startDate={new Date('2025-01-01')}
endDate={new Date('2025-12-31')}
height="600px"
showCurrentTime={true}
theme="light"
>
<TimelineRowGroup>
<TimelineRow id="row-1" label="Production Line A" rowCount={2} collapsible={true}>
<TimelineItem
startTime="2025-03-15"
duration="1 week"
row={0}
>
<div style={{ background: '#3b82f6', padding: '8px', color: 'white' }}>
Order #1234
</div>
</TimelineItem>
<TimelineItem
startTime="2025-03-25"
duration="5 days"
row={1}
>
<div style={{ background: '#10b981', padding: '8px', color: 'white' }}>
Order #1235
</div>
</TimelineItem>
</TimelineRow>
</TimelineRowGroup>
</TimelineCalendar>
);
}Core Concepts
TimelineCalendar
The main container component that manages the timeline viewport, zoom level, and scrolling.
<TimelineCalendar
startDate={new Date('2024-01-01')}
endDate={new Date('2026-12-31')}
width="100%"
height="600px"
minZoom="1000 years"
maxZoom="100 milliseconds"
showNavigation={false}
showCurrentTime={true}
locale={fiFI}
theme="dark"
onViewportChange={(start, end) => console.log('Viewport:', start, end)}
onZoomChange={(pixelsPerMs) => console.log('Zoom:', pixelsPerMs)}
>
{/* Timeline items and rows */}
</TimelineCalendar>Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| startDate | TimeValue | required | Start date of the timeline |
| endDate | TimeValue | required | End date of the timeline |
| width | string \| number | "100%" | Width of the container |
| height | string \| number | "600px" | Height of the container |
| minZoom | number \| string | - | Minimum zoom level (e.g., "5 years") |
| maxZoom | number \| string | - | Maximum zoom level (e.g., "1 hour") |
| showNavigation | boolean | false | Show navigation buttons |
| showCurrentTime | boolean | false | Show current time indicator |
| currentTimeLineWidth | number | 2 | Width of current time line in pixels |
| locale | CalendarLocale | - | Locale for date formatting |
| theme | 'light' \| 'dark' \| TimelineTheme | 'light' | Theme preset or custom theme object |
| availability | AvailabilityConfig | - | Availability/working hours configuration |
| onViewportChange | (start: Date, end: Date) => void | - | Called when viewport changes |
| onZoomChange | (pixelsPerMs: number) => void | - | Called when zoom level changes |
TimelineItem
Individual items placed on the timeline at specific times.
<TimelineItem
startTime="2025-03-15"
duration="1 week"
row={0}
draggable={true}
allowRowChange={true}
onDragStart={(timestamp, row, rowGroupId) => console.log('Drag started')}
onDrag={(timestamp, row, rowGroupId) => console.log('Dragging')}
onDragEnd={(newTime, oldTime, newRow, oldRow, newGroupId, oldGroupId) => {
console.log('Dropped at:', new Date(newTime), 'row:', newRow);
}}
>
<div>My Event</div>
</TimelineItem>Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| startTime | TimeValue | required | Start time (Date, timestamp, or ISO string) |
| duration | DurationValue | required | Duration (ms or human-readable like "1 week") |
| endTime | TimeValue | - | Alternative to duration - specify end time directly |
| row | number | 0 | Row/lane for vertical positioning |
| draggable | boolean | false | Enable drag and drop |
| allowRowChange | boolean | false | Allow dragging between rows (vertical) |
| onDragStart | (timestamp, row, rowGroupId?) => void | - | Called when drag starts |
| onDrag | (timestamp, row, rowGroupId?) => void | - | Called during drag |
| onRowChange | (newRow, oldRow, newGroupId?, oldGroupId?) => void | - | Called when row changes |
| onDragEnd | (newTime, oldTime, newRow, oldRow, newGroupId?, oldGroupId?) => void | - | Called when drag ends |
| className | string | - | CSS class name |
| style | CSSProperties | - | Inline styles |
TimelineRow & TimelineRowGroup
Organize timeline items into rows with optional collapsible headers.
<TimelineRowGroup>
<TimelineRow
id="line-a"
label="Production Line A"
rowCount={2}
collapsible={true}
defaultExpanded={true}
>
<TimelineItem startTime="2025-03-15" duration="1 week" row={0}>
<div>Order #1</div>
</TimelineItem>
<TimelineItem startTime="2025-03-20" duration="3 days" row={1}>
<div>Order #2</div>
</TimelineItem>
</TimelineRow>
<TimelineRow id="line-b" label="Production Line B" rowCount={1} collapsible={true}>
<TimelineItem startTime="2025-03-18" duration="5 days" row={0}>
<div>Order #3</div>
</TimelineItem>
</TimelineRow>
</TimelineRowGroup>TimelineRow Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| id | string | auto-generated | Unique identifier |
| label | string | - | Label text for header |
| rowCount | number | 1 | Number of rows this component occupies |
| collapsible | boolean | false | Whether row can be collapsed |
| showHeader | boolean | true | Show header (only if collapsible=true) |
| defaultExpanded | boolean | true | Initial expanded state |
| headerClassName | string | - | CSS class for header |
| headerStyle | CSSProperties | - | Inline styles for header |
| aggregation | AggregationConfig | - | Aggregation configuration (see Aggregation section) |
| renderAggregatedPeriod | (params) => ReactNode | - | Custom renderer for aggregated periods |
| getAggregatedTypeStyle | (type: string) => StyleObject | - | Function to get style for aggregated bar segments |
Drag and Drop
Horizontal Dragging (Time)
Move items forward or backward in time:
const [itemTime, setItemTime] = useState(new Date('2025-03-15'));
<TimelineItem
startTime={itemTime}
duration="5 days"
row={0}
draggable={true}
onDragEnd={(newTimestamp) => {
setItemTime(new Date(newTimestamp));
}}
>
<div>Drag me horizontally!</div>
</TimelineItem>Vertical Dragging (Rows)
Move items between rows within the same TimelineRow group:
const [itemRow, setItemRow] = useState(0);
<TimelineRow id="group-1" rowCount={3} collapsible={true}>
<TimelineItem
startTime="2025-03-15"
duration="5 days"
row={itemRow}
draggable={true}
allowRowChange={true}
onDragEnd={(newTime, oldTime, newRow) => {
setItemRow(newRow);
}}
>
<div>Drag me vertically!</div>
</TimelineItem>
</TimelineRow>Drag Behavior
- Direction-locked: Once drag starts, it's locked to either horizontal (time) or vertical (row) based on initial movement
- Snapping: Time changes snap to 15-minute intervals by default
- Threshold: 5-pixel movement required before drag activates (prevents accidental drags)
- Row constraints: Vertical dragging is constrained within the TimelineRow's
rowCount
Time Formats
The component accepts multiple time formats:
// Date object
startTime={new Date('2025-03-15')}
// ISO string
startTime="2025-03-15T10:00:00Z"
// Timestamp (milliseconds)
startTime={1710504000000}
// Duration can be:
duration={7 * 24 * 60 * 60 * 1000} // milliseconds
duration="1 week" // human-readable
duration="3 days"
duration="2 hours"
duration="30 minutes"Themes
Built-in Themes
// Light theme (default)
<TimelineCalendar theme="light">
// Dark theme
<TimelineCalendar theme="dark">Custom Theme
<TimelineCalendar
theme={{
colors: {
background: '#1a1a1a',
gridLine: '#333333',
headerBackground: '#2a2a2a',
headerText: '#ffffff',
currentTimeLine: '#3b82f6'
},
spacing: {
headerHeight: 100,
rowHeight: 60
}
}}
>Availability / Working Hours
Show available and unavailable time periods:
<TimelineCalendar
availability={{
// Weekly pattern: define working hours for each day
weekly: {
1: [{ start: '08:00', end: '17:30' }], // Monday
2: [{ start: '09:00', end: '16:00' }], // Tuesday
3: [{ start: '08:00', end: '17:30' }], // Wednesday
4: [{ start: '09:00', end: '16:00' }], // Thursday
5: [{ start: '08:00', end: '15:00' }], // Friday
},
unavailableStyle: {
backgroundColor: 'rgba(156, 163, 175, 0.12)',
}
}}
>Day Calendar (vertical day view)
<DayCalendar> is a separate component that renders a traditional day-planner
view: time runs on the Y-axis (constrained to opening hours derived from
AvailabilityConfig), and a list of mechanics (or any resources) is rendered
as columns. Use this when <TimelineCalendar>'s horizontal layout isn't a
fit — e.g., shop-floor "who's working on what today".
Basic Usage
import { DayCalendar } from 'mq-timeline-calendar/react';
const mechanics = [
{ id: 'm1', name: 'Mikko' },
{ id: 'm2', name: 'Pekka' },
{ id: 'm3', name: 'Anna' },
];
const events = [
{
id: 'e1',
mechanicId: 'm1',
startTime: '2026-05-11T09:00:00',
endTime: '2026-05-11T10:30:00',
title: 'Öljynvaihto',
color: '#3b82f6',
},
{
id: 'e2',
mechanicId: 'm2',
startTime: '2026-05-11T10:00:00',
endTime: '2026-05-11T12:00:00',
title: 'Jarruhuolto',
},
];
<DayCalendar
date={new Date('2026-05-11')}
mechanics={mechanics}
events={events}
availability={{
weekly: {
1: [{ start: '08:00', end: '17:30' }],
2: [{ start: '08:00', end: '17:30' }],
3: [{ start: '08:00', end: '17:30' }],
4: [{ start: '08:00', end: '17:30' }],
5: [{ start: '08:00', end: '15:00' }],
},
}}
slotMinutes={30}
height="600px"
showCurrentTime
onSlotClick={(mechanicId, datetime) => console.log('new slot:', mechanicId, datetime)}
onEventClick={(eventId, event) => console.log('event clicked:', eventId, event)}
/>Behavior
- Visible time range is the union of all ranges for the target day across
weekly,simple, andspecificinavailability. Falls back to00:00–24:00when no config is provided. - Slot grid is
slotMinuteswide (default 30). Range bounds snap to slot boundaries. - Events are absolute-positioned inside their mechanic column. Events that span outside opening hours are clipped to the visible range. Events that fall entirely outside are not rendered.
- Overlapping events in the same mechanic column lay out side-by-side
using the same
assignSubRowshelper as<TimelineCalendar>. - Current-time line (when
showCurrentTime) shows a horizontal line across all mechanic columns when the current time falls within the visible range. - Navigation (when
showNavigation) renders a prev / today / next toolbar with a localized date label.<DayCalendar>is controlled — wireonDateChangeto yourdatestate.
DayCalendar Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| date | TimeValue | required | Target day to display |
| mechanics | Mechanic[] | required | Columns. { id, name, color? } |
| events | ScheduleEvent[] | [] | { id, mechanicId, startTime, endTime, title?, color?, data? } |
| availability | AvailabilityConfig | - | Same shape as <TimelineCalendar> — drives the visible hour range |
| slotMinutes | number | 30 | Slot granularity |
| width | string \| number | '100%' | Container width |
| height | string \| number | '600px' | Container height |
| timeColumnWidth | number | 60 | Time column width in px |
| slotHeight | number | 40 | Slot row height in px |
| minColumnWidth | number | 100 | Min mechanic column width (triggers horizontal scroll) |
| theme | 'light' \| 'dark' \| 'compact' \| 'compact-dark' \| TimelineTheme | 'light' | Reused from <TimelineCalendar> |
| showCurrentTime | boolean | false | Show current-time line |
| showNavigation | boolean | false | Render prev / today / next toolbar |
| navigationLabels | { previous?, today?, next? } | - | Override toolbar button labels |
| onDateChange | (newDate: Date) => void | - | Required when showNavigation — receives the new target day |
| renderDateLabel | (date: Date) => ReactNode | - | Custom date label in the toolbar |
| onSlotClick | (mechanicId, datetime) => void | - | Fired with the slot's start datetime |
| onEventClick | (eventId, event) => void | - | Fired when an event is clicked |
| renderEvent | (params) => ReactNode | - | Custom event renderer; receives { event, top, height, left, width } (left/width are percentages) |
| renderMechanicHeader | (mechanic) => ReactNode | - | Custom mechanic header cell |
| renderTimeLabel | (time: Date) => ReactNode | - | Custom time-column label |
Differences from <TimelineCalendar>
| | <TimelineCalendar> | <DayCalendar> |
|--|--|--|
| Time axis | Horizontal (X) | Vertical (Y) |
| Zoom / scroll | Continuous (mouse wheel, pinch) | Native scroll, fixed slot size |
| Range | Multi-day / multi-year | Single day |
| Rows / columns | Many rows of any height | N mechanic columns |
| Children API | <TimelineItem> children | events prop |
| Drag & drop | Built-in | Not yet (planned) |
Not yet supported
- Drag & drop (move event in time or between mechanics)
- Week view
- Resizing events
Pinpoint Markers
Display point-in-time markers (milestones, events, deadlines) with automatic clustering when markers are too close together.
Basic Usage
import { TimelinePinpoint, TimelinePinpointGroup, TimelineRow } from 'mq-timeline-calendar/react';
<TimelineCalendar
startDate={new Date('2025-01-01')}
endDate={new Date('2025-12-31')}
>
<TimelineRow id="events" label="Events & Milestones" rowCount={1}>
<TimelinePinpointGroup row={0} clusterDistance={30}>
{/* Individual pinpoints - automatically cluster when too close */}
<TimelinePinpoint
time="2025-03-10T10:00:00"
color="#10b981"
data={{ type: 'inspection', name: 'Safety Check' }}
>
✓
</TimelinePinpoint>
<TimelinePinpoint
time="2025-03-10T14:00:00"
color="#3b82f6"
data={{ type: 'meeting', name: 'Team Sync' }}
>
👥
</TimelinePinpoint>
<TimelinePinpoint
time="2025-03-15T09:00:00"
color="#ef4444"
data={{ type: 'deadline' }}
onClick={(timestamp, data) => {
console.log('Clicked:', new Date(timestamp), data);
}}
>
!
</TimelinePinpoint>
</TimelinePinpointGroup>
</TimelineRow>
</TimelineCalendar>Customization
{/* Different sizes */}
<TimelinePinpoint
time="2025-03-01T10:00:00"
size={32} // Circle size in pixels (default: 24)
color="#10b981"
>
✓
</TimelinePinpoint>
{/* Different alignments */}
<TimelinePinpoint
time="2025-03-05T10:00:00"
alignment="top" // 'top' (default), 'center', or 'bottom'
>
📍
</TimelinePinpoint>
<TimelinePinpoint
time="2025-03-10T10:00:00"
alignment="center" // Center alignment hides the vertical line
>
◆
</TimelinePinpoint>
{/* Custom line styles */}
<TimelinePinpoint
time="2025-03-15T10:00:00"
lineStyle="dashed" // 'solid', 'dashed', or 'dotted'
lineWidth={3} // Line width in pixels (default: 2)
lineLength={50} // Line length in pixels (default: half row height)
>
!
</TimelinePinpoint>TimelinePinpoint Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| time | TimeValue | required | Time of the pinpoint (Date, timestamp, or ISO string) |
| row | number | 0 | Row/lane for vertical positioning |
| size | number | 24 | Circle marker size in pixels |
| color | string | theme color | Color of marker and line |
| alignment | 'top' \| 'center' \| 'bottom' | 'top' | Vertical position; 'center' hides line |
| lineWidth | number | 2 | Width of vertical line in pixels |
| lineLength | number | half row height | Length of vertical line in pixels |
| lineStyle | 'solid' \| 'dashed' \| 'dotted' | 'solid' | Style of vertical line |
| children | ReactNode | - | Icon/emoji to display in marker |
| data | any | - | Custom data to associate with pinpoint |
| onClick | (timestamp, data?) => void | - | Click handler |
TimelinePinpointGroup Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| row | number | 0 | Row/lane for vertical positioning |
| clusterDistance | number | 30 | Pixel distance threshold for clustering |
| clusterColor | string | theme color | Color for cluster markers |
| clusterSize | number | 24 | Size of cluster circle marker in pixels |
| pinpointSize | number | 24 | Default size for individual pinpoint markers in pixels |
| pinpointLineWidth | number | 2 | Default line width for pinpoints in pixels |
| pinpointLineLength | number | half row height | Default line length for pinpoints in pixels |
| onClusterClick | (timestamp, items) => void | - | Custom cluster click handler (default: zoom in) |
Clustering Behavior
- Pinpoints within
clusterDistancepixels are automatically grouped into clusters - Cluster markers display the count of pinpoints
- Clicking a cluster smoothly zooms in to separate the pinpoints
- Custom cluster click handler can override default zoom behavior
Group-Level Defaults
Set default properties for all pinpoints in a group:
{/* All pinpoints inherit size and line properties */}
<TimelinePinpointGroup
row={0}
clusterDistance={30}
clusterSize={32} // Larger cluster markers
clusterColor="#ec4899"
pinpointSize={16} // All pinpoints default to 16px
pinpointLineWidth={3} // Thicker lines for all
pinpointLineLength={25} // Shorter lines
>
<TimelinePinpoint time="2025-03-01T10:00:00" color="#10b981">✓</TimelinePinpoint>
<TimelinePinpoint time="2025-03-01T11:00:00" color="#3b82f6">👥</TimelinePinpoint>
<TimelinePinpoint time="2025-03-01T12:00:00" color="#f59e0b">🔧</TimelinePinpoint>
{/* All inherit group defaults */}
</TimelinePinpointGroup>
{/* Individual pinpoints can override group defaults */}
<TimelinePinpointGroup
row={1}
pinpointSize={24} // Default size for group
pinpointLineWidth={2}
>
<TimelinePinpoint time="2025-04-01T10:00:00" size={16}>✓</TimelinePinpoint> {/* Overrides to 16px */}
<TimelinePinpoint time="2025-04-05T10:00:00">👥</TimelinePinpoint> {/* Uses group default 24px */}
</TimelinePinpointGroup>Timeline Aggregation
When working with large datasets, the timeline can automatically aggregate items into grouped periods for better performance and visualization.
How It Works
When you zoom out far enough (beyond the configured threshold) and have many items, the timeline automatically switches from individual item rendering to an aggregated view. The aggregated view shows:
- Stacked bar charts grouped by week or month
- Occupancy percentage - how much of the available time is occupied
- Item counts by type
- Color-coded segments representing different item types
Configuration
Add the aggregation prop to TimelineRow:
<TimelineRow
id="production-line"
label="Production Line A"
rowCount={2}
aggregation={{
enabled: true,
threshold: "6 months", // Switch to aggregated view when viewport > 6 months
granularity: "dynamic", // "week" | "month" | "dynamic"
minItemsForAggregation: 50 // Only aggregate if row has 50+ items
}}
getAggregatedTypeStyle={(type) => ({
backgroundColor: type === 'urgent' ? '#ef4444' : '#3b82f6',
color: 'white'
})}
renderAggregatedPeriod={({ period, position, width, height }) => (
<div style={{ width, height }}>
Custom rendering for {period.start} - {period.end}
</div>
)}
>
{/* Many TimelineItem components */}
</TimelineRow>Aggregation Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| enabled | boolean | true | Enable/disable aggregation |
| threshold | DurationValue | "6 months" | Viewport duration that triggers aggregation |
| granularity | 'week' \| 'month' \| 'dynamic' | "dynamic" | How to group periods |
| minItemsForAggregation | number | 50 | Minimum items needed to activate aggregation |
Custom Rendering
You can customize the aggregated view rendering:
<TimelineRow
aggregation={{ enabled: true }}
renderAggregatedPeriod={({ period, position, width, height }) => {
const { occupancyPercent, byType } = period;
return (
<div
style={{
position: 'absolute',
left: position,
width,
height,
background: `linear-gradient(to top,
rgba(59, 130, 246, ${occupancyPercent / 100}) 0%,
rgba(59, 130, 246, 0.2) 100%)`
}}
>
<span>{occupancyPercent.toFixed(0)}%</span>
</div>
);
}}
>Type-Based Styling
Customize the appearance of different item types in aggregated view:
<TimelineRow
aggregation={{ enabled: true }}
getAggregatedTypeStyle={(type) => {
const styles = {
'production': { backgroundColor: '#10b981', color: 'white' },
'maintenance': { backgroundColor: '#f59e0b', color: 'white' },
'downtime': { backgroundColor: '#ef4444', color: 'white' }
};
return styles[type] || { backgroundColor: '#6b7280', color: 'white' };
}}
>Granularity Options
week: Always group by ISO weeks (Monday-Sunday)month: Always group by calendar monthsdynamic: Automatically choose based on viewport:- Weeks for 6-12 month viewports
- Months for >12 month viewports
Localization
import { deDE } from 'mq-timeline-calendar/react';
<TimelineCalendar locale={deDE}>Available locales:
enUS- English (US) - DefaultdeDE- German (Germany)frFR- French (France)esES- Spanish (Spain)itIT- Italian (Italy)ptPT- Portuguese (Portugal)nlNL- Dutch (Netherlands)svSE- Swedish (Sweden)noNO- Norwegian (Norway)daDK- Danish (Denmark)plPL- Polish (Poland)ruRU- Russian (Russia)fiFI- Finnish (Finland)
You can also create custom locales by implementing the CalendarLocale interface.
Keyboard & Mouse Controls
- Horizontal Trackpad Swipe / Wheel: Scroll horizontally through time
- Vertical Mouse Wheel (over header): Scroll horizontally through time
- Vertical Trackpad Swipe / Mouse Wheel (over content): Scroll vertically through rows (when overflow exists)
- Shift + Vertical Mouse Wheel: Scroll horizontally through time (works anywhere)
- Ctrl/Cmd + Mouse Wheel: Zoom in/out (centered on cursor position)
- Click header cells: Zoom to that time period
- Drag items: Move items in time or between rows (if enabled)
TypeScript Support
The package is written in TypeScript and includes full type definitions:
import type {
TimelineCalendarProps,
TimelineItemProps,
TimelineRowProps,
TimelinePinpointProps,
TimelinePinpointGroupProps,
PinpointAlignment,
TimeValue,
DurationValue,
CalendarLocale,
TimelineTheme,
AggregationConfig,
AggregatedPeriod,
AggregatedPeriodRenderParams
} from 'mq-timeline-calendar/react';CSS Custom Properties
Customize appearance using CSS variables:
:root {
--timeline-header-height: 100px;
--timeline-header-row-height: 40px;
--timeline-row-height: 60px;
--timeline-header-bg: #ffffff;
--timeline-header-text: #374151;
--timeline-header-border: #d1d5db;
--timeline-grid-line: #e5e7eb;
--timeline-grid-line-primary: #d1d5db;
}Framework-Agnostic Core
The core timeline engine can be used without React:
import { TimelineEngine } from 'mq-timeline-calendar/core';
const engine = new TimelineEngine(
new Date('2025-01-01'),
new Date('2025-12-31'),
1000 // viewport width in pixels
);
// Convert time to pixel position
const pixelX = engine.timeToPixel(new Date('2025-06-15').getTime());
// Convert pixel to time
const timestamp = engine.pixelToTime(500);
// Zoom
engine.zoom(1.5, 500); // 1.5x zoom at pixel position 500
// Scroll
engine.scroll(100); // Scroll 100 pixelsExamples
Basic Timeline
import { TimelineCalendar, TimelineItem } from 'mq-timeline-calendar/react';
function BasicTimeline() {
return (
<TimelineCalendar
startDate={new Date('2025-01-01')}
endDate={new Date('2025-12-31')}
height="400px"
>
<TimelineItem startTime="2025-03-15" duration="1 week" row={0}>
<div>Event 1</div>
</TimelineItem>
<TimelineItem startTime="2025-06-20" duration="3 days" row={1}>
<div>Event 2</div>
</TimelineItem>
</TimelineCalendar>
);
}Production Schedule
import { TimelineCalendar, TimelineItem, TimelineRow, TimelineRowGroup } from 'mq-timeline-calendar/react';
function ProductionSchedule() {
return (
<TimelineCalendar
startDate={new Date('2025-01-01')}
endDate={new Date('2025-12-31')}
height="600px"
showCurrentTime={true}
theme="dark"
>
<TimelineRowGroup>
<TimelineRow id="line-a" label="Production Line A" rowCount={2} collapsible={true}>
<TimelineItem startTime="2025-03-10" duration="5 days" row={0}>
<div className="order">Order #1234</div>
</TimelineItem>
<TimelineItem startTime="2025-03-18" duration="3 days" row={1}>
<div className="order">Order #1235</div>
</TimelineItem>
</TimelineRow>
<TimelineRow id="line-b" label="Production Line B" rowCount={1} collapsible={true}>
<TimelineItem startTime="2025-03-12" duration="4 days" row={0}>
<div className="order">Order #1240</div>
</TimelineItem>
</TimelineRow>
</TimelineRowGroup>
</TimelineCalendar>
);
}Draggable Items
function DraggableTimeline() {
const [items, setItems] = useState([
{ id: 1, time: new Date('2025-03-15'), duration: '5 days', row: 0 },
{ id: 2, time: new Date('2025-03-20'), duration: '3 days', row: 1 },
]);
const handleDragEnd = (itemId, newTime, newRow) => {
setItems(prev => prev.map(item =>
item.id === itemId
? { ...item, time: new Date(newTime), row: newRow }
: item
));
};
return (
<TimelineCalendar
startDate={new Date('2025-01-01')}
endDate={new Date('2025-12-31')}
height="600px"
>
<TimelineRow id="tasks" rowCount={3} collapsible={false}>
{items.map(item => (
<TimelineItem
key={item.id}
startTime={item.time}
duration={item.duration}
row={item.row}
draggable={true}
allowRowChange={true}
onDragEnd={(newTime, _, newRow) => handleDragEnd(item.id, newTime, newRow)}
>
<div>Task {item.id}</div>
</TimelineItem>
))}
</TimelineRow>
</TimelineCalendar>
);
}Timeline with Aggregation
function LargeDatasetTimeline() {
// Generate hundreds of items
const items = useMemo(() => {
const result = [];
for (let i = 0; i < 200; i++) {
const dayOffset = Math.floor(Math.random() * 365);
const types = ['production', 'maintenance', 'testing', 'downtime'];
result.push({
id: i,
startTime: addDays(new Date('2025-01-01'), dayOffset),
duration: `${Math.floor(Math.random() * 5) + 1} days`,
type: types[Math.floor(Math.random() * types.length)],
row: Math.floor(Math.random() * 3)
});
}
return result;
}, []);
return (
<TimelineCalendar
startDate={new Date('2025-01-01')}
endDate={new Date('2025-12-31')}
height="600px"
showCurrentTime={true}
>
<TimelineRowGroup>
<TimelineRow
id="production"
label="Production Line"
rowCount={3}
collapsible={true}
aggregation={{
enabled: true,
threshold: "6 months",
granularity: "dynamic",
minItemsForAggregation: 50
}}
getAggregatedTypeStyle={(type) => {
const styles = {
production: { backgroundColor: '#10b981', color: 'white' },
maintenance: { backgroundColor: '#f59e0b', color: 'white' },
testing: { backgroundColor: '#3b82f6', color: 'white' },
downtime: { backgroundColor: '#ef4444', color: 'white' }
};
return styles[type] || { backgroundColor: '#6b7280', color: 'white' };
}}
>
{items.map(item => (
<TimelineItem
key={item.id}
startTime={item.startTime}
duration={item.duration}
row={item.row}
type={item.type}
>
<div style={{ padding: '4px', fontSize: '12px' }}>
{item.type} #{item.id}
</div>
</TimelineItem>
))}
</TimelineRow>
</TimelineRowGroup>
</TimelineCalendar>
);
}Browser Support
- Chrome/Edge (latest)
- Firefox (latest)
- Safari (latest)
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT © [Your Name]
