diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index db4bd8305..21a24e280 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -10,7 +10,7 @@ A clear and concise description of what the bug is. _Please provide one or both of the following:_ -Code (If you have a branch somewhere that shows your issue) or link to codesandbox example. [Here's a base template for this library](https://codesandbox.io/s/lyjr9p0119): +Code (If you have a branch somewhere that shows your issue) or link to codesandbox example. [Here's a base template for this library](https://codesandbox.io/s/w6xvqzno4w): OR diff --git a/.gitignore b/.gitignore index 71054179f..2db738b4f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,8 @@ bower_components modules build gh-pages -lib +/lib +package-lock.json # vscode stuff .vscode diff --git a/CHANGELOG.md b/CHANGELOG.md index 97cb27c0e..a83167594 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,61 @@ and this project adheres (more or less) to [Semantic Versioning](http://semver.o ## Unreleased +## 0.23.0 + +- improve unit tests coverage #426 - @ilaiwi +- stack items by group #384 - @acemac +- fix bug where `canMove` prop gets ignored #484 - @acemac + @ilaiwi +- fix sidebar re-render when groupHeights do not change #478 - @SDupZ + +### Stack per group + +now you can stack choose to stack items in individual groups by providing the property `stackItems` in group object. The property in group overrides the timeline prop `stackItems`. + +``` +const groups = [{ id: 1, title: 'group 1', stackItems: false }, { id: 2, title: 'group 2', stackItems: true }] + +const items = [ + { + id: 1, + group: 1, + title: 'item 1', + start_time: moment(), + end_time: moment().add(1, 'hour') + }, + { + id: 2, + group: 2, + title: 'item 2', + start_time: moment().add(-0.5, 'hour'), + end_time: moment().add(0.5, 'hour') + }, + { + id: 3, + group: 1, + title: 'item 3', + start_time: moment().add(2, 'hour'), + end_time: moment().add(3, 'hour') + } +] + +ReactDOM.render( +
+ Rendered by react! + +
, + document.getElementById('root') +) +``` + + +## 0.22.0 + ### Fixed * Provided a new key `groupLabelKey` to allow splitting of the key used to render the Sidebar and the InfoLabel visible during drag operations. `groupTitleKey` continues to be used to render the Sidebar. #442 @thiagosatoshi diff --git a/README.md b/README.md index 5ad25df48..9f06b30a7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # React Calendar Timeline -A modern and responsive react timeline component. +A modern and responsive React timeline component. ![calendar demo](https://raw.githubusercontent.com/namespace-ee/react-calendar-timeline/master/demo.gif) @@ -16,7 +16,7 @@ yarn add react-calendar-timeline npm install --save react-calendar-timeline ``` -`react-calendar-timeline` has `react`, `react-dom`, [`moment`](http://momentjs.com/) and [`interactjs`](http://interactjs.io/docs/) as peer dependencies. +`react-calendar-timeline` has [react](https://reactjs.org/), [react-dom](https://reactjs.org/docs/react-dom.html), [`moment`](http://momentjs.com/) and [`interactjs`](http://interactjs.io/docs/) as peer dependencies. # Usage @@ -83,12 +83,12 @@ Expects either a vanilla JS array or an immutableJS array, consisting of objects id: 1, title: 'group 1', rightTitle: 'title in the right sidebar', - stackItems?: true, + stackItems?: false or 'space' or 'line', height?: 30 } ``` -If you use right sidebar, you can pass optional `rightTitle` property here. +If you use the right sidebar, you can pass optional `rightTitle` property here. If you want to overwrite the calculated height with a custom height, you can pass a `height` property as an int in pixels here. This can be very useful for categorized groups. ## items @@ -118,7 +118,7 @@ Expects either a vanilla JS array or an immutableJS array, consisting of objects } ``` -The preferred (fastest) option is to give unix timestamps in milliseconds for `start_time` and `end_time`. Objects that convert to them (JavaScript Date or moment()) will also work, but will be a lot slower. +The preferred (fastest) option is to give Unix timestamps in milliseconds for `start_time` and `end_time`. Objects that convert to them (JavaScript `Date` or `moment()`) will also work, but will be a lot slower. ## defaultTimeStart and defaultTimeEnd @@ -126,7 +126,7 @@ Unless overridden by `visibleTimeStart` and `visibleTimeEnd`, specify where the ## visibleTimeStart and visibleTimeEnd -The exact viewport of the calendar. When these are specified, scrolling in the calendar must be orchestrated by the `onTimeChange` function. This parameter expects a unix timestamp in milliseconds. +The exact viewport of the calendar. When these are specified, scrolling in the calendar must be orchestrated by the `onTimeChange` function. This parameter expects a Unix timestamp in milliseconds. **Note that you need to provide either `defaultTimeStart/End` or `visibleTimeStart/End` for the timeline to function** @@ -179,8 +179,7 @@ The minimum width, in pixels, of a timeline entry when it's possible to resize. ## stickyOffset -At what height from the top of the screen should we start "sticking" the header (i.e. position: sticky)? This is useful if for example you already have -a sticky navbar and want to push the timeline header down further. Defaults `0`. +At what height from the top of the screen should we start "sticking" the header (i.e. position: sticky)? This is useful if for example you already have a sticky navbar and want to push the timeline header down further. Defaults `0`. ## stickyHeader @@ -216,7 +215,7 @@ Largest time the calendar can zoom to in milliseconds. Default `5 * 365.24 * 864 ## clickTolerance -How many pixels we can drag the background for it to be counted as a click on the background. Defualt: `3` +How many pixels we can drag the background for it to be counted as a click on the background. Default `3` ## canMove @@ -238,6 +237,11 @@ Append a special `.rct-drag-right` handle to the elements and only resize if dra Stack items under each other, so there is no visual overlap when times collide. Can be overridden in the `groups` array. Defaults to `false`. +can be assigned to +- false +- space (saves space in stacking) +- line (stack each item in a line) + ## traditionalZoom Zoom in when scrolling the mouse up/down. Defaults to `false` @@ -332,6 +336,11 @@ function (action, item, time, resizeEdge) { } ``` +## onUpdateMove(itemId, time, newGroup, action,resizeEdge) + +this function is called after moveResizeValidator on every drag update + + ## headerLabelFormats and subHeaderLabelFormats The formats passed to moment to render times in the header and subheader. Defaults to these: @@ -435,9 +444,9 @@ Called when the bounds in the calendar's canvas change. Use it for example to lo ## itemRenderer -Render prop function used to render a customized item. The function provides multiple paramerters that can be used to render each item. +Render prop function used to render a customized item. The function provides multiple parameters that can be used to render each item. -Paramters provided to the function has two types: context params which have the state of the item and timeline, and prop getters functions +Parameters provided to the function has two types: context params which have the state of the item and timeline, and prop getters functions #### Render props params @@ -468,10 +477,12 @@ Paramters provided to the function has two types: context params which have the | `selected` | `boolean` | returns if the item is selected. | | `dragging` | `boolean` | returns if the item is being dragged | | `dragStart` | `object` | returns `x` and `y` of the start dragging point of the item. | +| `dragTime` | `number` | current drag time. | | `dragGroupDelta` | `number` | returns number of groups the item moved. if negative, moving was to top. If positive, moving was to down | | `resizing` | `boolean` | returns if the item is being resized. | | `resizeEdge` | `left`, `right` | the side from which the component is being resized form | | `resizeStart` | `number` | returns the x value from where the component start moving | +| `resizeTime` | `number` | current resize time | | `width` | `boolean` | returns the width of the item (same as in dimensions) | ##### prop getters functions @@ -488,7 +499,7 @@ Rather than applying props on the element yourself and to avoid your props being * `getItemProps` returns the props you should apply to the root item element. The returned props are: * key: item id - * ref: function to get item referance + * ref: function to get item reference * className: classnames to be applied to the item * onMouseDown: event handler * onMouseUp: event handler @@ -498,9 +509,9 @@ Rather than applying props on the element yourself and to avoid your props being * onContextMenu: event handler * style: inline object style - \*\* _the given styles will only override the styles that are not a requirement for postioning the item. Other styles like `color`, `radius` and others_ + \*\* _the given styles will only override the styles that are not a requirement for positioning the item. Other styles like `color`, `radius` and others_ - These properties can be override using the prop argument with proprties: + These properties can be override using the prop argument with properties: * className: class names to be added * onMouseDown: event handler will be called after the component's event handler @@ -709,10 +720,10 @@ const twoSeconds = 2000 {({ styles, date }) => - // date is value of current date. Use this to render special styles for the marker - // or any other custom logic based on date: - // e.g. styles = {...styles, backgroundColor: isDateInAfternoon(date) ? 'red' : 'limegreen'} - return
+ // date is value of current date. Use this to render special styles for the marker + // or any other custom logic based on date: + // e.g. styles = {...styles, backgroundColor: isDateInAfternoon(date) ? 'red' : 'limegreen'} +
} ``` @@ -735,9 +746,7 @@ const today = Date.now() //custom renderer - {({ styles, date }) => - return
- } + {({ styles, date }) =>
} // multiple CustomMarkers @@ -767,10 +776,10 @@ Custom renderer for this marker. Ensure that you always pass `styles` to the roo //custom renderer {({ styles, date }) => - // date is value of current date. Use this to render special styles for the marker - // or any other custom logic based on date: - // e.g. styles = {...styles, backgroundColor: isDateInAfternoon(date) ? 'red' : 'limegreen'} - return
+ // date is value of current date. Use this to render special styles for the marker + // or any other custom logic based on date: + // e.g. styles = {...styles, backgroundColor: isDateInAfternoon(date) ? 'red' : 'limegreen'} +
} ``` @@ -1318,7 +1327,7 @@ You need to include the `Timeline.css` file, either via static file reference or ## How can I have items with different colors? -Now you can use item renderer for rendering items with different colors [itemRenderer](https://github.com/namespace-ee/react-calendar-timeline#itemrenderer). +Now you can use item renderer for rendering items with different colors [itemRenderer](https://github.com/namespace-ee/react-calendar-timeline#itemrenderer). Please refer to [examples](https://github.com/namespace-ee/react-calendar-timeline/tree/master/examples#custom-item-rendering) for a sandbox example ## How can I add a sidebar on the right? @@ -1450,3 +1459,6 @@ npm version patch ``` --> + +## License +[MIT licensed](/LICENSE.md). diff --git a/__fixtures__/itemsAndGroups.js b/__fixtures__/itemsAndGroups.js index 0ad6371d2..f98b3810b 100644 --- a/__fixtures__/itemsAndGroups.js +++ b/__fixtures__/itemsAndGroups.js @@ -1,19 +1,18 @@ -import moment from 'moment' export const items = [ { id: '0', group: '1', - start_time: moment('2018-10-26T10:46:40.000').valueOf(), - end_time: moment('2018-10-26T12:40:03.877').valueOf(), + start_time: 1540540000000, + end_time: 1540546803877, canMove: false, canResize: false }, { id: '5', group: '1', - start_time: moment('2018-10-26T08:46:40.000').valueOf(), - end_time: moment('2018-10-26T14:40:03.877').valueOf(), + start_time: 1540532800000, + end_time: 1540554003877, canMove: false, canResize: false, className: '' @@ -21,8 +20,8 @@ export const items = [ { id: '6', group: '1', - start_time: moment('2018-10-26T13:46:40.000').valueOf(), - end_time: moment('2018-10-26T20:40:03.877').valueOf(), + start_time: 1540550800000, + end_time: 1540575603877, canMove: false, canResize: false, className: '' @@ -30,16 +29,16 @@ export const items = [ { id: '1', group: '1', - start_time: moment('2018-10-26T19:06:40.000').valueOf(), - end_time: moment('2018-10-26T23:14:35.919').valueOf(), + start_time: 1540570000000, + end_time: 1540584875919, canMove: true, canResize: 'both' }, { id: '2', group: '1', - start_time: moment('2018-10-27T08:00:00.000').valueOf(), - end_time: moment('2018-10-27T13:39:57.548').valueOf(), + start_time: 1540620000000, + end_time: 1540640397548, canMove: false, canResize: false, className: '' @@ -47,8 +46,8 @@ export const items = [ { id: '3', group: '3', - start_time: moment('2018-10-27T18:00:00.000').valueOf(), - end_time: moment('2018-10-27T23:39:57.548').valueOf(), + start_time: 1540656000000, + end_time: 1540676397548, canMove: false, canResize: false, className: '' diff --git a/__fixtures__/stateAndProps.js b/__fixtures__/stateAndProps.js index c343c4905..6427947ec 100644 --- a/__fixtures__/stateAndProps.js +++ b/__fixtures__/stateAndProps.js @@ -1,10 +1,9 @@ import { defaultKeys } from 'lib/default-config' -import moment from 'moment' import {items} from './itemsAndGroups' export const props = { keys: defaultKeys, lineHeight: 30, - stackItems: true, + stackItems: 'space', itemHeightRatio: 0.75 } @@ -13,8 +12,8 @@ export const propsNoStack = { stackItems: false, } -export const visibleTimeStart = moment('2018-10-26T00:00:00.000') -export const visibleTimeEnd = moment('2018-10-27T00:00:00.000') +export const visibleTimeStart = 1540501200000 +export const visibleTimeEnd = 1540587600000 export const state = { draggingItem: undefined, @@ -23,9 +22,9 @@ export const state = { resizingEdge: null, resizeTime: null, newGroupOrder: null, - canvasTimeStart: moment('2018-10-25T00:00:00.000').valueOf(), - visibleTimeEnd: visibleTimeEnd.valueOf(), - visibleTimeStart: visibleTimeStart.valueOf(), + canvasTimeStart: 1540414800000, + visibleTimeEnd: visibleTimeEnd, + visibleTimeStart: visibleTimeStart, canvasTimeEnd: 1540674000000, width: 1000, } diff --git a/__tests__/components/Headers/CustomHeader.test.js b/__tests__/components/Headers/CustomHeader.test.js new file mode 100644 index 000000000..06883ce1f --- /dev/null +++ b/__tests__/components/Headers/CustomHeader.test.js @@ -0,0 +1,242 @@ +import React from 'react' +import { render, cleanup, prettyDOM } from 'react-testing-library' +import Timeline from 'lib/Timeline' +import DateHeader from 'lib/headers/DateHeader' +import SidebarHeader from 'lib/headers/SidebarHeader' +import TimelineHeaders from 'lib/headers/TimelineHeaders' +import CustomHeader from 'lib/headers/CustomHeader' + +import 'jest-dom/extend-expect' +import moment from 'moment' + +import { items, groups } from '../../../__fixtures__/itemsAndGroups' +import { visibleTimeEnd, visibleTimeStart } from '../../../__fixtures__/stateAndProps' + +const defaultProps = { + groups, + items, + defaultTimeStart: moment(visibleTimeStart, 'x'), + defaultTimeEnd: moment(visibleTimeEnd, 'x'), +} + +describe('CustomHeader Component Test', () => { + + afterEach(cleanup) + // Render The Example In The Docs + it('Given CustomHeader When render Then it should renderd Correctly in the timeline', () => { + const { getByTestId } = render( + + + + {({ getRootProps }) => { + return
Left
+ }} +
+ + + + {({ + headerContext: { intervals }, + getRootProps, + getIntervalProps, + showPeriod + }) => { + return ( +
+ {intervals.map(interval => { + const intervalStyle = { + // height: 30, + lineHeight: '30px', + textAlign: 'center', + borderLeft: '1px solid black', + cursor: 'pointer', + backgroundColor: 'Turquoise', + color: 'white' + } + return ( +
{ + showPeriod(interval.startTime, interval.endTime) + }} + {...getIntervalProps({ + interval, + style: intervalStyle + })} + > +
+ {interval.startTime.format('YYYY')} +
+
+ ) + })} +
+ ) + }} +
+
+
+ ) + + expect(getByTestId('customHeader')).toBeInTheDocument() + }) + it('Given CustomHeader When pass a unit to it Then it should take it', () => { + const { getByTestId, rerender } = render(getCustomHeadersInTimeline({ unit: "year" })); + expect(getByTestId('customHeader')).toHaveTextContent('01/01/2018') + + rerender(getCustomHeadersInTimeline({ unit: "month" })); + expect(getByTestId('customHeader')).toHaveTextContent('10/01/2018') + + rerender(getCustomHeadersInTimeline({ unit: "day" })); + expect(getByTestId('customHeader')).toHaveTextContent('10/25/2018') + expect(getByTestId('customHeader')).toHaveTextContent('10/26/2018') + expect(getByTestId('customHeader')).toHaveTextContent('10/27/2018') + }) + it('Given CustomHeader When pass a style props with (width, position) Then it should not ovverride the default values', () => { + const { getByTestId } = render(getCustomHeadersInTimeline({ props: { style: { width: 0, position: 'fixed' } } })); + const { width, position } = getComputedStyle(getByTestId('customHeader')) + expect(width).not.toBe('0px') + expect(position).not.toBe('fixed') + }) + + it('Given CustomHeader When pass a style props other than (width, position) Then it should renderd Correctly', () => { + const { getByTestId } = render(getCustomHeadersInTimeline({ props: { style: { height: 150 } } })); + const { height } = getComputedStyle(getByTestId('customHeader')) + expect(height).toBe("150px") + }) + + it('Given CustomHeader When pass an interval style with (width, position, left) Then it should not ovverride the default values', () => { + const { getByTestId } = render(getCustomHeadersInTimeline({ + intervalStyle: { + width: 0, + position: 'fixed', + left: 0 + } + })); + const { width, position, left } = getComputedStyle(getByTestId('customHeaderInterval')) + expect(width).not.toBe('0px') + expect(position).not.toBe('fixed') + expect(left).not.toBe('0px') + }) + it('Given CustomHeader When pass an interval style other than (width, position) Then it should rendered correctly', () => { + const { getByTestId } = render(getCustomHeadersInTimeline({ + intervalStyle: { + lineHeight: '30px', + textAlign: 'center', + borderLeft: '1px solid black', + cursor: 'pointer', + color: 'white' + } + })); + const { lineHeight, textAlign, borderLeft, cursor, color } = getComputedStyle(getByTestId('customHeaderInterval')) + expect(lineHeight).toBe('30px') + expect(textAlign).toBe('center') + expect(borderLeft).toBe('1px solid black') + expect(cursor).toBe('pointer') + expect(color).toBe('white') + }) + it('Given a CustomHeader When pass a jsx as a children Then it Should be rendered Correctly', () => { + const { getByText } = render(getCustomHeadersInTimeline()) + expect(getByText('Should Be Rendered')).toBeInTheDocument() + }) + it('Given a CustomHeader When not pass any unit prop Then it Should take the default timeline unit (year)', () => { + const { getByTestId } = render(getCustomHeadersInTimeline()) + expect(getByTestId('customHeader')).toHaveTextContent('01/01/2018') + }) + it('Given DateHeader When Rendered with day unit Then getIntervalStyle function shuold be act correctly', () => { + const props = getIntervalStyle({ startTime: moment("2018-10-27T21:00:00.000"), canvasTimeStart: 1540414800000, ratio: 0.5, unit: 'month', labelWidth: 150 }) + expect(props).toEqual({ + left: 124200000, + width: 150, + position: 'absolute' + }) + }) + it('Given CustomHeader When pass a style in props obj to props renderer Then it should not override correctly render it', () => { + const { getByTestId } = render(getCustomHeadersInTimeline({props: {'aria-hidden': false}})) + expect(getByTestId('customHeader')).toHaveAttribute('aria-hidden') + + }) + describe('Testing Diffrent Unit Values', () => { + it('Given CustomHeader When pass a year unit then it should render it correctly', () => { + const { getByTestId } = render(getCustomHeadersInTimeline({ unit: 'year' })); + expect(getByTestId('customHeader')).toHaveTextContent('01/01/2018') + }) + it('Given CustomHeader When pass a month unit then it should render it correctly', () => { + const { getByTestId } = render(getCustomHeadersInTimeline({ unit: 'month' })); + expect(getByTestId('customHeader')).toHaveTextContent('10/01/2018') + }) + it('Given CustomHeader When pass a day unit then it should render it correctly', () => { + const { getByTestId } = render(getCustomHeadersInTimeline({ unit: 'day' })); + expect(getByTestId('customHeader')).toHaveTextContent('10/25/2018') + }) + it('Given CustomHeader When pass a hour unit then it should render it correctly', () => { + const { getByTestId } = render(getCustomHeadersInTimeline({ unit: 'hour' })); + expect(getByTestId('customHeader')).toHaveTextContent('10/25/2018') + }) + }) + +}) + +function getCustomHeadersInTimeline({ unit = "year", props, intervalStyle } = {}) { + + return ( + + + + {({ getRootProps }) => { + return
Left
+ }} +
+ + + + {({ + headerContext: { intervals }, + getRootProps, + getIntervalProps, + showPeriod + }, extraProps) => { + return ( +
+ {intervals.map(interval => { + return ( +
{ + showPeriod(interval.startTime, interval.endTime) + }} + {...getIntervalProps({ + interval, + style: intervalStyle + })} + > +
+ {interval.startTime.format('MM/DD/YYYY')} +
+
+ ) + })} +
+ Should Be Rendered +
+
+ + ) + }} +
+
+
+ ) +} + + +const getIntervalStyle = ({ startTime, canvasTimeStart, ratio, unit, labelWidth, style }) => { + const left = Math.round((startTime.valueOf() - canvasTimeStart) * ratio) + const unitValue = startTime.get(unit === 'day' ? 'date' : unit) + const firstOfType = unitValue === (unit === 'day' ? 1 : 0) + const leftCorrect = firstOfType ? 1 : 0 + return { + ...style, + left: left - leftCorrect, + width: labelWidth, + position: 'absolute' + } +} diff --git a/__tests__/components/Headers/DateHeader.test.js b/__tests__/components/Headers/DateHeader.test.js new file mode 100644 index 000000000..857d2dbf0 --- /dev/null +++ b/__tests__/components/Headers/DateHeader.test.js @@ -0,0 +1,401 @@ +import React from 'react' +import { render, cleanup, within } from 'react-testing-library' +import Timeline from 'lib/Timeline' +import DateHeader from 'lib/headers/DateHeader' +import SidebarHeader from 'lib/headers/SidebarHeader' +import TimelineHeaders from 'lib/headers/TimelineHeaders' +import 'jest-dom/extend-expect' +import moment from 'moment' +import { items, groups } from '../../../__fixtures__/itemsAndGroups' +import { visibleTimeEnd, visibleTimeStart } from '../../../__fixtures__/stateAndProps' +import { TimelineStateProvider } from 'lib/timeline/TimelineStateContext' +import { TimelineHeadersProvider } from 'lib/headers/HeadersContext' +const defaultProps = { + groups, + items, + defaultTimeStart: moment(visibleTimeStart, 'x'), + defaultTimeEnd: moment(visibleTimeEnd, 'x'), +} + +describe("Testing DateHeader Component", () => { + beforeEach(() => { + + Element.prototype.getBoundingClientRect = jest.fn(() => { + return { + width: 1000, + height: 120, + top: 0, + left: 0, + bottom: 0, + right: 0, + } + }); + }) + afterEach(cleanup) + + // Testing The Example In The Docs + it("Given DateHeader When rendered Then it should be rendered correctly in the timeLine", () => { + const { getAllByTestId } = render( + + + + {({ getRootProps }) => { + return
Left
+ }} +
+ + + { + return
+ {intervalContext.intervalText} +
+ }} + /> +
+
+ ) + + expect(getAllByTestId('dateHeader')).toHaveLength(3) + + }) + + it("Given Dateheader When pass a string typed labelFormat Then it should render the intervals with hte given format", () => { + const { getAllByTestId } = render(dateHeaderComponent({ unit: "day", labelFormat: "MM/DD" })); + expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('10/25') + expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('10/26') + expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('10/27') + expect(getAllByTestId('dateHeader')[0]).toHaveTextContent('Thursday, October 25') + + }) + + it("Given Dateheader When pass an object typed labelFormat Then it should render the intervals with hte given format", () => { + const { getAllByTestId } = render(dateHeaderComponent({ unit: "day", labelFormat: { day: { long: "MM/DD/YYYY" } } })); + + expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('10/25/2018') + expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('10/26/2018') + expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('10/27/2018') + expect(getAllByTestId('dateHeader')[0]).toHaveTextContent('Thursday, October 25') + }) + it("Given Dateheader When pass a function typed labelFormat Then it should render the intervals with hte given format", () => { + const formatlabel = jest.fn((interval, unit, labelWidth) => interval[0].format("MM/DD/YYYY")) + const { getAllByTestId } = render(dateHeaderComponent({ unit: "day", labelFormat: formatlabel })); + + expect(formatlabel).toHaveBeenCalled() + + expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('10/25/2018') + expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('10/26/2018') + expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('10/27/2018') + + expect(getAllByTestId('dateHeader')[0]).toHaveTextContent('Thursday, October 25') + + }) + + it("Given Dateheader When pass a string typed labelFormat Then it should be called with the right params", () => { + const formatlabel = jest.fn((interval, unit, labelWidth) => interval[0].format("MM/DD/YYYY")) + render(dateHeaderComponent({ unit: "day", labelFormat: formatlabel })) + expect(formatlabel).toHaveBeenCalled() + expect(formatlabel).toHaveBeenCalledWith(expect.any(Array), "day", expect.any(Number)) + }) + + + + it("Given Dateheader When click on the primary header Then it should change the unit", async () => { + + const formatlabel = jest.fn((interval, unit, labelWidth) => interval[0].format("MM/DD/YYYY")) + const { getByTestId, getAllByTestId } = render(dateHeaderComponent({ unit: "day", labelFormat: formatlabel })); + // Arrange + const primaryHeader = getByTestId('dateHeader') + const seconderyHeader = getAllByTestId('dateHeader')[2] + + // Act + const primaryFirstClick = within(primaryHeader).getByText('Friday, October 26, 2018').parentElement + primaryFirstClick.click() + const primarySecondClick = within(primaryHeader).getByText('October 2018').parentElement + primarySecondClick.click() + + // Assert + expect(seconderyHeader).toHaveTextContent('January') + expect(primaryHeader).toHaveTextContent('2018') + }) + + it("Given Dateheader When click on the secondary header Then it should change the unit", async () => { + + const formatlabel = jest.fn((interval, unit, labelWidth) => interval[0].format("MM/DD/YYYY")) + const { getByTestId, getAllByTestId } = render(dateHeaderComponent({ unit: "day", labelFormat: formatlabel })); + // Arrange + const primaryHeader = getByTestId('dateHeader') + const seconderyHeader = getAllByTestId('dateHeader')[2] + + const primaryFirstClick = within(primaryHeader).getByText('Friday, October 26, 2018').parentElement + primaryFirstClick.click() + const primarySecondClick = within(primaryHeader).getByText('October 2018').parentElement + + primarySecondClick.click() + + // Act + const secondaryFirstClick = within(seconderyHeader).queryByText('January') + secondaryFirstClick.click() + expect(primaryHeader).toHaveTextContent('December 2016') + const secondarySecondClick = within(seconderyHeader).queryByText('1') + secondarySecondClick.click() + expect(primaryHeader).toHaveTextContent('Wednesday, November 30, 2016') + }) + + it('Given DateHeadr When click on primary or secondary Then onTimeChange function should be called with the right params', () => { + const handleTimeChange = jest.fn((visibleTimeStart, visibleTimeEnd, updateScrollCanvas) => + updateScrollCanvas(visibleTimeStart, visibleTimeEnd)) + const { getByTestId, getAllByTestId, debug } = render(dateHeaderComponent({ unit: "day", handleTimeChange: handleTimeChange })); + const primaryHeader = within(getByTestId('dateHeader')).getByText('Friday, October 26, 2018').parentElement + primaryHeader.click() + const secondaryHeader = within(getAllByTestId('dateHeader')[1]).getByText('1').parentElement + expect(handleTimeChange).toBeCalled() + expect(handleTimeChange).toBeCalledTimes(1) + expect(handleTimeChange).toBeCalledWith(expect.any(Number), expect.any(Number), expect.any(Function)) + expect(handleTimeChange).toReturn() + + secondaryHeader.click(); + expect(handleTimeChange).toBeCalled() + expect(handleTimeChange).toBeCalledTimes(1) + expect(handleTimeChange).toBeCalledWith(expect.any(Number), expect.any(Number), expect.any(Function)) + expect(handleTimeChange).toReturn() + }) + + it('Given Dateheader When pass a className Then it should be applied to DateHeader', () => { + const { getAllByTestId } = render(dateHeaderComponent({ labelFormat: "MM/DD/YYYY", className: 'test-class-name' })); + expect(getAllByTestId('dateHeader')[1]).toHaveClass('test-class-name') + }) + + it('Given Interval When pass an ovveride values for (width, left, position) it should not ovverride the default values', () => { + const { getAllByTestId } = render(dateHeaderComponent({ labelFormat: "MM/DD/YYYY", props: { style: { width: 100, position: 'fixed' } } })); + const { width, position } = getComputedStyle(getAllByTestId('interval')[0]) + expect(width).not.toBe('100px') + expect(position).not.toBe('fixed') + }) + + it('Given Interval When pass an override (width, position) Then it should render the default values for it', () => { + const { getAllByTestId } = render(dateHeaderComponent({ labelFormat: "MM/DD/YYYY", props: { style: { width: 100, position: 'fixed' } } })); + const { width, position } = getComputedStyle(getAllByTestId('interval')[0]) + expect(width).toBe('36px') + expect(position).toBe('absolute') + + + }) + it('Given Interval When pass any style other than (position, width, left) through the Dateheader Then it should take it', () => { + const { getAllByTestId } = render(dateHeaderComponent({ labelFormat: "MM/DD/YYYY", props: { style: { display: 'flex' } } })); + const { display } = getComputedStyle(getAllByTestId('interval')[0]) + + expect(display).toBe('flex') + + }) + it('Given unit Dateheader When pass a style Object Then it should render the given style correctly', () => { + const { getAllByTestId } = render(dateHeaderComponent({ style: { height: 50 }, labelFormat: "MM/DD/YYYY" })); + const { height } = getComputedStyle(getAllByTestId('dateHeader')[1]) + + expect(height).toBe('50px') + }) + + it('Given DateHeader component When pass an intervalRenderer prop then it should be called with the right params', () => { + const intervalRenderer = jest.fn(({ getIntervalProps, intervalContext }, props) =>
{intervalContext.intervalText}
) + const { getByTestId, rerender } = render(dateHeaderWithIntervalRenderer({ intervalRenderer: intervalRenderer })) + const bluePrint = { + getIntervalProps: expect.any(Function), + intervalContext: expect.any(Object) + } + expect(intervalRenderer).toBeCalled() + expect(intervalRenderer).toReturn() + // because we did not pass a props then the function will called with undefined props + expect(intervalRenderer).toBeCalledWith(expect.objectContaining(bluePrint), undefined) + rerender(dateHeaderWithIntervalRenderer({ intervalRenderer: intervalRenderer, props: { style: { height: 50 } } })) + expect(intervalRenderer).toBeCalledWith(expect.objectContaining(bluePrint), expect.any(Object)) + expect(getByTestId('myAwesomeInterval')).toBeInTheDocument() + }) + describe('Testing The Label Format Diffrent Cases', () => { + it('Given DateHeader When resize the width of the screen to 1000 Then it Should take the long format', () => { + Element.prototype.getBoundingClientRect = jest.fn(() => ({ width: 1000 })); + const { getAllByTestId, rerender } = render(dateHeaderComponent({ + unit: "day", labelFormat: { + day: { short: "DD", medium: "DD/MM", mediumLong: "MM/YYYY", long: "MM/DD/YYYY", } + } + })); + expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('10/26/2018') + }) + + it('Given DateHeader When resize the width of the screen to 250 Then it Should take the mediumLong format', () => { + Element.prototype.getBoundingClientRect = jest.fn(() => ({ width: 250 })); + const { getAllByTestId } = render(dateHeaderComponent({ + unit: "day", labelFormat: { + day: { short: "DD", medium: "DD/MM", mediumLong: "MM/YYYY", long: "MM/DD/YYYY", } + } + })); + expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('10/2018') + expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('10/2018') + expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('10/2018') + }) + it('Given DateHeader When resize the width of the screen to 200 Then it Should take the medium format', () => { + Element.prototype.getBoundingClientRect = jest.fn(() => ({ width: 200 })); + const { getAllByTestId } = render(dateHeaderComponent({ + unit: "day", labelFormat: { + day: { short: "DD", medium: "DD/MM", mediumLong: "MM/YYYY", long: "MM/DD/YYYY", } + } + })); + expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('25/10') + expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('26/10') + expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('27/10') + }) + it('Given DateHeader When resize the width of the screen to 100 Then it Should take the short format', () => { + Element.prototype.getBoundingClientRect = jest.fn(() => ({ width: 100 })); + const { getAllByTestId } = render(dateHeaderComponent({ + unit: "day", labelFormat: { + day: { short: "DD", medium: "DD/MM", mediumLong: "MM/YYYY", long: "MM/DD/YYYY", } + } + })); + expect(getAllByTestId('dateHeader')[1]).toHaveTextContent('26') + }) + }) + + describe('Testing Diffrent Unit Values', () => { + it('Given DateHeader When pass a year unit to the timeline then it should take it as default', () => { + const children = + const {getAllByTestId} = renderDateHeaderWithContext({unit: 'year', children: children}); + const primaryHeader = getAllByTestId('dateHeader')[0] + const secondaryHeader = getAllByTestId('dateHeader')[1] + expect(primaryHeader).toHaveTextContent('2019') + expect(secondaryHeader).toHaveTextContent('2019') + }) + it('Given DateHeader When pass a month unit to the timeline then it should take it as default', () => { + const children = + const {getAllByTestId} = renderDateHeaderWithContext({unit: 'month', children: children}); + const primaryHeader = getAllByTestId('dateHeader')[0] + const secondaryHeader = getAllByTestId('dateHeader')[1] + expect(primaryHeader).toHaveTextContent('2019') + expect(secondaryHeader).toHaveTextContent('January 2019') + }) + it('Given DateHeader When pass a day unit to the timeline then it should take it as default', () => { + const children = + const {getAllByTestId} = renderDateHeaderWithContext({unit: 'day', children: children}); + const primaryHeader = getAllByTestId('dateHeader')[0] + const secondaryHeader = getAllByTestId('dateHeader')[1] + expect(primaryHeader).toHaveTextContent('January 2019') + expect(secondaryHeader).toHaveTextContent('Monday, January 21, 2019') + }) + it('Given DateHeader When pass a hour unit to the timeline then it should take it as default', () => { + const children = + const {getAllByTestId} = renderDateHeaderWithContext({unit: 'hour', children: children}); + const primaryHeader = getAllByTestId('dateHeader')[0] + const secondaryHeader = getAllByTestId('dateHeader')[1] + expect(primaryHeader).toHaveTextContent('Monday, January 21, 2019') + expect(secondaryHeader).toHaveTextContent('30') + }) + }) + +}) + +function dateHeaderComponent({ labelFormat, unit, props, className, style, handleTimeChange } = {}) { + + return ( + + + + + {({ getRootProps }) => { + return
Left
+ }} +
+ + { + return
+ {intervalContext.intervalText} + +
+ }} + /> + +
+
+ ) +} + +function dateHeaderWithIntervalRenderer({ intervalRenderer, props } = {}) { + + return ( + + + + + {({ getRootProps }) => { + return
Left
+ }} +
+ + + +
+
+ ) +} + + +function renderDateHeaderWithContext({unit, children} = {}) +{ + const oneDay = 1000 * 60 * 60 * 24 + const now = Date.now() + const visibleTimeStart = now - oneDay + const visibleTimeEnd = now + oneDay + const defaultTimelineState = { + visibleTimeStart, + visibleTimeEnd, + canvasTimeStart: visibleTimeStart - oneDay, + canvasTimeEnd: visibleTimeEnd + oneDay, + canvasWidth: 3000, + visibleWidth: 1000, + showPeriod:()=>{}, + timelineWidth:1000, + timelineUnit:unit + } + + return render( + + React.createRef()} + rightSidebarWidth={0} + > + {children} + + + ) + +} diff --git a/__tests__/components/Headers/SideBarHeader.test.js b/__tests__/components/Headers/SideBarHeader.test.js new file mode 100644 index 000000000..b15c13a3b --- /dev/null +++ b/__tests__/components/Headers/SideBarHeader.test.js @@ -0,0 +1,164 @@ +import React from 'react' +import { render, cleanup, within } from 'react-testing-library' +import Timeline from 'lib/Timeline' +import DateHeader from 'lib/headers/DateHeader' +import SidebarHeader from 'lib/headers/SidebarHeader' +import TimelineHeaders from 'lib/headers/TimelineHeaders' +import 'jest-dom/extend-expect' + +import moment from 'moment' + +import { items, groups } from '../../../__fixtures__/itemsAndGroups' + + +const defaultProps = { + groups, + items, + defaultTimeStart: moment('1995-12-25').add(-12, 'hour'), + defaultTimeEnd: moment('1995-12-25').add(12, 'hour') +} + +describe("Testing SidebarHeader Component", () => { + afterEach(cleanup) + // Testing The Example In The Docs + it("Given SidebarHeader When rendered Then it should shown correctly in the timeline", () => { + const { container } = render( + + + + {({ getRootProps }) => { + return
Left
+ }} +
+ + {({ getRootProps }) => { + return
Right
+ }} +
+ + +
+
+ ) + + const { getByTestId } = within(container) + + expect(getByTestId('leftSidebarHeader')).toBeInTheDocument() + expect(getByTestId('rightSidebarHeader')).toBeInTheDocument() + + expect(getByTestId('leftSidebarHeader').nextElementSibling).toHaveAttribute('data-testid', 'headerContainer') + expect(getByTestId('rightSidebarHeader').previousElementSibling).toHaveAttribute('data-testid', 'headerContainer') + }) + + it("Given SidebarHeader When passing no variant prop Then it should rendered above the left sidebar", () => { + const { getByTestId, getAllByTestId } = renderSidebarHeaderWithCustomValues() + expect(getByTestId('sidebarHeader')).toBeInTheDocument() + expect(getByTestId('sidebarHeader').nextElementSibling).toHaveAttribute('data-testid', 'headerContainer') + expect(getAllByTestId('sidebarHeader')).toHaveLength(1) + + + }) + it("Given SidebarHeader When passing variant prop with left value Then it should rendered above the left sidebar", () => { + const { getByTestId, getAllByTestId } = renderSidebarHeaderWithCustomValues({ variant: "left" }) + expect(getByTestId('sidebarHeader')).toBeInTheDocument() + expect(getByTestId('sidebarHeader').nextElementSibling).toHaveAttribute('data-testid', 'headerContainer') + expect(getAllByTestId('sidebarHeader')).toHaveLength(1) + }) + it("Given SidebarHeader When passing variant prop with right value Then it should rendered above the right sidebar", () => { + const { getByTestId, getAllByTestId } = renderSidebarHeaderWithCustomValues({ variant: "right" }) + expect(getByTestId('sidebarHeader')).toBeInTheDocument() + expect(getByTestId('sidebarHeader').previousElementSibling).toHaveAttribute('data-testid', 'headerContainer') + expect(getAllByTestId('sidebarHeader')).toHaveLength(1) + }) + + it("Given SidebarHeader When passing variant prop with unusual value Then it should rendered above the left sidebar by default", () => { + const { getByTestId } = renderSidebarHeaderWithCustomValues({ variant: "" }) + expect(getByTestId('sidebarHeader')).toBeInTheDocument() + expect(getByTestId('sidebarHeader').nextElementSibling).toHaveAttribute('data-testid', 'headerContainer') + }) + + it("Given SidebarHeader When passing props to the props getter Then it should rendered correctly", () => { + const { getByTestId } = renderSidebarHeaderWithCustomValues({ props: { style: { width: 250 } } }) + const { width } = getComputedStyle(getByTestId("sidebarHeader")) + expect(width).toBe("250px") + }) + + it("Given SidebarHeader When passing children to it Then it should rendered correctly", () => { + const { getByText } = renderSidebarHeaderWithCustomValues() + expect(getByText("Should Be Rendred")).toBeInTheDocument() + }) + + it("Given sidebarheader When pass a variant and props Then it should render both correctly", () => { + const { getByTestId } = renderSidebarHeaderWithCustomValues({ props: { style: { width: 250 }, variant: "left" } }) + const { width } = getComputedStyle(getByTestId("sidebarHeader")) + expect(width).toBe("250px") + expect(getByTestId('sidebarHeader').nextElementSibling).toHaveAttribute('data-testid', 'headerContainer') + }) + + it("Given two sidebarheaders When pass a variants and props Then it should render both correctly", () => { + const { getByText } = renderTwoSidebarHeadersWithCustomValues({ props: { style: { width: 250 } } }) + const { width: leftWidth } = getComputedStyle(getByText('LeftSideBar')) + const { width: rightWidth } = getComputedStyle(getByText('RightSideBar')) + expect(leftWidth).toBe("250px") + expect(rightWidth).toBe("250px") + expect(getByText('LeftSideBar').nextElementSibling).toHaveAttribute('data-testid', 'headerContainer') + expect(getByText('RightSideBar').previousElementSibling).toHaveAttribute('data-testid', 'headerContainer') + }) + + it('Given SidebarHeader When Pass an props obj to props renderer Then it should render it correctly', () => { + const { getByTestId } = renderSidebarHeaderWithCustomValues({ props: { 'aria-hidden': false } }) + expect(getByTestId("sidebarHeader")).toHaveAttribute('aria-hidden') + }) + +}) + + +function renderSidebarHeaderWithCustomValues({ variant = undefined, props, rightSidebarWidth } = {}) { + return render( + + + {({ getRootProps }, extraProps) => { + return
SidebarHeader +
Should Be Rendred
+
+ }} +
+ + +
+
+ ) +} + + +function renderTwoSidebarHeadersWithCustomValues({ props, rightSidebarWidth } = {}) { + return render( + + + {({ getRootProps }) => { + return
LeftSideBar +
Should Be Rendred
+
+ }} +
+ + {({ getRootProps }, props) => { + return
RightSideBar +
+ }} +
+ + +
+
+ ) +} + diff --git a/__tests__/components/Headers/TimelineHeader.test.js b/__tests__/components/Headers/TimelineHeader.test.js new file mode 100644 index 000000000..96ab239f0 --- /dev/null +++ b/__tests__/components/Headers/TimelineHeader.test.js @@ -0,0 +1,239 @@ +import { render, cleanup } from 'react-testing-library' +import Timeline from 'lib/Timeline' +import SidebarHeader from 'lib/headers/SidebarHeader' +import DateHeader from 'lib/headers/DateHeader' +import TimelineHeaders from 'lib/headers/TimelineHeaders' +import 'jest-dom/extend-expect' + +import React from 'react' +import moment from 'moment' + +import { items, groups } from '../../../__fixtures__/itemsAndGroups' +import { + visibleTimeStart, + visibleTimeEnd +} from '../../../__fixtures__/stateAndProps' + +const defaultProps = { + groups, + items, + defaultTimeStart: moment('1995-12-25').add(-12, 'hour'), + defaultTimeEnd: moment('1995-12-25').add(12, 'hour') +} + +describe('TimelineHeader', () => { + beforeEach(() => { + Element.prototype.getBoundingClientRect = jest.fn(() => { + return { + width: 1000, + height: 120, + top: 0, + left: 0, + bottom: 0, + right: 0, + } + }); + }) + afterEach(cleanup) + /** + * Testing The Default Functionality + */ + describe('renders default headers correctly', () => { + it('Given TimelineHeader When rendered Then it should render tow date headers by default', () => { + const { getAllByTestId, getByTestId } = renderDefaultTimeline() + expect(getAllByTestId('dateHeader')).toHaveLength(2) + expect(getByTestId('headerContainer').children).toHaveLength(2) + }) + it('Given TimelineHeader When rendered Then it should render a left sidebar by default', () => { + const { getByTestId } = renderDefaultTimeline() + expect(getByTestId("sidebarHeader")).toBeInTheDocument() + }) + it('Given TimelineHeader When pass a rightsidebarWidth Then it should render two sidebar headers', () => { + let rightSidebarWidth = 150; + const { getAllByTestId } = renderDefaultTimeline({ rightSidebarWidth }); + const sidebarHeaders = getAllByTestId('sidebarHeader') + + expect(sidebarHeaders).toHaveLength(2) + expect(sidebarHeaders[0]).toBeInTheDocument() + expect(sidebarHeaders[1]).toBeInTheDocument() + const { width } = getComputedStyle(sidebarHeaders[1]) + expect(width).toBe("150px") + + + }) + it('Given TimelineHeader When rendered Then it should render a two dateHeadrs default', () => { + const { getAllByTestId } = renderDefaultTimeline(); + const dateHeaders = getAllByTestId("dateHeader") + + expect(dateHeaders).toHaveLength(2) + expect(dateHeaders[1].childElementCount).toBeGreaterThan(dateHeaders[0].childElementCount) + + }) + + it("Given TimelineHeader When pass a left sidebar as a child Then it should render a left sidebar", () => { + const { getByTestId, getAllByTestId } = renderTimelineWithVariantSidebar({ variant: "left" }); + expect(getByTestId('sidebarHeader')).toBeInTheDocument(); + expect(getAllByTestId('sidebarHeader')).toHaveLength(1) + }) + it("Given TimelineHeader When pass a right sidebar as a child Then it should render a right sidebar", () => { + const { getByTestId, getAllByTestId } = renderTimelineWithVariantSidebar({ variant: "right" }); + expect(getByTestId('sidebarHeader')).toBeInTheDocument(); + expect(getAllByTestId('sidebarHeader')).toHaveLength(1) + }) + it("Given TimelineHeader When pass a left and right sidebars as children Then it should render a left and right sidebars", () => { + const { getByTestId } = renderTimelineWithLeftAndRightSidebar(); + expect(getByTestId('left-header')).toBeInTheDocument(); + expect(getByTestId('right-header')).toBeInTheDocument(); + }) + + it("Given TimelineHeader When pass calendarHeaderStyle with overrided (overflow, width) Then it should not override the deaful values", () => { + const { getByTestId } = renderTimelineWithLeftAndRightSidebar({ calendarHeaderStyle: { overflow: 'unset', width: 0 } }); + const headerContainer = getByTestId('headerContainer') + const { width, overflow } = getComputedStyle(headerContainer) + + + expect(overflow).not.toBe('unset') + expect(width).not.toBe("0px") + + }) + it("Given TimelineHeader When pass rootStyle with overrided (display, width) Then it should not override the deaful values", () => { + const { getByTestId } = renderTimelineWithLeftAndRightSidebar({ style: { display: 'none', width: 0 } }); + const rootDiv = getByTestId('headerRootDiv') + const { width, display } = getComputedStyle(rootDiv) + + expect(display).not.toBe('none') + expect(width).not.toBe("0px") + + }) + it("Given TimelineHeader When pass calendarHeaderClassName Then it should be applied to the date header container", () => { + const { getByTestId } = renderTimelineWithLeftAndRightSidebar({ calendarHeaderClassName: "testClassName" }); + expect(getByTestId("headerContainer")).toHaveClass("testClassName") + }) + }) + + it('Given TimelineHeader When rendered Then it should render the default styles of the date header container', () => { + const { getByTestId } = renderTimelineWithLeftAndRightSidebar(); + const headerContainer = getByTestId('headerContainer') + const { overflow } = getComputedStyle(headerContainer) + expect(overflow).toBe('hidden') + // The JSDOM will not fire the calc css function + }) + + it('Given TimelineHeader When rendered Then it should render the default styles of the rootStyle', () => { + const { getByTestId } = renderTimelineWithLeftAndRightSidebar(); + const rootDiv = getByTestId('headerRootDiv') + const { width, display } = getComputedStyle(rootDiv) + + expect(display).toBe('flex') + expect(width).toBe("100%") + }) + + /** + * Testing The Example Provided In The Docs + */ + it('Given TimelineHeader When pass a headers as children Then it should render them correctly', () => { + const { getByText, rerender, queryByText } = render( + + + + {({ getRootProps }) => { + return
Left
+ }} +
+ + {({ getRootProps }) => { + return ( +
+ Right +
+ ) + }} +
+ +
+
+ ) + expect(getByText('Left')).toBeInTheDocument() + expect(getByText('Right')).toBeInTheDocument() + rerender( + + + + {({ getRootProps }) => { + return
Left
+ }} +
+ +
+
+ ) + expect(queryByText('Right')).toBeNull() + }) +}) + +function renderDefaultTimeline(props = {}) { + const timelineProps = { + ...defaultProps, + ...props + } + return render() +} + +function renderTimelineWithVariantSidebar({ props, variant } = {}) { + const timelineProps = { + ...defaultProps, + ...props + } + return render( + + + + {({ getRootProps }) => { + return
Header
+ }} +
+
+
+ ) +} + + +function renderTimelineWithLeftAndRightSidebar({ props, calendarHeaderClassName, calendarHeaderStyle, style } = {}) { + + const timelineProps = { + ...defaultProps, + ...props + } + + return render( + + + + {({ getRootProps }) => { + return
Right
+ }} +
+ + {({ getRootProps }) => { + return
Left
+ }} +
+
+
+ ) +} + + + diff --git a/__tests__/index.js b/__tests__/index.js index 3122950ba..b93609565 100644 --- a/__tests__/index.js +++ b/__tests__/index.js @@ -132,7 +132,7 @@ xdescribe('Timeline', () => { diff --git a/__tests__/utils/calendar/__snapshots__/calculate-scroll-canvas.js.snap b/__tests__/utils/calendar/__snapshots__/calculate-scroll-canvas.js.snap index 0d4c752b1..757fcc868 100644 --- a/__tests__/utils/calendar/__snapshots__/calculate-scroll-canvas.js.snap +++ b/__tests__/utils/calendar/__snapshots__/calculate-scroll-canvas.js.snap @@ -18,7 +18,7 @@ Object { "index": 0, }, "stack": true, - "top": 7.5, + "top": 3.75, "width": 78.74857638888886, }, "id": "0", @@ -36,7 +36,7 @@ Object { "index": 0, }, "stack": true, - "top": 37.5, + "top": 33.75, "width": 245.4152430555556, }, "id": "5", @@ -54,7 +54,7 @@ Object { "index": 0, }, "stack": true, - "top": 7.5, + "top": 3.75, "width": 287.08190972222224, }, "id": "6", @@ -72,7 +72,7 @@ Object { "index": 0, }, "stack": true, - "top": 37.5, + "top": 33.75, "width": 172.1749884259259, }, "id": "1", @@ -90,7 +90,7 @@ Object { "index": 0, }, "stack": true, - "top": 7.5, + "top": 3.75, "width": 236.08273148148123, }, "id": "2", @@ -108,23 +108,23 @@ Object { "index": 2, }, "stack": true, - "top": 105, + "top": 93.75, "width": 236.08273148148146, }, "id": "3", }, ], "groupHeights": Array [ - 67.5, + 60, + 30, 30, - 37.5, ], "groupTops": Array [ 0, - 67.5, - 97.5, + 60, + 90, ], - "height": 135, + "height": 120, "visibleTimeEnd": 1540634400000, "visibleTimeStart": 1540548000000, } @@ -148,7 +148,7 @@ Object { "index": 0, }, "stack": true, - "top": 7.5, + "top": 3.75, "width": 75.59863333333351, }, "id": "0", @@ -166,7 +166,7 @@ Object { "index": 0, }, "stack": true, - "top": 37.5, + "top": 33.75, "width": 235.5986333333335, }, "id": "5", @@ -184,7 +184,7 @@ Object { "index": 0, }, "stack": true, - "top": 7.5, + "top": 3.75, "width": 275.5986333333335, }, "id": "6", @@ -202,7 +202,7 @@ Object { "index": 0, }, "stack": true, - "top": 37.5, + "top": 33.75, "width": 165.28798888888878, }, "id": "1", @@ -220,7 +220,7 @@ Object { "index": 0, }, "stack": true, - "top": 7.5, + "top": 3.75, "width": 226.6394222222225, }, "id": "2", @@ -238,23 +238,23 @@ Object { "index": 2, }, "stack": true, - "top": 105, + "top": 93.75, "width": 226.6394222222225, }, "id": "3", }, ], "groupHeights": Array [ - 67.5, + 60, + 30, 30, - 37.5, ], "groupTops": Array [ 0, - 67.5, - 97.5, + 60, + 90, ], - "height": 135, + "height": 120, "visibleTimeEnd": 1540591200000, "visibleTimeStart": 1540501200000, } diff --git a/__tests__/utils/calendar/__snapshots__/get-next-unit.js.snap b/__tests__/utils/calendar/__snapshots__/get-next-unit.js.snap new file mode 100644 index 000000000..b56de34c6 --- /dev/null +++ b/__tests__/utils/calendar/__snapshots__/get-next-unit.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getNextUnit unknown value to throw error 1`] = `"unit foo in not acceptable"`; diff --git a/__tests__/utils/calendar/__snapshots__/group-stack.js.snap b/__tests__/utils/calendar/__snapshots__/group-stack.js.snap index 6f22034d7..bf2b6c159 100644 --- a/__tests__/utils/calendar/__snapshots__/group-stack.js.snap +++ b/__tests__/utils/calendar/__snapshots__/group-stack.js.snap @@ -4,6 +4,6 @@ exports[`groupStack works as expected 1`] = ` Object { "groupHeight": 0, "itemTop": 7.5, - "verticalMargin": 37.5, + "verticalMargin": 18.75, } `; diff --git a/__tests__/utils/calendar/__snapshots__/stack-group.js.snap b/__tests__/utils/calendar/__snapshots__/stack-group.js.snap index bbe832509..9fe6769be 100644 --- a/__tests__/utils/calendar/__snapshots__/stack-group.js.snap +++ b/__tests__/utils/calendar/__snapshots__/stack-group.js.snap @@ -8,6 +8,20 @@ Object { `; exports[`stackGroup should stack list of items 1`] = ` +Object { + "groupHeight": 0, + "verticalMargin": 3.75, +} +`; + +exports[`stackGroup should stack list of items lines 1`] = ` +Object { + "groupHeight": 180, + "verticalMargin": 7.5, +} +`; + +exports[`stackGroup should stack list of items space 1`] = ` Object { "groupHeight": 0, "verticalMargin": 7.5, diff --git a/__tests__/utils/calendar/__snapshots__/stack-items.js.snap b/__tests__/utils/calendar/__snapshots__/stack-items.js.snap index 8d27dc42d..9d0000a46 100644 --- a/__tests__/utils/calendar/__snapshots__/stack-items.js.snap +++ b/__tests__/utils/calendar/__snapshots__/stack-items.js.snap @@ -16,7 +16,7 @@ Object { "index": 0, }, "stack": true, - "top": 7.5, + "top": 3.75, "width": 236.24572916666602, }, "id": "0", @@ -34,7 +34,7 @@ Object { "index": 0, }, "stack": true, - "top": 37.5, + "top": 33.75, "width": 736.245729166666, }, "id": "5", @@ -52,7 +52,7 @@ Object { "index": 0, }, "stack": true, - "top": 7.5, + "top": 3.75, "width": 861.245729166666, }, "id": "6", @@ -70,7 +70,7 @@ Object { "index": 0, }, "stack": true, - "top": 37.5, + "top": 33.75, "width": 516.5249652777784, }, "id": "1", @@ -88,7 +88,7 @@ Object { "index": 0, }, "stack": true, - "top": 7.5, + "top": 3.75, "width": 708.2481944444444, }, "id": "2", @@ -106,23 +106,23 @@ Object { "index": 2, }, "stack": true, - "top": 105, + "top": 93.75, "width": 625, }, "id": "3", }, ], "groupHeights": Array [ - 67.5, + 60, + 30, 30, - 37.5, ], "groupTops": Array [ 0, - 67.5, - 97.5, + 60, + 90, ], - "height": 135, + "height": 120, } `; @@ -142,7 +142,7 @@ Object { "index": 0, }, "stack": true, - "top": 7.5, + "top": 3.75, "width": 111.24572916666602, }, "id": "0", @@ -160,7 +160,7 @@ Object { "index": 0, }, "stack": true, - "top": 37.5, + "top": 33.75, "width": 736.245729166666, }, "id": "5", @@ -178,7 +178,7 @@ Object { "index": 0, }, "stack": true, - "top": 7.5, + "top": 3.75, "width": 861.245729166666, }, "id": "6", @@ -196,7 +196,7 @@ Object { "index": 0, }, "stack": true, - "top": 37.5, + "top": 33.75, "width": 516.5249652777784, }, "id": "1", @@ -214,7 +214,7 @@ Object { "index": 0, }, "stack": true, - "top": 7.5, + "top": 3.75, "width": 708.2481944444444, }, "id": "2", @@ -232,23 +232,23 @@ Object { "index": 2, }, "stack": true, - "top": 105, + "top": 93.75, "width": 625, }, "id": "3", }, ], "groupHeights": Array [ - 67.5, + 60, + 30, 30, - 37.5, ], "groupTops": Array [ 0, - 67.5, - 97.5, + 60, + 90, ], - "height": 135, + "height": 120, } `; @@ -268,7 +268,7 @@ Object { "index": 0, }, "stack": true, - "top": 7.5, + "top": 3.75, "width": 361.245729166666, }, "id": "0", @@ -286,7 +286,7 @@ Object { "index": 0, }, "stack": true, - "top": 37.5, + "top": 33.75, "width": 736.245729166666, }, "id": "5", @@ -304,7 +304,7 @@ Object { "index": 0, }, "stack": true, - "top": 7.5, + "top": 3.75, "width": 861.245729166666, }, "id": "6", @@ -322,7 +322,7 @@ Object { "index": 0, }, "stack": true, - "top": 37.5, + "top": 33.75, "width": 516.5249652777784, }, "id": "1", @@ -340,7 +340,7 @@ Object { "index": 0, }, "stack": true, - "top": 7.5, + "top": 3.75, "width": 708.2481944444444, }, "id": "2", @@ -358,23 +358,23 @@ Object { "index": 2, }, "stack": true, - "top": 105, + "top": 93.75, "width": 625, }, "id": "3", }, ], "groupHeights": Array [ - 67.5, + 60, + 30, 30, - 37.5, ], "groupTops": Array [ 0, - 67.5, - 97.5, + 60, + 90, ], - "height": 135, + "height": 120, } `; @@ -394,7 +394,7 @@ Object { "index": 0, }, "stack": true, - "top": 7.5, + "top": 3.75, "width": 236.24572916666602, }, "id": "0", @@ -412,7 +412,7 @@ Object { "index": 0, }, "stack": true, - "top": 37.5, + "top": 33.75, "width": 736.245729166666, }, "id": "5", @@ -430,7 +430,7 @@ Object { "index": 0, }, "stack": true, - "top": 7.5, + "top": 3.75, "width": 861.245729166666, }, "id": "6", @@ -448,7 +448,7 @@ Object { "index": 0, }, "stack": true, - "top": 37.5, + "top": 33.75, "width": 516.5249652777784, }, "id": "1", @@ -466,7 +466,7 @@ Object { "index": 0, }, "stack": true, - "top": 7.5, + "top": 3.75, "width": 708.2481944444444, }, "id": "2", @@ -484,23 +484,23 @@ Object { "index": 2, }, "stack": true, - "top": 105, + "top": 93.75, "width": 625, }, "id": "3", }, ], "groupHeights": Array [ - 67.5, + 60, + 30, 30, - 37.5, ], "groupTops": Array [ 0, - 67.5, - 97.5, + 60, + 90, ], - "height": 135, + "height": 120, } `; diff --git a/__tests__/utils/calendar/calculate-scroll-canvas.js b/__tests__/utils/calendar/calculate-scroll-canvas.js index ed5ee7048..71f37a27f 100644 --- a/__tests__/utils/calendar/calculate-scroll-canvas.js +++ b/__tests__/utils/calendar/calculate-scroll-canvas.js @@ -1,17 +1,15 @@ import { calculateScrollCanvas } from 'lib/utility/calendar' -import {defaultKeys} from 'lib/default-config' -import moment from 'moment' import {items, groups} from '../../../__fixtures__/itemsAndGroups' import {props, state, visibleTimeStart, visibleTimeEnd} from '../../../__fixtures__/stateAndProps' describe('calculateScrollCanvas', () => { it('should calculate new scroll state', () => { - const newStartTime = visibleTimeStart.clone().add(13, 'h') - const newEndTime = visibleTimeEnd.clone().add(13, 'h') + const newStartTime = visibleTimeStart + 13 * 60 * 60 * 1000 + const newEndTime = visibleTimeEnd + visibleTimeStart + 13 * 60 * 60 * 1000 const result = calculateScrollCanvas( - newStartTime.valueOf(), - newEndTime.valueOf(), + newStartTime, + newEndTime, false, items, groups, @@ -23,11 +21,11 @@ describe('calculateScrollCanvas', () => { expect(result).toHaveProperty('dimensionItems') }) it('should calculate new scroll state correctly', () => { - const newStartTime = visibleTimeStart.clone().add(13, 'h') - const newEndTime = visibleTimeEnd.clone().add(13, 'h') + const newStartTime = visibleTimeStart + 13 * 60 * 60 * 1000 + const newEndTime = visibleTimeEnd + 13 * 60 * 60 * 1000 const result = calculateScrollCanvas( - newStartTime.valueOf(), - newEndTime.valueOf(), + newStartTime, + newEndTime, false, items, groups, @@ -37,11 +35,11 @@ describe('calculateScrollCanvas', () => { expect(result).toMatchSnapshot() }) it('should skip new calculation if new visible start and visible end in canvas', () => { - const newStartTime = visibleTimeStart.clone().add(1, 'h') - const newEndTime = visibleTimeEnd.clone().add(1, 'h') + const newStartTime = visibleTimeStart + 1 * 60 * 60 * 1000 + const newEndTime = visibleTimeEnd + 1 * 60 * 60 * 1000 const result = calculateScrollCanvas( - newStartTime.valueOf(), - newEndTime.valueOf(), + newStartTime, + newEndTime, false, items, groups, @@ -53,8 +51,8 @@ describe('calculateScrollCanvas', () => { expect(result).not.toHaveProperty('dimensionItems') }) it('should force new calculation', () => { - const newStartTime = visibleTimeStart.clone().add(1, 'h') - const newEndTime = visibleTimeEnd.clone().add(1, 'h') + const newStartTime = visibleTimeStart + 1 * 60 * 60 * 1000 + const newEndTime = visibleTimeEnd + 1 * 60 * 60 * 1000 const result = calculateScrollCanvas( newStartTime.valueOf(), newEndTime.valueOf(), @@ -69,11 +67,11 @@ describe('calculateScrollCanvas', () => { expect(result).toHaveProperty('dimensionItems') }) it('should calculate new state if zoom changed ', () => { - const newStartTime = visibleTimeStart.clone() - const newEndTime = visibleTimeEnd.clone().add(1, 'h') + const newStartTime = visibleTimeStart + const newEndTime = visibleTimeEnd + 1 * 60 * 60 * 1000 const result = calculateScrollCanvas( - newStartTime.valueOf(), - newEndTime.valueOf(), + newStartTime, + newEndTime, false, items, groups, @@ -85,11 +83,11 @@ describe('calculateScrollCanvas', () => { expect(result).toHaveProperty('dimensionItems') }) it('should calculate new state if zoom changed correctly', () => { - const newStartTime = visibleTimeStart.clone() - const newEndTime = visibleTimeEnd.clone().add(1, 'h') + const newStartTime = visibleTimeStart + const newEndTime = visibleTimeEnd + 1 * 60 * 60 * 1000 const result = calculateScrollCanvas( - newStartTime.valueOf(), - newEndTime.valueOf(), + newStartTime, + newEndTime, false, items, groups, diff --git a/__tests__/utils/calendar/get-next-unit.js b/__tests__/utils/calendar/get-next-unit.js index 40b19bb81..306e4353f 100644 --- a/__tests__/utils/calendar/get-next-unit.js +++ b/__tests__/utils/calendar/get-next-unit.js @@ -23,12 +23,11 @@ describe('getNextUnit', () => { const result = getNextUnit('month') expect(result).toBe('year') }) - it('year to empty string', () => { + it('year to year', () => { const result = getNextUnit('year') - expect(result).toBe('') + expect(result).toBe('year') }) - it('unknown value to empty string', () => { - const result = getNextUnit('foo') - expect(result).toBe('') + it('unknown value to throw error', () => { + expect(() => getNextUnit('foo')).toThrowErrorMatchingSnapshot() }) }) diff --git a/__tests__/utils/calendar/stack-group.js b/__tests__/utils/calendar/stack-group.js index be91fa399..678cf455d 100644 --- a/__tests__/utils/calendar/stack-group.js +++ b/__tests__/utils/calendar/stack-group.js @@ -2,8 +2,11 @@ import { stackGroup } from 'lib/utility/calendar' import { dimensionItems } from '../../../__fixtures__/groupOrderAndItemDimentions' describe('stackGroup', ()=>{ - it('should stack list of items', ()=>{ - expect(stackGroup(dimensionItems, true, 30, 0)).toMatchSnapshot() + it('should stack list of items space', ()=>{ + expect(stackGroup(dimensionItems, 'space', 30, 0)).toMatchSnapshot() + }) + it('should stack list of items lines', ()=>{ + expect(stackGroup(dimensionItems, 'lines', 30, 0)).toMatchSnapshot() }) it('should not stack list of items', ()=>{ expect(stackGroup(dimensionItems, false, 30, 0)).toMatchSnapshot() diff --git a/demo/app/demo-custom-items/index.js b/demo/app/demo-custom-items/index.js index 73a3e8d10..bf598b8a9 100644 --- a/demo/app/demo-custom-items/index.js +++ b/demo/app/demo-custom-items/index.js @@ -207,7 +207,7 @@ export default class App extends Component { canSelect itemsSorted itemTouchSendsClick={false} - stackItems + stackItems="space" itemHeightRatio={0.75} lineHeight={40} showCursorLine diff --git a/demo/app/demo-element-resize/index.js b/demo/app/demo-element-resize/index.js index ff25558fe..ca74a17d2 100644 --- a/demo/app/demo-element-resize/index.js +++ b/demo/app/demo-element-resize/index.js @@ -69,7 +69,7 @@ export default class App extends Component { canSelect itemsSorted itemTouchSendsClick={false} - stackItems + stackItems="space" itemHeightRatio={0.75} resizeDetector={containerResizeDetector} defaultTimeStart={defaultTimeStart} diff --git a/demo/app/demo-headers/index.js b/demo/app/demo-headers/index.js index 0fafd2e29..9f8fc2498 100644 --- a/demo/app/demo-headers/index.js +++ b/demo/app/demo-headers/index.js @@ -65,6 +65,9 @@ export default class App extends Component { } handleCanvasClick = (groupId, time) => { + this.setState(state => ({ + groups: state.groups + })) console.log('Canvas clicked', groupId, moment(time).format()) } @@ -82,6 +85,9 @@ export default class App extends Component { handleItemSelect = (itemId, _, time) => { console.log('Selected: ' + itemId, moment(time).format()) + this.setState((state)=>({ + groups: state.groups.filter(_ => Math.random() > 0.5 ) + })) } handleItemDoubleClick = (itemId, _, time) => { @@ -194,8 +200,8 @@ export default class App extends Component { // moveResizeValidator={this.moveResizeValidator} rightSidebarWidth={150} rightSidebarContent={
Above The Right
} - stackItems - > + stackItems="space" + > {({ getRootProps }) => { diff --git a/demo/app/demo-linked-timelines/index.js b/demo/app/demo-linked-timelines/index.js index 8ec0af119..b68a3a5d6 100644 --- a/demo/app/demo-linked-timelines/index.js +++ b/demo/app/demo-linked-timelines/index.js @@ -70,7 +70,7 @@ export default class App extends Component { canSelect itemsSorted itemTouchSendsClick={false} - stackItems + stackItems="space" itemHeightRatio={0.75} visibleTimeStart={visibleTimeStart} visibleTimeEnd={visibleTimeEnd} @@ -94,7 +94,7 @@ export default class App extends Component { canSelect itemsSorted itemTouchSendsClick={false} - stackItems + stackItems="space" itemHeightRatio={0.75} visibleTimeStart={visibleTimeStart} visibleTimeEnd={visibleTimeEnd} diff --git a/demo/app/demo-main/index.js b/demo/app/demo-main/index.js index 19d1145ca..c72697609 100644 --- a/demo/app/demo-main/index.js +++ b/demo/app/demo-main/index.js @@ -4,9 +4,13 @@ import moment from 'moment' import Timeline, { TimelineMarkers, + TimelineHeaders, TodayMarker, CustomMarker, - CursorMarker + CursorMarker, + CustomHeader, + SidebarHeader, + DateHeader } from 'react-calendar-timeline' import generateFakeData from '../generate-fake-data' @@ -34,15 +38,151 @@ export default class App extends Component { constructor(props) { super(props) - const { groups, items } = generateFakeData() - const defaultTimeStart = moment() + const items = [ + // { + // id: '4', + // group: '1', + // title: + // '4', + // start: 1550538000000, + // end: 1550560702870, + // canResize: false, + // className: '', + // bgColor: 'rgba(209, 154, 237, 0.6)', + // selectedBgColor: 'rgba(209, 154, 237, 1)', + // color: '#55077c', + // itemProps: { + // 'data-tip': + // 'The AGP bus is down, calculate the multi-byte alarm so we can generate the THX driver!' + // } + // }, + // { + // id: '5', + // group: '1', + // title: + // '5', + // start: 1550549700000, + // end: 1550569678295, + // canMove: true, + // canResize: 'both', + // className: '', + // bgColor: 'rgba(235, 171, 242, 0.6)', + // selectedBgColor: 'rgba(235, 171, 242, 1)', + // color: '#ad0fbf', + // itemProps: { + // 'data-tip': + // "I'll parse the virtual AI monitor, that should microchip the SDD circuit!" + // } + // }, + { + id: '1', + group: '1', + title: + '1', + start: 1550562300000, + end: 1550566800000, + canMove: true, + canResize: 'both', + className: '', + bgColor: 'rgba(119, 126, 249, 0.6)', + selectedBgColor: 'rgba(119, 126, 249, 1)', + color: '#010887', + itemProps: { + 'data-tip': + 'The IB alarm is down, parse the virtual driver so we can copy the COM bus!' + } + }, + { + id: '3', + group: '2', + title: '3', + start: 1550538000000, + end: 1550557157371, + canResize: false, + className: '', + bgColor: 'rgba(184, 141, 239, 0.6)', + selectedBgColor: 'rgba(184, 141, 239, 1)', + color: '#3c0584', + itemProps: { + 'data-tip': + 'Try to synthesize the AI circuit, maybe it will calculate the cross-platform interface!' + } + }, + + + { + id: '6', + group: '2', + title: '6', + start: 1550551500000, + end: 1550571478295, + canResize: false, + className: '', + bgColor: 'rgba(252, 191, 243, 0.6)', + selectedBgColor: 'rgba(252, 191, 243, 1)', + color: '#ea15ca', + itemProps: { + 'data-tip': 'We need to input the haptic USB panel!' + } + }, + { + id: '7', + group: '2', + title: + "7", + start: 1550539800000, + end: 1550559571292, + canResize: false, + className: '', + bgColor: 'rgba(247, 116, 197, 0.6)', + selectedBgColor: 'rgba(247, 116, 197, 1)', + color: '#db0288', + itemProps: { + 'data-tip': + "bypassing the driver won't do anything, we need to compress the haptic XML monitor!" + } + }, + { + id: '8', + group: '2', + title: '8', + start: 1550535300000, + end: 1550550380987, + canResize: false, + className: '', + bgColor: 'rgba(244, 129, 173, 0.6)', + selectedBgColor: 'rgba(244, 129, 173, 1)', + color: '#99043d', + itemProps: { + 'data-tip': + "copying the system won't do anything, we need to quantify the neural SCSI protocol!" + } + } + ] + + const groups = [ + { + id: '1', + title: 'Dee', + rightTitle: 'Kuhn', + label: 'Label Amari', + bgColor: '#c0d0f9' + }, + { + id: '2', + title: 'Brennon', + rightTitle: 'Cronin', + label: 'Label Maude', + bgColor: '#777ef9' + } + ] + + const defaultTimeStart = moment('19/2/2019', 'dd/mm/yyyy') .startOf('day') .toDate() - const defaultTimeEnd = moment() - .startOf('day') - .add(1, 'day') + const defaultTimeEnd = moment('19/2/2019', 'dd/mm/yyyy') + .endOf('day') .toDate() - this.state = { groups, items, @@ -141,6 +281,10 @@ export default class App extends Component { return time } + handleMoveUpdate= (...args)=> { + console.log(args) + } + render() { const { groups, items, defaultTimeStart, defaultTimeEnd } = this.state @@ -156,7 +300,7 @@ export default class App extends Component { canSelect itemsSorted itemTouchSendsClick={false} - stackItems + stackItems='lines' itemHeightRatio={0.75} defaultTimeStart={defaultTimeStart} defaultTimeEnd={defaultTimeEnd} @@ -170,7 +314,9 @@ export default class App extends Component { onItemResize={this.handleItemResize} onItemDoubleClick={this.handleItemDoubleClick} onTimeChange={this.handleTimeChange} - // moveResizeValidator={this.moveResizeValidator} + moveResizeValidator={this.moveResizeValidator} + width={1000} + onUpdateMove={this.handleMoveUpdate} > diff --git a/demo/app/demo-performance/index.js b/demo/app/demo-performance/index.js index 97c940d00..147db94a9 100644 --- a/demo/app/demo-performance/index.js +++ b/demo/app/demo-performance/index.js @@ -168,7 +168,7 @@ export default class App extends Component { canSelect itemsSorted itemTouchSendsClick={false} - stackItems + stackItems="space" itemHeightRatio={0.75} // resizeDetector={containerResizeDetector} diff --git a/demo/app/demo-renderers/index.js b/demo/app/demo-renderers/index.js index 898dfb426..7a3229647 100644 --- a/demo/app/demo-renderers/index.js +++ b/demo/app/demo-renderers/index.js @@ -187,7 +187,7 @@ export default class App extends Component { canSelect itemsSorted itemTouchSendsClick={false} - stackItems + stackItems="space" itemHeightRatio={0.75} // resizeDetector={containerResizeDetector} diff --git a/demo/app/demo-sticky-header/index.js b/demo/app/demo-sticky-header/index.js index adb26d533..d0685849e 100644 --- a/demo/app/demo-sticky-header/index.js +++ b/demo/app/demo-sticky-header/index.js @@ -65,7 +65,7 @@ export default class App extends Component { canSelect itemsSorted itemTouchSendsClick={false} - stackItems + stackItems="space" itemHeightRatio={0.75} resizeDetector={containerResizeDetector} defaultTimeStart={defaultTimeStart} @@ -113,7 +113,7 @@ export default class App extends Component { canSelect itemsSorted itemTouchSendsClick={false} - stackItems + stackItems="space" itemHeightRatio={0.75} resizeDetector={containerResizeDetector} defaultTimeStart={defaultTimeStart} diff --git a/demo/app/demo-tree-groups/index.js b/demo/app/demo-tree-groups/index.js index 63cb3ea2e..41dafbcea 100644 --- a/demo/app/demo-tree-groups/index.js +++ b/demo/app/demo-tree-groups/index.js @@ -111,7 +111,7 @@ export default class App extends Component { canSelect itemsSorted itemTouchSendsClick={false} - stackItems + stackItems="space" itemHeightRatio={0.75} defaultTimeStart={defaultTimeStart} defaultTimeEnd={defaultTimeEnd} diff --git a/demo/app/demo-vertical-classes/index.js b/demo/app/demo-vertical-classes/index.js index c8f0fbc5f..8d023e3b5 100644 --- a/demo/app/demo-vertical-classes/index.js +++ b/demo/app/demo-vertical-classes/index.js @@ -93,7 +93,7 @@ export default class App extends Component { canSelect itemsSorted itemTouchSendsClick={false} - stackItems + stackItems="space" itemHeightRatio={0.75} defaultTimeStart={defaultTimeStart} defaultTimeEnd={defaultTimeEnd} diff --git a/examples/README.md b/examples/README.md index c05551b01..13bdcad66 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,19 +6,19 @@ Below are links to Codesandbox instances for examples of how to use this repo. F Basic functionality of the timeline, included panning, zooming and rendering of items -[Example Codesandbox](https://codesandbox.io/s/zr5r289rm4) +[Example Codesandbox](https://codesandbox.io/s/w6xvqzno4w) ## Item Moving and Resizing Moving and Resizing examples. Clicking and dragging can move items into different groups. Notice the `handleItemMove` and `handleItemResize` handlers passed to the Timeline. -[Example Codesandbox](https://codesandbox.io/s/6vo2jkov23) +[Example Codesandbox](https://codesandbox.io/s/q3rkx1478q) ## Custom Item Rendering The `itemRenderer` component allows you to customize what contents are shown in the item on the calendar. -[Example Codesandbox](https://codesandbox.io/s/1p8m477w7) +[Example Codesandbox](https://codesandbox.io/s/k0wn41y0o7)