react-timelane 0.0.0

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.
Files changed (72) hide show
  1. package/.github/workflows/static.yml +55 -0
  2. package/README.md +54 -0
  3. package/docs/README.md +54 -0
  4. package/docs/eslint.config.js +28 -0
  5. package/docs/index.html +12 -0
  6. package/docs/package-lock.json +5101 -0
  7. package/docs/package.json +35 -0
  8. package/docs/src/App.css +5 -0
  9. package/docs/src/App.tsx +59 -0
  10. package/docs/src/assets/react.svg +1 -0
  11. package/docs/src/components/AllocationComponent.tsx +82 -0
  12. package/docs/src/components/Timeline.tsx +183 -0
  13. package/docs/src/constants.ts +42 -0
  14. package/docs/src/hooks/useLocalStorage.ts +21 -0
  15. package/docs/src/main.tsx +9 -0
  16. package/docs/src/models/Allocation.ts +11 -0
  17. package/docs/src/models/AllocationId.ts +1 -0
  18. package/docs/src/models/Resource.ts +8 -0
  19. package/docs/src/models/ResourceId.ts +1 -0
  20. package/docs/src/vite-env.d.ts +1 -0
  21. package/docs/tsconfig.json +27 -0
  22. package/docs/vite.config.ts +8 -0
  23. package/eslint.config.js +28 -0
  24. package/index.html +13 -0
  25. package/package.json +43 -0
  26. package/src/components/Timeline.scss +297 -0
  27. package/src/components/TimelineAside.tsx +81 -0
  28. package/src/components/TimelineBackground.tsx +53 -0
  29. package/src/components/TimelineBody.tsx +54 -0
  30. package/src/components/TimelineHeader/DaysHeader.tsx +44 -0
  31. package/src/components/TimelineHeader/MonthsHeader.tsx +62 -0
  32. package/src/components/TimelineHeader/TimelineHeader.tsx +64 -0
  33. package/src/components/TimelineHeader/WeeksHeader.tsx +63 -0
  34. package/src/components/TimelineHeader/index.ts +9 -0
  35. package/src/components/TimelineHeader/renderingUtils.tsx +57 -0
  36. package/src/components/TimelineSelectionLayer.tsx +179 -0
  37. package/src/components/TimelineSettingsContext.tsx +27 -0
  38. package/src/components/TimelineSettingsProvider.tsx +24 -0
  39. package/src/components/TimelineWrapper.tsx +51 -0
  40. package/src/components/core/CoreItem/CoreItemComponent.tsx +69 -0
  41. package/src/components/core/CoreItem/DragResizeComponent.tsx +180 -0
  42. package/src/components/core/CoreSwimlane/AvailableSpaceIndicator.tsx +156 -0
  43. package/src/components/core/CoreSwimlane/CoreSwimlane.tsx +245 -0
  44. package/src/components/core/CoreSwimlane/DropPreview.tsx +30 -0
  45. package/src/components/core/CoreSwimlane/DropTarget.tsx +83 -0
  46. package/src/components/core/CoreSwimlane/OverlapIndicator.tsx +22 -0
  47. package/src/components/core/CoreSwimlane/utils.ts +375 -0
  48. package/src/components/core/utils.ts +154 -0
  49. package/src/components/layout/TimelineLayout.tsx +93 -0
  50. package/src/components/layout/layout.scss +107 -0
  51. package/src/global.d.ts +9 -0
  52. package/src/hooks/useScroll.tsx +71 -0
  53. package/src/hooks/useTimelineContext.tsx +6 -0
  54. package/src/index.ts +15 -0
  55. package/src/types/AvailableSpace.ts +6 -0
  56. package/src/types/CoreItem.ts +23 -0
  57. package/src/types/DateBounds.ts +4 -0
  58. package/src/types/Dimensions.ts +4 -0
  59. package/src/types/GrabInfo.ts +6 -0
  60. package/src/types/Grid.ts +6 -0
  61. package/src/types/ItemId.ts +1 -0
  62. package/src/types/OffsetBounds.ts +4 -0
  63. package/src/types/Pixels.ts +4 -0
  64. package/src/types/Position.ts +4 -0
  65. package/src/types/Rectangle.ts +6 -0
  66. package/src/types/SwimlaneId.ts +1 -0
  67. package/src/types/SwimlaneT.ts +6 -0
  68. package/src/types/TimeRange.ts +4 -0
  69. package/src/types/TimelineSettings.ts +11 -0
  70. package/src/types/index.ts +15 -0
  71. package/tsconfig.json +32 -0
  72. package/vite.config.ts +32 -0
