diff --git a/apps/playground/src/data/examples.ts b/apps/playground/src/data/examples.ts
index 1af817fab..9f2e04ff2 100644
--- a/apps/playground/src/data/examples.ts
+++ b/apps/playground/src/data/examples.ts
@@ -638,6 +638,93 @@ export const examples = {
]
}
]
+}`,
+
+ // Calendar View - Airtable-style calendar
+ 'calendar-view': `{
+ "type": "div",
+ "className": "space-y-4",
+ "body": [
+ {
+ "type": "div",
+ "className": "space-y-2",
+ "body": [
+ {
+ "type": "text",
+ "content": "Calendar View",
+ "className": "text-2xl font-bold"
+ },
+ {
+ "type": "text",
+ "content": "Airtable-style calendar for displaying records as events",
+ "className": "text-muted-foreground"
+ }
+ ]
+ },
+ {
+ "type": "calendar-view",
+ "className": "h-[600px] border rounded-lg",
+ "view": "month",
+ "titleField": "title",
+ "startDateField": "start",
+ "endDateField": "end",
+ "colorField": "type",
+ "colorMapping": {
+ "meeting": "#3b82f6",
+ "deadline": "#ef4444",
+ "event": "#10b981",
+ "holiday": "#8b5cf6"
+ },
+ "data": [
+ {
+ "id": 1,
+ "title": "Team Standup",
+ "start": "${new Date(new Date().setHours(9, 0, 0, 0)).toISOString()}",
+ "end": "${new Date(new Date().setHours(9, 30, 0, 0)).toISOString()}",
+ "type": "meeting",
+ "allDay": false
+ },
+ {
+ "id": 2,
+ "title": "Project Launch",
+ "start": "${new Date(new Date().setDate(new Date().getDate() + 3)).toISOString()}",
+ "type": "deadline",
+ "allDay": true
+ },
+ {
+ "id": 3,
+ "title": "Client Meeting",
+ "start": "${new Date(new Date().setDate(new Date().getDate() + 5)).toISOString()}",
+ "end": "${new Date(new Date(new Date().setDate(new Date().getDate() + 5)).setHours(14, 0, 0, 0)).toISOString()}",
+ "type": "meeting",
+ "allDay": false
+ },
+ {
+ "id": 4,
+ "title": "Team Building Event",
+ "start": "${new Date(new Date().setDate(new Date().getDate() + 7)).toISOString()}",
+ "end": "${new Date(new Date().setDate(new Date().getDate() + 9)).toISOString()}",
+ "type": "event",
+ "allDay": true
+ },
+ {
+ "id": 5,
+ "title": "Code Review",
+ "start": "${new Date(new Date().setDate(new Date().getDate() + 1)).toISOString()}",
+ "end": "${new Date(new Date(new Date().setDate(new Date().getDate() + 1)).setHours(15, 0, 0, 0)).toISOString()}",
+ "type": "meeting",
+ "allDay": false
+ },
+ {
+ "id": 6,
+ "title": "National Holiday",
+ "start": "${new Date(new Date().setDate(new Date().getDate() + 10)).toISOString()}",
+ "type": "holiday",
+ "allDay": true
+ }
+ ]
+ }
+ ]
}`
};
@@ -646,5 +733,6 @@ export type ExampleKey = keyof typeof examples;
export const exampleCategories = {
'Primitives': ['simple-page', 'input-states', 'button-variants'],
'Layouts': ['grid-layout', 'dashboard', 'tabs-demo'],
- 'Forms': ['form-demo']
+ 'Forms': ['form-demo'],
+ 'Data Display': ['calendar-view']
};
diff --git a/docs/components/calendar-view.md b/docs/components/calendar-view.md
new file mode 100644
index 000000000..05b491a7e
--- /dev/null
+++ b/docs/components/calendar-view.md
@@ -0,0 +1,121 @@
+# Calendar View Component
+
+The `calendar-view` component is an Airtable-style calendar for displaying records as events. It provides three view modes: Month, Week, and Day.
+
+## Features
+
+- **Multiple View Modes**: Switch between Month, Week, and Day views
+- **Flexible Data Mapping**: Map your data fields to event properties
+- **Color Coding**: Support for color-coded events with custom color mappings
+- **Interactive**: Click on events and dates (with callbacks)
+- **Responsive**: Works seamlessly on different screen sizes
+
+## Basic Usage
+
+```json
+{
+ "type": "calendar-view",
+ "data": [
+ {
+ "id": 1,
+ "title": "Team Meeting",
+ "start": "2026-01-13T10:00:00.000Z",
+ "end": "2026-01-13T11:00:00.000Z",
+ "color": "#3b82f6"
+ }
+ ]
+}
+```
+
+## Properties
+
+| Property | Type | Default | Description |
+|:---|:---|:---|:---|
+| `data` | `array` | `[]` | Array of record objects to display as events |
+| `view` | `'month' \| 'week' \| 'day'` | `'month'` | Default view mode |
+| `titleField` | `string` | `'title'` | Field name to use for event title |
+| `startDateField` | `string` | `'start'` | Field name for event start date |
+| `endDateField` | `string` | `'end'` | Field name for event end date (optional) |
+| `allDayField` | `string` | `'allDay'` | Field name for all-day flag |
+| `colorField` | `string` | `'color'` | Field name for event color |
+| `colorMapping` | `object` | `{}` | Map field values to colors |
+| `allowCreate` | `boolean` | `false` | Allow creating events by clicking on dates |
+| `className` | `string` | - | Additional CSS classes |
+
+## Data Structure
+
+Each event object in the `data` array should have the following structure:
+
+```typescript
+{
+ id: string | number; // Unique identifier
+ title: string; // Event title (or use custom titleField)
+ start: string | Date; // Start date/time (ISO string or Date)
+ end?: string | Date; // End date/time (optional)
+ allDay?: boolean; // Whether it's an all-day event
+ color?: string; // Event color (hex or CSS color)
+ [key: string]: any; // Any other custom data
+}
+```
+
+## Examples
+
+### Month View with Color Mapping
+
+```json
+{
+ "type": "calendar-view",
+ "className": "h-[600px] border rounded-lg",
+ "view": "month",
+ "colorField": "type",
+ "colorMapping": {
+ "meeting": "#3b82f6",
+ "deadline": "#ef4444",
+ "event": "#10b981"
+ },
+ "data": [
+ {
+ "id": 1,
+ "title": "Team Standup",
+ "start": "2026-01-13T09:00:00.000Z",
+ "end": "2026-01-13T09:30:00.000Z",
+ "type": "meeting"
+ },
+ {
+ "id": 2,
+ "title": "Project Deadline",
+ "start": "2026-01-20T00:00:00.000Z",
+ "type": "deadline",
+ "allDay": true
+ }
+ ]
+}
+```
+
+## View Modes
+
+### Month View
+Displays a full month calendar grid with events shown as colored bars on their respective dates. Perfect for getting a high-level overview of the month.
+
+### Week View
+Shows a week at a time with each day in a column. Events display with their times, ideal for detailed weekly planning.
+
+### Day View
+Displays a single day with hourly time slots from 12 AM to 11 PM. Events are positioned at their scheduled times, great for detailed daily schedules.
+
+## Events & Callbacks
+
+The calendar view supports several event callbacks through the `onAction` mechanism:
+
+- `event_click`: Triggered when an event is clicked
+- `date_click`: Triggered when a date cell is clicked
+- `view_change`: Triggered when the view mode changes
+- `navigate`: Triggered when navigating between dates
+
+## Styling
+
+The calendar view is fully styled with Tailwind CSS and supports custom styling through the `className` prop.
+
+## Integration with ObjectQL
+
+When used with ObjectQL, the calendar view can automatically fetch and display records from your database.
diff --git a/docs/spec/component-library.md b/docs/spec/component-library.md
index 3f021e2e4..62e0ab692 100644
--- a/docs/spec/component-library.md
+++ b/docs/spec/component-library.md
@@ -68,6 +68,7 @@ Components for visualizing data.
| `alert` | Highlighted message | `variant`, `title`, `description` |
| `table` | Data-driven table | `columns`, `data`, `caption`, `footer` |
| `carousel` | Slideshow component | `items`, `orientation`, `showArrows` |
+| `calendar-view` | Airtable-style calendar | `data`, `view`, `titleField`, `startDateField`, `endDateField`, `colorField` |
## 6. Feedback Components
Indicators for system status or feedback.
diff --git a/packages/components/src/renderers/complex/calendar-view.tsx b/packages/components/src/renderers/complex/calendar-view.tsx
new file mode 100644
index 000000000..b620c9956
--- /dev/null
+++ b/packages/components/src/renderers/complex/calendar-view.tsx
@@ -0,0 +1,218 @@
+import { ComponentRegistry } from '@object-ui/core';
+import { CalendarView, type CalendarEvent } from '@/ui';
+import React from 'react';
+
+// Calendar View Renderer - Airtable-style calendar for displaying records as events
+ComponentRegistry.register('calendar-view',
+ ({ schema, className, onAction, ...props }) => {
+ // Transform schema data to CalendarEvent format
+ const events: CalendarEvent[] = React.useMemo(() => {
+ if (!schema.data || !Array.isArray(schema.data)) return [];
+
+ return schema.data.map((record: any, index: number) => {
+ /** Field name to use for event title display */
+ const titleField = schema.titleField || 'title';
+ /** Field name containing the event start date/time */
+ const startField = schema.startDateField || 'start';
+ /** Field name containing the event end date/time (optional) */
+ const endField = schema.endDateField || 'end';
+ /** Field name to determine event color or color category */
+ const colorField = schema.colorField || 'color';
+ /** Field name indicating if event is all-day */
+ const allDayField = schema.allDayField || 'allDay';
+
+ const title = record[titleField] || 'Untitled';
+ const start = record[startField] ? new Date(record[startField]) : new Date();
+ const end = record[endField] ? new Date(record[endField]) : undefined;
+ const allDay = record[allDayField] !== undefined ? record[allDayField] : false;
+
+ // Handle color mapping
+ let color = record[colorField];
+ if (color && schema.colorMapping && schema.colorMapping[color]) {
+ color = schema.colorMapping[color];
+ }
+
+ return {
+ id: record.id || record._id || index,
+ title,
+ start,
+ end,
+ allDay,
+ color,
+ data: record,
+ };
+ });
+ }, [schema.data, schema.titleField, schema.startDateField, schema.endDateField, schema.colorField, schema.allDayField, schema.colorMapping]);
+
+ const handleEventClick = React.useCallback((event: CalendarEvent) => {
+ if (onAction) {
+ onAction({
+ type: 'event_click',
+ payload: { event: event.data, eventId: event.id }
+ });
+ }
+ if (schema.onEventClick) {
+ schema.onEventClick(event.data);
+ }
+ }, [onAction, schema]);
+
+ const handleDateClick = React.useCallback((date: Date) => {
+ if (onAction) {
+ onAction({
+ type: 'date_click',
+ payload: { date }
+ });
+ }
+ if (schema.onDateClick) {
+ schema.onDateClick(date);
+ }
+ }, [onAction, schema]);
+
+ const handleViewChange = React.useCallback((view: "month" | "week" | "day") => {
+ if (onAction) {
+ onAction({
+ type: 'view_change',
+ payload: { view }
+ });
+ }
+ if (schema.onViewChange) {
+ schema.onViewChange(view);
+ }
+ }, [onAction, schema]);
+
+ const handleNavigate = React.useCallback((date: Date) => {
+ if (onAction) {
+ onAction({
+ type: 'navigate',
+ payload: { date }
+ });
+ }
+ if (schema.onNavigate) {
+ schema.onNavigate(date);
+ }
+ }, [onAction, schema]);
+
+ return (
+