Docs

Calendar Widget

calendar widget is calender like widget with smooth animations and cool effects

Installation

Install dependencies

npm install framer-motion lucide-react

Run the following command

It will create a new file calendar-widget.tsx inside the components/animata/widget directory.

mkdir -p components/animata/widget && touch components/animata/widget/calendar-widget.tsx

Paste the code

Open the newly created file and paste the following code:

import React, { useEffect, useRef, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { Calendar as CalendarIcon, DotIcon } from "lucide-react";
 
const monthArray = [
  "",
  "January",
  "February",
  "March",
  "April",
  "May",
  "June",
  "July",
  "August",
  "September",
  "October",
  "November",
  "December",
];
 
interface EventType {
  date: number;
  title: string;
  time: string;
}
 
export default function CalendarWidget({
  initialSelectedDate = 1,
  initialShowEvents = true,
  eventsData = [],
  month = 1,
  year = new Date().getFullYear(),
}: {
  initialSelectedDate?: number;
  initialShowEvents?: boolean;
  eventsData?: EventType[];
  month?: number;
  year?: number;
}) {
  const [selectedDate, setSelectedDate] = useState(initialSelectedDate);
  const [showEvents, setShowEvents] = useState(initialShowEvents);
  const scrollRef = useRef<HTMLDivElement>(null);
 
  const dates = Array.from({ length: 30 }, (_, i) => i + 1);
  const daySymbols = ["S", "M", "T", "W", "T", "F", "S"];
 
  const filteredEvents = eventsData.filter((event: EventType) => event.date === selectedDate);
 
  useEffect(() => {
    if (scrollRef.current) {
      const selectedElement = scrollRef.current.querySelector(`[data-date="${selectedDate}"]`);
      if (selectedElement) {
        selectedElement.scrollIntoView({ behavior: "smooth", inline: "center", block: "nearest" });
      }
    }
  }, [selectedDate]);
 
  return (
    <motion.div
      className="mx-auto max-w-sm rounded-3xl bg-slate-200 shadow-lg"
      initial={{ scale: 0.9, opacity: 0 }}
      animate={{ scale: 1, opacity: 1 }}
      transition={{ duration: 0.3 }}
    >
      <div className="mx-auto max-w-sm rounded-3xl px-6 py-2">
        <h2 className="mb-4 text-2xl font-bold">{`${monthArray[month]} ${year}`}</h2>
        <div
          ref={scrollRef}
          className="scrollbar-hide flex items-start space-x-2 overflow-x-auto py-2"
          style={{ scrollbarWidth: "none", msOverflowStyle: "none" }}
        >
          {dates.map((date, index) => (
            <motion.button
              key={date}
              data-date={date}
              className="flex w-10 flex-shrink-0 flex-col items-center justify-center gap-y-2 rounded-lg"
              onClick={() => {
                setSelectedDate(date);
                setShowEvents(true);
              }}
              whileHover={{ scale: 1.05 }}
              whileTap={{ scale: 0.95 }}
            >
              <span className="mb-1 text-xs">{daySymbols[(index + 4) % 7]}</span>
              <AnimatePresence>
                {selectedDate === date ? (
                  <motion.span
                    layoutId="highlighted-date"
                    className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 text-black shadow-lg"
                    transition={{
                      type: "spring",
                      stiffness: 300,
                      damping: 20,
                    }}
                  >
                    {date}
                  </motion.span>
                ) : (
                  <span className="flex h-8 w-8 items-center justify-center">{date}</span>
                )}
              </AnimatePresence>
              <span>
                {eventsData.find((alldates: EventType) => alldates.date === date) ? (
                  <DotIcon />
                ) : (
                  ""
                )}
              </span>
            </motion.button>
          ))}
        </div>
      </div>
 
      <div className="w-full rounded-3xl bg-[#fefefe] px-6 shadow-lg">
        <AnimatePresence mode="wait">
          {showEvents && (
            <motion.div
              key="events"
              className="scrollbar-hide mt-2 h-[150px] overflow-scroll border-t pt-4"
              initial={{ opacity: 0, y: 20 }}
              animate={{ opacity: 1, y: 0 }}
              exit={{ opacity: 0, y: -20 }}
              transition={{ duration: 0.2 }}
            >
              {filteredEvents.length > 0 ? (
                filteredEvents.map((event: EventType, index: number) => (
                  <motion.div
                    key={index}
                    className="mb-2 border-b-2 border-slate-200"
                    initial={{ opacity: 0, x: -20 }}
                    animate={{ opacity: 1, x: 0 }}
                    transition={{ delay: index * 0.1 }}
                  >
                    <h4 className="font-medium">{event.title}</h4>
                    <p className="text-sm text-gray-500">{event.time}</p>
                  </motion.div>
                ))
              ) : (
                <motion.div
                  className="flex flex-col items-center text-gray-500"
                  initial={{ opacity: 0 }}
                  animate={{ opacity: 1 }}
                >
                  <CalendarIcon className="mb-2 h-8 w-8" />
                  <p>No Events</p>
                </motion.div>
              )}
            </motion.div>
          )}
        </AnimatePresence>
      </div>
    </motion.div>
  );
}

Credits

Built by Anshuman