moved to create-react-library

This commit is contained in:
unknown 2020-08-05 08:14:22 +03:00
parent d27d58d44c
commit 09520361c2
65 changed files with 40755 additions and 16260 deletions

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

6
.eslintignore Normal file
View File

@ -0,0 +1,6 @@
build/
dist/
node_modules/
.snapshots/
*.min.js
*.css

37
.eslintrc Normal file
View File

@ -0,0 +1,37 @@
{
"parser": "@typescript-eslint/parser",
"extends": [
"standard",
"standard-react",
"plugin:prettier/recommended",
"prettier/standard",
"prettier/react",
"plugin:@typescript-eslint/eslint-recommended"
],
"plugins": ["@typescript-eslint"],
"env": {
"node": true
},
"parserOptions": {
"ecmaVersion": 2020,
"ecmaFeatures": {
"legacyDecorators": true,
"jsx": true
}
},
"settings": {
"react": {
"version": "16"
}
},
"rules": {
"space-before-function-paren": 0,
"react/prop-types": 0,
"react/jsx-handler-names": 0,
"react/jsx-fragments": 0,
"react/no-unused-prop-types": 0,
"import/export": 0,
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error"
}
}

View File

@ -1,42 +0,0 @@
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Begin CI...
uses: actions/checkout@v2
- name: Use Node 12
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: Use cached node_modules
uses: actions/cache@v1
with:
path: node_modules
key: nodeModules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
nodeModules-
- name: Install dependencies
run: yarn install --frozen-lockfile
env:
CI: true
- name: Lint
run: yarn lint
env:
CI: true
- name: Test
run: yarn test --ci --coverage --maxWorkers=2
env:
CI: true
- name: Build
run: yarn build
env:
CI: true

View File

@ -1,27 +0,0 @@
name: publish
on:
release:
types: [published, edited]
jobs:
release:
name: publish
runs-on: ubuntu-latest
steps:
- name: Begin CI...
uses: actions/checkout@v2
- name: Use Node 12
uses: actions/setup-node@v1
with:
node-version: 12.x
registry-url: https://registry.npmjs.org
- name: NPM Publish
run: |
npm ci
npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}

23
.gitignore vendored
View File

@ -1,5 +1,22 @@
*.log
.DS_Store
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
node_modules
.cache
# builds
build
dist
.rpt2_cache
# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

12
.prettierrc Normal file
View File

@ -0,0 +1,12 @@
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"trailingComma": "es5",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "avoid",
"endOfLine": "auto"
}

4
.travis.yml Normal file
View File

@ -0,0 +1,4 @@
language: node_js
node_js:
- 12
- 10

View File

@ -1,5 +0,0 @@
{
"cSpell.words": [
"callout"
]
}

