Data Display
stable

Calendar

Calendar: Date and schedule UI for picking and browsing dates.

Preview
calendar

April 2026

Sun
Mon
Tue
Wed
Thu
Fri
Sat
29
30
31
1
2
3
4
5
6
7
Design review
Ship blocks page
8
9
Billing reminder
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
1
2

Import

tsx
import { Calendar } from "@/components/ui/calendar";
bash
npx sui add calendar

Source

From components/ui/calendar.tsx

tsx
"use client";

// @ts-nocheck
import React, { useState } from "react";
import {
  format,
  addMonths,
  subMonths,
  startOfMonth,
  endOfMonth,
  startOfWeek,
  endOfWeek,
  isSameMonth,
  isSameDay,
  addDays,
  eachDayOfInterval
} from "date-fns";
import { ChevronLeft, ChevronRight, Plus } from "lucide-react";
import { cn } from "@/src/lib/utils";
import { Button } from "./button";
import { Badge } from "./badge";

interface CalendarEvent {
  id: string;
  title: string;
  date: Date;
  type: "meeting" | "task" | "reminder";
}

interface CalendarProps {
  events?: CalendarEvent[];
  className?: string;
}

export const Calendar = ({ events = [], className }: CalendarProps) => {
  const [currentMonth, setCurrentMonth] = useState(new Date());
  const [selectedDate, setSelectedDate] = useState(new Date());

  const renderHeader = () => {
    return (
      <div className="flex items-center justify-between px-4 py-4 border-b">
        <h2 className="text-lg font-bold">
          {format(currentMonth, "MMMM yyyy")}
        </h2>
        <div className="flex items-center gap-2">
          <Button variant="outline" size="icon" onClick={() => setCurrentMonth(subMonths(currentMonth, 1))}>
            <ChevronLeft className="h-4 w-4" />
          </Button>
          <Button variant="outline" size="sm" onClick={() => setCurrentMonth(new Date())}>
            Today
          </Button>
          <Button variant="outline" size="icon" onClick={() => setCurrentMonth(addMonths(currentMonth, 1))}>
            <ChevronRight className="h-4 w-4" />
          </Button>
          <Button className="ml-2 gap-2" size="sm">
            <Plus className="h-4 w-4" /> New Event
          </Button>
        </div>
      </div>
    );
  };

  const renderDays = () => {
    const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
    return (
      <div className="grid grid-cols-7 border-b bg-muted/30">
        {days.map((day) => (
          <div key={day} className="py-2 text-center text-xs font-bold uppercase tracking-wider text-muted-foreground">
            {day}
          </div>
        ))}
      </div>
    );
  };

  const renderCells = () => {
    const monthStart = startOfMonth(currentMonth);
    const monthEnd = endOfMonth(monthStart);
    const startDate = startOfWeek(monthStart);
    const endDate = endOfWeek(monthEnd);

    const dateFormat = "d";
    const rows = [];
    let days = [];
    let day = startDate;
    let formattedDate = "";

    while (day <= endDate) {
      for (let i = 0; i < 7; i++) {
        formattedDate = format(day, dateFormat);
        const cloneDay = day;
        const dayEvents = events.filter(e => isSameDay(e.date, cloneDay));

        days.push(
          <div
            key={day.toString()}
            className={cn(
              "min-h-[120px] p-2 border-r border-b transition-colors hover:bg-muted/10 cursor-pointer",
              !isSameMonth(day, monthStart) ? "bg-muted/5 text-muted-foreground/50" : "",
              isSameDay(day, selectedDate) ? "bg-primary/5" : ""
            )}
            onClick={() => setSelectedDate(cloneDay)}
          >
            <div className="flex justify-between items-start mb-1">
              <span className={cn(
                "text-sm font-medium h-7 w-7 flex items-center justify-center rounded-full",
                isSameDay(day, new Date()) ? "bg-primary text-primary-foreground" : ""
              )}>
                {formattedDate}
              </span>
            </div>
            <div className="space-y-1 overflow-hidden">
              {dayEvents.slice(0, 3).map(event => (
                <div
                  key={event.id}
                  className={cn(
                    "text-[10px] px-1.5 py-0.5 rounded border truncate",
                    event.type === "meeting" ? "bg-blue-500/10 border-blue-500/20 text-blue-600 dark:text-blue-400" :
                      event.type === "task" ? "bg-emerald-500/10 border-emerald-500/20 text-emerald-600 dark:text-emerald-400" :
                        "bg-amber-500/10 border-amber-500/20 text-amber-600 dark:text-amber-400"
                  )}
                >
                  {event.title}
                </div>
              ))}
              {dayEvents.length > 3 && (
                <div className="text-[9px] text-muted-foreground pl-1">
                  + {dayEvents.length - 3} more
                </div>
              )}
            </div>
          </div>
        );
        day = addDays(day, 1);
      }
      rows.push(
        <div className="grid grid-cols-7" key={day.toString()}>
          {days}
        </div>
      );
      days = [];
    }
    return <div className="bg-background">{rows}</div>;
  };

  return (
    <div className={cn("border rounded-xl overflow-hidden shadow-sm", className)}>
      {renderHeader()}
      {renderDays()}
      {renderCells()}
    </div>
  );
};