Calendar

An interactive calendar component with month navigation, event indicators, and event details panel. Perfect for scheduling apps and dashboards.

Month ViewEventsNavigationToday Highlight

Installation

Terminal
ReactTailwind
TSX
npx @uiblox/cli add calendar

Preview

January 2024

Sun
Mon
Tue
Wed
Thu
Fri
Sat

Events

Team Meeting

10:00 AM

Source Code

calendar.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
"use client"; import { useState } from "react"; import { cn } from "@/lib/utils"; interface Event { id: number; title: string; time: string; color: string; } const events: Record<string, Event[]> = { "2024-01-15": [{ id: 1, title: "Team Meeting", time: "10:00 AM", color: "bg-purple-500" }], "2024-01-18": [{ id: 2, title: "Project Review", time: "2:00 PM", color: "bg-cyan-500" }], "2024-01-22": [ { id: 3, title: "Design Sprint", time: "9:00 AM", color: "bg-emerald-500" }, { id: 4, title: "Client Call", time: "3:00 PM", color: "bg-amber-500" }, ], }; export function Calendar() { const [currentDate, setCurrentDate] = useState(new Date(2024, 0, 1)); const [selectedDate, setSelectedDate] = useState<string | null>(null); const daysInMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0).getDate(); const firstDayOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1).getDay(); const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; const prevMonth = () => setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1)); const nextMonth = () => setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1)); const getDateKey = (day: number) => { const month = String(currentDate.getMonth() + 1).padStart(2, "0"); const dayStr = String(day).padStart(2, "0"); return `${currentDate.getFullYear()}-${month}-${dayStr}`; }; return ( <div className="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 overflow-hidden"> {/* Header */} <div className="flex items-center justify-between p-4 border-b border-slate-200 dark:border-slate-800"> <h2 className="text-lg font-semibold text-slate-900 dark:text-white"> {monthNames[currentDate.getMonth()]} {currentDate.getFullYear()} </h2> <div className="flex items-center gap-2"> <button onClick={prevMonth} className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg"> <ChevronLeftIcon /> </button> <button onClick={() => setCurrentDate(new Date())} className="px-3 py-1.5 text-sm font-medium text-purple-600 hover:bg-purple-50 dark:hover:bg-purple-900/20 rounded-lg"> Today </button> <button onClick={nextMonth} className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg"> <ChevronRightIcon /> </button> </div> </div> {/* Calendar Grid */} <div className="p-4"> {/* Day Names */} <div className="grid grid-cols-7 mb-2"> {dayNames.map((day) => ( <div key={day} className="text-center text-xs font-medium text-slate-500 py-2"> {day} </div> ))} </div> {/* Days */} <div className="grid grid-cols-7 gap-1"> {Array.from({ length: firstDayOfMonth }).map((_, i) => ( <div key={`empty-${i}`} className="aspect-square" /> ))} {Array.from({ length: daysInMonth }).map((_, i) => { const day = i + 1; const dateKey = getDateKey(day); const dayEvents = events[dateKey] || []; const isToday = new Date().toDateString() === new Date(currentDate.getFullYear(), currentDate.getMonth(), day).toDateString(); return ( <button key={day} onClick={() => setSelectedDate(dateKey)} className={cn( "aspect-square p-1 rounded-lg text-sm transition-colors relative", selectedDate === dateKey ? "bg-purple-100 dark:bg-purple-900/30" : "hover:bg-slate-100 dark:hover:bg-slate-800", isToday && "ring-2 ring-purple-500" )} > <span className={cn("block", isToday && "font-bold text-purple-600")}>{day}</span> {dayEvents.length > 0 && ( <div className="flex justify-center gap-0.5 mt-0.5"> {dayEvents.slice(0, 3).map((event) => ( <div key={event.id} className={`w-1 h-1 rounded-full ${event.color}`} /> ))} </div> )} </button> ); })} </div> </div> {/* Events Panel */} {selectedDate && events[selectedDate] && ( <div className="border-t border-slate-200 dark:border-slate-800 p-4"> <h3 className="text-sm font-semibold text-slate-900 dark:text-white mb-3">Events</h3> <div className="space-y-2"> {events[selectedDate].map((event) => ( <div key={event.id} className="flex items-center gap-3 p-2 rounded-lg bg-slate-50 dark:bg-slate-800/50"> <div className={`w-2 h-8 rounded-full ${event.color}`} /> <div> <p className="text-sm font-medium text-slate-900 dark:text-white">{event.title}</p> <p className="text-xs text-slate-500">{event.time}</p> </div> </div> ))} </div> </div> )} </div> ); } function ChevronLeftIcon() { return <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" /></svg>; } function ChevronRightIcon() { return <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /></svg>; }