21
LICENSE
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2020 Maksym Vikarii https://github.com/MaTeMaTuK
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -13,6 +13,7 @@ npm install gantt-task-react
## How to use it
```javascript
import { Gantt, Task, EventOption, StylingOption, ViewMode, DisplayOption } from 'gantt-task-react';
let tasks: Task[] = [
{
start: new Date(2020, 1, 1),

View File

@ -1,3 +0,0 @@
node_modules
.cache
dist

5
example/README.md Normal file
View File

@ -0,0 +1,5 @@
This example was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
It is linked to the gantt-task-react package in the parent directory for development purposes.
You can run `npm install` and then `npm start` to test your package.

View File

@ -1,15 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Playground</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="root"></div>
<script src="./index.tsx"></script>
</body>
</html>

View File

@ -1,259 +0,0 @@
import 'react-app-polyfill/ie11';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {
Gantt,
Task,
EventOption,
StylingOption,
ViewMode,
DisplayOption,
} from '../src/index';
//Init
const App = () => {
const currentDate = new Date();
const [view, setView] = React.useState<ViewMode>(ViewMode.Day);
let tasks: Task[] = [
{
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 1),
end: new Date(
currentDate.getFullYear(),
currentDate.getMonth(),
2,
12,
28
),
name: 'Idea',
id: 'Task 0',
progress: 45,
isDisabled: true,
},
{
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 2),
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 4, 0, 0),
name: 'Research',
id: 'Task 1',
progress: 25,
dependencies: ['Task 0'],
},
{
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 4),
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 8, 0, 0),
name: 'Discussion with team',
id: 'Task 2',
progress: 10,
dependencies: ['Task 1'],
},
{
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 8),
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 9, 0, 0),
name: 'Developing',
id: 'Task 3',
progress: 2,
dependencies: ['Task 2'],
},
{
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 8),
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 10),
name: 'Review',
id: 'Task 4',
progress: 70,
dependencies: ['Task 2'],
},
{
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 15),
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 26),
name: 'Release',
id: 'Task 6',
progress: currentDate.getMonth(),
dependencies: ['Task 4'],
styles: { progressColor: '#ffbb54', progressSelectedColor: '#ff9e0d' },
},
];
let onTaskChange = (task: Task) => {
console.log('On date change');
};
let onTaskDelete = (task: Task) => {
const conf = confirm('Are you sure?');
if (!conf) throw 'No del';
};
let onProgressChange = (task: Task) => {
console.log('On progress change');
};
let onDblClick = (task: Task) => {
alert('On Double Click event');
};
return (
<div>
<ViewSwitcher onViewChange={viewMode => setView(viewMode)} />
<GanttTableExample
tasks={tasks}
viewMode={view}
onDateChange={onTaskChange}
onTaskDelete={onTaskDelete}
onProgressChange={onProgressChange}
onDoubleClick={onDblClick}
/>
</div>
);
};
//Gantt with Custom table example
type GanttTableExampleProps = { tasks: Task[] } & EventOption & DisplayOption;
export const GanttTableExample: React.SFC<GanttTableExampleProps> = 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;
}
const [tasks, setTasks] = React.useState(props.tasks);
const onTaskDateChange = async (task: Task) => {
if (props.onDateChange) {
try {
await props.onDateChange(task);
} catch (e) {
throw e;
}
setTasks(tasks.map(t => (t.id === task.id ? task : t)));
}
};
const onTaskProgressChange = async (task: Task) => {
if (props.onProgressChange) {
try {
await props.onProgressChange(task);
} catch (e) {
setTasks(props.tasks.slice());
throw e;
}
setTasks(tasks.map(t => (t.id === task.id ? task : t)));
}
};
const onTaskItemDelete = async (task: Task) => {
if (props.onTaskDelete) {
await props.onTaskDelete(task);
setTasks(tasks.filter(t => t.id !== task.id));
}
};
return (
<div className="Wrapper">
<div
className="GanttTable"
style={{
fontFamily: options.fontFamily,
fontSize: options.fontSize,
}}
>
<div
className="GanttTable-header"
style={{
height: options.headerHeight,
}}
>
<div
className="GanttTable-headerItem"
style={{
minWidth: gridColumnWidth,
}}
>
<span role="img" aria-label="fromDate" className="GanttTable-icon">
📃
</span>
Name
</div>
<div
className="GanttTable-headerItem"
style={{
minWidth: gridColumnWidth,
}}
>
<span role="img" aria-label="fromDate" className="GanttTable-icon">
📅
</span>
From
</div>
<div
className="GanttTable-headerItem"
style={{
minWidth: gridColumnWidth,
}}
>
<span role="img" aria-label="toDate" className="GanttTable-icon">
📅
</span>
To
</div>
</div>
{tasks.map(t => {
return (
<div
className="GanttTable-row"
style={{ height: options.rowHeight }}
>
<div className="GanttTable-cell">{t.name}</div>
<div className="GanttTable-cell">{t.start.toDateString()}</div>
<div className="GanttTable-cell">{t.end.toDateString()}</div>
</div>
);
})}
</div>
<div style={{ overflowX: 'scroll' }}>
<Gantt
{...options}
{...props}
tasks={tasks}
onDateChange={onTaskDateChange}
onTaskDelete={onTaskItemDelete}
onProgressChange={onTaskProgressChange}
/>
</div>
</div>
);
};
type ViewSwitcherProps = {
onViewChange: (viewMode: ViewMode) => void;
};
const ViewSwitcher: React.SFC<ViewSwitcherProps> = ({ onViewChange }) => {
return (
<div className="ViewContainer">
<button
className="Button"
onClick={() => onViewChange(ViewMode.QuarterDay)}
>
Quarter of Day
</button>
<button className="Button" onClick={() => onViewChange(ViewMode.HalfDay)}>
Half of Day
</button>
<button className="Button" onClick={() => onViewChange(ViewMode.Day)}>
Day
</button>
<button className="Button" onClick={() => onViewChange(ViewMode.Week)}>
Week
</button>
<button className="Button" onClick={() => onViewChange(ViewMode.Month)}>
Month
</button>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));

27343
example/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +1,44 @@
{
"name": "example",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"name": "gantt-task-react-example",
"homepage": ".",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "parcel index.html",
"build": "parcel build index.html"
"start": "node ../node_modules/react-scripts/bin/react-scripts.js start",
"build": "node ../node_modules/react-scripts/bin/react-scripts.js build",
"test": "node ../node_modules/react-scripts/bin/react-scripts.js test",
"eject": "node ../node_modules/react-scripts/bin/react-scripts.js eject"
},
"dependencies": {
"react-app-polyfill": "^1.0.0"
},
"alias": {
"react": "../node_modules/react",
"react-dom": "../node_modules/react-dom/profiling",
"scheduler/tracing": "../node_modules/scheduler/tracing-profiling"
"@testing-library/jest-dom": "file:../node_modules/@testing-library/jest-dom",
"@testing-library/react": "file:../node_modules/@testing-library/react",
"@testing-library/user-event": "file:../node_modules/@testing-library/user-event",
"@types/jest": "file:../node_modules/@types/jest",
"@types/node": "file:../node_modules/@types/node",
"@types/react": "file:../node_modules/@types/react",
"@types/react-dom": "file:../node_modules/@types/react-dom",
"react": "file:../node_modules/react",
"react-dom": "file:../node_modules/react-dom",
"react-scripts": "file:../node_modules/react-scripts",
"typescript": "file:../node_modules/typescript",
"gantt-task-react": "file:.."
},
"devDependencies": {
"@types/react": "^16.9.11",
"@types/react-dom": "^16.8.4",
"parcel": "^1.12.3",
"typescript": "^3.4.5"
"@babel/plugin-syntax-object-rest-spread": "^7.8.3"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

BIN
example/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

48
example/public/index.html Normal file
View File

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>gantt-task-react</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@ -0,0 +1,15 @@
{
"short_name": "gantt-task-react",
"name": "gantt-task-react",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

9
example/src/App.test.tsx Normal file
View File

@ -0,0 +1,9 @@
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
it('renders without crashing', () => {
const div = document.createElement('div')
ReactDOM.render(<App />, div)
ReactDOM.unmountComponentAtNode(div)
})

101
example/src/App.tsx Normal file
View File

@ -0,0 +1,101 @@
import React from "react";
import "gantt-task-react/dist/index.css";
import { Task, ViewMode } 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>(ViewMode.Day);
let tasks: Task[] = [
{
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 1),
end: new Date(
currentDate.getFullYear(),
currentDate.getMonth(),
2,
12,
28
),
name: "Idea",
id: "Task 0",
progress: 45,
isDisabled: true,
},
{
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 2),
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 4, 0, 0),
name: "Research",
id: "Task 1",
progress: 25,
dependencies: ["Task 0"],
},
{
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 4),
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 8, 0, 0),
name: "Discussion with team",
id: "Task 2",
progress: 10,
dependencies: ["Task 1"],
},
{
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 8),
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 9, 0, 0),
name: "Developing",
id: "Task 3",
progress: 2,
dependencies: ["Task 2"],
},
{
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 8),
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 10),
name: "Review",
id: "Task 4",
progress: 70,
dependencies: ["Task 2"],
},
{
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 15),
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 26),
name: "Release",
id: "Task 6",
progress: currentDate.getMonth(),
dependencies: ["Task 4"],
styles: { progressColor: "#ffbb54", progressSelectedColor: "#ff9e0d" },
},
];
let onTaskChange = (task: Task) => {
console.log("On date change Id:" + task.id);
};
let onTaskDelete = (task: Task) => {
const conf = window.confirm("Are you sure?");
if (!conf) throw "No del Id:" + task.id;
};
let onProgressChange = (task: Task) => {
console.log("On progress change Id:" + task.id);
};
let onDblClick = (task: Task) => {
alert("On Double Click event Id:" + task.id);
};
return (
<div>
<ViewSwitcher onViewChange={viewMode => setView(viewMode)} />
<GanttTableExample
tasks={tasks}
viewMode={view}
onDateChange={onTaskChange}
onTaskDelete={onTaskDelete}
onProgressChange={onProgressChange}
onDoubleClick={onDblClick}
/>
</div>
);
};
export default App;

View File

@ -0,0 +1,135 @@
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<GanttTableExampleProps> = 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;
}
const [tasks, setTasks] = React.useState(props.tasks);
const onTaskDateChange = async (task: Task) => {
if (props.onDateChange) {
try {
await props.onDateChange(task);
} catch (e) {
throw e;
}
setTasks(tasks.map(t => (t.id === task.id ? task : t)));
}
};
const onTaskProgressChange = async (task: Task) => {
if (props.onProgressChange) {
try {
await props.onProgressChange(task);
} catch (e) {
setTasks(props.tasks.slice());
throw e;
}
setTasks(tasks.map(t => (t.id === task.id ? task : t)));
}
};
const onTaskItemDelete = async (task: Task) => {
if (props.onTaskDelete) {
await props.onTaskDelete(task);
setTasks(tasks.filter(t => t.id !== task.id));
}
};
return (
<div className="Wrapper">
<div
className="GanttTable"
style={{
fontFamily: options.fontFamily,
fontSize: options.fontSize,
}}
>
<div
className="GanttTable-header"
style={{
height: options.headerHeight,
}}
>
<div
className="GanttTable-headerItem"
style={{
minWidth: gridColumnWidth,
}}
>
<span role="img" aria-label="fromDate" className="GanttTable-icon">
📃
</span>
Name
</div>
<div
className="GanttTable-headerItem"
style={{
minWidth: gridColumnWidth,
}}
>
<span role="img" aria-label="fromDate" className="GanttTable-icon">
📅
</span>
From
</div>
<div
className="GanttTable-headerItem"
style={{
minWidth: gridColumnWidth,
}}
>
<span role="img" aria-label="toDate" className="GanttTable-icon">
📅
</span>
To
</div>
</div>
{tasks.map(t => {
return (
<div
className="GanttTable-row"
style={{ height: options.rowHeight }}
>
<div className="GanttTable-cell">{t.name}</div>
<div className="GanttTable-cell">{t.start.toDateString()}</div>
<div className="GanttTable-cell">{t.end.toDateString()}</div>
</div>
);
})}
</div>
<div style={{ overflowX: "scroll" }}>
<Gantt
{...options}
{...props}
tasks={tasks}
onDateChange={onTaskDateChange}
onTaskDelete={onTaskItemDelete}
onProgressChange={onTaskProgressChange}
/>
</div>
</div>
);
};

View File

@ -0,0 +1,32 @@
import React from "react";
import "gantt-task-react/dist/index.css";
import { ViewMode } from "gantt-task-react";
type ViewSwitcherProps = {
onViewChange: (viewMode: ViewMode) => void;
};
export const ViewSwitcher: React.SFC<ViewSwitcherProps> = ({
onViewChange,
}) => {
return (
<div className="ViewContainer">
<button
className="Button"
onClick={() => onViewChange(ViewMode.QuarterDay)}
>
Quarter of Day
</button>
<button className="Button" onClick={() => onViewChange(ViewMode.HalfDay)}>
Half of Day
</button>
<button className="Button" onClick={() => onViewChange(ViewMode.Day)}>
Day
</button>
<button className="Button" onClick={() => onViewChange(ViewMode.Week)}>
Week
</button>
<button className="Button" onClick={() => onViewChange(ViewMode.Month)}>
Month
</button>
</div>
);
};

7
example/src/index.tsx Normal file
View File

@ -0,0 +1,7 @@
import './index.css'
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))

1
example/src/react-app-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';

View File

@ -1,19 +1,38 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": false,
"target": "es5",
"module": "commonjs",
"jsx": "react",
"outDir": "dist",
"module": "esnext",
"lib": [
"dom",
"esnext"
],
"moduleResolution": "node",
"noImplicitAny": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"removeComments": true,
"strictNullChecks": true,
"preserveConstEnums": true,
"jsx": "react",
"sourceMap": true,
"lib": ["es2015", "es2016", "dom"],
"baseUrl": ".",
"types": ["node"]
}
"declaration": true,
"esModuleInterop": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"allowSyntheticDefaultImports": true,
"target": "es5",
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
},
"include": [
"src"
],
"exclude": [
"node_modules",
"build"
]
}

View File

@ -1,5 +0,0 @@
module.exports = {
moduleNameMapper: {
'^.+\\.(css|less|scss)$': 'babel-jest',
},
};

11208
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,58 +1,63 @@
{
"version": "0.0.2",
"license": "MIT",
"name": "gantt-task-react",
"author": "Maksym Vikarii <maksym.vikarii@gmail.com>",
"homepage": "https://github.com/MaTeMaTuK/gantt-task-react",
"module": "dist/gantt-task-react.esm.js",
"version": "0.0.3",
"description": "Interactive Gantt Chart for React with TypeScript.",
"author": "MaTeMaTuK",
"license": "MIT",
"repository": "MaTeMaTuK/gantt-task-react",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"files": [
"dist",
"src"
],
"module": "dist/index.modern.js",
"source": "src/index.tsx",
"engines": {
"node": ">=10"
},
"scripts": {
"start": "tsdx watch",
"build": "tsdx build",
"test": "tsdx test --passWithNoTests",
"lint": "tsdx lint",
"prepare": "tsdx build"
"build": "microbundle-crl --no-compress --format modern,cjs",
"start": "microbundle-crl watch --no-compress --format modern,cjs",
"prepare": "run-s build",
"test": "run-s test:unit test:lint test:build",
"test:build": "run-s build",
"test:lint": "eslint --ext .tsx src/**/*",
"test:unit": "cross-env CI=1 react-scripts test --env=jsdom",
"test:watch": "react-scripts test --env=jsdom",
"predeploy": "cd example && npm install && npm run build",
"deploy": "gh-pages -d example/build"
},
"peerDependencies": {
"react": ">=16"
},
"husky": {
"hooks": {
"pre-commit": "tsdx lint"
}
},
"prettier": {
"printWidth": 80,
"semi": true,
"singleQuote": true,
"trailingComma": "es5"
"react": "^16.0.0"
},
"devDependencies": {
"@types/react": "^16.9.41",
"@types/react-dom": "^16.9.8",
"husky": "^4.2.5",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"@types/jest": "^25.1.4",
"@types/node": "^12.12.38",
"@types/react": "^16.9.27",
"@types/react-dom": "^16.9.7",
"@typescript-eslint/eslint-plugin": "^2.26.0",
"@typescript-eslint/parser": "^2.26.0",
"microbundle-crl": "^0.13.10",
"babel-eslint": "^10.0.3",
"cross-env": "^7.0.2",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.7.0",
"eslint-config-standard": "^14.1.0",
"eslint-config-standard-react": "^9.2.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.17.0",
"eslint-plugin-standard": "^4.0.1",
"gh-pages": "^2.2.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.4",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"tsdx": "^0.13.2",
"tslib": "^2.0.0",
"typescript": "^3.9.6"
"react-scripts": "^3.4.1",
"typescript": "^3.7.5"
},
"dependencies": {
"autoprefixer": "^9.8.5",
"cssnano": "^4.1.10",
"rollup-plugin-postcss": "^3.1.2"
},
"keywords": [
"gantt",
"typescript",
"react"
"files": [
"dist"
]
}

5
src/.eslintrc Normal file
View File

@ -0,0 +1,5 @@
{
"env": {
"jest": true
}
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import '../../style.css';
import React from "react";
import styles from "./bar.module.css";
type BarDateHandleProps = {
x: number;
@ -23,10 +23,10 @@ export const BarDateHandle: React.FC<BarDateHandleProps> = ({
y={y}
width={width}
height={height}
className="GanttBar-handle"
className={styles.barHandle}
ry={barCornerRadius}
rx={barCornerRadius}
onMouseDown={onMouseDown}
></rect>
/>
);
};

View File

@ -1,5 +1,5 @@
import React, { useRef, useState, useEffect } from 'react';
import '../../style.css';
import React, { useRef, useState, useEffect } from "react";
import style from "./bar.module.css";
type BarDisplayProps = {
x: number;
@ -66,7 +66,7 @@ export const BarDisplay: React.FC<BarDisplayProps> = ({
ry={barCornerRadius}
rx={barCornerRadius}
fill={getBarColor()}
className="GanttBar"
className={style.barBackground}
/>
<rect
x={x}
@ -80,9 +80,11 @@ export const BarDisplay: React.FC<BarDisplayProps> = ({
<text
x={getX()}
y={y + height * 0.5}
className={`GanttBar-label ${
isTextInside ? '' : 'GanttBar-label-outside'
}`}
className={
isTextInside
? style.barLabel
: style.barLabel && style.barLabelOutside
}
ref={textRef}
>
{text}

View File

@ -1,5 +1,5 @@
import React from 'react';
import '../../style.css';
import React from "react";
import styles from "./bar.module.css";
type BarProgressHandleProps = {
progressPoint: string;
@ -11,9 +11,9 @@ export const BarProgressHandle: React.FC<BarProgressHandleProps> = ({
}) => {
return (
<polygon
className="GanttBar-handle"
className={styles.barHandle}
points={progressPoint}
onMouseDown={onMouseDown}
></polygon>
/>
);
};

View File

@ -0,0 +1,40 @@
.barWrapper {
cursor: pointer;
outline: none;
}
.barWrapper:hover .barHandle {
visibility: visible;
opacity: 1;
}
.barHandle {
fill: #ddd;
cursor: ew-resize;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease;
}
.barLabel {
fill: #fff;
text-anchor: middle;
font-weight: lighter;
dominant-baseline: central;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
}
.barLabelOutside {
fill: #555;
text-anchor: start;
}
.barBackground {
user-select: none;
stroke-width: 0;
}

View File

@ -1,15 +1,15 @@
import React, { useState } from 'react';
import { Task } from '../../types/public-types';
import { BarProgressHandle } from './bar-progress-handle';
import { BarDateHandle } from './bar-date-handle';
import { BarDisplay } from './bar-display';
import { BarTask } from '../../types/bar-task';
import { BarAction } from '../Gantt/gantt-content';
import React, { useState } from "react";
import { Task } from "../../types/public-types";
import { BarProgressHandle } from "./bar-progress-handle";
import { BarDateHandle } from "./bar-date-handle";
import { BarDisplay } from "./bar-display";
import { BarTask } from "../../types/bar-task";
import { BarAction } from "../Gantt/gantt-content";
import {
progressWithByParams,
getProgressPoint,
} from '../../helpers/bar-helper';
import '../../style.css';
} from "../../helpers/bar-helper";
import styles from "./bar.module.css";
export type BarProps = {
task: BarTask;
@ -51,7 +51,7 @@ export const Bar: React.FC<BarProps> = ({
return (
<g
className="GanttBar-wrapper"
className={styles.barWrapper}
onDoubleClick={() => {
!!onDoubleClick && onDoubleClick(task);
}}
@ -60,10 +60,10 @@ export const Bar: React.FC<BarProps> = ({
handleButtonSVGEvents(e, task);
}}
onMouseEnter={e => {
handleMouseEvents(e, 'mouseenter', task);
handleMouseEvents(e, "mouseenter", task);
}}
onMouseLeave={e => {
handleMouseEvents(e, 'mouseleave', task);
handleMouseEvents(e, "mouseleave", task);
}}
onFocus={() => setIsSelected(true)}
onBlur={() => setIsSelected(false)}
@ -81,12 +81,12 @@ export const Bar: React.FC<BarProps> = ({
styles={task.styles}
isSelected={isSelected}
onMouseDown={e => {
isDateChangeable && handleMouseEvents(e, 'move', task);
isDateChangeable && handleMouseEvents(e, "move", task);
}}
/>
<g className="handleGroup">
{isDateChangeable && (
<>
<g>
{/* left */}
<BarDateHandle
x={task.x1 + 1}
@ -95,7 +95,7 @@ export const Bar: React.FC<BarProps> = ({
height={task.height - 2}
barCornerRadius={task.barCornerRadius}
onMouseDown={e => {
handleMouseEvents(e, 'start', task);
handleMouseEvents(e, "start", task);
}}
/>
{/* right */}
@ -106,16 +106,16 @@ export const Bar: React.FC<BarProps> = ({
height={task.height - 2}
barCornerRadius={task.barCornerRadius}
onMouseDown={e => {
handleMouseEvents(e, 'end', task);
handleMouseEvents(e, "end", task);
}}
/>
</>
</g>
)}
{isProgressChangeable && (
<BarProgressHandle
progressPoint={progressPoint}
onMouseDown={e => {
handleMouseEvents(e, 'progress', task);
handleMouseEvents(e, "progress", task);
}}
/>
)}

View File

@ -0,0 +1,25 @@
.calendarBottomText {
text-anchor: middle;
fill: #333;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
}
.calendarTopTick {
stroke: #e6e4e4;
}
.calendarTopText {
text-anchor: middle;
fill: #555;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
}

View File

@ -1,11 +1,12 @@
import React, { ReactChild } from 'react';
import { ViewMode } from '../../types/public-types';
import { TopPartOfCalendar } from './top-part-of-calendar';
import React, { ReactChild } from "react";
import { ViewMode } from "../../types/public-types";
import { TopPartOfCalendar } from "./top-part-of-calendar";
import {
getLocaleMonth,
getWeekNumberISO8601,
} from '../../helpers/date-helper';
import '../../style.css';
} from "../../helpers/date-helper";
import styles from "./calendar.module.css";
export type CalendarProps = {
dates: Date[];
locale: string;
@ -26,25 +27,25 @@ export const Calendar: React.FC<CalendarProps> = ({
fontSize,
}) => {
const getCalendarValuesForMonth = () => {
let topValues: ReactChild[] = [];
let bottomValues: ReactChild[] = [];
const topValues: ReactChild[] = [];
const bottomValues: ReactChild[] = [];
const topDefaultWidth = columnWidth * 6;
const topDefaultHeight = headerHeight * 0.5;
for (let i = 0; i < dates.length; i++) {
const date = dates[i];
let bottomValue = getLocaleMonth(date, locale);
const bottomValue = getLocaleMonth(date, locale);
bottomValues.push(
<text
key={bottomValue + date.getFullYear()}
y={headerHeight * 0.8}
x={columnWidth * i + columnWidth * 0.5}
className="GanttCalendar-bottomText"
className={styles.calendarBottomText}
>
{bottomValue}
</text>
);
if (i === 0 || date.getFullYear() !== dates[i - 1].getFullYear()) {
let topValue = date.getFullYear().toString();
const topValue = date.getFullYear().toString();
topValues.push(
<TopPartOfCalendar
key={topValue}
@ -64,13 +65,13 @@ export const Calendar: React.FC<CalendarProps> = ({
};
const getCalendarValuesForWeek = () => {
let topValues: ReactChild[] = [];
let bottomValues: ReactChild[] = [];
const topValues: ReactChild[] = [];
const bottomValues: ReactChild[] = [];
let weeksCount: number = 0;
const topDefaultHeight = headerHeight * 0.5;
for (let i = dates.length - 1; i >= 0; i--) {
const date = dates[i];
let topValue = '';
let topValue = "";
if (i === 0 || date.getMonth() !== dates[i - 1].getMonth()) {
// top
topValue = `${getLocaleMonth(date, locale)}, ${date.getFullYear()}`;
@ -83,7 +84,7 @@ export const Calendar: React.FC<CalendarProps> = ({
key={date.getTime()}
y={headerHeight * 0.8}
x={columnWidth * i}
className="GanttCalendar-bottomText"
className={styles.calendarBottomText}
>
{bottomValue}
</text>
@ -112,8 +113,8 @@ export const Calendar: React.FC<CalendarProps> = ({
};
const getCalendarValuesForDay = () => {
let topValues: ReactChild[] = [];
let bottomValues: ReactChild[] = [];
const topValues: ReactChild[] = [];
const bottomValues: ReactChild[] = [];
const topDefaultHeight = headerHeight * 0.5;
for (let i = 0; i < dates.length; i++) {
const date = dates[i];
@ -124,7 +125,7 @@ export const Calendar: React.FC<CalendarProps> = ({
key={date.getTime()}
y={headerHeight * 0.8}
x={columnWidth * i + columnWidth * 0.5}
className="GanttCalendar-bottomText"
className={styles.calendarBottomText}
>
{bottomValue}
</text>
@ -133,7 +134,7 @@ export const Calendar: React.FC<CalendarProps> = ({
i + 1 !== dates.length &&
date.getMonth() !== dates[i + 1].getMonth()
) {
let topValue = getLocaleMonth(date, locale);
const topValue = getLocaleMonth(date, locale);
topValues.push(
<TopPartOfCalendar
@ -152,15 +153,15 @@ export const Calendar: React.FC<CalendarProps> = ({
};
const getCalendarValuesForOther = () => {
let topValues: ReactChild[] = [];
let bottomValues: ReactChild[] = [];
let ticks = viewMode === ViewMode.HalfDay ? 2 : 4;
const topValues: ReactChild[] = [];
const bottomValues: ReactChild[] = [];
const ticks = viewMode === ViewMode.HalfDay ? 2 : 4;
const topDefaultHeight = headerHeight * 0.5;
for (let i = 0; i < dates.length; i++) {
const date = dates[i];
const bottomValue = Intl.DateTimeFormat(locale, {
hour: 'numeric',
hour: "numeric",
}).format(date);
bottomValues.push(
@ -168,7 +169,7 @@ export const Calendar: React.FC<CalendarProps> = ({
key={date.getTime()}
y={headerHeight * 0.8}
x={columnWidth * i}
className="GanttCalendar-bottomText"
className={styles.calendarBottomText}
fontFamily={fontFamily}
>
{bottomValue}

View File

@ -1,5 +1,5 @@
import React from 'react';
import '../../style.css';
import React from "react";
import styles from "./calendar.module.css";
type TopPartOfCalendarProps = {
value: string;
@ -19,23 +19,23 @@ export const TopPartOfCalendar: React.FC<TopPartOfCalendarProps> = ({
yText,
}) => {
return (
<>
<g className="calendarTop">
<line
x1={x1Line}
y1={y1Line}
x2={x1Line}
y2={y2Line}
className="GanttCalendar-topTick"
key={value + 'line'}
></line>
className={styles.calendarTopTick}
key={value + "line"}
/>
<text
key={value + 'text'}
key={value + "text"}
y={yText}
x={xText}
className="GanttCalendar-topText"
className={styles.calendarTopText}
>
{value}
</text>
</>
</g>
);
};

View File

@ -1,8 +1,8 @@
import React, { useState, useEffect } from 'react';
import { Task, EventOption } from '../../types/public-types';
import { Bar } from '../Bar/bar';
import { BarTask } from '../../types/bar-task';
import { Arrow } from '../Other/arrow';
import React, { useState, useEffect } from "react";
import { Task, EventOption } from "../../types/public-types";
import { Bar } from "../Bar/bar";
import { BarTask } from "../../types/bar-task";
import { Arrow } from "../Other/arrow";
import {
convertToBarTasks,
progressByX,
@ -10,8 +10,9 @@ import {
endByX,
moveByX,
dateByX,
} from '../../helpers/bar-helper';
import { Tooltip } from '../Other/tooltip';
} from "../../helpers/bar-helper";
import { Tooltip } from "../Other/tooltip";
export interface GanttTask extends Task {
x1: number;
x2: number;
@ -46,13 +47,13 @@ export type GanttContentProps = {
} & EventOption;
export type BarAction =
| 'progress'
| 'end'
| 'start'
| 'move'
| 'mouseenter'
| 'mouseleave'
| '';
| "progress"
| "end"
| "start"
| "move"
| "mouseenter"
| "mouseleave"
| "";
type BarEvent = {
action: BarAction;
@ -84,7 +85,7 @@ export const GanttContent: React.FC<GanttContentProps> = ({
getTooltipContent,
}) => {
const [barEvent, setBarEvent] = useState<BarEvent>({
action: '',
action: "",
selectedTask: null,
});
const [isSVGListen, setIsSVGListen] = useState(false);
@ -146,19 +147,20 @@ export const GanttContent: React.FC<GanttContentProps> = ({
* Method handles event in real time(mousemove) and on finish(mouseup)
*/
const handleMouseSVGChangeEventsSubscribe = async (event: MouseEvent) => {
if (!barEvent.selectedTask || !barEvent.action) return;
if (!barEvent.selectedTask || !barEvent.action || !svg || !svg.current)
return;
const selectedTask = barEvent.selectedTask;
const changedTask = { ...selectedTask } as BarTask;
switch (event.type) {
// On Event changing
case 'mousemove': {
case "mousemove": {
switch (barEvent.action) {
case 'progress':
case "progress":
changedTask.progress = progressByX(event.offsetX, selectedTask);
break;
case 'start':
let newX1 = startByX(event.offsetX, xStep, selectedTask);
case "start": {
const newX1 = startByX(event.offsetX, xStep, selectedTask);
changedTask.x1 = newX1;
changedTask.start = dateByX(
newX1,
@ -168,8 +170,9 @@ export const GanttContent: React.FC<GanttContentProps> = ({
timeStep
);
break;
case 'end':
let newX2 = endByX(event.offsetX, xStep, selectedTask);
}
case "end": {
const newX2 = endByX(event.offsetX, xStep, selectedTask);
changedTask.x2 = newX2;
changedTask.end = dateByX(
newX2,
@ -179,7 +182,8 @@ export const GanttContent: React.FC<GanttContentProps> = ({
timeStep
);
break;
case 'move':
}
case "move": {
const [newMoveX1, newMoveX2] = moveByX(
event.offsetX - initEventX1Delta,
xStep,
@ -203,6 +207,8 @@ export const GanttContent: React.FC<GanttContentProps> = ({
changedTask.x2 = newMoveX2;
break;
}
}
// Update internal state
setBarTasks(
barTasks.map(t => (t.id === changedTask.id ? changedTask : t))
@ -212,18 +218,18 @@ export const GanttContent: React.FC<GanttContentProps> = ({
}
// On finish Event
case 'mouseup': {
case "mouseup": {
let eventForExecution: (
task: Task
) => void | Promise<void> = () => {};
switch (barEvent.action) {
case 'progress':
case "progress":
changedTask.progress = progressByX(event.offsetX, selectedTask);
if (onProgressChange) {
eventForExecution = onProgressChange;
}
break;
case 'start':
case "start": {
const newX1 = startByX(event.offsetX, xStep, selectedTask);
changedTask.start = dateByX(
newX1,
@ -236,7 +242,8 @@ export const GanttContent: React.FC<GanttContentProps> = ({
eventForExecution = onDateChange;
}
break;
case 'end':
}
case "end": {
const newX2 = endByX(event.offsetX, xStep, selectedTask);
changedTask.end = dateByX(
newX2,
@ -250,7 +257,8 @@ export const GanttContent: React.FC<GanttContentProps> = ({
eventForExecution = onDateChange;
}
break;
case 'move':
}
case "move": {
const [newMoveX1, newMoveX2] = moveByX(
event.offsetX - initEventX1Delta,
xStep,
@ -279,15 +287,16 @@ export const GanttContent: React.FC<GanttContentProps> = ({
}
break;
}
}
setBarEvent({ action: '', selectedTask: null });
setBarEvent({ action: "", selectedTask: null });
setIsSVGListen(false);
svg.current?.removeEventListener(
'mousemove',
svg.current.removeEventListener(
"mousemove",
handleMouseSVGChangeEventsSubscribe
);
svg.current?.removeEventListener(
'mouseup',
svg.current.removeEventListener(
"mouseup",
handleMouseSVGChangeEventsSubscribe
);
@ -298,14 +307,20 @@ export const GanttContent: React.FC<GanttContentProps> = ({
}
};
if (barEvent.selectedTask && barEvent.action && !isSVGListen) {
if (
barEvent.selectedTask &&
barEvent.action &&
!isSVGListen &&
svg &&
svg.current
) {
setIsSVGListen(true);
svg.current?.addEventListener(
'mousemove',
svg.current.addEventListener(
"mousemove",
handleMouseSVGChangeEventsSubscribe
);
svg.current?.addEventListener(
'mouseup',
svg.current.addEventListener(
"mouseup",
handleMouseSVGChangeEventsSubscribe
);
}
@ -336,18 +351,19 @@ export const GanttContent: React.FC<GanttContentProps> = ({
task: BarTask
) => {
switch (event.type) {
case 'mousedown':
case "mousedown":
setBarEvent({ ...barEvent, selectedTask: task, action: eventType });
setInitEventX1Delta(event.nativeEvent.offsetX - task.x1);
event.stopPropagation();
break;
case 'mouseleave':
case "mouseleave":
console.log("mouseleave");
if (!barEvent.action)
setBarEvent({ ...barEvent, selectedTask: task, action: '' });
setBarEvent({ ...barEvent, selectedTask: null, action: "" });
break;
case 'mouseenter':
case "mouseenter":
if (!barEvent.selectedTask) {
setBarEvent({ ...barEvent, selectedTask: task, action: '' });
setBarEvent({ ...barEvent, selectedTask: task, action: "" });
}
break;
}
@ -364,7 +380,7 @@ export const GanttContent: React.FC<GanttContentProps> = ({
) => {
if (task.isDisabled) return;
switch (event.key) {
case 'Delete': {
case "Delete": {
if (onTaskDelete) {
onTaskDelete(task);
}
@ -374,8 +390,8 @@ export const GanttContent: React.FC<GanttContentProps> = ({
};
return (
<>
<g className="arrow" fill={arrowColor} stroke={arrowColor}>
<g className="content">
<g className="arrows" fill={arrowColor} stroke={arrowColor}>
{barTasks.map(task => {
return task.barChildren.map(child => {
return (
@ -408,8 +424,8 @@ export const GanttContent: React.FC<GanttContentProps> = ({
</g>
<g className="toolTip">
{barEvent.selectedTask &&
barEvent.action !== 'end' &&
barEvent.action !== 'start' && (
barEvent.action !== "end" &&
barEvent.action !== "start" && (
<Tooltip
x={barEvent.selectedTask.x2 + columnWidth + arrowIndent}
y={barEvent.selectedTask.y + rowHeight}
@ -420,6 +436,6 @@ export const GanttContent: React.FC<GanttContentProps> = ({
/>
)}
</g>
</>
</g>
);
};

View File

@ -1,9 +1,9 @@
import React, { useRef } from 'react';
import { ViewMode, GanttProps } from '../../types/public-types';
import { Grid, GridProps } from '../Grid/grid';
import { Calendar, CalendarProps } from '../Calendar/calendar';
import { GanttContent, GanttContentProps } from './gantt-content';
import { ganttDateRange, seedDates } from '../../helpers/date-helper';
import React, { useRef } from "react";
import { ViewMode, GanttProps } from "../../types/public-types";
import { Grid, GridProps } from "../Grid/grid";
import { Calendar, CalendarProps } from "../Calendar/calendar";
import { GanttContent, GanttContentProps } from "./gantt-content";
import { ganttDateRange, seedDates } from "../../helpers/date-helper";
export const Gantt: React.SFC<GanttProps> = ({
tasks,
@ -11,20 +11,20 @@ export const Gantt: React.SFC<GanttProps> = ({
columnWidth = 60,
rowHeight = 50,
viewMode = ViewMode.Day,
locale = 'en-GB',
locale = "en-GB",
barFill = 60,
barCornerRadius = 3,
barProgressColor = '#a3a3ff',
barProgressSelectedColor = '#8282f5',
barBackgroundColor = '#b8c2cc',
barBackgroundSelectedColor = '#aeb8c2',
barProgressColor = "#a3a3ff",
barProgressSelectedColor = "#8282f5",
barBackgroundColor = "#b8c2cc",
barBackgroundSelectedColor = "#aeb8c2",
handleWidth = 8,
timeStep = 300000,
arrowColor = 'grey',
fontFamily = 'Arial, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue',
fontSize = '14px',
arrowColor = "grey",
fontFamily = "Arial, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue",
fontSize = "14px",
arrowIndent = 20,
todayColor = 'rgba(252, 248, 227, 0.5)',
todayColor = "rgba(252, 248, 227, 0.5)",
onDateChange,
onProgressChange,
onDoubleClick,

View File

@ -1,7 +1,7 @@
import React, { ReactChild } from 'react';
import { Task } from '../../types/public-types';
import { addToDate } from '../../helpers/date-helper';
import '../../style.css';
import React, { ReactChild } from "react";
import { Task } from "../../types/public-types";
import { addToDate } from "../../helpers/date-helper";
import styles from "./grid.module.css";
export type GridBodyProps = {
tasks: Task[];
@ -22,36 +22,36 @@ export const GridBody: React.FC<GridBodyProps> = ({
todayColor,
}) => {
let y = headerHeight;
let gridRows: ReactChild[] = [];
let rowLines: ReactChild[] = [];
const gridRows: ReactChild[] = [];
const rowLines: ReactChild[] = [];
for (const task of tasks) {
gridRows.push(
<rect
key={'Row' + task.id}
key={"Row" + task.id}
x="0"
y={y}
width={gridWidth}
height={rowHeight}
className="GanttGrid-row"
></rect>
className={styles.gridRow}
/>
);
rowLines.push(
<line
key={'RowLine' + task.id}
key={"RowLine" + task.id}
x="0"
y1={y + rowHeight}
x2={gridWidth}
y2={y + rowHeight}
className="GanttGrid-rowLine"
></line>
className={styles.gridRowLine}
/>
);
y += rowHeight;
}
const now = new Date();
let tickX = 0;
let ticks: ReactChild[] = [];
let today: ReactChild = <></>;
const ticks: ReactChild[] = [];
let today: ReactChild = <rect />;
for (let i = 0; i < dates.length; i++) {
const date = dates[i];
ticks.push(
@ -61,8 +61,8 @@ export const GridBody: React.FC<GridBodyProps> = ({
y1={headerHeight}
x2={tickX}
y2={y}
className="GanttGrid-tick"
></line>
className={styles.gridTick}
/>
);
if (
(i + 1 !== dates.length &&
@ -75,7 +75,7 @@ export const GridBody: React.FC<GridBodyProps> = ({
addToDate(
date,
date.getTime() - dates[i - 1].getTime(),
'millisecond'
"millisecond"
).getTime() >= now.getTime())
) {
today = (
@ -85,17 +85,17 @@ export const GridBody: React.FC<GridBodyProps> = ({
width={columnWidth}
height={y}
fill={todayColor}
></rect>
/>
);
}
tickX += columnWidth;
}
return (
<>
<g className="gridBody">
<g className="rows">{gridRows}</g>
<g className="rowLines">{rowLines}</g>
<g className="ticks">{ticks}</g>
<g className="today">{today}</g>
</>
</g>
);
};

View File

@ -1,5 +1,5 @@
import React from 'react';
import '../../style.css';
import React from "react";
import styles from "./grid.module.css";
export type GridHeaderProps = {
gridWidth: number;
@ -15,7 +15,7 @@ export const GridHeader: React.FC<GridHeaderProps> = ({
y="0"
width={gridWidth}
height={headerHeight}
className="GanttGrid-header"
></rect>
className={styles.gridHeader}
/>
);
};

View File

@ -0,0 +1,21 @@
.gridRow {
fill: #ffffff;
}
.gridRow:nth-child(even) {
fill: #f5f5f5;
}
.gridHeader {
fill: #ffffff;
stroke: #e0e0e0;
stroke-width: 1.4;
}
.gridRowLine {
stroke: #ebeff2;
}
.gridTick {
stroke: #e6e4e4;
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import { GridBody, GridBodyProps } from './grid-body';
import { GridHeader, GridHeaderProps } from './grid-header';
import React from "react";
import { GridBody, GridBodyProps } from "./grid-body";
import { GridHeader, GridHeaderProps } from "./grid-header";
export type GridProps = GridBodyProps & GridHeaderProps;
export const Grid: React.FC<GridProps> = props => {

View File

@ -1,5 +1,5 @@
import React from 'react';
import { BarTask } from '../../types/bar-task';
import React from "react";
import { BarTask } from "../../types/bar-task";
type ArrowProps = {
taskFrom: BarTask;
@ -26,9 +26,9 @@ export const Arrow: React.FC<ArrowProps> = ({
${taskTo.x1 - 5},${taskToEndPosition - 5}
${taskTo.x1 - 5},${taskToEndPosition + 5}`;
return (
<>
<g className="arrow">
<path strokeWidth="1.5" d={path} fill="none" />
<polygon points={trianglePoints} />
</>
</g>
);
};

