feat(scheduler): better time picker

fix(scheduler): time picker fixed
This commit is contained in:
Evan Morikawa 2016-04-07 12:15:04 -07:00
parent 199a900ac1
commit 6bc42a10dc
4 changed files with 148 additions and 18 deletions

View file

@ -17,9 +17,6 @@
},
"icon": "./icon.png",
"isOptional": true,
"dependencies": {
"moment-round": "^1.0"
},
"windowTypes": {
"default": true,
"composer": true,

View file

@ -33,6 +33,7 @@
color: @text-color;
}
.row {
position: relative;
display: flex;
align-items: baseline;
padding: 10px 0;
@ -60,6 +61,7 @@
}
}
.row.time {
z-index: 10; // So the time pickers show over
.time-picker {
text-align: center;
}

View file

@ -1,8 +1,11 @@
import React from 'react'
import ReactDOM from 'react-dom'
import moment from 'moment'
require('moment-round') // overrides moment
import classnames from 'classnames'
const INTERVAL = [30, 'minutes']
export default class TimePicker extends React.Component {
static displayName = "TimePicker";
@ -29,26 +32,54 @@ export default class TimePicker extends React.Component {
}
}
componentDidMount() {
this._fixTimeOptionScroll()
}
componentWillReceiveProps(newProps) {
this.setState({rawText: this._valToTimeString(newProps.value)})
}
componentDidUpdate() {
if (this._gotoScrollStartOnUpdate) {
this._fixTimeOptionScroll()
}
}
_valToTimeString(value) {
return moment(value).format("LT")
}
_onKeyDown = (event) => {
if (event.key === "ArrowUp") {
// TODO: When `renderTimeOptions` is implemented
event.preventDefault()
this._onArrow(event.key)
} else if (event.key === "ArrowDown") {
// TODO: When `renderTimeOptions` is implemented
event.preventDefault()
this._onArrow(event.key)
} else if (event.key === "Enter") {
this.context.parentTabGroup.shiftFocus(1);
}
}
_onArrow(key) {
let newT = moment(this.props.value);
newT = newT.round.apply(newT, INTERVAL);
if (key === "ArrowUp") {
newT = newT.subtract.apply(newT, INTERVAL);
} else if (key === "ArrowDown") {
newT = newT.add.apply(newT, INTERVAL);
}
if (moment(this.props.value).day() !== newT.day()) {
return
}
this._gotoScrollStartOnUpdate = true
this.props.onChange(newT);
}
_onFocus = () => {
this.setState({focused: true});
this._gotoScrollStartOnUpdate = true
const el = ReactDOM.findDOMNode(this.refs.input);
el.setSelectionRange(0, el.value.length)
}
@ -83,26 +114,86 @@ export default class TimePicker extends React.Component {
if (simpleDigitMatch && simpleDigitMatch.length > 0) {
const hr = parseInt(simpleDigitMatch[1], 10);
if (hr <= 7) {
// If you're going to punch in "2" into the time field, you
// probably mean 2pm, not 2am.
return true
}
}
return false
}
// TODO
_renderTimeOptions() {
// TODO: When you select a time a dropdown will additionally show
// letting you pick from preset times. The `relativeTo` prop will give
// you relative times
const opts = []
if (this.state.focused) {
return (
<div className="time-options">{opts}</div>
)
_fixTimeOptionScroll() {
this._gotoScrollStartOnUpdate = false
const el = ReactDOM.findDOMNode(this);
const scrollTo = el.querySelector(".scroll-start");
const scrollWrap = el.querySelector(".time-options");
if (scrollTo && scrollWrap) {
scrollWrap.scrollTop = scrollTo.offsetTop
}
return false
}
_onSelectOption(val) {
this.props.onChange(val)
}
_renderTimeOptions() {
if (!this.state.focused) {
return false
}
const enteredMoment = moment(this.props.value);
const roundedMoment = moment(enteredMoment);
roundedMoment.ceil.apply(roundedMoment, INTERVAL);
const firstVisibleMoment = moment(roundedMoment);
firstVisibleMoment.add.apply(firstVisibleMoment, INTERVAL);
let startVal = moment(this.props.value).startOf('day').valueOf();
startVal = Math.max(startVal, (this.props.relativeTo || 0));
const startMoment = moment(startVal)
if (this.props.relativeTo) {
startMoment.ceil.apply(startMoment, INTERVAL).add.apply(startMoment, INTERVAL)
}
const endMoment = moment(startVal).endOf('day');
const opts = []
const relStart = moment(this.props.relativeTo);
const timeIter = moment(startMoment)
while (timeIter.isSameOrBefore(endMoment)) {
const val = timeIter.valueOf();
const className = classnames({
option: true,
selected: timeIter.isSame(enteredMoment),
"scroll-start": timeIter.isSame(firstVisibleMoment),
})
let relTxt = false
if (this.props.relativeTo) {
relTxt = (
<span className="rel-text">
{`(${timeIter.diff(relStart, 'hours', true)}hr)`}
</span>
)
}
opts.push(
<div className={className} key={val}
onMouseDown={() => this._onSelectOption(val)}
>
{timeIter.format("LT")}{relTxt}
</div>
)
timeIter.add.apply(timeIter, INTERVAL)
}
const className = classnames({
"time-options": true,
"relative-to": this.props.relativeTo,
})
return (
<div className={className}>{opts}</div>
)
}
render() {

View file

@ -3,10 +3,50 @@
.time-picker-wrap {
display: inline-block;
width: 5.5em;
position: relative;
input {
width: 5.5em;
&.invalid {
background: fade(@color-error, 10%);
}
}
.time-options {
max-height: 158px;
overflow: auto;
border: 1px solid #eee;
position: absolute;
width: 100%;
text-align: center;
background: @background-primary;
border-top: 0;
border-radius: 0 0 @border-radius-base @border-radius-base;
box-shadow: 0 0 3px rgba(0,0,0,0.1);
&.relative-to {
width: 120px;
text-align: left;
.option {
padding-left: 12px;
}
}
.option {
line-height: 1.75;
&.selected {
color: @text-color;
&:hover {
background: transparent;
}
}
&.focused, &:hover {
background: rgba(0,0,0,0.05);
}
.rel-text {
font-size: 0.8em;
margin-left: 4px;
}
}
}
}