# rn-resource-scheduler-panel URL: /docs/native/docyrus/resource-scheduler-panel 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. ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/rn-resource-scheduler-panel ``` **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) ## Usage ```tsx 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([ { id: 'e1', resourceId: 'room-a', title: 'Standup', startDate: new Date(2026, 8, 24, 9), endDate: new Date(2026, 8, 24, 10), color: '#3b82f6' }, ]); 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 ```tsx 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 }, ]; ``` ## Behavior - **Presets** — every preset has an interval unit (column) and a group unit (header row above it). The built-in `SCHEDULER_PRESETS` are `day-hours`, `weeks-days`, `months-days`, `months-weeks` and `months` (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. `onDateRangeChange` fires 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 a `group` get 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 when `readOnly` or when the matching callback is missing. - **Tapping** — tapping an event fires `onEventClick` and opens a detail bottom sheet (time, resource, location, description) unless `showEventDetails={false}` (web: popover). Tapping an empty lane area fires `onSlotClick(resourceId, date)` with the date under the finger. - **Quick slots (native-only)** — with `quickSlotHours` and `onSlotClick`, tapping a resource cell opens a sheet of hour chips for today (or the range start when today is outside the range); a chip calls `onSlotClick`. - **Loading / empty** — `isLoading` renders skeleton rows; no resources renders the "No events to display" state. - **Formatting** — the event sheet and drag pill format dates through `useDateFormat().formatDateTime` when a provider is mounted (else `MMM d, HH:mm`). Header / column labels use the preset's date-fns formats, like web. Copy goes through `useUiTranslation()` with the web `ui.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` | 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`). |