Skip to content

Instantly share code, notes, and snippets.

@jonahallibone
Last active November 10, 2021 00:43
Show Gist options
  • Save jonahallibone/3251df4455ce88abb1905cfcdbdc721f to your computer and use it in GitHub Desktop.
Save jonahallibone/3251df4455ce88abb1905cfcdbdc721f to your computer and use it in GitHub Desktop.
import { useRef, useState } from "react";
import {
Box,
Button,
Center,
Flex,
Grid,
GridItem,
IconButton,
Input,
Popover,
PopoverBody,
PopoverContent,
PopoverTrigger,
useDisclosure,
useOutsideClick,
} from "@chakra-ui/react";
import { RiArrowLeftSLine, RiArrowRightSLine } from "react-icons/ri";
const removeEmptyWeeks = (week) => week.length;
const months = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
const days = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
const getWeeksForMonthAndYear = (year, monthNumber) => {
const daysInMonth = new Date(year, monthNumber, 0).getDate();
const daysInPrevMonth = new Date(year, monthNumber - 1, 0).getDate();
const allDays = Array(daysInMonth)
.fill()
.map((_, idx) => ({
enabled: true,
date: new Date(year, monthNumber - 1, idx + 1),
}));
const allWeeks = allDays.reduce(
(weeks, day) => {
if (day.date.getDay() === 0) {
return [...weeks, [day]];
}
const tmpWeeks = weeks;
tmpWeeks[tmpWeeks.length - 1].push(day);
return tmpWeeks;
},
[[]]
);
const filledWeeks = allWeeks.filter(removeEmptyWeeks).map((week) => {
const firstDay = week[0].date.getDay();
const lastDay = week[week.length - 1].date.getDay();
if (firstDay !== 0) {
const fillFrontArray = Array(firstDay)
.fill()
.map((_, idx) => ({
enabled: false,
date: new Date(year, monthNumber - 2, daysInPrevMonth - idx),
}));
return [...fillFrontArray.reverse(), ...week];
}
if (lastDay !== 6) {
const fillBackArray = Array(6 - lastDay)
.fill()
.map((_, idx) => ({
enabled: false,
date: new Date(year, monthNumber, idx + 1),
}));
return [...week, ...fillBackArray];
}
return week;
});
return filledWeeks;
};
const getCurrentMonthYear = () => {
const dateObj = new Date();
const month = dateObj.getUTCMonth() + 1; // months from 1-12
const year = dateObj.getUTCFullYear();
return { month, year };
};
const getCurrentDate = () => new Date();
const datesAreOnSameDay = (first, second) =>
first.getFullYear() === second.getFullYear() &&
first.getMonth() === second.getMonth() &&
first.getDate() === second.getDate();
const getVariant = (date, selectedDate, enabled) => {
const isDaySelected = datesAreOnSameDay(date, selectedDate);
if (enabled && isDaySelected) {
return "solid";
}
return "ghost";
};
const getColorScheme = (colorScheme, enabled, day, selectedDay) => {
const isDaySelected = datesAreOnSameDay(day, selectedDay);
if (!enabled || !isDaySelected) {
return "gray";
}
return colorScheme;
};
const DatePicker = ({ colorScheme = "blue" }) => {
const [currentMonthYear, setCurrentMonthYear] = useState(() =>
getCurrentMonthYear()
);
const { onOpen, onClose, isOpen } = useDisclosure();
const inputRef = useRef();
const calendarRef = useRef();
useOutsideClick({
ref: inputRef,
handler: (event) => {
if (event.target.contains(calendarRef.current)) {
onClose();
}
},
});
const [selectedDate, setSelectedDate] = useState(() => getCurrentDate());
const incrementMonth = () => {
setCurrentMonthYear((prev) => ({
year: prev.month === 12 ? prev.year + 1 : prev.year,
month: prev.month < 12 ? prev.month + 1 : 1,
}));
};
const decrementMonth = () => {
setCurrentMonthYear((prev) => ({
year: prev.month > 1 ? prev.year : prev.year - 1,
month: prev.month > 1 ? prev.month - 1 : 12,
}));
};
return (
<Popover
isOpen={isOpen}
autoFocus={false}
onClose={onClose}
onOpen={onOpen}
placement="bottom-start"
>
<PopoverTrigger>
<Input
ref={inputRef}
size="md"
value={selectedDate.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
/>
</PopoverTrigger>
<PopoverContent onClick={(e) => e.stopPropagation()}>
<PopoverBody p={0}>
<Box maxWidth="500px" bg="white" p={2} ref={calendarRef}>
<Center
justifyContent="space-between"
as="nav"
borderBottom="1px solid #DDD"
padding={2}
>
<IconButton
onClick={decrementMonth}
icon={<RiArrowLeftSLine />}
/>
{months[currentMonthYear.month - 1]}, {currentMonthYear.year}
<IconButton
onClick={incrementMonth}
icon={<RiArrowRightSLine />}
/>
</Center>
<Grid templateColumns="repeat(7, 1fr)" gap={1}>
{days.map((day) => (
<GridItem
as={Flex}
justifyContent="center"
fontWeight="bold"
key={day}
p={2}
>
{day}
</GridItem>
))}
{getWeeksForMonthAndYear(
currentMonthYear.year,
currentMonthYear.month
).map((week) =>
week.map((day) => (
<GridItem
as={Button}
isDisabled={!day.enabled}
variant={getVariant(day.date, selectedDate, day.enabled)}
colorScheme={getColorScheme(
colorScheme,
day.enabled,
day.date,
selectedDate
)}
key={`${day.date.getDate()}-${day.date.getMonth()}`}
p={0}
m={0}
onClick={() => setSelectedDate(day.date)}
>
{day.date.getDate()}
</GridItem>
))
)}
</Grid>
</Box>
</PopoverBody>
</PopoverContent>
</Popover>
);
};
export default DatePicker;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment