Last active
December 30, 2021 02:28
-
-
Save annjawn/66ba488a41883adcecb9b46c8853b5a3 to your computer and use it in GitHub Desktop.
Keyboard and Performance friendly Ant Design Datepicker
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from "react"; | |
import { Popover, Input, Calendar, Icon, Select, Button, Col, Row } from "antd"; | |
import debounce from "lodash/debounce"; | |
import moment from "moment"; | |
import "antd/dist/antd.css"; | |
class DatePicker extends Component { | |
static getDerivedStateFromProps(nextProps) { | |
// Should be a controlled component. | |
if ("value" in nextProps) { | |
return { | |
...(nextProps.value || {}) | |
}; | |
} | |
return null; | |
} | |
state = { | |
dateVal: "", | |
calVal: moment(), | |
popupVisible: false | |
}; | |
dateChange = e => { | |
const date = e.target.value; | |
this.triggerChange({ date }); | |
}; | |
handleSelect = value => { | |
this.dateInput.focus(); | |
const date = value.format(this.props.format); | |
this.triggerChange({ date }); | |
}; | |
triggerChange = changedValue => { | |
// Should provide an event to pass value to Form. | |
const { onChange, onBlur, dateChange, format } = this.props; | |
const debChange = debounce(changes => dateChange(changes.date), 300); | |
const calChange = debounce(dt => { | |
if (dt.length === format.length && moment(dt, format).isValid()) { | |
this.setState({ calVal: moment(dt) }); | |
} | |
}, 300); | |
if (dateChange) { | |
debChange(changedValue); | |
} | |
if (onChange) { | |
calChange(changedValue.date); | |
this.setState({ | |
dateVal: changedValue.date | |
}); | |
} | |
if (onBlur) { | |
onBlur({ | |
...changedValue | |
}); | |
} | |
}; | |
hidePopup = () => this.setState({ popupVisible: false }); | |
visibilityChange = popupVisible => { | |
if ( | |
document.activeElement === this.dateInput.input && | |
!this.state.popupVisible | |
) { | |
this.setState({ popupVisible }); | |
} | |
}; | |
render() { | |
const { size, disabledDate, style, trigger } = this.props; | |
return ( | |
<Popover | |
content={ | |
<Calendar | |
fullscreen={false} | |
disabledDate={disabledDate} | |
value={this.state.calVal} | |
onSelect={value => { | |
this.handleSelect(value); | |
}} | |
headerRender={({ value, type, onChange, onTypeChange }) => { | |
const start = 0; | |
const end = 12; | |
const monthOptions = []; | |
const current = value.clone(); | |
const localeData = value.localeData(); | |
const months = []; | |
for (let i = 0; i < 12; i++) { | |
current.month(i); | |
months.push(localeData.months(current)); | |
} | |
for (let index = start; index < end; index++) { | |
monthOptions.push( | |
<Select.Option className="month-item" key={`${index}`}> | |
{months[index]} | |
</Select.Option> | |
); | |
} | |
const month = value.month(); | |
const year = value.year(); | |
const options = []; | |
for (let i = year - 10; i < year + 10; i += 1) { | |
options.push( | |
<Select.Option key={i} value={i} className="year-item"> | |
{i} | |
</Select.Option> | |
); | |
} | |
return ( | |
<div style={{ padding: 10 }}> | |
<Row type="flex" justify="space-between"> | |
<Col> | |
<Select | |
size="small" | |
dropdownMatchSelectWidth={false} | |
className="my-year-select" | |
onChange={newYear => { | |
const now = value.clone().year(newYear); | |
onChange(now); | |
}} | |
value={String(year)} | |
> | |
{options} | |
</Select> | |
</Col> | |
<Col> | |
<Select | |
size="small" | |
dropdownMatchSelectWidth={false} | |
value={String(month)} | |
onChange={selectedMonth => { | |
const newValue = value.clone(); | |
newValue.month(parseInt(selectedMonth, 10)); | |
onChange(newValue); | |
}} | |
> | |
{monthOptions} | |
</Select> | |
</Col> | |
<Col> | |
<Button | |
type="link" | |
size={size} | |
icon="close-circle" | |
onClick={this.hidePopup} | |
style={{ height: "16px", width: "16px" }} | |
/> | |
</Col> | |
</Row> | |
</div> | |
); | |
}} | |
/> | |
} | |
visible={this.state.popupVisible} | |
onVisibleChange={this.visibilityChange} | |
placement="bottom" | |
trigger={trigger} | |
> | |
<Input | |
placeholder={this.props.placeholder} | |
size={size} | |
value={this.state.dateVal} | |
onChange={this.dateChange} | |
onKeyDown={e => { | |
//hide popup on tab or esc | |
if (e.keyCode === 9 || e.keyCode === 27) { | |
this.setState({ popupVisible: false }); | |
} | |
}} | |
allowClear | |
suffix={<Icon type="calendar" style={{ opacity: "0.5" }} />} | |
style={style} | |
ref={input => { | |
this.dateInput = input; | |
}} | |
/> | |
</Popover> | |
); | |
} | |
} | |
export default DatePicker; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment