diff --git a/README.md b/README.md index 61704f8..c64ba07 100644 --- a/README.md +++ b/README.md @@ -84,26 +84,32 @@ npm start ### StylingOption -| Parameter Name | Type | Description | -| :------------------------- | :----- | :---------------------------------------------------------------------- | -| headerHeight | number | Specifies the header height. | -| columnWidth | number | Specifies the time period width. | -| rowHeight | number | Specifies the task row height. | -| barCornerRadius | number | Specifies the taskbar corner rounding. | -| barFill | number | Specifies the taskbar occupation. Sets in percent from 0 to 100. | -| handleWidth | number | Specifies width the taskbar drag event control for start and end dates. | -| fontFamily | string | Specifies the application font. | -| fontSize | string | Specifies the application font size. | -| barProgressColor | string | Specifies the taskbar progress fill color globally. | -| barProgressSelectedColor | string | Specifies the taskbar progress fill color globally on select. | -| barBackgroundColor | string | Specifies the taskbar background fill color globally. | -| barBackgroundSelectedColor | string | Specifies the taskbar background fill color globally on select. | -| arrowColor | string | Specifies the relationship arrow fill color. | -| arrowIndent | number | Specifies the relationship arrow right indent. Sets in px | -| todayColor | string | Specifies the current period column fill color. | -| getTooltipContent | \*\* | Specifies the Tooltip for selected taskbar. | +| Parameter Name | Type | Description | +| :------------------------- | :------- | :--------------------------------------------------------------------------------------------- | +| headerHeight | number | Specifies the header height. | +| ganttHeight | number | Specifies the gantt chart height without header. Default is 0. It`s mean no height limitation. | +| columnWidth | number | Specifies the time period width. | +| listCellWidth | string | Specifies the task list cell width. Empty string is mean "no display". | +| rowHeight | number | Specifies the task row height. | +| barCornerRadius | number | Specifies the taskbar corner rounding. | +| barFill | number | Specifies the taskbar occupation. Sets in percent from 0 to 100. | +| handleWidth | number | Specifies width the taskbar drag event control for start and end dates. | +| fontFamily | string | Specifies the application font. | +| fontSize | string | Specifies the application font size. | +| barProgressColor | string | Specifies the taskbar progress fill color globally. | +| barProgressSelectedColor | string | Specifies the taskbar progress fill color globally on select. | +| barBackgroundColor | string | Specifies the taskbar background fill color globally. | +| barBackgroundSelectedColor | string | Specifies the taskbar background fill color globally on select. | +| arrowColor | string | Specifies the relationship arrow fill color. | +| arrowIndent | number | Specifies the relationship arrow right indent. Sets in px | +| todayColor | string | Specifies the current period column fill color. | +| TooltipContent | \*\* | Specifies the Tooltip view for selected taskbar. | +| TaskListHeader | \*\*\* | Specifies the task list Header view | +| TaskListTable | \*\*\*\* | Specifies the task list Table view | -[\*\*`(task:Task, fontSize:string , fontFamily:string) => JSX.Element;`](https://github.com/MaTeMaTuK/gantt-task-react/blob/07dfeddd4d96ecc418619cad9cd9ba3c31bb82a8/src/components/Other/tooltip.tsx#L47) +[\*\*`React.FC<{ task: Task; fontSize: string; fontFamily: string; }>;`](https://github.com/MaTeMaTuK/gantt-task-react/blob/07dfeddd4d96ecc418619cad9cd9ba3c31bb82a8/src/components/Other/tooltip.tsx#L47) +\*\*\*React.SFC<{ headerHeight: number; rowWidth: string; fontFamily: string; fontSize: string;}>; +\*\*\*\*TaskListTable: React.SFC<{ rowHeight: number; rowWidth: string; fontFamily: string; fontSize: string; locale: string; tasks: Task[]; }>; ### Task diff --git a/example/src/App.tsx b/example/src/App.tsx index b14f529..e2c9ea4 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,13 +1,19 @@ import React from "react"; import "gantt-task-react/dist/index.css"; -import { Task, ViewMode } from "gantt-task-react"; +import { Task, ViewMode, Gantt } from "gantt-task-react"; import { ViewSwitcher } from "./components/view-switcher"; -import { GanttTableExample } from "./components/gantt-table"; //Init const App = () => { const currentDate = new Date(); const [view, setView] = React.useState(ViewMode.Day); + const [isChecked, setIsChecked] = React.useState(true); + let columnWidth = 60; + if (view === ViewMode.Month) { + columnWidth = 300; + } else if (view === ViewMode.Week) { + columnWidth = 250; + } let tasks: Task[] = [ { start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 1), @@ -21,7 +27,6 @@ const App = () => { name: "Idea", id: "Task 0", progress: 45, - isDisabled: true, }, { start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 2), @@ -58,12 +63,20 @@ const App = () => { { start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 15), end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 16), - name: "Release & Eat Burgers", + name: "Release & Eat Pizza", id: "Task 6", progress: currentDate.getMonth(), dependencies: ["Task 4"], styles: { progressColor: "#ffbb54", progressSelectedColor: "#ff9e0d" }, }, + { + start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 24), + end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 25), + name: "Closing", + id: "Task 9", + progress: 0, + isDisabled: true, + }, ]; let onTaskChange = (task: Task) => { @@ -85,14 +98,33 @@ const App = () => { return (
- setView(viewMode)} /> - setView(viewMode)} + onViewListChange={setIsChecked} + isChecked={isChecked} + /> +

