├── .nvmrc
├── demo
├── .env
├── src
│ ├── index.jsx
│ ├── index.css
│ ├── constants.js
│ ├── App.jsx
│ ├── builders.js
│ └── utils.js
├── public
│ └── index.html
├── ops
│ └── deploy.sh
└── package.json
├── src
├── scss
│ ├── components
│ │ ├── _tracks.scss
│ │ ├── _track-keys.scss
│ │ ├── _grid.scss
│ │ ├── _track.scss
│ │ ├── _timebar-key.scss
│ │ ├── _timeline.scss
│ │ ├── _layout.scss
│ │ ├── _timebar.scss
│ │ ├── _sidebar.scss
│ │ ├── _controls.scss
│ │ ├── _element.scss
│ │ ├── _marker.scss
│ │ └── _track-key.scss
│ ├── _utils.scss
│ └── style.scss
├── utils
│ ├── raf.js
│ ├── computedStyle.js
│ ├── getGrid.js
│ ├── events.js
│ ├── getNumericPropertyValue.js
│ ├── getMouseX.js
│ ├── formatDate.js
│ ├── classes.js
│ ├── __tests__
│ │ ├── getMouseX.js
│ │ ├── classes.js
│ │ ├── getNumericPropertyValue.js
│ │ ├── formatDate.js
│ │ ├── getGrid.js
│ │ └── time.js
│ └── time.js
├── components
│ ├── Sidebar
│ │ ├── __tests__
│ │ │ ├── Body.jsx
│ │ │ ├── index.jsx
│ │ │ └── Header.jsx
│ │ ├── Body.jsx
│ │ ├── TrackKeys
│ │ │ ├── index.jsx
│ │ │ ├── __tests__
│ │ │ │ ├── index.jsx
│ │ │ │ └── TrackKey.jsx
│ │ │ └── TrackKey.jsx
│ │ ├── index.jsx
│ │ └── Header.jsx
│ ├── Timeline
│ │ ├── Timebar
│ │ │ ├── __tests__
│ │ │ │ ├── index.jsx
│ │ │ │ ├── Row.jsx
│ │ │ │ └── Cell.jsx
│ │ │ ├── Row.jsx
│ │ │ ├── index.jsx
│ │ │ └── Cell.jsx
│ │ ├── Tracks
│ │ │ ├── __tests__
│ │ │ │ ├── index.jsx
│ │ │ │ ├── Element.jsx
│ │ │ │ └── Track.jsx
│ │ │ ├── index.jsx
│ │ │ ├── Track.jsx
│ │ │ └── Element.jsx
│ │ ├── Body.jsx
│ │ ├── Grid
│ │ │ ├── index.jsx
│ │ │ └── __tests__
│ │ │ │ └── index.jsx
│ │ ├── Marker
│ │ │ ├── index.jsx
│ │ │ ├── Pointer.jsx
│ │ │ ├── Now.jsx
│ │ │ └── __tests__
│ │ │ │ ├── Pointer.jsx
│ │ │ │ ├── Now.jsx
│ │ │ │ └── index.jsx
│ │ ├── __tests__
│ │ │ ├── Body.jsx
│ │ │ ├── index.jsx
│ │ │ └── Header.jsx
│ │ ├── index.jsx
│ │ └── Header.jsx
│ ├── Controls
│ │ ├── __tests__
│ │ │ ├── Toggle.jsx
│ │ │ └── index.jsx
│ │ ├── index.jsx
│ │ ├── ZoomOut.jsx
│ │ ├── ZoomIn.jsx
│ │ └── Toggle.jsx
│ ├── Elements
│ │ ├── Basic.jsx
│ │ └── __tests__
│ │ │ └── Basic.jsx
│ └── Layout
│ │ ├── __tests__
│ │ └── index.jsx
│ │ └── index.jsx
├── __tests__
│ └── index.jsx
└── index.jsx
├── .vscode
├── settings.json
└── extensions.json
├── .eslintignore
├── .gitignore
├── .prettierrc.js
├── jestSetup.js
├── .babelrc
├── .eslintrc
├── .github
└── workflows
│ └── main.yml
├── LICENSE
├── README.md
├── .editorconfig
└── package.json
/.nvmrc:
--------------------------------------------------------------------------------
1 | 10
2 |
--------------------------------------------------------------------------------
/demo/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
2 |
--------------------------------------------------------------------------------
/src/scss/components/_tracks.scss:
--------------------------------------------------------------------------------
1 | .rt-tracks {}
2 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/raf.js:
--------------------------------------------------------------------------------
1 | export default cb => window.requestAnimationFrame(cb)
2 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | lib
2 | demo/node_modules
3 | build
4 | node_modules
5 | coverage
6 |
--------------------------------------------------------------------------------
/src/utils/computedStyle.js:
--------------------------------------------------------------------------------
1 | export default (node, x) => window.getComputedStyle(node, x)
2 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib
2 | node_modules
3 | npm-debug.log
4 | coverage
5 | .yarnclean
6 | yarn-error.log
7 | yarn.lock
8 | build
9 |
--------------------------------------------------------------------------------
/src/scss/components/_track-keys.scss:
--------------------------------------------------------------------------------
1 | .rt-track-keys {
2 | margin: 0;
3 | padding-left: 0;
4 | list-style: none;
5 | }
6 |
--------------------------------------------------------------------------------
/src/utils/getGrid.js:
--------------------------------------------------------------------------------
1 | const getGrid = timebar => (timebar.find(row => row.useAsGrid) || {}).cells
2 |
3 | export default getGrid
4 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 120,
3 | semi: false,
4 | singleQuote: true,
5 | trailingComma: 'es5',
6 | arrowParens: 'avoid',
7 | }
8 |
--------------------------------------------------------------------------------
/src/utils/events.js:
--------------------------------------------------------------------------------
1 | export const addListener = (e, t) => window.addEventListener(e, t)
2 | export const removeListener = (e, t) => window.removeEventListener(e, t)
3 |
--------------------------------------------------------------------------------
/src/utils/getNumericPropertyValue.js:
--------------------------------------------------------------------------------
1 | import computedStyle from './computedStyle'
2 |
3 | export default (node, prop) => parseInt(computedStyle(node).getPropertyValue(prop), 10)
4 |
--------------------------------------------------------------------------------
/src/scss/_utils.scss:
--------------------------------------------------------------------------------
1 | .rt-visually-hidden {
2 | position: absolute;
3 | overflow: hidden;
4 | clip: rect(0 0 0 0);
5 | height: 1px; width: 1px;
6 | margin: -1px; padding: 0; border: 0;
7 | }
--------------------------------------------------------------------------------
/jestSetup.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 |
3 | import { configure } from 'enzyme'
4 | import Adapter from 'enzyme-adapter-react-16'
5 |
6 | configure({ adapter: new Adapter() })
7 |
--------------------------------------------------------------------------------
/demo/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | import App from './App'
5 |
6 | import './index.css'
7 |
8 | ReactDOM.render(
', () => {
8 | it('renders
)
14 | expect(wrapper.find(TrackKeys).exists()).toBe(true)
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/src/scss/components/_timebar-key.scss:
--------------------------------------------------------------------------------
1 | .rt-timebar-key {
2 | height: $react-timelines-header-row-height + $react-timelines-border-width;
3 | padding-right: $react-timelines-sidebar-key-indent-width;
4 | line-height: $react-timelines-header-row-height;
5 | text-align: right;
6 | font-weight: bold;
7 | border-bottom: 1px solid $react-timelines-sidebar-separator-color;
8 |
9 | &:last-child {
10 | border-bottom-color: $react-timelines-header-separator-color;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/demo/src/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | border: 0;
4 | padding: 0;
5 | margin: 0;
6 | }
7 |
8 | body {
9 | background-color: #f0f0f0;
10 | }
11 |
12 | .app {
13 | font-family: sans-serif;
14 | padding-top: 48px;
15 | padding-bottom: 48px;
16 | }
17 |
18 | @media screen and (min-width: 1000px) {
19 | .app {
20 | padding-left: 32px;
21 | padding-right: 32px;
22 | }
23 | }
24 |
25 | .title {
26 | text-align: center;
27 | margin-bottom: 32px;
28 | }
29 |
--------------------------------------------------------------------------------
/src/scss/components/_timeline.scss:
--------------------------------------------------------------------------------
1 | .rt-timeline {
2 | position: relative;
3 | overflow: hidden;
4 | }
5 |
6 | .rt-timeline__header {}
7 |
8 | .rt-timeline__header-scroll {
9 | overflow-x: auto;
10 |
11 | &::-webkit-scrollbar {
12 | display: none;
13 | }
14 | }
15 |
16 | .rt-timeline__header.rt-is-sticky {
17 | position: fixed;
18 | top: 0;
19 | z-index: 1;
20 | overflow: hidden;
21 | }
22 |
23 | .rt-timeline__body {
24 | position: relative;
25 | background: white;
26 | }
27 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Test and build for master
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: Use Node.js 10
15 | uses: actions/setup-node@v1
16 | with:
17 | node-version: 10.x
18 | - run: npm ci
19 | - run: npm test
20 | env:
21 | CI: true
22 | - run: npm run build
23 |
--------------------------------------------------------------------------------
/src/components/Timeline/Timebar/__tests__/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { shallow } from 'enzyme'
3 |
4 | import Timebar from '..'
5 | import Row from '../Row'
6 |
7 | describe('
12 | ) 13 | 14 | Row.propTypes = { 15 | time: PropTypes.shape({}).isRequired, 16 | cells: PropTypes.arrayOf(PropTypes.shape({})).isRequired, 17 | style: PropTypes.shape({}), 18 | } 19 | 20 | export default Row 21 | -------------------------------------------------------------------------------- /src/components/Timeline/Timebar/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | import Row from './Row' 5 | 6 | const Timebar = ({ time, rows }) => ( 7 |
12 | ) 13 | 14 | Timebar.propTypes = { 15 | time: PropTypes.shape({}).isRequired, 16 | rows: PropTypes.arrayOf(PropTypes.shape({})).isRequired, 17 | } 18 | 19 | export default Timebar 20 | -------------------------------------------------------------------------------- /src/components/Sidebar/Body.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | import TrackKeys from './TrackKeys' 5 | 6 | const Body = ({ tracks, toggleTrackOpen, clickTrackButton }) => ( 7 |
10 | ) 11 | 12 | Body.propTypes = { 13 | tracks: PropTypes.arrayOf(PropTypes.shape({})), 14 | toggleTrackOpen: PropTypes.func, 15 | clickTrackButton: PropTypes.func, 16 | } 17 | 18 | export default Body 19 | -------------------------------------------------------------------------------- /src/components/Timeline/Timebar/Cell.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | const Cell = ({ time, title, start, end }) => ( 5 |
8 | )
9 |
10 | Cell.propTypes = {
11 | time: PropTypes.shape({
12 | toStyleLeftAndWidth: PropTypes.func,
13 | }),
14 | title: PropTypes.string.isRequired,
15 | start: PropTypes.instanceOf(Date).isRequired,
16 | end: PropTypes.instanceOf(Date).isRequired,
17 | }
18 |
19 | export default Cell
20 |
--------------------------------------------------------------------------------
/src/utils/__tests__/classes.js:
--------------------------------------------------------------------------------
1 | import classes from '../classes'
2 |
3 | describe('classes', () => {
4 | it('returns the base class', () => {
5 | expect(classes('foo')).toBe('foo')
6 | })
7 |
8 | it('returns the base class plus additional class passed as string', () => {
9 | expect(classes('bar', 'hello')).toBe('bar hello')
10 | })
11 |
12 | it('returns the base class plus additional class passed as array', () => {
13 | expect(classes('bar', ['hello'])).toBe('bar hello')
14 | expect(classes('foo', ['hello', 'world'])).toBe('foo hello world')
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/src/components/Sidebar/__tests__/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { shallow } from 'enzyme'
3 |
4 | import Sidebar from '..'
5 | import Header from '../Header'
6 | import Body from '../Body'
7 |
8 | describe('
', () => {
10 | const props = {
11 | timebar: [],
12 | tracks: [{}],
13 | toggleTrackOpen: jest.fn(),
14 | }
15 | const wrapper = shallow(
12 | ) 13 | 14 | TrackKeys.propTypes = { 15 | tracks: PropTypes.arrayOf(PropTypes.shape({})), 16 | toggleOpen: PropTypes.func, 17 | clickTrackButton: PropTypes.func, 18 | } 19 | 20 | export default TrackKeys 21 | -------------------------------------------------------------------------------- /src/components/Timeline/Tracks/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | import Track from './Track' 5 | 6 | const Tracks = ({ time, tracks, clickElement }) => ( 7 |
12 | ) 13 | 14 | Tracks.propTypes = { 15 | time: PropTypes.shape({}).isRequired, 16 | tracks: PropTypes.arrayOf(PropTypes.shape({})), 17 | clickElement: PropTypes.func, 18 | } 19 | 20 | export default Tracks 21 | -------------------------------------------------------------------------------- /src/components/Timeline/Body.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | import Tracks from './Tracks' 5 | import Grid from './Grid' 6 | 7 | const Body = ({ time, grid, tracks, clickElement }) => ( 8 |
12 | )
13 |
14 | Body.propTypes = {
15 | time: PropTypes.shape({}).isRequired,
16 | grid: PropTypes.arrayOf(PropTypes.shape({})),
17 | tracks: PropTypes.arrayOf(PropTypes.shape({})),
18 | clickElement: PropTypes.func,
19 | }
20 |
21 | export default Body
22 |
--------------------------------------------------------------------------------
/src/components/Sidebar/TrackKeys/__tests__/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { shallow } from 'enzyme'
3 |
4 | import TrackKeys from '..'
5 | import TrackKey from '../TrackKey'
6 |
7 | describe('
10 | )
11 |
12 | Grid.propTypes = {
13 | time: PropTypes.shape({
14 | toStyleLeftAndWidth: PropTypes.func,
15 | }).isRequired,
16 | grid: PropTypes.arrayOf(
17 | PropTypes.shape({
18 | start: PropTypes.instanceOf(Date).isRequired,
19 | end: PropTypes.instanceOf(Date).isRequired,
20 | })
21 | ).isRequired,
22 | }
23 |
24 | export default Grid
25 |
--------------------------------------------------------------------------------
/src/scss/components/_timebar.scss:
--------------------------------------------------------------------------------
1 | .rt-timebar {
2 | background-color: $react-timelines-keyline-color;
3 | }
4 |
5 | .rt-timebar__row {
6 | position: relative;
7 | height: $react-timelines-header-row-height + $react-timelines-border-width;
8 | overflow: hidden;
9 | line-height: $react-timelines-header-row-height;
10 | border-bottom: $react-timelines-border-width solid $react-timelines-keyline-color;
11 | &:last-child {
12 | border-bottom-color: $react-timelines-header-separator-color;
13 | }
14 | }
15 |
16 | .rt-timebar__cell {
17 | position: absolute;
18 | text-align: center;
19 |
20 | background-color: $react-timelines-timebar-cell-background-color;
21 | border-left: 1px solid $react-timelines-keyline-color;
22 | }
23 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.13.1",
7 | "react-dom": "^16.13.1",
8 | "react-scripts": "^3.4.1",
9 | "react-timelines": "^2.6.1"
10 | },
11 | "scripts": {
12 | "start": "react-scripts start",
13 | "build": "react-scripts build",
14 | "deploy": "gh-pages -d ./build"
15 | },
16 | "devDependencies": {
17 | "gh-pages": "^2.2.0"
18 | },
19 | "browserslist": {
20 | "production": [
21 | ">0.2%",
22 | "not dead",
23 | "not op_mini all"
24 | ],
25 | "development": [
26 | "last 1 chrome version",
27 | "last 1 firefox version",
28 | "last 1 safari version"
29 | ]
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/Timeline/Timebar/__tests__/Row.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { shallow } from 'enzyme'
3 |
4 | import Row from '../Row'
5 | import Cell from '../Cell'
6 |
7 | describe('
15 | )
16 |
17 | Marker.propTypes = {
18 | x: PropTypes.number.isRequired,
19 | modifier: PropTypes.string.isRequired,
20 | visible: PropTypes.bool,
21 | highlighted: PropTypes.bool,
22 | children: PropTypes.node,
23 | }
24 |
25 | export default Marker
26 |
--------------------------------------------------------------------------------
/src/scss/components/_sidebar.scss:
--------------------------------------------------------------------------------
1 | .rt-sidebar {
2 | background-color: $react-timelines-sidebar-background-color;
3 | box-shadow: 10px 0 10px -5px rgba(12, 12, 12, 0.1);
4 | }
5 |
6 | .rt-sidebar__header {
7 | background-color: $react-timelines-sidebar-background-color;
8 | }
9 |
10 | .rt-sidebar__header.rt-is-sticky {
11 | position: fixed;
12 | top: 0;
13 | z-index: 2;
14 | direction: rtl;
15 | margin-left: ($react-timelines-sidebar-width - $react-timelines-sidebar-closed-offset);
16 |
17 | @media (min-width: $react-timelines-auto-open-breakpoint) {
18 | margin-left: 0;
19 | direction: ltr;
20 | }
21 | }
22 |
23 | .rt-layout.rt-is-open .rt-sidebar__header.rt-is-sticky {
24 | margin-left: 0;
25 | direction: ltr;
26 | }
27 |
28 | .rt-sidebar__body {}
29 |
--------------------------------------------------------------------------------
/src/utils/__tests__/formatDate.js:
--------------------------------------------------------------------------------
1 | import { getMonth, getDayMonth } from '../formatDate'
2 |
3 | describe('formatDate', () => {
4 | describe('getMonth', () => {
5 | it('returns the current month name for a given date', () => {
6 | expect(getMonth(new Date('2017-01-01'))).toEqual('Jan')
7 | expect(getMonth(new Date('2017-02-01'))).toEqual('Feb')
8 | expect(getMonth(new Date('2017-11-01'))).toEqual('Nov')
9 | })
10 | })
11 |
12 | describe('getDayMonth', () => {
13 | it('returns the current day and month', () => {
14 | expect(getDayMonth(new Date('2017-02-01'))).toEqual('1 Feb')
15 | expect(getDayMonth(new Date('2017-05-20'))).toEqual('20 May')
16 | expect(getDayMonth(new Date('2017-12-20'))).toEqual('20 Dec')
17 | })
18 | })
19 | })
20 |
--------------------------------------------------------------------------------
/src/components/Timeline/Marker/Pointer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import { getDayMonth } from '../../../utils/formatDate'
5 | import Marker from '.'
6 |
7 | const PointerMarker = ({ time, date, visible, highlighted }) => (
8 |
12 | )
13 |
14 | Sidebar.propTypes = {
15 | timebar: PropTypes.arrayOf(
16 | PropTypes.shape({
17 | id: PropTypes.string.isRequired,
18 | title: PropTypes.string,
19 | }).isRequired
20 | ).isRequired,
21 | tracks: PropTypes.arrayOf(PropTypes.shape({})),
22 | toggleTrackOpen: PropTypes.func,
23 | sticky: PropTypes.shape({}),
24 | clickTrackButton: PropTypes.func,
25 | }
26 |
27 | export default Sidebar
28 |
--------------------------------------------------------------------------------
/src/components/Timeline/Marker/__tests__/Pointer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { shallow } from 'enzyme'
3 |
4 | import PointerMarker from '../Pointer'
5 | import Marker from '..'
6 | import createTime from '../../../../utils/time'
7 |
8 | const time = createTime({
9 | start: new Date('2017-01-01'),
10 | end: new Date('2018-01-01'),
11 | zoom: 1,
12 | })
13 |
14 | describe('
16 | ) 17 | 18 | Controls.propTypes = { 19 | zoom: PropTypes.number.isRequired, 20 | isOpen: PropTypes.bool, 21 | toggleOpen: PropTypes.func, 22 | zoomIn: PropTypes.func, 23 | zoomOut: PropTypes.func, 24 | zoomMin: PropTypes.number, 25 | zoomMax: PropTypes.number, 26 | } 27 | 28 | export default Controls 29 | -------------------------------------------------------------------------------- /src/components/Timeline/__tests__/Body.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { shallow } from 'enzyme' 3 | 4 | import Body from '../Body' 5 | import Tracks from '../Tracks' 6 | import Grid from '../Grid' 7 | 8 | const defaultProps = { 9 | time: {}, 10 | grid: [], 11 | tracks: [], 12 | } 13 | 14 | describe('
', () => {
15 | it('renders
)
17 | expect(wrapper.find(Tracks).exists()).toBe(true)
18 | })
19 |
20 | it('renders
)
22 | expect(wrapper.find(Grid).exists()).toBe(true)
23 | })
24 |
25 | it('does not render
) 31 | expect(wrapper.find(Grid).exists()).toBe(false) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /src/scss/components/_controls.scss: -------------------------------------------------------------------------------- 1 | .rt-controls { 2 | display: inline-block; 3 | padding: 8px; 4 | margin: 0 0 $react-timelines-spacing 0; 5 | background-color: #fff; 6 | } 7 | 8 | .rt-controls__button { 9 | display: inline-block; 10 | width: $react-timelines-button-size; 11 | height: $react-timelines-button-size; 12 | overflow: hidden; 13 | background-color: $react-timelines-button-background-color; 14 | color: transparent; 15 | white-space: nowrap; 16 | padding: $react-timelines-spacing; 17 | outline: none; 18 | 19 | &:last-child { 20 | margin-right: 0; 21 | } 22 | 23 | &:hover { 24 | background-color: $react-timelines-button-background-color-hover; 25 | } 26 | 27 | &:focus, 28 | &:active { 29 | background-color: $react-timelines-button-background-color-hover; 30 | } 31 | } 32 | 33 | .rt-controls__button[disabled] { 34 | opacity: 0.5; 35 | } 36 | 37 | .rt-controls__button--toggle { 38 | @media (min-width: $react-timelines-auto-open-breakpoint) { 39 | display: none; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/Sidebar/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | const Header = ({ timebar, sticky: { isSticky, sidebarWidth, headerHeight } = {} }) => ( 5 |
17 | ) 18 | 19 | Header.propTypes = { 20 | sticky: PropTypes.shape({ 21 | isSticky: PropTypes.bool.isRequired, 22 | headerHeight: PropTypes.number.isRequired, 23 | sidebarWidth: PropTypes.number.isRequired, 24 | }), 25 | timebar: PropTypes.arrayOf( 26 | PropTypes.shape({ 27 | id: PropTypes.string.isRequired, 28 | title: PropTypes.string, 29 | }).isRequired 30 | ).isRequired, 31 | } 32 | 33 | export default Header 34 | -------------------------------------------------------------------------------- /src/components/Timeline/Tracks/Track.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | import Tracks from '.' 5 | import Element from './Element' 6 | 7 | const Track = ({ time, elements, isOpen, tracks, clickElement }) => ( 8 |
18 | )
19 |
20 | Track.propTypes = {
21 | time: PropTypes.shape({}).isRequired,
22 | isOpen: PropTypes.bool,
23 | elements: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
24 | tracks: PropTypes.arrayOf(PropTypes.shape({})),
25 | clickElement: PropTypes.func,
26 | }
27 |
28 | Track.defaultProps = {
29 | clickElement: undefined,
30 | }
31 |
32 | export default Track
33 |
--------------------------------------------------------------------------------
/src/components/Controls/ZoomOut.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | const ZoomOut = ({ zoom, zoomMin, zoomOut }) => (
5 |
20 | )
21 |
22 | ZoomOut.propTypes = {
23 | zoom: PropTypes.number.isRequired,
24 | zoomMin: PropTypes.number.isRequired,
25 | zoomOut: PropTypes.func.isRequired,
26 | }
27 |
28 | export default ZoomOut
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Sainsbury's PLC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/components/Controls/ZoomIn.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | const ZoomIn = ({ zoom, zoomMax, zoomIn }) => (
5 |
21 | )
22 |
23 | ZoomIn.propTypes = {
24 | zoom: PropTypes.number.isRequired,
25 | zoomMax: PropTypes.number.isRequired,
26 | zoomIn: PropTypes.func.isRequired,
27 | }
28 |
29 | export default ZoomIn
30 |
--------------------------------------------------------------------------------
/src/components/Timeline/Marker/__tests__/Now.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { shallow } from 'enzyme'
3 |
4 | import NowMarker from '../Now'
5 | import Marker from '..'
6 | import createTime from '../../../../utils/time'
7 |
8 | const createProps = ({
9 | now = new Date(),
10 | time = createTime({
11 | start: new Date(),
12 | end: new Date(),
13 | zoom: 1,
14 | }),
15 | visible = true,
16 | }) => ({ now, time, visible })
17 |
18 | describe('
`
49 |
50 | ## Development
51 |
52 | ```sh
53 | npm install
54 | npm run watch
55 | ```
56 |
57 | This library is developed using VSCode - opening it in VSCode will recommend extensions, and enable linting and auto-formatting of code.
58 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs.
2 | # Requires EditorConfig JetBrains Plugin - http://github.com/editorconfig/editorconfig-jetbrains
3 |
4 | # Set this file as the topmost .editorconfig
5 | # (multiple files can be used, and are applied starting from current document location)
6 | root = true
7 |
8 | # Use bracketed regexp to target specific file types or file locations
9 | [*.{js,json}]
10 |
11 | # Use hard or soft tabs ["tab", "space"]
12 | indent_style = space
13 |
14 | # Size of a single indent [an integer, "tab"]
15 | indent_size = tab
16 |
17 | # Number of columns representing a tab character [an integer]
18 | tab_width = 2
19 |
20 | # Line breaks representation ["lf", "cr", "crlf"]
21 | end_of_line = lf
22 |
23 | # ["latin1", "utf-8", "utf-16be", "utf-16le"]
24 | charset = utf-8
25 |
26 | # Remove any whitespace characters preceding newline characters ["true", "false"]
27 | trim_trailing_whitespace = true
28 |
29 | # Ensure file ends with a newline when saving ["true", "false"]
30 | insert_final_newline = true
31 |
32 | # Markdown files
33 | [*.md]
34 |
35 | # Trailing whitespaces are significant in Markdown.
36 | trim_trailing_whitespace = false
37 |
--------------------------------------------------------------------------------
/src/components/Controls/Toggle.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | const CloseSvg = () => (
5 |
11 | )
12 |
13 | const OpenSvg = () => (
14 |
21 | )
22 |
23 | const Toggle = ({ toggleOpen, isOpen }) => (
24 |
28 | )
29 |
30 | Toggle.propTypes = {
31 | toggleOpen: PropTypes.func.isRequired,
32 | isOpen: PropTypes.bool.isRequired,
33 | }
34 |
35 | export default Toggle
36 |
--------------------------------------------------------------------------------
/src/utils/time.js:
--------------------------------------------------------------------------------
1 | const MILLIS_IN_A_DAY = 24 * 60 * 60 * 1000
2 |
3 | const create = ({ start, end, zoom, viewportWidth = 0, minWidth = 0 }) => {
4 | const duration = end - start
5 |
6 | const days = duration / MILLIS_IN_A_DAY
7 | const daysZoomWidth = days * zoom
8 |
9 | let timelineWidth
10 |
11 | if (daysZoomWidth > viewportWidth) {
12 | timelineWidth = daysZoomWidth
13 | } else {
14 | timelineWidth = viewportWidth
15 | }
16 |
17 | if (timelineWidth < minWidth) {
18 | timelineWidth = minWidth
19 | }
20 |
21 | const timelineWidthStyle = `${timelineWidth}px`
22 |
23 | const toX = from => {
24 | const value = (from - start) / duration
25 | return Math.round(value * timelineWidth)
26 | }
27 |
28 | const toStyleLeft = from => ({
29 | left: `${toX(from)}px`,
30 | })
31 |
32 | const toStyleLeftAndWidth = (from, to) => {
33 | const left = toX(from)
34 | return {
35 | left: `${left}px`,
36 | width: `${toX(to) - left}px`,
37 | }
38 | }
39 |
40 | const fromX = x => new Date(start.getTime() + (x / timelineWidth) * duration)
41 |
42 | return {
43 | timelineWidth,
44 | timelineWidthStyle,
45 | start,
46 | end,
47 | zoom,
48 | toX,
49 | toStyleLeft,
50 | toStyleLeftAndWidth,
51 | fromX,
52 | }
53 | }
54 |
55 | export default create
56 |
--------------------------------------------------------------------------------
/src/scss/components/_element.scss:
--------------------------------------------------------------------------------
1 | .rt-element {
2 | $height: $react-timelines-track-height - 2 * $react-timelines-element-spacing;
3 |
4 | position: relative;
5 | height: $height;
6 | line-height: $height;
7 | background: #06f;
8 | color: #fff;
9 | text-align: center;
10 | }
11 |
12 | .rt-element__content {
13 | padding: 0 10px;
14 | overflow: hidden;
15 | white-space: nowrap;
16 | font-weight: bold;
17 | text-overflow: ellipsis;
18 | }
19 |
20 | .rt-element__tooltip {
21 | position: absolute;
22 | bottom: 100%;
23 | left: 50%;
24 | z-index: 2;
25 | padding: 10px;
26 | line-height: 1.3;
27 | white-space: nowrap;
28 | text-align: left;
29 | background: $react-timelines-text-color;
30 | color: white;
31 | transform: translateX(-50%) scale(0);
32 | pointer-events: none;
33 |
34 | &::before {
35 | $size: 6px;
36 | position: absolute;
37 | top: 100%;
38 | left: 50%;
39 | border-top: $size solid $react-timelines-text-color;
40 | border-right: $size solid transparent;
41 | border-left: $size solid transparent;
42 | transform: translateX(-50%);
43 | content: ' ';
44 | }
45 | }
46 |
47 | .rt-element:hover > .rt-element__tooltip,
48 | .rt-element:focus > .rt-element__tooltip {
49 | $delay: 0.3s;
50 | transform: translateX(-50%) scale(1);
51 | transition: transform 0s $delay;
52 | }
53 |
--------------------------------------------------------------------------------
/src/components/Timeline/Grid/__tests__/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { shallow } from 'enzyme'
3 |
4 | import Grid from '..'
5 | import createTime from '../../../../utils/time'
6 |
7 | const time = createTime({
8 | start: new Date('2017-01-01T00:00:00.000Z'),
9 | end: new Date('2017-01-30T00:00:00.000Z'),
10 | zoom: 10, // 10px === 1 day
11 | })
12 |
13 | const grid = [
14 | {
15 | id: '1',
16 | start: new Date('2017-01-01T00:00:00.000Z'),
17 | end: new Date('2017-01-06T00:00:00.000Z'),
18 | },
19 | {
20 | id: '2',
21 | start: new Date('2017-01-06T00:00:00.000Z'),
22 | end: new Date('2017-01-11T00:00:00.000Z'),
23 | },
24 | {
25 | id: '3',
26 | start: new Date('2017-01-11T00:00:00.000Z'),
27 | end: new Date('2017-01-16T00:00:00.000Z'),
28 | },
29 | ]
30 |
31 | describe('
30 | ) 31 | } 32 | 33 | Element.propTypes = { 34 | time: PropTypes.shape({ 35 | toStyleLeftAndWidth: PropTypes.func, 36 | }).isRequired, 37 | style: PropTypes.shape({}), 38 | classes: PropTypes.arrayOf(PropTypes.string.isRequired), 39 | dataSet: PropTypes.shape({}), 40 | title: PropTypes.string, 41 | start: PropTypes.instanceOf(Date).isRequired, 42 | end: PropTypes.instanceOf(Date).isRequired, 43 | tooltip: PropTypes.string, 44 | clickElement: PropTypes.func, 45 | } 46 | 47 | Element.defaultTypes = { 48 | clickElement: undefined, 49 | } 50 | 51 | export default Element 52 | -------------------------------------------------------------------------------- /src/components/Elements/Basic.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { getDayMonth } from '../../utils/formatDate' 4 | import createClasses from '../../utils/classes' 5 | 6 | const buildDataAttributes = (attributes = {}) => { 7 | const value = {} 8 | Object.keys(attributes).forEach(name => { 9 | value[`data-${name}`] = attributes[name] 10 | }) 11 | return value 12 | } 13 | 14 | const Basic = ({ title, start, end, style, classes, dataSet, tooltip }) => ( 15 |