# trud-calendar — Full reference > The most complete open-source React calendar component library. This file gives an LLM a self-contained mental model of the API in one read. ## Packages - `trud-calendar-core` (zero deps) — pure functions, types, constants - `trud-calendar` (react 18+) — components, hooks, contexts Both at version 0.4.0. Same major/minor cadence. ## Core concepts ### Date format All dates are **ISO 8601 strings** — never `Date` objects. ```ts type DateString = `${number}-${number}-${number}`; // "2026-03-13" type DateTimeString = `${DateString}T${string}`; // "2026-03-13T09:00:00" ``` ### CalendarEvent ```ts type CalendarEvent = { id: string; title: string; start: DateTimeString; end: DateTimeString; allDay?: boolean; color?: string; resourceId?: string; display?: "auto" | "background"; rrule?: string | RecurrenceRule; exDates?: DateString[]; recurringEventId?: string; originalDate?: DateString; // any other consumer-defined props pass through }; ``` ### Resource Timeline Optional horizontal scheduling view: `view="timeline"`. Resources render as horizontal rows with a sticky left column, hours on a sticky top header, and events as draggable bars. Drag horizontally to change time within the same resource; drag vertically to reassign across resources (`extra.resourceId` set on `onEventDrop` only when changed). Resize from the right edge to change end time. `onSlotClick` fires with `extra.resourceId`. Empty state when no resources configured. CSS variables: `--trc-resource-col` (160px), `--trc-timeline-row` (56px), `--trc-now-line` (#ef4444). Single-day scale only in v1.0; multi-day timeline scales (week/month) and left-edge resize are deferred. The vertical [Resource Views](/resource-views/) layout is still the default when `resources` is provided without `view="timeline"`. Core util: `computeTimelinePositions(events, resourceIds, day, dayStartHour, dayEndHour)` returns `Map` with `leftPct`, `widthPct`, `row`, `totalRows`, `isSegmentStart`, `isSegmentEnd`. Available standalone for custom timeline implementations. ### Timezones Optional `timeZone` (IANA) on `CalendarEvent` anchors it to a zone with RFC 5545 (TZID) semantics. Floating events (no zone) display literally. Calendar accepts `displayTimeZone` (defaults to runtime's local zone via `Intl.DateTimeFormat().resolvedOptions().timeZone`); event times are converted from `event.timeZone` → `displayTimeZone` for rendering. Drag/resize on anchored events report wall-clocks in the event's anchored zone, not the display zone (Google Calendar semantics). DST handled: gaps shift forward by default (opt-in throw), overlaps return the earlier instant by default (opt-in later). Recurring events anchored to a zone preserve their wall-clock across DST transitions. Core APIs (zero deps, built on `Intl.DateTimeFormat` with cached DTF instances): - `getTimeZoneOffset(utc, tz)` → minutes east of UTC - `wallTimeToUtc(wall, tz, opts?)` → UTC ISO with Z; `opts.invalid: "shift" | "throw"`, `opts.ambiguous: "earlier" | "later"` - `utcToWallTime(utc, tz)` → wall-clock string in tz - `convertWallTime(wall, fromTz, toTz, opts?)` → wall-clock in toTz - `listTimeZones()` → `Intl.supportedValuesOf("timeZone")` + always-included `"UTC"`, with curated 80-zone fallback - `isValidTimeZone(tz)` → boolean (cached) - `getTimeZoneAbbreviation(tz, atInstant?)` → `"EST"`, `"EDT"`, `"GMT+5:30"`, etc. - `getBrowserTimeZone()` → runtime's local zone with UTC fallback - `eventWallToDisplay(wall, eventTz, displayTz)` / `displayWallToEvent(wall, eventTz, displayTz, opts?)` — used internally by the React layer; floating events pass through unchanged ### Recurrence RFC 5545 RRULE semantics. The calendar does NOT auto-expand. Consumers must call: ```ts import { expandRecurringEvents } from "trud-calendar-core"; const expanded = expandRecurringEvents(events, rangeStart, rangeEnd); ``` Generated instances carry `recurringEventId` and `originalDate`. ## Quick start ```tsx import { Calendar } from "trud-calendar"; import "trud-calendar/styles.css"; save(event.id, newStart, newEnd)} onEventResize={(event, newStart, newEnd) => save(event.id, newStart, newEnd)} onSlotClick={(start, end) => create(start, end)} /> ``` ## Calendar component — main props ```ts type CalendarProps = { // data events: CalendarEvent[]; resources?: Resource[]; // view + navigation view?: CalendarView; // "month" | "week" | "day" | "agenda" | "year" | "timeline" defaultView?: CalendarView; date?: DateString; defaultDate?: DateString; validRange?: { start?: DateString; end?: DateString }; visibleDays?: 1 | 3 | 7; hiddenDays?: number[]; // 0 = Sunday weekStartsOn?: number; showWeekNumbers?: boolean; highlightedDates?: DateString[]; // time-grid config dayStartHour?: number; dayEndHour?: number; flexibleSlotTimeLimits?: boolean; slotClickTime?: string; // e.g. "09:00:00" snapDuration?: 5 | 10 | 15 | 30 | 60; // interactions enableDnD?: boolean; longPressDelay?: number; dragConstraint?: (event, newStart, newEnd) => boolean; resizeConstraint?: (event, newStart, newEnd) => boolean; selectConstraint?: (start, end) => boolean; // handlers onEventClick?: (event) => void; onEventDrop?: (event, newStart, newEnd, extra?) => void; onEventResize?: (event, newStart, newEnd) => void; onSlotClick?: (start, end, extra?) => void; onSlotSelect?: (start, end, extra?) => void; onViewChange?: (view) => void; onDateChange?: (date) => void; // i18n locale?: string; // BCP 47 ("en-US", "es-ES", "ja-JP", ...) labels?: Partial; // customization slots?: CalendarSlots; // override toolbar, dayCell, timeEvent, allDayEvent, popover, agendaEvent, resourceHeader customButtons?: CustomButton[]; // misc className?: string; }; ``` ## Slots (custom rendering) ```ts type CalendarSlots = { toolbar?: ComponentType; dayCell?: ComponentType; timeEvent?: ComponentType; allDayEvent?: ComponentType; popover?: ComponentType; agendaEvent?: ComponentType; resourceHeader?: ComponentType; }; ``` `ToolbarSlotProps` includes: `view`, `date`, `setView`, `setDate`, `goToPrev`, `goToNext`, `goToToday`, `canGoPrev`, `canGoNext`, `customButtons`, `labels`, `locale`. ## React hooks | Hook | Purpose | |---|---| | `useCalendar` | Top-level calendar state + actions | | `useNavigation` | Prev/next/today, view switching, validRange | | `useEvents` | Filter, sort, position events for current range | | `useEventLayout` | Column-pack overlapping events | | `useCurrentTime` | Now-line position (auto-updates) | | `useDateFormat` | Locale-aware Intl formatting | | `useEventResize` | Pointer-based resize (top + bottom edges) | | `useEventDrag` | Pointer-based drag with snap, constraints, auto-scroll | | `useSlotSelection` | Drag-to-create slot selection | | `useGridKeyboard` | WAI-ARIA grid roving tabindex | | `useSwipeNavigation` | Touch swipe between periods | | `useResponsiveView` | Auto-switch view by container width | | `useEventSelection` | Selected event state | | `useVirtualScroll` | Opt-in virtualization for huge ranges | | `useAutoScroll` | Edge auto-scroll during drag | | `useEventSources` | Fetch events by visible range, with caching + isLoading | | `useExternalDrag` | HTML5 drag from outside onto slots | | `useUndoableEvents` | Built-in undo/redo stack | ## Headless core utilities Available in `trud-calendar-core` with **zero dependencies**: - **Date:** `parseDate`, `addDays`, `addWeeks`, `addMonths`, `startOfWeek`, `startOfMonth`, `endOfMonth`, `isSameDay`, `isToday`, `rangesOverlap`, `eachDayOfRange`, `getMonthViewRange`, `getWeekViewRange`, `getISOWeekNumber`, `filterHiddenDays` - **Format:** `formatToolbarTitle`, `formatTime`, `formatTimeRange`, `formatAgendaDate`, `formatMonthDay`, `formatWeekdayShort` - **Events:** `sortEvents`, `filterEventsInRange`, `getEventsForDay`, `isMultiDayEvent`, `partitionEvents`, `segmentMultiDayEvent`, `buildOverlapGroups`, `assignColumns`, `computeTimePositions` - **Time:** `snapToIncrement`, `fractionalHourToDateTime`, `yPositionToFractionalHour`, `computeDropPosition` - **Recurrence:** `generateOccurrences`, `expandRecurringEvents`, `parseRRule`, `toRRuleString` - **Resources:** `flattenResources`, `getEventsForResource`, `groupEventsByResource` - **Undo:** `createUndoStack`, `pushState`, `undo`, `redo`, `canUndo`, `canRedo` - **iCal:** `eventsToICal`, `downloadICal` - **Virtualization:** `filterVisibleEvents`, `scrollToViewportRange` - **State:** `calendarReducer`, `createInitialState` ## Styling - Tailwind v4: `@source "../node_modules/trud-calendar/dist"; @import "trud-calendar/styles.css";` - Without Tailwind: `@import "trud-calendar/styles.css";` - All CSS vars use `--trc-` prefix - Auto-inherits shadcn/ui CSS variables when present - Dark mode via `.dark` class on parent - RTL auto-detected from `dir="rtl"` on parent - `@media print` styles built-in ## Architecture rules (for contributors) - All dates are ISO 8601 strings — never `Date` objects (except in core parsing utilities) - Core package has zero dependencies — no React, no lodash - All core functions are pure — no side effects - All interactions use Pointer Events API (never MouseEvent or HTML5 Drag API for events) - `touch-action: none` on interactive elements - 5px movement threshold to distinguish click from drag - TypeScript strict mode, no `any` - Conventional Commits: `feat:`, `fix:`, `docs:`, `test:`, `chore:` ## License MIT. Repository: https://github.com/trudapp/trud-calendar