View File

@ -0,0 +1,15 @@
.tooltipDefaultContainer {
background: #fff;
padding: 12px;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
}
.tooltipDefaultContainerParagraph {
font-size: 12px;
margin-bottom: 6px;
color: #666;
}
.tooltipDetailsContainer {
display: table;
}

View File

@ -1,6 +1,7 @@
import React, { useRef, useEffect, useState } from 'react';
import { Task } from '../../types/public-types';
import '../../style.css';
import React, { useRef, useEffect, useState } from "react";
import { Task } from "../../types/public-types";
import styles from "./tooltip.module.css";
export type TooltipProps = {
x: number;
y: number;
@ -36,7 +37,7 @@ export const Tooltip: React.FC<TooltipProps> = ({
}, [tooltipRef, y]);
return (
<foreignObject x={x} y={relatedY} width={toolWidth} height={1000}>
<div ref={tooltipRef} className="TooltipDetailsContainer">
<div ref={tooltipRef} className={styles.tooltipDetailsContainer}>
{getTooltipContent(task, fontSize, fontFamily)}
</div>
</foreignObject>
@ -53,17 +54,21 @@ const getStandardTooltipContent = (
fontFamily,
};
return (
<div className="TooltipDefaultContainer" style={style}>
<div className={styles.tooltipDefaultContainer} style={style}>
<b style={{ fontSize: fontSize + 6 }}>{`${
task.name
}: ${task.start.getDate()}-${task.start.getMonth() +
1}-${task.start.getFullYear()} - ${task.end.getDate()}-${task.end.getMonth() +
1}-${task.end.getFullYear()}`}</b>
<p className="TooltipDefaultContainer-paragraph">{`Duration: ${~~(
}: ${task.start.getDate()}-${
task.start.getMonth() + 1
}-${task.start.getFullYear()} - ${task.end.getDate()}-${
task.end.getMonth() + 1
}-${task.end.getFullYear()}`}</b>
<p className={styles.tooltipDefaultContainerParagraph}>{`Duration: ${~~(
(task.end.getTime() - task.start.getTime()) /
(1000 * 60 * 60 * 24)
)} day(s)`}</p>
<p className="TooltipDefaultContainer-paragraph">{`Progress: ${task.progress} %`}</p>
<p
className={styles.tooltipDefaultContainerParagraph}
>{`Progress: ${task.progress} %`}</p>
</div>
);
};

View File

@ -1,5 +1,5 @@
import { Task } from '../types/public-types';
import { BarTask } from '../types/bar-task';
import { Task } from "../types/public-types";
import { BarTask } from "../types/bar-task";
export const convertToBarTasks = (
tasks: Task[],
@ -168,7 +168,7 @@ export const getProgressPoint = (
progressX,
taskY + taskHeight - 8.66,
];
return point.join(',');
return point.join(",");
};
export const startByX = (x: number, xStep: number, task: BarTask) => {

View File

@ -1,54 +1,54 @@
import { Task, ViewMode } from '../types/public-types';
import { Task, ViewMode } from "../types/public-types";
type DateHelperScales =
| 'year'
| 'month'
| 'day'
| 'hour'
| 'minute'
| 'second'
| 'millisecond';
| "year"
| "month"
| "day"
| "hour"
| "minute"
| "second"
| "millisecond";
export const addToDate = (
date: Date,
quantity: number,
scale: DateHelperScales
) => {
let newDate = new Date(
date.getFullYear() + (scale === 'year' ? quantity : 0),
date.getMonth() + (scale === 'month' ? quantity : 0),
date.getDate() + (scale === 'day' ? quantity : 0),
date.getHours() + (scale === 'hour' ? quantity : 0),
date.getMinutes() + (scale === 'minute' ? quantity : 0),
date.getSeconds() + (scale === 'second' ? quantity : 0),
date.getMilliseconds() + (scale === 'millisecond' ? quantity : 0)
const newDate = new Date(
date.getFullYear() + (scale === "year" ? quantity : 0),
date.getMonth() + (scale === "month" ? quantity : 0),
date.getDate() + (scale === "day" ? quantity : 0),
date.getHours() + (scale === "hour" ? quantity : 0),
date.getMinutes() + (scale === "minute" ? quantity : 0),
date.getSeconds() + (scale === "second" ? quantity : 0),
date.getMilliseconds() + (scale === "millisecond" ? quantity : 0)
);
return newDate;
};
export const startOfDate = (date: Date, scale: DateHelperScales) => {
const scores = [
'millisecond',
'second',
'minute',
'hour',
'day',
'month',
'year',
"millisecond",
"second",
"minute",
"hour",
"day",
"month",
"year",
];
const shouldReset = (_scale: DateHelperScales) => {
const max_score = scores.indexOf(scale);
return scores.indexOf(_scale) <= max_score;
const maxScore = scores.indexOf(scale);
return scores.indexOf(_scale) <= maxScore;
};
let newDate = new Date(
const newDate = new Date(
date.getFullYear(),
shouldReset('year') ? 0 : date.getMonth(),
shouldReset('month') ? 1 : date.getDate(),
shouldReset('day') ? 0 : date.getHours(),
shouldReset('hour') ? 0 : date.getMinutes(),
shouldReset('minute') ? 0 : date.getSeconds(),
shouldReset('second') ? 0 : date.getMilliseconds()
shouldReset("year") ? 0 : date.getMonth(),
shouldReset("month") ? 1 : date.getDate(),
shouldReset("day") ? 0 : date.getHours(),
shouldReset("hour") ? 0 : date.getMinutes(),
shouldReset("minute") ? 0 : date.getSeconds(),
shouldReset("second") ? 0 : date.getMilliseconds()
);
return newDate;
};
@ -56,7 +56,7 @@ export const startOfDate = (date: Date, scale: DateHelperScales) => {
export const ganttDateRange = (tasks: Task[], viewMode: ViewMode) => {
let newStartDate: Date = tasks[0].start;
let newEndDate: Date = tasks[0].end;
for (let task of tasks) {
for (const task of tasks) {
if (task.start < newStartDate) {
newStartDate = task.start;
}
@ -64,22 +64,24 @@ export const ganttDateRange = (tasks: Task[], viewMode: ViewMode) => {
newEndDate = task.end;
}
}
if (viewMode === ViewMode.Month) {
newStartDate = addToDate(newStartDate, -1, 'month');
newEndDate = addToDate(newEndDate, 1, 'year');
newEndDate = startOfDate(newEndDate, 'year');
} else if (viewMode === ViewMode.Week) {
newStartDate = startOfDate(newStartDate, 'day');
newEndDate = startOfDate(newEndDate, 'day');
newStartDate = addToDate(getMonday(newStartDate), -7, 'day');
newEndDate = addToDate(newEndDate, 1.5, 'month');
} else {
newStartDate = startOfDate(newStartDate, 'day');
newEndDate = startOfDate(newEndDate, 'day');
newStartDate = addToDate(newStartDate, -1, 'day');
newEndDate = addToDate(newEndDate, 19, 'day');
switch (viewMode) {
case ViewMode.Month:
newStartDate = addToDate(newStartDate, -1, "month");
newEndDate = addToDate(newEndDate, 1, "year");
newEndDate = startOfDate(newEndDate, "year");
break;
case ViewMode.Week:
newStartDate = startOfDate(newStartDate, "day");
newEndDate = startOfDate(newEndDate, "day");
newStartDate = addToDate(getMonday(newStartDate), -7, "day");
newEndDate = addToDate(newEndDate, 1.5, "month");
break;
default:
newStartDate = startOfDate(newStartDate, "day");
newEndDate = startOfDate(newEndDate, "day");
newStartDate = addToDate(newStartDate, -1, "day");
newEndDate = addToDate(newEndDate, 19, "day");
break;
}
return [newStartDate, newEndDate];
};
@ -90,18 +92,24 @@ export const seedDates = (
viewMode: ViewMode
) => {
let currentDate: Date = new Date(startDate);
let dates: Date[] = [currentDate];
const dates: Date[] = [currentDate];
while (currentDate < endDate) {
if (viewMode === ViewMode.Month) {
currentDate = addToDate(currentDate, 1, 'month');
} else if (viewMode === ViewMode.Week) {
currentDate = addToDate(currentDate, 7, 'day');
} else if (viewMode === ViewMode.Day) {
currentDate = addToDate(currentDate, 1, 'day');
} else if (viewMode === ViewMode.HalfDay) {
currentDate = addToDate(currentDate, 12, 'hour');
} else if (viewMode === ViewMode.QuarterDay) {
currentDate = addToDate(currentDate, 6, 'hour');
switch (viewMode) {
case ViewMode.Month:
currentDate = addToDate(currentDate, 1, "month");
break;
case ViewMode.Week:
currentDate = addToDate(currentDate, 7, "day");
break;
case ViewMode.Day:
currentDate = addToDate(currentDate, 1, "day");
break;
case ViewMode.HalfDay:
currentDate = addToDate(currentDate, 12, "hour");
break;
case ViewMode.QuarterDay:
currentDate = addToDate(currentDate, 6, "hour");
break;
}
dates.push(currentDate);
}
@ -110,7 +118,7 @@ export const seedDates = (
export const getLocaleMonth = (date: Date, locale: string) => {
let bottomValue = new Intl.DateTimeFormat(locale, {
month: 'long',
month: "long",
}).format(date);
bottomValue = bottomValue.replace(
bottomValue[0],
@ -130,7 +138,7 @@ const getMonday = (date: Date) => {
};
export const getWeekNumberISO8601 = (date: Date) => {
let tmpDate = new Date(date.valueOf());
const tmpDate = new Date(date.valueOf());
const dayNumber = (tmpDate.getDay() + 6) % 7;
tmpDate.setDate(tmpDate.getDate() - dayNumber + 3);
const firstThursday = tmpDate.valueOf();

View File

@ -1,9 +1,9 @@
export { Gantt } from './components/Gantt/gantt';
export {
export { Gantt } from "./components/Gantt/gantt";
export { ViewMode } from "./types/public-types";
export type {
GanttProps,
Task,
ViewMode,
StylingOption,
DisplayOption,
EventOption,
} from './types/public-types';
} from "./types/public-types";

1
src/react-app-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@ -1,34 +0,0 @@
import { Task } from '../types/public-types';
type GanttReduceState = {
ganttTasks: Task[];
};
export type GanttReduceAction = {
type: 'update' | 'delete';
changedTask?: Task;
};
export function ganttReducer(
state: GanttReduceState,
action: GanttReduceAction
): GanttReduceState {
switch (action.type) {
case 'update': {
return {
ganttTasks: state.ganttTasks.map(t =>
t.id === action.changedTask?.id ? action.changedTask : t
),
};
}
case 'delete': {
return {
ganttTasks: state.ganttTasks.filter(
t => t.id !== action.changedTask?.id
),
};
}
default:
return state;
}
}

View File

@ -1,4 +1,4 @@
import { Task } from './public-types';
import { Task } from "./public-types";
export interface BarTask extends Task {
index: number;

View File

@ -1,10 +1,10 @@
export enum ViewMode {
QuarterDay = 'Quarter Day',
HalfDay = 'Half Day',
Day = 'Day',
QuarterDay = "Quarter Day",
HalfDay = "Half Day",
Day = "Day",
/** ISO-8601 week */
Week = 'Week',
Month = 'Month',
Week = "Week",
Month = "Month",
}
export interface Task {
id: string;

18
src/typings.d.ts vendored Normal file
View File

@ -0,0 +1,18 @@
/**
* Default CSS definition for typescript,
* will be overridden with file-specific definitions by rollup
*/
declare module "*.css" {
const content: { [className: string]: string };
export default content;
}
interface SvgrComponent
extends React.StatelessComponent<React.SVGAttributes<SVGElement>> {}
declare module "*.svg" {
const svgUrl: string;
const svgComponent: SvgrComponent;
export default svgUrl;
export { svgComponent as ReactComponent };
}

View File

@ -2,11 +2,11 @@ import {
seedDates,
addToDate,
getWeekNumberISO8601,
} from '../src/helpers/date-helper';
import { ViewMode } from '../src/types/public-types';
} from "../helpers/date-helper";
import { ViewMode } from "../types/public-types";
describe('seed date', () => {
test('daily', () => {
describe("seed date", () => {
test("daily", () => {
expect(
seedDates(new Date(2020, 5, 28), new Date(2020, 6, 2), ViewMode.Day)
).toEqual([
@ -18,7 +18,7 @@ describe('seed date', () => {
]);
});
test('weekly', () => {
test("weekly", () => {
expect(
seedDates(new Date(2020, 5, 28), new Date(2020, 6, 19), ViewMode.Week)
).toEqual([
@ -29,13 +29,13 @@ describe('seed date', () => {
]);
});
test('monthly', () => {
test("monthly", () => {
expect(
seedDates(new Date(2020, 5, 28), new Date(2020, 6, 19), ViewMode.Month)
).toEqual([new Date(2020, 5, 28), new Date(2020, 6, 28)]);
});
test('quarterly', () => {
test("quarterly", () => {
expect(
seedDates(
new Date(2020, 5, 28),
@ -52,22 +52,22 @@ describe('seed date', () => {
});
});
describe('add to date', () => {
test('add month', () => {
expect(addToDate(new Date(2020, 0, 1), 40, 'month')).toEqual(
describe("add to date", () => {
test("add month", () => {
expect(addToDate(new Date(2020, 0, 1), 40, "month")).toEqual(
new Date(2023, 4, 1)
);
});
test('add day', () => {
expect(addToDate(new Date(2020, 0, 1), 40, 'day')).toEqual(
test("add day", () => {
expect(addToDate(new Date(2020, 0, 1), 40, "day")).toEqual(
new Date(2020, 1, 10)
);
});
});
test('get week number', () => {
expect(getWeekNumberISO8601(new Date(2019, 11, 31))).toEqual('01');
expect(getWeekNumberISO8601(new Date(2021, 0, 1))).toEqual('53');
expect(getWeekNumberISO8601(new Date(2020, 6, 20))).toEqual('30');
test("get week number", () => {
expect(getWeekNumberISO8601(new Date(2019, 11, 31))).toEqual("01");
expect(getWeekNumberISO8601(new Date(2021, 0, 1))).toEqual("53");
expect(getWeekNumberISO8601(new Date(2020, 6, 20))).toEqual("30");
});

View File

@ -1,18 +1,18 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Gantt } from '../src/index';
import React from "react";
import ReactDOM from "react-dom";
import { Gantt } from "../index";
describe('gantt', () => {
it('renders without crashing', () => {
const div = document.createElement('div');
describe("gantt", () => {
it("renders without crashing", () => {
const div = document.createElement("div");
ReactDOM.render(
<Gantt
tasks={[
{
start: new Date(2020, 0, 1),
end: new Date(2020, 2, 2),
name: 'Redesign website',
id: 'Task 0',
name: "Redesign website",
id: "Task 0",
progress: 45,
},
]}

View File

@ -1,23 +1,39 @@
{
"include": ["src", "types", "tsdx.config.js", "tsdx.config.js"],
"compilerOptions": {
"outDir": "dist",
"module": "esnext",
"lib": ["dom", "esnext"],
"importHelpers": true,
"declaration": true,
"lib": [
"dom",
"esnext"
],
"moduleResolution": "node",
"jsx": "react",
"sourceMap": true,
"rootDir": "./src",
"strict": true,
"declaration": true,
"esModuleInterop": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"baseUrl": "./",
"paths": {
"*": ["src/*", "node_modules/*"]
"allowSyntheticDefaultImports": true,
"target": "es5",
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
},
"jsx": "react",
"esModuleInterop": true
}
"include": [
"src"
],
"exclude": [
"node_modules",
"dist",
"example"
]
}

6
tsconfig.test.json Normal file
View File

@ -0,0 +1,6 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs"
}
}

View File

@ -1,20 +0,0 @@
const postcss = require('rollup-plugin-postcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
module.exports = {
rollup(config, options) {
config.plugins.push(
postcss({
plugins: [
autoprefixer(),
cssnano({
preset: 'default',
}),
],
inject: false,
extract: !!options.writeMeta,
})
);
return config;
},
};

7046
yarn.lock

File diff suppressed because it is too large Load Diff