Gantt With Unlimited Height

+ +

Gantt With Limited Height

+
); diff --git a/example/src/components/gantt-table.tsx b/example/src/components/gantt-table.tsx deleted file mode 100644 index 5687115..0000000 --- a/example/src/components/gantt-table.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React from "react"; -import "gantt-task-react/dist/index.css"; -import { - Gantt, - Task, - EventOption, - StylingOption, - ViewMode, - DisplayOption, -} from "gantt-task-react"; - -//Gantt with Custom table example -type GanttTableExampleProps = { tasks: Task[] } & EventOption & DisplayOption; -export const GanttTableExample: React.SFC = props => { - const gridColumnWidth = 150; - let options: StylingOption = { - fontSize: "14px", - fontFamily: - "Arial, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue", - headerHeight: 50, - rowHeight: 50, - }; - if (props.viewMode === ViewMode.Month) { - options.columnWidth = 300; - } else if (props.viewMode === ViewMode.Week) { - options.columnWidth = 250; - } - - return ( -
-
-
-
- - 📃 - - Name -
-
- - 📅 - - From -
-
- - 📅 - - To -
-
- {props.tasks.map(t => { - return ( -
-
{t.name}
-
{t.start.toDateString()}
-
{t.end.toDateString()}
-
- ); - })} -
-
- -
-
- ); -}; diff --git a/example/src/components/view-switcher.tsx b/example/src/components/view-switcher.tsx index 93b700a..44d96ae 100644 --- a/example/src/components/view-switcher.tsx +++ b/example/src/components/view-switcher.tsx @@ -2,31 +2,56 @@ import React from "react"; import "gantt-task-react/dist/index.css"; import { ViewMode } from "gantt-task-react"; type ViewSwitcherProps = { - onViewChange: (viewMode: ViewMode) => void; + isChecked: boolean; + onViewListChange: (isChecked: boolean) => void; + onViewModeChange: (viewMode: ViewMode) => void; }; export const ViewSwitcher: React.SFC = ({ - onViewChange, + onViewModeChange, + onViewListChange, + isChecked, }) => { return (
- - - - + +
+ + Show Task List +
); }; diff --git a/example/src/index.css b/example/src/index.css index ec6adb0..1e7e9de 100644 --- a/example/src/index.css +++ b/example/src/index.css @@ -1,57 +1,10 @@ -/*Styles for Example Table*/ -.Wrapper { - display: flex; - justify-content: space-around; - background: #ffff; - padding: 0; - margin: 0; - list-style: none; -} - -.GanttTable { - display: table; -} - -.GanttTable-header { - display: table-row; - list-style: none; -} - -.GanttTable-headerItem { - display: table-cell; - padding-top: 24px; - vertical-align: text-bottom; - - border-left: #e6e4e4 1px solid; - border-top: #e6e4e4 1px solid; - border-bottom: #e6e4e4 1px solid; -} - -.GanttTable-row { - display: table-row; - text-overflow: ellipsis; -} - -.GanttTable-row:nth-of-type(odd) { - background-color: #f5f5f5; -} - -.GanttTable-cell { - display: table-cell; - vertical-align: middle; - border-left: #e6e4e4 1px solid; -} - -.GanttTable-icon { - padding-right: 2px; -} - .ViewContainer { list-style: none; -ms-box-orient: horizontal; display: flex; -webkit-justify-content: flex-end; justify-content: flex-end; + align-items: center; } .Button { @@ -59,9 +12,68 @@ color: black; border: none; padding: 7px 16px; - text-align: center; text-decoration: none; - font-size: 14px; margin: 4px 2px; cursor: pointer; + font-size: 14px; + text-align: center; +} +.Switch { + margin: 4px 15px; + font-size: 14px; + font-family: "Arial, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue"; + display: flex; + justify-content: center; + align-items: center; +} +.Switch_Toggle { + position: relative; + display: inline-block; + width: 60px; + height: 30px; + margin-right: 5px; +} + +.Switch_Toggle input { + opacity: 0; + width: 0; + height: 0; +} + +.Slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: 0.4s; + transition: 0.4s; +} + +.Slider:before { + position: absolute; + content: ""; + height: 21px; + width: 21px; + left: 6px; + bottom: 4px; + background-color: white; + -webkit-transition: 0.4s; + transition: 0.4s; +} + +input:checked + .Slider { + background-color: #2196f3; +} + +input:focus + .Slider { + box-shadow: 0 0 1px #2196f3; +} + +input:checked + .Slider:before { + -webkit-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(26px); } diff --git a/package.json b/package.json index 1278d78..862aa35 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gantt-task-react", - "version": "0.1.2", + "version": "0.1.3", "description": "Interactive Gantt Chart for React with TypeScript.", "author": "MaTeMaTuK ", "homepage": "https://github.com/MaTeMaTuK/gantt-task-react", diff --git a/src/components/Bar/bar.module.css b/src/components/Bar/bar.module.css index 41fde69..8a6414b 100644 --- a/src/components/Bar/bar.module.css +++ b/src/components/Bar/bar.module.css @@ -32,6 +32,12 @@ .barLabelOutside { fill: #555; text-anchor: start; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + pointer-events: none; } .barBackground { diff --git a/src/components/Bar/bar.tsx b/src/components/Bar/bar.tsx index e32c698..37e7885 100644 --- a/src/components/Bar/bar.tsx +++ b/src/components/Bar/bar.tsx @@ -1,14 +1,14 @@ import React, { useState } from "react"; -import { BarProgressHandle } from "./bar-progress-handle"; -import { BarDateHandle } from "./bar-date-handle"; -import { BarDisplay } from "./bar-display"; import { BarTask } from "../../types/bar-task"; import { progressWithByParams, getProgressPoint, } from "../../helpers/bar-helper"; import styles from "./bar.module.css"; -import { GanttContentMoveAction } from "../Gantt/gantt-content"; +import { GanttContentMoveAction } from "../gantt/task-gantt-content"; +import { BarDisplay } from "./bar-display"; +import { BarDateHandle } from "./bar-date-handle"; +import { BarProgressHandle } from "./bar-progress-handle"; export type BarProps = { task: BarTask; diff --git a/src/components/Calendar/calendar.module.css b/src/components/Calendar/calendar.module.css index 4186c7c..1be99d3 100644 --- a/src/components/Calendar/calendar.module.css +++ b/src/components/Calendar/calendar.module.css @@ -23,3 +23,9 @@ user-select: none; pointer-events: none; } + +.calendarHeader { + fill: #ffffff; + stroke: #e0e0e0; + stroke-width: 1.4; +} diff --git a/src/components/Calendar/calendar.tsx b/src/components/Calendar/calendar.tsx index bb1badc..c0a409e 100644 --- a/src/components/Calendar/calendar.tsx +++ b/src/components/Calendar/calendar.tsx @@ -67,7 +67,7 @@ export const Calendar: React.FC = ({ const getCalendarValuesForWeek = () => { const topValues: ReactChild[] = []; const bottomValues: ReactChild[] = []; - let weeksCount: number = 0; + let weeksCount: number = 1; const topDefaultHeight = headerHeight * 0.5; for (let i = dates.length - 1; i >= 0; i--) { const date = dates[i]; @@ -138,7 +138,7 @@ export const Calendar: React.FC = ({ topValues.push( = ({ const topValue = `${date.getDate()} ${getLocaleMonth(date, locale)}`; topValues.push( = ({ } return ( + {bottomValues} {topValues} ); diff --git a/src/components/Gantt/gantt.module.css b/src/components/Gantt/gantt.module.css new file mode 100644 index 0000000..39e74b9 --- /dev/null +++ b/src/components/Gantt/gantt.module.css @@ -0,0 +1,20 @@ +.ganttVerticalContainer { + overflow-x: auto; + overflow-y: hidden; + font-size: 0; + margin: 0; + padding: 0; +} + +.horizontalContainer { + margin: 0; + padding: 0; + overflow-y: hidden; +} + +.wrapper { + display: flex; + padding: 0; + margin: 0; + list-style: none; +} diff --git a/src/components/Gantt/gantt.tsx b/src/components/Gantt/gantt.tsx index 9e3990c..3047efc 100644 --- a/src/components/Gantt/gantt.tsx +++ b/src/components/Gantt/gantt.tsx @@ -1,15 +1,24 @@ -import React, { useRef, useState } from "react"; +import React, { useState, SyntheticEvent } from "react"; import { ViewMode, GanttProps, Task } from "../../types/public-types"; -import { Grid, GridProps } from "../Grid/grid"; -import { Calendar, CalendarProps } from "../Calendar/calendar"; -import { GanttContent, GanttContentProps } from "./gantt-content"; +import { GridProps } from "../grid/grid"; import { ganttDateRange, seedDates } from "../../helpers/date-helper"; +import { CalendarProps } from "../calendar/calendar"; +import { TaskGanttContentProps } from "./task-gantt-content"; +import { TaskListHeaderDefault } from "../task-list/task-list-header"; +import { TaskListTableDefault } from "../task-list/task-list-table"; +import { StandardTooltipContent } from "../other/tooltip"; +import { Scroll } from "../other/scroll"; +import { TaskListProps, TaskList } from "../task-list/task-list"; +import styles from "./gantt.module.css"; +import { TaskGantt } from "./task-gantt"; export const Gantt: React.SFC = ({ tasks, headerHeight = 50, columnWidth = 60, + listCellWidth = "150px", rowHeight = 50, + ganttHeight = 0, viewMode = ViewMode.Day, locale = "en-GB", barFill = 60, @@ -25,27 +34,36 @@ export const Gantt: React.SFC = ({ fontSize = "14px", arrowIndent = 20, todayColor = "rgba(252, 248, 227, 0.5)", + TooltipContent = StandardTooltipContent, + TaskListHeader = TaskListHeaderDefault, + TaskListTable = TaskListTableDefault, onDateChange, onProgressChange, onDoubleClick, onTaskDelete, - getTooltipContent, }) => { - const svg = useRef(null); const [ganttTasks, setGanttTasks] = useState(tasks); + const [scroll, setScroll] = useState(0); + const [startDate, endDate] = ganttDateRange(ganttTasks, viewMode); const dates = seedDates(startDate, endDate, viewMode); - const handleOnTasksChange = (tasks: Task[]) => { + const svgHeight = rowHeight * tasks.length; + const gridWidth = dates.length * columnWidth; + + const onTasksDateChange = (tasks: Task[]) => { setGanttTasks(tasks); }; + const handleScroll = (event: SyntheticEvent) => { + setScroll(event.currentTarget.scrollTop); + }; + const gridProps: GridProps = { columnWidth, - gridWidth: dates.length * columnWidth, + gridWidth, tasks: ganttTasks, rowHeight, - headerHeight, dates, todayColor, }; @@ -58,7 +76,7 @@ export const Gantt: React.SFC = ({ fontFamily, fontSize, }; - const barProps: GanttContentProps = { + const barProps: TaskGanttContentProps = { tasks: ganttTasks, rowHeight, barCornerRadius, @@ -69,32 +87,52 @@ export const Gantt: React.SFC = ({ barProgressSelectedColor, barBackgroundColor, barBackgroundSelectedColor, - headerHeight, handleWidth, arrowColor, timeStep, fontFamily, fontSize, arrowIndent, - svg, - onTasksDateChange: handleOnTasksChange, + svgHeight, + onTasksDateChange: onTasksDateChange, onDateChange, onProgressChange, onDoubleClick, onTaskDelete, - getTooltipContent, + TooltipContent, }; + + const tableProps: TaskListProps = { + rowHeight, + rowWidth: listCellWidth, + fontFamily, + fontSize, + tasks: ganttTasks, + locale, + headerHeight, + scroll, + ganttHeight, + horizontalContainerClass: styles.horizontalContainer, + TaskListHeader, + TaskListTable, + }; + return ( - - - - - +
+ {listCellWidth && } + + +
); }; diff --git a/src/components/Gantt/gantt-content.tsx b/src/components/Gantt/task-gantt-content.tsx similarity index 87% rename from src/components/Gantt/gantt-content.tsx rename to src/components/Gantt/task-gantt-content.tsx index db3929c..0666803 100644 --- a/src/components/Gantt/gantt-content.tsx +++ b/src/components/Gantt/task-gantt-content.tsx @@ -1,14 +1,14 @@ import React, { useEffect, useState } from "react"; import { Task, EventOption } from "../../types/public-types"; -import { Bar } from "../Bar/bar"; +import { Bar } from "../bar/bar"; import { BarTask } from "../../types/bar-task"; -import { Arrow } from "../Other/arrow"; +import { Arrow } from "../other/arrow"; import { convertToBarTasks, handleTaskBySVGMouseEvent, BarMoveAction, } from "../../helpers/bar-helper"; -import { Tooltip } from "../Other/tooltip"; +import { Tooltip } from "../other/tooltip"; import { isKeyboardEvent } from "../../helpers/other-helper"; export type GanttContentMoveAction = @@ -21,7 +21,7 @@ export type BarEvent = { selectedTask?: BarTask; action: GanttContentMoveAction; }; -export type GanttContentProps = { +export type TaskGanttContentProps = { tasks: Task[]; dates: Date[]; rowHeight: number; @@ -32,23 +32,23 @@ export type GanttContentProps = { barProgressSelectedColor: string; barBackgroundColor: string; barBackgroundSelectedColor: string; - headerHeight: number; handleWidth: number; timeStep: number; - svg: React.RefObject; + svg?: React.RefObject; + svgHeight: number; arrowColor: string; arrowIndent: number; fontSize: string; fontFamily: string; - getTooltipContent?: ( - task: Task, - fontSize: string, - fontFamily: string - ) => JSX.Element; + TooltipContent: React.FC<{ + task: Task; + fontSize: string; + fontFamily: string; + }>; onTasksDateChange: (tasks: Task[]) => void; } & EventOption; -export const GanttContent: React.FC = ({ +export const TaskGanttContent: React.FC = ({ tasks, dates, rowHeight, @@ -59,10 +59,10 @@ export const GanttContent: React.FC = ({ barProgressSelectedColor, barBackgroundColor, barBackgroundSelectedColor, - headerHeight, handleWidth, timeStep, svg, + svgHeight, arrowColor, arrowIndent, fontFamily, @@ -72,9 +72,9 @@ export const GanttContent: React.FC = ({ onProgressChange, onDoubleClick, onTaskDelete, - getTooltipContent, + TooltipContent, }) => { - const point = svg.current?.createSVGPoint(); + const point = svg?.current?.createSVGPoint(); const [barEvent, setBarEvent] = useState({ action: "", }); @@ -103,7 +103,6 @@ export const GanttContent: React.FC = ({ columnWidth, rowHeight, barFill, - headerHeight, barCornerRadius, handleWidth, barProgressColor, @@ -120,7 +119,6 @@ export const GanttContent: React.FC = ({ dates, barFill, handleWidth, - headerHeight, barProgressColor, barProgressSelectedColor, barBackgroundColor, @@ -139,9 +137,7 @@ export const GanttContent: React.FC = ({ if (action === "delete") { if (onTaskDelete) { await onTaskDelete(selectedTask); - const newTasks = barTasks.filter( - t => t.id !== barEvent.selectedTask?.id - ); + const newTasks = barTasks.filter(t => t.id !== selectedTask.id); onTasksDateChange(newTasks); } } @@ -154,7 +150,7 @@ export const GanttContent: React.FC = ({ setBarEvent({ action: "" }); } } else if (action === "move") { - if (!svg.current || !point) return; + if (!svg?.current || !point) return; point.x = event.clientX; const cursor = point.matrixTransform( svg.current.getScreenCTM()?.inverse() @@ -173,12 +169,12 @@ export const GanttContent: React.FC = ({ useEffect(() => { const handleMouseMove = async (event: MouseEvent) => { - if (!barEvent.selectedTask || !point || !svg.current) return; + if (!barEvent.selectedTask || !point || !svg?.current) return; event.preventDefault(); point.x = event.clientX; const cursor = point.matrixTransform( - svg.current.getScreenCTM()?.inverse() + svg?.current.getScreenCTM()?.inverse() ); const { isChanged, changedTask } = handleTaskBySVGMouseEvent( @@ -199,12 +195,12 @@ export const GanttContent: React.FC = ({ const handleMouseUp = async (event: MouseEvent) => { const { selectedTask, action } = barEvent; - if (!selectedTask || !point || !svg.current) return; + if (!selectedTask || !point || !svg?.current) return; event.preventDefault(); point.x = event.clientX; const cursor = point.matrixTransform( - svg.current.getScreenCTM()?.inverse() + svg?.current.getScreenCTM()?.inverse() ); const { changedTask } = handleTaskBySVGMouseEvent( @@ -239,7 +235,7 @@ export const GanttContent: React.FC = ({ barEvent.action === "end" || barEvent.action === "start" || barEvent.action === "progress") && - svg.current + svg?.current ) { svg.current.addEventListener("mousemove", handleMouseMove); svg.current.addEventListener("mouseup", handleMouseUp); @@ -282,7 +278,7 @@ export const GanttContent: React.FC = ({ arrowIndent={arrowIndent} isProgressChangeable={!!onProgressChange && !task.isDisabled} isDateChangeable={!!onDateChange && !task.isDisabled} - isDelete={!!onTaskDelete && !task.isDisabled} + isDelete={!task.isDisabled} onEventStart={handleBarEventStart} key={task.id} /> @@ -293,11 +289,12 @@ export const GanttContent: React.FC = ({ {barEvent.selectedTask && ( )} diff --git a/src/components/Gantt/task-gantt.tsx b/src/components/Gantt/task-gantt.tsx new file mode 100644 index 0000000..3f8c290 --- /dev/null +++ b/src/components/Gantt/task-gantt.tsx @@ -0,0 +1,63 @@ +import React, { useRef, useEffect } from "react"; +import { GridProps, Grid } from "../grid/grid"; +import { CalendarProps, Calendar } from "../calendar/calendar"; +import { TaskGanttContentProps, TaskGanttContent } from "./task-gantt-content"; +import styles from "./gantt.module.css"; + +export type TaskGanttProps = { + gridProps: GridProps; + calendarProps: CalendarProps; + barProps: TaskGanttContentProps; + ganttHeight: number; + scroll: number; +}; +export const TaskGantt: React.FC = ({ + gridProps, + calendarProps, + barProps, + ganttHeight, + scroll, +}) => { + const ganttSVGRef = useRef(null); + const horizontalContainerRef = useRef(null); + const newBarProps = { ...barProps, svg: ganttSVGRef }; + + useEffect(() => { + if (horizontalContainerRef.current) { + horizontalContainerRef.current.scrollTop = scroll; + } + }, [scroll]); + + return ( +
+ + + +
+ + + + +
+
+ ); +}; diff --git a/src/components/Grid/grid-body.tsx b/src/components/Grid/grid-body.tsx index 980cdcb..4f0e246 100644 --- a/src/components/Grid/grid-body.tsx +++ b/src/components/Grid/grid-body.tsx @@ -8,7 +8,6 @@ export type GridBodyProps = { dates: Date[]; gridWidth: number; rowHeight: number; - headerHeight: number; columnWidth: number; todayColor: string; }; @@ -16,14 +15,22 @@ export const GridBody: React.FC = ({ tasks, dates, rowHeight, - headerHeight, gridWidth, columnWidth, todayColor, }) => { - let y = headerHeight; + let y = 0; const gridRows: ReactChild[] = []; - const rowLines: ReactChild[] = []; + const rowLines: ReactChild[] = [ + , + ]; for (const task of tasks) { gridRows.push( = ({ = ({ - gridWidth, - headerHeight, -}) => { - return ( - - ); -}; diff --git a/src/components/Grid/grid.module.css b/src/components/Grid/grid.module.css index 18b2bf9..964303f 100644 --- a/src/components/Grid/grid.module.css +++ b/src/components/Grid/grid.module.css @@ -1,17 +1,11 @@ .gridRow { - fill: #ffffff; + fill: #fff; } .gridRow:nth-child(even) { fill: #f5f5f5; } -.gridHeader { - fill: #ffffff; - stroke: #e0e0e0; - stroke-width: 1.4; -} - .gridRowLine { stroke: #ebeff2; } diff --git a/src/components/Grid/grid.tsx b/src/components/Grid/grid.tsx index 2b77ecd..488cfa3 100644 --- a/src/components/Grid/grid.tsx +++ b/src/components/Grid/grid.tsx @@ -1,12 +1,10 @@ import React from "react"; import { GridBody, GridBodyProps } from "./grid-body"; -import { GridHeader, GridHeaderProps } from "./grid-header"; -export type GridProps = GridBodyProps & GridHeaderProps; +export type GridProps = GridBodyProps; export const Grid: React.FC = props => { return ( - ); diff --git a/src/components/Other/scroll.module.css b/src/components/Other/scroll.module.css new file mode 100644 index 0000000..c2dcbc5 --- /dev/null +++ b/src/components/Other/scroll.module.css @@ -0,0 +1,6 @@ +.scroll { + overflow: hidden auto; + margin-left: -17px; + width: 17px; + flex-shrink: 0; +} diff --git a/src/components/Other/scroll.tsx b/src/components/Other/scroll.tsx new file mode 100644 index 0000000..bec2f16 --- /dev/null +++ b/src/components/Other/scroll.tsx @@ -0,0 +1,19 @@ +import React, { SyntheticEvent } from "react"; +import styles from "./scroll.module.css"; + +export const Scroll: React.FC<{ + ganttHeight: number; + ganttFullHeight: number; + headerHeight: number; + onScroll: (event: SyntheticEvent) => void; +}> = ({ ganttHeight, ganttFullHeight, headerHeight, onScroll }) => { + return ( +
+
+
+ ); +}; diff --git a/src/components/Other/tooltip.tsx b/src/components/Other/tooltip.tsx index cb66fb0..14f2bd2 100644 --- a/src/components/Other/tooltip.tsx +++ b/src/components/Other/tooltip.tsx @@ -1,54 +1,63 @@ import React, { useRef, useEffect, useState } from "react"; import { Task } from "../../types/public-types"; +import { BarTask } from "../../types/bar-task"; import styles from "./tooltip.module.css"; export type TooltipProps = { x: number; - y: number; - task: Task; + svgHeight: number; + rowHeight: number; + task: BarTask; fontSize: string; fontFamily: string; - getTooltipContent?: ( - task: Task, - fontSize: string, - fontFamily: string - ) => JSX.Element; + TooltipContent: React.FC<{ + task: Task; + fontSize: string; + fontFamily: string; + }>; }; export const Tooltip: React.FC = ({ x, - y, + rowHeight, + svgHeight, task, fontSize, fontFamily, - - getTooltipContent = getStandardTooltipContent, + TooltipContent, }) => { const tooltipRef = useRef(null); const [toolWidth, setToolWidth] = useState(1000); - const [relatedY, setRelatedY] = useState(y); + const [relatedY, setRelatedY] = useState((task.index - 1) * rowHeight); useEffect(() => { if (tooltipRef.current) { - const height = - tooltipRef.current.offsetHeight + - tooltipRef.current.offsetHeight * 0.15; - setRelatedY(y - height); + const tooltipHeight = tooltipRef.current.offsetHeight; + const tooltipY = task.index * rowHeight + rowHeight; + if (tooltipHeight > tooltipY) { + setRelatedY(tooltipHeight * 0.5); + } else if (tooltipY + tooltipHeight > svgHeight) { + setRelatedY(svgHeight - tooltipHeight * 1.05); + } setToolWidth(tooltipRef.current.scrollWidth * 1.1); } - }, [tooltipRef, y]); + }, [tooltipRef, task]); return (
- {getTooltipContent(task, fontSize, fontFamily)} +
); }; -const getStandardTooltipContent = ( - task: Task, - fontSize: string, - fontFamily: string -) => { +export const StandardTooltipContent: React.FC<{ + task: Task; + fontSize: string; + fontFamily: string; +}> = ({ task, fontSize, fontFamily }) => { const style = { fontSize, fontFamily, @@ -66,9 +75,9 @@ const getStandardTooltipContent = ( (task.end.getTime() - task.start.getTime()) / (1000 * 60 * 60 * 24) )} day(s)`}

-

{`Progress: ${task.progress} %`}

+

+ {!!task.progress && `Progress: ${task.progress} %`} +

); }; diff --git a/src/components/task-list/task-list-header.module.css b/src/components/task-list/task-list-header.module.css new file mode 100644 index 0000000..c250354 --- /dev/null +++ b/src/components/task-list/task-list-header.module.css @@ -0,0 +1,23 @@ +.ganttTable { + display: table; + border-bottom: #e6e4e4 1px solid; + border-top: #e6e4e4 1px solid; + border-left: #e6e4e4 1px solid; +} + +.ganttTable_Header { + display: table-row; + list-style: none; +} + +.ganttTable_HeaderSeparator { + border-right: 1px solid rgb(196, 196, 196); + opacity: 1; + margin-left: -2px; +} + +.ganttTable_HeaderItem { + display: table-cell; + vertical-align: -webkit-baseline-middle; + vertical-align: middle; +} diff --git a/src/components/task-list/task-list-header.tsx b/src/components/task-list/task-list-header.tsx new file mode 100644 index 0000000..9be6702 --- /dev/null +++ b/src/components/task-list/task-list-header.tsx @@ -0,0 +1,65 @@ +import React from "react"; +import styles from "./task-list-header.module.css"; + +export const TaskListHeaderDefault: React.SFC<{ + headerHeight: number; + rowWidth: string; + fontFamily: string; + fontSize: string; +}> = ({ headerHeight, fontFamily, fontSize, rowWidth }) => { + return ( +
+
+
+  Name +
+
+
+  From +
+
+
+  To +
+
+
+ ); +}; diff --git a/src/components/task-list/task-list-table.module.css b/src/components/task-list/task-list-table.module.css new file mode 100644 index 0000000..1556a1d --- /dev/null +++ b/src/components/task-list/task-list-table.module.css @@ -0,0 +1,19 @@ +.taskListWrapper { + display: table; + border-bottom: #e6e4e4 1px solid; + border-left: #e6e4e4 1px solid; +} + +.taskListTableRow { + display: table-row; + text-overflow: ellipsis; +} + +.taskListTableRow:nth-of-type(even) { + background-color: #f5f5f5; +} + +.taskListCell { + display: table-cell; + vertical-align: middle; +} diff --git a/src/components/task-list/task-list-table.tsx b/src/components/task-list/task-list-table.tsx new file mode 100644 index 0000000..4e6fb20 --- /dev/null +++ b/src/components/task-list/task-list-table.tsx @@ -0,0 +1,64 @@ +import React from "react"; +import styles from "./task-list-table.module.css"; +import { Task } from "../../types/public-types"; + +export const TaskListTableDefault: React.SFC<{ + rowHeight: number; + rowWidth: string; + fontFamily: string; + fontSize: string; + locale: string; + tasks: Task[]; +}> = ({ rowHeight, rowWidth, tasks, fontFamily, fontSize, locale }) => { + const dateTimeOptions = { + weekday: "short", + year: "numeric", + month: "long", + day: "numeric", + }; + return ( +
+ {tasks.map(t => { + return ( +
+
+  {t.name} +
+
+  {t.start.toLocaleDateString(locale, dateTimeOptions)} +
+
+  {t.end.toLocaleDateString(locale, dateTimeOptions)} +
+
+ ); + })} +
+ ); +}; diff --git a/src/components/task-list/task-list.tsx b/src/components/task-list/task-list.tsx new file mode 100644 index 0000000..98d770d --- /dev/null +++ b/src/components/task-list/task-list.tsx @@ -0,0 +1,78 @@ +import React, { useEffect, useRef } from "react"; +import { Task } from "../../types/public-types"; + +export type TaskListProps = { + headerHeight: number; + rowWidth: string; + fontFamily: string; + fontSize: string; + rowHeight: number; + ganttHeight: number; + scroll: number; + locale: string; + tasks: Task[]; + horizontalContainerClass?: string; + TaskListHeader: React.SFC<{ + headerHeight: number; + rowWidth: string; + fontFamily: string; + fontSize: string; + }>; + TaskListTable: React.SFC<{ + rowHeight: number; + rowWidth: string; + fontFamily: string; + fontSize: string; + locale: string; + tasks: Task[]; + }>; +}; + +export const TaskList: React.FC = ({ + headerHeight, + fontFamily, + fontSize, + rowWidth, + rowHeight, + scroll, + tasks, + locale, + ganttHeight, + horizontalContainerClass, + TaskListHeader, + TaskListTable, +}) => { + const horizontalContainerRef = useRef(null); + useEffect(() => { + if (horizontalContainerRef.current) { + horizontalContainerRef.current.scrollTop = scroll; + } + }, [scroll]); + + const headerProps = { + headerHeight, + fontFamily, + fontSize, + rowWidth, + }; + const tableProps = { + rowHeight, + rowWidth, + fontFamily, + fontSize, + tasks, + locale, + }; + return ( +
+ +
+ +
+
+ ); +}; diff --git a/src/helpers/bar-helper.ts b/src/helpers/bar-helper.ts index 125d4e7..a86adc9 100644 --- a/src/helpers/bar-helper.ts +++ b/src/helpers/bar-helper.ts @@ -7,7 +7,6 @@ export const convertToBarTasks = ( columnWidth: number, rowHeight: number, barFill: number, - headerHeight: number, barCornerRadius: number, handleWidth: number, barProgressColor: string, @@ -31,7 +30,6 @@ export const convertToBarTasks = ( columnWidth, rowHeight, taskHeight, - headerHeight, barCornerRadius, handleWidth, barProgressColor, @@ -64,7 +62,6 @@ export const convertToBarTask = ( columnWidth: number, rowHeight: number, taskHeight: number, - headerHeight: number, barCornerRadius: number, handleWidth: number, barProgressColor: string, @@ -74,7 +71,7 @@ export const convertToBarTask = ( ): BarTask => { const x1 = taskXCoordinate(task.start, dates, dateDelta, columnWidth); const x2 = taskXCoordinate(task.end, dates, dateDelta, columnWidth); - const y = taskYCoordinate(index, rowHeight, taskHeight, headerHeight); + const y = taskYCoordinate(index, rowHeight, taskHeight); const styles = { backgroundColor: barBackgroundColor, @@ -125,10 +122,9 @@ export const taskXCoordinate = ( export const taskYCoordinate = ( index: number, rowHeight: number, - taskHeight: number, - headerHeight: number + taskHeight: number ) => { - const y = index * rowHeight + headerHeight + (rowHeight - taskHeight) / 2; + const y = index * rowHeight + (rowHeight - taskHeight) / 2; return y; }; diff --git a/src/index.tsx b/src/index.tsx index 7f7e625..47e8b63 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,4 @@ -export { Gantt } from "./components/Gantt/gantt"; +export { Gantt } from "./components/gantt/gantt"; export { ViewMode } from "./types/public-types"; export type { GanttProps, diff --git a/src/styles.css b/src/styles.css index aa68035..d83ae7d 100644 --- a/src/styles.css +++ b/src/styles.css @@ -8,6 +8,14 @@ outline: none; } +.GanttBar-handle { + fill: #ddd; + cursor: ew-resize; + opacity: 0; + visibility: hidden; + transition: opacity 0.3s ease; +} + .GanttBar-wrapper:hover .GanttBar-handle { visibility: visible; opacity: 1; @@ -31,14 +39,6 @@ text-anchor: start; } -.GanttBar-handle { - fill: #ddd; - cursor: ew-resize; - opacity: 0; - visibility: hidden; - transition: opacity 0.3s ease; -} - .GanttCalendar-bottomText { text-anchor: middle; fill: #333; @@ -66,7 +66,7 @@ } .GanttGrid-row { - fill: #ffffff; + fill: #fff; } .GanttGrid-row:nth-child(even) { @@ -74,7 +74,7 @@ } .GanttGrid-header { - fill: #ffffff; + fill: #fff; stroke: #e0e0e0; stroke-width: 1.4; } diff --git a/src/types/public-types.ts b/src/types/public-types.ts index 424e133..7872b2f 100644 --- a/src/types/public-types.ts +++ b/src/types/public-types.ts @@ -47,7 +47,9 @@ export interface DisplayOption { export interface StylingOption { headerHeight?: number; columnWidth?: number; + listCellWidth?: string; rowHeight?: number; + ganttHeight?: number; barCornerRadius?: number; handleWidth?: number; fontFamily?: string; @@ -64,11 +66,25 @@ export interface StylingOption { arrowColor?: string; arrowIndent?: number; todayColor?: string; - getTooltipContent?: ( - task: Task, - fontSize: string, - fontFamily: string - ) => JSX.Element; + TooltipContent?: React.FC<{ + task: Task; + fontSize: string; + fontFamily: string; + }>; + TaskListHeader?: React.SFC<{ + headerHeight: number; + rowWidth: string; + fontFamily: string; + fontSize: string; + }>; + TaskListTable?: React.SFC<{ + rowHeight: number; + rowWidth: string; + fontFamily: string; + fontSize: string; + locale: string; + tasks: Task[]; + }>; } export interface GanttProps extends EventOption, DisplayOption, StylingOption {