Blox/
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
TSXReactTailwind
npx @uiblox/cli add calendarPreview
January 2024
Sun
Mon
Tue
Wed
Thu
Fri
Sat
Events
Team Meeting
10:00 AM
Source Code
calendar.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129"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>; }