@legendarymediatv/bootstrap
Version:
additional Bootstrap-based React components for extending functionality to react-bootstrap and gatsby
244 lines (213 loc) • 6.92 kB
JavaScript
// dependencies
import React from "react";
import { combine, prepare } from "../functions/functions";
// components
import Button from "react-bootstrap/Button";
import Overlay from "react-bootstrap/Overlay";
import Popover from "react-bootstrap/Popover";
// styling
import "./DatePicker.css";
export default class DatePicker extends React.Component {
state = {};
target = React.createRef();
render = () => {
// prepare properties
const [properties] = prepare(this.props);
const weeks = [0, 1, 2, 3, 4, 5];
const days = [0, 1, 2, 3, 4, 5, 6];
// extract popover properties
const title = properties.title;
const titleAs = properties.titleAs;
delete properties.title;
delete properties.titleAs;
// set defaults
if (!properties.variant) {
properties.variant = "light";
properties.className.push("border");
}
// set required attributes
properties.ref = this.target;
properties.onClick = this.onToggle;
// merge classes
properties.className = combine(properties.className);
return (
<>
<Button {...properties}>
{this.props.value
? this.props.value.toLocaleDateString()
: "no date selected"}
</Button>
<Overlay
target={this.target.current}
show={this.state.show}
rootClose
onHide={this.onClose}
onEnter={this.load}
>
{(props) => (
<Popover {...props}>
<Popover.Header as={titleAs ?? "h3"}>
{title ?? "Date Picker"}
</Popover.Header>
<table id="DatePicker">
<thead className="border-bottom">
<tr>
<th>
<Button
variant="link"
onClick={this.onMonthChange.bind(this, -1)}
className="text-body"
>
<
</Button>
</th>
<th colSpan="5">
{this.state.displayed.toLocaleDateString("en-US", {
month: "long",
year: "numeric",
})}
</th>
<th>
<Button
variant="link"
onClick={this.onMonthChange.bind(this, 1)}
className="text-body"
>
>
</Button>
</th>
</tr>
<tr>
<th>Sun</th>
<th>Mon</th>
<th>Tue</th>
<th>Wed</th>
<th>Thu</th>
<th>Fri</th>
<th>Sat</th>
</tr>
</thead>
<tbody>
{weeks.map((week, weekIndex) => (
<tr key={weekIndex}>
{days.map((day, dayIndex) => (
<td key={dayIndex}>
<Button
variant="link"
onClick={this.onChange.bind(
this,
new Date(
this.state.start.getFullYear(),
this.state.start.getMonth(),
this.state.start.getDate() + week * 7 + day
)
)}
className={this.dayClassName(week, day)}
>
{new Date(
this.state.start.getFullYear(),
this.state.start.getMonth(),
this.state.start.getDate() + week * 7 + day
).toLocaleDateString("en-US", {
day: "numeric",
})}
</Button>
</td>
))}
</tr>
))}
</tbody>
</table>
<div className="text-end">
<Button
variant="link"
onClick={this.onChange.bind(this, new Date())}
className="text-body"
>
today
</Button>
</div>
</Popover>
)}
</Overlay>
</>
);
};
/********************
***** Handlers *****
*******************/
/** when component is ready to interact */
componentDidMount = () => {
this.load();
};
/** when date selected */
onChange = (selected) => {
// close popover
this.onClose();
// notify of change
this.props.onChange(selected);
};
/** when popover needs to close */
onClose = () => {
this.setState({ show: false });
};
/** when month change button clicked (i.e., prev/next month) */
onMonthChange(deltaMonths) {
// skip the requested number of months
const displayed = new Date(
this.state.displayed.getFullYear(),
this.state.displayed.getMonth() + deltaMonths,
1
);
this.setState({
displayed: displayed,
start: this.startDate(displayed),
});
}
/** when popover needs to toggle open/closed */
onToggle = () => {
this.setState({ show: !this.state.show });
};
/*******************
***** Methods *****
******************/
dayClassName = (week, day) => {
const className = ["w-100"];
const current = new Date(
this.state.start.getFullYear(),
this.state.start.getMonth(),
this.state.start.getDate() + week * 7 + day
);
if (this.state.displayed.getMonth() === current.getMonth())
className.push("text-body");
else className.push("text-muted");
if (
this.state.selected &&
this.state.selected.toLocaleDateString() === current.toLocaleDateString()
)
className.push("border border-primary");
return className.join(" ");
};
load = () => {
this.setState({
displayed: this.props.value ? new Date(this.props.value) : new Date(),
selected: this.props.value ? new Date(this.props.value) : null,
start: this.startDate(
this.props.value ? new Date(this.props.value) : new Date()
),
});
};
startDate = (input) => {
// set start date to the beginning of the month
let output = new Date(input.getFullYear(), input.getMonth(), 1);
// set start date to the beginning of the week
if (output.getDay()) {
output = new Date(
output.getFullYear(),
output.getMonth(),
1 - output.getDay()
);
}
return output;
};
}