--- title: Custom calendar tags: Projects --- Back in the day, when dealing with date picker, I always go for ready made plugins on the internet, But the size of calendar is usually kind of small, it just lowers UX, so this time, I gonna show you guys how I build a customer calendar from stratch, let's started!! ## [example code](https://codepen.io/nienyingchou/pen/bGRqQpJ) ## Agenda * Get browser language * Put moment.js in that language * Get weekday names * Generate calendar arrays * Build the layout * Add functions (last month, next month, select date) ## Get browser language * because the property of browser in mobile is not the same, so we have to detect if the user is using mobile ```javascript= const isUsingMobile = navigator.userAgent.match(/Android/i) || navigator.userAgent.match(/webOS/i) || navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i) || navigator.userAgent.match(/iPod/i) || navigator.userAgent.match(/BlackBerry/i) || navigator.userAgent.match(/Windows Phone/i); const getBrowserLanguage = () => { // default the locale to English let lang = "en"; if (isUsingMobile) { lang = navigator.languages .find((x) => x.substr(0, 2) === navigator.language.substr(0, 2)) .substr(0, 2); } else { lang = navigator.language; } console.log("getBrowserLanguage", lang); return lang; }; ``` ## Put moment.js in that language ```javascript= moment.locale(getBrowserLanguage()); ``` ## Get weekday names ```javascript= moment.weekdaysMin(true); ``` ## Generate calendar arrays ```javascript= // generate calendar array according to selectDate // return date array const getCalendarArray = (selectDate) => { // default to the beginning of the month let startDate = moment(selectDate).format("01-MM-YYYY"); // default to the end of the month const daysInMonth = moment(selectDate).daysInMonth(); let endDate = moment(selectDate).format(`${daysInMonth}-MM-YYYY`); // modify startDate to the exact date const firstDate = selectDate .clone() .startOf("month") .startOf("week") .date(); if (firstDate > 20) { // means the date is from last month const MMYYYY = moment(selectDate).subtract(1, "month").format("MM-YYYY"); startDate = moment(selectDate).format(`${firstDate}-${MMYYYY}`); } // modify endDate to the exact date const lastDate = selectDate.clone().endOf("month").endOf("week").date(); if (lastDate < 10) { // means the date is from next month const MMYYYY = moment(selectDate).add(1, "month").format("MM-YYYY"); endDate = moment(selectDate).format(`0${lastDate}-${MMYYYY}`); } const date = []; // format startDate and endDate const formatStartDate = moment(startDate, dateFormat); const formatEndDate = moment(endDate, dateFormat); // push the dates between startDate and endDate into date array for ( let m = moment(formatStartDate); m.isSame(formatEndDate) || m.isBefore(formatEndDate); m.add(1, "days") ) { date.push(m.format(dateFormat)); } const daysInWeek = 7; // split the array by 7 (7, because 7 days in a week) const dateArrayOfWeeks = [ ...Array(Math.ceil(date.length / daysInWeek)) // eslint-disable-next-line ].map((_) => date.splice(0, daysInWeek)); console.log(dateArrayOfWeeks); setDates(dateArrayOfWeeks); }; ``` ## Build the layout ### calender body ```javascript= <table> {/* Calendar Header */} <tr> {weekdaysShort.map((weekday) => ( <th key={weekday}>{weekday}</th> ))} </tr> {/* Calendar Body */} {dates.map((everyWeek) => ( <tr key={everyWeek[0]}> {everyWeek.map((everyDay) => ( <td onClick={() => handleSelectDate(everyDay)} style={{ background: moment(everyDay, dateFormat).format(dateFormat) === selectDate.format(dateFormat) ? "#CCC" : "inherit", color: moment(everyDay, dateFormat).format("MM") === selectDate.format("MM") ? "inherit" : "#CCC" }} > {moment(everyDay, dateFormat).format("DD")} </td> ))} </tr> ))} </table> ``` ### calender control ```javascript= <div className="flex"> <button onClick={lastMonth}> <b>{"<"}</b> </button> <div>{selectDate.format("LL")}</div> <button onClick={nextMonth}> <b>{">"}</b> </button> </div> ``` ## Add functions ```javascript= const lastMonth = () => { const newDate = moment(selectDate).subtract(1, "month"); updateData(newDate); }; const nextMonth = () => { const newDate = moment(selectDate).add(1, "month"); updateData(newDate); }; // update calender when user click date const handleSelectDate = (selectDate) => { const newDate = moment(selectDate, dateFormat); updateData(newDate); }; const updateData = (newDate) => { setSelectDate(newDate); getCalendarArray(newDate); }; ```