@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 CHANGED
@@ -2,24 +2,31 @@
2
2
 
3
3
  <!-- ![React Gantt Chart](https://raw.githubusercontent.com/jaeungkim/gantt-chart/main/public/readmeImg.png) -->
4
4
 
5
- Lightweight, high-performance Gantt chart component for React applications, for fast rendering and state management. It is designed to be highly customizable and easy to integrate into modern React projects.
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 arent 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.
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
- - 📆 Supports multiple timeline scales: Day, Week, Month, Year
18
- - 🔄 Drag-and-drop resizing (snap to configured intervals)
19
- - 🧲 Smart dependency lines (Finish-Start, Start-Start, Start-Finish, Finish-Finish)
20
- - 📦 Lightweight and framework-agnostic component design
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 is worth a thousand words](https://jaeungkim.com/gantt-chart)
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
- ```ts
35
- import { Gantt } from '@jaeungkim/gantt-chart';
36
- import type { Task } from '@jaeungkim/gantt-chart';
41
+ ### Basic Usage
37
42
 
38
- export type DependencyType = 'FS' | 'SS' | 'FF' | 'SF';
43
+ ```tsx
44
+ import { ReactGanttChart } from '@jaeungkim/gantt-chart';
45
+ import type { Task } from '@jaeungkim/gantt-chart';
39
46
 
40
- const ReactGanttChart: Task[] = [
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-01T11:00:00Z',
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 Example() {
68
+ export default function App() {
54
69
  return (
55
- <div style={{ width: 'auto', height: '100dvh' }}>
56
- <ReactGanttChart
57
- tasks={tasks}
58
- ganttHeight="100%"
59
- columnWidth="100%"
60
- onTasksChange={(updated) => console.log(updated)}
61
- />
62
- </div>
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
- ### Props
82
+ ## Props
68
83
 
69
- | Prop | Type | Description |
70
- | --------------- | -------------------------------- | ---------------------------------------------- |
71
- | `tasks` | `Task[]` | Array of task objects to render |
72
- | `onTasksChange` | `(updatedTasks: Task[]) => void` | Callback fired when a task is moved or resized |
73
- | `ganttHeight` | `number \| string` | Height of the chart (`number` or `string`) |
74
- | `columnWidth` | `number \| string` | Width of the chart (`number` or `string`) |
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
- ### Task Format
94
+ ## Task Format
77
95
 
78
- All dates must be in **UTC ISO string format**, like: `"2024-06-01T09:00:00Z"`.
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; // UTC ISO string
86
- endDate: string; // UTC ISO 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
- The chart supports four built-in scales:
103
-
104
- - **`day`**
105
- - Label: Day
106
- - Tick Unit: Hour
107
- - Drag Step: 15 minutes
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
- - **`year`**
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
- You can switch between them using the dropdown at the top-right of the chart.
132
+ ## Theming
125
133
 
126
- ## Customization
134
+ The chart supports three theme modes:
127
135
 
128
- Currently at this stage it's not quite customizable other than importing your own tasks.
129
- But in near future, I will definitely make it customizable with adding features like
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
- - Timeline structure (`setupTimelineStructure`)
132
- - Header display (`GanttChartHeader`)
133
- - Bar visuals (`GanttBar`)
134
- - Tick intervals, formats, and drag steps (`GANTT_SCALE_CONFIG`)
135
-
136
- Stay Tuned~
140
+ ```tsx
141
+ <ReactGanttChart theme="dark" ... />
142
+ ```
137
143
 
138
- ## Roadmap
144
+ ## Roadmap
139
145
 
140
- - [ ] Left sidebar for displaying tasks' names
141
- - [ ] Right sidebar for selected tasks' to view their information
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 or SVG
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}}