Calendar

App

Month / week / day / agenda / resource calendar with view switcher, full keyboard nav (PageUp/Down + T + arrow keys for day-step), per-event color and icon, all-day bars + timed pills, TR/EN locales, full interactions (anchored popover, drag-move, edge-resize, drag-create), in-house RRULE expansion (FREQ/INTERVAL/COUNT/UNTIL/BYDAY + exceptions), multi-calendar overlay with per-calendar visibility legend, ResourceView lanes with O(n²) conflict highlighting, agenda list (search + date grouping) and a composable MiniCalendar sidebar. WAI-ARIA grid pattern with live-region nav announcements ("Showing May 2026") and event-count cell labels ("Tuesday May 12, 3 events"). Optional Intl.DateTimeFormat-based time formatting for locale-aware clocks.

Month view — Türkçe

Preview

Mayıs 2026

Pzt
Sal
Çar
Per
Cum
Cmt
Paz
27
28
29
30
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
1
2
3
4
5
6
7
Code
<Calendar
  events={events}
  view="month"
  defaultDate={new Date(2026, 4, 13)}
  onViewChange={setView}
  locale="tr"
/>

Week view — working hours shading

Preview

11 – 17 Mayıs 2026

Pzt
11
Sal
12
Çar
13
Per
14
Cum
15
Cmt
16
Paz
17
Tüm gün
01:00
02:00
03:00
04:00
05:00
06:00
07:00
08:00
09:00
10:00
11:00
12:00
13:00
14:00
15:00
16:00
17:00
18:00
19:00
20:00
21:00
22:00
23:00
Code
<Calendar
  events={events}
  view="week"
  defaultDate={new Date(2026, 4, 13)}
  onViewChange={setView}
  locale="tr"
  workingHours={{ start: 9, end: 18, days: [1,2,3,4,5] }}
/>

Day view — English

Preview

13 May 2026

Wednesday13
01:00
02:00
03:00
04:00
05:00
06:00
07:00
08:00
09:00
10:00
11:00
12:00
13:00
14:00
15:00
16:00
17:00
18:00
19:00
20:00
21:00
22:00
23:00
Code
<Calendar
  events={events}
  view="day"
  defaultDate={new Date(2026, 4, 13)}
  onViewChange={setView}
  locale="en"
  workingHours={{ start: 9, end: 18, days: [1,2,3,4,5] }}
/>

Recurring — RRULE expansion

Preview

10 – 16 May 2026

Sun
10
Mon
11
Tue
12
Wed
13
Thu
14
Fri
15
Sat
16
01:00
02:00
03:00
04:00
05:00
06:00
07:00
08:00
09:00
10:00
11:00
12:00
13:00
14:00
15:00
16:00
17:00
18:00
19:00
20:00
21:00
22:00
23:00
Code
// Lazy in-house RRULE expander — FREQ + INTERVAL + COUNT + UNTIL + BYDAY.
const events: Event[] = [
  {
    id: 'standup',
    title: 'Daily standup',
    start: new Date(2026, 4, 11, 9, 30),
    end:   new Date(2026, 4, 11, 9, 45),
    rrule: 'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;COUNT=20',
    exceptions: [new Date(2026, 4, 13)], // skip team off-site
  },
  {
    id: 'coffee',
    title: 'Coffee with Ada',
    start: new Date(2026, 4, 12, 8, 30),
    end:   new Date(2026, 4, 12, 9, 0),
    rrule: 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU;COUNT=5',
  },
];

<Calendar
  events={events}
  view="week"
  defaultDate={new Date(2026, 4, 13)}
  slotMinutes={15}
  workingHours={{ start: 9, end: 18, days: [1, 2, 3, 4, 5] }}
/>

Interactive — drag, resize, popover

Preview

10 – 16 May 2026

Sun
10
Mon
11
Tue
12
Wed
13
Thu
14
Fri
15
Sat
16
All-day
01:00
02:00
03:00
04:00
05:00
06:00
07:00
08:00
09:00
10:00
11:00
12:00
13:00
14:00
15:00
16:00
17:00
18:00
19:00
20:00
21:00
22:00
23:00
Code
<Calendar
  events={events}
  view="week"
  defaultDate={new Date(2026, 4, 13)}
  slotMinutes={30}
  workingHours={{ start: 9, end: 18, days: [1, 2, 3, 4, 5] }}
  onEventCreate={({ start, end }) =>
    setEvents((prev) => [...prev, { id: `n${Date.now()}`, title: 'New event', start, end }])
  }
  onEventUpdate={(updated) =>
    setEvents((prev) => prev.map((e) => (e.id === updated.id ? updated : e)))
  }
  onEventDelete={(id) =>
    setEvents((prev) => prev.filter((e) => e.id !== id))
  }
