@flikk/ui 1.0.0-beta.25 → 1.0.0-beta.27

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 (211) hide show
  1. package/dist/components/ai/CodeBlock/CodeBlock.theme.js +2 -2
  2. package/dist/components/ai/PromptInput/PromptInput.js +4 -0
  3. package/dist/components/ai/PromptInput/VoiceRecorder.js +4 -0
  4. package/dist/components/ai/StreamingResponse/MarkdownRenderer.js +1 -1
  5. package/dist/components/ai/TokenCounter/TokenCounter.theme.js +1 -1
  6. package/dist/components/charts/BarChart/BarChart.js +2 -2
  7. package/dist/components/charts/StackedBarChart/StackedBarChart.js +1 -1
  8. package/dist/components/charts/shared/BarRenderer/BarRenderer.js +2 -2
  9. package/dist/components/core/Breadcrumbs/Breadcrumbs.theme.js +1 -1
  10. package/dist/components/core/Button/Button.js +1 -1
  11. package/dist/components/core/Button/Button.theme.js +5 -5
  12. package/dist/components/core/Calendar/Calendar.theme.js +1 -1
  13. package/dist/components/core/Calendar/CalendarMini/CalendarMini.theme.js +1 -1
  14. package/dist/components/core/CommandPalette/CommandItem.js +4 -5
  15. package/dist/components/core/CommandPalette/CommandPalette.js +1 -1
  16. package/dist/components/core/CommandPalette/CommandPalette.theme.js +11 -13
  17. package/dist/components/core/CommandPalette/CommandPalette.types.d.ts +0 -2
  18. package/dist/components/core/ContextMenu/ContextMenu.theme.js +13 -14
  19. package/dist/components/core/DotSeparator/DotSeparator.d.ts +3 -0
  20. package/dist/components/core/DotSeparator/DotSeparator.js +19 -0
  21. package/dist/components/core/DotSeparator/DotSeparator.theme.d.ts +2 -0
  22. package/dist/components/core/DotSeparator/DotSeparator.theme.js +10 -0
  23. package/dist/components/core/DotSeparator/DotSeparator.types.d.ts +25 -0
  24. package/dist/components/core/DotSeparator/index.d.ts +3 -0
  25. package/dist/components/core/Drawer/Drawer.theme.js +2 -2
  26. package/dist/components/core/Dropdown/Dropdown.theme.js +4 -5
  27. package/dist/components/core/Dropdown/DropdownItem.js +1 -1
  28. package/dist/components/core/Dropdown/DropdownMenu.js +1 -1
  29. package/dist/components/core/Empty/Empty.js +1 -1
  30. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.d.ts +14 -0
  31. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.js +28 -0
  32. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.theme.d.ts +6 -0
  33. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.theme.js +8 -0
  34. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.types.d.ts +32 -0
  35. package/dist/components/core/ImagePlaceholder/index.d.ts +2 -0
  36. package/dist/components/core/MenuItem/MenuItem.js +5 -2
  37. package/dist/components/core/MenuItem/MenuItem.theme.d.ts +6 -0
  38. package/dist/components/core/MenuItem/MenuItem.theme.js +19 -16
  39. package/dist/components/core/MenuItem/MenuItem.types.d.ts +2 -0
  40. package/dist/components/core/Message/Message.theme.js +1 -1
  41. package/dist/components/core/Modal/Modal.theme.js +1 -1
  42. package/dist/components/core/NavItem/NavItem.d.ts +1 -1
  43. package/dist/components/core/NavItem/NavItem.js +187 -68
  44. package/dist/components/core/NavItem/NavItem.theme.js +31 -12
  45. package/dist/components/core/NavItem/NavItem.types.d.ts +17 -39
  46. package/dist/components/core/Pagination/Pagination.theme.js +3 -3
  47. package/dist/components/core/Popover/Popover.theme.js +1 -1
  48. package/dist/components/core/Popover/Popover.types.d.ts +6 -2
  49. package/dist/components/core/Popover/PopoverBody.js +10 -3
  50. package/dist/components/core/Progress/Progress.theme.js +1 -1
  51. package/dist/components/core/ScrollArea/ScrollArea.js +2 -2
  52. package/dist/components/core/ScrollArea/ScrollArea.theme.js +1 -1
  53. package/dist/components/core/Segmented/Segmented.js +8 -6
  54. package/dist/components/core/Segmented/Segmented.theme.js +3 -3
  55. package/dist/components/core/Sidebar/Sidebar.d.ts +3 -1
  56. package/dist/components/core/Sidebar/Sidebar.js +53 -16
  57. package/dist/components/core/Sidebar/Sidebar.theme.js +5 -7
  58. package/dist/components/core/Sidebar/Sidebar.types.d.ts +125 -8
  59. package/dist/components/core/Sidebar/SidebarCard.d.ts +25 -0
  60. package/dist/components/core/Sidebar/SidebarCard.js +68 -0
  61. package/dist/components/core/Sidebar/SidebarCard.theme.d.ts +8 -0
  62. package/dist/components/core/Sidebar/SidebarCard.theme.js +18 -0
  63. package/dist/components/core/Sidebar/SidebarContent.js +10 -6
  64. package/dist/components/core/Sidebar/SidebarContext.d.ts +17 -16
  65. package/dist/components/core/Sidebar/SidebarContext.js +43 -20
  66. package/dist/components/core/Sidebar/SidebarFooter.js +4 -6
  67. package/dist/components/core/Sidebar/SidebarHeader.js +12 -9
  68. package/dist/components/core/Sidebar/SidebarLogo.js +39 -8
  69. package/dist/components/core/Sidebar/SidebarNav.js +4 -3
  70. package/dist/components/core/Sidebar/SidebarNavGroup.js +4 -7
  71. package/dist/components/core/Sidebar/SidebarSearch.d.ts +22 -0
  72. package/dist/components/core/Sidebar/SidebarSearch.js +135 -0
  73. package/dist/components/core/Sidebar/SidebarToggle.js +6 -1
  74. package/dist/components/core/Sidebar/SidebarUserProfile.js +33 -17
  75. package/dist/components/core/Sidebar/index.d.ts +4 -3
  76. package/dist/components/core/Skeleton/Skeleton.theme.js +1 -1
  77. package/dist/components/core/Spinner/Spinner.theme.js +1 -1
  78. package/dist/components/core/Tabs/Tabs.js +6 -1
  79. package/dist/components/core/Tabs/Tabs.types.d.ts +2 -0
  80. package/dist/components/core/Tabs/TabsBody.js +5 -3
  81. package/dist/components/core/Tag/Tag.js +29 -5
  82. package/dist/components/core/Tag/Tag.theme.js +14 -9
  83. package/dist/components/core/Tag/Tag.types.d.ts +7 -0
  84. package/dist/components/core/Tooltip/Tooltip.js +1 -1
  85. package/dist/components/core/Tooltip/Tooltip.theme.js +1 -2
  86. package/dist/components/core/index.d.ts +2 -0
  87. package/dist/components/core/index.js +6 -2
  88. package/dist/components/data-display/GanttChart/GanttChart.d.ts +1 -1
  89. package/dist/components/data-display/GanttChart/GanttChart.js +31 -4
  90. package/dist/components/data-display/GanttChart/GanttChart.theme.js +19 -17
  91. package/dist/components/data-display/GanttChart/GanttChart.types.d.ts +19 -0
  92. package/dist/components/data-display/GanttChart/GanttChart.utils.js +1 -1
  93. package/dist/components/data-display/GanttChart/GanttChartPanel.d.ts +1 -1
  94. package/dist/components/data-display/GanttChart/GanttChartPanel.js +3 -3
  95. package/dist/components/data-display/GanttChart/GanttDependencyArrows.js +7 -4
  96. package/dist/components/data-display/GanttChart/GanttMilestone.js +5 -5
  97. package/dist/components/data-display/GanttChart/GanttTableGroupRow.js +1 -1
  98. package/dist/components/data-display/GanttChart/GanttTablePanel.js +1 -1
  99. package/dist/components/data-display/GanttChart/GanttTableTaskRow.js +2 -2
  100. package/dist/components/data-display/GanttChart/GanttTaskBar.js +37 -16
  101. package/dist/components/data-display/GanttChart/GanttTimelineGrid.js +2 -1
  102. package/dist/components/data-display/GanttChart/GanttTimelineHeader.js +35 -11
  103. package/dist/components/data-display/GanttChart/GanttToolbar.js +2 -2
  104. package/dist/components/data-display/GanttChart/hooks/useGanttDrag.js +3 -2
  105. package/dist/components/data-display/GanttChart/index.d.ts +1 -1
  106. package/dist/components/data-display/KPI/KPI.js +13 -3
  107. package/dist/components/data-display/KPI/KPI.theme.js +3 -3
  108. package/dist/components/data-display/Metric/Metric.theme.js +3 -3
  109. package/dist/components/data-display/Table/Table.js +4 -3
  110. package/dist/components/data-display/Table/Table.theme.js +14 -6
  111. package/dist/components/data-display/Table/Table.types.d.ts +15 -0
  112. package/dist/components/data-display/Table/TableColumnManager.js +1 -1
  113. package/dist/components/data-display/Table/index.d.ts +2 -2
  114. package/dist/components/effects/Aurora/Aurora.d.ts +6 -17
  115. package/dist/components/effects/Aurora/Aurora.js +310 -105
  116. package/dist/components/effects/Aurora/Aurora.types.d.ts +6 -0
  117. package/dist/components/effects/InteractiveCharacters/InteractiveCharacters.js +3 -3
  118. package/dist/components/effects/InteractiveCharacters/characters/BlobCharacter.js +13 -4
  119. package/dist/components/effects/InteractiveCharacters/characters/SmallCharacter.js +13 -4
  120. package/dist/components/effects/InteractiveCharacters/characters/TallCharacter.js +14 -4
  121. package/dist/components/effects/MorphingText/MorphingText.js +40 -7
  122. package/dist/components/effects/StripePattern/StripePattern.d.ts +18 -0
  123. package/dist/components/effects/StripePattern/StripePattern.js +54 -0
  124. package/dist/components/effects/StripePattern/StripePattern.types.d.ts +53 -0
  125. package/dist/components/effects/StripePattern/index.d.ts +2 -0
  126. package/dist/components/effects/index.d.ts +2 -0
  127. package/dist/components/effects/index.js +1 -0
  128. package/dist/components/forms/Checkbox/Checkbox.theme.js +1 -1
  129. package/dist/components/forms/Combobox/Combobox.js +1 -1
  130. package/dist/components/forms/Combobox/Combobox.theme.js +2 -4
  131. package/dist/components/forms/CronInput/CronInput.js +1 -1
  132. package/dist/components/forms/CronInput/CronInput.theme.js +22 -31
  133. package/dist/components/forms/DatePicker/DatePicker.theme.js +11 -18
  134. package/dist/components/forms/DatePicker/DatePickerTrigger.js +1 -1
  135. package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +7 -14
  136. package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +1 -1
  137. package/dist/components/forms/FileUpload/FileUpload.animations.d.ts +29 -0
  138. package/dist/components/forms/FileUpload/FileUpload.animations.js +19 -0
  139. package/dist/components/forms/FileUpload/FileUpload.js +88 -49
  140. package/dist/components/forms/FileUpload/FileUpload.theme.d.ts +2 -10
  141. package/dist/components/forms/FileUpload/FileUpload.theme.js +16 -30
  142. package/dist/components/forms/FileUpload/FileUpload.types.d.ts +26 -10
  143. package/dist/components/forms/FileUpload/FileUploadProgress.js +27 -20
  144. package/dist/components/forms/FileUpload/FileUploadProgress.theme.js +2 -2
  145. package/dist/components/forms/Input/Input.theme.js +16 -15
  146. package/dist/components/forms/InputCounter/InputCounter.theme.js +1 -1
  147. package/dist/components/forms/Mention/Mention.js +152 -57
  148. package/dist/components/forms/Mention/Mention.theme.js +16 -23
  149. package/dist/components/forms/Mention/Mention.types.d.ts +12 -12
  150. package/dist/components/forms/Mention/Mention.utils.js +32 -4
  151. package/dist/components/forms/Radio/Radio.theme.js +1 -1
  152. package/dist/components/forms/RichTextEditor/RichTextEditor.d.ts +18 -0
  153. package/dist/components/forms/RichTextEditor/RichTextEditor.js +1231 -236
  154. package/dist/components/forms/RichTextEditor/RichTextEditor.theme.d.ts +53 -1
  155. package/dist/components/forms/RichTextEditor/RichTextEditor.theme.js +99 -25
  156. package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +42 -3
  157. package/dist/components/forms/RichTextEditor/index.d.ts +2 -2
  158. package/dist/components/forms/Select/Select.js +2 -2
  159. package/dist/components/forms/Select/Select.theme.js +3 -3
  160. package/dist/components/forms/Slider/Slider.theme.js +1 -1
  161. package/dist/components/forms/Switch/Switch.theme.js +1 -1
  162. package/dist/components/forms/TimePicker/TimePicker.theme.js +2 -2
  163. package/dist/components/forms/TimePicker/TimePickerContent.js +4 -0
  164. package/dist/components/forms/TimePicker/TimePickerTrigger.js +1 -1
  165. package/dist/components/forms/forms.theme.js +24 -19
  166. package/dist/components/forms/index.js +4 -0
  167. package/dist/components/layout/AppShell/AppShell.d.ts +10 -0
  168. package/dist/components/layout/AppShell/AppShell.js +84 -0
  169. package/dist/components/layout/AppShell/AppShell.theme.d.ts +3 -0
  170. package/dist/components/layout/AppShell/AppShell.theme.js +42 -0
  171. package/dist/components/layout/AppShell/AppShell.types.d.ts +127 -0
  172. package/dist/components/layout/AppShell/AppShellContext.d.ts +14 -0
  173. package/dist/components/layout/AppShell/AppShellContext.js +19 -0
  174. package/dist/components/layout/AppShell/AppShellFooter.d.ts +3 -0
  175. package/dist/components/layout/AppShell/AppShellFooter.js +12 -0
  176. package/dist/components/layout/AppShell/AppShellHeader.d.ts +3 -0
  177. package/dist/components/layout/AppShell/AppShellHeader.js +12 -0
  178. package/dist/components/layout/AppShell/AppShellMain.d.ts +3 -0
  179. package/dist/components/layout/AppShell/AppShellMain.js +19 -0
  180. package/dist/components/layout/AppShell/AppShellRightPanel.d.ts +3 -0
  181. package/dist/components/layout/AppShell/AppShellRightPanel.js +16 -0
  182. package/dist/components/layout/AppShell/AppShellSidebar.d.ts +3 -0
  183. package/dist/components/layout/AppShell/AppShellSidebar.js +16 -0
  184. package/dist/components/layout/AppShell/AppShellTopbar.d.ts +3 -0
  185. package/dist/components/layout/AppShell/AppShellTopbar.js +12 -0
  186. package/dist/components/layout/AppShell/index.d.ts +3 -0
  187. package/dist/components/layout/FormLayout/FormLayout.js +9 -4
  188. package/dist/components/layout/FormLayout/FormLayout.types.d.ts +3 -3
  189. package/dist/components/layout/FormLayout/FormLayoutSection.js +6 -1
  190. package/dist/components/layout/PageLayout/PageLayout.d.ts +2 -2
  191. package/dist/components/layout/PageLayout/PageLayout.js +23 -5
  192. package/dist/components/layout/PageLayout/PageLayout.theme.d.ts +2 -1
  193. package/dist/components/layout/PageLayout/PageLayout.theme.js +35 -6
  194. package/dist/components/layout/PageLayout/PageLayout.types.d.ts +45 -0
  195. package/dist/components/layout/PageLayout/PageLayoutContent.js +6 -4
  196. package/dist/components/layout/PageLayout/PageLayoutHeader.js +3 -3
  197. package/dist/components/layout/PageLayout/PageLayoutSidebar.js +5 -3
  198. package/dist/components/layout/PageLayout/index.d.ts +1 -1
  199. package/dist/components/layout/index.d.ts +1 -0
  200. package/dist/components/layout/index.js +2 -0
  201. package/dist/hooks/useSelectPortal.d.ts +10 -2
  202. package/dist/hooks/useSelectPortal.js +45 -24
  203. package/dist/index.js +8 -2
  204. package/dist/registry.json +10021 -0
  205. package/dist/styles.css +1 -1
  206. package/dist/utils/dateUtils.js +11 -1
  207. package/package.json +4 -2
  208. package/src/global.scss +29 -8
  209. package/src/styles/theme.css +174 -100
  210. package/dist/components/core/Sidebar/SidebarSubmenu.d.ts +0 -7
  211. package/dist/components/core/Sidebar/SidebarSubmenu.js +0 -12
