Interactive Calendar Widget
A fully interactive calendar widget with event management, month navigation, and date selection. Perfect for scheduling applications and dashboards.
Previewing:
desktop1200px
July 2025
Sun
Mon
Tue
Wed
Thu
Fri
Sat
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
Upcoming Events
Team Meeting
Sun, Dec 15
meetingProject Deadline
Wed, Dec 18
deadlineClient Call
Fri, Dec 20
callHoliday Party
Sun, Dec 22
eventChristmas
Wed, Dec 25
holidayImplementation
'use client'
import { useState } from 'react'
import { Card, Badge } from '@akitectio/aki-ui'
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline'
function CalendarWidget() {
const [currentDate, setCurrentDate] = useState(new Date())
const [selectedDate, setSelectedDate] = useState<Date | null>(null)
const events = [
{ date: '2024-12-15', title: 'Team Meeting', type: 'meeting' },
{ date: '2024-12-18', title: 'Project Deadline', type: 'deadline' },
{ date: '2024-12-20', title: 'Client Call', type: 'call' },
{ date: '2024-12-22', title: 'Holiday Party', type: 'event' },
{ date: '2024-12-25', title: 'Christmas', type: 'holiday' },
]
const getDaysInMonth = (date: Date) => {
const year = date.getFullYear()
const month = date.getMonth()
const firstDay = new Date(year, month, 1)
const lastDay = new Date(year, month + 1, 0)
const daysInMonth = lastDay.getDate()
const startingDayOfWeek = firstDay.getDay()
const days = []
// Add empty cells for days before the first day of the month
for (let i = 0; i < startingDayOfWeek; i++) {
days.push(null)
}
// Add days of the month
for (let day = 1; day <= daysInMonth; day++) {
days.push(day)
}
return days
}
const formatDate = (date: Date) => {
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long'
})
}
const getEventForDate = (day: number) => {
const dateString = `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`
return events.find(event => event.date === dateString)
}
const navigateMonth = (direction: 'prev' | 'next') => {
const newDate = new Date(currentDate)
if (direction === 'prev') {
newDate.setMonth(newDate.getMonth() - 1)
} else {
newDate.setMonth(newDate.getMonth() + 1)
}
setCurrentDate(newDate)
}
const days = getDaysInMonth(currentDate)
const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
const today = new Date()
return (
<div className="max-w-4xl mx-auto p-6">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Calendar */}
<Card className="lg:col-span-2 p-6">
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
{formatDate(currentDate)}
</h2>
<div className="flex items-center space-x-2">
<button
onClick={() => navigateMonth('prev')}
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
>
<ChevronLeftIcon className="w-5 h-5 text-gray-600 dark:text-gray-300" />
</button>
<button
onClick={() => navigateMonth('next')}
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
>
<ChevronRightIcon className="w-5 h-5 text-gray-600 dark:text-gray-300" />
</button>
</div>
</div>
{/* Calendar Grid */}
<div className="grid grid-cols-7 gap-1">
{/* Weekday headers */}
{weekdays.map((day) => (
<div key={day} className="p-2 text-center text-sm font-medium text-gray-500 dark:text-gray-400">
{day}
</div>
))}
{/* Calendar days */}
{days.map((day, index) => {
if (day === null) {
return <div key={index} className="p-2 h-12"></div>
}
const isToday =
today.getDate() === day &&
today.getMonth() === currentDate.getMonth() &&
today.getFullYear() === currentDate.getFullYear()
const isSelected =
selectedDate &&
selectedDate.getDate() === day &&
selectedDate.getMonth() === currentDate.getMonth() &&
selectedDate.getFullYear() === currentDate.getFullYear()
const event = getEventForDate(day)
return (
<div
key={day}
className={`p-2 h-12 flex items-center justify-center text-sm cursor-pointer rounded-lg transition-colors relative ${
isToday
? 'bg-blue-600 text-white'
: isSelected
? 'bg-blue-100 text-blue-800 dark:bg-blue-900/20 dark:text-blue-300'
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
}`}
onClick={() => setSelectedDate(new Date(currentDate.getFullYear(), currentDate.getMonth(), day))}
>
{day}
{event && (
<div className="absolute bottom-0 left-1/2 transform -translate-x-1/2 w-2 h-2 bg-current rounded-full opacity-60"></div>
)}
</div>
)
})}
</div>
</Card>
{/* Events Sidebar */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
Upcoming Events
</h3>
<div className="space-y-3">
{events.map((event, index) => (
<div key={index} className="flex items-start space-x-3 p-3 rounded-lg bg-gray-50 dark:bg-gray-800">
<div className="w-3 h-3 rounded-full bg-blue-600 mt-1 flex-shrink-0"></div>
<div className="flex-1">
<p className="text-sm font-medium text-gray-900 dark:text-white">
{event.title}
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
{new Date(event.date).toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric'
})}
</p>
<Badge
variant="secondary"
className="mt-1"
>
{event.type}
</Badge>
</div>
</div>
))}
</div>
{/* Quick Actions */}
<div className="mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
<button className="w-full px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm font-medium">
Add Event
</button>
</div>
</Card>
</div>
</div>
)
}
Features
- • Month navigation with arrows
- • Event indicators on dates
- • Today highlighting
- • Date selection
- • Event sidebar with details
- • Responsive grid layout
Use Cases
- • Event management systems
- • Booking applications
- • Dashboard widgets
- • Scheduling tools
- • Project timelines
- • Content planning