UNPKG

@nateradebaugh/react-datetime

Version:

A lightweight but complete datetime picker React.js component

340 lines (293 loc) 8.83 kB
import React, { Component } from "react"; import format from "date-fns/format"; import getHours from "date-fns/get_hours"; import onClickOutside from "react-onclickoutside"; const padValues = { hours: 1, minutes: 2, seconds: 2, milliseconds: 3 }; class TimeView extends Component { constructor(props) { super(props); this.state = this.calculateState(props); // Bind functions this.onStartClicking = this.onStartClicking.bind(this); this.disableContextMenu = this.disableContextMenu.bind(this); this.toggleDayPart = this.toggleDayPart.bind(this); this.increase = this.increase.bind(this); this.decrease = this.decrease.bind(this); this.pad = this.pad.bind(this); this.calculateState = this.calculateState.bind(this); this.updateMilli = this.updateMilli.bind(this); this.renderHeader = this.renderHeader.bind(this); this.renderCounter = this.renderCounter.bind(this); this.renderDayPart = this.renderDayPart.bind(this); this.getFormatOptions = this.getFormatOptions.bind(this); } getFormatOptions() { return { locale: this.props.locale }; } componentDidMount() { this.timeConstraints = { hours: { min: 0, max: 23, step: 1 }, minutes: { min: 0, max: 59, step: 1 }, seconds: { min: 0, max: 59, step: 1 }, milliseconds: { min: 0, max: 999, step: 1 } }; if (this.props.timeConstraints) { ["hours", "minutes", "seconds", "millisecond"].forEach(type => { if (this.props.timeConstraints[type]) { this.timeConstraints[type] = { ...this.timeConstraints[type], ...this.props.timeConstraints[type] }; } }); } this.setState(this.calculateState(this.props)); } UNSAFE_componentWillReceiveProps(nextProps) { this.setState(this.calculateState(nextProps)); } onStartClicking(action, type) { return () => { const update = {}; update[type] = this[action](type); this.setState(update); this.timer = setTimeout(() => { this.increaseTimer = setInterval(() => { update[type] = this[action](type); this.setState(update); }, 70); }, 500); this.mouseUpListener = () => { clearTimeout(this.timer); clearInterval(this.increaseTimer); this.props.setTime(type, this.state[type]); document.body.removeEventListener("mouseup", this.mouseUpListener); document.body.removeEventListener("touchend", this.mouseUpListener); }; document.body.addEventListener("mouseup", this.mouseUpListener); document.body.addEventListener("touchend", this.mouseUpListener); }; } disableContextMenu(event) { event.preventDefault(); return false; } toggleDayPart(type) { const constraints = this.timeConstraints[type]; // type is always 'hours' let value = parseInt(this.state[type], 10) + 12; if (value > constraints.max) { value = constraints.min + (value - (constraints.max + 1)); } return this.pad(type, value); } increase(type) { const constraints = this.timeConstraints[type]; let value = parseInt(this.state[type], 10) + constraints.step; if (value > constraints.max) { value = constraints.min + (value - (constraints.max + 1)); } return this.pad(type, value); } decrease(type) { const constraints = this.timeConstraints[type]; let value = parseInt(this.state[type], 10) - constraints.step; if (value < constraints.min) { value = constraints.max + 1 - (constraints.min - value); } return this.pad(type, value); } pad(type, value) { let str = value + ""; while (str.length < padValues[type]) str = "0" + str; return str; } calculateState(props) { const date = props.selectedDate || props.viewDate; const timeFormat = typeof props.timeFormat === "string" ? props.timeFormat.toLowerCase() : ""; const counters = []; if (timeFormat.toLowerCase().indexOf("h") !== -1) { counters.push("hours"); if (timeFormat.indexOf("m") !== -1) { counters.push("minutes"); if (timeFormat.indexOf("s") !== -1) { counters.push("seconds"); } } } const hours = getHours(date); let daypart = undefined; if (this.state !== null && timeFormat.indexOf(" a") !== -1) { if (props.timeFormat.indexOf(" A") !== -1) { daypart = hours >= 12 ? "PM" : "AM"; } else { daypart = hours >= 12 ? "pm" : "am"; } } return { hours: hours, minutes: format(date, "mm", this.getFormatOptions()), seconds: format(date, "ss", this.getFormatOptions()), milliseconds: format(date, "SSS", this.getFormatOptions()), daypart: daypart, counters: counters }; } updateMilli(e) { const milli = parseInt(e.target.value, 10); if (milli === e.target.value && milli >= 0 && milli < 1000) { this.props.setTime("milliseconds", milli); this.setState({ milliseconds: milli }); } } renderHeader() { if (!this.props.dateFormat) { return null; } const date = this.props.selectedDate || this.props.viewDate; return ( <thead> <tr> <th className="rdtSwitch" colSpan={4} onClick={this.props.showView("days")} > {format(date, this.props.dateFormat)} </th> </tr> </thead> ); } renderCounter(type) { const timeFormat = typeof this.props.timeFormat === "string" ? this.props.timeFormat.toLowerCase() : ""; if (type !== "daypart") { let value = this.state[type]; if (type === "hours" && timeFormat.indexOf(" a") !== -1) { value = ((value - 1) % 12) + 1; if (value === 0) { value = 12; } } return ( <div key={type} className="rdtCounter"> <span className="rdtBtn" onMouseDown={this.onStartClicking("increase", type)} onContextMenu={this.disableContextMenu} > ▲ </span> <div className="rdtCount">{value}</div> <span className="rdtBtn" onMouseDown={this.onStartClicking("decrease", type)} onContextMenu={this.disableContextMenu} > ▼ </span> </div> ); } return null; } renderDayPart() { return ( <div key="dayPart" className="rdtCounter"> <span className="rdtBtn" onMouseDown={this.onStartClicking("toggleDayPart", "hours")} onContextMenu={this.disableContextMenu} > ▲ </span> <div className="rdtCount">{this.state.daypart}</div> <span className="rdtBtn" onMouseDown={this.onStartClicking("toggleDayPart", "hours")} onContextMenu={this.disableContextMenu} > ▼ </span> </div> ); } render() { const counters = []; this.state.counters.forEach(c => { if (counters.length) { counters.push( <div key={`sep${counters.length}`} className="rdtCounterSeparator"> : </div> ); } counters.push(this.renderCounter(c)); }); if (this.state.daypart) { counters.push(this.renderDayPart()); } const timeFormat = this.props.timeFormat || ""; if (this.state.counters.length === 3 && timeFormat.indexOf("S") !== -1) { counters.push( <div key="sep5" className="rdtCounterSeparator"> : </div> ); counters.push( <div key="ms" className="rdtCounter rdtMilli"> <input type="text" value={this.state.milliseconds} onChange={this.updateMilli} /> </div> ); } return ( <div className="rdtTime"> <table> {this.renderHeader()} <tbody> <tr> <td> <div className="rdtCounters">{counters}</div> </td> </tr> </tbody> </table> </div> ); } handleClickOutside() { this.props.handleClickOutside(); } } export default onClickOutside(TimeView);