Docyrus
ResourceSchedulerPanel
Preset-driven resource timeline — grouped multi-unit headers, a sticky resource column with synced scrolling, long-press drag to move events across time and resources, edge resize, and event detail bottom sheets.
iOSAndroid
Installation
pnpm dlx @docyrus/cli add @docyrus/rn-resource-scheduler-panelRequired Packages(3 packages)
pnpm add date-fns react-native-gesture-handler react-native-reanimatedUsage
import { ResourceSchedulerPanel } from '@/components/docyrus-native/resource-scheduler-panel';
import type { SchedulerEvent, SchedulerResource } from '@/components/docyrus-native/resource-scheduler-panel';
const resources: SchedulerResource[] = [
{ id: 'room-a', name: 'Room A', subtitle: '12 seats', group: 'Meeting rooms' },
{ id: 'dr-smith', name: 'Dr. Smith', subtitle: 'Cardiology', group: 'Doctors', avatar: 'https://…' },
];
const [events, setEvents] = useState<SchedulerEvent[]>([
{ id: 'e1', resourceId: 'room-a', title: 'Standup', startDate: new Date(2026, 8, 24, 9), endDate: new Date(2026, 8, 24, 10), color: '#3b82f6' },
]);
<ResourceSchedulerPanel
resources={resources}
events={events}
defaultPresetId="weeks-days"
onEventMove={(event, start, end, newResourceId) =>
setEvents(prev => prev.map(e => e.id === event.id
? { ...e, startDate: start, endDate: end, resourceId: newResourceId ?? e.resourceId }
: e))}
onEventResize={(event, start, end) =>
setEvents(prev => prev.map(e => e.id === event.id ? { ...e, startDate: start, endDate: end } : e))}
onSlotClick={(resourceId, date) => createBooking(resourceId, date)}
quickSlotHours={[9, 11, 13, 15, 17]}
/>Custom resource columns
import type { ResourceSchedulerColumn } from '@/components/docyrus-native/resource-scheduler-panel';
const columns: ResourceSchedulerColumn[] = [
{ id: 'name', header: 'Resource', getValue: r => r.name },
{ id: 'seats', header: 'Seats', getValue: r => String(r.seats ?? '—'), width: 56 },
];
<ResourceSchedulerPanel resources={resources} events={events} resourceColumns={columns} resourcePanelWidth={180} />Behavior
- Presets — every preset has an interval unit (column) and a group unit (header row above it). The built-in
SCHEDULER_PRESETSareday-hours,weeks-days,months-days,months-weeksandmonths(Year / Months). Changing the preset (header select) resets the range to that preset's default range. Timeline math (lib/timeline-utils.ts,lib/presets.ts) is shared verbatim with web. - Navigation — Today resets to the preset's default range; the arrows shift the range by one group unit.
onDateRangeChangefires for all three. When today falls inside the range, the timeline scrolls it into view. - Layout — the resource column stays fixed on the left while the lanes scroll horizontally; vertical scrolling of the lanes drives the resource column (Reanimated
scrollTo), so rows stay aligned. Overlapping events stack into sub-rows and the lane grows to fit (the resource cell grows with it). Resources with agroupget collapsible group header rows in both columns. - Drag & drop — long-press an event (300 ms), then drag: it moves in whole interval units (hour / day / week / month / year) and can be dropped on another resource row →
onEventMove(event, start, end, newResourceId?). Long-press an edge handle (120 ms) to resize →onEventResize. A floating pill shows the snapped range and target resource during the drag; scrolling is locked while dragging. Drag is disabled whenreadOnlyor when the matching callback is missing. - Tapping — tapping an event fires
onEventClickand opens a detail bottom sheet (time, resource, location, description) unlessshowEventDetails={false}(web: popover). Tapping an empty lane area firesonSlotClick(resourceId, date)with the date under the finger. - Quick slots (native-only) — with
quickSlotHoursandonSlotClick, tapping a resource cell opens a sheet of hour chips for today (or the range start when today is outside the range); a chip callsonSlotClick. - Loading / empty —
isLoadingrenders skeleton rows; no resources renders the "No events to display" state. - Formatting — the event sheet and drag pill format dates through
useDateFormat().formatDateTimewhen a provider is mounted (elseMMM d, HH:mm). Header / column labels use the preset's date-fns formats, like web. Copy goes throughuseUiTranslation()with the webui.scheduler.*keys.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
resources | SchedulerResource[] | — | Required. Rows of the scheduler. |
events | SchedulerEvent[] | — | Required. Events placed on the resource lanes. |
presets | TimelinePreset[] | SCHEDULER_PRESETS | Presets offered in the header select. |
defaultPresetId | string | presets[0].id | Initially active preset. |
startDate | Date | preset default range start | Initial range start. |
endDate | Date | preset default range end | Initial range end. |
resourceColumns | ResourceSchedulerColumn[] | — | Light column list for the resource panel (web: TanStack ColumnDef[]). The first column's header labels the panel. |
renderResource | (resource: SchedulerResource) => ReactNode | — | Native-only. Custom resource cell; wins over resourceColumns. |
onEventClick | (event: SchedulerEvent) => void | — | Fires when an event is tapped. |
onEventMove | (event: SchedulerEvent, newStart: Date, newEnd: Date, newResourceId?: string) => void | — | Fires after a drag; newResourceId is set only when the resource changed. Enables move drag. |
onEventResize | (event: SchedulerEvent, newStart: Date, newEnd: Date) => void | — | Fires after an edge resize. Enables resize handles. |
onSlotClick | (resourceId: string, date: Date) => void | — | Fires when an empty lane area (or a quick-slot chip) is tapped. |
onDateRangeChange | (start: Date, end: Date) => void | — | Fires when the visible range changes. |
onPresetChange | (presetId: string) => void | — | Fires when the preset changes. |
isLoading | boolean | false | Render skeleton rows. |
readOnly | boolean | false | Disable drag, resize, slot taps and quick slots. |
showTodayIndicator | boolean | true | Show the today line and header pill. |
showResourceCount | boolean | true | Show the resource count badge. |
height | number | 'auto' | 480 | Panel height (web default 600). 'auto' grows with the rows and disables vertical scrolling. |
resourcePanelWidth | number | 140 | Resource column width (web default 280). |
rowHeight | number | 48 | Minimum lane height; lanes grow with stacked events. |
showEventDetails | boolean | true | Native-only. Open the event detail sheet on tap. |
quickSlotHours | number[] | — | Native-only. Hours (0–23) offered as quick-slot chips when a resource is tapped. |
className | string | — | Additional classes for the root container. |
SchedulerResource
| Property | Type | Description |
|---|---|---|
id | string | Unique id. |
name | string | Display name. |
avatar | string | Optional avatar image (initials otherwise). |
subtitle | string | Optional second line. |
group | string | Optional group; grouped resources get collapsible header rows. |
[key: string] | unknown | Any extra data (for resourceColumns / renderResource). |
SchedulerEvent
| Property | Type | Description |
|---|---|---|
id | string | Unique id. |
resourceId | string | Resource the event belongs to. |
title | string | Bar label. |
startDate | Date | Start. |
endDate | Date | End. |
color | string | Bar color (defaults to the theme primary). |
variant | 'solid' | 'outlined' | 'subtle' | Bar style. Default 'solid'. |
icon | ReactNode | Optional icon before the title. |
description | string | Shown in the detail sheet. |
location | string | Shown in the detail sheet. |
metadata | Record<string, unknown> | Free-form data. |
ResourceSchedulerColumn
| Property | Type | Description |
|---|---|---|
id | string | Column id. |
header | string | Header label (the first column's header labels the panel). |
getValue | (resource: SchedulerResource) => ReactNode | Cell content; strings / numbers are wrapped in Text. |
width | number | Optional fixed width; columns without one share the rest. |
TimelinePreset / TimelinePresetConfig
| Property | Type | Description |
|---|---|---|
id | string | Preset id. |
name | string | Label in the preset select. |
config.unitWidth | number | Column width in px. |
config.interval | { unit: TimeUnit; format: string } | Column unit + date-fns label format. |
config.groups | Array<{ unit: TimeUnit; format: string }> | Group header unit + format (the first entry is rendered). |
Exports
| Export | Description |
|---|---|
ResourceSchedulerPanel | The component. |
SCHEDULER_PRESETS | Built-in presets. |
getPresetById(presets, id) | Find a preset (falls back to the first). |
eventBarVariants | tv() slots (base, label) for the event bar (web: CVA). |
Type Exports
| Type | Description |
|---|---|
ResourceSchedulerPanelProps | Props for ResourceSchedulerPanel. |
SchedulerResource | Resource row. |
SchedulerEvent | Timeline event. |
ResourceSchedulerColumn | Native-only. Resource panel column. |
TimelinePreset | Preset definition. |
TimelinePresetConfig | Preset config (unit width, interval, groups). |
TimeUnit | 'hour' | 'day' | 'week' | 'month' | 'year'. |
TimelineColumn | Generated interval column (start, end, label, index). |
TimelineGroupHeader | Generated group header (start, end, label, spanCount). |
RecordSharing
Share a record with users, teams, roles or the workspace — staged multi-select search, one batch permission, per-row pending states and a share trigger that opens the panel in a bottom sheet.
SchemaRepeater
A generic repeater component for managing dynamic lists of items with reordering, adding, and removing capabilities.