@omidrahmati/react-slot-scheduler 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,717 @@
1
+ <div align="center">
2
+
3
+ # react-slot-scheduler
4
+
5
+ **The lightweight slot calendar for React โ€” zero dependencies, RTL-first, 3 scheduler modes + complete demo patterns.**
6
+
7
+ [![npm](https://img.shields.io/npm/v/@omidrahmati/react-slot-scheduler?color=0f766e)](https://www.npmjs.com/package/@omidrahmati/react-slot-scheduler)
8
+ [![gzip size](https://img.shields.io/bundlephobia/minzip/@omidrahmati/react-slot-scheduler?color=0f766e&label=27%20kB%20gzip)](https://bundlephobia.com/package/@omidrahmati/react-slot-scheduler)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178c6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
10
+ [![React](https://img.shields.io/badge/React-18%2B-61dafb?logo=react&logoColor=white)](https://react.dev/)
11
+ [![zero deps](https://img.shields.io/badge/dependencies-0-brightgreen)](https://bundlephobia.com/package/@omidrahmati/react-slot-scheduler)
12
+ [![license: MIT](https://img.shields.io/npm/l/@omidrahmati/react-slot-scheduler?color=0f766e)](./LICENSE)
13
+
14
+ </div>
15
+
16
+ ---
17
+
18
+ <table>
19
+ <tr>
20
+ <td width="50%">
21
+ <img src="https://raw.githubusercontent.com/omidrahmati2000/react-slot-scheduler/main/media/screenshot-fa-light.png" alt="React slot scheduler โ€” Persian RTL, Jalali calendar, appointment booking" />
22
+ <p align="center"><sub>๐Ÿ‡ฎ๐Ÿ‡ท Persian ยท RTL ยท Jalali ยท Appointment</sub></p>
23
+ </td>
24
+ <td width="50%">
25
+ <img src="https://raw.githubusercontent.com/omidrahmati2000/react-slot-scheduler/main/media/screenshot-meeting-dark.png" alt="React slot scheduler โ€” Meeting room drag and drop rescheduling, dark mode" />
26
+ <p align="center"><sub>๐Ÿข Meeting Room ยท Drag & drop ยท Dark mode</sub></p>
27
+ </td>
28
+ </tr>
29
+ <tr>
30
+ <td width="50%">
31
+ <img src="https://raw.githubusercontent.com/omidrahmati2000/react-slot-scheduler/main/media/screenshot-task-timeline.png" alt="React slot scheduler โ€” Gantt task timeline, assignee, progress, dark mode" />
32
+ <p align="center"><sub>๐Ÿ—‚๏ธ Task Timeline ยท Gantt ยท Assignee ยท Progress %</sub></p>
33
+ </td>
34
+ <td width="50%">
35
+ <img src="https://raw.githubusercontent.com/omidrahmati2000/react-slot-scheduler/main/media/screenshot-resource-planner.png" alt="React slot scheduler โ€” Resource planner, rooms, doctors, overlap stacking" />
36
+ <p align="center"><sub>๐Ÿงฉ Resource Planner ยท Overlap stacking ยท Week/Month</sub></p>
37
+ </td>
38
+ </tr>
39
+ </table>
40
+
41
+ ---
42
+
43
+ ## Why react-slot-scheduler?
44
+
45
+ | | **react-slot-scheduler** | Alternatives |
46
+ |---|---|---|
47
+ | Bundle (gzip) | **27 kB** | 400โ€“800 kB (antd + react-dnd + dayjs) |
48
+ | Dependencies | **0** | 5โ€“10 packages |
49
+ | RTL / Persian / Arabic | โœ… **built-in** | โŒ not supported |
50
+ | Dark mode | โœ… **CSS tokens** | โŒ manual |
51
+ | TypeScript | โœ… **100% typed** | partial |
52
+ | Scheduling modes | โœ… **Multiple production modes** | time-grid only |
53
+ | API style | โœ… **hooks props** | class-based (`SchedulerData`) |
54
+ | UI framework lock-in | โœ… **none** | Ant Design required |
55
+ | Next.js / SSR | โœ… **ready** | varies |
56
+
57
+ ---
58
+
59
+ ## Scheduling Modes
60
+
61
+ | Mode | Import / `mode` prop | Use case |
62
+ |---|---|---|
63
+ | **Time Grid** | default โ€” no `mode` needed | Appointment booking, clinic, salon |
64
+ | **Meeting Room** | default โ€” use `DaySchedule[]` | Office meeting room reservation |
65
+ | **Task Timeline** | `mode="task-timeline"` + `createTaskTimelineAdapter()` | Team task / sprint scheduling |
66
+ | **Resource Planner** | `mode="resource-planner"` + `createResourcePlannerAdapter()` | Multi-resource (rooms, doctors, staff) |
67
+ | **Theme Playground (Demo tab)** | โ€” | Preview 6 built-in color presets |
68
+
69
+ ---
70
+
71
+ ## Install
72
+
73
+ ```bash
74
+ npm i @omidrahmati/react-slot-scheduler
75
+ # yarn add @omidrahmati/react-slot-scheduler
76
+ # pnpm add @omidrahmati/react-slot-scheduler
77
+ ```
78
+
79
+ Import the stylesheet once at your app entry:
80
+
81
+ ```ts
82
+ import '@omidrahmati/react-slot-scheduler/dist/index.css';
83
+ ```
84
+
85
+ ### Quick Start (from scratch)
86
+
87
+ ```tsx
88
+ import { useState } from 'react';
89
+ import { BookingCalendar, type DaySchedule } from '@omidrahmati/react-slot-scheduler';
90
+ import '@omidrahmati/react-slot-scheduler/dist/index.css';
91
+
92
+ const schedules: DaySchedule[] = [
93
+ {
94
+ date: '2026-06-01',
95
+ isWorkingDay: true,
96
+ slots: [
97
+ { startTime: '09:00', endTime: '10:00', status: 'booked', itemId: 'a-1', title: 'Standup' },
98
+ ],
99
+ },
100
+ ];
101
+
102
+ export default function App() {
103
+ const [value, setValue] = useState(new Date('2026-06-01'));
104
+ return <BookingCalendar value={value} onChange={setValue} schedules={schedules} locale="en-US" />;
105
+ }
106
+ ```
107
+
108
+ ### Next.js App Router
109
+
110
+ Import CSS once in your app layout or root client entry:
111
+
112
+ ```tsx
113
+ // app/layout.tsx
114
+ import '@omidrahmati/react-slot-scheduler/dist/index.css';
115
+ ```
116
+
117
+ ---
118
+
119
+ ## Mode 1 โ€” Appointment / Time Grid
120
+
121
+ The default mode. Pass a `DaySchedule[]` array โ€” each day has a list of time slots.
122
+
123
+ ```tsx
124
+ import { useState } from 'react';
125
+ import { BookingCalendar } from '@omidrahmati/react-slot-scheduler';
126
+ import type { DaySchedule } from '@omidrahmati/react-slot-scheduler';
127
+ import '@omidrahmati/react-slot-scheduler/dist/index.css';
128
+
129
+ const schedules: DaySchedule[] = [
130
+ {
131
+ date: '2026-06-01', // YYYY-MM-DD
132
+ isWorkingDay: true,
133
+ workStartTime: '09:00', // sets the visible grid start
134
+ workEndTime: '18:00', // sets the visible grid end
135
+ slots: [
136
+ {
137
+ startTime: '09:00',
138
+ endTime: '10:00',
139
+ status: 'booked',
140
+ itemId: 'appt-1', // passed to onItemClick when clicked
141
+ title: 'Alice โ€” Haircut',
142
+ description: 'Confirmed',
143
+ },
144
+ {
145
+ startTime: '11:00',
146
+ endTime: '11:30',
147
+ status: 'blocked',
148
+ title: 'Lunch Break',
149
+ },
150
+ ],
151
+ },
152
+ {
153
+ date: '2026-06-02',
154
+ isWorkingDay: false, // greys out the column, no slots rendered
155
+ slots: [],
156
+ },
157
+ ];
158
+
159
+ export default function App() {
160
+ const [date, setDate] = useState(new Date());
161
+
162
+ return (
163
+ <BookingCalendar
164
+ value={date}
165
+ onChange={setDate}
166
+ schedules={schedules}
167
+ locale="en-US"
168
+ weekStartsOn={1} // 1 = Monday
169
+ onItemClick={(id) => console.log('booked slot clicked:', id)}
170
+ onSlotClick={(date, slot) => console.log('empty area clicked:', date, slot)}
171
+ />
172
+ );
173
+ }
174
+ ```
175
+
176
+ ### Drag & Drop Rescheduling
177
+
178
+ ```tsx
179
+ const [schedules, setSchedules] = useState<DaySchedule[]>(initial);
180
+
181
+ function handleMove({ slot, from, to }: SlotMovePayload) {
182
+ setSchedules(prev =>
183
+ prev.map(day => {
184
+ if (day.date === from.date)
185
+ return {
186
+ ...day,
187
+ slots: day.slots.filter(s => s.startTime !== from.startTime),
188
+ };
189
+ if (day.date === to.date)
190
+ return {
191
+ ...day,
192
+ slots: [...day.slots, { ...slot, startTime: to.startTime, endTime: to.endTime }],
193
+ };
194
+ return day;
195
+ })
196
+ );
197
+ }
198
+
199
+ <BookingCalendar
200
+ draggableSlots
201
+ onSlotMove={handleMove}
202
+ schedules={schedules}
203
+ value={date}
204
+ onChange={setDate}
205
+ />
206
+ ```
207
+
208
+ ### Multi-Select Empty Slots
209
+
210
+ Drag across empty cells to select a time range. Only fires on cells that have no existing slot.
211
+
212
+ ```tsx
213
+ <BookingCalendar
214
+ selectionMode
215
+ onSlotDragSelectEnd={(slots) => {
216
+ // slots: Array<{ date: string; startTime: string; endTime: string }>
217
+ openBookingDialog(slots);
218
+ }}
219
+ onSelectionChange={(slots) => setSelectionCount(slots.length)}
220
+ schedules={schedules}
221
+ value={date}
222
+ onChange={setDate}
223
+ />
224
+ ```
225
+
226
+ ---
227
+
228
+ ## Mode 2 โ€” Persian / Arabic RTL
229
+
230
+ Switch locale and the layout, dates, and labels all change automatically.
231
+
232
+ ```tsx
233
+ <BookingCalendar
234
+ locale="fa-IR"
235
+ weekStartsOn={6} // 6 = Saturday (start of week in Iran)
236
+ direction="auto" // RTL detected from locale automatically
237
+ translations={{
238
+ previous: 'Previous',
239
+ today: 'Today',
240
+ next: 'Next',
241
+ day: 'Day',
242
+ week: 'Week',
243
+ }}
244
+ schedules={schedules}
245
+ value={date}
246
+ onChange={setDate}
247
+ />
248
+ ```
249
+
250
+ Supported locales: `fa-IR`, `ar`, `ar-SA`, `ar-EG`, `en-US`, `en-GB`, and any BCP-47 string supported by `Intl`.
251
+
252
+ ---
253
+
254
+ ## Mode 3 โ€” Task Timeline (Gantt)
255
+
256
+ Use `mode="task-timeline"` with `createTaskTimelineAdapter()`. Each item has an `assignee` and `progress` field.
257
+
258
+ ```tsx
259
+ import {
260
+ BookingCalendar,
261
+ createTaskTimelineAdapter,
262
+ } from '@omidrahmati/react-slot-scheduler';
263
+ import type { TaskTimelineItem } from '@omidrahmati/react-slot-scheduler';
264
+
265
+ const tasks: TaskTimelineItem[] = [
266
+ {
267
+ id: 'task-1',
268
+ date: '2026-06-01',
269
+ startTime: '09:00',
270
+ endTime: '10:30',
271
+ title: 'Design Review',
272
+ status: 'booked', // 'booked' | 'blocked' | 'custom'
273
+ assignee: 'Sara',
274
+ progress: 70, // 0โ€“100
275
+ },
276
+ {
277
+ id: 'task-2',
278
+ date: '2026-06-01',
279
+ startTime: '11:00',
280
+ endTime: '12:00',
281
+ title: 'API Contract',
282
+ status: 'custom',
283
+ assignee: 'Arman',
284
+ progress: 35,
285
+ },
286
+ {
287
+ id: 'task-3',
288
+ date: '2026-06-02',
289
+ startTime: '10:00',
290
+ endTime: '11:30',
291
+ title: 'QA Sync',
292
+ status: 'blocked',
293
+ assignee: 'Neda',
294
+ progress: 50,
295
+ },
296
+ ];
297
+
298
+ export default function TaskScheduler() {
299
+ const [date, setDate] = useState(new Date());
300
+ const adapter = useMemo(() => createTaskTimelineAdapter(), []);
301
+
302
+ return (
303
+ <BookingCalendar
304
+ mode="task-timeline"
305
+ value={date}
306
+ onChange={setDate}
307
+ schedules={[]} // not used in task-timeline mode
308
+ dataAdapter={adapter}
309
+ dataSource={tasks}
310
+ locale="en-US"
311
+ onItemClick={(id) => {
312
+ const task = tasks.find(t => t.id === id);
313
+ console.log('Task clicked:', task);
314
+ }}
315
+ />
316
+ );
317
+ }
318
+ ```
319
+
320
+ ### `TaskTimelineItem` interface
321
+
322
+ ```ts
323
+ interface TaskTimelineItem {
324
+ id: string;
325
+ date: string; // start date: 'YYYY-MM-DD'
326
+ endDate?: string; // optional multi-day end date (inclusive)
327
+ startTime: string; // 'HH:mm'
328
+ endTime: string; // 'HH:mm'
329
+ title: string;
330
+ status?: 'available' | 'booked' | 'blocked' | 'outside' | 'custom';
331
+ description?: string;
332
+ assignee?: string;
333
+ progress?: number; // 0โ€“100
334
+ resourceId?: string;
335
+ meta?: Record<string, unknown>;
336
+ }
337
+ ```
338
+
339
+ ---
340
+
341
+ ## Mode 4 โ€” Resource Planner
342
+
343
+ Use `mode="resource-planner"` with `createResourcePlannerAdapter(resources)`. Each item is linked to a resource via `resourceId`. The resource name is shown as the sub-label in each slot.
344
+
345
+ ```tsx
346
+ import {
347
+ BookingCalendar,
348
+ createResourcePlannerAdapter,
349
+ } from '@omidrahmati/react-slot-scheduler';
350
+ import type {
351
+ ResourceDefinition,
352
+ ResourcePlannerItem,
353
+ } from '@omidrahmati/react-slot-scheduler';
354
+
355
+ const resources: ResourceDefinition[] = [
356
+ { id: 'room-a', title: 'Room A' },
357
+ { id: 'room-b', title: 'Room B' },
358
+ { id: 'dr-amini', title: 'Dr. Amini' },
359
+ ];
360
+
361
+ const items: ResourcePlannerItem[] = [
362
+ {
363
+ id: 'rp-1',
364
+ date: '2026-06-01',
365
+ startTime: '09:00',
366
+ endTime: '10:00',
367
+ resourceId: 'room-a', // must match a resource id
368
+ title: 'Team Sync',
369
+ status: 'booked',
370
+ },
371
+ {
372
+ id: 'rp-2',
373
+ date: '2026-06-01',
374
+ startTime: '10:30',
375
+ endTime: '11:30',
376
+ resourceId: 'room-b',
377
+ title: 'Client Call',
378
+ status: 'custom',
379
+ },
380
+ {
381
+ id: 'rp-3',
382
+ date: '2026-06-02',
383
+ startTime: '14:00',
384
+ endTime: '15:00',
385
+ resourceId: 'dr-amini',
386
+ title: 'Consultation',
387
+ status: 'booked',
388
+ },
389
+ ];
390
+
391
+ export default function ResourceCalendar() {
392
+ const [date, setDate] = useState(new Date());
393
+ const adapter = useMemo(() => createResourcePlannerAdapter(resources), []);
394
+
395
+ return (
396
+ <BookingCalendar
397
+ mode="resource-planner"
398
+ value={date}
399
+ onChange={setDate}
400
+ schedules={[]}
401
+ resources={resources}
402
+ dataAdapter={adapter}
403
+ dataSource={items}
404
+ locale="en-US"
405
+ onItemClick={(id) => {
406
+ const item = items.find(i => i.id === id);
407
+ const resource = resources.find(r => r.id === item?.resourceId);
408
+ console.log(`${item?.title} โ€” ${resource?.title}`);
409
+ }}
410
+ />
411
+ );
412
+ }
413
+ ```
414
+
415
+ ### `ResourceDefinition` interface
416
+
417
+ ```ts
418
+ interface ResourceDefinition {
419
+ id: string;
420
+ title: string; // displayed as slot sub-label
421
+ meta?: Record<string, unknown>;
422
+ }
423
+ ```
424
+
425
+ ### `ResourcePlannerItem` interface
426
+
427
+ ```ts
428
+ interface ResourcePlannerItem {
429
+ id: string;
430
+ date: string; // 'YYYY-MM-DD'
431
+ startTime: string; // 'HH:mm'
432
+ endTime: string; // 'HH:mm'
433
+ resourceId: string; // must match a ResourceDefinition.id
434
+ title: string;
435
+ status?: 'available' | 'booked' | 'blocked' | 'outside' | 'custom';
436
+ description?: string;
437
+ meta?: Record<string, unknown>;
438
+ }
439
+ ```
440
+
441
+ ---
442
+
443
+ ## Dark Mode / Custom Theming
444
+
445
+ Override any or all of the 9 CSS design tokens:
446
+
447
+ ```tsx
448
+ <BookingCalendar
449
+ theme={{
450
+ primary: '#818cf8', // accent โ€” buttons, today cell, active tab
451
+ bg: '#0f172a', // grid background
452
+ panel: '#1e293b', // toolbar + time-label column
453
+ border: '#334155', // grid lines
454
+ text: '#f1f5f9', // primary text
455
+ mutedText: '#94a3b8', // time labels + slot sub-labels
456
+ bookedBg: '#3730a3', // booked slot fill
457
+ blockedBg: '#334155', // blocked / outside slot fill
458
+ customBg: '#5b21b6', // custom slot fill
459
+ }}
460
+ schedules={schedules}
461
+ value={date}
462
+ onChange={setDate}
463
+ />
464
+ ```
465
+
466
+ ---
467
+
468
+ ## Full API Reference
469
+
470
+ ### `BookingCalendarProps`
471
+
472
+ | Prop | Type | Default | Description |
473
+ |---|---|---|---|
474
+ | `value` | `Date` | โ€” | **Required.** Currently focused date |
475
+ | `onChange` | `(date: Date) => void` | โ€” | **Required.** Navigation callback |
476
+ | `schedules` | `DaySchedule[]` | โ€” | **Required.** Pass `[]` when using `dataSource` |
477
+ | `mode` | `'time-grid' \| 'task-timeline' \| 'resource-planner'` | `'time-grid'` | Scheduler mode |
478
+ | `dataAdapter` | `SchedulerDataAdapter` | โ€” | Required when `mode` is not `'time-grid'` |
479
+ | `dataSource` | `TaskTimelineItem[] \| ResourcePlannerItem[]` | โ€” | Data for non-time-grid modes |
480
+ | `resources` | `ResourceDefinition[]` | โ€” | Required when `mode="resource-planner"` |
481
+ | `ganttTimeUnit` | `'hour' \| 'day'` | adapter-driven | Override Gantt time unit in non-time-grid modes |
482
+ | `ganttScale` | `'day' \| 'week' \| 'month'` | adapter/default | Override day-mode column scale |
483
+ | `viewMode` | `'day' \| 'week'` | auto | Controlled view; auto = Day on mobile |
484
+ | `onViewModeChange` | `(mode) => void` | โ€” | Fired on Day/Week toggle |
485
+ | `draggableSlots` | `boolean` | `false` | Enable HTML5 drag & drop |
486
+ | `onSlotMove` | `(payload: SlotMovePayload) => void` | โ€” | Fires after a successful drop |
487
+ | `onBeforeSlotMove` | `(payload) => boolean \| Promise<boolean>` | โ€” | Validate/deny drop before `onSlotMove` |
488
+ | `onSlotConflict` | `(payload: SlotConflictPayload) => void` | โ€” | Fires on overlap or policy denial |
489
+ | `onGanttItemMove` | `(payload: GanttMovePayload) => void` | โ€” | Fired when an item is moved in Gantt modes |
490
+ | `onGanttItemResize` | `(payload: GanttResizePayload) => void` | โ€” | Fired when an item is resized in hour-based Gantt |
491
+ | `onGanttItemCreate` | `(payload: GanttCreatePayload) => void` | โ€” | Fired when a new item is created by dragging on empty row space (hour mode) |
492
+ | `selectionMode` | `boolean` | `false` | Enable drag-to-select on empty cells |
493
+ | `selectedSlots` | `Array<{ date; startTime; endTime }>` | โ€” | Controlled external selection model |
494
+ | `onSlotDragSelectStart` | `(slot) => void` | โ€” | Fires when drag selection starts |
495
+ | `onSlotDragSelectMove` | `(slot) => void` | โ€” | Fires for each entered empty cell |
496
+ | `onSlotDragSelectEnd` | `(slots[]) => void` | โ€” | Fires on mouseup with selected cells |
497
+ | `onSelectionChange` | `(slots[]) => void` | โ€” | Fires on each added cell |
498
+ | `isSlotSelected` | `(slot) => boolean` | โ€” | Controlled external selection |
499
+ | `onSlotClick` | `(date, slot) => void` | โ€” | Click on any slot |
500
+ | `onItemClick` | `(itemId) => void` | โ€” | Click on slots with `itemId` / `id` |
501
+ | `onBookingClick` | `(bookingId) => void` | โ€” | Deprecated alias of `onItemClick` |
502
+ | `slotGranularity` | `number` | `30` | Minutes per grid row (15, 30, 60โ€ฆ) |
503
+ | `locale` | `string` | `'fa-IR'` | BCP-47 locale โ€” drives labels & direction |
504
+ | `weekStartsOn` | `0 \| 1 \| 6` | `6` | Week start: 0=Sun, 1=Mon, 6=Sat |
505
+ | `direction` | `'rtl' \| 'ltr' \| 'auto'` | `'auto'` | Layout direction |
506
+ | `translations` | `Partial<Translations>` | โ€” | Override toolbar label strings |
507
+ | `theme` | `Partial<CalendarTheme>` | โ€” | Override design tokens |
508
+ | `hideTimeColumn` | `boolean` | `false` | Hide the time-label column |
509
+ | `className` | `string` | โ€” | Extra CSS class on root |
510
+
511
+ ### Notes For Production Usage
512
+
513
+ - In `task-timeline` and `resource-planner` modes, pass `schedules={[]}` and provide `dataAdapter` + `dataSource`.
514
+ - `onGanttItemResize` applies to hour-based Gantt mode (`ganttTimeUnit="hour"`).
515
+ - `onGanttItemCreate` applies to hour-based Gantt mode (`ganttTimeUnit="hour"`).
516
+ - For policy validation in time-grid mode, use `onBeforeSlotMove` and handle denials via `onSlotConflict`.
517
+
518
+ ---
519
+
520
+ ### `DaySchedule`
521
+
522
+ Used in default `time-grid` mode. The `workStartTime` / `workEndTime` pair defines the visible hour range.
523
+
524
+ ```ts
525
+ interface DaySchedule {
526
+ date: string; // 'YYYY-MM-DD'
527
+ isWorkingDay: boolean; // false โ†’ column is greyed, slots ignored
528
+ workStartTime?: string; // 'HH:mm' โ€” grid visible start (default: min slot time)
529
+ workEndTime?: string; // 'HH:mm' โ€” grid visible end (default: max slot time)
530
+ slots: CalendarSlot[];
531
+ }
532
+ ```
533
+
534
+ ### `CalendarSlot`
535
+
536
+ ```ts
537
+ interface CalendarSlot {
538
+ startTime: string; // 'HH:mm'
539
+ endTime: string; // 'HH:mm'
540
+ status: 'booked' | 'blocked' | 'outside' | 'custom';
541
+ itemId?: string; // โ†’ onItemClick fired when clicked
542
+ bookingId?: string; // deprecated alias for itemId
543
+ title?: string; // main label in slot
544
+ description?: string; // sub-label in slot
545
+ }
546
+ ```
547
+
548
+ > **Tip:** Do **not** create `status: 'available'` slots โ€” empty grid cells already represent available time.
549
+ > In `selectionMode`, users drag across empty cells to select them.
550
+
551
+ ### Slot Status Guide
552
+
553
+ | Status | Typical use | Click | Drag |
554
+ |---|---|---|---|
555
+ | `booked` | Confirmed reservation | โœ… `onItemClick` | โœ… if `draggableSlots` |
556
+ | `blocked` | Break, holiday, lunch | โ€” | โ€” |
557
+ | `outside` | Outside working hours | โ€” | โ€” |
558
+ | `custom` | App-defined category | โœ… `onItemClick` | โœ… if `draggableSlots` |
559
+
560
+ ### `SlotMovePayload`
561
+
562
+ ```ts
563
+ interface SlotMovePayload {
564
+ slot: CalendarSlot;
565
+ from: { date: string; startTime: string; endTime: string };
566
+ to: { date: string; startTime: string; endTime: string };
567
+ }
568
+ ```
569
+
570
+ ### `SlotConflictPayload`
571
+
572
+ ```ts
573
+ interface SlotConflictPayload extends SlotMovePayload {
574
+ reason: 'overlap' | 'blocked-by-policy';
575
+ conflictingSlot?: CalendarSlot;
576
+ }
577
+ ```
578
+
579
+ ### `GanttMovePayload`
580
+
581
+ ```ts
582
+ interface GanttMovePayload {
583
+ item: GanttItem;
584
+ newRowId: string;
585
+ newDate: string; // 'YYYY-MM-DD'
586
+ newEndDate?: string; // set for multi-day item moves
587
+ newStartTime: string; // 'HH:mm'
588
+ newEndTime: string; // 'HH:mm'
589
+ }
590
+ ```
591
+
592
+ ### `GanttResizePayload`
593
+
594
+ ```ts
595
+ interface GanttResizePayload {
596
+ item: GanttItem;
597
+ newStartTime: string;
598
+ newEndTime: string;
599
+ }
600
+ ```
601
+
602
+ ### `GanttCreatePayload`
603
+
604
+ ```ts
605
+ interface GanttCreatePayload {
606
+ rowId: string;
607
+ date: string; // start date: 'YYYY-MM-DD'
608
+ endDate?: string; // end date in day-scale multi-column drag
609
+ startTime: string; // 'HH:mm'
610
+ endTime: string; // 'HH:mm'
611
+ }
612
+ ```
613
+
614
+ ### `CalendarTheme` Tokens
615
+
616
+ | Token | Default | Controls |
617
+ |---|---|---|
618
+ | `primary` | `#0f766e` | Buttons, today column, active view tab |
619
+ | `bg` | `#f4f7f7` | Grid background |
620
+ | `panel` | `#ffffff` | Toolbar + time column |
621
+ | `border` | `#d6e0df` | Grid lines |
622
+ | `text` | `#102725` | Primary text |
623
+ | `mutedText` | `#5c7270` | Time labels, slot sub-labels |
624
+ | `availableBg` | `#ffffff` | Empty-slot surface |
625
+ | `bookedBg` | `#fee2e2` | Booked slot background |
626
+ | `blockedBg` | `#e5e7eb` | Blocked/outside slot background |
627
+ | `customBg` | `#e0f2fe` | Custom slot background |
628
+
629
+ ---
630
+
631
+ ## Demo App
632
+
633
+ ```bash
634
+ git clone https://github.com/omidrahmati2000/react-slot-scheduler
635
+ cd react-slot-scheduler/example
636
+ npm install
637
+ npm run dev # โ†’ http://localhost:5173
638
+ ```
639
+
640
+ ### Deploy Demo on Vercel
641
+
642
+ The example app is deploy-ready for Vercel.
643
+
644
+ 1. Import this repository into Vercel
645
+ 2. Set Root Directory to `example`
646
+ 3. Deploy
647
+
648
+ Detailed guide: [Vercel Deployment Guide](./docs/usage/vercel-deployment.md)
649
+
650
+ | Tab | Mode | What it shows |
651
+ |---|---|---|
652
+ | ๐Ÿ“… Appointment | time-grid | FA โ†” EN, drag & drop, multi-select, 4 color swatches |
653
+ | ๐Ÿข Meeting Room | time-grid | Live drag-rescheduling with React state |
654
+ | ๐Ÿ—‚๏ธ Task Timeline | task-timeline | Bilingual tasks with assignee, dark/light |
655
+ | ๐Ÿงฉ Resource Planner | resource-planner | Multi-resource (rooms + doctors), FA/EN |
656
+ | ๐ŸŽจ Themes | โ€” | 6 presets: Ocean, Forest, Sunset, Midnight, Sakura, Gold |
657
+
658
+ ---
659
+
660
+ ## Scripts
661
+
662
+ ```bash
663
+ npm run build # compile โ†’ dist/
664
+ npm run typecheck # TypeScript strict check
665
+ npm run test # vitest run
666
+ npm run test:coverage # coverage report
667
+ ```
668
+
669
+ ---
670
+
671
+ ## Deep Usage Guides
672
+
673
+ - [Model Selection Guide](./docs/usage/model-selection-guide.md)
674
+ - [Conflict Policy Guide](./docs/usage/conflict-policy-guide.md)
675
+ - [Resource Planner Guide](./docs/usage/resource-planner-guide.md)
676
+ - [Gantt Interactions Guide](./docs/usage/gantt-interactions-guide.md)
677
+ - [Vercel Deployment Guide](./docs/usage/vercel-deployment.md)
678
+
679
+ ## AI Usage & Attribution
680
+
681
+ - This project may use AI-assisted drafting for some docs, examples, or refactors.
682
+ - All published code and documentation are manually reviewed and validated before release.
683
+ - No generated output is accepted as-is without human verification.
684
+ - If a contribution includes AI-assisted content, keep prompts/private data out of commits and ensure licensing/compliance checks are completed.
685
+
686
+ ## Documentation Standard
687
+
688
+ - All public documentation and code examples are maintained in English.
689
+ - Root `README.md` is the primary reference for installation, usage patterns, API, and production integration.
690
+
691
+ ---
692
+
693
+ ## Roadmap
694
+
695
+ - [ ] Month view
696
+ - [ ] Event resize โ€” drag slot start/end edges
697
+ - [x] Click-to-create (hour mode) โ€” drag on empty area to define a new slot
698
+ - [ ] Cross-day slots โ€” slots spanning midnight
699
+
700
+ PRs and issues welcome โ€” see [CONTRIBUTING.md](./CONTRIBUTING.md).
701
+
702
+ ---
703
+
704
+ ## License
705
+
706
+ MIT ยฉ [Omid Rahmati](https://github.com/omidrahmati2000)
707
+
708
+ ## Author
709
+
710
+ Omid Rahmati
711
+ Email: `omidrahmati2000@gmail.com`
712
+
713
+ ---
714
+
715
+ <div align="center">
716
+ <sub>React 18+ ยท Zero dependencies ยท 27 kB gzip ยท TypeScript-first ยท RTL/LTR ยท 3 scheduler modes</sub>
717
+ </div>