Event Calendar
alphav0.4.0Editable event calendar with month, week, day, and agenda views — multi-day spans, drag and resize editing, clipboard support, and keyboard navigation.
Context
Event Calendar is the fifth surface onto the canonical TaskItem the rest of the task family renders: task-card (list), task-tree (outline), kanban-board (board), gantt-timeline (continuous timeline), and now event-calendar (date grid). A product's 'Calendar' tab is literally the same task data its other tabs show, with no adapter. v0.2 adds the editing layer additively over v0.1's display surface, reusing gantt's controlled-echo vocabulary + the shared TaskPermissions matrix; editable defaults off so consumers opt in. Because every task surface speaks the same TaskItem, copy/paste rides a shared 'ilinxa/task' clipboard envelope — a task copied in the calendar pastes into gantt/kanban/tree and back. Compound structure: EventCalendarRoot (headless provider) + flat parts (Toolbar / MonthView / WeekView / DayView / AgendaView / MiniNav / Inspector / QuickComposer / ContextMenu / edit overlays) + Tier-C primitives (CalendarEventChip / CalendarEventBar / CalendarTimeBlock / MonthDayCell / TimeGrid / TimeGutter / NowIndicator / AgendaRow / EventTooltip / EventEditorPanel / CalendarSkeleton) + the EventCalendar assembly. Each view is its own module so a month-only consumer never pulls the week/day time-grid code; the full-card tooltip + detail editor lazy-load task-card, so the default lightweight tooltip keeps it out of the bundle.
Installation
pnpm dlx shadcn@latest init"registries": {
"@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}pnpm dlx shadcn@latest add @ilinxa/event-calendarAdd -fixtures for dummy data:
pnpm dlx shadcn@latest add @ilinxa/event-calendar-fixturespnpm dlx shadcn@latest add @ilinxa/event-calendar-editingDrag/resize editing, quick-compose, clipboard, keyboard mutations, and permission-gated actions.
Preview
Demo source
Usage
When to use
Reach for EventCalendar when you already hold the canonical TaskItem[] (the data behind task-card, task-tree, kanban-board, and gantt-timeline) and want a date-grid surface — month, week, day, or agenda — with no adapter. It is the read-only display sibling of the gantt; editing lands in v0.2.
Basic example
import { EventCalendar } from "@/components/event-calendar"
export function Example({ tasks }) {
return (
<EventCalendar
data={tasks} // TaskItem[]
statusOptions={statusOptions}
defaultView="month" // "month" | "week" | "day" | "agenda"
now={serverNow} // SSR-stable "now"
showMiniNav
onTaskClick={(item) => openDetail(item)}
onRangeChange={({ start, end }) => fetchWindow(start, end)}
/>
)
}Editing (opt-in feature)
Editing (drag/resize/create, clipboard, keyboard mutations, permission-gated actions) ships as a separate feature slice — installing the base package alone keeps it out of your bundle entirely. Install @ilinxa/event-calendar-editing, import calendarEditing, and pass it alongside editable.
import { EventCalendar } from "@/components/event-calendar"
import { calendarEditing } from "@/components/event-calendar/features/editing"
<EventCalendar
editable
editing={calendarEditing} // wires drag/resize/create/clipboard/keyboard
data={tasks}
onChange={setTasks} // controlled — echo the mutated forest back
statusOptions={statusOptions}
permissions={permissions} // optional — the shared TaskPermissions matrix
/>editable without editing wired stays fully read-only (byte-identical to a base-only install) — the calendar logs one console.warn in development and never throws.
Lighter (hand-assembled subset)
import {
EventCalendarRoot, CalendarToolbar, CalendarMonthView,
} from "@/components/event-calendar"
// month-only — the week/day time-grid code never enters your bundle
<EventCalendarRoot data={tasks} statusOptions={statusOptions} views={["month"]}>
<CalendarToolbar />
<CalendarMonthView />
</EventCalendarRoot>All-day vs timed
- A
classifyEvent(item)predicate wins when it returns a kind. - Otherwise a date-only string (
"2026-06-22", noT) is an all-day event (parsed floating-local — no timezone off-by-one); a full timestamp is timed. - With no end and a full timestamp, the item is a milestone (a marker / dot).
Notes
- Fully controlled — no internal data state. Pass
nowfor an SSR-stable first paint. - Keyboard:
M/W/D/Aswitch views,←/→+PageUp/PageDownstep the period,Tjumps to today. - The default hover tooltip is a native title; pass
renderTooltip(e.g.CalendarFullCardTooltip) for a rich card.
Features
- Four views: Month (date cells with multi-day spanning bars + chips + '+N more' overflow), Week + Day (hour time-grids: all-day band + lane-packed timed blocks + now-line), Agenda (day-grouped chronological list)
- Consumes the canonical TaskItem[] directly — same data as task-card / task-tree / kanban-board / gantt-timeline; no adapter
- Opt-in editing (editable, default off → read-only): drag-to-reschedule + edge-resize + draw/double-click create + quick-composer + right-click menu (Edit/Rename/Status/Priority/Copy/Cut/Delete) + selected-event inspector + modal detail editor + inline rename; controlled-echo events + onChange, no internal data state, gated by the shared TaskPermissions matrix
- Full keyboard editing: M/W/D/A switch views, ←/→ step the period, T today; focus an event → ←/→ move, Shift+←/→ (+↑/↓ in the time grid) resize, Enter edit, F2 rename, Delete remove; focus a day → Enter quick-create
- Cross-surface copy / cut / paste: events copy as a portable TaskItem envelope through the OS clipboard (⌘/Ctrl+C·X·V), so a task copied here pastes into gantt / kanban / tree — paste-target decides all-day vs timed
- All-day / timed / milestone derived via a three-layer rule: consumer classifyEvent predicate → date-only strings (parsed as floating-local, no TZ off-by-one) → span heuristic; all-day⇄timed conversion via paste-target or drag onto the all-day band
- Status-driven event color (statusColors + colorBy, default 'status'; colorBy='urgency' restores the v0.1 deadline ramp) imported from task-card; per-item borderColor override; high-priority Flag; overdue + inactive treatments
- Cursor (view + focus date) controlled OR uncontrolled; period nav, view switch, optional jump-to-date mini-nav (shadcn calendar), onRangeChange for lazy windowed fetch; height-responsive month overflow (maxEventsPerCell overrides)
- SSR-safe first paint (now prop seeds; client interval refreshes); finite-date guards (unparseable dates render label-only, never throw); all-day floating-local round-trip (no off-by-one)
- Compound: EventCalendarRoot + flat view parts + edit overlays + Tier-C primitives + the EventCalendar assembly; each view its own module (tree-shakeable); a month-only subset drops the time-grid code; the detail editor lazy-loads task-card