/>

Resource view — rooms with conflict highlight

Preview

May 2026

13
Studio A
Studio B
Boardroom
01:00
02:00
03:00
04:00
05:00
06:00
07:00
08:00
09:00
10:00
11:00
12:00
13:00
14:00
15:00
16:00
17:00
18:00
19:00
20:00
21:00
22:00
23:00
Code
const resources = [
  { id: 'room-a', name: 'Studio A',  color: 'primary' },
  { id: 'room-b', name: 'Studio B',  color: 'success' },
  { id: 'room-c', name: 'Boardroom', color: 'warning' },
];
const events = [
  { id: 'r1', title: 'Sprint planning',
    start: new Date(2026, 4, 13,  9, 0), end: new Date(2026, 4, 13, 11, 0),
    resourceId: 'room-a' },
  { id: 'r2', title: 'Design crit',  // overlaps r1 → ring-error
    start: new Date(2026, 4, 13, 10, 30), end: new Date(2026, 4, 13, 12, 0),
    resourceId: 'room-a' },
  // …
];

<Calendar
  events={events}
  view="resource"
  defaultDate={new Date(2026, 4, 13)}
  resources={resources}
  slotMinutes={15}
  workingHours={{ start: 9, end: 18, days: [1, 2, 3, 4, 5] }}
/>

Multi-calendar overlay — toggle visibility

Preview

10 – 16 May 2026

Sun
10
Mon
11
Tue
12
Wed
13
Thu
14
Fri
15
Sat
16
01:00
02:00
03:00
04:00
05:00
06:00
07:00
08:00
09:00
10:00
11:00
12:00
13:00
14:00
15:00
16:00
17:00
18:00
19:00
20:00
21:00
22:00
23:00
Code
const calendars = [
  { id: 'work',     name: 'Work',     color: 'primary' },
  { id: 'personal', name: 'Personal', color: 'success' },
  { id: 'family',   name: 'Family',   color: 'warning' },
];
const events = [
  { id: 'm1', title: 'Design sync',     start: ..., end: ..., calendarId: 'work' },
  { id: 'm2', title: 'Yoga',            start: ..., end: ..., calendarId: 'personal' },
  { id: 'm3', title: 'Dinner — parents', start: ..., end: ..., calendarId: 'family' },
];

<Calendar
  events={events}
  view="week"
  calendars={calendars}
  onCalendarToggle={(id, visible) => console.log(id, visible)}
/>

Agenda view — date-grouped + search

Preview

May 2026

11MondayMay 2026
12TuesdayMay 2026
13WednesdayMay 2026
14ThursdayMay 2026
15FridayMay 2026
Code
<Calendar
  events={events}
  view="agenda"
  defaultDate={new Date(2026, 4, 13)}
  onViewChange={setView}
  locale="en"
/>

MiniCalendar sidebar — jumps the main view to picked date

Preview
May 2026
S
M
T
W
T
F
S

10 – 16 May 2026

Sun
10
Mon
11
Tue
12
Wed
13
Thu
14
Fri
15
Sat
16
All-day
01:00
02:00
03:00
04:00
05:00
06:00
07:00
08:00
09:00
10:00
11:00
12:00
13:00
14:00
15:00
16:00
17:00
18:00
19:00
20:00
21:00
22:00
23:00
Code
import { Calendar, MiniCalendar } from '@/modules/app/Calendar';

const [date, setDate] = useState(new Date(2026, 4, 13));
const [view, setView] = useState<View>('week');

<div className="grid grid-cols-[15rem_1fr] gap-3">
  <MiniCalendar
    value={date}
    onChange={(d) => { setDate(d); setView('day'); }}
    locale="en"
  />
  <Calendar
    events={events}
    view={view}
    defaultDate={date}
    onViewChange={setView}
    onDateChange={setDate}
  />
</div>
Sourcemodules/app/Calendar/index.tsx