I tried combining Glad Chinda's great react datepicker/calendar component 3/5 pages ((x)DatePicker, (x)DatePickerStyle, Calendar, CalendarStyle, Helpers) into a single component, so I can use my own buttons for switching months.
I haven't used proptypes, styledcomponents (so I made it a css file), or helper concept yet - I can't tell if the error has to do with either of these three.
import React from "react";
import "./FullCalBackdrop.css";
import "./CalendarStyle.css";
import PropTypes from "prop-types";
export const THIS_YEAR = +(new Date().getFullYear());
export const THIS_MONTH = +(new Date().getMonth()) + 1;
export const WEEK_DAYS = {
Sunday: "Sun",
Monday: "Mon",
Tuesday: "Tue",
Wednesday: "Wed",
Thursday: "Thu",
Friday: "Fri",
Saturday: "Sat"
}
export const CALENDAR_MONTHS = {
January: "Jan",
February: "Feb",
March: "Mar",
April: "Apr",
May: "May",
June: "Jun",
July: "Jul",
August: "Aug",
September: "Sep",
October: "Oct",
November: "Nov",
December: "Dec"
}
export const CALENDAR_WEEKS = 6;
export const zeroPad = (value, length) => `${value}`.padStart(length, '0')
export const isDate = date => {
const isDate = Object.prototype.toString.call(date) === '[object Date]';
const isValidDate = date && !Number.isNaN(date.valueOf());
return isDate && isValidDate;
}
export const getDateISO = (date = new Date) => {
if (!isDate(date)) return null;
return [
date.getFullYear(),
zeroPad(+date.getMonth() + 1, 2),
zeroPad(+date.getDate(), 2)
].join('-');
}
export const getMonthDays = (month = THIS_MONTH, year = THIS_YEAR) => {
const months30 = [4, 6, 9, 11];
const leapYear = year % 4 === 0;
return month === 2
? leapYear
? 29
: 28
: months30.includes(month)
? 30
: 31;
}
export const getMonthFirstDay = (month = THIS_MONTH, year = THIS_YEAR) => {
return +(new Date(`${year}-${zeroPad(month, 2)}-01`).getDay()) + 1;
}
export const isSameMonth = (date, basedate = new Date()) => {
if (!(isDate(date) && isDate(basedate))) return false;
const basedateMonth = +(basedate.getMonth()) + 1;
const basedateYear = basedate.getFullYear();
const dateMonth = +(date.getMonth()) + 1;
const dateYear = date.getFullYear();
return (+basedateMonth === +dateMonth) && (+basedateYear === +dateYear);
}
export const isSameDay = (date, basedate = new Date()) => {
if (!(isDate(date) && isDate(basedate))) return false;
const basedateDate = basedate.getDate();
const basedateMonth = +(basedate.getMonth()) + 1;
const basedateYear = basedate.getFullYear();
const dateDate = date.getDate();
const dateMonth = +(date.getMonth()) + 1;
const dateYear = date.getFullYear();
return (+basedateDate === +dateDate) && (+basedateMonth === +dateMonth) && (+basedateYear === +dateYear);
}
export const getPreviousMonth = (month, year) => {
const prevMonth = (month > 1) ? month - 1 : 12;
const prevMonthYear = (month > 1) ? year : year - 1;
return { month: prevMonth, year: prevMonthYear };
}
export const getNextMonth = (month, year) => {
const nextMonth = (month < 12) ? month + 1 : 1;
const nextMonthYear = (month < 12) ? year : year + 1;
return { month: nextMonth, year: nextMonthYear };
}
export const calendar = (month = THIS_MONTH, year = THIS_YEAR) => {
const monthDays = getMonthDays(month, year);
const monthFirstDay = getMonthFirstDay(month, year);
const daysFromPrevMonth = monthFirstDay - 1;
const daysFromNextMonth = (CALENDAR_WEEKS * 7) - (daysFromPrevMonth + monthDays);
const { month: prevMonth, year: prevMonthYear } = getPreviousMonth(month, year);
const { month: nextMonth, year: nextMonthYear } = getNextMonth(month, year);
const prevMonthDays = getMonthDays(prevMonth, prevMonthYear);
const prevMonthDates = [...new Array(daysFromPrevMonth)].map((n, index) => {
const day = index + 1 + (prevMonthDays - daysFromPrevMonth);
return [ prevMonthYear, zeroPad(prevMonth, 2), zeroPad(day, 2) ];
});
const thisMonthDates = [...new Array(monthDays)].map((n, index) => {
const day = index + 1;
return [year, zeroPad(month, 2), zeroPad(day, 2)];
});
const nextMonthDates = [...new Array(daysFromNextMonth)].map((n, index) => {
const day = index + 1;
return [nextMonthYear, zeroPad(nextMonth, 2), zeroPad(day, 2)];
});
return [ ...prevMonthDates, ...thisMonthDates, ...nextMonthDates ];
}
class FullCalSlideDrawer extends React.Component {
state = {
date: null,
calendarOpen: false,
...this.resolveStateFromProp(),
today: new Date()
};
toggleCalendar = () =>
this.setState({ calendarOpen: !this.state.calendarOpen });
handleChange = evt => evt.preventDefault();
handleDateChange = date => {
const { onDateChanged } = this.props;
const { date: currentDate } = this.state;
const newDate = date ? getDateISO(date) : null;
currentDate !== newDate &&
this.setState({ date: newDate, calendarOpen: false }, () => {
typeof onDateChanged === "function" && onDateChanged(this.state.date);
});
};
resolveStateFromDate(date) {
const isDateObject = isDate(date);
const _date = isDateObject ? date : new Date();
return {
current: isDateObject ? date : null,
month: +_date.getMonth() + 1,
year: _date.getFullYear()
};
}
resolveStateFromProp() {
return this.resolveStateFromDate(this.props.date);
}
getCalendarDates = () => {
const { current, month, year } = this.state;
const calendarMonth = month || +current.getMonth() + 1;
const calendarYear = year || current.getFullYear();
return calendar(calendarMonth, calendarYear);
};
gotoDate = date => evt => {
evt && evt.preventDefault();
const { current } = this.state;
const { onDateChanged } = this.props;
!(current && isSameDay(date, current)) &&
this.setState(this.resolveStateFromDate(date), () => {
typeof onDateChanged === "function" && onDateChanged(date);
});
};
gotoPreviousMonth = () => {
const { month, year } = this.state;
this.setState(getPreviousMonth(month, year));
};
gotoNextMonth = () => {
const { month, year } = this.state;
this.setState(getNextMonth(month, year));
};
gotoPreviousYear = () => {
const { year } = this.state;
this.setState({ year: year - 1 });
};
gotoNextYear = () => {
const { year } = this.state;
this.setState({ year: year + 1 });
};
handlePressure = fn => {
if (typeof fn === "function") {
fn();
this.pressureTimeout = setTimeout(() => {
this.pressureTimer = setInterval(fn, 100);
}, 500);
}
};
clearPressureTimer = () => {
this.pressureTimer && clearInterval(this.pressureTimer);
this.pressureTimeout && clearTimeout(this.pressureTimeout);
};
clearDayTimeout = () => {
this.dayTimeout && clearTimeout(this.dayTimeout);
};
handlePrevious = evt => {
evt && evt.preventDefault();
const fn = evt.shiftKey ? this.gotoPreviousYear : this.gotoPreviousMonth;
this.handlePressure(fn);
};
handleNext = evt => {
evt && evt.preventDefault();
const fn = evt.shiftKey ? this.gotoNextYear : this.gotoNextMonth;
this.handlePressure(fn);
};
renderMonthAndYear = () => {
const { month, year } = this.state;
const monthname = Object.keys(CALENDAR_MONTHS)[
Math.max(0, Math.min(month - 1, 11))
];
return (
<div className="CalendarHeader">
<div className="CalendarMonth">
{monthname} {year}
</div>
</div>
);
};
renderDayLabel = (day, index) => {
const daylabel = WEEK_DAYS[day].toUpperCase();
return (
<div className="CalendarDay" key={daylabel} index={index}>
{daylabel}
</div>
);
};
renderCalendarDate = (date, index) => {
const { current, month, year, today } = this.state;
const _date = new Date(date.join("-"));
const isToday = isSameDay(_date, today);
const isCurrent = current && isSameDay(_date, current);
const inMonth =
month && year && isSameMonth(_date, new Date([year, month, 1].join("-")));
const onClick = this.gotoDate(_date);
const props = { index, inMonth, onClick, title: _date.toDateString() };
return (
<div className={isCurrent ? "HighlightedCalendarDate" : isToday ? "TodayCalendarDate" : "CalendarDate"} key={getDateISO(_date)} {...props}>
{_date.getDate()}
</div>
);
};
componentDidMount(date) {
//const { value: date } = this.props;
const newDate = date && new Date(date);
isDate(newDate) && this.setState({ date: getDateISO(newDate) });
const now = new Date();
const tomorrow = new Date().setHours(0, 0, 0, 0) + 24 * 60 * 60 * 1000;
const ms = tomorrow - now;
this.dayTimeout = setTimeout(() => {
this.setState({ today: new Date() }, this.clearDayTimeout);
}, ms);
}
componentDidUpdate(prevProps) {
const { value: date } = this.props;
const { value: prevDate } = prevProps;
const dateISO = getDateISO(new Date(date));
const prevDateISO = getDateISO(new Date(prevDate));
dateISO !== prevDateISO && this.setState({ date: dateISO });
//const { date: prevDate } = prevProps;
const dateMatch = date == prevDate || isSameDay(date, prevDate);
!dateMatch &&
this.setState(this.resolveStateFromDate(date), () => {
typeof onDateChanged === "function" && onDateChanged(date);
});
}
componentWillUnmount() {
this.clearPressureTimer();
this.clearDayTimeout();
}
render() {
const date = this.state;
return (
<div
className={
this.props.show1
? "fullcal_slide-drawer open"
: "fullcal_slide-drawer"
}
>
<div>
<div className="CalendarContainer">
{this.renderMonthAndYear()}
<div className="CalendarGrid">
<div>
{Object.keys(WEEK_DAYS).map(this.renderDayLabel)}
</div>
<div>
{this.getCalendarDates().map(this.renderCalendarDate)}
</div>
</div>
</div>
</div>
<div className="monthlyskip">
<div
className="weeklylcal"
onClick={this.handlePrevious}
onMouseUp={this.clearPressureTimer}
>
Last
<br />
Month
</div>
<div className="closecal" onClick={this.props.close}>
Close
</div>
<div
className="weeklyrcal"
onClick={this.handleNext}
onMouseUp={this.clearPressureTimer}
>
Next
<br />
Month
</div>
</div>
</div>
);
}
}
FullCalSlideDrawer.propTypes = {
date: PropTypes.instanceOf(Date),
label: PropTypes.string,
value: PropTypes.string,
onDateChanged: PropTypes.func
};
export default FullCalSlideDrawer;
CalendarStyle.css
.CalendarContainer {
font-size: 5px;
border: 2px solid #06c;
border-radius: 5px;
overflow: hidden;
}
.CalendarHeader {
display: flex;
align-items: center;
justify-content: space-between;
}
.CalendarGrid {
display: inline-grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
}
.CalendarMonth {
font-weight: 500;
font-size: 5em;
color: #06c;
text-align: center;
padding: 0.5em 0.25em;
word-spacing: 5px;
user-select: none;
}
.CalendarCell {
text-align: center;
align-self: center;
height: 9.5vw;
letter-spacing: 0.1rem;
padding: 0.6em 0.25em;
user-select: none;
grid-column:12vw;
/*grid-column: ${props => (props.index % 7) + 1} / span 1;*/
}
.CalendarDay {
font-weight: 600;
font-size: 2.25em;
color: #06c;
border-top: 2px solid #06c;
border-bottom: 2px solid #06c;
/*border-right: ${props => (props.index % 7) + 1 === 7 ? `none` : `2px solid #06c`};*/
}
.CalendarDate {
/*font-weight: ${props => props.inMonth ? 500 : 300};*/
font-size: 4em;
cursor: pointer;
/*border-bottom: ${props => ((props.index + 1) / 7) <= 5 ? `1px solid #ddd` : `none`};
border-right: ${props => (props.index % 7) + 1 === 7 ? `none` : `1px solid #ddd`};
color: ${props => props.inMonth ? `#333` : `#ddd`};
grid-row: ${props => Math.floor(props.index / 7) + 2} / span 1;*/
grid-row:12vw;
transition: all .4s ease-out;
:hover {
color: #06c;
background: rgba(0, 102, 204, 0.075);
}
.HighlightedCalendarDate {
color: #fff !important;
background: #06c !important;
position: relative;
::before {
content: '';
position: absolute;
top: -1px;
left: -1px;
width: calc(100% + 2px);
height: calc(100% + 2px);
border: 2px solid #06c;
}
}
.TodayCalendarDate {
color: #06c !important;
background: transparent !important;
::after {
content: '';
position: absolute;
right: 0;
bottom: 0;
border-bottom: 0.75em solid #06c;
border-left: 0.75em solid transparent;
border-top: 0.75em solid transparent;
}
:hover {
color: #06c !important;
background: rgba(0, 102, 204, 0.075) !important;
}
}
Go to https://1c31n.csb.app/planner, click top-left logo icon, then top-left calendar-icon, to see what this (and any improvements afterwards) is rendering
first div (with the sunday monday...) and second add:
display: grid;
grid-template-columns: repeat(7, 1fr);
You can play with that and if you mean something else tell me.
Also there is a nice game for grid (https://cssgridgarden.com)