Skip to main content

Overview

The MonthlyCalendar component displays events in a traditional month grid view, similar to Google Calendar or Outlook. It automatically handles event overflow with popover lists, highlights the current day, and supports custom event rendering. Ideal for applications that need a high-level overview of monthly schedules.

Import

import { MonthlyCalendar } from '@newtonschool/grauity';
import type { MonthlyCalendarProps, CalendarEvent } from '@newtonschool/grauity';

Basic Usage

import { MonthlyCalendar } from '@newtonschool/grauity';
import { useState } from 'react';

function MyMonthlyCalendar() {
  const [date, setDate] = useState(new Date());

  const events = [
    {
      id: '1',
      title: 'Team Meeting',
      start: new Date(2024, 2, 15, 10, 0),
      end: new Date(2024, 2, 15, 11, 0),
    },
    {
      id: '2',
      title: 'Project Deadline',
      start: new Date(2024, 2, 25, 17, 0),
      end: new Date(2024, 2, 25, 18, 0),
    },
  ];

  return (
    <MonthlyCalendar
      events={events}
      date={date}
      onDateChange={setDate}
      eventRenderer={(event) => (
        <div>{event.title}</div>
      )}
    />
  );
}

Props

events
CalendarEvent<T>[]
required
Array of events to display in the calendar. Events are automatically sorted by start time within each day.
eventRenderer
(item: CalendarEvent<T>) => React.ReactNode
required
Function to render each event in the calendar grid. This is used for the main event display.
date
Date
default:"new Date()"
The date to show in the calendar. Determines which month is displayed.
onDateChange
(date: Date) => void
Callback fired when the date changes through month navigation controls.
shouldShowMonthControls
boolean
default:"true"
Whether to show month navigation controls (previous/next arrows and Today button).
header
React.ReactNode
default:"null"
Custom header content to display above the calendar.
renderDayItem
(item: CalendarEvent<T>) => React.ReactNode
Optional alternative render function for events. When provided, this is used instead of eventRenderer for rendering events in day cells.
loading
boolean
default:"false"
Whether the calendar is in a loading state. Shows placeholder UI when true.
onPopOverClose
() => void
Callback to run when the overflow events popover closes.
className
string
default:"''"
Additional class name for the calendar container element.

Event Data Structure

The CalendarEvent interface defines the structure for calendar events:
interface CalendarEvent<T = {}> {
  id?: string | number;
  title?: string;
  start: Date;        // Required: Event start time
  end: Date;          // Required: Event end time
  allDay?: boolean;
  render?: (event: CalendarEvent<T>) => React.ReactNode;
  focused?: boolean;
}

Custom Event Rendering

Use the MonthlyCalendarEvent component for styled event items:
import { MonthlyCalendar } from '@newtonschool/grauity';
import MonthlyCalendarEventItem from '@newtonschool/grauity';

function CustomMonthlyCalendar() {
  const events = [
    {
      id: '1',
      title: 'Morning Meeting',
      start: new Date(2024, 2, 7, 9, 0),
      end: new Date(2024, 2, 7, 10, 30),
    },
  ];

  return (
    <MonthlyCalendar
      events={events}
      eventRenderer={(event) => (
        <MonthlyCalendarEventItem
          key={event.id}
          eventTime={event.start}
          eventTitle={event.title}
          eventTitleColor="var(--text-emphasis-invert-primary-default, #ffffff)"
          eventTimeColor="var(--text-emphasis-invert-primary-default, #ffffff)"
          backgroundColor="var(--bg-emphasis-brand-default, #0673f9)"
          height="50px"
        />
      )}
    />
  );
}

Event Overflow Handling

When a day has more events than can fit in the grid cell:
1

Visible Events

The first few events are displayed in the day cell (number depends on cell height).
2

Overflow Indicator

An indicator like “+3 more” appears at the bottom of the cell.
3

Popover Display

Clicking the indicator opens a popover showing all events for that day.
4

Popover Close

The onPopOverClose callback fires when the user closes the popover.
<MonthlyCalendar
  events={manyEvents}
  eventRenderer={(event) => <div>{event.title}</div>}
  onPopOverClose={() => console.log('Overflow popover closed')}
/>

Month Navigation

<MonthlyCalendar
  shouldShowMonthControls
  date={date}
  onDateChange={setDate}
  events={events}
  eventRenderer={(event) => <div>{event.title}</div>}
/>

Complete Example

import { MonthlyCalendar } from '@newtonschool/grauity';
import MonthlyCalendarEventItem from '@newtonschool/grauity';
import { useState } from 'react';

