Skip to content

Instantly share code, notes, and snippets.

@justgeek
Created January 3, 2025 15:24
Show Gist options
  • Save justgeek/fe03f66b619044362ab43411488172cc to your computer and use it in GitHub Desktop.
Save justgeek/fe03f66b619044362ab43411488172cc to your computer and use it in GitHub Desktop.
import {
addMonths,
addYears,
eachDayOfInterval,
eachMonthOfInterval,
eachWeekOfInterval,
endOfMonth,
endOfWeek,
isAfter,
isBefore,
isEqual,
set,
setMonth,
setYear,
startOfMonth,
startOfToday,
startOfWeek,
subMonths,
subYears,
isToday,
} from 'date-fns';
import { useCallback, useMemo, useState } from 'react';
export const Month = {
JANUARY: 'JANUARY',
FEBRUARY: 'FEBRUARY',
MARCH: 'MARCH',
APRIL: 'APRIL',
MAY: 'MAY',
JUNE: 'JUNE',
JULY: 'JULY',
AUGUST: 'AUGUST',
SEPTEMBER: 'SEPTEMBER',
OCTOBER: 'OCTOBER',
NOVEMBER: 'NOVEMBER',
DECEMBER: 'DECEMBER',
};
export const Day = {
SUNDAY: 0,
MONDAY: 1,
TUESDAY: 2,
WEDNESDAY: 3,
THURSDAY: 4,
FRIDAY: 5,
SATURDAY: 6,
};
const inRange = (date, min, max) => (isEqual(date, min) || isAfter(date, min)) && (isEqual(date, max) || isBefore(date, max));
const clearTime = (date) => set(date, { hours: 0, minutes: 0, seconds: 0, milliseconds: 0 });
export const useCalendar = ({
/**
* What day a week starts on within the calendar matrix.
*
* @default Day.SUNDAY
*/
weekStartsOn = Day.SUNDAY,
/**
* The initial viewing date.
*
* @default new Date()
*/
viewing: initialViewing = new Date(),
/**
* The initial date(s) selection.
*
* @default []
*/
selected: initialSelected = [],
/**
* The number of months in the calendar.
*
* @default 1
*/
numberOfMonths = 1,
} = {}) => {
const [viewing, setViewing] = useState(initialViewing);
const viewToday = useCallback(() => setViewing(startOfToday()), [setViewing]);
const viewMonth = useCallback((month) => setViewing((v) => setMonth(v, month)), []);
const viewPreviousMonth = useCallback(() => setViewing((v) => subMonths(v, 1)), []);
const viewNextMonth = useCallback(() => setViewing((v) => addMonths(v, 1)), []);
const viewYear = useCallback((year) => setViewing((v) => setYear(v, year)), []);
const viewPreviousYear = useCallback(() => setViewing((v) => subYears(v, 1)), []);
const viewNextYear = useCallback(() => setViewing((v) => addYears(v, 1)), []);
const [selected, setSelected] = useState(initialSelected);
const clearSelected = () => setSelected([]);
const isSelected = useCallback((date) => selected.findIndex((s) => isEqual(s, date)) > -1, [selected]);
const select = useCallback((date, replaceExisting = false) => {
if (replaceExisting) {
setSelected(Array.isArray(date) ? date : [date]);
} else {
setSelected((selectedItems) => selectedItems.concat(Array.isArray(date) ? date : [date]));
}
}, []);
const deselect = useCallback(
(date) =>
setSelected((selectedItems) =>
Array.isArray(date)
? selectedItems.filter((s) => !date.map((d) => d.getTime()).includes(s.getTime()))
: selectedItems.filter((s) => !isEqual(s, date)),
),
[],
);
const toggle = useCallback((date, replaceExisting = false) => (isSelected(date) ? deselect(date) : select(date, replaceExisting)), [
deselect,
isSelected,
select,
]);
const selectRange = useCallback((start, end, replaceExisting = false) => {
if (replaceExisting) {
setSelected(eachDayOfInterval({ start, end }));
} else {
setSelected((selectedItems) => selectedItems.concat(eachDayOfInterval({ start, end })));
}
}, []);
const deselectRange = useCallback((start, end) => {
setSelected((selectedItems) =>
selectedItems.filter(
(s) =>
!eachDayOfInterval({ start, end })
.map((d) => d.getTime())
.includes(s.getTime()),
),
);
}, []);
const calendar = useMemo(
() =>
eachMonthOfInterval({
start: startOfMonth(viewing),
end: endOfMonth(addMonths(viewing, numberOfMonths - 1)),
}).map((month) =>
eachWeekOfInterval(
{
start: startOfMonth(month),
end: endOfMonth(month),
},
{ weekStartsOn },
).map((week) =>
eachDayOfInterval({
start: startOfWeek(week, { weekStartsOn }),
end: endOfWeek(week, { weekStartsOn }),
}),
),
),
[viewing, weekStartsOn, numberOfMonths],
);
return {
clearTime,
inRange,
viewing,
setViewing,
viewToday,
viewMonth,
viewPreviousMonth,
viewNextMonth,
viewYear,
viewPreviousYear,
viewNextYear,
selected,
setSelected,
clearSelected,
isSelected,
select,
deselect,
toggle,
selectRange,
deselectRange,
calendar,
isToday,
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment