Skip to content

Instantly share code, notes, and snippets.

@azaek
Created December 23, 2022 17:13
Show Gist options
  • Save azaek/b1bd5aef61beda06a466f7b82a0e663e to your computer and use it in GitHub Desktop.
Save azaek/b1bd5aef61beda06a466f7b82a0e663e to your computer and use it in GitHub Desktop.
Calendar Component using date-fns, tailwindcss and framer-motion
import clsx from "clsx";
import {
add,
eachDayOfInterval,
endOfMonth,
format,
getDay,
isEqual,
isSameMonth,
isToday,
parse,
startOfToday,
} from "date-fns";
import { AnimatePresence, motion } from "framer-motion";
import { useState } from "react";
import { ChevronRight_14 } from "../../utils/SVGHub";
const Calendar = () => {
let today = startOfToday();
let [selectedDay, setSelectedDay] = useState(today)
let [currentMonth, setCurrentMonth] = useState(format(today, 'MMM-yyyy'))
let firstDayCurrentMonth = parse(currentMonth, 'MMM-yyyy', new Date())
const [move, setMove] = useState(0);
let days = eachDayOfInterval({
start: firstDayCurrentMonth,
end: endOfMonth(firstDayCurrentMonth),
})
function previousMonth() {
let firstDayNextMonth = add(firstDayCurrentMonth, { months: -1 })
setCurrentMonth(format(firstDayNextMonth, 'MMM-yyyy'))
setMove(-1);
}
function nextMonth() {
let firstDayNextMonth = add(firstDayCurrentMonth, { months: 1 })
setCurrentMonth(format(firstDayNextMonth, 'MMM-yyyy'))
setMove(1);
}
return (
<AnimatePresence >
<motion.div layout className="max-w-sm flex flex-col w-full">
<motion.div layout className="w-full flex flex-col bg-white p-5">
<motion.div layout="position"
className="flex items-center overflow-hidden">
<motion.p key={currentMonth}
initial={{ opacity: 0, x: move * 50 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -move * 50 }} className="text-2021 t-lp font-bold flex-auto">
{format(firstDayCurrentMonth, 'MMMM yyyy')}
</motion.p>
<button
type="button"
onClick={previousMonth}
className="flex flex-none items-center justify-center p-1.5 hover:bg-l-f-overlay1 t-all"
>
<span className="rotate-180"><ChevronRight_14 /></span>
</button>
<button
onClick={nextMonth}
type="button"
className=" ml-1 flex flex-none items-center justify-center p-1.5 hover:bg-l-f-overlay1 t-all"
>
<span className=""><ChevronRight_14 /></span>
</button>
</motion.div>
<motion.div layout className="grid grid-cols-7 mt-10 text-xs leading-6 text-center text-gray-500">
<div>Su</div>
<div>Mo</div>
<div>Tu</div>
<div>We</div>
<div>Th</div>
<div>Fr</div>
<div>Sa</div>
</motion.div>
<motion.div layout className="grid grid-cols-7 mt-2 text-sm">
{days.map((day, dayIdx) => (
<motion.div layout="position"
initial={{ opacity: 0, x: move * 50 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -move * 50 }}
key={day.toString()}
className={clsx(
dayIdx === 0 && colStartClasses[getDay(day)],
'py-1.5'
)}
>
<button
type="button"
onClick={() => setSelectedDay(day)}
className={clsx(
isEqual(day, selectedDay) && isToday(day) && 'text-d-t-primary',
isEqual(day, selectedDay) && !isToday(day) && 'text-l-t-primary',
!isEqual(day, selectedDay) && isToday(day) &&
'text-l-t-primary',
!isEqual(day, selectedDay) && !isToday(day) &&
isSameMonth(day, firstDayCurrentMonth) &&
'text-l-t-primary',
!isEqual(day, selectedDay) && !isToday(day) &&
!isSameMonth(day, firstDayCurrentMonth) &&
'text-l-t-primary',
isEqual(day, selectedDay) && isToday(day) && 'bg-black',
isEqual(day, selectedDay) && !isToday(day) &&
'bg-l-f-stroke',
!isEqual(day, selectedDay) && 'hover:bg-gray-200',
(isEqual(day, selectedDay) || isToday(day)) &&
'font-semibold',
'mx-auto flex h-8 w-8 items-center justify-center rounded-full'
)}
>
<time dateTime={format(day, 'yyyy-MM-dd')}>
{format(day, 'd')}
</time>
</button>
</motion.div>
))}
</motion.div>
</motion.div>
</motion.div>
</AnimatePresence>
);
}
let colStartClasses = [
'',
'col-start-2',
'col-start-3',
'col-start-4',
'col-start-5',
'col-start-6',
'col-start-7',
]
export default Calendar;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment