modern-react-timeline-component
v0.5.1
Published
A modern, interactive timeline component for React with drag-and-drop, zoom, and customizable rendering
Downloads
309
Maintainers
Readme
Modern React Timeline Component
A modern, feature-rich timeline/calendar component for React applications. Built with TypeScript, Tailwind CSS, and shadcn/ui components.
Features
✨ Interactive & Modern
- 🎯 Drag and drop items between groups
- 🔍 Zoom in/out functionality
- 📅 Sticky headers with smooth scrolling
- 🎨 Customizable item and group rendering
- 🌍 Multi-language support (11+ locales)
⚡ Powerful
- 📊 Category-based styling
- 🎭 Custom icons with tooltips
- 📐 Resize items by dragging edges
- 📏 Variable row height (compact to spacious)
- 🔄 Automatic item stacking for overlapping items
- ⌨️ Keyboard accessible
- 🎬 Smooth animations
🛠️ Developer Friendly
- 💪 Full TypeScript support
- 🎨 Built with Tailwind CSS
- 📦 Tree-shakeable
- 🔧 Highly customizable
- 📖 Comprehensive type definitions
Installation
npm install modern-react-timeline-component
# or
yarn add modern-react-timeline-component
# or
pnpm add modern-react-timeline-componentPeer Dependencies
npm install react react-dom dayjs lucide-react✨ Zero-Config Setup
That's it! The component works out of the box with zero configuration:
- ✅ CSS is automatically injected - No manual imports needed
- ✅ Works with any bundler - Webpack, Vite, Next.js, etc.
- ✅ No build configuration required - Just import and use
import { Timeline } from 'modern-react-timeline-component';
// Styles are automatically included - nothing else needed!Optional: Tailwind CSS Integration
If you're using Tailwind CSS and want to customize the component's styles, you can optionally use our preset:
// tailwind.config.js
module.exports = {
presets: [
require('modern-react-timeline-component/tailwind-preset')
],
content: [
'./src/**/*.{js,jsx,ts,tsx}',
// your other content paths...
],
// ... rest of your config
}Note: This is completely optional. The component works perfectly without any Tailwind configuration.
Quick Start
import { Timeline, TimelineItem, TimelineGroupData, Category } from 'modern-react-timeline-component';
const groups: TimelineGroupData[] = [
{ id: 'group-1', title: 'Team Alpha' },
{ id: 'group-2', title: 'Team Beta' },
];
const categories: Category[] = [
{
id: 'dev',
title: 'Development',
background_color: '#3b82f6',
text_color: '#ffffff'
},
];
const items: TimelineItem[] = [
{
id: '1',
group: 'group-1',
title: 'Project Kickoff',
start: new Date('2024-01-15T09:00:00'),
end: new Date('2024-01-15T17:00:00'),
category: 'dev',
},
];
function App() {
return (
<div className="h-screen p-4">
<Timeline
groups={groups}
categories={categories}
items={items}
locale="en"
editable={true}
selectable={true}
showLegend={true}
showControls={true}
/>
</div>
);
}Props
Timeline Component
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| groups | TimelineGroupData[] | required | Array of group/row definitions |
| categories | Category[] | required | Array of category definitions for styling |
| items | TimelineItem[] | required | Array of timeline items to display |
| locale | string | 'en' | Locale code for date/time formatting |
| defaultTimeStart | Date | 6 months ago | Start of the total scrollable range |
| defaultTimeEnd | Date | 6 months ahead | End of the total scrollable range |
| visibleTimeStart | Date | 3 days ago | Defines the start of the visible range at 100% zoom |
| visibleTimeEnd | Date | 4 days ahead | Defines the end of the visible range at 100% zoom |
| stickyHeader | boolean | true | Enable sticky date headers |
| editable | boolean | true | Allow drag and resize |
| selectable | boolean | true | Allow item selection |
| showLegend | boolean | true | Show category legend |
| showControls | boolean | true | Show zoom/navigation controls |
| showZoomLevel | boolean | true | Show zoom level percentage in controls |
| groupBarWidth | number | 192 | Width of the group labels column (px) |
| rowHeight | number | 60 | Height of each timeline row/group (px) |
| selectedItemId | string \| null | null | ID of currently selected item |
| groupRenderer | function | - | Custom group label renderer |
| groupHeaderRenderer | function | - | Custom group header renderer |
| itemRenderer | function | - | Custom item renderer |
| controlsRenderer | function | - | Custom controls renderer |
| onItemClick | function | - | Called when item is clicked |
| onItemSelect | function | - | Called when item selection changes |
| onItemMove | function | - | Called when item is dragged |
| onItemResize | function | - | Called when item is resized |
Understanding Time Ranges
The timeline uses two different time range concepts:
Total Range (
defaultTimeStart/defaultTimeEnd): Defines the entire scrollable area. Users can scroll through this entire range. Defaults to 6 months before and after today.Visible Range (
visibleTimeStart/visibleTimeEnd): Defines what time period is shown at 100% zoom level. This controls the "base" zoom - at zoom level 1.0, exactly this time range fits in the viewport. Defaults to 1 week (3 days before, 4 days after today).
Example:
<Timeline
// Total scrollable range: 1 year
defaultTimeStart={dayjs().subtract(6, 'month').toDate()}
defaultTimeEnd={dayjs().add(6, 'month').toDate()}
// At 100% zoom, show 5 weeks
visibleTimeStart={dayjs().startOf('week').toDate()}
visibleTimeEnd={dayjs().add(4, 'week').endOf('week').toDate()}
// ... other props
/>In this example:
- Users can scroll through a full year of data
- At 100% zoom, they see 5 weeks at a time
- Zooming in (200%) shows ~2.5 weeks
- Zooming out (50%) shows ~10 weeks
Type Definitions
TimelineGroupData
interface TimelineGroupData {
id: string;
title: string;
height?: number;
[key: string]: any; // Additional custom properties
}TimelineItem
interface TimelineItem {
id: string;
group: string; // References group id
title: string;
start: Date;
end: Date;
show_duration?: boolean;
icon?: {
name: string; // Icon name from lucide-react
text: string; // Tooltip text
position: "left" | "right";
};
category?: string; // References category id
style?: React.CSSProperties;
}Category
interface Category {
id: string;
title: string;
background_color: string;
text_color: string;
}Advanced Usage
Custom Item Renderer
import { ItemRendererProps } from 'modern-react-timeline-component';
const customItemRenderer = ({ item, itemContext, getItemProps, getResizeProps }: ItemRendererProps) => {
const { left: leftResize, right: rightResize } = getResizeProps();
const itemProps = getItemProps();
return (
<div {...itemProps}>
{itemContext.useResizeHandle && <div {...leftResize} />}
<div className="p-2">
<h4>{itemContext.title}</h4>
<p>{item.category}</p>
</div>
{itemContext.useResizeHandle && <div {...rightResize} />}
</div>
);
};
<Timeline
itemRenderer={customItemRenderer}
// ... other props
/>Custom Group Renderer
const customGroupRenderer = ({ group }) => (
<div className="flex items-center gap-2">
<Avatar src={group.avatar} />
<div>
<div className="font-bold">{group.title}</div>
<div className="text-sm text-gray-500">{group.description}</div>
</div>
</div>
);
<Timeline
groupRenderer={customGroupRenderer}
// ... other props
/>Custom Controls Renderer
import { ControlsRendererProps } from 'modern-react-timeline-component';
const customControlsRenderer = ({ controls, state }: ControlsRendererProps) => (
<div className="flex gap-2 p-4">
<button onClick={controls.zoomIn} disabled={!state.canZoomIn}>
Zoom In
</button>
<button onClick={controls.zoomOut} disabled={!state.canZoomOut}>
Zoom Out
</button>
<button onClick={controls.goHome}>
Reset
</button>
<span>Zoom: {Math.round(state.zoom * 100)}%</span>
</div>
);
<Timeline
controlsRenderer={customControlsRenderer}
// ... other props
/>Scroll to Group Programmatically
import { useRef } from 'react';
import { Timeline, TimelineRef } from 'modern-react-timeline-component';
function App() {
const timelineRef = useRef<TimelineRef>(null);
const scrollToGroup = (groupId: string) => {
timelineRef.current?.scrollToGroup(groupId);
};
return (
<>
<button onClick={() => scrollToGroup('group-1')}>
Go to Team Alpha
</button>
<Timeline
ref={timelineRef}
// ... other props
/>
</>
);
}Custom Group Header Renderer
Customize the header area above the group labels (top-left corner of the timeline):
import { Users } from 'lucide-react';
const customGroupHeaderRenderer = ({ width, height }) => {
return (
<div
className="bg-gradient-to-br from-primary/10 to-primary/5 border-r flex items-center justify-center"
style={{ width: `${width}px`, height: `${height}px` }}
>
<div className="flex flex-col items-center gap-1">
<div className="flex items-center gap-2">
<Users className="w-4 h-4 text-primary" />
<span className="font-semibold text-sm">Teams</span>
</div>
<span className="text-xs text-muted-foreground">
{groups.length} teams
</span>
</div>
</div>
);
};
<Timeline
groupHeaderRenderer={customGroupHeaderRenderer}
// ... other props
/>Use cases:
- Display a logo or branding
- Show summary information (e.g., total groups count)
- Add custom styling to match your design system
- Create a title for the groups section
Event Handlers
<Timeline
onItemClick={(item) => {
console.log('Item clicked:', item);
}}
onItemMove={(itemId, newTime, newGroupId) => {
console.log('Item moved:', itemId, newTime, newGroupId);
// Update your state here
}}
onItemResize={(itemId, newStart, newEnd) => {
console.log('Item resized:', itemId, newStart, newEnd);
// Update your state here
}}
// ... other props
/>Supported Locales
The component supports 11+ locales with proper date/time formatting:
- 🇬🇧 English (
en) - 🇪🇸 Spanish (
es) - 🇫🇷 French (
fr) - 🇩🇪 German (
de) - 🇳🇱 Dutch (
nl) - 🇯🇵 Japanese (
ja) - 🇨🇳 Chinese (
zh) - 🇵🇹 Portuguese (
pt) - 🇷🇺 Russian (
ru) - 🇸🇦 Arabic (
ar) - 🇮🇳 Hindi (
hi)
Styling
The component uses Tailwind CSS classes. You can customize the theme by extending your Tailwind configuration:
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
// Override timeline colors
primary: {
DEFAULT: '#your-color',
// ...
},
},
},
},
}License
MIT © Jeremie Van de Walle
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
If you encounter any issues or have questions, please file an issue on the GitHub repository.
