labo-components
Version:
182 lines (160 loc) • 5.49 kB
JSX
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import IDUtil from '../../../util/IDUtil';
export default class TimeInput extends React.PureComponent {
constructor(props) {
super(props);
this.hours = React.createRef();
this.minutes = React.createRef();
this.seconds = React.createRef();
this.state = this.parseTime(this.props.time);
}
parseTime(time) {
const hours = Math.floor(time / 3600);
time -= hours * 3600;
const minutes = Math.floor(time / 60);
time -= minutes * 60;
const seconds = Math.floor(time);
time -= seconds;
return { hours, minutes, seconds };
}
getTime(hours, minutes, seconds) {
return Math.max(0, hours * 3600 + minutes * 60 + seconds);
}
onUpdate = () => {
const values = this.getFormValues();
const time = this.getTime(values.hours, values.minutes, values.seconds);
this.setState(this.parseTime(time), () => {
// always force update to prevent negative inputs
this.forceUpdate();
});
// optional callback
if (this.props.onUpdate) {
this.props.onUpdate(time);
}
};
// can be called from ref
getValue() {
const values = this.getFormValues();
return this.getTime(values.hours, values.minutes, values.seconds);
}
// can be called from ref
setValue(time) {
this.setState(this.parseTime(time));
}
getFormValues = () => {
// additional checks to make the value increase/decrease, but not zero out smaller values
// hours
let hours = Math.max(0, parseInt(this.hours.current.value));
hours = isNaN(hours) ? 0 : hours;
// minutes
let minutes = parseInt(this.minutes.current.value.toString().slice(-2));
minutes = isNaN(minutes) ? 0 : minutes;
if (hours == 0) {
minutes = Math.max(0, minutes);
}
// seconds
let seconds = parseInt(this.seconds.current.value.toString().slice(-2));
seconds = isNaN(seconds) ? 0 : seconds;
if (minutes == 0) {
seconds = Math.max(0, seconds);
}
return { hours, minutes, seconds };
};
zeroPad(v) {
const zero = 2 - v.toString().length + 1;
return Array(+(zero > 0 && zero)).join('0') + v;
}
onFocus = e => {
e.target.select();
};
onKeyDown = e => {
switch (e.keyCode) {
case 38: // up
e.target.value =
parseInt(e.target.value) + (e.shiftKey ? 10 : 1);
this.onUpdate();
break;
case 40: // down
e.target.value =
parseInt(e.target.value) - (e.shiftKey ? 10 : 1);
this.onUpdate();
break;
case 13: // enter
this.onFinish();
break;
}
};
onBlur = () => {
this.forceUpdate();
};
// optional onFinish callback will be called on blur or on enter-key
// to let parent know input can be progressed
onFinish = () => {
if (this.props.onFinish) {
const values = this.getFormValues();
const time = this.getTime(
values.hours,
values.minutes,
values.seconds
);
this.props.onFinish(time);
}
};
render() {
return (
<div className={classNames(IDUtil.cssClassName('time-input'))}>
<input
type="text"
ref={this.hours}
value={
this.hours.current == document.activeElement
? this.state.hours
: this.zeroPad(this.state.hours)
}
onChange={this.onUpdate}
onFocus={this.onFocus}
onKeyDown={this.onKeyDown}
onBlur={this.onBlur}
/>
:
<input
type="text"
ref={this.minutes}
value={
this.minutes.current == document.activeElement
? this.state.minutes
: this.zeroPad(this.state.minutes)
}
maxLength={2}
onChange={this.onUpdate}
onFocus={this.onFocus}
onKeyDown={this.onKeyDown}
onBlur={this.onBlur}
/>
:
<input
type="text"
ref={this.seconds}
value={
this.seconds.current == document.activeElement
? this.state.seconds
: this.zeroPad(this.state.seconds)
}
maxLength={2}
onChange={this.onUpdate}
onFocus={this.onFocus}
onKeyDown={this.onKeyDown}
onBlur={this.onBlur}
/>
</div>
);
}
}
TimeInput.propTypes = {
// time in seconds
time: PropTypes.number.isRequired,
onUpdate: PropTypes.func,
onFinish: PropTypes.func
};