UNPKG

@yncoder/element-react

Version:
520 lines (462 loc) 15.3 kB
import React from 'react' import ReactDOM from 'react-dom' import { PropTypes } from '../../../libs' import Locale from '../../locale' import Input from '../../input' import TimePanel from './TimePanel' import {MountBody} from '../MountBody' import { SELECTION_MODES, deconstructDate, formatDate, parseDate, toDate } from '../utils' import { DateTable, MonthTable, YearTable } from '../basic' import { PopperBase } from './PopperBase' import {PLACEMENT_MAP} from '../constants' const PICKER_VIEWS = { YEAR: 'year', MONTH: 'month', DATE: 'date', } export default class DatePanel extends PopperBase { static get propTypes() { return Object.assign({ // user picked date value // value: Date | null value: PropTypes.instanceOf(Date), // (Date)=>void onPick: PropTypes.func.isRequired, isShowTime: PropTypes.bool, showWeekNumber: PropTypes.bool, format: PropTypes.string, // Array[{text: String, onClick: (picker)=>()}] shortcuts: PropTypes.arrayOf( PropTypes.shape({ text: PropTypes.string.isRequired, // ()=>() onClick: PropTypes.func.isRequired }) ), selectionMode: PropTypes.oneOf(Object.keys(SELECTION_MODES).map(e => SELECTION_MODES[e])), // (Date)=>bool, if true, disabled disabledDate: PropTypes.func, firstDayOfWeek: PropTypes.range(0, 6), }, PopperBase.propTypes) } constructor(props) { super(props) let currentView = PICKER_VIEWS.DATE switch (props.selectionMode) { case SELECTION_MODES.MONTH: currentView = PICKER_VIEWS.MONTH; break; case SELECTION_MODES.YEAR: currentView = PICKER_VIEWS.YEAR; break; } this.state = { currentView, timePickerVisible: false, pickerWidth: 0, date: new Date() // current view's date } if (props.value) { this.state.date = new Date(props.value) } } componentWillReceiveProps(nextProps) { let date = new Date() if (nextProps.value){ date = toDate(nextProps.value) } this.setState({ date }) } resetDate() { this.date = new Date(this.date) } showMonthPicker() { this.setState({ currentView: PICKER_VIEWS.MONTH }) } showYearPicker() { this.setState({ currentView: PICKER_VIEWS.YEAR }) } prevMonth() { this.updateState(() => { const { date } = this.state const { month, year } = deconstructDate(date) date.setMonth(month, 1) if (month == 0) { date.setFullYear(year - 1) date.setMonth(11) } else { date.setMonth(month - 1) } }) } nextMonth() { this.updateState(() => { const { date } = this.state const { month, year } = deconstructDate(date) date.setMonth(month, 1) if (month == 11) { date.setFullYear(year + 1) date.setMonth(0) } else { date.setMonth(month + 1) } }) } nextYear() { this.updateState(() => { const { date, currentView } = this.state const { year } = deconstructDate(date) if (currentView === 'year') { date.setFullYear(year + 10) } else { date.setFullYear(year + 1) } }) } updateState(cb) { cb(this.state) this.setState({}) } prevYear() { this.updateState(() => { const { date, currentView } = this.state const { year } = deconstructDate(date) if (currentView === 'year') { date.setFullYear(year - 10) } else { date.setFullYear(year - 1) } }) } handleShortcutClick(shortcut) { shortcut.onClick() } handleTimePick(pickedDate, isKeepPanel) { this.updateState(state=>{ if (pickedDate) { let oldDate = state.date oldDate.setHours(pickedDate.getHours()) oldDate.setMinutes(pickedDate.getMinutes()) oldDate.setSeconds(pickedDate.getSeconds()) } state.timePickerVisible = isKeepPanel }) } handleMonthPick(month) { this.updateState(state => { const { date } = state const { selectionMode } = this.props const { year } = deconstructDate(date) if (selectionMode !== SELECTION_MODES.MONTH) { date.setMonth(month) state.currentView = PICKER_VIEWS.DATE } else { date.setMonth(month) date.setFullYear(year) this.props.onPick(new Date(year, month, 1)) } }) } handleDatePick(value) { this.updateState(state => { const { date } = state const { selectionMode, isShowTime, onPick } = this.props const pdate = value.date if (selectionMode === SELECTION_MODES.DAY) { if (!isShowTime) { onPick(new Date(pdate.getTime())) } date.setTime(pdate.getTime()) } else if (selectionMode === SELECTION_MODES.WEEK) { onPick(pdate) } }) } handleYearPick(year) { this.updateState(state => { const { onPick, selectionMode } = this.props const { date } = state date.setFullYear(year) if (selectionMode === SELECTION_MODES.YEAR) { onPick(new Date(year, 0)) } else { state.currentView = PICKER_VIEWS.MONTH } }) } changeToNow() { const now = new Date() this.props.onPick(now) this.setState({ date: now }) } confirm() { this.props.onPick(new Date(this.state.date.getTime())) } resetView() { let {selectionMode} = this.props this.updateState(state=>{ if (selectionMode === SELECTION_MODES.MONTH) { state.currentView = PICKER_VIEWS.MONTH } else if (selectionMode === SELECTION_MODES.YEAR) { state.currentView = PICKER_VIEWS.YEAR } else { state.currentView = PICKER_VIEWS.DATE } }) } yearLabel() { const { currentView, date } = this.state const { year } = deconstructDate(date) const yearTranslation = Locale.t('el.datepicker.year') if (currentView === 'year') { const startYear = Math.floor(year / 10) * 10 if (yearTranslation) { return startYear + ' ' + yearTranslation + '-' + (startYear + 9) + ' ' + yearTranslation } return startYear + ' - ' + (startYear + 9) } return year + ' ' + yearTranslation } get visibleTime(){ return formatDate(this.state.date, this.timeFormat) } set visibleTime(val){ if (val) { const ndate = parseDate(val, this.timeFormat) let {date} = this.state if (ndate) { ndate.setFullYear(date.getFullYear()) ndate.setMonth(date.getMonth()) ndate.setDate(date.getDate()) this.setState({date: ndate, timePickerVisible: false}) } } } get visibleDate(){ return formatDate(this.state.date, this.dateFormat) } set visibleDate(val){ const ndate = parseDate(val, this.dateFormat) if (!ndate) { return } let {disabledDate } = this.props let {date} = this.state if (typeof disabledDate === 'function' && disabledDate(ndate)) { return } ndate.setHours(date.getHours()) ndate.setMinutes(date.getMinutes()) ndate.setSeconds(date.getSeconds()) this.setState({date: ndate}) this.resetView() } get timeFormat() { let {format} = this.props if (format && format.indexOf('ss') === -1) { return 'HH:mm' } else { return 'HH:mm:ss' } } get dateFormat(){ if (this.props.format) return this.props.format.replace('HH:mm', '').replace(':ss', '').trim() else return 'yyyy-MM-dd' } // end: ------ public methods _pickerContent() { const { value, selectionMode, disabledDate, showWeekNumber, firstDayOfWeek } = this.props const { date } = this.state const { currentView } = this.state let result = null switch (currentView) { case PICKER_VIEWS.DATE: result = (<DateTable onPick={this.handleDatePick.bind(this)} date={date} value={value} selectionMode={selectionMode} disabledDate={disabledDate} showWeekNumber={showWeekNumber} firstDayOfWeek={firstDayOfWeek} />) break case PICKER_VIEWS.YEAR: result = (<YearTable ref="yearTable" value={value} date={date} onPick={this.handleYearPick.bind(this)} disabledDate={disabledDate} />) break case PICKER_VIEWS.MONTH: result = (<MonthTable value={value} date={date} onPick={this.handleMonthPick.bind(this)} disabledDate={disabledDate} />) break default: throw new Error('invalid currentView value') } return result } render() { const { isShowTime, shortcuts } = this.props const { currentView, date, pickerWidth, timePickerVisible } = this.state const { month } = deconstructDate(date) const t = Locale.t return ( <div ref="root" className={this.classNames('el-picker-panel el-date-picker', { 'has-sidebar': shortcuts, 'has-time': isShowTime })} > <div className="el-picker-panel__body-wrapper"> { Array.isArray(shortcuts) && ( <div className="el-picker-panel__sidebar"> { shortcuts.map((e, idx) => { return ( <button key={idx} type="button" className="el-picker-panel__shortcut" onClick={() => this.handleShortcutClick(e)}>{e.text}</button> ) }) } </div> ) } <div className="el-picker-panel__body"> { isShowTime && ( <div className="el-date-picker__time-header"> <span className="el-date-picker__editor-wrap"> <Input placeholder={t('el.datepicker.selectDate')} value={this.visibleDate} size="small" onChange={date=>this.visibleDate=date} /> </span> <span className="el-date-picker__editor-wrap"> <Input ref="input" onFocus={()=> this.setState({timePickerVisible: !this.state.timePickerVisible})} placeholder={t('el.datepicker.selectTime')} value={this.visibleTime} size="small" onChange={date=>this.visibleDate=date} /> { timePickerVisible && ( <MountBody> <TimePanel ref="timepicker" currentDate={new Date(date.getTime()) /* should i dont mutate date directly here ? */} pickerWidth={pickerWidth /* todo: pickerWidth? in original elmenent repo, this width is set by getting input with using getClientRect() method but it seems work even though I purposely leave this logic unimplemented. To be honest it would require some hack to get this actually done, since I can't do any setState method on componentDidUpdate method. DateRangePicker has same issue */ } onPicked={this.handleTimePick.bind(this)} format={this.timeFormat} getPopperRefElement={() => ReactDOM.findDOMNode(this.refs.input)} popperMixinOption={ { placement: PLACEMENT_MAP[this.props.align] || PLACEMENT_MAP.left } } onCancel={()=> this.setState({timePickerVisible: false})} /> </MountBody> ) } </span> </div> ) } { currentView !== 'time' && ( <div className="el-date-picker__header"> <button type="button" onClick={this.prevYear.bind(this)} className="el-picker-panel__icon-btn el-date-picker__prev-btn el-icon-d-arrow-left"> </button> { currentView === PICKER_VIEWS.DATE && ( <button type="button" onClick={this.prevMonth.bind(this)} className="el-picker-panel__icon-btn el-date-picker__prev-btn el-icon-arrow-left"> </button>) } <span onClick={this.showYearPicker.bind(this)} className="el-date-picker__header-label">{this.yearLabel()}</span> { currentView === PICKER_VIEWS.DATE && ( <span onClick={this.showMonthPicker.bind(this)} className={ this.classNames('el-date-picker__header-label', { active: currentView === 'month' }) } >{t(`el.datepicker.month${month + 1}`)}</span> ) } <button type="button" onClick={this.nextYear.bind(this)} className="el-picker-panel__icon-btn el-date-picker__next-btn el-icon-d-arrow-right"> </button> { currentView === PICKER_VIEWS.DATE && ( <button type="button" onClick={this.nextMonth.bind(this)} className="el-picker-panel__icon-btn el-date-picker__next-btn el-icon-arrow-right"> </button> ) } </div> ) } <div className="el-picker-panel__content"> {this._pickerContent()} </div> </div> </div> { isShowTime && currentView === PICKER_VIEWS.DATE && ( <div className="el-picker-panel__footer"> <a href="JavaScript:" className="el-picker-panel__link-btn" onClick={this.changeToNow.bind(this)}>{t('el.datepicker.now')}</a> <button type="button" className="el-picker-panel__btn" onClick={() => this.confirm()}>{t('el.datepicker.confirm')}</button> </div> ) } </div> ) } } DatePanel.defaultProps = { isShowTime: false, selectionMode: SELECTION_MODES.DAY, }