Skip to content
This repository was archived by the owner on Oct 20, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-timelines",
"version": "2.4.0",
"version": "2.5.0",
"description": "React Timelines",
"main": "lib/index.js",
"scripts": {
Expand Down Expand Up @@ -42,7 +42,7 @@
"babel-preset-react": "^6.23.0",
"babel-preset-stage-2": "^6.22.0",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"enzyme-adapter-react-16": "npm:enzyme-react-adapter-future",
"eslint": "^4.19.1",
"eslint-config-airbnb": "^16.1.0",
"eslint-plugin-import": "^2.12.0",
Expand Down
6 changes: 6 additions & 0 deletions src/components/Contexts/Viewport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react'

export default React.createContext({
left: 0,
right: 0
})
25 changes: 13 additions & 12 deletions src/components/Elements/Basic.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React from 'react'
import PropTypes from 'prop-types'
import { getDayMonth } from '../../utils/formatDate'

import createClasses from '../../utils/classes'
import Tooltip from './Tooltip'
import Viewport from '../Contexts/Viewport'

const buildDataAttributes = (attributes = {}) => {
const value = {}
Expand All @@ -22,20 +24,19 @@ const Basic = ({
<div className="rt-element__content" aria-hidden="true">
<span className="rt-element__title">{ title }</span>
</div>
<div className="rt-element__tooltip">
<Viewport.Consumer>
{
tooltip
// eslint-disable-next-line react/no-danger
? <div dangerouslySetInnerHTML={{ __html: tooltip.split('\n').join('<br>') }} />
: (
<div>
<div>{title}</div>
<div><strong>Start</strong> {getDayMonth(start)}</div>
<div><strong>End</strong> {getDayMonth(end)}</div>
</div>
viewport => (
<Tooltip
title={title}
start={start}
end={end}
tooltip={tooltip}
viewport={viewport}
/>
)
}
</div>
</Viewport.Consumer>
</div>
)

Expand Down
85 changes: 85 additions & 0 deletions src/components/Elements/Tooltip.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'

import { getDayMonth } from '../../utils/formatDate'

class Tooltip extends Component {
constructor(props) {
super(props)

this.selfRef = React.createRef()
this.state = {
bounds: {}
}
}

componentDidMount() {
const bounds = this.selfRef.current.getBoundingClientRect()

// eslint-disable-next-line react/no-did-mount-set-state
this.setState({ bounds })
}

render() {
const {
title,
start,
end,
tooltip,
viewport
} = this.props
const { bounds } = this.state

let offset = 0
let className = 'rt-element__tooltip'

if (bounds.left < viewport.left) {
offset = Math.floor(viewport.left - bounds.left)
className += ' rt-element__tooltip--left'
}

if (bounds.right > viewport.right) {
offset = -Math.ceil(bounds.right - viewport.right)
className += ' rt-element__tooltip--right'
}

const style = {
marginLeft: `${offset}px`
}

return (
<div className={className} ref={this.selfRef} style={style}>
{
tooltip
// eslint-disable-next-line react/no-danger
? <div dangerouslySetInnerHTML={{ __html: tooltip.split('\n').join('<br>') }} />
: (
<div>
<div>{title}</div>
<div><strong>Start</strong> {getDayMonth(start)}</div>
<div><strong>End</strong> {getDayMonth(end)}</div>
</div>
)
}
</div>
)
}
}

Tooltip.propTypes = {
title: PropTypes.string.isRequired,
start: PropTypes.instanceOf(Date).isRequired,
end: PropTypes.instanceOf(Date).isRequired,
tooltip: PropTypes.string,
viewport: PropTypes.shape({
left: PropTypes.number
})
}

Tooltip.defaultProps = {
viewport: {
left: 0
}
}

export default Tooltip
10 changes: 5 additions & 5 deletions src/components/Elements/__tests__/Basic.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import { shallow } from 'enzyme'
import { mount, shallow } from 'enzyme'

import Basic from '../Basic'

Expand All @@ -19,14 +19,14 @@ describe('<Basic />', () => {
it('renders the tooltip value if it exists', () => {
const tooltip = 'Test tooltip'
const props = { ...defaultProps, tooltip }
const wrapper = shallow(<Basic {...props} />)
const wrapper = mount(<Basic {...props} />)
expect(getTooltip(wrapper).html()).toMatch('Test tooltip')
})

it('handles multiline tooltips', () => {
const tooltip = 'Test\ntooltip'
const props = { ...defaultProps, tooltip }
const wrapper = shallow(<Basic {...props} />)
const wrapper = mount(<Basic {...props} />)
expect(getTooltip(wrapper).html()).toMatch('Test<br>tooltip')
})

Expand All @@ -38,15 +38,15 @@ describe('<Basic />', () => {
const props = {
...defaultProps, tooltip, title, start, end
}
const wrapper = shallow(<Basic {...props} />)
const wrapper = mount(<Basic {...props} />)
expect(getTooltip(wrapper).text()).toMatch('TEST')
expect(getTooltip(wrapper).text()).toMatch('Start 20 Mar')
expect(getTooltip(wrapper).text()).toMatch('End 15 Apr')
})

it('can take an optional list of classnames to add to the parent', () => {
const props = { ...defaultProps, classes: ['foo', 'bar'] }
const wrapper = shallow(<Basic {...props} />)
const wrapper = mount(<Basic {...props} />)
expect(wrapper.find('.rt-element').hasClass('foo')).toBe(true)
expect(wrapper.find('.rt-element').hasClass('bar')).toBe(true)
})
Expand Down
70 changes: 44 additions & 26 deletions src/components/Layout/index.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { PureComponent } from 'react'
import React, { Component } from 'react'
import PropTypes from 'prop-types'

import ViewportContext from '../Contexts/Viewport'
import Sidebar from '../Sidebar'
import Timeline from '../Timeline'
import { addListener, removeListener } from '../../utils/events'
Expand All @@ -9,7 +10,7 @@ import getNumericPropertyValue from '../../utils/getNumericPropertyValue'

const noop = () => {}

class Layout extends PureComponent {
class Layout extends Component {
constructor(props) {
super(props)

Expand All @@ -20,7 +21,8 @@ class Layout extends PureComponent {
this.state = {
isSticky: false,
headerHeight: 0,
scrollLeft: 0
scrollLeft: 0,
viewport: {}
}
}

Expand All @@ -36,14 +38,13 @@ class Layout extends PureComponent {
}

componentDidUpdate(prevProps, prevState) {
if (this.props.enableSticky && this.state.isSticky) {
if (!prevState.isSticky) {
this.updateTimelineHeaderScroll()
}
if (this.props.enableSticky && this.state.isSticky && !prevState.isSticky) {
this.updateTimelineHeaderScroll()
}

if (this.state.scrollLeft !== prevState.scrollLeft) {
this.updateTimelineBodyScroll()
}
if (this.state.scrollLeft !== prevState.scrollLeft) {
this.updateTimelineBodyScroll()
this.updateTimelineViewport()
}

if (this.props.isOpen !== prevProps.isOpen) {
Expand All @@ -64,9 +65,12 @@ class Layout extends PureComponent {

scrollToNow = () => {
const { time, scrollToNow, now } = this.props

if (scrollToNow) {
this.timeline.current.scrollLeft = time.toX(now) - (0.5 * this.props.timelineViewportWidth)
}

this.updateTimelineHeaderScroll()
}

updateTimelineBodyScroll = () => {
Expand All @@ -75,7 +79,17 @@ class Layout extends PureComponent {

updateTimelineHeaderScroll = () => {
const { scrollLeft } = this.timeline.current
this.setState({ scrollLeft })
this.setState({ scrollLeft }, () => this.updateTimelineViewport())
}

updateTimelineViewport = () => {
const { left, right } = this.timeline.current.getBoundingClientRect()
this.setState({
viewport: {
left: left + this.state.scrollLeft,
right: right + this.state.scrollLeft
}
})
}

handleHeaderScrollY = (scrollLeft) => {
Expand Down Expand Up @@ -136,8 +150,10 @@ class Layout extends PureComponent {
const {
isSticky,
headerHeight,
scrollLeft
scrollLeft,
viewport
} = this.state

return (
<div
className={`rt-layout ${isOpen ? 'rt-is-open' : ''}`}
Expand All @@ -160,20 +176,22 @@ class Layout extends PureComponent {
ref={this.timeline}
onScroll={isSticky ? this.handleScrollX : noop}
>
<Timeline
now={now}
time={time}
timebar={timebar}
tracks={tracks}
sticky={{
isSticky,
setHeaderHeight: this.setHeaderHeight,
viewportWidth: timelineViewportWidth,
handleHeaderScrollY: this.handleHeaderScrollY,
headerHeight,
scrollLeft
}}
/>
<ViewportContext.Provider value={viewport}>
<Timeline
now={now}
time={time}
timebar={timebar}
tracks={tracks}
sticky={{
isSticky,
setHeaderHeight: this.setHeaderHeight,
viewportWidth: timelineViewportWidth,
handleHeaderScrollY: this.handleHeaderScrollY,
headerHeight,
scrollLeft
}}
/>
</ViewportContext.Provider>
</div>
</div>
</div>
Expand Down
26 changes: 19 additions & 7 deletions src/scss/components/_element.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
text-overflow: ellipsis;
}

$tooltip-size: 6px;

.rt-element__tooltip {
position: absolute;
bottom: 100%;
Expand All @@ -28,25 +30,35 @@
text-align: left;
background: $react-timelines-text-color;
color: white;
transform: translateX(-50%) scale(0);
transform: translateX(-50%);
opacity: 0;
pointer-events: none;

&::before {
$size: 6px;
$tooltip-size: 6px;
position: absolute;
top: 100%;
left: 50%;
border-top: $size solid $react-timelines-text-color;
border-right: $size solid transparent;
border-left: $size solid transparent;
border-top: $tooltip-size solid $react-timelines-text-color;
border-right: $tooltip-size solid transparent;
border-left: $tooltip-size solid transparent;
transform: translateX(-50%);
content: ' ';
}
}

.rt-element__tooltip--left::before {
left: $tooltip-size;
}

.rt-element__tooltip--right::before {
left: 100%;
margin-left: -$tooltip-size;
}

.rt-element:hover > .rt-element__tooltip,
.rt-element:focus > .rt-element__tooltip {
$delay: 0.3s;
transform: translateX(-50%) scale(1);
transition: transform 0s $delay;
opacity: 1;
transition: opacity 0s $delay;
}
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1753,13 +1753,13 @@ entities@^1.1.1, entities@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"

enzyme-adapter-react-16@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.1.1.tgz#a8f4278b47e082fbca14f5bfb1ee50ee650717b4"
"enzyme-adapter-react-16@npm:enzyme-react-adapter-future":
version "1.1.3"
resolved "https://registry.yarnpkg.com/enzyme-react-adapter-future/-/enzyme-react-adapter-future-1.1.3.tgz#f0c102f098086a0ad0270bbdaf9da5113297dc05"
dependencies:
enzyme-adapter-utils "^1.3.0"
lodash "^4.17.4"
object.assign "^4.0.4"
object.assign "^4.1.0"
object.values "^1.0.4"
prop-types "^15.6.0"
react-reconciler "^0.7.0"
Expand Down