@@ -1,4 +1,4 @@
1
- import { jsx, jsxs } from 'react/jsx-runtime';
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { useContext, createContext, useState, useCallback, useMemo } from 'react';
3
3
  import { cn } from '../../../utils/cn.js';
4
4
  import { ganttChartTheme } from './GanttChart.theme.js';
@@ -60,10 +60,30 @@ const defaultColumns = [
60
60
  align: "center",
61
61
  },
62
62
  ];
63
+ const defaultToolbarOptions = {
64
+ today: true,
65
+ zoom: true,
66
+ hideCompleted: true,
67
+ addGroup: true,
68
+ };
69
+ function resolveToolbarOptions(toolbar) {
70
+ if (toolbar === false) {
71
+ return {
72
+ today: false,
73
+ zoom: false,
74
+ hideCompleted: false,
75
+ addGroup: false,
76
+ };
77
+ }
78
+ if (toolbar === true || toolbar === undefined) {
79
+ return defaultToolbarOptions;
80
+ }
81
+ return { ...defaultToolbarOptions, ...toolbar };
82
+ }
63
83
  // ---------------------------------------------------------------------------
64
84
  // Component
65
85
  // ---------------------------------------------------------------------------
66
- function GanttChartComponent({ data, groups, columns, startDate, endDate, todayMarker = true, scrollToToday = true, readonly: readonlyProp = false, readonlyDates: readonlyDatesProp = false, readonlyProgress: readonlyProgressProp = false, zoom: zoomProp, defaultZoom = "day", onZoomChange, collapsedGroups: collapsedProp, defaultCollapsedGroups = [], onCollapsedGroupsChange, hideCompleted: hideCompletedProp, defaultHideCompleted = false, onHideCompletedChange, tablePanelWidth: tablePanelWidthProp, defaultTablePanelWidth = 400, onTablePanelWidthChange, onTaskUpdate, onTaskDelete, onTaskDuplicate, onTaskAdd, onGroupAdd, onTaskClick, holidays: holidaysProp = [], gridLines = "both", className, theme: themeOverrides, rowHeight = 40, }) {
86
+ function GanttChartComponent({ data, groups, columns, startDate, endDate, todayMarker = true, scrollToToday = true, toolbar, showTable = true, showDependencies = true, showTaskLabels = "auto", emptyState, readonly: readonlyProp = false, readonlyDates: readonlyDatesProp = false, readonlyProgress: readonlyProgressProp = false, zoom: zoomProp, defaultZoom = "day", onZoomChange, collapsedGroups: collapsedProp, defaultCollapsedGroups = [], onCollapsedGroupsChange, hideCompleted: hideCompletedProp, defaultHideCompleted = false, onHideCompletedChange, tablePanelWidth: tablePanelWidthProp, defaultTablePanelWidth = 400, onTablePanelWidthChange, onTaskUpdate, onTaskDelete, onTaskDuplicate, onTaskAdd, onGroupAdd, onTaskClick, holidays: holidaysProp = [], gridLines = "both", className, theme: themeOverrides, rowHeight = 40, }) {
67
87
  // ----- Controlled / uncontrolled state -----
68
88
  const [uncontrolledZoom, setUncontrolledZoom] = useState(defaultZoom);
69
89
  const isZoomControlled = zoomProp !== undefined;
@@ -111,6 +131,7 @@ function GanttChartComponent({ data, groups, columns, startDate, endDate, todayM
111
131
  const rows = useGanttLayout(groups, data, collapsedGroups, hideCompleted);
112
132
  const { tableRef, chartRef, handleTableScroll, handleChartScroll } = useGanttScrollSync();
113
133
  const theme = useMemo(() => ({ ...ganttChartTheme, ...(themeOverrides || {}) }), [themeOverrides]);
134
+ const toolbarOptions = useMemo(() => resolveToolbarOptions(toolbar), [toolbar]);
114
135
  // ----- Context -----
115
136
  const contextValue = useMemo(() => ({
116
137
  groups,
@@ -125,6 +146,11 @@ function GanttChartComponent({ data, groups, columns, startDate, endDate, todayM
125
146
  collapsedGroups,
126
147
  hideCompleted,
127
148
  tablePanelWidth,
149
+ toolbar: toolbarOptions,
150
+ showTable,
151
+ showDependencies,
152
+ showTaskLabels,
153
+ emptyState,
128
154
  readonly: readonlyProp,
129
155
  readonlyDates: readonlyDatesProp,
130
156
  readonlyProgress: readonlyProgressProp,
@@ -147,7 +173,8 @@ function GanttChartComponent({ data, groups, columns, startDate, endDate, todayM
147
173
  rowHeight,
148
174
  }), [
149
175
  groups, data, resolvedColumns, rows, dates, zoom, collapsedGroups,
150
- hideCompleted, tablePanelWidth, readonlyProp, readonlyDatesProp,
176
+ hideCompleted, tablePanelWidth, toolbarOptions, showTable,
177
+ showDependencies, showTaskLabels, emptyState, readonlyProp, readonlyDatesProp,
151
178
  readonlyProgressProp, onTaskUpdate, onTaskDelete, onTaskDuplicate,
152
179
  onTaskAdd, onGroupAdd, onTaskClick, toggleGroup, setZoom,
153
180
  setHideCompleted, setTablePanelWidth,
@@ -163,7 +190,7 @@ function GanttChartComponent({ data, groups, columns, startDate, endDate, todayM
163
190
  activeDrag,
164
191
  setActiveDrag,
165
192
  }), [hoveredTaskId, focusedTaskId, activeDrag]);
166
- return (jsx(GanttContext.Provider, { value: contextValue, children: jsx(GanttInteractionContext.Provider, { value: interactionValue, children: jsxs("div", { className: cn(theme.baseStyle, className), children: [jsx(GanttToolbar, {}), jsxs("div", { className: "flex flex-1 min-h-0 overflow-x-auto", children: [jsx(GanttTablePanel, { ref: tableRef, onScroll: handleTableScroll }), jsx(GanttSplitter, {}), jsx(GanttChartPanel, { ref: chartRef, onScroll: handleChartScroll, days: dates.days, monthSpans: dates.monthSpans })] })] }) }) }));
193
+ return (jsx(GanttContext.Provider, { value: contextValue, children: jsx(GanttInteractionContext.Provider, { value: interactionValue, children: jsxs("div", { className: cn(theme.baseStyle, className), children: [Object.values(toolbarOptions).some(Boolean) && jsx(GanttToolbar, {}), jsxs("div", { className: "flex flex-1 min-h-0 overflow-x-auto", children: [showTable && (jsxs(Fragment, { children: [jsx(GanttTablePanel, { ref: tableRef, onScroll: handleTableScroll }), jsx(GanttSplitter, {})] })), jsx(GanttChartPanel, { ref: chartRef, onScroll: showTable ? handleChartScroll : undefined, days: dates.days, monthSpans: dates.monthSpans })] })] }) }) }));
167
194
  }
168
195
  GanttChartComponent.displayName = "GanttChart";
169
196
 
@@ -1,23 +1,25 @@
1
1
  const ganttChartTheme = {
2
- baseStyle: "flex flex-col w-full overflow-hidden text-sm",
3
- toolbarStyle: "flex flex-wrap items-center justify-between gap-3 px-3 py-2 border-b border-[var(--color-border)] bg-[var(--color-background-secondary)]",
4
- tablePanelStyle: "hidden sm:flex flex-col shrink-0 overflow-hidden",
2
+ baseStyle: "flex flex-col w-full overflow-hidden rounded-lg border border-[var(--color-border)] bg-[var(--color-background)] text-sm shadow-sm",
3
+ toolbarStyle: "flex flex-wrap items-center justify-between gap-2 px-3 py-2 border-b border-[var(--color-border)] bg-[var(--color-background-secondary)]/80",
4
+ tablePanelStyle: "flex flex-col shrink-0 overflow-hidden border-r border-[var(--color-border)] bg-[var(--color-background)]",
5
5
  chartPanelStyle: "flex flex-col flex-1 min-w-0 overflow-hidden",
6
- tableHeaderStyle: "flex items-center h-[56px] border-b border-[var(--color-border)] bg-[var(--color-background-secondary)]",
7
- tableHeaderCellStyle: "px-3 font-medium text-[var(--color-text-secondary)] truncate",
8
- tableGroupRowStyle: "flex items-center cursor-pointer select-none hover:bg-[var(--color-background-secondary)] transition-colors",
9
- tableTaskRowStyle: "flex items-center hover:bg-[var(--color-background-secondary)] transition-colors",
6
+ tableHeaderStyle: "flex items-center h-[56px] border-b border-[var(--color-border)] bg-[var(--color-background-secondary)]/80",
7
+ tableHeaderCellStyle: "px-3 text-[11px] font-semibold uppercase tracking-wide text-[var(--color-text-muted)] truncate",
8
+ tableGroupRowStyle: "flex items-center cursor-pointer select-none border-b border-[var(--color-border)]/30 bg-[var(--color-background-secondary)]/35 hover:bg-[var(--color-background-secondary)] transition-colors",
9
+ tableTaskRowStyle: "flex items-center border-b border-[var(--color-border)]/20 hover:bg-[var(--color-background-secondary)]/70 transition-colors",
10
10
  tableCellStyle: "px-3 truncate text-[var(--color-text-primary)]",
11
- timelineHeaderStyle: "bg-[var(--color-background)] border-b border-[var(--color-border)]",
12
- timelineMonthStyle: "text-xs font-medium text-[var(--color-text-secondary)] px-2 border-b border-[var(--color-border)] flex items-center",
13
- timelineDayStyle: "text-[10px] text-[var(--color-text-muted)] flex items-center justify-center",
14
- gridLineStyle: "border-r border-[var(--color-border)]/30",
15
- weekendStyle: "bg-[var(--color-background-secondary)]/50",
16
- todayMarkerStyle: "bg-[var(--color-primary)] w-px",
17
- todayColumnStyle: "bg-[var(--color-primary)]/5",
18
- taskBarStyle: "absolute rounded-md cursor-pointer flex items-center",
19
- groupBarStyle: "absolute rounded-md opacity-25",
20
- splitterStyle: "hidden sm:block w-1 cursor-col-resize bg-[var(--color-border)] hover:bg-[var(--color-primary)] transition-colors shrink-0",
11
+ timelineHeaderStyle: "bg-[var(--color-background)] border-b border-[var(--color-border)] shadow-[0_1px_0_var(--color-border)]",
12
+ timelineMonthStyle: "text-[11px] font-semibold uppercase tracking-wide text-[var(--color-text-secondary)] px-2 border-b border-[var(--color-border)] flex items-center bg-[var(--color-background-secondary)]/45",
13
+ timelineDayStyle: "text-[10px] text-[var(--color-text-muted)] flex items-center justify-center border-r border-[var(--color-border)]/20",
14
+ gridLineStyle: "border-r border-[var(--color-border)]/20",
15
+ weekendStyle: "bg-[var(--color-background-secondary)]/35",
16
+ todayMarkerStyle: "bg-[var(--color-primary)] w-0.5",
17
+ todayColumnStyle: "bg-[var(--color-primary)]/10",
18
+ taskBarStyle: "absolute cursor-pointer flex items-center overflow-hidden shadow-sm ring-1 ring-black/10",
19
+ taskLabelOutsideStyle: "absolute text-xs font-medium whitespace-nowrap pointer-events-none drop-shadow-sm",
20
+ emptyStateStyle: "flex flex-col items-center gap-2 px-6 py-8 text-center text-[var(--color-text-muted)]",
21
+ groupBarStyle: "absolute rounded-full opacity-40",
22
+ splitterStyle: "w-1 cursor-col-resize bg-[var(--color-border)]/70 hover:bg-[var(--color-primary)] transition-colors shrink-0",
21
23
  };
22
24
 
23
25
  export { ganttChartTheme };
@@ -41,6 +41,13 @@ export interface GanttDragState {
41
41
  deltaX: number;
42
42
  }
43
43
  export type GanttZoom = "day" | "week" | "quarter" | "month" | "year";
44
+ export type GanttTaskLabelMode = "auto" | "inside" | "outside" | "none";
45
+ export interface GanttToolbarOptions {
46
+ today?: boolean;
47
+ zoom?: boolean;
48
+ hideCompleted?: boolean;
49
+ addGroup?: boolean;
50
+ }
44
51
  export declare const DAY_WIDTHS: Record<GanttZoom, number>;
45
52
  export type GanttRowType = "group" | "task";
46
53
  export interface GanttGroupRow {
@@ -75,6 +82,8 @@ export interface GanttChartThemeOverrides {
75
82
  todayMarkerStyle?: string;
76
83
  todayColumnStyle?: string;
77
84
  taskBarStyle?: string;
85
+ taskLabelOutsideStyle?: string;
86
+ emptyStateStyle?: string;
78
87
  groupBarStyle?: string;
79
88
  splitterStyle?: string;
80
89
  }
@@ -91,6 +100,11 @@ export interface GanttContextValue {
91
100
  collapsedGroups: string[];
92
101
  hideCompleted: boolean;
93
102
  tablePanelWidth: number;
103
+ toolbar: GanttToolbarOptions;
104
+ showTable: boolean;
105
+ showDependencies: boolean;
106
+ showTaskLabels: GanttTaskLabelMode;
107
+ emptyState?: ReactNode;
94
108
  readonly: boolean;
95
109
  readonlyDates: boolean;
96
110
  readonlyProgress: boolean;
@@ -133,6 +147,11 @@ export interface GanttChartProps {
133
147
  endDate?: Date | string;
134
148
  todayMarker?: boolean;
135
149
  scrollToToday?: boolean;
150
+ toolbar?: boolean | GanttToolbarOptions;
151
+ showTable?: boolean;
152
+ showDependencies?: boolean;
153
+ showTaskLabels?: GanttTaskLabelMode;
154
+ emptyState?: ReactNode;
136
155
  readonly?: boolean;
137
156
  readonlyDates?: boolean;
138
157
  readonlyProgress?: boolean;
@@ -1,5 +1,5 @@
1
1
  import { toDate, getDaysBetween } from '../../../utils/dateUtils.js';
2
- export { addDays, isSameDay, isWeekend } from '../../../utils/dateUtils.js';
2
+ export { addDays, getWeekNumber, isSameDay, isWeekend } from '../../../utils/dateUtils.js';
3
3
 
4
4
  // Re-export generic date utilities from shared utils so existing consumers
5
5
  // that import from "GanttChart.utils" continue to work.
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import type { MonthSpan } from "./GanttChart.utils";
3
3
  interface GanttChartPanelProps {
4
- onScroll: () => void;
4
+ onScroll?: () => void;
5
5
  days: Date[];
6
6
  monthSpans: MonthSpan[];
7
7
  }
@@ -11,7 +11,7 @@ import { GanttGroupBar } from './GanttGroupBar.js';
11
11
  import { GanttMilestone } from './GanttMilestone.js';
12
12
 
13
13
  const GanttChartPanel = forwardRef(({ onScroll, days, monthSpans }, ref) => {
14
- const { theme, rows, tasks, dayWidth, rowHeight, todayOffset, scrollToToday, onTaskClick, } = useGanttContext();
14
+ const { theme, rows, tasks, dayWidth, rowHeight, todayOffset, scrollToToday, onTaskClick, showDependencies, emptyState, } = useGanttContext();
15
15
  const { focusedTaskId, setFocusedTaskId } = useGanttInteraction();
16
16
  const totalWidth = days.length * dayWidth;
17
17
  const bodyHeight = rows.length * rowHeight;
@@ -64,9 +64,9 @@ const GanttChartPanel = forwardRef(({ onScroll, days, monthSpans }, ref) => {
64
64
  }
65
65
  }, [rows, focusedTaskId, setFocusedTaskId, onTaskClick]);
66
66
  if (tasks.length === 0) {
67
- return (jsx("div", { className: cn(theme.chartPanelStyle, "items-center justify-center"), children: jsxs("div", { className: "flex flex-col items-center gap-2 text-[var(--color-text-muted)]", children: [jsx(CalendarDaysIcon, { className: "size-10 opacity-40" }), jsx("div", { className: "text-base font-medium", children: "No tasks to display" }), jsx("div", { className: "text-sm opacity-70", children: "Add tasks to see them on the timeline" })] }) }));
67
+ return (jsx("div", { className: cn(theme.chartPanelStyle, "items-center justify-center"), children: emptyState !== null && emptyState !== void 0 ? emptyState : (jsxs("div", { className: cn(theme.emptyStateStyle), children: [jsx(CalendarDaysIcon, { className: "size-10 opacity-40" }), jsx("div", { className: "text-base font-medium text-[var(--color-text-secondary)]", children: "No tasks to display" }), jsx("div", { className: "text-sm opacity-70", children: "Add tasks to see them on the timeline" })] })) }));
68
68
  }
69
- return (jsx("div", { className: cn(theme.chartPanelStyle), children: jsx("div", { ref: ref, className: "overflow-auto flex-1 min-h-0 focus:outline-none", onScroll: onScroll, onKeyDown: handleKeyDown, tabIndex: 0, role: "grid", "aria-label": "Gantt chart timeline", "data-gantt-chart-scroll": true, children: jsxs("div", { style: { width: totalWidth }, children: [jsx("div", { className: "sticky top-0 z-10", children: jsx(GanttTimelineHeader, { days: days, monthSpans: monthSpans }) }), jsxs("div", { className: "relative", style: { height: bodyHeight }, children: [jsx(GanttTimelineGrid, { days: days, height: bodyHeight }), jsx(GanttDependencyArrows, {}), rows.map((row, i) => {
69
+ return (jsx("div", { className: cn(theme.chartPanelStyle), children: jsx("div", { ref: ref, className: "overflow-auto flex-1 min-h-0 focus:outline-none", onScroll: onScroll, onKeyDown: handleKeyDown, tabIndex: 0, role: "grid", "aria-label": "Gantt chart timeline", "data-gantt-chart-scroll": true, children: jsxs("div", { style: { width: totalWidth }, children: [jsx("div", { className: "sticky top-0 z-10", children: jsx(GanttTimelineHeader, { days: days, monthSpans: monthSpans }) }), jsxs("div", { className: "relative", style: { height: bodyHeight }, children: [jsx(GanttTimelineGrid, { days: days, height: bodyHeight }), showDependencies && jsx(GanttDependencyArrows, {}), rows.map((row, i) => {
70
70
  const top = i * rowHeight;
71
71
  if (row.type === "group") {
72
72
  return (jsx(GanttGroupBar, { row: row, top: top }, `group-${row.group.id}`));
@@ -1,11 +1,14 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
- import { useMemo } from 'react';
2
+ import { useId, useMemo } from 'react';
3
3
  import { useGanttContext, useGanttInteraction } from './GanttChart.js';
4
4
  import { buildDependencyArrows } from './GanttChart.utils.js';
5
5
 
6
6
  const GanttDependencyArrows = () => {
7
7
  const { tasks, rows, dayWidth, rowHeight, rangeStart } = useGanttContext();
8
8
  const { hoveredTaskId, activeDrag } = useGanttInteraction();
9
+ const markerId = useId();
10
+ const arrowheadId = `${markerId}-gantt-arrowhead`;
11
+ const highlightedArrowheadId = `${markerId}-gantt-arrowhead-highlight`;
9
12
  const arrows = useMemo(() => buildDependencyArrows(tasks, rows, dayWidth, rowHeight, rangeStart, activeDrag), [tasks, rows, dayWidth, rowHeight, rangeStart, activeDrag]);
10
13
  if (arrows.length === 0)
11
14
  return null;
@@ -17,14 +20,14 @@ const GanttDependencyArrows = () => {
17
20
  const [sourceId, targetId] = arrowId.split("->");
18
21
  return sourceId === hoveredTaskId || targetId === hoveredTaskId;
19
22
  };
20
- return (jsxs("svg", { className: "absolute inset-0 pointer-events-none", style: { overflow: "visible" }, children: [jsxs("defs", { children: [jsx("marker", { id: "gantt-arrowhead", markerWidth: "8", markerHeight: "6", refX: "8", refY: "3", orient: "auto", children: jsx("path", { d: "M 0 0 L 8 3 L 0 6 Z", fill: "var(--color-text-secondary, #888)" }) }), jsx("marker", { id: "gantt-arrowhead-highlight", markerWidth: "8", markerHeight: "6", refX: "8", refY: "3", orient: "auto", children: jsx("path", { d: "M 0 0 L 8 3 L 0 6 Z", fill: "var(--color-primary, #3B82F6)" }) })] }), arrows.map((arrow) => {
23
+ return (jsxs("svg", { className: "absolute inset-0 pointer-events-none", style: { overflow: "visible" }, children: [jsxs("defs", { children: [jsx("marker", { id: arrowheadId, markerWidth: "8", markerHeight: "6", refX: "8", refY: "3", orient: "auto", children: jsx("path", { d: "M 0 0 L 8 3 L 0 6 Z", fill: "var(--color-text-secondary, #888)" }) }), jsx("marker", { id: highlightedArrowheadId, markerWidth: "8", markerHeight: "6", refX: "8", refY: "3", orient: "auto", children: jsx("path", { d: "M 0 0 L 8 3 L 0 6 Z", fill: "var(--color-primary, #3B82F6)" }) })] }), arrows.map((arrow) => {
21
24
  const connected = isArrowConnected(arrow.id);
22
25
  const faded = hoveredTaskId !== null && !connected;
23
26
  return (jsx("path", { d: arrow.d, fill: "none", stroke: connected
24
27
  ? "var(--color-primary, #3B82F6)"
25
28
  : "var(--color-text-secondary, #888)", strokeWidth: connected ? 2.5 : 1.5, strokeDasharray: arrow.isBackward ? "4 3" : undefined, markerEnd: connected
26
- ? "url(#gantt-arrowhead-highlight)"
27
- : "url(#gantt-arrowhead)", opacity: faded ? 0.15 : connected ? 1 : 0.6, style: {
29
+ ? `url(#${highlightedArrowheadId})`
30
+ : `url(#${arrowheadId})`, opacity: faded ? 0.15 : connected ? 1 : 0.6, style: {
28
31
  transition: activeDrag
29
32
  ? "none"
30
33
  : "opacity 0.15s, stroke-width 0.15s",
@@ -8,7 +8,7 @@ import { GanttTaskContextMenu } from './GanttTaskContextMenu.js';
8
8
 
9
9
  const GanttMilestone = React__default.memo(({ task, group, top, }) => {
10
10
  var _a;
11
- const { dayWidth, rangeStart, rowHeight, onTaskClick, readonly } = useGanttContext();
11
+ const { dayWidth, rangeStart, rowHeight, onTaskClick, readonly, showTaskLabels, theme, } = useGanttContext();
12
12
  const offset = getDayOffset(task.endDate, rangeStart);
13
13
  const left = offset * dayWidth + dayWidth / 2;
14
14
  const size = 14;
@@ -26,19 +26,19 @@ const GanttMilestone = React__default.memo(({ task, group, top, }) => {
26
26
  return (jsx(GanttTaskContextMenu, { task: task, children: jsx(Tooltip, { content: tooltipContent, placement: "top", children: jsxs("div", { className: "absolute", style: {
27
27
  left: left - size / 2,
28
28
  top: top + rowHeight / 2 - size / 2,
29
- }, onClick: onTaskClick ? handleClick : undefined, onKeyDown: handleKeyDown, role: "button", tabIndex: onTaskClick ? 0 : undefined, "aria-label": `Milestone: ${task.title}`, children: [jsx("div", { className: `hover:scale-110 transition-transform ${readonly ? "cursor-default" : "cursor-pointer"}`, style: {
29
+ }, onClick: onTaskClick ? handleClick : undefined, onKeyDown: handleKeyDown, role: onTaskClick ? "button" : "img", tabIndex: onTaskClick ? 0 : undefined, "aria-label": `Milestone: ${task.title}`, children: [jsx("div", { className: `hover:scale-110 transition-transform ${readonly ? "cursor-default" : "cursor-pointer"}`, style: {
30
30
  width: size,
31
31
  height: size,
32
32
  backgroundColor: barColor,
33
33
  border: "2px solid var(--color-background, white)",
34
34
  transform: "rotate(45deg)",
35
35
  boxShadow: "0 1px 3px rgba(0,0,0,0.2)",
36
- } }), jsx("span", { className: "absolute text-sm font-medium whitespace-nowrap pointer-events-none", style: {
37
- left: size + 4,
36
+ } }), showTaskLabels !== "none" && showTaskLabels !== "inside" && (jsx("span", { className: theme.taskLabelOutsideStyle, style: {
37
+ left: size + 6,
38
38
  top: size / 2,
39
39
  transform: "translateY(-50%)",
40
40
  color: barColor,
41
- }, children: task.title })] }) }) }));
41
+ }, children: task.title }))] }) }) }));
42
42
  });
43
43
 
44
44
  export { GanttMilestone };
@@ -14,7 +14,7 @@ const GanttTableGroupRow = React__default.memo(({ row, }) => {
14
14
  e.preventDefault();
15
15
  toggleGroup(row.group.id);
16
16
  }
17
- }, role: "button", tabIndex: 0, "aria-expanded": !isCollapsed, "aria-label": `${row.group.title} — ${row.completedCount} of ${row.taskCount} completed`, children: [jsxs("div", { className: "flex items-center gap-2 px-3 flex-1 min-w-0", children: [jsx(motion.div, { variants: chevronVariants, animate: isCollapsed ? "collapsed" : "open", children: jsx(ChevronRightIcon, { className: "size-4 text-[var(--color-text-muted)]" }) }), jsx("div", { className: "size-2.5 rounded-full shrink-0", style: { backgroundColor: row.group.color } }), jsx("span", { className: "font-medium text-[var(--color-text-primary)] truncate", children: row.group.title }), jsxs("span", { className: "text-sm text-[var(--color-text-muted)] shrink-0", children: [row.completedCount, "/", row.taskCount] })] }), onTaskAdd && !readonly && (jsx("button", { className: "p-1 mr-2 rounded hover:bg-[var(--color-background-tertiary)] text-[var(--color-text-muted)] opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacity", "aria-label": `Add task to ${row.group.title}`, onClick: (e) => {
17
+ }, role: "button", tabIndex: 0, "aria-expanded": !isCollapsed, "aria-label": `${row.group.title} — ${row.completedCount} of ${row.taskCount} completed`, children: [jsxs("div", { className: "flex items-center gap-2 px-3 flex-1 min-w-0", children: [jsx(motion.div, { variants: chevronVariants, animate: isCollapsed ? "collapsed" : "open", children: jsx(ChevronRightIcon, { className: "size-4 text-[var(--color-text-muted)]" }) }), jsx("div", { className: "size-2.5 rounded-full shrink-0", style: { backgroundColor: row.group.color } }), jsx("span", { className: "font-medium text-[var(--color-text-primary)] truncate", children: row.group.title }), jsxs("span", { className: "text-sm text-[var(--color-text-muted)] shrink-0", children: [row.completedCount, "/", row.taskCount] })] }), onTaskAdd && !readonly && (jsx("button", { className: "p-1 mr-2 rounded hover:bg-[var(--color-surface-hover)] text-[var(--color-text-muted)] opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacity", "aria-label": `Add task to ${row.group.title}`, onClick: (e) => {
18
18
  e.stopPropagation();
19
19
  onTaskAdd(row.group.id);
20
20
  }, children: jsx(PlusIcon, { className: "size-4" }) }))] }));
@@ -8,7 +8,7 @@ import { GanttTableTaskRow } from './GanttTableTaskRow.js';
8
8
 
9
9
  const GanttTablePanel = forwardRef(({ onScroll }, ref) => {
10
10
  const { theme, rows, tablePanelWidth } = useGanttContext();
11
- return (jsxs("div", { className: cn(theme.tablePanelStyle), style: { width: tablePanelWidth }, children: [jsx(GanttTableHeader, {}), jsx("div", { ref: ref, className: "overflow-y-auto overflow-x-hidden flex-1 min-h-0", onScroll: onScroll, children: rows.map((row, i) => {
11
+ return (jsxs("div", { className: cn(theme.tablePanelStyle), style: { width: tablePanelWidth, minWidth: Math.min(tablePanelWidth, 280) }, children: [jsx(GanttTableHeader, {}), jsx("div", { ref: ref, className: "overflow-y-auto overflow-x-hidden flex-1 min-h-0", onScroll: onScroll, children: rows.map((row, i) => {
12
12
  if (row.type === "group") {
13
13
  return (jsx(GanttTableGroupRow, { row: row }, `group-${row.group.id}`));
14
14
  }
@@ -15,8 +15,8 @@ function getCellValue(task, group, column) {
15
15
  }
16
16
  const GanttTableTaskRow = React__default.memo(({ task, group, }) => {
17
17
  const { theme, columns, rowHeight, onTaskClick } = useGanttContext();
18
- const { focusedTaskId } = useGanttInteraction();
19
- return (jsx(GanttTaskContextMenu, { task: task, children: jsx("div", { className: cn(theme.tableTaskRowStyle, "group", onTaskClick && "cursor-pointer", focusedTaskId === task.id && "bg-[var(--color-primary)]/5"), style: { height: rowHeight }, onClick: onTaskClick ? (e) => onTaskClick(task, e) : undefined, onKeyDown: onTaskClick ? (e) => {
18
+ const { focusedTaskId, setFocusedTaskId, setHoveredTaskId } = useGanttInteraction();
19
+ return (jsx(GanttTaskContextMenu, { task: task, children: jsx("div", { className: cn(theme.tableTaskRowStyle, "group", onTaskClick && "cursor-pointer", focusedTaskId === task.id && "bg-[var(--color-primary)]/5"), style: { height: rowHeight }, onMouseEnter: () => setHoveredTaskId(task.id), onMouseLeave: () => setHoveredTaskId(null), onClick: onTaskClick ? (e) => onTaskClick(task, e) : undefined, onFocus: () => setFocusedTaskId(task.id), onBlur: () => setFocusedTaskId(null), onKeyDown: onTaskClick ? (e) => {
20
20
  if (e.key === "Enter" || e.key === " ") {
21
21
  e.preventDefault();
22
22
  onTaskClick(task, e);
@@ -13,8 +13,8 @@ import { getDaysBetween, addDays, toDate } from '../../../utils/dateUtils.js';
13
13
 
14
14
  const GanttTaskBar = React__default.memo(({ task, group, top }) => {
15
15
  var _a;
16
- const { theme, dayWidth, rangeStart, rowHeight, readonly, readonlyDates, readonlyProgress, onTaskUpdate, onTaskClick, tasks, } = useGanttContext();
17
- const { hoveredTaskId, setHoveredTaskId, focusedTaskId, setActiveDrag } = useGanttInteraction();
16
+ const { theme, dayWidth, rangeStart, rowHeight, readonly, readonlyDates, readonlyProgress, onTaskUpdate, onTaskClick, tasks, showTaskLabels, } = useGanttContext();
17
+ const { hoveredTaskId, setHoveredTaskId, focusedTaskId, setFocusedTaskId, setActiveDrag, } = useGanttInteraction();
18
18
  const [tooltipPos, setTooltipPos] = useState(null);
19
19
  const offset = getDayOffset(task.startDate, rangeStart);
20
20
  const span = getDaysBetween(task.startDate, task.endDate);
@@ -26,6 +26,13 @@ const GanttTaskBar = React__default.memo(({ task, group, top }) => {
26
26
  const canDragProgress = !readonly && !readonlyProgress && !!onTaskUpdate;
27
27
  const expectedProgress = calculateExpectedProgress(task.startDate, task.endDate);
28
28
  const isBehindSchedule = !task.completed && task.progress < expectedProgress;
29
+ const updateActiveDrag = useCallback((isDragging, deltaX, mode) => {
30
+ if (isDragging) {
31
+ setTooltipPos(null);
32
+ setHoveredTaskId(null);
33
+ }
34
+ setActiveDrag(isDragging ? { taskId: task.id, mode, deltaX } : null);
35
+ }, [setActiveDrag, setHoveredTaskId, task.id]);
29
36
  // ----- Drag: Move -----
30
37
  const handleMoveDragEnd = useCallback((result) => {
31
38
  if (!onTaskUpdate || result.daysDelta === 0)
@@ -45,8 +52,8 @@ const GanttTaskBar = React__default.memo(({ task, group, top }) => {
45
52
  disabled: !canDragDates,
46
53
  onDragEnd: handleMoveDragEnd,
47
54
  onDragStateChange: useCallback((isDragging, deltaX) => {
48
- setActiveDrag(isDragging ? { taskId: task.id, mode: "move", deltaX } : null);
49
- }, [setActiveDrag, task.id]),
55
+ updateActiveDrag(isDragging, deltaX, "move");
56
+ }, [updateActiveDrag]),
50
57
  });
51
58
  // ----- Drag: Resize left -----
52
59
  const handleResizeLeftEnd = useCallback((result) => {
@@ -66,10 +73,8 @@ const GanttTaskBar = React__default.memo(({ task, group, top }) => {
66
73
  disabled: !canDragDates,
67
74
  onDragEnd: handleResizeLeftEnd,
68
75
  onDragStateChange: useCallback((isDragging, deltaX) => {
69
- setActiveDrag(isDragging
70
- ? { taskId: task.id, mode: "resize-left", deltaX }
71
- : null);
72
- }, [setActiveDrag, task.id]),
76
+ updateActiveDrag(isDragging, deltaX, "resize-left");
77
+ }, [updateActiveDrag]),
73
78
  });
74
79
  // ----- Drag: Resize right -----
75
80
  const handleResizeRightEnd = useCallback((result) => {
@@ -89,20 +94,20 @@ const GanttTaskBar = React__default.memo(({ task, group, top }) => {
89
94
  disabled: !canDragDates,
90
95
  onDragEnd: handleResizeRightEnd,
91
96
  onDragStateChange: useCallback((isDragging, deltaX) => {
92
- setActiveDrag(isDragging
93
- ? { taskId: task.id, mode: "resize-right", deltaX }
94
- : null);
95
- }, [setActiveDrag, task.id]),
97
+ updateActiveDrag(isDragging, deltaX, "resize-right");
98
+ }, [updateActiveDrag]),
96
99
  });
97
100
  // ----- Drag: Progress -----
98
101
  const handleProgressDragEnd = useCallback((result) => {
99
102
  if (!onTaskUpdate)
100
103
  return;
104
+ if (result.newProgress === task.progress)
105
+ return;
101
106
  onTaskUpdate(task.id, {
102
107
  progress: result.newProgress,
103
108
  completed: result.newProgress === 100,
104
109
  });
105
- }, [onTaskUpdate, task.id]);
110
+ }, [onTaskUpdate, task.id, task.progress]);
106
111
  const progressDrag = useGanttDrag({
107
112
  dayWidth,
108
113
  mode: "progress",
@@ -110,6 +115,12 @@ const GanttTaskBar = React__default.memo(({ task, group, top }) => {
110
115
  originalProgress: task.progress,
111
116
  disabled: !canDragProgress,
112
117
  onDragEnd: handleProgressDragEnd,
118
+ onDragStateChange: useCallback((isDragging) => {
119
+ if (isDragging) {
120
+ setTooltipPos(null);
121
+ setHoveredTaskId(null);
122
+ }
123
+ }, [setHoveredTaskId]),
113
124
  });
114
125
  const anyDragging = moveDrag.isDragging ||
115
126
  resizeLeftDrag.isDragging ||
@@ -143,6 +154,10 @@ const GanttTaskBar = React__default.memo(({ task, group, top }) => {
143
154
  displayProgress = progressDrag.dragProgress;
144
155
  }
145
156
  const isNarrow = visualWidth <= 80;
157
+ const showInsideLabel = showTaskLabels === "inside" ||
158
+ (showTaskLabels === "auto" && !isNarrow);
159
+ const showOutsideLabel = showTaskLabels === "outside" ||
160
+ (showTaskLabels === "auto" && isNarrow);
146
161
  // ----- Hover highlighting (dependency connections) -----
147
162
  const isHighlighted = useMemo(() => {
148
163
  var _a, _b, _c;
@@ -163,6 +178,12 @@ const GanttTaskBar = React__default.memo(({ task, group, top }) => {
163
178
  return;
164
179
  onTaskClick === null || onTaskClick === void 0 ? void 0 : onTaskClick(task, e);
165
180
  };
181
+ const handleKeyDown = (e) => {
182
+ if ((e.key === "Enter" || e.key === " ") && onTaskClick) {
183
+ e.preventDefault();
184
+ onTaskClick(task, e);
185
+ }
186
+ };
166
187
  // ----- Tooltip -----
167
188
  const tooltipContent = (jsxs("div", { className: "text-sm space-y-2 min-w-[180px]", children: [jsx("div", { className: "font-semibold text-base", children: task.title }), jsxs("div", { className: "flex items-center gap-1.5 opacity-70", children: [jsx("span", { className: "inline-block size-2 rounded-full shrink-0", style: { backgroundColor: group.color } }), group.title] }), jsxs("div", { className: "space-y-0.5", children: [jsxs("div", { children: [formatDateShort(task.startDate), " \u2014 ", formatDateShort(task.endDate)] }), jsx("div", { className: "opacity-70", children: formatDuration(task.startDate, task.endDate) })] }), jsxs("div", { children: [jsxs("div", { className: "flex justify-between mb-1", children: [jsx("span", { children: "Progress" }), jsxs("span", { className: "font-medium", children: [task.progress, "%"] })] }), jsx("div", { className: "h-1.5 rounded-full bg-white/20 overflow-hidden", children: jsx("div", { className: "h-full rounded-full", style: {
168
189
  width: `${task.progress}%`,
@@ -183,12 +204,12 @@ const GanttTaskBar = React__default.memo(({ task, group, top }) => {
183
204
  }, onMouseLeave: () => {
184
205
  setTooltipPos(null);
185
206
  setHoveredTaskId(null);
186
- }, onClick: onTaskClick ? handleClick : undefined, onPointerDown: canDragDates ? moveDrag.handlePointerDown : undefined, role: "button", "aria-label": task.title, children: [jsxs(motion.div, { className: cn(theme.taskBarStyle, "group", task.completed && "opacity-60", canDragDates && !anyDragging && "cursor-grab", anyDragging && "cursor-grabbing", !canDragDates && "cursor-default", isHighlighted && "ring-2 ring-white/60", isFaded && "opacity-40", isFocused && "ring-2 ring-[var(--color-primary)] ring-offset-1"), style: {
207
+ }, onClick: onTaskClick ? handleClick : undefined, onPointerDown: canDragDates ? moveDrag.handlePointerDown : undefined, onFocus: () => setFocusedTaskId(task.id), onBlur: () => setFocusedTaskId(null), onKeyDown: handleKeyDown, role: onTaskClick ? "button" : "img", tabIndex: onTaskClick ? 0 : undefined, "aria-label": task.title, children: [jsxs(motion.div, { className: cn(theme.taskBarStyle, "group", task.completed && "opacity-60", canDragDates && !anyDragging && "cursor-grab", anyDragging && "cursor-grabbing", !canDragDates && "cursor-default", isHighlighted && "ring-2 ring-white/60", isFaded && "opacity-40", isFocused && "ring-2 ring-[var(--color-primary)] ring-offset-2 ring-offset-[var(--color-background)]"), style: {
187
208
  width: visualWidth,
188
209
  height: barHeight,
189
210
  backgroundColor: barColor,
190
211
  border: "1px solid rgba(0,0,0,0.12)",
191
- borderRadius: "2rem",
212
+ borderRadius: 999,
192
213
  transformOrigin: "left center",
193
214
  transition: anyDragging ? "none" : undefined,
194
215
  }, variants: taskBarVariants, initial: "hidden", animate: "visible", whileHover: anyDragging ? undefined : { filter: "brightness(0.96)" }, children: [jsx("div", { className: "absolute inset-0 rounded-full", style: {
@@ -197,7 +218,7 @@ const GanttTaskBar = React__default.memo(({ task, group, top }) => {
197
218
  } }), isBehindSchedule && (jsx("div", { className: "absolute top-0 bottom-0 w-px", style: {
198
219
  left: `${expectedProgress}%`,
199
220
  borderLeft: "2px dashed rgba(255,255,255,0.5)",
200
- } })), task.completed && (jsx("div", { className: "absolute top-1/2 left-2 right-2 h-px bg-white/60", style: { transform: "translateY(-0.5px)" } })), canDragDates && (jsx("div", { className: "absolute left-0 top-0 bottom-0 w-2 cursor-ew-resize z-20 rounded-l-full opacity-0 group-hover:opacity-100 transition-opacity hover:bg-black/10", onPointerDown: resizeLeftDrag.handlePointerDown })), canDragDates && (jsx("div", { className: "absolute right-0 top-0 bottom-0 w-2 cursor-ew-resize z-20 rounded-r-full opacity-0 group-hover:opacity-100 transition-opacity hover:bg-black/10", onPointerDown: resizeRightDrag.handlePointerDown })), canDragProgress && !isNarrow && (jsx("div", { className: "absolute top-0 bottom-0 w-3 cursor-ew-resize z-20 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity", style: { left: `calc(${displayProgress}% - 6px)` }, onPointerDown: progressDrag.handlePointerDown, children: jsx("div", { className: "w-0.5 h-3 rounded-full bg-white/80 shadow" }) })), !isNarrow && (jsxs("div", { className: "relative flex items-center gap-1.5 px-1 z-10 min-w-0 pointer-events-none", children: [task.assignee && (jsx(Avatar, { src: task.assignee.avatar, name: task.assignee.name, size: "xs" })), jsx("span", { className: "truncate text-sm text-[var(--color-text-inverse)] font-medium", children: task.title })] }))] }), isNarrow && (jsx("span", { className: "absolute text-sm font-medium whitespace-nowrap pointer-events-none", style: {
221
+ } })), task.completed && (jsx("div", { className: "absolute top-1/2 left-2 right-2 h-px bg-white/60", style: { transform: "translateY(-0.5px)" } })), canDragDates && (jsx("div", { className: "absolute left-0 top-0 bottom-0 z-20 w-3 cursor-ew-resize rounded-l-full opacity-0 transition-opacity hover:bg-black/15 group-hover:opacity-100", onPointerDown: resizeLeftDrag.handlePointerDown, "aria-hidden": "true" })), canDragDates && (jsx("div", { className: "absolute right-0 top-0 bottom-0 z-20 w-3 cursor-ew-resize rounded-r-full opacity-0 transition-opacity hover:bg-black/15 group-hover:opacity-100", onPointerDown: resizeRightDrag.handlePointerDown, "aria-hidden": "true" })), canDragProgress && !isNarrow && (jsx("div", { className: "absolute top-0 bottom-0 z-20 flex w-4 cursor-ew-resize items-center justify-center opacity-0 transition-opacity group-hover:opacity-100", style: { left: `calc(${displayProgress}% - 6px)` }, onPointerDown: progressDrag.handlePointerDown, "aria-hidden": "true", children: jsx("div", { className: "h-3.5 w-0.5 rounded-full bg-white/90 shadow" }) })), showInsideLabel && (jsxs("div", { className: "relative z-10 flex min-w-0 items-center gap-1.5 px-1.5 pointer-events-none", children: [task.assignee && (jsx(Avatar, { src: task.assignee.avatar, name: task.assignee.name, size: "xs" })), jsx("span", { className: "truncate text-sm text-[var(--color-text-inverse)] font-medium", children: task.title })] }))] }), showOutsideLabel && (jsx("span", { className: cn(theme.taskLabelOutsideStyle), style: {
201
222
  left: visualWidth + 6,
202
223
  top: barHeight / 2,
203
224
  transform: "translateY(-50%)",
@@ -9,6 +9,7 @@ const GanttTimelineGrid = ({ days, height, }) => {
9
9
  const totalWidth = days.length * dayWidth;
10
10
  const showVertical = gridLines === "both" || gridLines === "vertical";
11
11
  const showHorizontal = gridLines === "both" || gridLines === "horizontal";
12
+ const shouldDrawVerticalLine = (day) => dayWidth >= 12 || day.getDate() === 1 || day.getDay() === 1;
12
13
  // Build a dateKey -> holiday lookup
13
14
  const holidayMap = useMemo(() => {
14
15
  const map = new Map();
@@ -23,7 +24,7 @@ const GanttTimelineGrid = ({ days, height, }) => {
23
24
  var _a, _b;
24
25
  const key = `${day.getFullYear()}-${day.getMonth()}-${day.getDate()}`;
25
26
  const holiday = holidayMap.get(key);
26
- return (jsx("div", { className: cn("absolute top-0 bottom-0", showVertical && theme.gridLineStyle, isWeekend(day) && theme.weekendStyle), style: { left: i * dayWidth, width: dayWidth }, children: holiday && (jsxs(Fragment, { children: [jsx("div", { className: "absolute inset-0", style: {
27
+ return (jsx("div", { className: cn("absolute top-0 bottom-0", showVertical && shouldDrawVerticalLine(day) && theme.gridLineStyle, isWeekend(day) && theme.weekendStyle), style: { left: i * dayWidth, width: dayWidth }, children: holiday && (jsxs(Fragment, { children: [jsx("div", { className: "absolute inset-0", style: {
27
28
  backgroundColor: (_a = holiday.color) !== null && _a !== void 0 ? _a : "var(--color-warning-100, rgba(245, 158, 11, 0.08))",
28
29
  } }), jsx("span", { className: "absolute bottom-1 left-1/2 -translate-x-1/2 text-[8px] whitespace-nowrap opacity-60", style: { color: (_b = holiday.color) !== null && _b !== void 0 ? _b : "var(--color-warning, #F59E0B)" }, children: holiday.label })] })) }, i));
29
30
  }), showHorizontal &&
@@ -2,38 +2,62 @@ import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { cn } from '../../../utils/cn.js';
3
3
  import { useGanttContext } from './GanttChart.js';
4
4
  import { formatDayLabel } from './GanttChart.utils.js';
5
- import { isSameDay, isWeekend } from '../../../utils/dateUtils.js';
5
+ import { isSameDay, isWeekend, getWeekNumber } from '../../../utils/dateUtils.js';
6
6
 
7
7
  function getDayLabel(day, zoom) {
8
8
  switch (zoom) {
9
9
  case "day":
10
10
  return formatDayLabel(day, zoom);
11
11
  case "week":
12
- // Show day number only on Mondays
13
- return day.getDay() === 1 ? `${day.getDate()}` : "";
12
+ return day.getDay() === 1 ? `W${getWeekNumber(day)}` : "";
14
13
  case "quarter":
15
- // Show day number on 1st, 8th, 15th, 22nd
16
14
  return [1, 8, 15, 22].includes(day.getDate())
17
15
  ? `${day.getDate()}`
18
16
  : "";
19
17
  case "month":
20
- // Show day number on 1st and 15th
21
- return day.getDate() === 1 || day.getDate() === 15
22
- ? `${day.getDate()}`
23
- : "";
24
- case "year":
25
- // Show only on 1st of each month
26
18
  return day.getDate() === 1
27
19
  ? day.toLocaleDateString("en-US", { month: "short" })
28
20
  : "";
21
+ case "year":
22
+ return day.getDate() === 1 && day.getMonth() === 0
23
+ ? `${day.getFullYear()}`
24
+ : "";
25
+ }
26
+ }
27
+ function getHeaderSpans(days, monthSpans, zoom) {
28
+ if (zoom !== "month" && zoom !== "year") {
29
+ return monthSpans;
30
+ }
31
+ const spans = [];
32
+ let currentLabel = "";
33
+ let startIndex = 0;
34
+ days.forEach((day, index) => {
35
+ const label = zoom === "year"
36
+ ? `${day.getFullYear()}`
37
+ : day.toLocaleDateString("en-US", { year: "numeric" });
38
+ if (index === 0) {
39
+ currentLabel = label;
40
+ startIndex = 0;
41
+ return;
42
+ }
43
+ if (label !== currentLabel) {
44
+ spans.push({ label: currentLabel, startIndex, span: index - startIndex });
45
+ currentLabel = label;
46
+ startIndex = index;
47
+ }
48
+ });
49
+ if (days.length > 0) {
50
+ spans.push({ label: currentLabel, startIndex, span: days.length - startIndex });
29
51
  }
52
+ return spans;
30
53
  }
31
54
  const GanttTimelineHeader = ({ days, monthSpans, }) => {
32
55
  const { theme, dayWidth, zoom } = useGanttContext();
33
56
  const totalWidth = days.length * dayWidth;
57
+ const headerSpans = getHeaderSpans(days, monthSpans, zoom);
34
58
  const today = new Date();
35
59
  today.setHours(0, 0, 0, 0);
36
- return (jsxs("div", { className: cn(theme.timelineHeaderStyle), style: { width: totalWidth }, children: [jsx("div", { className: "flex", style: { height: 28 }, children: monthSpans.map((span) => (jsx("div", { className: cn(theme.timelineMonthStyle), style: { width: span.span * dayWidth }, children: span.label }, `${span.label}-${span.startIndex}`))) }), jsx("div", { className: "flex", style: { height: 28 }, children: days.map((day, i) => {
60
+ return (jsxs("div", { className: cn(theme.timelineHeaderStyle), style: { width: totalWidth }, children: [jsx("div", { className: "flex", style: { height: 28 }, children: headerSpans.map((span) => (jsx("div", { className: cn(theme.timelineMonthStyle), style: { width: span.span * dayWidth }, children: span.label }, `${span.label}-${span.startIndex}`))) }), jsx("div", { className: "flex", style: { height: 28 }, children: days.map((day, i) => {
37
61
  const isToday = isSameDay(day, today);
38
62
  const weekend = isWeekend(day);
39
63
  return (jsx("div", { className: cn(theme.timelineDayStyle, weekend && "bg-[var(--color-background-secondary)]/50", isToday && "font-semibold text-[var(--color-primary)]"), style: { width: dayWidth }, children: getDayLabel(day, zoom) }, i));
@@ -15,7 +15,7 @@ const ZOOM_OPTIONS = [
15
15
  { value: "year", label: "Year" },
16
16
  ];
17
17
  const GanttToolbar = () => {
18
- const { theme, zoom, setZoom, hideCompleted, setHideCompleted, onGroupAdd, todayOffset, dayWidth, readonly, } = useGanttContext();
18
+ const { theme, zoom, setZoom, hideCompleted, setHideCompleted, onGroupAdd, todayOffset, dayWidth, readonly, toolbar, } = useGanttContext();
19
19
  const handleScrollToToday = () => {
20
20
  const container = document.querySelector("[data-gantt-chart-scroll]");
21
21
  if (!container || todayOffset === null)
@@ -23,7 +23,7 @@ const GanttToolbar = () => {
23
23
  const targetScroll = todayOffset * dayWidth - container.clientWidth / 2;
24
24
  container.scrollTo({ left: targetScroll, behavior: "smooth" });
25
25
  };
26
- return (jsxs("div", { className: cn(theme.toolbarStyle), children: [jsxs("div", { className: "flex items-center gap-3", children: [todayOffset !== null && (jsxs(Button, { variant: "outline", color: "neutral", size: "sm", onClick: handleScrollToToday, children: [jsx(CalendarIcon, { className: "size-4" }), "Today"] })), jsx(Segmented, { value: zoom, onChange: (val) => setZoom(val), size: "sm", children: ZOOM_OPTIONS.map((opt) => (jsx(Segmented.Item, { value: opt.value, children: opt.label }, opt.value))) }), jsx(Checkbox, { id: "gantt-hide-completed", name: "hideCompleted", value: "hideCompleted", checked: hideCompleted, onChange: (checked) => setHideCompleted(checked), label: "Hide completed", size: "sm" })] }), jsx("div", { className: "flex items-center gap-2", children: onGroupAdd && !readonly && (jsxs(Button, { variant: "soft", size: "sm", onClick: onGroupAdd, children: [jsx(PlusIcon, { className: "size-3.5" }), "Add group"] })) })] }));
26
+ return (jsxs("div", { className: cn(theme.toolbarStyle), children: [jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [toolbar.today && todayOffset !== null && (jsxs(Button, { variant: "outline", color: "neutral", size: "sm", onClick: handleScrollToToday, children: [jsx(CalendarIcon, { className: "size-4" }), "Today"] })), toolbar.zoom && (jsx(Segmented, { value: zoom, onChange: (val) => setZoom(val), size: "sm", "aria-label": "Gantt zoom", className: "max-w-full overflow-x-auto", children: ZOOM_OPTIONS.map((opt) => (jsx(Segmented.Item, { value: opt.value, children: opt.label }, opt.value))) })), toolbar.hideCompleted && (jsx(Checkbox, { id: "gantt-hide-completed", name: "hideCompleted", value: "hideCompleted", checked: hideCompleted, onChange: (checked) => setHideCompleted(checked), label: "Hide completed", size: "sm" }))] }), jsx("div", { className: "flex items-center gap-2", children: toolbar.addGroup && onGroupAdd && !readonly && (jsxs(Button, { variant: "soft", size: "sm", onClick: onGroupAdd, children: [jsx(PlusIcon, { className: "size-3.5" }), "Add group"] })) })] }));
27
27
  };
28
28
 
29
29
  export { GanttToolbar };
@@ -34,7 +34,7 @@ function useGanttDrag({ dayWidth, mode, barWidthPx, originalProgress, disabled =
34
34
  cancelledRef.current = false;
35
35
  didDragRef.current = false;
36
36
  const onPointerMove = (ev) => {
37
- var _a;
37
+ var _a, _b;
38
38
  if (cancelledRef.current)
39
39
  return;
40
40
  const rawDelta = ev.clientX - startXRef.current;
@@ -52,12 +52,13 @@ function useGanttDrag({ dayWidth, mode, barWidthPx, originalProgress, disabled =
52
52
  const newProgress = Math.round(Math.max(0, Math.min(100, originalProgress + progressDelta)));
53
53
  setDragProgress(newProgress);
54
54
  setDragDeltaX(rawDelta);
55
+ (_a = onDragStateChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onDragStateChangeRef, true, rawDelta);
55
56
  }
56
57
  else {
57
58
  // Move/resize: snap to day grid
58
59
  const snappedDelta = Math.round(rawDelta / dayWidth) * dayWidth;
59
60
  setDragDeltaX(snappedDelta);
60
- (_a = onDragStateChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onDragStateChangeRef, true, snappedDelta);
61
+ (_b = onDragStateChangeRef.current) === null || _b === void 0 ? void 0 : _b.call(onDragStateChangeRef, true, snappedDelta);
61
62
  }
62
63
  };
63
64
  const onPointerUp = () => {
@@ -1,3 +1,3 @@
1
1
  export { GanttChart } from "./GanttChart";
2
- export type { GanttChartProps, GanttTask, GanttGroup, GanttColumn, GanttZoom, GanttHoliday, GanttChartThemeOverrides, } from "./GanttChart.types";
2
+ export type { GanttChartProps, GanttTask, GanttGroup, GanttColumn, GanttZoom, GanttTaskLabelMode, GanttToolbarOptions, GanttHoliday, GanttChartThemeOverrides, } from "./GanttChart.types";
3
3
  export { ganttChartTheme } from "./GanttChart.theme";