diff --git a/demo-app/src/stories/table/TreeFixedDataTableGantt.stories.tsx b/demo-app/src/stories/table/TreeFixedDataTableGantt.stories.tsx index 940d844a..d57242ab 100644 --- a/demo-app/src/stories/table/TreeFixedDataTableGantt.stories.tsx +++ b/demo-app/src/stories/table/TreeFixedDataTableGantt.stories.tsx @@ -1,17 +1,57 @@ import { TreeFixedDataTableRRC } from "@crispico/foundation-react/components/treeFixedDataTable/TreeFixedDataTable"; -import { Main, Task, root } from "@crispico/foundation-react/components/treeFixedDataTable/TreeFixedDataTable.stories"; +import { Main, Task, root, Segment } from "@crispico/foundation-react/components/treeFixedDataTable/TreeFixedDataTable.stories"; import { ReduxReusableComponents } from "@crispico/foundation-react/reduxReusableComponents/ReduxReusableComponents"; import { Utils } from "@crispico/foundation-react/utils/Utils"; -import { Group, Item, Timeline } from "@famiprog-foundation/react-gantt"; +import { Group, Item, ItemRenderer, Timeline } from "@famiprog-foundation/react-gantt"; import { Cell, Column, Table } from 'fixed-data-table-2'; import { d } from "../sampleData"; import { Message } from "semantic-ui-react"; +import { getKeyThenIncreaseKey } from "antd/lib/message"; export default { title: 'Features/Table', }; +class CustomItemRenderer extends ItemRenderer { + render() { + if (this.props.item.isParentSegment) { + return + } else { + return super.render(); + } + } +} + +class ParentItemRenderer extends ItemRenderer { + hasCustomShape() { + return true; + } + + drawCustomShapeRenderer() { + const ARROW_WIDTH = 20; + const height = this.getHeight() as number; + // We need to make the rectangle a little bit bigger because the row height is 40 px and the actual segment height is 30. + // So if we draw the rectangle only till the middle of the segment + // the text (that has 18px height) will overflow the rectangle with 3 px + const rectangleHeight = (this.getHeight() as number) / 2 + 4; + const rightArrowStartX = this.props.width - ARROW_WIDTH; + const leftArrowPoints = "0," + rectangleHeight + " " + ARROW_WIDTH + "," + rectangleHeight + " " + ARROW_WIDTH / 2 + "," + height + " 0," + rectangleHeight; + const rightArrowPoints = rightArrowStartX + "," + rectangleHeight + " " + (rightArrowStartX + ARROW_WIDTH) + "," + rectangleHeight + " " + + (rightArrowStartX + ARROW_WIDTH / 2) + "," + height + " " + rightArrowStartX + "," + rectangleHeight; + + return <> + + + + + } + + getTitleStyle() { + return {alignSelf: "start"}; + } +} + export const TreeTable = () => { // adding an additional task here, so that the shape of the data appears in the Storybook code snippet // however, subtle detail: don't do this in prod; I mean, don't give a new "root" to the component on each render (which happens here) @@ -55,7 +95,7 @@ export const TreeTable = () => { getChildrenFunction={task => Object.keys(task.subtasks).map(key => { return { localId: key, item: task.subtasks[key] } })} initialExpandedIds={{ 1: true, 2: true, [`2${Utils.defaultIdSeparator}0`]: true }} renderMainElementFunction={({ mainChildren, linearizedItems }) => { - const items: Item[] = []; + const items: (Item & {isParentSegment: boolean}) [] = []; const groups: Group[] = []; // using a for (instead of 2 x map()) in order to avoid 2 iterations linearizedItems.forEach((li, i) => { @@ -64,7 +104,8 @@ export const TreeTable = () => { const task = navigateToTask(li.itemId); task.segments.forEach((segment, j) => items.push({ key: "" + i + "." + j, row: i, start: segment.start, end: segment.end, title: segment.percentComplete + "%", - color: segment.percentComplete == 0 ? "#FA0000" : (segment.percentComplete === 100 ? "#9ACD32" : "#FFA500") + color: segment.percentComplete == 0 ? "#FA0000" : (segment.percentComplete === 100 ? "#9ACD32" : "#FFA500"), + isParentSegment: task.subtasks != undefined && task.subtasks.length > 0 })) }); @@ -74,7 +115,8 @@ export const TreeTable = () => { width={420} height={300}> Name} width={400} cell={props => mainChildren[props.rowIndex]} /> - } /> + } + itemRenderer={CustomItemRenderer}/> }} renderItemFunction={({ linearizedItem }) => navigateToTask(linearizedItem.itemId).name} /> diff --git a/src/components/ItemRenderer.js b/src/components/ItemRenderer.js index 46462a53..1df5c6b9 100644 --- a/src/components/ItemRenderer.js +++ b/src/components/ItemRenderer.js @@ -4,6 +4,12 @@ import React from 'react'; import {Item} from '../utils/itemUtils'; const ITEM_RENDERER_CLS = 'rct9k-item-renderer'; + +// This class is used in case of a custom shape renderer drawn via `drawCustomShapeRenderer()`. +// It disables the visual styles related to the default rectangle shape of the renderer: +// 1. The background color and the borders +// 2. The box shadow effect +const CUSTOM_FIGURE_ITEM_RENDERER_CLS = 'rct9k-custom-shape-item-renderer'; const ITEM_RENDERER_GLOW_CLS = 'rct9k-item-glow'; /** @@ -34,6 +40,12 @@ export default class ItemRenderer extends React.Component { */ tooltip: PropTypes.string, + /** + * It's passed by the parent. Though not used by this component. It exists because maybe subclasses want to use it. + * @type { number } + */ + width: PropTypes.number, + /** * The height of the segment (item). * @@ -83,6 +95,11 @@ export default class ItemRenderer extends React.Component { */ style: PropTypes.object, + /** + * @type { object } + */ + titleStyle: PropTypes.object, + /** * Class name used to render the segment (item). * @type { string } @@ -100,6 +117,7 @@ export default class ItemRenderer extends React.Component { tooltip: undefined, className: undefined, style: {}, + titleStyle: {}, item: {} }; @@ -209,6 +227,10 @@ export default class ItemRenderer extends React.Component { return style; } + getTitleStyle() { + return this.props.titleStyle; + } + /** * Returns a css class used to apply glow on item hover. * @returns { string } @@ -222,13 +244,48 @@ export default class ItemRenderer extends React.Component { * @returns { string } */ getClassName() { - return ITEM_RENDERER_CLS + ' ' + this.props.className + ' ' + this.getGlowOnHoverClassName(); + return ( + ITEM_RENDERER_CLS + + ' ' + + this.props.className + + ' ' + + this.getGlowOnHoverClassName() + + (this.hasCustomShape() ? ' ' + CUSTOM_FIGURE_ITEM_RENDERER_CLS : '') + ); + } + + hasCustomShape() { + return false; + } + + /** + * If the user wants an item renderer with a special shape: + * 1. It needs to override `hasCustomShape()` to return true + * 1. And implement the drawCustomShapeRenderer(). This needs to return the content of a svg + * + * @type { JSX.element | undefined} + */ + drawCustomShapeRenderer() { + throw new Error('In case of a custom shape renderer the `drawCustomShapeRenderer()` should be implemented'); } render() { return ( - - {this.getTitle()} + + + {this.getTitle()} + + {this.hasCustomShape() && ( + + {this.drawCustomShapeRenderer()} + + )} ); } diff --git a/src/style.css b/src/style.css index 81be6b45..2a532692 100644 --- a/src/style.css +++ b/src/style.css @@ -221,10 +221,10 @@ width: 2px; } -.rct9k-item-renderer { - opacity: 0.8; - border: darkgrey solid 1px; - border-radius: 3px; +.rct9k-custom-shape-item-renderer { + box-shadow: none !important; + border: none !important; + background: none !important; } .rct9k-item-renderer-inner { diff --git a/src/utils/itemUtils.js b/src/utils/itemUtils.js index 88a4fb47..2f3229af 100644 --- a/src/utils/itemUtils.js +++ b/src/utils/itemUtils.js @@ -83,7 +83,14 @@ export function rowItemsRenderer( data-item-index={i.key} className={outerClassnames} style={{left, width, top, backgroundColor: 'transparent'}}> - + ); });