Month calendar
Events across a month, with a 'more' overflow.
Built with
- Button
- CalendarEvent
- CalendarMonthCell
- DirectionalIcon
- EventPopover
- IconButton
Team calendar
Holidays, leave and meetings at a glance. Select an event for details; “+N more” opens the day.
October 2026
Mon
Tue
Wed
Thu
Fri
Sat
Sun
Thursday 22 October
month-calendar.tsx
"use client";
import { useState } from "react";
import { CalendarDate } from "@internationalized/date";
import { LuChevronLeft, LuChevronRight, LuEllipsis, LuPencil } from "react-icons/lu";
import {
Button,
CalendarEvent,
CalendarMonthCell,
DirectionalIcon,
EventPopover,
IconButton,
} from "@nexera-ui/react";
const ZONE = "Asia/Karachi";
const TODAY = new CalendarDate(2026, 10, 15);
const WEEKDAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
/** October 2026 events by day: [title, start time]. Days with more than two events show "+N more". */
const EVENTS: Record<number, [string, string][]> = {
1: [["Payroll cut-off", "17:00"]],
5: [
["Kashmir Day", ""],
["Ops review", "11:00"],
],
9: [["Board meeting", "14:00"]],
13: [
["Interview: Mehak Ali", "10:30"],
["Design review", "14:00"],
],
14: [
["Hina Raza: first day", ""],
["Town hall", "15:00"],
],
15: [
["1:1 Sara and Ayesha", "11:00"],
["Interview: Zara Siddiqui", "10:30"],
["Payroll review", "12:00"],
["Write Q4 plan", "13:30"],
["Vendor demo", "16:00"],
],
16: [["Ayesha Khan: annual leave", ""]],
17: [["Ayesha Khan: annual leave", ""]],
20: [["Focus time", "09:00"]],
22: [
["Security training", "10:00"],
["Team lunch", "13:00"],
],
27: [["Performance reviews", "09:00"]],
30: [
["Payroll run", "09:00"],
["Payslips sent", "12:00"],
["Benefits sync", "15:00"],
],
31: [["Quarter close", ""]],
};
const typeOf = (title: string) =>
/leave/i.test(title)
? "out-of-office"
: /Focus|Quarter close|Q4 plan/.test(title)
? "focus-time"
: /first day|Kashmir/.test(title)
? "all-day"
: /Interview: Mehak|Design review/.test(title)
? "tentative"
: "timed";
/** Month calendar: October 2026 with leave, holidays and meetings; each event opens its details, "+N more" opens the day. */
export default function MonthCalendar() {
const [month, setMonth] = useState(new CalendarDate(2026, 10, 1));
const [selected, setSelected] = useState(new CalendarDate(2026, 10, 22));
const isOctober = (d: CalendarDate) => d.year === 2026 && d.month === 10;
const title = new Intl.DateTimeFormat("en-GB", {
month: "long",
year: "numeric",
timeZone: "UTC",
}).format(month.toDate("UTC"));
const offset = (month.toDate("UTC").getUTCDay() + 6) % 7;
const first = month.subtract({ days: offset });
const rows = Math.ceil((offset + month.calendar.getDaysInMonth(month)) / 7);
const dates = Array.from({ length: rows * 7 }, (_, i) => first.add({ days: i }));
const dayEvents = (d: CalendarDate) => (isOctober(d) ? (EVENTS[d.day] ?? []) : []);
const event = ([name, time]: [string, string], d: CalendarDate) => {
const allDay = time === "";
const start = new Date(`${d.toString()}T${allDay ? "00:00" : time}:00+05:00`);
return (
<EventPopover
key={name}
title={name}
when={`${new Intl.DateTimeFormat("en-GB", { weekday: "long", day: "numeric", month: "long", timeZone: ZONE }).format(start)} · ${allDay ? "All day" : time}`}
meta="Pakistan Standard Time"
calendarTone={allDay ? "leave" : "info"}
actions={
<>
<IconButton variant="ghost" size="sm" aria-label="Edit event" icon={<LuPencil />} />
<IconButton variant="ghost" size="sm" aria-label="More actions" icon={<LuEllipsis />} />
</>
}
>
<CalendarEvent
title={name}
type={typeOf(name)}
isAllDay={allDay}
start={start}
timeZone={ZONE}
{...(allDay ? {} : { time })}
/>
</EventPopover>
);
};
return (
<div className="@container bg-page px-4 py-8 @3xl:px-8">
<div className="grid gap-4">
<header className="grid gap-1">
<h2 className="text-heading-h3 text-primary">Team calendar</h2>
<p className="text-body-default text-secondary">
Holidays, leave and meetings at a glance. Select an event for details; “+N more” opens
the day.
</p>
</header>
<div className="flex flex-wrap items-center gap-2">
<Button
variant="secondary"
onPress={() => {
setMonth(new CalendarDate(2026, 10, 1));
setSelected(TODAY);
}}
>
Today
</Button>
<IconButton
variant="ghost"
aria-label="Previous month"
icon={
<DirectionalIcon>
<LuChevronLeft />
</DirectionalIcon>
}
onPress={() => {
setMonth(month.subtract({ months: 1 }));
}}
/>
<IconButton
variant="ghost"
aria-label="Next month"
icon={
<DirectionalIcon>
<LuChevronRight />
</DirectionalIcon>
}
onPress={() => {
setMonth(month.add({ months: 1 }));
}}
/>
<h3 aria-live="polite" className="text-heading-h4 text-primary">
{title}
</h3>
</div>
<div className="overflow-x-auto rounded-lg border border-default">
<div role="grid" aria-label={title} className="min-w-[46rem]">
<div role="row" className="grid grid-cols-7 bg-sunken">
{WEEKDAYS.map((d) => (
<div role="columnheader" key={d} className="p-2 text-body-caption text-muted">
{d}
</div>
))}
</div>
{Array.from({ length: rows }, (_, row) => (
<div role="row" key={row} className="grid grid-cols-7">
{dates.slice(row * 7, row * 7 + 7).map((d) => {
const list = dayEvents(d);
return (
<CalendarMonthCell
key={d.toString()}
date={d}
isToday={d.compare(TODAY) === 0}
isSelected={d.compare(selected) === 0}
isOutsideMonth={d.month !== month.month}
onDatePress={() => {
setSelected(d);
}}
{...(list.length > 2
? {
more: `+${list.length - 2} more`,
onMorePress: () => {
setSelected(d);
},
}
: {})}
>
{list.length > 0 ? list.slice(0, 2).map((e) => event(e, d)) : null}
</CalendarMonthCell>
);
})}
</div>
))}
</div>
</div>
<section
aria-labelledby="day-title"
className="grid gap-2 rounded-lg border border-default bg-surface p-4"
>
<h3 id="day-title" className="text-body-strong text-primary">
{new Intl.DateTimeFormat("en-GB", {
weekday: "long",
day: "numeric",
month: "long",
timeZone: "UTC",
}).format(selected.toDate("UTC"))}
</h3>
{dayEvents(selected).length === 0 ? (
<p className="text-body-small text-secondary">Nothing scheduled.</p>
) : (
<ul className="m-0 grid list-none gap-1.5 p-0 @3xl:grid-cols-2">
{dayEvents(selected).map(([name, time]) => (
<li key={name}>
<CalendarEvent
title={name}
type={typeOf(name)}
layout="block"
isAllDay={time === ""}
start={new Date(`${selected.toString()}T${time || "00:00"}:00+05:00`)}
timeZone={ZONE}
href={`#${name.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`}
/>
</li>
))}
</ul>
)}
</section>
</div>
</div>
);
}
More scheduling and calendar blocks
- Week calendarHour-by-hour week view with events.
- Team availabilityWho is free when, side by side.
- Book a meetingPick a day, then a free time slot.
- Event detailsA popover with time, place and people.
- Leave requestRequest time off and see the balance.
- Leave approvalReview a request and approve or decline.
- Shift pickerChoose a morning, evening or night shift.