Magic Calendar
An interactive calendar component with confetti effects and magic date triggers.
Installation
Add this component to your project using the CLI:
terminal
npx vui-registry-cli-v1 add magic-calendarSource Code
magic-calendar.tsx
'use client'
import React, { useMemo, useState, useRef } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { ChevronLeft, ChevronRight, MapPin, Sparkles } from 'lucide-react'
import { cn } from '@/lib/utils'
import confetti from 'canvas-confetti'
import { MONTHS, YEARS, daysMatrix } from './magic-calendar-utils'
export default function MagicCalendar() {
const [viewDate, setViewDate] = useState(new Date(2002, 4, 1))
const [selectedDate, setSelectedDate] = useState<Date | null>(null)
const [isPopupOpen, setIsPopupOpen] = useState(false)
const [showDial, setShowDial] = useState<'month' | 'year' | null>(null)
const y = viewDate.getFullYear()
const m = viewDate.getMonth()
const cells = useMemo(() => daysMatrix(y, m), [y, m])
const fireConfetti = () => {
confetti({
particleCount: 150,
spread: 70,
origin: { y: 0.6 },
colors: ['#38bdf8', '#818cf8', '#ffffff']
});
};
const handleDateClick = (d: number) => {
const newDate = new Date(y, m, d)
setSelectedDate(newDate)
setIsPopupOpen(true)
if (d === 11 && m === 4 && y === 2002) fireConfetti();
}
const triggerMagic = () => {
setViewDate(new Date(2002, 4, 1));
setSelectedDate(new Date(2002, 4, 11));
setIsPopupOpen(true);
setTimeout(fireConfetti, 50);
}
const USER_IMAGE = "https://i.postimg.cc/3xgQH76g/Whats-App-Image-2026-02-19-at-8-23-43-PM.jpg";
const isVikasBirthday = selectedDate?.getDate() === 11 &&
selectedDate?.getMonth() === 4 &&
selectedDate?.getFullYear() === 2002;
return (
<div className="relative flex flex-col items-center justify-center p-8 min-h-[700px] select-none font-sans">
<div className="relative p-[3px] rounded-[34px] bg-gradient-to-b from-neutral-200 to-neutral-100 dark:from-neutral-800 dark:to-neutral-900 shadow-2xl">
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/50 to-transparent opacity-50 blur-sm" />
<motion.div
className="relative w-full max-w-sm overflow-hidden rounded-[32px] bg-card/90 backdrop-blur-xl border border-white/20 shadow-inner"
>
{/* Inner Double Border Effect */}
<div className="absolute inset-[3px] rounded-[29px] border border-neutral-100/50 dark:border-white/5 pointer-events-none z-0" />
<div className="relative flex items-center justify-between p-6 z-30">
<div className="relative space-y-1">
<div className="flex gap-1 items-baseline">
<button
onClick={() => setShowDial(showDial === 'month' ? null : 'month')}
className={cn("text-xl font-semibold tracking-tight transition-all hover:text-sky-400 active:scale-95", showDial === 'month' && "text-sky-500")}
>
{MONTHS[m]}
</button>
<button
onClick={() => setShowDial(showDial === 'year' ? null : 'year')}
className={cn("text-sm font-bold opacity-30 transition-all hover:opacity-100 hover:text-sky-400 active:scale-95", showDial === 'year' && "opacity-100 text-sky-500")}
>
{y}
</button>
</div>
<p className="text-[10px] font-bold text-muted-foreground/50 uppercase tracking-[0.2em]">
{showDial ? "Pick and close" : "Select a date"}
</p>
<AnimatePresence>
{showDial && (
<>
<motion.div
initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
onClick={() => setShowDial(null)}
className="fixed inset-0 z-40"
/>
<motion.div
initial={{ opacity: 0, y: 10, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 10, scale: 0.95 }}
className="absolute top-full left-0 mt-2 w-48 z-50 overflow-hidden rounded-2xl border border-white/10 bg-background/80 backdrop-blur-2xl shadow-2xl"
>
<div className="h-48 overflow-y-auto scrollbar-hide py-2 px-1">
{(showDial === 'month' ? MONTHS : YEARS).map((item, idx) => (
<button
key={item}
onClick={() => {
if (showDial === 'month') setViewDate(new Date(y, idx, 1));
else setViewDate(new Date(Number(item), m, 1));
setShowDial(null);
}}
className={cn(
"w-full px-4 py-2 text-left text-xs font-bold uppercase tracking-wider transition-colors rounded-lg mb-1",
(showDial === 'month' ? m === idx : y === Number(item))
? "bg-sky-500 text-white"
: "text-foreground/60 hover:bg-foreground/5 hover:text-foreground"
)}
>
{item}
</button>
))}
</div>
</motion.div>
</>
)}
</AnimatePresence>
</div>
<div className="flex gap-1 bg-foreground/5 p-1 rounded-full relative z-20">
<button onClick={() => setViewDate(new Date(y, m - 1, 1))} className="p-2 hover:bg-background rounded-full transition-colors">
<ChevronLeft className="w-4 h-4" />
</button>
<button onClick={() => setViewDate(new Date(y, m + 1, 1))} className="p-2 hover:bg-background rounded-full transition-colors">
<ChevronRight className="w-4 h-4" />
</button>
</div>
</div>
<div className="px-6 pb-8 pt-2">
<div className="grid grid-cols-7 mb-4">
{['S', 'M', 'T', 'W', 'T', 'F', 'S'].map((day, idx) => (
<span
key={day + '-' + idx}
className="text-center text-[10px] font-bold text-muted-foreground/40"
>
{day}
</span>
))}
</div>
<div className="grid grid-cols-7 gap-y-2">
{cells.map((cell) => {
const isSelected = selectedDate?.getDate() === cell.day && selectedDate?.getMonth() === m && selectedDate?.getFullYear() === y;
return (
<div key={cell.key} className="relative flex items-center justify-center aspect-square">
{cell.day && (
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
onClick={() => handleDateClick(cell.day!)}
className={cn(
"relative z-10 w-10 h-10 rounded-2xl text-sm font-medium transition-colors flex items-center justify-center",
isSelected ? "text-background" : "text-foreground/70 hover:text-foreground"
)}
>
{cell.day}
{isSelected && (
<motion.div
layoutId="active-pill"
className="absolute inset-0 bg-foreground rounded-2xl -z-10 shadow-lg"
/>
)}
</motion.button>
)}
</div>
)
})}
</div>
</div>
</motion.div>
</div>
<button
onClick={triggerMagic}
className="mt-8 flex items-center gap-2 text-[10px] font-black uppercase tracking-[0.3em] opacity-40 hover:opacity-100 hover:text-sky-400 transition-all group"
>
<Sparkles size={12} className="group-hover:scale-125 transition-transform text-sky-400" />
Redirect to <span className="underline underline-offset-4 decoration-sky-500/30">11 May 2002</span>
</button>
<AnimatePresence>
{isPopupOpen && selectedDate && (
<div className="absolute inset-0 z-50 flex items-center justify-center p-4">
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} onClick={() => setIsPopupOpen(false)} className="absolute inset-0 bg-background/40 backdrop-blur-md" />
<motion.div
initial={{ scale: 0.9, opacity: 0, y: 10 }}
animate={{ scale: 1, opacity: 1, y: 0 }}
exit={{ scale: 0.9, opacity: 0, y: 10 }}
transition={{ type: "spring", stiffness: 400, damping: 25 }}
className={cn(
"relative w-full max-w-[280px] overflow-hidden rounded-[44px] p-1 shadow-2xl",
isVikasBirthday ? "bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500 shadow-purple-500/40" : "bg-card border border-white/20"
)}
>
{isVikasBirthday && (
<div className="absolute inset-0 bg-gradient-to-tr from-transparent via-white/20 to-transparent skew-x-12 translate-x-[-150%] animate-shimmer pointer-events-none z-10" />
)}
<div className="absolute inset-0 rounded-[44px] border border-white/40 pointer-events-none z-20" />
<div className="absolute inset-3 rounded-[34px] border border-dashed border-white/20 pointer-events-none z-20" />
<div className="relative z-10">
<div className={cn(
"p-8 text-center space-y-2 rounded-t-[40px] flex flex-col items-center",
isVikasBirthday ? "bg-white/10 text-white" : "bg-foreground text-background"
)}>
{isVikasBirthday && (
<div className="w-20 h-20 rounded-full border-4 border-white/20 shadow-xl overflow-hidden mb-2 relative group">
<div className="absolute inset-0 bg-black/10 group-hover:bg-transparent transition-colors" />
<img src={USER_IMAGE} alt="Profile" className="w-full h-full object-cover scale-110" />
</div>
)}
<h3 className="text-6xl font-black tracking-tighter tabular-nums leading-none">{selectedDate.getDate()}</h3>
<p className="text-[10px] font-black uppercase tracking-[0.2em] opacity-60">
{MONTHS[selectedDate.getMonth()]} {selectedDate.getFullYear()}
</p>
</div>
<div className="p-8 space-y-4 text-center">
<p className={cn("text-base font-bold", isVikasBirthday ? "text-white" : "text-foreground")}>
{isVikasBirthday ? "Vikas Birthday 🎂" : "Date Selected"}
</p>
{isVikasBirthday && (
<div className="flex items-center justify-center gap-2 opacity-70 text-white">
<MapPin size={12} />
<span className="text-[10px] font-bold uppercase tracking-tight">Dehradun, India</span>
</div>
)}
<button
onClick={() => setIsPopupOpen(false)}
className={cn(
"w-full py-4 rounded-2xl text-[10px] font-black uppercase tracking-[0.2em] transition-all",
isVikasBirthday ? "bg-white text-sky-600 shadow-xl active:scale-95" : "bg-foreground/5 text-foreground hover:bg-foreground/10"
)}
>
Close
</button>
</div>
</div>
</motion.div>
</div>
)}
</AnimatePresence>
</div>
)
}
magic-calendar-utils.ts
magic-calendar-utils.ts
export const MONTHS = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
]
export const YEARS = Array.from({ length: 101 }, (_, i) => 1950 + i)
export const daysMatrix = (y: number, m: number) => {
const first = new Date(y, m, 1).getDay()
const last = new Date(y, m + 1, 0).getDate()
const cells: { day: number | null; key: string }[] = []
for (let i = 0; i < first; i++) cells.push({ day: null, key: `p-${i}` })
for (let d = 1; d <= last; d++) cells.push({ day: d, key: `${y}-${m}-${d}` })
return cells
}
Dependencies
framer-motion: latestlucide-react: latestcanvas-confetti: latestdate-fns: latest

