Calendar
Full calendar for React Native — month, week, day, agenda and year views plus year-timeline, quarter and 13-week period timelines, with drag & drop, resize, user / color filters, settings and event sheets.
Installation
pnpm dlx @docyrus/cli add @docyrus/rn-calendarpnpm add date-fns react-native-gesture-handler react-native-reanimated @shopify/flash-listThe native calendar mirrors the web Calendar API 1:1 (same IEvent model, same prop names, same ui.calendar.* translation keys) and adapts it to touch: popovers become bottom sheets, right-click / hover become long-press, HTML5 drag becomes a gesture-handler long-press drag with a reanimated ghost.
Expo Configuration
Month and weekday names use the device locale through the optional react-native-localize peer. Add its plugin to app.config.ts when you install it:
{
plugins: [
['react-native-localize', { locales: ['en', 'tr'] }]
]
}The app must be wrapped in GestureHandlerRootView (drag & drop, resize, taps).
Usage
import { Calendar, type IEvent } from '@/components/docyrus-native/calendar';
const events: IEvent[] = [
{
id: 1,
title: 'Team Meeting',
startDate: '2026-03-04T10:00:00.000Z',
endDate: '2026-03-04T11:00:00.000Z',
color: 'blue',
description: 'Weekly sync',
user: { id: 'u1', name: 'Ada Lovelace', picturePath: null }
}
];
<Calendar
events={events}
users={[{ id: 'u1', name: 'Ada Lovelace', picturePath: null }]}
defaultView="month"
weekStartsOn={1}
onEventCreate={event => api.create(event)}
onEventUpdate={event => api.update(event)}
onEventDelete={id => api.remove(id)}
onEventDrop={(event, start, end) => api.move(event.id, start, end)}
onVisibleRangeChange={(range, view) => refetch(range)}
/>Period timelines
<Calendar
events={projectPlans}
defaultView="year-timeline"
visibleViews={['year-timeline', 'quarter', '13weeks']}
defaultYearTimelineMode="continuous"
defaultQuarterMode="single"
timelineMaxLanes={4}
/>Refetch without resetting the view
const { data, isFetching, isLoading } = useQuery(/* … */);
<Calendar
events={data ?? []}
isLoading={isLoading} // first load → skeleton (unmounts)
isRefreshing={isFetching} // later fetches → overlay, keeps view / date / scroll
/>Controlled / persisted settings
// Persist the settings blob (badge style, 24h format, agenda grouping,
// timeline modes, last view) through lib/storage — mount a sync store with
// <DocyStorageProvider> (expo-sqlite kv-store or MMKV).
<Calendar events={events} persistSettings />
// Or own the settings yourself:
<Calendar
events={events}
settings={{ use24HourFormat: false }}
onSettingsChange={settings => save(settings)}
/>Custom create / open flows
<Calendar
events={events}
onCreateEvent={date => router.push({ pathname: '/events/new', params: { date: date.toISOString() } })}
onEventClick={event => router.push(`/events/${event.id}`)}
/>Views
| View | Description |
|---|---|
month | Month grid; multi-day bars keep their row across cells, +N opens a sheet with the day's events. Tap an empty cell to create. |
week | 7-day time grid (startHour–endHour), all-day / multi-day row, now-line, overlap packing. Tap a slot to create (15-minute precision). |
day | Single-day time grid with the same features as week. |
agenda | The month's events, searchable, grouped by date or by color (settings). |
year | Twelve mini months with event dots; tap a month → month view, tap a day → day view. |
year-timeline | Year → 4 quarter rows → month groups → week columns; bars span the weeks they cover. |
quarter | Quarter → 3 month rows → week groups → day columns. |
13weeks | A floating 13-week window (anchored at the selected week, ±91 days per step) with a weekday header and per-row date strip. |
Timeline views scroll horizontally on phones with a sticky row gutter. year-timeline and quarter support continuous (periods stacked, more load on scroll, capped at ±12) and single navigation; 13weeks is always single. A row shows timelineMaxLanes lanes and collapses the rest into +N more (only that row expands).
Interactions
| Gesture | Result |
|---|---|
| Tap an event | onEventClick / onEventPress, otherwise the built-in details sheet (title, responsible user, start / end, description, Edit / Delete with confirmation). |
| Long-press an event, then drag | Moves it. Month cells keep the time of day, week / day columns snap to 15 minutes, timeline week cells keep the weekday. Fires onEventUpdate then onEventDrop. |
| Drag the bottom handle of a week / day block | Resizes it (15-minute snap, minimum 15 minutes, never past the start day). Fires onEventUpdate. |
| Tap an empty cell / slot / header "Add Event" / FAB | onCreateEvent(date) → else onSlotClick(start, end) → else the built-in form. |
Tap a month cell with events or +N | Sheet listing the day's events. |
API Reference
Calendar
| Prop | Type | Default | Description |
|---|---|---|---|
events | IEvent[] | [] | Events. A new array replaces the calendar's local copy. |
users | IUser[] | [] | Users for the header filter select. |
defaultView | TCalendarView | 'month' | Initial view. A later change switches the view (host-driven view switcher). |
defaultDate | Date | today | Initially selected date (clamped to minDate / maxDate). |
visibleViews | TCalendarView[] | all eight | Views shown in the tab strip. |
weekStartsOn | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 0 | First day of the week (date-fns, 0 = Sunday). Drives month grid, week view, week numbers and the 13-week window. |
minDate | Date | — | Navigation never moves before this date (prev arrow disables). |
maxDate | Date | — | Navigation never moves past this date (next arrow disables). |
isLoading | boolean | false | Render a skeleton instead of the calendar (unmounts it). |
isRefreshing | boolean | false | Spinner overlay over the body that keeps view, date and scroll position. |
variant | 'default' | 'bordered' | 'compact' | 'default' | Visual style. |
size | 'sm' | 'default' | 'lg' | 'default' | Header text density. |
badge | 'dot' | 'colored' | 'colored' | Initial badge style (seeds the settings). |
showFab | boolean | false | Floating "+" button that opens the create flow. |
onCreateEvent | (date: Date) => void | — | Replaces the built-in create form. |
onEventCreate | (event: IEvent) => void | — | Called after an event is added (built-in form). |
onEventUpdate | (event: IEvent) => void | — | Called after an event is updated (form, resize, drag). |
onEventDelete | (eventId: TEventId) => void | — | Called after an event is deleted. |
onEventClick | (event: IEvent) => void | — | Replaces the built-in details sheet. |
onEventPress | (event: IEvent) => void | — | Native alias of onEventClick (onEventClick wins). |
onDaySelect | (date: Date) => void | — | Any day cell / day header tapped, independent of creation. |
onSlotClick | (start: Date, end: Date) => void | — | Empty cell / slot tapped when onCreateEvent is not set (bypasses the form). |
onVisibleRangeChange | (range: { start: Date; end: Date }, view: TCalendarView) => void | — | The visible window changed. Timeline views report the window they rendered (debounced 150 ms, reset when the view changes). |
onEventDrop | (event: IEvent, newStart: Date, newEnd: Date) => void | — | Called after drag & drop moves an event (receives the original event). |
onDateChange | (date: Date) => void | — | Selected date changed. |
onViewChange | (view: TCalendarView) => void | — | Active view changed. |
hideAddButton | boolean | false | Deprecated — hides both header and cell add. |
hideHeaderAddButton | boolean | false | Hide the header "Add Event" button. |
hideCellAddButton | boolean | false | Tapping an empty cell / slot no longer opens the create flow. |
hideUserSelect | boolean | false | Hide the user / filter select. |
hideSettings | boolean | false | Hide the settings button. |
hideFilter | boolean | false | Hide the color filter strip. |
hideColorFilter | boolean | — | Native alias of hideFilter. |
hideViewTabs | boolean | false | Hide the view tab strip. |
hideNavigation | boolean | false | Hide the today / title / arrows row. |
hideEventCount | boolean | false | Hide the event count badge. |
addButtonLabel | string | t('ui.calendar.addEvent', 'Add Event') | Header add button label. |
eventCountLabel | string | t('ui.calendar.events', 'events') | Event count badge label (e.g. "entries"). Timeline views count events overlapping the rendered window. |
searchPlaceholder | string | t('ui.calendar.searchPlaceholder', 'Search events...') | Agenda search placeholder. |
filterItems | CalendarFilterItem[] | — | Generic filter items; replaces the users list. |
onFilterChange | (id: string) => void | — | Filter item picked ('all' resets). |
renderFilterSelect | (context: CalendarContextValue) => ReactNode | — | Fully replaces the header filter select. |
sidebarContent | ReactNode | — | Content of a right-side drawer opened from a header button. |
renderEventBadge | (event: IEvent, defaultBadge: ReactNode) => ReactNode | — | Custom month-view badge renderer. |
cellMinHeight | number | 52 | Minimum height (px) of a month cell. |
cellMinWidth | number | — | Minimum width (px) of a month cell. |
hourSlotHeight | number | 60 | Height (px) of one hour in week / day view. |
startHour | number | 0 | First hour of the week / day grid (0-23). |
endHour | number | 24 | End hour of the week / day grid (1-24, exclusive). |
timelineMaxLanes | number | 3 | Timeline lanes a row shows before +N more. |
defaultYearTimelineMode | 'continuous' | 'single' | 'continuous' | Initial year-timeline pagination (a later change is applied). |
defaultQuarterMode | 'continuous' | 'single' | 'continuous' | Initial quarter pagination (a later change is applied). |
settings | Partial<CalendarSettings> | — | Controlled settings — every supplied key wins over the internal state. |
onSettingsChange | (settings: CalendarSettings) => void | — | Called with the full settings blob on every change. |
persistSettings | boolean | { storage?: 'session' | 'local'; key?: string } | false | Persist the settings through lib/storage (true → 'local', key 'calendar-settings'). |
className | string | — | Root container classes. |
style | ViewStyle | — | Root container style. |
CalendarProvider accepts every prop above except isLoading, variant, size, showFab, className and style, plus children.
IEvent
| Field | Type | Description |
|---|---|---|
id | TEventId (number | string) | Unique id. |
startDate | string | ISO start. |
endDate | string | ISO end. An event from 00:00 to 23:59 (or spanning days) renders in the all-day row. |
title | string | Title. |
color | TEventColor | 'blue' | 'green' | 'red' | 'yellow' | 'purple' | 'orange' — resolved through the theme palette. |
description | string | Description (may be empty). |
user | IUser | Responsible user. |
IUser
| Field | Type | Description |
|---|---|---|
id | string | User id. |
name | string | Display name. |
picturePath | string | null | Avatar URL. |
CalendarFilterItem
| Field | Type | Description |
|---|---|---|
id | string | Item id passed to onFilterChange. |
label | string | Label. |
image | string | null | Optional avatar URL. |
CalendarSettings
| Field | Type | Default | Description |
|---|---|---|---|
badgeVariant | 'dot' | 'colored' | 'colored' | Badge style. |
view | TCalendarView | defaultView | Active view. |
use24HourFormat | boolean | true | 24-hour times. |
agendaModeGroupBy | 'date' | 'color' | 'date' | Agenda grouping. |
yearTimelineMode | 'continuous' | 'single' | 'continuous' | Year-timeline pagination. |
quarterMode | 'continuous' | 'single' | 'continuous' | Quarter pagination. |
The header settings sheet edits every field except view (the tab strip does).
useCalendar()
Returns CalendarContextValue: selectedDate / setSelectedDate, view / setView, the settings getters + setters (badgeVariant, use24HourFormat / toggleTimeFormat, agendaModeGroupBy, yearTimelineMode, quarterMode, settings), filters (selectedColors, filterEventsBySelectedColors, selectedUserId, filterEventsBySelectedUser, selectedFilterId, onFilterChange, clearFilter), events (filtered) / allEvents, CRUD (addEvent, updateEvent, removeEvent, moveEvent), flows (requestCreate, openEvent, openEditForm, openDayEvents), the grid config (weekStartsOn, startHour, endHour, hourSlotHeight, cellMinHeight, cellMinWidth, timelineMaxLanes, minDate, maxDate) and timelineRange / reportTimelineRange.
Components
| Component | Description |
|---|---|
Calendar | The full calendar. |
CalendarView | Alias of Calendar (previous native name). |
CalendarProvider | Context provider (for custom layouts built with useCalendar). |
CalendarTimelineView | The period-timeline engine used by year-timeline / quarter / 13weeks, usable standalone. |
Type Exports
| Type | Description |
|---|---|
CalendarProps | Props of Calendar. |
CalendarProviderProps | Props of CalendarProvider. |
CalendarContextValue / ICalendarContext | Value returned by useCalendar(). |
CalendarSettings | Settings blob. |
CalendarPersistSettings | persistSettings option. |
CalendarVisibleRange | { start: Date; end: Date }. |
CalendarBadgeVariant | 'dot' | 'colored'. |
CalendarAgendaGroupBy | 'date' | 'color'. |
IEvent, IUser, ICalendarCell, CalendarFilterItem, TEventId | Event model. |
TCalendarView | 'day' | 'week' | 'month' | 'year' | 'agenda' | 'year-timeline' | 'quarter' | '13weeks'. |
TTimelineView | 'year-timeline' | 'quarter' | '13weeks'. |
TTimelineNavigationMode | 'continuous' | 'single'. |
TEventColor | 'blue' | 'green' | 'red' | 'yellow' | 'purple' | 'orange'. |
TWeekStartsOn | 0 | 1 | 2 | 3 | 4 | 5 | 6. |
CalendarTimelineViewProps, TimelineColumn, TimelineColumnGroup, TimelineRow, TimelineSection, TimelineVariant | Timeline engine types. |
Translations
All copy goes through useUiTranslation() with the web ui.calendar.* keys (addEvent, settings, dotBadge, 24HourFormat, agendaGroupBy, yearTimelineDisplay, quarterDisplay, continuous, singleYear, singleQuarter, viewMonth … view13Weeks, previousMonth / nextQuarter … nextPeriod, eventsOn, noEvents, noResults, searchPlaceholder, responsible, startDate, endDate, deleteEvent, deleteConfirm, loading, more, showLess, …). Native-only keys: ui.calendar.today, ui.calendar.events, ui.calendar.allDay, ui.calendar.startTime, ui.calendar.selectDate, ui.calendar.selectTime, ui.calendar.endTimeError, ui.calendar.currentUser, ui.calendar.event, ui.calendar.sidebar, ui.calendar.resizeEvent.
Migration from the previous native API
| Before | Now |
|---|---|
CalendarEvent { id: string; start: Date; end: Date; color?: hex; allDay? } | IEvent { id: number | string; startDate: ISO; endDate: ISO; color: TEventColor; description; user } (all-day = 00:00–23:59 or multi-day) |
CalendarViewMode (5 views) | TCalendarView (8 views) |
CalendarViewProps | CalendarProps |
firstDayOfWeek: 0 | 1 | weekStartsOn: 0–6 |
views (deprecated) | removed — use visibleViews |
onEventCreate(event without id) | onEventCreate(event: IEvent) (id generated) |
onEventDelete(id: string) | onEventDelete(id: TEventId) |
showFab default true | default false (header "Add Event" button is shown instead) |
visibleViews default month/week/day/agenda | all eight views |
| action-sheet event menu | details bottom sheet + delete confirm |
BulkUpdateDialog
Bottom-sheet dialog that overwrites one or more fields on every selected record of a Docyrus data source in a single bulk PATCH. Field values are edited with the native form fields. API-aligned with the web BulkUpdateDialog.
Chart
Comprehensive charting library with 13 chart types, VChart adapter, touch interactions, and animations.