Split out CustomTimeRange date picker into its own component so it can be shared between separate Dropdown and Overlay components.

pull/10616/head
Hunter Trujillo 2017-06-28 17:50:35 -06:00
parent 217695b962
commit 35d75b5d9d
4 changed files with 227 additions and 103 deletions

View File

@ -0,0 +1,97 @@
import React, {PropTypes, Component} from 'react'
import rome from 'rome'
import moment from 'moment'
class CustomTimeRange extends Component {
constructor(props) {
super(props)
this.handleClick = ::this.handleClick
this._formatTimeRange = ::this._formatTimeRange
}
componentDidMount() {
const {timeRange} = this.props
const lower = rome(this.lower, {
initialValue: this._formatTimeRange(timeRange.lower),
})
const upper = rome(this.upper, {
initialValue: this._formatTimeRange(timeRange.upper),
})
this.lowerCal = lower
this.upperCal = upper
}
// If there is an upper or lower time range set, set the corresponding calendar's value.
componentWillReceiveProps(nextProps) {
const {lower, upper} = nextProps.timeRange
if (lower) {
this.lowerCal.setValue(this._formatTimeRange(lower))
}
if (upper) {
this.upperCal.setValue(this._formatTimeRange(upper))
}
}
render() {
return (
<div className="custom-time--container">
<div className="custom-time--dates">
<div className="custom-time--lower" ref={r => (this.lower = r)} />
<div className="custom-time--upper" ref={r => (this.upper = r)} />
</div>
<div
className="custom-time--apply btn btn-sm btn-primary"
onClick={this.handleClick}
>
Apply
</div>
</div>
)
}
/*
* Upper and lower time ranges are passed in with single quotes as part of
* the string literal, i.e. "'2015-09-23T18:00:00.000Z'". Remove them
* before passing the string to be parsed.
*/
_formatTimeRange(timeRange) {
if (!timeRange) {
return ''
}
// If the given time range is relative, create a fixed timestamp based on its value
if (timeRange.match(/^now/)) {
const match = timeRange.match(/\d+\w/)[0]
const duration = match.slice(0, match.length - 1)
const unitOfTime = match[match.length - 1]
return moment().subtract(duration, unitOfTime)
}
return moment(timeRange.replace(/\'/g, '')).format('YYYY-MM-DD HH:mm')
}
handleClick() {
const lower = this.lowerCal.getDate().toISOString()
const upper = this.upperCal.getDate().toISOString()
this.props.onApplyTimeRange({lower, upper})
this.props.onClose()
}
}
const {func, shape, string} = PropTypes
CustomTimeRange.propTypes = {
onApplyTimeRange: func.isRequired,
timeRange: shape({
lower: string.isRequired,
upper: string.isRequired,
}).isRequired,
onClose: func.isRequired,
}
export default CustomTimeRange

View File

@ -1,48 +1,28 @@
import React, {PropTypes, Component} from 'react'
import rome from 'rome'
import moment from 'moment'
import classnames from 'classnames'
import OnClickOutside from 'react-onclickoutside'
import CustomTimeRange from 'shared/components/CustomTimeRange'
class CustomTimeRangeDropdown extends Component {
constructor(props) {
super(props)
this.handleClick = ::this.handleClick
}
handleClickOutside() {
this.props.onClose()
}
componentDidMount() {
const {timeRange} = this.props
const lower = rome(this.lower, {
initialValue: this._formatTimeRange(timeRange.lower),
})
const upper = rome(this.upper, {
initialValue: this._formatTimeRange(timeRange.upper),
})
this.lowerCal = lower
this.upperCal = upper
}
// If there is an upper or lower time range set, set the corresponding calendar's value.
componentWillReceiveProps(nextProps) {
const {lower, upper} = nextProps.timeRange
if (lower) {
this.lowerCal.setValue(this._formatTimeRange(lower))
}
if (upper) {
this.upperCal.setValue(this._formatTimeRange(upper))
}
}
render() {
const {isVisible, onToggle, timeRange: {upper, lower}} = this.props
const {
isVisible,
onToggle,
onClose,
timeRange: {upper, lower},
timeRange,
onApplyTimeRange,
} = this.props
return (
<div
@ -60,48 +40,15 @@ class CustomTimeRangeDropdown extends Component {
<span className="caret" />
</button>
<div className="custom-time--container">
<div className="custom-time--dates">
<div className="custom-time--lower" ref={r => (this.lower = r)} />
<div className="custom-time--upper" ref={r => (this.upper = r)} />
</div>
<div
className="custom-time--apply btn btn-sm btn-primary"
onClick={this.handleClick}
>
Apply
</div>
<CustomTimeRange
onApplyTimeRange={onApplyTimeRange}
timeRange={timeRange}
onClose={onClose}
/>
</div>
</div>
)
}
handleClick() {
const lower = this.lowerCal.getDate().toISOString()
const upper = this.upperCal.getDate().toISOString()
this.props.onApplyTimeRange({lower, upper})
this.props.onClose()
}
/*
* Upper and lower time ranges are passed in with single quotes as part of
* the string literal, i.e. "'2015-09-23T18:00:00.000Z'". Remove them
* before passing the string to be parsed.
*/
_formatTimeRange(timeRange) {
if (!timeRange) {
return ''
}
// If the given time range is relative, create a fixed timestamp based on its value
if (timeRange.match(/^now/)) {
const match = timeRange.match(/\d+\w/)[0]
const duration = match.slice(0, match.length - 1)
const unitOfTime = match[match.length - 1]
return moment().subtract(duration, unitOfTime)
}
return moment(timeRange.replace(/\'/g, '')).format('YYYY-MM-DD HH:mm')
}
}
const {bool, func, shape, string} = PropTypes

View File

@ -0,0 +1,46 @@
import React, {PropTypes, Component} from 'react'
import OnClickOutside from 'react-onclickoutside'
import CustomTimeRange from 'shared/components/CustomTimeRange'
import OverlayTechnologies from 'shared/components/OverlayTechnologies'
class CustomTimeRangeOverlay extends Component {
constructor(props) {
super(props)
}
handleClickOutside() {
this.props.onClose()
}
render() {
const {onClose, timeRange, onApplyTimeRange} = this.props
return (
<OverlayTechnologies>
<div className="custom-time--overlay-container">
<CustomTimeRange
onApplyTimeRange={onApplyTimeRange}
timeRange={timeRange}
onClose={onClose}
/>
</div>
</OverlayTechnologies>
)
}
}
const {bool, func, shape, string} = PropTypes
CustomTimeRangeOverlay.propTypes = {
onApplyTimeRange: func.isRequired,
timeRange: shape({
lower: string.isRequired,
upper: string.isRequired,
}).isRequired,
isVisible: bool.isRequired,
onToggle: func.isRequired,
onClose: func.isRequired,
}
export default OnClickOutside(CustomTimeRangeOverlay)

View File

@ -4,6 +4,7 @@ import moment from 'moment'
import OnClickOutside from 'shared/components/OnClickOutside'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import CustomTimeRangeOverlay from 'shared/components/CustomTimeRangeOverlay'
import timeRanges from 'hson!shared/data/timeRanges.hson'
import {DROPDOWN_MENU_MAX_HEIGHT} from 'shared/constants/index'
@ -14,11 +15,19 @@ class TimeRangeDropdown extends Component {
this.state = {
autobind: false,
isOpen: false,
isCustomTimeRangeOpen: false,
customTimeRange: {
lower: '',
upper: '',
},
}
this.findTimeRangeInputValue = ::this.findTimeRangeInputValue
this.handleSelection = ::this.handleSelection
this.toggleMenu = ::this.toggleMenu
this.showCustomTimeRange = ::this.showCustomTimeRange
this.handleApplyCustomTimeRange = ::this.handleApplyCustomTimeRange
this.handleToggleCustomTimeRange = ::this.handleToggleCustomTimeRange
this.handleCloseCustomTimeRange = ::this.handleCloseCustomTimeRange
}
findTimeRangeInputValue({upper, lower}) {
@ -50,47 +59,72 @@ class TimeRangeDropdown extends Component {
this.setState({isOpen: !this.state.isOpen})
}
showCustomTimeRange() {}
showCustomTimeRange() {
this.setState({isCustomTimeRangeOpen: true})
}
handleApplyCustomTimeRange(timeRange) {
this.setState({timeRange})
}
handleToggleCustomTimeRange() {
this.setState({isCustomTimeRangeOpen: !this.state.isCustomTimeRangeOpen})
}
handleCloseCustomTimeRange() {
this.setState({isCustomTimeRangeOpen: false})
}
render() {
const {selected} = this.props
const {isOpen} = this.state
const {isOpen, customTimeRange, isCustomTimeRangeOpen} = this.state
return (
<div className={classnames('dropdown dropdown-160', {open: isOpen})}>
<div
className="btn btn-sm btn-default dropdown-toggle"
onClick={() => this.toggleMenu()}
>
<span className="icon clock" />
<span className="dropdown-selected">
{this.findTimeRangeInputValue(selected)}
</span>
<span className="caret" />
</div>
<ul className="dropdown-menu">
<FancyScrollbar
autoHide={false}
autoHeight={true}
maxHeight={DROPDOWN_MENU_MAX_HEIGHT}
<div>
<div className={classnames('dropdown dropdown-160', {open: isOpen})}>
<div
className="btn btn-sm btn-default dropdown-toggle"
onClick={() => this.toggleMenu()}
>
<li className="dropdown-header">Time Range</li>
<li className="custom-timerange">
<a href="#" onClick={this.showCustomTimeRange}>
Custom Time Range
</a>
</li>
{timeRanges.map(item => {
return (
<li className="dropdown-item" key={item.menuOption}>
<a href="#" onClick={() => this.handleSelection(item)}>
{item.menuOption}
</a>
</li>
)
})}
</FancyScrollbar>
</ul>
<span className="icon clock" />
<span className="dropdown-selected">
{this.findTimeRangeInputValue(selected)}
</span>
<span className="caret" />
</div>
<ul className="dropdown-menu">
<FancyScrollbar
autoHide={false}
autoHeight={true}
maxHeight={DROPDOWN_MENU_MAX_HEIGHT}
>
<li className="dropdown-header">Time Range</li>
<li className="custom-timerange">
<a href="#" onClick={this.showCustomTimeRange}>
Custom Time Range
</a>
</li>
{timeRanges.map(item => {
return (
<li className="dropdown-item" key={item.menuOption}>
<a href="#" onClick={() => this.handleSelection(item)}>
{item.menuOption}
</a>
</li>
)
})}
</FancyScrollbar>
</ul>
</div>
{isCustomTimeRangeOpen
? <CustomTimeRangeOverlay
onApplyTimeRange={this.handleApplyCustomTimeRange}
timeRange={customTimeRange}
isVisible={isCustomTimeRangeOpen}
onToggle={this.handleToggleCustomTimeRange}
onClose={this.handleCloseCustomTimeRange}
/>
: null}
</div>
)
}