@jaeungkim/gantt-chart 0.2.4 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -75
- package/dist/gantt-chart.css +1 -0
- package/dist/index.cjs.js +2 -32
- package/dist/index.d.ts +51 -0
- package/dist/index.es.js +2041 -2966
- package/package.json +12 -23
- package/dist/index.css +0 -126
package/README.md
CHANGED
|
@@ -2,24 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
<!--  -->
|
|
4
4
|
|
|
5
|
-
Lightweight, high-performance Gantt chart component for React applications
|
|
5
|
+
Lightweight, high-performance Gantt chart component for React applications. Designed for fast rendering with virtualization and clean, minimal aesthetics.
|
|
6
6
|
|
|
7
7
|
## 🎯 Motivation
|
|
8
8
|
|
|
9
|
-
I originally wanted to use Microsoft Project's Gantt Chart for personal project management, but it required subscription 😔. Thus, I decided to build my own Gantt chart, referencing various open-source projects and examples, including MS Project, DHTMLX, Frappe Gantt Chart, and etc.
|
|
9
|
+
I originally wanted to use Microsoft Project's Gantt Chart for personal project management, but it required a subscription 😔. Thus, I decided to build my own Gantt chart, referencing various open-source projects and examples, including MS Project, DHTMLX, Frappe Gantt Chart, and etc.
|
|
10
10
|
|
|
11
|
-
Since there aren
|
|
11
|
+
Since there aren't many open-source Gantt chart solutions available, I hope this project will be useful for others as well. I am very open to feedback, feature requests, and contributions to make this Gantt chart as robust and versatile as possible.
|
|
12
12
|
|
|
13
13
|
Currently, this project is built specifically for React due to my development background, but in the future, I may explore making it available for other frameworks as well. Since this is my first open-source project, I look forward to learning and improving it with the community!
|
|
14
14
|
|
|
15
15
|
## ✨ Features
|
|
16
16
|
|
|
17
|
-
- 📆
|
|
18
|
-
- 🔄 Drag-and-drop
|
|
19
|
-
-
|
|
20
|
-
-
|
|
17
|
+
- 📆 Multiple timeline scales: Day, Week, Month, Year
|
|
18
|
+
- 🔄 Drag-and-drop support:
|
|
19
|
+
- Move entire task bars
|
|
20
|
+
- Resize from left/right edges
|
|
21
|
+
- Snap to configured intervals
|
|
22
|
+
- 🧲 Smart dependency arrows (FS, SS, FF, SF)
|
|
23
|
+
- ⚡ Virtualized rendering for performance
|
|
24
|
+
- 🌙 Light/Dark/System theme support
|
|
25
|
+
- 📍 Today marker indicator
|
|
26
|
+
- 💬 Drag tooltip showing date changes
|
|
27
|
+
- 📦 Lightweight with minimal dependencies
|
|
21
28
|
|
|
22
|
-
## 📺 [Demo
|
|
29
|
+
## 📺 [Demo](https://jaeungkim.com/gantt-chart)
|
|
23
30
|
|
|
24
31
|
## 🚀 Getting Started
|
|
25
32
|
|
|
@@ -31,125 +38,123 @@ npm install @jaeungkim/gantt-chart
|
|
|
31
38
|
yarn add @jaeungkim/gantt-chart
|
|
32
39
|
```
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
import { Gantt } from '@jaeungkim/gantt-chart';
|
|
36
|
-
import type { Task } from '@jaeungkim/gantt-chart';
|
|
41
|
+
### Basic Usage
|
|
37
42
|
|
|
38
|
-
|
|
43
|
+
```tsx
|
|
44
|
+
import { ReactGanttChart } from '@jaeungkim/gantt-chart';
|
|
45
|
+
import type { Task } from '@jaeungkim/gantt-chart';
|
|
39
46
|
|
|
40
|
-
const
|
|
47
|
+
const tasks: Task[] = [
|
|
41
48
|
{
|
|
42
49
|
id: '1',
|
|
43
50
|
name: 'Project Kickoff',
|
|
44
51
|
startDate: '2024-06-01T09:00:00Z',
|
|
45
|
-
endDate: '2024-06-
|
|
52
|
+
endDate: '2024-06-03T17:00:00Z',
|
|
46
53
|
parentId: null,
|
|
47
54
|
sequence: '1',
|
|
55
|
+
dependencies: [],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: '2',
|
|
59
|
+
name: 'Requirements Gathering',
|
|
60
|
+
startDate: '2024-06-04T09:00:00Z',
|
|
61
|
+
endDate: '2024-06-10T17:00:00Z',
|
|
62
|
+
parentId: null,
|
|
63
|
+
sequence: '2',
|
|
48
64
|
dependencies: [{ targetId: '1', type: 'FS' }],
|
|
49
65
|
},
|
|
50
|
-
...
|
|
51
66
|
];
|
|
52
67
|
|
|
53
|
-
export default function
|
|
68
|
+
export default function App() {
|
|
54
69
|
return (
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
<ReactGanttChart
|
|
71
|
+
tasks={tasks}
|
|
72
|
+
height="100vh"
|
|
73
|
+
width="100%"
|
|
74
|
+
theme="system"
|
|
75
|
+
defaultScale="month"
|
|
76
|
+
onTasksChange={(updated) => console.log('Tasks updated:', updated)}
|
|
77
|
+
/>
|
|
63
78
|
);
|
|
64
79
|
}
|
|
65
80
|
```
|
|
66
81
|
|
|
67
|
-
|
|
82
|
+
## Props
|
|
68
83
|
|
|
69
|
-
| Prop
|
|
70
|
-
|
|
71
|
-
| `tasks`
|
|
72
|
-
| `onTasksChange` | `(
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
84
|
+
| Prop | Type | Default | Description |
|
|
85
|
+
|------|------|---------|-------------|
|
|
86
|
+
| `tasks` | `Task[]` | `[]` | Array of task objects to render |
|
|
87
|
+
| `onTasksChange` | `(tasks: Task[]) => void` | - | Callback when tasks are moved or resized |
|
|
88
|
+
| `height` | `number \| string` | `600` | Chart height (px or CSS value) |
|
|
89
|
+
| `width` | `number \| string` | `"100%"` | Chart width (px or CSS value) |
|
|
90
|
+
| `theme` | `"light" \| "dark" \| "system"` | - | Theme mode |
|
|
91
|
+
| `defaultScale` | `"day" \| "week" \| "month" \| "year"` | `"month"` | Initial timeline scale |
|
|
92
|
+
| `className` | `string` | - | Additional CSS class for the container |
|
|
75
93
|
|
|
76
|
-
|
|
94
|
+
## Task Format
|
|
77
95
|
|
|
78
|
-
All dates must be in **UTC ISO string format
|
|
79
|
-
Internally, dates are parsed and converted to local time using `dayjs`.
|
|
96
|
+
All dates must be in **UTC ISO string format**: `"2024-06-01T09:00:00Z"`
|
|
80
97
|
|
|
81
98
|
```ts
|
|
82
99
|
interface Task {
|
|
83
100
|
id: string;
|
|
84
101
|
name: string;
|
|
85
|
-
startDate: string;
|
|
86
|
-
endDate: string;
|
|
102
|
+
startDate: string; // UTC ISO string
|
|
103
|
+
endDate: string; // UTC ISO string
|
|
87
104
|
parentId: string | null;
|
|
88
105
|
sequence: string;
|
|
89
106
|
dependencies?: TaskDependency[];
|
|
90
107
|
}
|
|
91
108
|
|
|
92
|
-
export type DependencyType = 'FS' | 'SS' | 'FF' | 'SF';
|
|
93
|
-
|
|
94
109
|
interface TaskDependency {
|
|
95
110
|
targetId: string;
|
|
96
111
|
type: DependencyType;
|
|
97
112
|
}
|
|
113
|
+
|
|
114
|
+
type DependencyType = 'FS' | 'SS' | 'FF' | 'SF';
|
|
115
|
+
// FS = Finish-to-Start
|
|
116
|
+
// SS = Start-to-Start
|
|
117
|
+
// FF = Finish-to-Finish
|
|
118
|
+
// SF = Start-to-Finish
|
|
98
119
|
```
|
|
99
120
|
|
|
100
121
|
## Timeline Scales
|
|
101
122
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
- **`week`**
|
|
110
|
-
- Label: Week
|
|
111
|
-
- Tick Unit: Day
|
|
112
|
-
- Drag Step: 6 hours
|
|
113
|
-
|
|
114
|
-
- **`month`**
|
|
115
|
-
- Label: Month
|
|
116
|
-
- Tick Unit: Day
|
|
117
|
-
- Drag Step: 1 day
|
|
123
|
+
| Scale | Header Label | Tick Unit | Drag Step |
|
|
124
|
+
|-------|-------------|-----------|-----------|
|
|
125
|
+
| `day` | Day | Hour | 1 hour |
|
|
126
|
+
| `week` | Week | Day | 6 hours |
|
|
127
|
+
| `month` | Month | Day | 1 day |
|
|
128
|
+
| `year` | Year | Month | 7 days |
|
|
118
129
|
|
|
119
|
-
-
|
|
120
|
-
- Label: Year
|
|
121
|
-
- Tick Unit: 7 days
|
|
122
|
-
- Drag Step: 1 day
|
|
130
|
+
Switch scales using the dropdown at the top-right of the chart.
|
|
123
131
|
|
|
124
|
-
|
|
132
|
+
## Theming
|
|
125
133
|
|
|
126
|
-
|
|
134
|
+
The chart supports three theme modes:
|
|
127
135
|
|
|
128
|
-
|
|
129
|
-
|
|
136
|
+
- **`light`** - Light background with dark text
|
|
137
|
+
- **`dark`** - Dark background with light text
|
|
138
|
+
- **`system`** - Follows system preference (uses `prefers-color-scheme`)
|
|
130
139
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
- Tick intervals, formats, and drag steps (`GANTT_SCALE_CONFIG`)
|
|
135
|
-
|
|
136
|
-
Stay Tuned~
|
|
140
|
+
```tsx
|
|
141
|
+
<ReactGanttChart theme="dark" ... />
|
|
142
|
+
```
|
|
137
143
|
|
|
138
|
-
##
|
|
144
|
+
## Roadmap
|
|
139
145
|
|
|
140
|
-
- [ ] Left sidebar for
|
|
141
|
-
- [ ] Right sidebar for
|
|
146
|
+
- [ ] Left sidebar for task names
|
|
147
|
+
- [ ] Right sidebar for task details
|
|
142
148
|
- [ ] Collapsible parent-child rows
|
|
143
|
-
- [ ] Virtualized rows for large datasets
|
|
144
149
|
- [ ] Inline editing for task names
|
|
145
|
-
- [ ] Export to PNG
|
|
150
|
+
- [ ] Export to PNG/SVG
|
|
151
|
+
- [ ] Custom bar colors
|
|
146
152
|
|
|
147
153
|
## 🤝 Contributing
|
|
148
154
|
|
|
149
155
|
Pull requests are welcome!
|
|
150
156
|
If you find bugs or have suggestions, feel free to open an issue or contribute directly.
|
|
151
157
|
|
|
152
|
-
|
|
153
158
|
## 📄 License
|
|
154
159
|
|
|
155
|
-
MIT © [jaeungkim](https://github.com/jaeungkim)
|
|
160
|
+
MIT © [jaeungkim](https://github.com/jaeungkim)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import"https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600&display=swap";:root{--font-sans: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--background: #fafafa;--foreground: #18181b;--muted: #f4f4f5;--muted-foreground: #71717a;--border: #e4e4e7;--border-subtle: rgba(0, 0, 0, .04);--bar-bg: #e4e4e7;--bar-bg-hover: #d4d4d8;--bar-text: #18181b;--bar-shadow: 0 1px 2px rgba(0, 0, 0, .05);--bar-shadow-hover: 0 4px 12px rgba(0, 0, 0, .1);--bar-shadow-drag: 0 8px 24px rgba(0, 0, 0, .15);--arrow: #a1a1aa;--accent: #3b82f6;--transition-fast: .15s ease;--transition-normal: .2s ease}.dark,[data-theme=dark]{--background: #09090b;--foreground: #fafafa;--muted: #18181b;--muted-foreground: #a1a1aa;--border: #27272a;--border-subtle: rgba(255, 255, 255, .04);--bar-bg: #27272a;--bar-bg-hover: #3f3f46;--bar-text: #fafafa;--bar-shadow: 0 1px 2px rgba(0, 0, 0, .2);--bar-shadow-hover: 0 4px 12px rgba(0, 0, 0, .3);--bar-shadow-drag: 0 8px 24px rgba(0, 0, 0, .4);--arrow: #71717a}.gantt-container{display:flex;flex-direction:column;overflow:hidden;background:var(--background);font-family:var(--font-sans);font-size:13px;line-height:1.5;color:var(--foreground);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.gantt-toolbar{flex-shrink:0;display:flex;align-items:center;justify-content:flex-end;padding:12px 16px;background:var(--background);border-bottom:1px solid var(--border)}.gantt-scale-selector{display:inline-flex}.gantt-scale-control{display:flex;gap:2px;padding:3px;background:var(--muted);border-radius:8px;border:1px solid var(--border)}.gantt-scale-button{position:relative;padding:6px 14px;font-family:inherit;font-size:12px;font-weight:500;color:var(--muted-foreground);background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all var(--transition-fast);text-transform:capitalize}.gantt-scale-button:hover{color:var(--foreground)}.gantt-scale-button:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}.gantt-scale-button[data-active=true]{color:var(--foreground);background:var(--background);box-shadow:0 1px 3px #00000014}.gantt-main{flex:1;min-height:0;overflow:hidden}.gantt-scroll-container{width:100%;height:100%;overflow:auto}.gantt-scroll-container::-webkit-scrollbar{width:10px;height:10px}.gantt-scroll-container::-webkit-scrollbar-track{background:transparent}.gantt-scroll-container::-webkit-scrollbar-thumb{background:var(--border);border-radius:5px;border:2px solid var(--background)}.gantt-scroll-container::-webkit-scrollbar-thumb:hover{background:var(--muted-foreground)}.gantt-scroll-container::-webkit-scrollbar-corner{background:transparent}.gantt-header-wrapper{position:sticky;top:0;z-index:30}.gantt-header{position:relative;background:var(--background);border-bottom:1px solid var(--border)}.gantt-header-content{display:flex;flex-direction:column}.gantt-top-header{position:relative;display:flex;height:44px;border-bottom:1px solid var(--border-subtle)}.gantt-top-groups{display:flex}.gantt-top-group{display:flex;align-items:center;padding:0 16px;font-size:13px;font-weight:600;letter-spacing:-.01em;background:var(--background);color:var(--foreground)}.gantt-top-group.sticky{position:sticky;left:0;z-index:1}.gantt-top-group-label{margin:0;padding:0;white-space:nowrap}.gantt-bottom-row{display:flex;height:28px;background:var(--muted)}.gantt-bottom-cell{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:500;color:var(--muted-foreground);letter-spacing:.01em}.gantt-content{position:relative;background:var(--background)}.gantt-rows{position:absolute;top:0;left:0;width:100%;height:100%;z-index:1;pointer-events:none}.gantt-task-row{position:absolute;top:0;left:0;display:flex;width:100%;align-items:center;border-bottom:1px solid var(--border-subtle);pointer-events:none}.gantt-dependency-arrows{position:absolute;top:0;left:0;width:100%;pointer-events:none;z-index:5}.gantt-dependency-arrow{stroke:var(--arrow);stroke-width:1.5;fill:none}.gantt-dependency-arrow-head{fill:var(--arrow);stroke:none}.gantt-task-bar{position:relative;z-index:10;display:flex;align-items:center;background:var(--bar-bg);border-radius:6px;-webkit-user-select:none;user-select:none;cursor:grab;box-shadow:var(--bar-shadow);transition:background var(--transition-fast),box-shadow var(--transition-normal)}.gantt-task-bar:before,.gantt-task-bar:after{content:"";position:absolute;top:50%;width:3px;height:12px;background:var(--muted-foreground);border-radius:2px;opacity:0;transform:translateY(-50%);transition:opacity var(--transition-fast)}.gantt-task-bar:before{left:4px}.gantt-task-bar:after{right:4px}.gantt-task-bar:hover{background:var(--bar-bg-hover);box-shadow:var(--bar-shadow-hover)}.gantt-task-bar:hover:before,.gantt-task-bar:hover:after{opacity:.5}.gantt-task-bar:active,.gantt-task-bar.dragging{cursor:grabbing;box-shadow:var(--bar-shadow-drag);z-index:100}.gantt-task-bar.dragging:before,.gantt-task-bar.dragging:after{opacity:.7}.gantt-task-name{flex:1;padding:0 14px;font-family:inherit;font-size:12px;font-weight:500;color:var(--bar-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:none}.gantt-bar-tooltip{position:absolute;bottom:calc(100% + 8px);left:50%;transform:translate(-50%);z-index:200;padding:8px 12px;background:var(--foreground);color:var(--background);border-radius:6px;font-family:inherit;font-size:11px;font-weight:500;white-space:nowrap;box-shadow:0 4px 16px #00000026;pointer-events:none;animation:tooltipFadeIn var(--transition-fast) ease-out}@keyframes tooltipFadeIn{0%{opacity:0;transform:translate(-50%) translateY(4px)}to{opacity:1;transform:translate(-50%) translateY(0)}}.gantt-bar-tooltip:after{content:"";position:absolute;top:100%;left:50%;transform:translate(-50%);border:5px solid transparent;border-top-color:var(--foreground)}@media (max-width: 768px){.gantt-toolbar{padding:8px 12px}.gantt-scale-button{padding:5px 10px;font-size:11px}.gantt-top-group{font-size:12px;padding:0 12px}.gantt-bottom-cell{font-size:10px}.gantt-task-name{font-size:11px;padding:0 10px}}
|