Overview
The InteractiveCalendar component displays a full-year 2026 calendar grid for tracking daily moods, work logs, and journal entries. It supports quadrimester and full-year views with interactive day cells that open popovers for data entry.
Props
Optional CSS class name to apply to the root container element.
Usage
Basic Implementation
import { InteractiveCalendar } from "@/components/interactive-calendar";
function App() {
return (
<div className="flex min-h-0 flex-1">
<InteractiveCalendar />
</div>
);
}
Example from src/app.tsx:122:
calendar: (
<div className="flex min-h-0 flex-1">
<InteractiveCalendar />
</div>
),
With Custom Styling
<InteractiveCalendar className="custom-calendar" />
State Management
The component manages multiple pieces of state:
Calendar Data
From src/hooks/use-calendar-data.ts:7:
const { getEntryForDate, handleSaveEntry } = useCalendarData();
// Internal hook implementation:
const [calendarData, setCalendarData] = useLocalStorage<CalendarData>(
"mood-calendar-2026-data",
{}
);
View State
const [currentQuadrimester, setCurrentQuadrimester] = useState(0);
const [showAllYear, setShowAllYear] = useState(false);
const [showNumbers, setShowNumbers] = useLocalStorage(
"mood-calendar-show-numbers",
true
);
Types and Interfaces
InteractiveCalendarProps
From src/types/calendar.ts:21:
interface InteractiveCalendarProps {
className?: string;
}
DayEntry
From src/types/calendar.ts:5:
interface DayEntry {
mood: MoodType | null;
workLog: string;
journal: string;
}
CalendarData
From src/types/calendar.ts:11:
interface CalendarData {
[dateKey: string]: DayEntry;
}
MonthGridProps
From src/types/calendar.ts:38:
interface MonthGridProps {
month: {
name: string;
days: number;
startDay: number;
};
monthIndex: number;
getEntryForDate: (dateKey: string) => DayEntry;
getFillColor: (dateKey: string) => string;
handleSaveEntry: (dateKey: string, entry: DayEntry) => void;
showAllYear: boolean;
showNumbers: boolean;
animationDelay?: number;
}
Features
View Modes
Quadrimester View (Default)
Displays 4 months at a time:
- Q1: January - April
- Q2: May - August
- Q3: September - December
Navigation buttons in the footer allow cycling through quadrimesters.
Full Year View
Displays all 12 months in a grid layout. Toggle via the “full year” button in the header.
Color Coding
From src/components/interactive-calendar.tsx:33:
const getFillColor = (dateKey: string): string => {
const entry = getEntryForDate(dateKey);
if (entry.mood) {
return MOOD_COLORS[entry.mood].color;
}
if (entry.workLog || entry.journal) {
return "var(--muted)"; // Color for entries without mood
}
return "var(--muted)";
};
Mood colors are defined in @/lib/calendar-constants and map MoodType values to CSS color variables.
Date Numbers Toggle
The menu button in the header toggles visibility of date numbers within calendar cells. This preference persists in local storage.
Month Grid Rendering
From src/components/interactive-calendar.tsx:102:
{currentMonths.map((monthIndex, i) => {
const month = MONTHS_2026[monthIndex];
return (
<MonthGrid
animationDelay={i * 0.05}
getEntryForDate={getEntryForDate}
getFillColor={getFillColor}
handleSaveEntry={handleSaveEntry}
key={month.name}
month={month}
monthIndex={monthIndex}
showAllYear={showAllYear}
showNumbers={showNumbers}
/>
);
})}
Animation
The calendar view transitions are animated using Framer Motion:
initial={{ filter: "blur(4px)", opacity: 0 }}
animate={{ filter: "blur(0px)", opacity: 1 }}
exit={{ filter: "blur(4px)", opacity: 0 }}
transition={{ duration: 0.2, ease: "easeOut" }}
Each month grid has a staggered animation delay based on its index: i * 0.05 seconds.
Layout
The component uses responsive grid layouts:
Quadrimester View
className="wide-mode grid-cols-1 grid-rows-4 gap-y- gap-x-8 lg:grid-cols-2 lg:grid-rows-2"
Full Year View
className="grid h-[250%] grid-cols-2 grid-rows-6 lg:h-full lg:grid-cols-4 lg:grid-rows-3"
Navigation
From src/components/interactive-calendar.tsx:122:
<CardFooter className="flex items-center justify-end px-4 pt-2">
{!showAllYear && (
<div className="flex items-center gap-1">
<Button onClick={handlePrevQuadrimester}>
<IconCaretLeftFilled className="size-4" />
</Button>
<span className="min-w-16 text-center font-medium text-xs">
{QUADRIMESTERS[currentQuadrimester].label}
</span>
<Button onClick={handleNextQuadrimester}>
<IconCaretRightFilled className="size-4" />
</Button>
</div>
)}
</CardFooter>
Data Persistence
The useCalendarData hook provides two key functions:
getEntryForDate
const getEntryForDate = useCallback(
(dateKey: string): DayEntry => {
return calendarData[dateKey] ?? { mood: null, workLog: "", journal: "" };
},
[calendarData]
);
handleSaveEntry
const handleSaveEntry = useCallback(
(dateKey: string, entry: DayEntry) => {
setCalendarData((prev) => ({
...prev,
[dateKey]: entry,
}));
},
[setCalendarData]
);
All data is automatically persisted to localStorage with the key "mood-calendar-2026-data".
Dependencies
motion/react - View transition animations
@tabler/icons-react - Navigation icons
@/hooks/use-calendar-data - Data management and persistence
@/hooks/use-local-storage - Settings persistence
@/lib/calendar-constants - Mood type definitions and colors
@/lib/calendar-utils - Month data and quadrimester configuration
@/components/month-grid - Individual month rendering
@/components/ui/* - Shadcn UI components