Plugin Calendar
Calendar view components for ObjectUI - includes both ObjectQL-integrated and standalone calendar components.
Installation
npm install @object-ui/plugin-calendarThe props an object-calendar node reads on a page are generated from the spec into the component reference; this page covers using the plugin from React.
Overview
The @object-ui/plugin-calendar plugin provides two calendar components:
- ObjectCalendar (
object-calendar): For ObjectQL data sources - displays database records as calendar events - CalendarView (
calendar-view): Standalone calendar component for displaying pre-loaded event data
Both components support month, week, and day views with full event management capabilities.
Every week starts on the first day of the week of the calendar's locale (the locale prop, else the user's display locale): Sunday under en-US, Monday under en-GB or zh-CN. The month grid, its weekday heads, the week view's columns, the header's week range and the date popover all start on that day. There is no separate week-start setting.
Drag-and-Drop Rescheduling
Month view
- Move an event — grab any cell of the event pill and drop it on another day. The calendar days from the grabbed day to the drop day move both
startDateFieldandendDateField, so dragging a multi-day span from any of its days behaves intuitively. Each value keeps its time of day: a 10:00 event stays at 10:00, even across a DST change. - Resize an event — grab the small right-edge handle on the last day of a multi-day pill and drop it on a different day to extend or shrink the end date. The end keeps its time of day, and the start date is preserved; drops earlier than the start are ignored.
Week / Day view (time grid)
Week and day views render a classic Google Calendar-style vertical time
grid. All gestures use pointer events and snap to slotMinutes (default
30):
- Move an event — drag the event body vertically to change its start time (and end, by the same delta). In week view, drag horizontally to also change the day.
- Resize start — drag the top edge of an event to adjust only the start time. Refuses to cross the existing end.
- Resize end — drag the bottom edge to adjust only the end time. Refuses to cross the existing start.
- Drag-to-create — click-drag on an empty area of the time grid to open the quick-create dialog with start/end pre-filled to the dragged time range.
Pass slotMinutes={15} to change the snap granularity, or
onTimeRangeSelect={(start, end) => …} to override the drag-to-create
default.
When ObjectCalendar is bound to an object (i.e. it has objectName and a dataSource), the new dates are persisted automatically via dataSource.update() — local state is updated optimistically and rolled back on failure. To intercept (e.g. for a confirm dialog) pass an onEventDrop prop; supplying your own handler disables the default persistence.
The default persistence is offered only to a caller who may make the write: events
are draggable when the calendarReschedule row of the affordance-to-grant map
(@object-ui/core) allows it — the object's policy, the effective API operation
set and the caller's update grant. Without it the events stay where they are.
With no permission provider mounted it reads open.
<ObjectCalendar
schema={{ type: 'object-calendar', objectName: 'campaign' }}
// optional — omit to get default backend persistence
onEventDrop={(record, newStart, newEnd) => {
if (confirm(`Move ${record.name}?`)) save({ id: record.id, start_date: newStart, end_date: newEnd });
}}
/>Click-to-Create
Clicking the empty area of any day cell (month view) or click-dragging
in the week/day time grid opens a quick-create dialog pre-filled with
the selected date/range. Type a title and press Enter (or
click Create) to persist a new record via
dataSource.create(objectName, payload). The new record is inserted
optimistically into the calendar so it appears immediately.
The payload includes the configured titleField, startDateField,
optional endDateField (set to the clicked day), plus auto-defaults
for any other required fields not provided by the user (first picklist
option for select/status, false for booleans, 0 for numerics,
or the field's declared defaultValue). This ensures the create
succeeds against NOT NULL columns without forcing the user through
the full form.
When the object has a required field outside that set (a required lookup,
or a required text field other than the title), the dialog's create could
not succeed, so it is not offered. The same gesture opens the host's full
create form instead: the calendar calls onEdit with a new record (no id)
carrying the date values the dialog would have written, and the host
pre-fills its create form with them. The console does this already. Without
an onEdit, the gesture offers nothing on such an object. Required fields
the server fills on insert (readonly, system, autonumber) do not count.
<ObjectCalendar
schema={{ type: 'object-calendar', objectName: 'task', calendar: { startDateField: 'due_date' } }}
// Called as onEdit({ due_date: '2026-10-20' }) on an object with a required lookup.
onEdit={(record) => openCreateForm({ initialValues: record })}
/>Quick-create is offered only when the calendarQuickCreate row of the
affordance-to-grant map allows it (the caller's create grant, with the
object's policy and the effective API operation set): without it a day click
opens nothing and the time grid starts no range drag. With no permission
provider mounted it reads open.
To override (e.g. open your own multi-field create form), pass
onDateClick:
<ObjectCalendar
schema={{ type: 'object-calendar', objectName: 'campaign' }}
onDateClick={(day) => navigate(`/campaign/new?start=${day.toISOString()}`)}
/>CalendarView Component
Full-featured standalone calendar with month, week, and day views for displaying events and scheduling.
Example schemas
Month View
Month View Calendar
{
"type": "calendar-view",
"view": "month",
"currentDate": "2026-05-15T12:00:00.000Z",
"data": [
{
"id": 1,
"title": "Team Meeting",
"start": "2026-05-28T02:00:00.000Z",
"end": "2026-05-28T03:00:00.000Z",
"color": "#3b82f6",
"allDay": false
},
{
"id": 2,
"title": "Project Deadline",
"start": "2026-05-31T06:32:37.758Z",
"color": "#ef4444",
"allDay": true
},
{
"id": 3,
"title": "Conference",
"start": "2026-06-04T06:32:37.758Z",
"end": "2026-06-06T06:32:37.758Z",
"color": "#10b981",
"allDay": true
}
],
"titleField": "title",
"startDateField": "start",
"endDateField": "end",
"colorField": "color",
"allDayField": "allDay",
"className": "h-[600px] border rounded-lg"
}Week View
Week View Calendar
{
"type": "calendar-view",
"view": "week",
"currentDate": "2026-05-27T12:00:00.000Z",
"data": [
{
"id": 1,
"title": "Standup",
"start": "2026-05-28T01:00:00.000Z",
"end": "2026-05-28T01:30:00.000Z",
"color": "#3b82f6",
"allDay": false
},
{
"id": 2,
"title": "Sprint Planning",
"start": "2026-05-28T06:00:00.000Z",
"end": "2026-05-28T07:30:00.000Z",
"color": "#8b5cf6",
"allDay": false
},
{
"id": 3,
"title": "1:1 with Manager",
"start": "2026-05-29T03:00:00.000Z",
"end": "2026-05-29T03:30:00.000Z",
"color": "#10b981",
"allDay": false
},
{
"id": 4,
"title": "Team Lunch",
"start": "2026-05-30T04:00:00.000Z",
"end": "2026-05-30T05:00:00.000Z",
"color": "#f59e0b",
"allDay": false
}
],
"titleField": "title",
"startDateField": "start",
"endDateField": "end",
"colorField": "color",
"allDayField": "allDay",
"className": "h-[500px] border rounded-lg"
}CalendarView Usage
import '@object-ui/plugin-calendar'
import type { CalendarViewSchema } from '@object-ui/types'
const schema: CalendarViewSchema = {
type: 'calendar-view',
view: 'month',
data: [
{
id: 1,
title: 'Team Meeting',
start: '2024-01-15T10:00:00',
end: '2024-01-15T11:00:00',
color: '#3b82f6'
}
]
}CalendarView Schema API
{
type: 'calendar-view',
view?: 'month' | 'week' | 'day',
data?: Array<CalendarEventData>,
titleField?: string,
startDateField?: string,
endDateField?: string,
allDayField?: string,
colorField?: string,
currentDate?: string,
allowCreate?: boolean,
onEventClick?: (event: any) => void,
onDateClick?: (date: Date) => void,
onViewChange?: (view: string) => void,
onNavigate?: (date: Date) => void,
className?: string
}Allowing event creation
allowCreate: true adds the header's New event button. Clicking it
dispatches the standard create action — { type: 'create', payload: {} } — on
the same action channel every other calendar-view gesture uses, so the host
decides what a "create" means (open a form, navigate, call an API).
const schema = {
type: 'calendar-view',
data: events,
allowCreate: true,
}Only the boolean true turns the button on. Omitting the key, false, and any
non-boolean value all render the calendar without the button — the default.
A React host can supply the affordance directly instead, with or without
allowCreate, by passing its own onAddClick handler; an explicit handler
replaces the action dispatch rather than running alongside it.
ObjectCalendar Component
Calendar component designed for use with ObjectQL data sources.
Features
- ObjectQL Integration: Works seamlessly with object/value data providers
- Automatic Field Mapping: Maps database fields to calendar events
- Multiple View Modes: Month, week, and day calendar views
- Visible days for a host: Reports the days it draws, so a host that fetches its records can fetch only those days (see Visible days); on its own it fetches the whole filtered set, up to the platform row ceiling
- Event Interaction: Click handling for events and dates
- Color Coding: Support for event color customization
ObjectCalendar Usage
Basic Usage with ObjectQL
import '@object-ui/plugin-calendar'
import type { ObjectCalendarSchema } from '@object-ui/types'
const schema: ObjectCalendarSchema = {
type: 'object-calendar',
objectName: 'events', // Your ObjectQL object
calendar: {
startDateField: 'startDate',
endDateField: 'endDate',
titleField: 'title',
colorField: 'category'
}
}With Static Data
import type { ObjectCalendarSchema } from '@object-ui/types'
const schema: ObjectCalendarSchema = {
type: 'object-calendar',
staticData: [
{
id: 1,
title: 'Team Meeting',
startDate: '2024-01-15T10:00:00',
endDate: '2024-01-15T11:00:00',
category: 'meeting'
},
{
id: 2,
title: 'Project Deadline',
startDate: '2024-01-20',
category: 'deadline'
}
],
calendar: {
startDateField: 'startDate',
endDateField: 'endDate',
titleField: 'title',
colorField: 'category'
}
}Comparison: CalendarView vs ObjectCalendar
| Feature | CalendarView | ObjectCalendar |
|---|---|---|
| Schema Type | calendar-view | object-calendar |
| Data Source | Static arrays | ObjectQL (database) |
| Use Case | Pre-loaded event data | Dynamic data from backend |
| Field Mapping | Standard field names | Configurable field names |
| Data Loading | Manual via props | Automatic via ObjectQL |
| Best For | Static schedules | Database-driven apps |
When to use CalendarView:
- You have static or pre-loaded event data
- You're not using ObjectQL
- You need a simple, standalone calendar
When to use ObjectCalendar:
- You're using ObjectQL for data management
- Events come from a database or API
- You need automatic data fetching and filtering
Event Handling
A click handler is a function, so it is a prop of the ObjectCalendar
component a React host renders, never a key on the node:
ObjectCalendarSchema declares no onEventClick or onDateClick, and
authored JSON can never carry a function anyway.
Event Click
import { ObjectCalendar } from '@object-ui/plugin-calendar';
import type { DataSource, ObjectCalendarSchema } from '@object-ui/types';
const eventClickCalendar: ObjectCalendarSchema = {
type: 'object-calendar',
objectName: 'events',
calendar: {
startDateField: 'start',
titleField: 'title'
}
};
export function EventClickCalendar({ dataSource }: { dataSource: DataSource }) {
return (
<ObjectCalendar
schema={eventClickCalendar}
dataSource={dataSource}
onEventClick={(record) => {
console.log('Event clicked:', record);
// Open event details, or navigate to the event page
}}
/>
);
}Date Click
import { ObjectCalendar } from '@object-ui/plugin-calendar';
import type { DataSource, ObjectCalendarSchema } from '@object-ui/types';
const dateClickCalendar: ObjectCalendarSchema = {
type: 'object-calendar',
objectName: 'events',
calendar: {
startDateField: 'start',
titleField: 'title'
}
};
export function DateClickCalendar({ dataSource }: { dataSource: DataSource }) {
return (
<ObjectCalendar
schema={dateClickCalendar}
dataSource={dataSource}
onDateClick={(date) => {
console.log('Date clicked:', date);
// Create a new event on this date
}}
/>
);
}Visible days
A host that fetches the calendar's records for it, and hands them over as
data, can fetch only the days on screen. onVisibleRangeChange receives
{ start, end } once on mount and again whenever navigation or a view change
moves them: start is local midnight of the first day drawn and end local
midnight of the day after the last. In the month view the range covers the
whole six-week grid, including the neighbouring months' leading and trailing
days, under the locale's first day of the week; in the week view it is the
week, and in the day view the day. An unchanged range is not reported again.
A list view's calendar uses it to fetch the days it shows instead of one batch
of the object (objectui#12081).
Direct Component Usage
You can also import and use the components directly in React:
import { CalendarView, ObjectCalendar } from '@object-ui/plugin-calendar';
import type { DataSource, ObjectCalendarSchema } from '@object-ui/types';
// CalendarView - Standalone calendar
function MyCalendar() {
const events = [
{
id: 1,
title: 'Meeting',
start: new Date('2024-01-15T10:00:00'),
end: new Date('2024-01-15T11:00:00'),
color: '#3b82f6'
}
];
return (
<CalendarView
events={events}
view="month"
onEventClick={(event) => console.log(event)}
/>
);
}
// ObjectCalendar - ObjectQL-integrated
function MyObjectCalendar({ dataSource }: { dataSource: DataSource }) {
const schema: ObjectCalendarSchema = {
type: 'object-calendar',
objectName: 'events',
calendar: {
startDateField: 'startDate',
titleField: 'title'
}
};
return (
<ObjectCalendar
schema={schema}
dataSource={dataSource}
onEventClick={(record) => console.log(record)}
/>
);
}TypeScript Support
import type {
CalendarViewSchema,
ObjectCalendarSchema,
CalendarConfig
} from '@object-ui/types'
import type { CalendarViewEvent } from '@object-ui/plugin-calendar'
// CalendarView component events — the RUNTIME shape: `id: string | number`,
// `start` / `end` are real `Date` objects. `@object-ui/types` exports a
// separate `CalendarEvent`, the AUTHORING event (`id: string`, ISO strings,
// `end` required); the two are not interchangeable. Renamed in objectui#5044,
// which left `CalendarEvent` on this package as a `@deprecated` alias.
const events: CalendarViewEvent[] = [
{
id: 1,
title: 'Meeting',
start: new Date('2024-01-15T10:00:00'),
end: new Date('2024-01-15T11:00:00'),
color: '#3b82f6'
}
]
const calendarViewSchema: CalendarViewSchema = {
type: 'calendar-view',
view: 'month',
data: events
}
// ObjectCalendar types
const calendarConfig: CalendarConfig = {
startDateField: 'startDate',
endDateField: 'endDate',
titleField: 'title',
colorField: 'category'
}
const objectCalendarSchema: ObjectCalendarSchema = {
type: 'object-calendar',
objectName: 'events',
calendar: calendarConfig
}Migration from @object-ui/plugin-calendar-view
The @object-ui/plugin-calendar-view package has been merged into this package. If you were using it:
Before
npm install @object-ui/plugin-calendar-viewimport '@object-ui/plugin-calendar-view'
import { CalendarView } from '@object-ui/plugin-calendar-view'After
npm install @object-ui/plugin-calendarimport '@object-ui/plugin-calendar'
import { CalendarView } from '@object-ui/plugin-calendar'All functionality remains the same - just update your imports and package dependencies.