@utahdts/utah-design-system
Version:
Utah Design System React Library
88 lines (80 loc) • 3.24 kB
JavaScript
import { add, format, isValid } from 'date-fns';
/** @typedef {import('@utahdts/utah-design-system').CalendarGridValue} CalendarGridValue */
/** @typedef {import('@utahdts/utah-design-system').CalendarGridMonth} CalendarGridMonth */
/**
* @param {Date} dateA
* @param {Date} dateB
* @returns {number} negative, 0, or positive indicative of sort order
*/
function dateIsEqualYM(dateA, dateB) {
return (
(dateA.getFullYear() - dateB.getFullYear())
|| (dateA.getMonth() - dateB.getMonth())
);
}
/**
* @param {Date | null} dateA
* @param {Date | null} dateB
* @returns {boolean}
*/
function dateIsEqualYMD(dateA, dateB) {
return (
!!dateA && !!dateB
&& dateA.getFullYear() === dateB.getFullYear()
&& dateA.getMonth() === dateB.getMonth()
&& dateA.getDate() === dateB.getDate()
);
}
/**
* @param {Date} infoDate single cell date in the grid for which to construct an information object
* @param {Date | null} focusDate date currently receiving focus in the UI
* @param {Date | null} selectedDate date selected in the UI
* @param {Date} viewedMonthDate date of a day in the month currently being viewed
* @returns {CalendarGridValue}
*/
function constructCalendarGridValue(infoDate, focusDate, selectedDate, viewedMonthDate) {
return {
date: infoDate,
isFocusDate: dateIsEqualYMD(infoDate, [focusDate, selectedDate].find((testDate) => testDate && isValid(testDate)) ?? new Date()),
isNextMonth: add(viewedMonthDate, { months: 1 }).getMonth() === infoDate.getMonth(),
isPreviousMonth: add(viewedMonthDate, { months: -1 }).getMonth() === infoDate.getMonth(),
isSelectedDate: dateIsEqualYMD(infoDate, selectedDate),
isTodayDate: dateIsEqualYMD(infoDate, new Date()),
};
}
/**
* @param {Date | null} focusDate
* @param {Date | null} selectedDate
* @returns {CalendarGridMonth}
*/
export function calendarGrid(focusDate, selectedDate) {
if (Number.isNaN(focusDate)) {
throw new Error('calendarGrid: focusDate is invalid');
}
if (Number.isNaN(selectedDate)) {
throw new Error('calendarGrid: selectedDate is invalid');
}
const viewedMonthDate = (focusDate && isValid(focusDate)) ? focusDate : new Date();
const firstOfMonth = new Date(viewedMonthDate.getFullYear(), viewedMonthDate.getMonth(), 1);
const startDayOfWeek = Number(format(firstOfMonth, 'e'));
/** @type {CalendarGridMonth} */
const calendarGridMonth = [];
// some month grids have only 4 (aug 2025) or even 6 (feb 2026) rows!
for (
let loopDate = add(firstOfMonth, { days: -1 * startDayOfWeek + 1 });
(
// work all the way through the viewed month dates
dateIsEqualYM(loopDate, viewedMonthDate) <= 0
// make sure the last row is filled
|| calendarGridMonth[calendarGridMonth.length - 1]?.length !== 7
);
loopDate = add(loopDate, { days: 1 })
) {
if (calendarGridMonth.length === 0 || calendarGridMonth[calendarGridMonth.length - 1]?.length === 7) {
// @ts-expect-error needs an empty entry to make sure it's full
calendarGridMonth.push([]);
}
calendarGridMonth[calendarGridMonth.length - 1]?.push(constructCalendarGridValue(loopDate, focusDate, selectedDate, viewedMonthDate));
}
return calendarGridMonth;
}