@marco.colia/react-fast-scheduler 1.0.2 → 2.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/LICENSE CHANGED
@@ -1,22 +1,22 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 marco
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
22
-
1
+ MIT License
2
+
3
+ Copyright (c) 2026 marco
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md CHANGED
@@ -1,215 +1,251 @@
1
- # react-fast-scheduler
2
-
3
- A lightweight React scheduling library scaffold with CI/CD publishing.
4
-
5
- ![img.png](https://bg-so-1.zippyimage.com/2026/03/20/9f3bd8a7a76d94f6e110aa0a0bef11d3.png)
6
-
7
- ## Install
8
-
9
- ```bash
10
- npm i @marco.colia/react-fast-scheduler
11
- ```
12
-
13
- ## Usage
14
-
15
- ```tsx
16
- import { useState } from "react";
17
- import { Scheduler } from "@marco.colia/react-fast-scheduler";
18
- import "@marco.colia/react-fast-scheduler/styles.css";
19
- import type { BaseSchedulerResource } from "@marco.colia/react-fast-scheduler";
20
-
21
- type Staff = BaseSchedulerResource<number> & {
22
- firstName: string;
23
- lastName: string;
24
- appointmentColorClass?: string;
25
- };
26
-
27
- type Appointment = {
28
- id: number;
29
- staffId: number;
30
- description?: string;
31
- customerName: string;
32
- title: string;
33
- start: string; // ISO string
34
- end: string; // ISO string
35
- };
36
-
37
- const initialStaff: Staff[] = [
38
- { id: 1, label: "Room A", firstName: "Anna", lastName: "Rossi", appointmentColorClass: "bg-amber-100" },
39
- { id: 2, label: "Room B", firstName: "Luca", lastName: "Bianchi", appointmentColorClass: "bg-blue-100" }
40
- ];
41
-
42
- const initialAppointments: Appointment[] = [
43
- {
44
- id: 100,
45
- staffId: 1,
46
- description: "First consultation",
47
- customerName: "Mario Rossi",
48
- title: "Consultation",
49
- start: "2026-03-20T09:00:00.000Z",
50
- end: "2026-03-20T09:30:00.000Z"
51
- }
52
- ];
53
-
54
- export function SchedulerExample() {
55
- const [selectedDate, setSelectedDate] = useState(new Date("2026-03-20T00:00:00.000Z"));
56
- const [appointments, setAppointments] = useState<Appointment[]>(initialAppointments);
57
-
58
- return (
59
- <Scheduler<Appointment, Staff, number>
60
- resources={initialStaff}
61
- appointments={appointments}
62
- selectedDate={selectedDate}
63
- onSelectedDateChange={setSelectedDate}
64
- prevButtonLabel="Previous day"
65
- nextButtonLabel="Next day"
66
- adapter={{
67
- getId: (a) => a.id,
68
- getResourceId: (a) => a.staffId,
69
- getStart: (a) => a.start,
70
- getEnd: (a) => a.end,
71
- getTitle: (a) => `${a.customerName} - ${a.title}`
72
- }}
73
- renderDatePicker={({ selectedDate: value, onSelectedDateChange }) => (
74
- <input
75
- type="date"
76
- value={value.toISOString().slice(0, 10)}
77
- onChange={(event) => onSelectedDateChange(new Date(`${event.target.value}T00:00:00`))}
78
- />
79
- )}
80
- onAppointmentChange={async ({ appointment, next }) => {
81
- setAppointments((prev) =>
82
- prev.map((a) =>
83
- a.id === appointment.id
84
- ? { ...a, staffId: next.resourceId, start: next.start.toISOString(), end: next.end.toISOString() }
85
- : a
86
- )
87
- );
88
- }}
89
- getResourceAppointmentBackground={(resource) => resource.appointmentColorClass}
90
- renderResourceHeader={(resource) => <strong>{resource.label}</strong>}
91
- renderAppointment={({ appointment, onPointerDown, onResizePointerDown, appointmentBackgroundColor }) => (
92
- <div
93
- onPointerDown={onPointerDown}
94
- className={`relative h-full cursor-grab overflow-hidden rounded-md border border-slate-300 p-2 pb-5 ${appointmentBackgroundColor ?? "bg-slate-100"}`}
95
- >
96
- <div className="text-xs font-semibold">{appointment.title}</div>
97
- {appointment.raw.description ? (
98
- <div className="mt-1 text-[10px] font-medium text-slate-500">
99
- {appointment.raw.description}
100
- </div>
101
- ) : null}
102
- <div
103
- role="button"
104
- aria-label="Resize appointment"
105
- onPointerDown={(e) => {
106
- e.stopPropagation();
107
- onResizePointerDown(e);
108
- }}
109
- className="absolute inset-x-1 bottom-1 h-2 cursor-ns-resize rounded-full bg-slate-300/90 hover:bg-slate-400"
110
- />
111
- </div>
112
- )}
113
- dayStart="09:00"
114
- dayEnd="18:00"
115
- />
116
- );
117
- }
118
- ```
119
-
120
- Main props you need to pass:
121
- - `resources`: your columns (staff, rooms, etc), each with at least `{ id, label }`.
122
- - `appointments`: raw items from your backend.
123
- - `adapter`: functions that map your raw appointment shape to scheduler fields.
124
- - `onAppointmentChange`: optional generic callback fired after move/resize drop.
125
- - `onPersistMoveResize`: optional legacy persistence callback (used if `onAppointmentChange` is not provided).
126
- - `getResourceAppointmentBackground`: optional function to provide appointment background color per resource.
127
- - `prevButtonLabel` and `nextButtonLabel`: custom labels for toolbar navigation buttons.
128
- - `renderDatePicker`: inject your own date picker UI (for example shadcn calendar).
129
- - `renderResourceHeader`, `renderAppointment`: rendering hooks for your UI.
130
-
131
- ### shadcn Calendar Date Picker
132
-
133
- ```tsx
134
- import { Button } from "@/components/ui/button";
135
- import { Calendar } from "@/components/ui/calendar";
136
- import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
137
-
138
- <Scheduler
139
- // ...other props
140
- renderDatePicker={({ selectedDate, onSelectedDateChange }) => (
141
- <Popover>
142
- <PopoverTrigger asChild>
143
- <Button variant="outline">{selectedDate.toDateString()}</Button>
144
- </PopoverTrigger>
145
- <PopoverContent className="w-auto p-0">
146
- <Calendar
147
- mode="single"
148
- selected={selectedDate}
149
- onSelect={(date) => date && onSelectedDateChange(date)}
150
- initialFocus
151
- />
152
- </PopoverContent>
153
- </Popover>
154
- )}
155
- />;
156
- ```
157
-
158
- ## Styles
159
-
160
- The package ships a precompiled CSS file generated from Tailwind:
161
-
162
- ```tsx
163
- import "@marco.colia/react-fast-scheduler/styles.css";
164
- ```
165
-
166
- Minimal app entry example:
167
-
168
- ```tsx
169
- import React from "react";
170
- import ReactDOM from "react-dom/client";
171
- import { SchedulerExample } from "./SchedulerExample";
172
- import "@marco.colia/react-fast-scheduler/styles.css";
173
-
174
- ReactDOM.createRoot(document.getElementById("root")!).render(<SchedulerExample />);
175
- ```
176
-
177
- If you work inside this library repo (local demo), import the Tailwind v4 source entry:
178
-
179
- ```tsx
180
- import "../src/global.css";
181
- ```
182
-
183
- You can override tokens in your app:
184
-
185
- ```css
186
- :root {
187
- --rfs-bg: #f8fafc;
188
- --rfs-surface: #ffffff;
189
- --rfs-border: #e2e8f0;
190
- --rfs-text: #0f172a;
191
- --rfs-muted: #64748b;
192
- }
193
- ```
194
-
195
- ## Development
196
-
197
- ```bash
198
- npm install
199
- npm run dev:demo
200
- npm run lint
201
- npm run test
202
- npm run build
203
- ```
204
-
205
- `dev/main.tsx` contains a minimal runnable scheduler demo used by `npm run dev:demo`.
206
- Tailwind v4 source styles are in `src/global.css`, and the demo imports them from `dev/main.tsx`.
207
-
208
- ## Releasing
209
-
210
- This repo uses [Changesets](https://github.com/changesets/changesets) and GitHub Actions.
211
-
212
- 1. Create a changeset: `npx changeset`
213
- 2. Merge to `main`
214
- 3. The `Release` workflow opens/updates a release PR
215
- 4. Merging that PR publishes to npm (via npm Trusted Publishing/OIDC, or `NPM_TOKEN` if configured)
1
+ # react-fast-scheduler
2
+
3
+ A lightweight React scheduling library scaffold with CI/CD publishing.
4
+
5
+ ![img.png](https://bg-so-1.zippyimage.com/2026/03/20/9f3bd8a7a76d94f6e110aa0a0bef11d3.png)
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm i @marco.colia/react-fast-scheduler
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```tsx
16
+ import { useState } from "react";
17
+ import { Scheduler } from "@marco.colia/react-fast-scheduler";
18
+ import "@marco.colia/react-fast-scheduler/styles.css";
19
+ import type { BaseSchedulerResource } from "@marco.colia/react-fast-scheduler";
20
+
21
+ type Staff = BaseSchedulerResource<number> & {
22
+ firstName: string;
23
+ lastName: string;
24
+ };
25
+
26
+ type Appointment = {
27
+ id: number;
28
+ staffId: number;
29
+ description?: string;
30
+ customerName: string;
31
+ title: string;
32
+ start: string; // ISO string
33
+ end: string; // ISO string
34
+ };
35
+
36
+ const initialStaff: Staff[] = [
37
+ { id: 1, label: "Room A", firstName: "Anna", lastName: "Rossi" },
38
+ { id: 2, label: "Room B", firstName: "Luca", lastName: "Bianchi" }
39
+ ];
40
+
41
+ const initialAppointments: Appointment[] = [
42
+ {
43
+ id: 100,
44
+ staffId: 1,
45
+ description: "First consultation",
46
+ customerName: "Mario Rossi",
47
+ title: "Consultation",
48
+ start: "2026-03-20T09:00:00.000Z",
49
+ end: "2026-03-20T09:30:00.000Z"
50
+ }
51
+ ];
52
+
53
+ export function SchedulerExample() {
54
+ const [selectedDate, setSelectedDate] = useState(new Date("2026-03-20T00:00:00.000Z"));
55
+ const [appointments, setAppointments] = useState<Appointment[]>(initialAppointments);
56
+
57
+ return (
58
+ <Scheduler<Appointment, Staff, number>
59
+ resources={initialStaff}
60
+ appointments={appointments}
61
+ selectedDate={selectedDate}
62
+ onSelectedDateChange={setSelectedDate}
63
+ prevButtonLabel="Previous day"
64
+ nextButtonLabel="Next day"
65
+ adapter={{
66
+ getId: (a) => a.id,
67
+ getResourceId: (a) => a.staffId,
68
+ getStart: (a) => a.start,
69
+ getEnd: (a) => a.end,
70
+ getTitle: (a) => `${a.customerName} - ${a.title}`
71
+ }}
72
+ onAppointmentChange={async ({ appointment, next }) => {
73
+ setAppointments((prev) =>
74
+ prev.map((a) =>
75
+ a.id === appointment.id
76
+ ? { ...a, staffId: next.resourceId, start: next.start.toISOString(), end: next.end.toISOString() }
77
+ : a
78
+ )
79
+ );
80
+ }}
81
+ resourceAppointmentClassMap={{
82
+ "1": "bg-amber-100 dark:bg-amber-950/40",
83
+ "2": "bg-blue-100 dark:bg-blue-950/40"
84
+ }}
85
+ dayStart="09:00"
86
+ dayEnd="18:00"
87
+ />
88
+ );
89
+ }
90
+ ```
91
+
92
+ `renderAppointment` is optional. If you omit it, the scheduler uses the built-in appointment card with light/dark mode support.
93
+ `renderDatePicker` is optional. If you omit it, the scheduler uses a built-in native date input.
94
+ `onAppointmentChange` is a simple callback: use it to update your state and run your own side effects.
95
+
96
+ Main props you need to pass:
97
+ - `resources`: your columns (staff, rooms, etc), each with at least `{ id, label }`.
98
+ - `appointments`: raw items from your backend.
99
+ - `adapter`: functions that map your raw appointment shape to scheduler fields.
100
+ - `onAppointmentChange`: optional generic callback fired after move/resize drop.
101
+ - `onPersistMoveResize`: optional legacy persistence callback (used if `onAppointmentChange` is not provided).
102
+ - `resourceAppointmentClassMap`: optional map of `resourceId -> className` for per-resource appointment colors/styles.
103
+ - `getResourceAppointmentColorToken`: optional function to provide a semantic color token per resource (for example `"amber"`).
104
+ - `appointmentColorTokenClassMap`: optional map to override token -> class resolution.
105
+ - `getResourceAppointmentAppearance`: optional low-level function to provide appointment `className` per resource.
106
+ - `getResourceAppointmentBackground`: legacy optional function to provide appointment background as a string.
107
+ - `prevButtonLabel` and `nextButtonLabel`: custom labels for toolbar navigation buttons.
108
+ - `renderDatePicker`: optional render hook for a custom date picker UI (for example shadcn calendar).
109
+ - `renderResourceHeader`: optional render hook for resource column headers.
110
+ - `renderAppointment`: optional render hook to fully customize appointment cards.
111
+
112
+ ### Resource classes by id (recommended)
113
+
114
+ ```tsx
115
+ <Scheduler
116
+ // ...other props
117
+ resourceAppointmentClassMap={{
118
+ "room-a": "bg-yellow-100 dark:bg-yellow-950/40",
119
+ "room-b": "bg-rose-100 dark:bg-rose-950/40"
120
+ }}
121
+ />
122
+ ```
123
+
124
+ ### Resource color tokens (optional)
125
+
126
+ ```tsx
127
+ <Scheduler
128
+ // ...other props
129
+ getResourceAppointmentColorToken={(resource) => resource.category}
130
+ appointmentColorTokenClassMap={{
131
+ vip: "bg-yellow-100 dark:bg-yellow-950/40",
132
+ urgent: "bg-rose-100 dark:bg-rose-950/40"
133
+ }}
134
+ />
135
+ ```
136
+
137
+ ### Custom appointment renderer (optional)
138
+
139
+ ```tsx
140
+ <Scheduler
141
+ // ...other props
142
+ renderAppointment={({ appointment, onPointerDown, onResizePointerDown, appointmentAppearance, appointmentBackgroundColor }) => (
143
+ <div
144
+ onPointerDown={onPointerDown}
145
+ className={`relative h-full cursor-grab overflow-hidden rounded-md border border-slate-300 p-2 pb-5 ${
146
+ appointmentAppearance?.className ?? appointmentBackgroundColor ?? "bg-slate-100"
147
+ }`}
148
+ >
149
+ <div className="text-xs font-semibold">{appointment.title}</div>
150
+ {appointment.raw.description ? (
151
+ <div className="mt-1 text-[10px] font-medium text-slate-500">{appointment.raw.description}</div>
152
+ ) : null}
153
+ <div
154
+ role="button"
155
+ aria-label="Resize appointment"
156
+ onPointerDown={(e) => {
157
+ e.stopPropagation();
158
+ onResizePointerDown(e);
159
+ }}
160
+ className="absolute inset-x-1 bottom-1 h-2 cursor-ns-resize rounded-full bg-slate-300/90 hover:bg-slate-400"
161
+ />
162
+ </div>
163
+ )}
164
+ />
165
+ ```
166
+
167
+ ### shadcn Calendar Date Picker
168
+
169
+ ```tsx
170
+ import { Button } from "@/components/ui/button";
171
+ import { Calendar } from "@/components/ui/calendar";
172
+ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
173
+
174
+ <Scheduler
175
+ // ...other props
176
+ renderDatePicker={({ selectedDate, onSelectedDateChange }) => (
177
+ <Popover>
178
+ <PopoverTrigger asChild>
179
+ <Button variant="outline">{selectedDate.toDateString()}</Button>
180
+ </PopoverTrigger>
181
+ <PopoverContent className="w-auto p-0">
182
+ <Calendar
183
+ mode="single"
184
+ selected={selectedDate}
185
+ onSelect={(date) => date && onSelectedDateChange(date)}
186
+ initialFocus
187
+ />
188
+ </PopoverContent>
189
+ </Popover>
190
+ )}
191
+ />;
192
+ ```
193
+
194
+ ## Styles
195
+
196
+ The package ships a precompiled CSS file generated from Tailwind:
197
+
198
+ ```tsx
199
+ import "@marco.colia/react-fast-scheduler/styles.css";
200
+ ```
201
+
202
+ Minimal app entry example:
203
+
204
+ ```tsx
205
+ import React from "react";
206
+ import ReactDOM from "react-dom/client";
207
+ import { SchedulerExample } from "./SchedulerExample";
208
+ import "@marco.colia/react-fast-scheduler/styles.css";
209
+
210
+ ReactDOM.createRoot(document.getElementById("root")!).render(<SchedulerExample />);
211
+ ```
212
+
213
+ If you work inside this library repo (local demo), import the Tailwind v4 source entry:
214
+
215
+ ```tsx
216
+ import "../src/global.css";
217
+ ```
218
+
219
+ You can override tokens in your app:
220
+
221
+ ```css
222
+ :root {
223
+ --rfs-bg: #f8fafc;
224
+ --rfs-surface: #ffffff;
225
+ --rfs-border: #e2e8f0;
226
+ --rfs-text: #0f172a;
227
+ --rfs-muted: #64748b;
228
+ }
229
+ ```
230
+
231
+ ## Development
232
+
233
+ ```bash
234
+ npm install
235
+ npm run dev:demo
236
+ npm run lint
237
+ npm run test
238
+ npm run build
239
+ ```
240
+
241
+ `dev/main.tsx` contains a minimal runnable scheduler demo used by `npm run dev:demo`.
242
+ Tailwind v4 source styles are in `src/global.css`, and the demo imports them from `dev/main.tsx`.
243
+
244
+ ## Releasing
245
+
246
+ This repo uses [Changesets](https://github.com/changesets/changesets) and GitHub Actions.
247
+
248
+ 1. Create a changeset: `npx changeset`
249
+ 2. Merge to `main`
250
+ 3. The `Release` workflow opens/updates a release PR
251
+ 4. Merging that PR publishes to npm (via npm Trusted Publishing/OIDC, or `NPM_TOKEN` if configured)