function CalendarApp() {
  const [date, setDate] = useState(new Date());
  
  const currentYear = new Date().getFullYear();
  const currentMonth = new Date().getMonth();

  const events = [
    {
      id: '1-1',
      title: 'Morning Meeting',
      start: new Date(currentYear, currentMonth, 1, 9, 0),
      end: new Date(currentYear, currentMonth, 1, 10, 30),
    },
    {
      id: '1-2',
      title: 'Team Lunch',
      start: new Date(currentYear, currentMonth, 1, 12, 0),
      end: new Date(currentYear, currentMonth, 1, 13, 0),
    },
    {
      id: '5-1',
      title: 'Project Review',
      start: new Date(currentYear, currentMonth, 5, 11, 0),
      end: new Date(currentYear, currentMonth, 5, 12, 30),
    },
    {
      id: '5-2',
      title: 'Client Call',
      start: new Date(currentYear, currentMonth, 5, 14, 0),
      end: new Date(currentYear, currentMonth, 5, 15, 0),
    },
    {
      id: '10-1',
      title: 'Sprint Planning',
      start: new Date(currentYear, currentMonth, 10, 10, 0),
      end: new Date(currentYear, currentMonth, 10, 11, 30),
    },
    {
      id: '15-1',
      title: 'Weekly Standup',
      start: new Date(currentYear, currentMonth, 15, 9, 30),
      end: new Date(currentYear, currentMonth, 15, 10, 30),
    },
  ];

  return (
    <div style={{ height: '100vh' }}>
      <MonthlyCalendar
        date={date}
        onDateChange={(newDate) => {
          console.log('Month changed to:', newDate);
          setDate(newDate);
        }}
        events={events}
        eventRenderer={(event) => (
          <MonthlyCalendarEventItem
            key={event.id}
            eventTime={event.start}
            eventTitle={event.title}
            eventTitleColor="var(--text-emphasis-invert-primary-default, #ffffff)"
            eventTimeColor="var(--text-emphasis-invert-primary-default, #ffffff)"
            backgroundColor="var(--bg-emphasis-brand-default, #0673f9)"
            height="50px"
          />
        )}
        shouldShowMonthControls
        onPopOverClose={() => {
          console.log('User closed overflow popover');
        }}
        header={
          <div style={{ padding: '16px', borderBottom: '1px solid #e1e5ea' }}>
            <h2>Monthly Schedule</h2>
          </div>
        }
      />
    </div>
  );
}

Grid Layout

The calendar grid:
  • Always shows complete weeks (42 or 35 days depending on the month)
  • Includes dates from previous and next months to fill the grid
  • Grays out dates outside the current month
  • Highlights today’s date
  • Starts weeks on Sunday

Custom Day Rendering

For advanced customization, use renderDayItem instead of eventRenderer:
<MonthlyCalendar
  events={events}
  eventRenderer={(event) => <div>{event.title}</div>}
  renderDayItem={(event) => (
    <div style={{ 
      padding: '4px',
      background: event.start.getDay() === 0 ? '#f0f0f0' : 'transparent' 
    }}>
      {event.title}
    </div>
  )}
/>
When renderDayItem is provided, it takes precedence over eventRenderer for the main calendar grid. The eventRenderer is still used in the overflow popover.

Loading State

<MonthlyCalendar
  loading={true}
  events={[]}
  eventRenderer={(event) => <div>{event.title}</div>}
/>
When loading is true, the calendar displays placeholder elements in each day cell.

Multiple Events Per Day

Events are automatically sorted by start time within each day:
const dayEvents = [
  {
    id: '1',
    title: 'Morning Meeting',
    start: new Date(2024, 2, 15, 9, 0),
    end: new Date(2024, 2, 15, 10, 0),
  },
  {
    id: '2',
    title: 'Lunch',
    start: new Date(2024, 2, 15, 12, 0),
    end: new Date(2024, 2, 15, 13, 0),
  },
  {
    id: '3',
    title: 'Afternoon Review',
    start: new Date(2024, 2, 15, 15, 0),
    end: new Date(2024, 2, 15, 16, 0),
  },
];

<MonthlyCalendar
  events={dayEvents}
  eventRenderer={(event) => <div>{event.title}</div>}
/>
The calendar will display these in chronological order within the day cell.

Accessibility

  • Semantic HTML structure with proper heading hierarchy
  • ARIA labels describing the current month
  • Keyboard navigation for month controls
  • Screen reader support for date changes
  • Focusable event items

Performance

Events are sorted once when passed to the component, not on every render. For optimal performance, memoize your events array.
The calendar calculates grid dates efficiently and only recalculates when the month changes.
Overflow popovers are rendered on-demand, not upfront, keeping the initial render fast.

TypeScript

The component supports generic typing for custom event data:
interface TaskEvent {
  priority: 'high' | 'medium' | 'low';
  assignee: string;
  completed: boolean;
}

const tasks: CalendarEvent<TaskEvent>[] = [
  {
    id: '1',
    title: 'Complete quarterly review',
    start: new Date(2024, 2, 15, 10, 0),
    end: new Date(2024, 2, 15, 11, 0),
    priority: 'high',
    assignee: '[email protected]',
    completed: false,
  },
];

<MonthlyCalendar<TaskEvent>
  events={tasks}
  eventRenderer={(event) => (
    <div>
      <span>{event.title}</span>
      {event.priority === 'high' && <span>🔴</span>}
      {event.completed && <span></span>}
    </div>
  )}
/>

Best Practices

Keep event heights consistent (around 40-50px) for the best visual layout. The calendar will show 3-4 events per cell before overflow.
Use concise event titles that fit within the narrow day cells. Long titles will be truncated.
Use consistent colors for event categories to help users quickly scan the calendar.
The calendar adapts to its container width. Ensure the container has adequate space (minimum 800px recommended).

UnifiedCalendar

Combined monthly and weekly calendar with view switching

WeeklyCalendar

Time-slot based weekly calendar view

Build docs developers (and LLMs) love