@@ -0,0 +1,297 @@
1
+ * {
2
+ box-sizing: border-box;
3
+ }
4
+ .timeline {
5
+ --timeline-aside-width: 150px;
6
+ --timeline-border-color-light: #f0f0f0;
7
+ --timeline-border-color-normal: lightgray;
8
+ --timeline-border-color-dark: gray;
9
+
10
+ --timeline-highlight-color: #f8f8f8;
11
+ --timeline-focused-color: rgba(0, 0, 255, 0.1);
12
+ --timeline-hover-color: rgba(0, 0, 0, 0.05);
13
+
14
+ overflow: auto;
15
+ position: relative;
16
+ width: 100%;
17
+ height: 100%;
18
+ border-left: 1px solid var(--timeline-border-color-normal);
19
+
20
+ .timeline-header {
21
+ top: 0;
22
+ z-index: 101;
23
+ background: white;
24
+
25
+ .timeline-header-months {
26
+ display: flex;
27
+ flex-flow: row nowrap;
28
+
29
+ .timeline-header-month-label {
30
+ border-right: 1px solid var(--timeline-border-color-normal);
31
+ border-bottom: 1px solid var(--timeline-border-color-normal);
32
+ overflow: hidden;
33
+ font-size: 1em;
34
+ height: 30px;
35
+ line-height: 30px;
36
+ text-align: center;
37
+ cursor: pointer;
38
+
39
+ &:hover {
40
+ background: var(--timeline-hover-color);
41
+ }
42
+ }
43
+ }
44
+
45
+ .timeline-header-weeks {
46
+ display: flex;
47
+ flex-flow: row nowrap;
48
+
49
+ .timeline-header-week-label {
50
+ border-right: 1px solid var(--timeline-border-color-normal);
51
+ border-bottom: 1px solid var(--timeline-border-color-normal);
52
+ overflow: hidden;
53
+ font-size: 1em;
54
+ height: 30px;
55
+ line-height: 30px;
56
+ text-align: center;
57
+ cursor: pointer;
58
+
59
+ &:hover {
60
+ background: var(--timeline-hover-color);
61
+ }
62
+ }
63
+ }
64
+
65
+ .timeline-header-days {
66
+ display: flex;
67
+ flex-flow: row nowrap;
68
+
69
+ .timeline-header-day-label {
70
+ border-right: 1px solid var(--timeline-border-color-normal);
71
+ border-bottom: 1px solid var(--timeline-border-color-normal);
72
+ overflow: hidden;
73
+ font-size: 0.8em;
74
+ height: 20px;
75
+ line-height: 20px;
76
+ text-align: center;
77
+ cursor: pointer;
78
+
79
+ &:hover {
80
+ background: var(--timeline-hover-color);
81
+ }
82
+
83
+ &.timeline-header-day-label-focused {
84
+ background: var(--timeline-focused-color);
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+ .timeline-header-corner {
91
+ z-index: 102;
92
+ background: white;
93
+ border-right: 1px solid var(--timeline-border-color-normal);
94
+ border-bottom: 1px solid var(--timeline-border-color-normal);
95
+ }
96
+
97
+ .timeline-body {
98
+ z-index: 99;
99
+ position: relative;
100
+ width: fit-content;
101
+
102
+ .timeline-swimlane {
103
+ border-color: var(--timeline-border-color-normal) !important;
104
+ border-top: 1px solid gray;
105
+ border-bottom: 1px solid gray;
106
+ margin-top: -1px;
107
+ overflow: hidden;
108
+
109
+ .timeline-drop-target {
110
+ position: relative;
111
+ width: 100%;
112
+ height: 100%;
113
+
114
+ .timeline-item {
115
+ position: absolute;
116
+ cursor: pointer;
117
+ border-radius: 3px;
118
+
119
+ -webkit-touch-callout: none; /* iOS Safari */
120
+ -webkit-user-select: none; /* Safari */
121
+ -khtml-user-select: none; /* Konqueror HTML */
122
+ -moz-user-select: none; /* Old versions of Firefox */
123
+ -ms-user-select: none; /* Internet Explorer/Edge */
124
+ user-select: none; /* Non-prefixed version, currently
125
+ supported by Chrome, Edge, Opera and Firefox */
126
+ &.timeline-item-marked {
127
+ .timeline-allocation {
128
+ border: 2px dashed rgba(0, 0, 0, 0.5);
129
+ }
130
+ }
131
+
132
+ .timeline-item-drag-handle {
133
+ height: 100%;
134
+ }
135
+
136
+ .timeline-item-resize-handle {
137
+ background: #bed7dc;
138
+ border-radius: 5px;
139
+ top: 4px !important;
140
+ bottom: 4px !important;
141
+ height: auto !important;
142
+ width: 6px !important;
143
+ z-index: 2;
144
+ background: transparent !important;
145
+ transition: 0.2s;
146
+
147
+ &:hover {
148
+ background: rgba(0, 0, 0, 0.1) !important;
149
+ }
150
+
151
+ &.timeline-item-resize-handle-left {
152
+ left: 0 !important;
153
+ margin-left: 1px;
154
+ }
155
+ &.timeline-item-resize-handle-right {
156
+ right: 0 !important;
157
+ }
158
+ }
159
+
160
+ .timeline-allocation {
161
+ background: #92a8d1;
162
+ border-radius: 2px;
163
+ border: 2px solid transparent;
164
+ height: calc(100% - 1px);
165
+ overflow: hidden;
166
+ cursor: pointer;
167
+ padding: 0 4px;
168
+ margin-left: 1px;
169
+
170
+ -webkit-touch-callout: none; /* iOS Safari */
171
+ -webkit-user-select: none; /* Safari */
172
+ -khtml-user-select: none; /* Konqueror HTML */
173
+ -moz-user-select: none; /* Old versions of Firefox */
174
+ -ms-user-select: none; /* Internet Explorer/Edge */
175
+ user-select: none; /* Non-prefixed version, currently
176
+ supported by Chrome, Edge, Opera and Firefox */
177
+
178
+ container-type: size;
179
+ container-name: timeline-allocation;
180
+
181
+ .timeline-allocation-title {
182
+ font-weight: bold;
183
+ transform-origin: top left;
184
+ // font-size: 20px;
185
+ line-height: 30px;
186
+ white-space: nowrap;
187
+ }
188
+
189
+ @container timeline-allocation (max-height: 30px) {
190
+ .timeline-allocation-title {
191
+ margin: 0;
192
+ }
193
+ }
194
+
195
+ &.timeline-allocation-selected {
196
+ border: 2px dashed rgba(0, 0, 0, 0.5);
197
+ }
198
+ }
199
+
200
+ &.dragging {
201
+ opacity: 0;
202
+ }
203
+ }
204
+
205
+ .timeline-drop-preview {
206
+ background: blue;
207
+ position: absolute;
208
+ border-radius: 2px;
209
+ color: white;
210
+ }
211
+ }
212
+
213
+ &.timeline-row-focused {
214
+ background: var(--timeline-focused-color);
215
+ }
216
+ }
217
+ }
218
+
219
+ .timeline-aside {
220
+ border-right: 1px solid var(--timeline-border-color-normal);
221
+ width: var(--timeline-aside-width);
222
+ background: white;
223
+
224
+ .timeline-aside-swimlane-header {
225
+ border: 1px solid var(--timeline-border-color-normal);
226
+ border-right: none;
227
+ border-left: none;
228
+ margin-top: -1px;
229
+ padding: 10px;
230
+ overflow: hidden;
231
+ position: relative;
232
+ cursor: pointer;
233
+
234
+ &.timeline-aside-swimlane-header-focused {
235
+ background: var(--timeline-focused-color);
236
+ }
237
+
238
+ &:hover {
239
+ .timeline-aside-resource-menu {
240
+ opacity: 1;
241
+ }
242
+ }
243
+
244
+ .timeline-aside-resource-menu {
245
+ position: absolute;
246
+ top: 5px;
247
+ right: 5px;
248
+ opacity: 0;
249
+ transition: 0.15s;
250
+
251
+ &.timeline-aside-resource-menu-open {
252
+ opacity: 1;
253
+ }
254
+ }
255
+ }
256
+ }
257
+
258
+ .timeline-background {
259
+ position: relative;
260
+ width: 100%;
261
+ height: 100%;
262
+
263
+ .timeline-background-inner {
264
+ display: flex;
265
+ flex-flow: row nowrap;
266
+ height: 100%;
267
+
268
+ .timeline-background-day-label {
269
+ border-right: 1px solid var(--timeline-border-color-light);
270
+ overflow: hidden;
271
+ font-size: 0.8em;
272
+ height: 100%;
273
+ z-index: -100;
274
+
275
+ &.timeline-background-day-label-sunday {
276
+ border-right: 1px solid var(--timeline-border-color-normal);
277
+ }
278
+
279
+ &.timeline-background-day-label-focused {
280
+ background: var(--timeline-focused-color);
281
+ }
282
+ }
283
+
284
+ .timeline-background-focused-day-position {
285
+ position: absolute;
286
+ height: 100%;
287
+ background: var(--timeline-focused-color);
288
+ z-index: -101;
289
+ }
290
+ }
291
+ }
292
+ }
293
+
294
+ .timeline-header-tooltip,
295
+ .timeline-header-day-tooltip {
296
+ font-size: 2em;
297
+ }
@@ -0,0 +1,81 @@
1
+ import { MouseEvent, PropsWithChildren, ReactElement } from "react";
2
+ import { useTimelineContext } from "../hooks/useTimelineContext";
3
+ import { SwimlaneT } from "../types";
4
+
5
+ interface TimelineAsideProps {
6
+ swimlanes: SwimlaneT[];
7
+ focusedSwimlane?: SwimlaneT | null;
8
+ setFocusedSwimlane?: (lane: SwimlaneT | null) => void;
9
+ onSwimlaneHeaderClick?: (lane: SwimlaneT, e: MouseEvent) => void;
10
+ onSwimlaneHeaderDoubleClick?: (lane: SwimlaneT, e: MouseEvent) => void;
11
+ onSwimlaneHeaderContextMenu?: (lane: SwimlaneT, e: MouseEvent) => void;
12
+ renderSwimlaneHeader?: (lane: SwimlaneT) => ReactElement;
13
+ }
14
+
15
+ export default function TimelineAside({
16
+ swimlanes,
17
+ focusedSwimlane,
18
+ setFocusedSwimlane = () => undefined,
19
+ onSwimlaneHeaderClick = () => undefined,
20
+ onSwimlaneHeaderDoubleClick = () => undefined,
21
+ onSwimlaneHeaderContextMenu = () => undefined,
22
+ renderSwimlaneHeader = defaultRenderSwimlaneHeader,
23
+ }: TimelineAsideProps) {
24
+ const { settings } = useTimelineContext();
25
+
26
+ return (
27
+ <div className="timeline-aside">
28
+ {swimlanes &&
29
+ swimlanes.map((lane) => (
30
+ <SwimlaneHeader
31
+ key={lane.id}
32
+ height={settings.pixelsPerResource}
33
+ isFocused={focusedSwimlane ? focusedSwimlane.id === lane.id : false}
34
+ onClick={(e) => {
35
+ setFocusedSwimlane(lane);
36
+ onSwimlaneHeaderClick(lane, e);
37
+ }}
38
+ onDoubleClick={(e) => onSwimlaneHeaderDoubleClick(lane, e)}
39
+ onContextMenu={(e) => onSwimlaneHeaderContextMenu(lane, e)}
40
+ >
41
+ {renderSwimlaneHeader(lane)}
42
+ </SwimlaneHeader>
43
+ ))}
44
+ </div>
45
+ );
46
+ }
47
+
48
+ interface SwimlaneHeaderProps {
49
+ height: number;
50
+ isFocused?: boolean;
51
+ onClick?: (e: MouseEvent) => void;
52
+ onDoubleClick?: (e: MouseEvent) => void;
53
+ onContextMenu?: (e: MouseEvent) => void;
54
+ }
55
+
56
+ function SwimlaneHeader({
57
+ height,
58
+ isFocused,
59
+ onClick,
60
+ onDoubleClick,
61
+ onContextMenu,
62
+ children,
63
+ }: PropsWithChildren<SwimlaneHeaderProps>) {
64
+ return (
65
+ <div
66
+ className={`timeline-aside-swimlane-header ${
67
+ isFocused ? "timeline-aside-swimlane-header-focused" : ""
68
+ }`}
69
+ style={{ height: `${height}px` }}
70
+ onClick={onClick}
71
+ onDoubleClick={onDoubleClick}
72
+ onContextMenu={onContextMenu}
73
+ >
74
+ {children}
75
+ </div>
76
+ );
77
+ }
78
+
79
+ function defaultRenderSwimlaneHeader(lane: SwimlaneT) {
80
+ return <div>{lane.id}</div>;
81
+ }
@@ -0,0 +1,53 @@
1
+ import { eachDayOfInterval, format, isSunday } from "date-fns";
2
+ import { dateToPixel } from "./core/utils";
3
+ import { useTimelineContext } from "../hooks/useTimelineContext";
4
+
5
+ interface TimelineBackgroundProps {
6
+ focusedDay?: Date | null;
7
+ }
8
+
9
+ export default function TimelineBackground({
10
+ focusedDay,
11
+ }: TimelineBackgroundProps) {
12
+ const { settings } = useTimelineContext();
13
+
14
+ return (
15
+ <div className="timeline-background">
16
+ <div className="timeline-background-inner">
17
+ {focusedDay && (
18
+ <div
19
+ className="timeline-background-focused-day-position"
20
+ style={{
21
+ width: `${settings.pixelsPerDay}px`,
22
+ marginLeft: `${
23
+ dateToPixel(focusedDay, settings.start, settings) -
24
+ settings.pixelsPerDay / 2
25
+ }px`,
26
+ }}
27
+ ></div>
28
+ )}
29
+
30
+ <div
31
+ id="timeline-background-date-anchor"
32
+ style={{
33
+ position: "absolute",
34
+ top: 0,
35
+ width: `1px`,
36
+ height: "100%",
37
+ }}
38
+ ></div>
39
+
40
+ {eachDayOfInterval(settings).map((day, index) => (
41
+ <div
42
+ key={index}
43
+ className={`timeline-background-day-label ${
44
+ isSunday(day) ? "timeline-background-day-label-sunday" : ""
45
+ } `}
46
+ style={{ width: `${settings.pixelsPerDay}px` }}
47
+ data-day={format(day, "yyyy-MM-dd")}
48
+ ></div>
49
+ ))}
50
+ </div>
51
+ </div>
52
+ );
53
+ }
@@ -0,0 +1,54 @@
1
+ import { MouseEvent, ReactElement } from "react";
2
+ import CoreSwimlane from "./core/CoreSwimlane/CoreSwimlane";
3
+ import { AvailableSpace, CoreItem, SwimlaneT } from "../types";
4
+
5
+ interface TimelineBodyProps<T> {
6
+ lanes: SwimlaneT[];
7
+ items: CoreItem<T>[];
8
+ renderItem?: (item: CoreItem<T>, isDragged: boolean) => ReactElement;
9
+ onItemUpdate?: (item: CoreItem<T>) => void;
10
+ onLaneClick?: (
11
+ lane: SwimlaneT,
12
+ when: Date,
13
+ availableSpace: AvailableSpace | null,
14
+ e: MouseEvent
15
+ ) => void;
16
+ onLaneDoubleClick?: (
17
+ lane: SwimlaneT,
18
+ when: Date,
19
+ availableSpace: AvailableSpace | null,
20
+ e: MouseEvent
21
+ ) => void;
22
+ onLaneContextMenu?: (lane: SwimlaneT, when: Date, e: MouseEvent) => void;
23
+ }
24
+
25
+ export function TimelineBody<T>({
26
+ lanes,
27
+ items,
28
+ renderItem,
29
+ onItemUpdate = () => undefined,
30
+ onLaneClick = () => undefined,
31
+ onLaneDoubleClick = () => undefined,
32
+ onLaneContextMenu = () => undefined,
33
+ }: TimelineBodyProps<T>) {
34
+ return (
35
+ <div className="timeline-body">
36
+ {lanes.map((lane) => (
37
+ <CoreSwimlane<T>
38
+ key={lane.id}
39
+ swimlane={lane}
40
+ items={items.filter((item) => item.swimlaneId === lane.id)}
41
+ renderItem={renderItem}
42
+ onItemUpdate={onItemUpdate}
43
+ onClick={(when, availableSpace, e) =>
44
+ onLaneClick(lane, when, availableSpace, e)
45
+ }
46
+ onDoubleClick={(when, availableSpace, e) =>
47
+ onLaneDoubleClick(lane, when, availableSpace, e)
48
+ }
49
+ onContextMenu={(when, e) => onLaneContextMenu(lane, when, e)}
50
+ />
51
+ ))}
52
+ </div>
53
+ );
54
+ }
@@ -0,0 +1,44 @@
1
+ import { MouseEvent, ReactElement } from "react";
2
+ import { Pixels, TimeRange } from "../../types";
3
+ import { eachDayOfInterval, isSameDay } from "date-fns";
4
+ import { renderDayHeader } from "./renderingUtils";
5
+
6
+ interface DaysHeaderProps {
7
+ range: TimeRange;
8
+ pixels: Pixels;
9
+ focusedDay?: Date | null;
10
+ setFocusedDay?: (day: Date | null) => void;
11
+ render?: (day: Date) => ReactElement;
12
+ onDayClick?: (_: { day: Date; e: MouseEvent }) => void;
13
+ }
14
+
15
+ export function DaysHeader({
16
+ range,
17
+ pixels,
18
+ focusedDay,
19
+ setFocusedDay = () => {},
20
+ render = renderDayHeader,
21
+ onDayClick = () => undefined,
22
+ }: DaysHeaderProps) {
23
+ return (
24
+ <div className="timeline-header-days">
25
+ {eachDayOfInterval(range).map((day, index) => (
26
+ <div
27
+ key={index}
28
+ className={`timeline-header-day-label ${
29
+ focusedDay && isSameDay(focusedDay, day)
30
+ ? "timeline-header-day-label-focused"
31
+ : ""
32
+ }`}
33
+ style={{ width: `${pixels.pixelsPerDay}px` }}
34
+ onClick={(e) => {
35
+ onDayClick({ day, e });
36
+ setFocusedDay(day);
37
+ }}
38
+ >
39
+ {render(day)}
40
+ </div>
41
+ ))}
42
+ </div>
43
+ );
44
+ }
@@ -0,0 +1,62 @@
1
+ import { MouseEvent, ReactElement } from "react";
2
+ import { Pixels, TimeRange } from "../../types";
3
+ import {
4
+ differenceInCalendarDays,
5
+ eachMonthOfInterval,
6
+ lastDayOfMonth,
7
+ } from "date-fns";
8
+ import { renderMonthHeader } from "./renderingUtils";
9
+
10
+ interface MonthsHeaderProps {
11
+ range: TimeRange;
12
+ pixels: Pixels;
13
+ setFocusedDay?: (day: Date | null) => void;
14
+ render?: (firstDay: Date, lastDay: Date) => ReactElement;
15
+ onMonthClick?: (params: {
16
+ firstDay: Date;
17
+ lastDay: Date;
18
+ e: MouseEvent;
19
+ }) => void;
20
+ }
21
+
22
+ export function MonthsHeader({
23
+ range,
24
+ pixels,
25
+ setFocusedDay = () => {},
26
+ render = renderMonthHeader,
27
+ onMonthClick = () => undefined,
28
+ }: MonthsHeaderProps) {
29
+ return (
30
+ <div className="timeline-header-months">
31
+ {eachMonthOfInterval(range).map((firstDay, index) => {
32
+ if (firstDay < range.start) {
33
+ firstDay = range.start;
34
+ }
35
+
36
+ let lastDay = lastDayOfMonth(firstDay);
37
+
38
+ if (lastDay > range.end) {
39
+ lastDay = range.end;
40
+ }
41
+
42
+ const numberOfDays = differenceInCalendarDays(lastDay, firstDay) + 1;
43
+
44
+ return (
45
+ <div
46
+ key={index}
47
+ className="timeline-header-month-label"
48
+ style={{
49
+ width: `${pixels.pixelsPerDay * numberOfDays}px`,
50
+ }}
51
+ onClick={(e) => {
52
+ onMonthClick({ firstDay, lastDay, e });
53
+ setFocusedDay(firstDay);
54
+ }}
55
+ >
56
+ {render(firstDay, lastDay)}
57
+ </div>
58
+ );
59
+ })}
60
+ </div>
61
+ );
62
+ }
@@ -0,0 +1,64 @@
1
+ import { MouseEvent, ReactElement } from "react";
2
+ import { MonthsHeader } from "./MonthsHeader";
3
+ import { WeeksHeader } from "./WeeksHeader";
4
+ import { DaysHeader } from "./DaysHeader";
5
+ import { useTimelineContext } from "../../hooks/useTimelineContext";
6
+
7
+ interface TimelineHeaderProps {
8
+ focusedDay?: Date | null;
9
+ setFocusedDay?: (day: Date | null) => void;
10
+
11
+ renderMonthHeader?: (firstDay: Date, lastDay: Date) => ReactElement;
12
+ renderWeekHeader?: (firstDay: Date, lastDay: Date) => ReactElement;
13
+ renderDayHeader?: (day: Date) => ReactElement;
14
+
15
+ onMonthClick?: (_: { firstDay: Date; lastDay: Date; e: MouseEvent }) => void;
16
+ onWeekClick?: (_: { firstDay: Date; lastDay: Date; e: MouseEvent }) => void;
17
+ onDayClick?: (_: { day: Date; e: MouseEvent }) => void;
18
+ }
19
+
20
+ export function TimelineHeader({
21
+ focusedDay,
22
+ setFocusedDay = () => {},
23
+ renderMonthHeader,
24
+ renderWeekHeader,
25
+ renderDayHeader,
26
+ onMonthClick,
27
+ onDayClick,
28
+ onWeekClick,
29
+ }: TimelineHeaderProps) {
30
+ const { settings } = useTimelineContext();
31
+
32
+ return (
33
+ <div className="timeline-header">
34
+ {settings.showMonths && (
35
+ <MonthsHeader
36
+ range={settings}
37
+ pixels={settings}
38
+ setFocusedDay={setFocusedDay}
39
+ render={renderMonthHeader}
40
+ onMonthClick={onMonthClick}
41
+ />
42
+ )}
43
+ {settings.showWeeks && (
44
+ <WeeksHeader
45
+ range={settings}
46
+ pixels={settings}
47
+ setFocusedDay={setFocusedDay}
48
+ render={renderWeekHeader}
49
+ onWeekClick={onWeekClick}
50
+ />
51
+ )}
52
+ {settings.showDays && (
53
+ <DaysHeader
54
+ range={settings}
55
+ pixels={settings}
56
+ focusedDay={focusedDay}
57
+ setFocusedDay={setFocusedDay}
58
+ render={renderDayHeader}
59
+ onDayClick={onDayClick}
60
+ />
61
+ )}
62
+ </div>
63
+ );
64
+ }