# rn-calendar URL: /docs/native/docyrus/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 ```bash pnpm dlx @docyrus/cli add @docyrus/rn-calendar ``` **Dependencies:** - [date-fns](https://www.npmjs.com/package/date-fns) - [react-native-gesture-handler](https://www.npmjs.com/package/react-native-gesture-handler) - [react-native-reanimated](https://www.npmjs.com/package/react-native-reanimated) - [@shopify/flash-list](https://www.npmjs.com/package/@shopify/flash-list) The 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: ```ts { plugins: [ ['react-native-localize', { locales: ['en', 'tr'] }] ] } ``` The app must be wrapped in `GestureHandlerRootView` (drag & drop, resize, taps). ## Usage ```tsx 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 } } ]; 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 ```tsx ``` ### Refetch without resetting the view ```tsx const { data, isFetching, isLoading } = useQuery(/* … */); ``` ### Controlled / persisted settings ```tsx // Persist the settings blob (badge style, 24h format, agenda grouping, // timeline modes, last view) through lib/storage — mount a sync store with // (expo-sqlite kv-store or MMKV). // Or own the settings yourself: save(settings)} /> ``` ### Custom create / open flows ```tsx 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` | — | 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 |