@innosolutions/inno-calendar 1.0.49 → 1.0.51
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/AGENT.md +1288 -359
- package/dist/{agenda-widget-DhCPt2vI.js → agenda-widget-Dp8XHfXT.js} +1248 -1185
- package/dist/agenda-widget-Dp8XHfXT.js.map +1 -0
- package/dist/agenda-widget-MtzFiON7.cjs +2 -0
- package/dist/agenda-widget-MtzFiON7.cjs.map +1 -0
- package/dist/components/event/event-card.d.ts +21 -2
- package/dist/components/event/event-card.d.ts.map +1 -1
- package/dist/components/header/calendar-header.d.ts +10 -2
- package/dist/components/header/calendar-header.d.ts.map +1 -1
- package/dist/components/index.cjs +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.mjs +49 -35
- package/dist/components/inno-calendar.d.ts +45 -2
- package/dist/components/inno-calendar.d.ts.map +1 -1
- package/dist/components/ui/dialog.d.ts +35 -0
- package/dist/components/ui/dialog.d.ts.map +1 -0
- package/dist/components/ui/index.d.ts +2 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/sheet.d.ts +35 -0
- package/dist/components/ui/sheet.d.ts.map +1 -0
- package/dist/components/views/timeline-view.d.ts +10 -1
- package/dist/components/views/timeline-view.d.ts.map +1 -1
- package/dist/core/context/drag-drop-context.d.ts +7 -1
- package/dist/core/context/drag-drop-context.d.ts.map +1 -1
- package/dist/core/index.cjs +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.mjs +1 -1
- package/dist/core/types.d.ts +81 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +206 -192
- package/dist/presets/index.cjs +1 -1
- package/dist/presets/index.mjs +1 -1
- package/dist/{slot-selection-context-D1495hEJ.js → slot-selection-context-BwghpxKA.js} +233 -231
- package/dist/slot-selection-context-BwghpxKA.js.map +1 -0
- package/dist/slot-selection-context-CHSaOPWP.cjs +2 -0
- package/dist/slot-selection-context-CHSaOPWP.cjs.map +1 -0
- package/dist/{tailwind-calendar-BmJa4HhQ.js → tailwind-calendar-Ctfffnbn.js} +2 -2
- package/dist/{tailwind-calendar-BmJa4HhQ.js.map → tailwind-calendar-Ctfffnbn.js.map} +1 -1
- package/dist/{tailwind-calendar-CJmOutn1.cjs → tailwind-calendar-Gy1Uc9ZK.cjs} +2 -2
- package/dist/{tailwind-calendar-CJmOutn1.cjs.map → tailwind-calendar-Gy1Uc9ZK.cjs.map} +1 -1
- package/dist/week-view-ITRsM6y1.cjs +11 -0
- package/dist/week-view-ITRsM6y1.cjs.map +1 -0
- package/dist/{week-view-aRPB2cjn.js → week-view-vnIcmoVr.js} +1564 -1405
- package/dist/week-view-vnIcmoVr.js.map +1 -0
- package/package.json +3 -2
- package/dist/agenda-widget-B-AVTnqM.cjs +0 -2
- package/dist/agenda-widget-B-AVTnqM.cjs.map +0 -1
- package/dist/agenda-widget-DhCPt2vI.js.map +0 -1
- package/dist/slot-selection-context-BCsC7WT7.cjs +0 -2
- package/dist/slot-selection-context-BCsC7WT7.cjs.map +0 -1
- package/dist/slot-selection-context-D1495hEJ.js.map +0 -1
- package/dist/week-view-D19YRBQC.cjs +0 -11
- package/dist/week-view-D19YRBQC.cjs.map +0 -1
- package/dist/week-view-aRPB2cjn.js.map +0 -1
package/AGENT.md
CHANGED
|
@@ -1,42 +1,78 @@
|
|
|
1
1
|
# @innosolutions/inno-calendar — AI Agent Reference
|
|
2
2
|
|
|
3
|
-
> **Version
|
|
4
|
-
>
|
|
3
|
+
> **Version 1.0.49** | React 18+/19 | TypeScript 5.9 | Vite 7 library-mode
|
|
4
|
+
> Headless-first, fully customizable React calendar for enterprise applications.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Table of Contents
|
|
9
|
+
|
|
10
|
+
1. [What This Package Is](#1-what-this-package-is)
|
|
11
|
+
2. [npm Scripts](#2-npm-scripts)
|
|
12
|
+
3. [Package Exports](#3-package-exports)
|
|
13
|
+
4. [Core Types](#4-core-types)
|
|
14
|
+
5. [InnoCalendar Props](#5-innocalendar-props)
|
|
15
|
+
6. [Slots System](#6-slots-system)
|
|
16
|
+
7. [ClassNames API](#7-classnames-api)
|
|
17
|
+
8. [EventCard & EventBlock](#8-eventcard--eventblock)
|
|
18
|
+
9. [EventPopover Component](#9-eventpopover-component)
|
|
19
|
+
10. [Preferences System](#10-preferences-system)
|
|
20
|
+
11. [Widget Components](#11-widget-components)
|
|
21
|
+
12. [Filter Components](#12-filter-components)
|
|
22
|
+
13. [Settings Components](#13-settings-components)
|
|
23
|
+
14. [Context & Hooks](#14-context--hooks)
|
|
24
|
+
15. [Drag & Drop](#15-drag--drop)
|
|
25
|
+
16. [Core Utilities](#16-core-utilities)
|
|
26
|
+
17. [Presets](#17-presets)
|
|
27
|
+
18. [CSS & Theming](#18-css--theming)
|
|
28
|
+
19. [Constants](#19-constants)
|
|
29
|
+
20. [Integration Guide](#20-integration-guide)
|
|
30
|
+
21. [Full Export Catalog](#21-full-export-catalog)
|
|
31
|
+
22. [File Naming Conventions](#22-file-naming-conventions)
|
|
32
|
+
23. [Styling Patterns](#23-styling-patterns)
|
|
33
|
+
24. [Biome Configuration](#24-biome-configuration)
|
|
34
|
+
25. [State Synchronization Guide](#25-state-synchronization-guide)
|
|
35
|
+
26. [Critical Rules](#26-critical-rules)
|
|
9
36
|
|
|
10
37
|
---
|
|
11
38
|
|
|
12
39
|
## 1. What This Package Is
|
|
13
40
|
|
|
14
|
-
A headless-first
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
A **headless-first** React calendar component library — all view logic lives in hooks and contexts; every UI element is an optional, replaceable component.
|
|
42
|
+
|
|
43
|
+
### Feature Set
|
|
44
|
+
|
|
45
|
+
| Category | Features |
|
|
46
|
+
|---|---|
|
|
47
|
+
| **Views** | Day, Week, Month, Year, Agenda, Resource-Day, Resource-Week, Timeline-Day, Timeline-3Day, Timeline-Week |
|
|
48
|
+
| **Interaction** | Drag-to-select (time & day mode), event click, slot click, drag-and-drop reposition, event resize |
|
|
49
|
+
| **Customization** | 25+ component slots, 30+ classNames keys, render props, custom popover |
|
|
50
|
+
| **Data** | Generic `CalendarEvent<TData>`, adapter pattern, BYO data fetching |
|
|
51
|
+
| **Preferences** | localStorage persistence, locking, session mode, per-instance storage keys |
|
|
52
|
+
| **Filtering** | Built-in schedule type filter, user/participant filter, text search |
|
|
53
|
+
| **UI Extras** | Loading overlay, focus-event deep-linking, imperative ref API, settings panel, filter sidebar |
|
|
54
|
+
| **Embeddable** | AgendaWidget (standalone card), AgendaDropdown (bell-icon notification panel) |
|
|
55
|
+
| **Presets** | DefaultCalendar (styled), TailwindCalendar (Tailwind-optimized) |
|
|
56
|
+
|
|
57
|
+
### Tech Stack
|
|
58
|
+
|
|
59
|
+
| Layer | Technology |
|
|
60
|
+
|---|---|
|
|
61
|
+
| Runtime | React 18+/19, TypeScript 5.9 |
|
|
62
|
+
| Build | Vite 7 (library mode), vite-plugin-dts |
|
|
63
|
+
| Styling | TailwindCSS 4+, class-variance-authority (CVA), clsx, tailwind-merge |
|
|
64
|
+
| UI Primitives | Radix UI (optional peer deps: popover, tooltip, dropdown-menu, dialog, scroll-area, select, avatar) |
|
|
65
|
+
| Icons | lucide-react |
|
|
66
|
+
| Date handling | Native Date API (no external date library) |
|
|
67
|
+
| Linting | Biome 2.2 |
|
|
32
68
|
|
|
33
69
|
---
|
|
34
70
|
|
|
35
71
|
## 2. npm Scripts
|
|
36
72
|
|
|
37
73
|
```bash
|
|
38
|
-
npm run dev # Vite dev
|
|
39
|
-
npm run build #
|
|
74
|
+
npm run dev # Vite dev mode
|
|
75
|
+
npm run build # Production build (Vite + dts declarations)
|
|
40
76
|
npm run typecheck # tsc --noEmit
|
|
41
77
|
npm run lint # Biome lint check
|
|
42
78
|
npm run format # Biome format
|
|
@@ -46,110 +82,95 @@ npm run format # Biome format
|
|
|
46
82
|
|
|
47
83
|
## 3. Package Exports
|
|
48
84
|
|
|
85
|
+
Defined in `package.json`:
|
|
86
|
+
|
|
49
87
|
```jsonc
|
|
50
88
|
{
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
89
|
+
"main": "./dist/index.cjs",
|
|
90
|
+
"module": "./dist/index.js",
|
|
91
|
+
"types": "./dist/index.d.ts",
|
|
92
|
+
"exports": {
|
|
93
|
+
".": {
|
|
94
|
+
"import": "./dist/index.js",
|
|
95
|
+
"require": "./dist/index.cjs",
|
|
96
|
+
"types": "./dist/index.d.ts"
|
|
97
|
+
},
|
|
98
|
+
"./styles": "./dist/styles/index.css"
|
|
99
|
+
}
|
|
59
100
|
}
|
|
60
101
|
```
|
|
61
102
|
|
|
62
|
-
|
|
103
|
+
Consumer import patterns:
|
|
63
104
|
|
|
64
105
|
```tsx
|
|
65
|
-
|
|
66
|
-
import
|
|
106
|
+
// Components and types
|
|
107
|
+
import { InnoCalendar, type CalendarEvent } from '@innosolutions/inno-calendar';
|
|
108
|
+
// Styles (required for built-in components)
|
|
109
|
+
import '@innosolutions/inno-calendar/styles';
|
|
67
110
|
```
|
|
68
111
|
|
|
69
112
|
---
|
|
70
113
|
|
|
71
|
-
## 4. Core Types
|
|
114
|
+
## 4. Core Types
|
|
72
115
|
|
|
73
|
-
|
|
116
|
+
All types are defined in `src/core/types.ts` (1473 lines).
|
|
74
117
|
|
|
75
|
-
|
|
118
|
+
### CalendarEvent\<TData\>
|
|
76
119
|
|
|
77
120
|
```typescript
|
|
78
121
|
interface CalendarEvent<TData = Record<string, unknown>> extends IBaseEvent {
|
|
79
122
|
// Required (from IBaseEvent)
|
|
80
123
|
id: string;
|
|
124
|
+
title: string;
|
|
81
125
|
startDate: Date;
|
|
82
126
|
endDate: Date;
|
|
83
|
-
title: string;
|
|
84
127
|
|
|
85
|
-
// Optional
|
|
128
|
+
// Optional visual/behavioral
|
|
86
129
|
description?: string;
|
|
87
|
-
color?: TEventColor;
|
|
88
|
-
hexColor?: string;
|
|
130
|
+
color?: TEventColor;
|
|
131
|
+
hexColor?: string;
|
|
89
132
|
isAllDay?: boolean;
|
|
90
133
|
isMultiDay?: boolean;
|
|
91
134
|
isCanceled?: boolean;
|
|
92
135
|
isRecurring?: boolean;
|
|
93
|
-
resourceId?: string;
|
|
94
|
-
|
|
95
|
-
// Built-in filtering (optional — skip if you filter server-side)
|
|
96
|
-
scheduleTypeId?: number; // Enables built-in schedule type filtering
|
|
97
|
-
scheduleTypeName?: string; // Enables built-in search
|
|
98
|
-
participants?: ICalendarUser[]; // Enables built-in participant filtering
|
|
99
|
-
|
|
100
|
-
// @deprecated — still functional for backward compat, will be removed in next major.
|
|
101
|
-
// Migrate to the `data` bag. Built-in components read `data` first, then fall back here.
|
|
102
|
-
meetingTookPlace?: boolean; // → data.meetingTookPlace
|
|
103
|
-
isAccepted?: boolean; // → data.isAccepted
|
|
104
|
-
companyId?: number; // → data.companyId
|
|
105
|
-
cancelReason?: string | null; // → data.cancelReason
|
|
106
|
-
scheduleTypeCode?: string; // → data.scheduleTypeCode
|
|
107
|
-
opportunityId?: number; // → data.opportunityId
|
|
108
|
-
propertyId?: number; // → data.propertyId
|
|
109
|
-
projectId?: number; // → data.projectId
|
|
110
|
-
contactId?: number; // → data.contactId
|
|
111
|
-
user?: ICalendarUser; // → data.user
|
|
112
|
-
|
|
113
|
-
// Consumer's domain-specific data (RECOMMENDED)
|
|
114
|
-
data?: TData;
|
|
115
|
-
}
|
|
116
|
-
```
|
|
136
|
+
resourceId?: string;
|
|
117
137
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
fall back to the deprecated top-level fields.
|
|
138
|
+
// Built-in filtering (optional)
|
|
139
|
+
scheduleTypeId?: number;
|
|
140
|
+
scheduleTypeName?: string;
|
|
141
|
+
participants?: ICalendarUser[];
|
|
123
142
|
|
|
124
|
-
|
|
143
|
+
// Consumer data bag (RECOMMENDED for all domain fields)
|
|
144
|
+
data?: TData;
|
|
125
145
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
146
|
+
// @deprecated — still functional, will be removed in next major
|
|
147
|
+
meetingTookPlace?: boolean;
|
|
148
|
+
isAccepted?: boolean;
|
|
149
|
+
companyId?: number;
|
|
150
|
+
cancelReason?: string | null;
|
|
151
|
+
scheduleTypeCode?: string;
|
|
152
|
+
opportunityId?: number;
|
|
153
|
+
propertyId?: number;
|
|
154
|
+
projectId?: number;
|
|
155
|
+
contactId?: number;
|
|
156
|
+
user?: ICalendarUser;
|
|
157
|
+
}
|
|
138
158
|
```
|
|
139
159
|
|
|
140
|
-
###
|
|
160
|
+
### IResource\<TData\>
|
|
141
161
|
|
|
142
162
|
```typescript
|
|
143
|
-
interface
|
|
163
|
+
interface IResource<TData = Record<string, unknown>> {
|
|
144
164
|
id: string;
|
|
145
165
|
name: string;
|
|
146
|
-
|
|
166
|
+
title?: string;
|
|
147
167
|
avatar?: string;
|
|
168
|
+
color?: TEventColor | string;
|
|
148
169
|
data?: TData;
|
|
149
170
|
}
|
|
150
171
|
```
|
|
151
172
|
|
|
152
|
-
### IScheduleType
|
|
173
|
+
### IScheduleType\<TData\>
|
|
153
174
|
|
|
154
175
|
```typescript
|
|
155
176
|
interface IScheduleType<TData = Record<string, unknown>> {
|
|
@@ -167,27 +188,100 @@ interface IScheduleType<TData = Record<string, unknown>> {
|
|
|
167
188
|
}
|
|
168
189
|
```
|
|
169
190
|
|
|
170
|
-
###
|
|
191
|
+
### ICalendarUser\<TData\>
|
|
171
192
|
|
|
172
193
|
```typescript
|
|
173
|
-
interface
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
194
|
+
interface ICalendarUser<TData = Record<string, unknown>> {
|
|
195
|
+
id: string;
|
|
196
|
+
name: string;
|
|
197
|
+
email?: string;
|
|
198
|
+
avatar?: string;
|
|
199
|
+
data?: TData;
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### ICalendarRefHandle
|
|
204
|
+
|
|
205
|
+
Imperative handle exposed via `React.forwardRef` on `<InnoCalendar>`:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
interface ICalendarRefHandle {
|
|
209
|
+
scrollToToday: () => void;
|
|
210
|
+
scrollToWorkingHours: () => void;
|
|
211
|
+
getViewRect: () => DOMRect | null;
|
|
212
|
+
focusEvent: (eventId: string) => void;
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Usage:
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
const ref = useRef<ICalendarRefHandle>(null);
|
|
220
|
+
<InnoCalendar ref={ref} events={events} />
|
|
221
|
+
// Later:
|
|
222
|
+
ref.current?.scrollToToday();
|
|
223
|
+
ref.current?.focusEvent('appointment-42');
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### ICalendarPreferences
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
interface ICalendarPreferences {
|
|
230
|
+
startHour: number; // 0–23
|
|
231
|
+
endHour: number; // 0–24
|
|
232
|
+
firstDayOfWeek: 0|1|2|3|4|5|6;
|
|
233
|
+
slotDuration: number;
|
|
234
|
+
showWeekends: boolean;
|
|
235
|
+
timeFormat: '12h' | '24h';
|
|
236
|
+
badgeVariant: TBadgeVariant;
|
|
237
|
+
showCanceledEvents: boolean;
|
|
177
238
|
}
|
|
178
239
|
```
|
|
179
240
|
|
|
180
|
-
###
|
|
241
|
+
### View Types
|
|
181
242
|
|
|
182
243
|
```typescript
|
|
183
|
-
type
|
|
244
|
+
type TCalendarView =
|
|
245
|
+
| 'day' | 'week' | 'month' | 'year' | 'agenda'
|
|
246
|
+
| 'resource-day' | 'resource-week'
|
|
247
|
+
| 'timeline-day' | 'timeline-3day' | 'timeline-week';
|
|
184
248
|
```
|
|
185
249
|
|
|
250
|
+
### Other Key Types
|
|
251
|
+
|
|
252
|
+
| Type | Purpose |
|
|
253
|
+
|---|---|
|
|
254
|
+
| `TEventColor` | `'blue'│'green'│'red'│'yellow'│'purple'│'orange'│'pink'│'teal'│'gray'│'indigo'` |
|
|
255
|
+
| `TBadgeVariant` | `'dot'│'colored'│'mixed'` |
|
|
256
|
+
| `TSlotDuration` | `15│30│60` |
|
|
257
|
+
| `TWorkingHours` | `Record<number, IWorkingHoursDay>` (0=Sun..6=Sat) |
|
|
258
|
+
| `ISelectionResult` | `{ startDate: Date; endDate: Date; resourceId?: string }` |
|
|
259
|
+
| `IDropResult<TData>` | Drag-drop result with event, newStartDate, newEndDate, newResourceId |
|
|
260
|
+
| `IDateRange` | `{ startDate: Date; endDate: Date }` |
|
|
261
|
+
| `IEventPosition` | `{ top, height, left, width, zIndex }` |
|
|
262
|
+
| `IPositionedEvent<TData>` | Event + IEventPosition + column info |
|
|
263
|
+
| `ICalendarFilters` | `{ scheduleTypeIds?, resourceIds?, search?, showCanceled?, dateRange?, [key]: unknown }` |
|
|
264
|
+
| `TSelectionMode` | `'time'│'day'` |
|
|
265
|
+
| `TEventDetailMode` | `'popover'│'dialog'│'sheet'│'custom'` — controls how event details are shown |
|
|
266
|
+
| `ICalendarHeaderConfig` | Object with 7 optional boolean keys controlling header section visibility |
|
|
267
|
+
|
|
268
|
+
### Callback Types
|
|
269
|
+
|
|
270
|
+
| Type | Signature |
|
|
271
|
+
|---|---|
|
|
272
|
+
| `TOnEventClick<TData>` | `(event, e?) => void` |
|
|
273
|
+
| `TOnSlotSelect` | `(selection: ISelectionResult) => void` |
|
|
274
|
+
| `TOnEventDrop<TData>` | `(event, newStart, newEnd, newResourceId?) => void` |
|
|
275
|
+
| `TOnEventResize<TData>` | `(event, newStart, newEnd) => void` |
|
|
276
|
+
| `TOnViewChange` | `(view: TCalendarView) => void` |
|
|
277
|
+
| `TOnDateChange` | `(date: Date) => void` |
|
|
278
|
+
| `TOnFiltersChange` | `(filters: ICalendarFilters) => void` |
|
|
279
|
+
|
|
186
280
|
---
|
|
187
281
|
|
|
188
282
|
## 5. InnoCalendar Props
|
|
189
283
|
|
|
190
|
-
|
|
284
|
+
`<InnoCalendar>` is the primary entry point. It wraps `InnoCalendarProvider` + `SlotSelectionProvider` + `DragDropProvider` internally.
|
|
191
285
|
|
|
192
286
|
```typescript
|
|
193
287
|
interface InnoCalendarProps<TEventData> {
|
|
@@ -197,12 +291,12 @@ interface InnoCalendarProps<TEventData> {
|
|
|
197
291
|
scheduleTypes?: IScheduleType[];
|
|
198
292
|
|
|
199
293
|
// === INITIAL STATE ===
|
|
200
|
-
initialView?: TCalendarView;
|
|
201
|
-
initialDate?: Date;
|
|
202
|
-
initialSelectedUserId?: string |
|
|
294
|
+
initialView?: TCalendarView; // default: 'week'
|
|
295
|
+
initialDate?: Date; // default: new Date()
|
|
296
|
+
initialSelectedUserId?: string | 'all'; // default: 'all'
|
|
203
297
|
initialScheduleTypeIds?: number[];
|
|
204
298
|
initialParticipantIds?: string[];
|
|
205
|
-
initialWorkingHoursView?:
|
|
299
|
+
initialWorkingHoursView?: 'default' | 'enabled' | 'disabled';
|
|
206
300
|
initialSearchQuery?: string;
|
|
207
301
|
|
|
208
302
|
// === PREFERENCES ===
|
|
@@ -219,33 +313,80 @@ interface InnoCalendarProps<TEventData> {
|
|
|
219
313
|
|
|
220
314
|
// === UI OPTIONS ===
|
|
221
315
|
className?: string;
|
|
222
|
-
showHeader?: boolean;
|
|
223
|
-
minSelectionMinutes?: number;
|
|
224
|
-
|
|
225
|
-
|
|
316
|
+
showHeader?: boolean; // default: true
|
|
317
|
+
minSelectionMinutes?: number; // default: 30
|
|
318
|
+
showMoreMode?: 'dayView' | 'popover' | 'expand'; // default: 'expand'
|
|
319
|
+
isLoading?: boolean; // default: false
|
|
226
320
|
|
|
227
321
|
// === CUSTOM RENDERING ===
|
|
228
|
-
renderPopover?: (props: {
|
|
229
|
-
event: CalendarEvent<TEventData>;
|
|
230
|
-
onClose: () => void;
|
|
231
|
-
}) => ReactNode;
|
|
232
|
-
|
|
233
|
-
// === EXTENSIBILITY ===
|
|
322
|
+
renderPopover?: (props: { event; onClose }) => ReactNode;
|
|
234
323
|
slots?: ICalendarSlots<TEventData>;
|
|
235
324
|
classNames?: ICalendarClassNames;
|
|
236
325
|
|
|
237
|
-
// ===
|
|
326
|
+
// === HEADER CONFIGURATION ===
|
|
327
|
+
headerConfig?: ICalendarHeaderConfig; // fine-grained header visibility
|
|
328
|
+
// see ICalendarHeaderConfig below
|
|
329
|
+
|
|
330
|
+
// === EVENT DETAIL MODE ===
|
|
331
|
+
eventDetailMode?: TEventDetailMode; // default: 'popover'
|
|
332
|
+
renderEventDetail?: (props: { event; onClose }) => ReactNode; // for 'custom' + override in dialog/sheet
|
|
333
|
+
|
|
334
|
+
// === CONTENT SLOTS ===
|
|
238
335
|
settingsContent?: ReactNode;
|
|
239
336
|
filterContent?: ReactNode;
|
|
240
337
|
headerActions?: ReactNode;
|
|
338
|
+
|
|
339
|
+
// === DEEP LINKING ===
|
|
340
|
+
focusEventId?: string | null;
|
|
341
|
+
|
|
342
|
+
// @deprecated — use showMoreMode instead
|
|
343
|
+
showMoreEventsInPopover?: boolean;
|
|
241
344
|
}
|
|
242
345
|
```
|
|
243
346
|
|
|
347
|
+
#### ICalendarHeaderConfig
|
|
348
|
+
|
|
349
|
+
Fine-grained visibility control for calendar header sections. Each key defaults to `true` when omitted.
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
interface ICalendarHeaderConfig {
|
|
353
|
+
showToday?: boolean; // Today button
|
|
354
|
+
showDateNavigator?: boolean; // Prev/Next arrows + date label
|
|
355
|
+
showCalendarViews?: boolean; // Day/Week/Month/Agenda dropdown
|
|
356
|
+
showResourceViews?: boolean; // Timeline/Resource dropdown
|
|
357
|
+
showSettings?: boolean; // Settings gear icon
|
|
358
|
+
showAddEvent?: boolean; // "+" add event button
|
|
359
|
+
showFilters?: boolean; // Filter row below header
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
#### TEventDetailMode
|
|
364
|
+
|
|
365
|
+
Controls how event details are displayed when clicking an event:
|
|
366
|
+
|
|
367
|
+
- `'popover'` — Radix popover anchored to the event card (default)
|
|
368
|
+
- `'dialog'` — Centered modal dialog overlay
|
|
369
|
+
- `'sheet'` — Slide-in side panel from the right
|
|
370
|
+
- `'custom'` — No built-in container; delegates to `renderEventDetail`
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
type TEventDetailMode = 'popover' | 'dialog' | 'sheet' | 'custom';
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Ref Support
|
|
377
|
+
|
|
378
|
+
`InnoCalendar` is wrapped with `forwardRef` and exposes `ICalendarRefHandle`:
|
|
379
|
+
|
|
380
|
+
```tsx
|
|
381
|
+
const calendarRef = useRef<ICalendarRefHandle>(null);
|
|
382
|
+
<InnoCalendar ref={calendarRef} events={events} />
|
|
383
|
+
```
|
|
384
|
+
|
|
244
385
|
---
|
|
245
386
|
|
|
246
|
-
## 6. Slots System
|
|
387
|
+
## 6. Slots System
|
|
247
388
|
|
|
248
|
-
Every visual region can be replaced
|
|
389
|
+
Every visual region can be replaced via `slots`:
|
|
249
390
|
|
|
250
391
|
```typescript
|
|
251
392
|
interface ICalendarSlots<TEventData, TScheduleTypeData, TResourceData> {
|
|
@@ -254,14 +395,14 @@ interface ICalendarSlots<TEventData, TScheduleTypeData, TResourceData> {
|
|
|
254
395
|
footer?: ComponentType<IFooterSlotProps<TEventData>>;
|
|
255
396
|
viewWrapper?: ComponentType<IViewWrapperSlotProps>;
|
|
256
397
|
|
|
257
|
-
// Day
|
|
398
|
+
// Day Cell (month/year views)
|
|
258
399
|
dayCell?: ComponentType<IDayCellSlotPropsExtended<TEventData>>;
|
|
259
400
|
dayCellHeader?: ComponentType<IDayCellAdornmentSlotProps<TEventData>>;
|
|
260
401
|
dayCellFooter?: ComponentType<IDayCellAdornmentSlotProps<TEventData>>;
|
|
261
402
|
dayCellStartAdornment?: ComponentType<IDayCellAdornmentSlotProps<TEventData>>;
|
|
262
403
|
dayCellEndAdornment?: ComponentType<IDayCellAdornmentSlotProps<TEventData>>;
|
|
263
404
|
|
|
264
|
-
// Time
|
|
405
|
+
// Time Grid (day/week views)
|
|
265
406
|
timeGutter?: ComponentType<ITimeGutterSlotProps>;
|
|
266
407
|
timeSlotBackground?: ComponentType<ITimeSlotBackgroundSlotProps>;
|
|
267
408
|
columnHeader?: ComponentType<IColumnHeaderSlotProps>;
|
|
@@ -276,20 +417,45 @@ interface ICalendarSlots<TEventData, TScheduleTypeData, TResourceData> {
|
|
|
276
417
|
resourceHeader?: ComponentType<IResourceHeaderSlotProps<TResourceData>>;
|
|
277
418
|
rowHeader?: ComponentType<IRowHeaderSlotProps<TResourceData>>;
|
|
278
419
|
|
|
279
|
-
//
|
|
420
|
+
// Filters
|
|
280
421
|
filter?: ComponentType<IFilterSlotProps<TScheduleTypeData>>;
|
|
281
422
|
|
|
282
|
-
// Empty
|
|
423
|
+
// Empty States
|
|
283
424
|
emptyCell?: ComponentType<IEmptyCellSlotProps>;
|
|
284
425
|
emptyState?: ComponentType<IEmptyStateSlotProps>;
|
|
426
|
+
|
|
427
|
+
// @deprecated
|
|
428
|
+
timeSlot?: ComponentType<ITimeSlotSlotProps>;
|
|
285
429
|
}
|
|
286
430
|
```
|
|
287
431
|
|
|
432
|
+
### Slot Props Interfaces
|
|
433
|
+
|
|
434
|
+
| Slot | Props Interface | Key Fields |
|
|
435
|
+
|---|---|---|
|
|
436
|
+
| `header` | `IHeaderSlotProps` | currentDate, view, onNavigate, onViewChange |
|
|
437
|
+
| `footer` | `IFooterSlotProps` | view, currentDate, events |
|
|
438
|
+
| `viewWrapper` | `IViewWrapperSlotProps` | view, children |
|
|
439
|
+
| `dayCell` | `IDayCellSlotPropsExtended` | date, events, isToday, isCurrentMonth, isWeekend, view, children, onDayClick |
|
|
440
|
+
| `dayCellHeader/Footer` | `IDayCellAdornmentSlotProps` | date, events, isToday, isCurrentMonth, isWeekend, view |
|
|
441
|
+
| `timeGutter` | `ITimeGutterSlotProps` | hour, formattedLabel, isFirst |
|
|
442
|
+
| `timeSlotBackground` | `ITimeSlotBackgroundSlotProps` | date, hour, minute, isWorkingHour, isToday |
|
|
443
|
+
| `columnHeader` | `IColumnHeaderSlotProps` | date, isToday, onDayClick, columnIndex |
|
|
444
|
+
| `currentTimeIndicator` | `ICurrentTimeIndicatorSlotProps` | currentTime, topOffset |
|
|
445
|
+
| `multiDayBanner` | `IMultiDayBannerSlotProps` | events, rangeStart, onEventClick |
|
|
446
|
+
| `eventCard` | `IEventCardSlotProps` | event, view, isCompact, onClick |
|
|
447
|
+
| `eventPopover` | `IEventPopoverSlotProps` | event, onClose, onEdit, onDelete |
|
|
448
|
+
| `resourceHeader` | `IResourceHeaderSlotProps` | resource |
|
|
449
|
+
| `rowHeader` | `IRowHeaderSlotProps` | resource, rowIndex |
|
|
450
|
+
| `filter` | `IFilterSlotProps` | scheduleTypes, filters, onFiltersChange |
|
|
451
|
+
| `emptyCell` | `IEmptyCellSlotProps` | date, isToday |
|
|
452
|
+
| `emptyState` | `IEmptyStateSlotProps` | view, dateRange |
|
|
453
|
+
|
|
288
454
|
---
|
|
289
455
|
|
|
290
|
-
## 7. ClassNames API
|
|
456
|
+
## 7. ClassNames API
|
|
291
457
|
|
|
292
|
-
|
|
458
|
+
`ICalendarClassNames` provides 30+ CSS class override keys. Classes are merged with defaults via `cn()`.
|
|
293
459
|
|
|
294
460
|
```typescript
|
|
295
461
|
interface ICalendarClassNames {
|
|
@@ -299,7 +465,7 @@ interface ICalendarClassNames {
|
|
|
299
465
|
footer?: string;
|
|
300
466
|
viewContainer?: string;
|
|
301
467
|
|
|
302
|
-
// Month
|
|
468
|
+
// Month View
|
|
303
469
|
monthGrid?: string;
|
|
304
470
|
weekdayHeader?: string;
|
|
305
471
|
weekdayLabel?: string;
|
|
@@ -311,7 +477,7 @@ interface ICalendarClassNames {
|
|
|
311
477
|
dayCellContent?: string;
|
|
312
478
|
dayCellFooter?: string;
|
|
313
479
|
|
|
314
|
-
// Week/Day
|
|
480
|
+
// Week/Day View
|
|
315
481
|
timeGutter?: string;
|
|
316
482
|
timeGutterLabel?: string;
|
|
317
483
|
timeSlot?: string;
|
|
@@ -343,339 +509,1102 @@ interface ICalendarClassNames {
|
|
|
343
509
|
|
|
344
510
|
---
|
|
345
511
|
|
|
346
|
-
## 8. EventCard
|
|
512
|
+
## 8. EventCard & EventBlock
|
|
513
|
+
|
|
514
|
+
Located in `src/components/event/event-card.tsx`.
|
|
515
|
+
|
|
516
|
+
### EventCard
|
|
517
|
+
|
|
518
|
+
Full-size event card used in day/week/timeline views. Renders differently based on `badgeVariant`:
|
|
519
|
+
|
|
520
|
+
| Badge Variant | Rendering |
|
|
521
|
+
|---|---|
|
|
522
|
+
| `'colored'` | Filled background with accent color, white/dark text |
|
|
523
|
+
| `'dot'` | White background with colored left-border accent |
|
|
524
|
+
| `'mixed'` | Light tinted background with colored left-border |
|
|
347
525
|
|
|
348
|
-
|
|
349
|
-
|
|
526
|
+
Key props:
|
|
527
|
+
|
|
528
|
+
| Prop | Type | Default | Description |
|
|
529
|
+
|---|---|---|---|
|
|
530
|
+
| `event` | `CalendarEvent<TData>` | required | Event data |
|
|
531
|
+
| `variant` | `'full'\|'compact'\|'dot'` | `'full'` | Visual layout |
|
|
532
|
+
| `badgeVariant` | `TBadgeVariant` | `'colored'` | Color treatment |
|
|
533
|
+
| `disablePopover` | `boolean` | `false` | Skip built-in popover, fire `onClick` |
|
|
534
|
+
| `renderPopover` | `(props) => ReactNode` | — | Custom content inside the default popover shell |
|
|
535
|
+
| `enableDrag` | `boolean` | `false` | Enable HTML5 drag & drop |
|
|
536
|
+
| `eventDetailMode` | `TEventDetailMode` | `'popover'` | Container for event details: popover, dialog, sheet, custom |
|
|
537
|
+
| `renderEventDetail` | `(props) => ReactNode` | — | Custom renderer for 'custom' mode; also inner content for dialog/sheet |
|
|
538
|
+
|
|
539
|
+
Built-in cards display enrichment data read from `event.data` first, with fallback to deprecated top-level fields:
|
|
350
540
|
|
|
351
541
|
```typescript
|
|
352
|
-
//
|
|
542
|
+
// Runtime enrichment reading pattern:
|
|
353
543
|
const eventData = event.data as Record<string, unknown> | undefined;
|
|
354
|
-
const meetingTookPlace =
|
|
355
|
-
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
(eventData?.scheduleTypeName as string) ||
|
|
359
|
-
(eventData?.typeName as string);
|
|
360
|
-
const productName = eventData?.productName as string;
|
|
361
|
-
const nrParticipant = eventData?.nrParticipant as number;
|
|
362
|
-
const siteName = eventData?.siteName as string;
|
|
363
|
-
const siteCity = eventData?.siteCity as string;
|
|
364
|
-
const locationStr = [siteName, siteCity].filter(Boolean).join(", ");
|
|
544
|
+
const meetingTookPlace = (eventData?.meetingTookPlace as boolean) ?? event.meetingTookPlace ?? false;
|
|
545
|
+
const typeName = eventData?.typeName as string | undefined;
|
|
546
|
+
const siteName = eventData?.siteName as string | undefined;
|
|
547
|
+
const nrParticipant = eventData?.nrParticipant as number | undefined;
|
|
365
548
|
```
|
|
366
549
|
|
|
367
|
-
###
|
|
550
|
+
### EventBlock
|
|
551
|
+
|
|
552
|
+
Compact event chip for month/year view cells. Displays title with optional color dot.
|
|
553
|
+
|
|
554
|
+
### MultiDayEventBar
|
|
555
|
+
|
|
556
|
+
Horizontal bar for multi-day/all-day events in the banner strip above day/week time grids.
|
|
368
557
|
|
|
369
|
-
|
|
370
|
-
All-day events show **"All day"** instead of confusing raw times like "00:00 - 23:59".
|
|
558
|
+
### Exports
|
|
371
559
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
560
|
+
```typescript
|
|
561
|
+
export { EventCard, type EventCardProps }
|
|
562
|
+
export { EventBlock, type EventBlockProps }
|
|
563
|
+
export { MultiDayEventBar, type MultiDayEventBarProps }
|
|
564
|
+
export { getEventColorClasses } // Utility: TEventColor → { bg, text, border }
|
|
565
|
+
```
|
|
378
566
|
|
|
379
|
-
|
|
380
|
-
midnight-to-midnight time boundaries (00:00:00 start, 23:59/00:00 end).
|
|
567
|
+
---
|
|
381
568
|
|
|
382
|
-
|
|
383
|
-
(banner display) even when they span only a single day.
|
|
569
|
+
## 9. EventPopover Component
|
|
384
570
|
|
|
385
|
-
|
|
571
|
+
Located in `src/components/event/event-popover.tsx` (1519 lines).
|
|
386
572
|
|
|
387
|
-
|
|
388
|
-
| --------- | ----------------------------------------------------------------- |
|
|
389
|
-
| < 25 min | Title only (centered) |
|
|
390
|
-
| 25–44 min | Title + time (with ClockIcon) |
|
|
391
|
-
| ≥ 45 min | Full details: title + type, product, participants, time, location |
|
|
573
|
+
Full-featured event detail popover with action buttons, participant list, and domain data display.
|
|
392
574
|
|
|
393
|
-
|
|
575
|
+
### Key Props
|
|
394
576
|
|
|
395
|
-
|
|
577
|
+
```typescript
|
|
578
|
+
interface EventPopoverProps<TData = Record<string, unknown>> {
|
|
579
|
+
event: CalendarEvent<TData>;
|
|
580
|
+
onClose: () => void;
|
|
581
|
+
|
|
582
|
+
// Action callbacks
|
|
583
|
+
onEdit?: () => void;
|
|
584
|
+
onDelete?: () => void;
|
|
585
|
+
onCancel?: () => void;
|
|
586
|
+
onAccept?: () => void;
|
|
587
|
+
onDecline?: () => void;
|
|
588
|
+
onConfirmMeeting?: () => void;
|
|
589
|
+
|
|
590
|
+
// Permission flags
|
|
591
|
+
canEdit?: boolean;
|
|
592
|
+
canDelete?: boolean;
|
|
593
|
+
canCancel?: boolean;
|
|
594
|
+
|
|
595
|
+
// Loading states
|
|
596
|
+
isAccepting?: boolean;
|
|
597
|
+
isDeclining?: boolean;
|
|
598
|
+
isConfirming?: boolean;
|
|
599
|
+
|
|
600
|
+
// Render customization
|
|
601
|
+
renderHeader?: (event) => ReactNode;
|
|
602
|
+
renderFooter?: (event) => ReactNode;
|
|
603
|
+
renderActions?: (event) => ReactNode;
|
|
604
|
+
|
|
605
|
+
// i18n labels
|
|
606
|
+
labels?: IEventPopoverLabels;
|
|
607
|
+
}
|
|
608
|
+
```
|
|
396
609
|
|
|
397
|
-
|
|
398
|
-
uses one of three modes controlled by `showMoreMode`:
|
|
610
|
+
### IEventPopoverLabels
|
|
399
611
|
|
|
400
|
-
|
|
401
|
-
| ---------- | ------------------------------------------------------------------------------ |
|
|
402
|
-
| `'expand'` | Morphing overlay (`DayEventsExpansion`) animates from cell to centered panel |
|
|
403
|
-
| `'popover'`| Radix popover shows all events inline |
|
|
404
|
-
| `'dayView'`| Redirects to day view via `onDateChange` |
|
|
612
|
+
All label strings are customizable for i18n:
|
|
405
613
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
614
|
+
```typescript
|
|
615
|
+
interface IEventPopoverLabels {
|
|
616
|
+
edit?: string;
|
|
617
|
+
delete?: string;
|
|
618
|
+
cancel?: string;
|
|
619
|
+
accept?: string;
|
|
620
|
+
decline?: string;
|
|
621
|
+
confirmMeeting?: string;
|
|
622
|
+
participants?: string;
|
|
623
|
+
description?: string;
|
|
624
|
+
// ... more keys
|
|
625
|
+
}
|
|
626
|
+
```
|
|
409
627
|
|
|
410
628
|
---
|
|
411
629
|
|
|
412
|
-
##
|
|
630
|
+
## 10. Preferences System
|
|
413
631
|
|
|
414
|
-
|
|
415
|
-
package. Adapt to whatever data layer, routing, or UI framework the consuming
|
|
416
|
-
application uses.
|
|
632
|
+
Located in `src/core/preferences/`.
|
|
417
633
|
|
|
418
|
-
###
|
|
634
|
+
### IPreferencesConfig
|
|
419
635
|
|
|
420
|
-
|
|
421
|
-
the package's generic `CalendarEvent<TData>` type. Place these in a `transformers.ts`
|
|
422
|
-
(or similar) file, co-located with the calendar feature.
|
|
636
|
+
Passed via `<InnoCalendar preferencesConfig={...}>`:
|
|
423
637
|
|
|
424
638
|
```typescript
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
/** Define the shape of your domain-specific event data. */
|
|
433
|
-
interface MyEventData {
|
|
434
|
-
[key: string]: unknown;
|
|
435
|
-
agendaTypeId: number;
|
|
436
|
-
agendaTypeName: string;
|
|
437
|
-
companyId: number;
|
|
438
|
-
meetingTookPlace: boolean;
|
|
439
|
-
isAccepted: boolean;
|
|
440
|
-
cancelReason?: string;
|
|
441
|
-
participantNames: string[];
|
|
639
|
+
interface IPreferencesConfig {
|
|
640
|
+
modes?: TPreferenceModes; // Per-key control mode
|
|
641
|
+
locked?: TLockedPreferences; // Values for locked preferences
|
|
642
|
+
defaults?: TPartialPreferences; // Custom defaults
|
|
643
|
+
storageKey?: string; // Custom localStorage key
|
|
644
|
+
disableStorage?: boolean; // Disable localStorage entirely
|
|
442
645
|
}
|
|
646
|
+
```
|
|
443
647
|
|
|
444
|
-
|
|
445
|
-
* Transform raw API events into CalendarEvent[].
|
|
446
|
-
* Core fields at top level, domain data in the `data` bag.
|
|
447
|
-
*/
|
|
448
|
-
export function transformEvents(
|
|
449
|
-
apiEvents: ApiEvent[],
|
|
450
|
-
): CalendarEvent<MyEventData>[] {
|
|
451
|
-
return apiEvents.map((event) => ({
|
|
452
|
-
id: event.id.toString(),
|
|
453
|
-
title: event.title,
|
|
454
|
-
startDate: new Date(event.startDate),
|
|
455
|
-
endDate: new Date(event.endDate),
|
|
456
|
-
description: event.description ?? undefined,
|
|
457
|
-
color: eventTypeColorMap[event.type.id] ?? "blue",
|
|
458
|
-
hexColor: eventTypeHexMap[event.type.id] ?? "#069AD7",
|
|
459
|
-
isCanceled: event.isCanceled,
|
|
460
|
-
isAllDay: false,
|
|
461
|
-
isMultiDay: isDifferentDay(
|
|
462
|
-
new Date(event.startDate),
|
|
463
|
-
new Date(event.endDate),
|
|
464
|
-
),
|
|
648
|
+
### Preference Modes
|
|
465
649
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
}
|
|
650
|
+
| Mode | Behavior |
|
|
651
|
+
|---|---|
|
|
652
|
+
| `'user'` | User can modify, persisted to localStorage (default) |
|
|
653
|
+
| `'locked'` | Developer-controlled, user cannot modify |
|
|
654
|
+
| `'session'` | User can modify during session, not persisted |
|
|
655
|
+
|
|
656
|
+
### IPreferences
|
|
657
|
+
|
|
658
|
+
```typescript
|
|
659
|
+
interface IPreferences {
|
|
660
|
+
view: TCalendarView;
|
|
661
|
+
badgeVariant: TBadgeVariant;
|
|
662
|
+
slotDuration: TSlotDuration;
|
|
663
|
+
visibleHours: IVisibleHoursConfig; // { from: number; to: number }
|
|
664
|
+
workingHours: TWorkingHoursConfig; // { [dayIndex]: { from, to } }
|
|
665
|
+
showWorkingHoursOnly: boolean;
|
|
666
|
+
showWeekends: boolean;
|
|
667
|
+
firstDayOfWeek: 0|1|2|3|4|5|6;
|
|
481
668
|
}
|
|
482
669
|
```
|
|
483
670
|
|
|
484
|
-
###
|
|
671
|
+
### useCalendarPreferences Hook
|
|
485
672
|
|
|
486
|
-
|
|
673
|
+
Returns `IUsePreferencesReturn`:
|
|
487
674
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
675
|
+
```typescript
|
|
676
|
+
interface IUsePreferencesReturn {
|
|
677
|
+
preferences: IPreferences;
|
|
678
|
+
setPreference: <K extends TPreferenceKey>(key: K, value: IPreferences[K]) => void;
|
|
679
|
+
setPreferences: (updates: TPartialPreferences) => void;
|
|
680
|
+
resetPreferences: () => void;
|
|
681
|
+
resetPreference: (key: TPreferenceKey) => void;
|
|
682
|
+
isLocked: (key: TPreferenceKey) => boolean;
|
|
683
|
+
isPersisted: (key: TPreferenceKey) => boolean;
|
|
684
|
+
getMode: (key: TPreferenceKey) => TPreferenceMode;
|
|
685
|
+
}
|
|
686
|
+
```
|
|
492
687
|
|
|
493
|
-
|
|
494
|
-
pass it via `filterContent`:
|
|
688
|
+
### Example: Locking slot duration
|
|
495
689
|
|
|
496
690
|
```tsx
|
|
497
691
|
<InnoCalendar
|
|
498
|
-
events={
|
|
499
|
-
|
|
692
|
+
events={events}
|
|
693
|
+
preferencesConfig={{
|
|
694
|
+
modes: { slotDuration: 'locked' },
|
|
695
|
+
locked: { slotDuration: 30 },
|
|
696
|
+
}}
|
|
500
697
|
/>
|
|
501
698
|
```
|
|
502
699
|
|
|
503
|
-
|
|
504
|
-
|
|
700
|
+
---
|
|
701
|
+
|
|
702
|
+
## 11. Widget Components
|
|
703
|
+
|
|
704
|
+
Located in `src/components/widget/`.
|
|
705
|
+
|
|
706
|
+
### AgendaWidget
|
|
707
|
+
|
|
708
|
+
Embeddable agenda card for dashboards. Can work standalone or tap into an existing `InnoCalendarProvider`.
|
|
709
|
+
|
|
710
|
+
```typescript
|
|
711
|
+
interface AgendaWidgetProps {
|
|
712
|
+
events: CalendarEvent[];
|
|
713
|
+
maxItems?: number;
|
|
714
|
+
onEventClick?: (event: CalendarEvent) => void;
|
|
715
|
+
className?: string;
|
|
716
|
+
// ... additional display options
|
|
717
|
+
}
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### AgendaDropdown
|
|
721
|
+
|
|
722
|
+
Bell-style notification dropdown for upcoming events:
|
|
723
|
+
|
|
724
|
+
```typescript
|
|
725
|
+
interface AgendaDropdownProps {
|
|
726
|
+
events: CalendarEvent[];
|
|
727
|
+
onEventClick?: (event: CalendarEvent) => void;
|
|
728
|
+
className?: string;
|
|
729
|
+
// ... trigger customization
|
|
730
|
+
}
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
---
|
|
734
|
+
|
|
735
|
+
## 12. Filter Components
|
|
736
|
+
|
|
737
|
+
Located in `src/components/filters/`.
|
|
738
|
+
|
|
739
|
+
### CalendarFilterSidebar
|
|
740
|
+
|
|
741
|
+
Full filter panel with schedule type and user filters:
|
|
742
|
+
|
|
743
|
+
```typescript
|
|
744
|
+
interface CalendarFilterSidebarProps {
|
|
745
|
+
scheduleTypes: IScheduleTypeOption[];
|
|
746
|
+
users: IUserOption[];
|
|
747
|
+
labels?: CalendarFilterSidebarLabels;
|
|
748
|
+
// Controlled state or context-driven
|
|
749
|
+
}
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
### ScheduleTypeFilter
|
|
753
|
+
|
|
754
|
+
Standalone schedule type filter component with checkbox list.
|
|
755
|
+
|
|
756
|
+
### UserFilter
|
|
757
|
+
|
|
758
|
+
Standalone user/participant filter with avatar support.
|
|
759
|
+
|
|
760
|
+
---
|
|
761
|
+
|
|
762
|
+
## 13. Settings Components
|
|
763
|
+
|
|
764
|
+
Located in `src/components/settings/`.
|
|
765
|
+
|
|
766
|
+
| Component | Props | What It Controls |
|
|
767
|
+
|---|---|---|
|
|
768
|
+
| `BadgeVariantSetting` | `BadgeVariantSettingProps` | Dot / Colored / Mixed badge display |
|
|
769
|
+
| `SlotDurationSetting` | `SlotDurationSettingProps` | 15 / 30 / 60 minute time slot granularity |
|
|
770
|
+
| `VisibleHoursSetting` | `VisibleHoursSettingProps` | Start/end hour range for day/week views |
|
|
771
|
+
| `WorkingHoursSetting` | `WorkingHoursSettingProps` | Per-day working hours with enable/disable toggle |
|
|
772
|
+
|
|
773
|
+
All settings components read/write preferences through the preferences context.
|
|
774
|
+
|
|
775
|
+
---
|
|
776
|
+
|
|
777
|
+
## 14. Context & Hooks
|
|
778
|
+
|
|
779
|
+
### Context Providers
|
|
780
|
+
|
|
781
|
+
| Provider | Purpose |
|
|
782
|
+
|---|---|
|
|
783
|
+
| `InnoCalendarProvider` | Main state: events, date, view, filters, search, preferences, time config |
|
|
784
|
+
| `CalendarProvider` | Lower-level provider (used by `Calendar` component) |
|
|
785
|
+
| `SlotSelectionProvider` | Drag-to-select state management |
|
|
786
|
+
| `DragDropProvider` | Event drag-and-drop state management |
|
|
787
|
+
|
|
788
|
+
### Primary Hooks
|
|
789
|
+
|
|
790
|
+
| Hook | Return Type | Purpose |
|
|
791
|
+
|---|---|---|
|
|
792
|
+
| `useInnoCalendar()` | `IInnoCalendarContext` | Full context access (events, view, date, filters, search, preferences) |
|
|
793
|
+
| `useCalendarContext()` | `IInnoCalendarContext` | Alias for `useInnoCalendar` |
|
|
794
|
+
| `useCalendar()` | `UseCalendarReturn` | Core calendar state (date, view, navigation, event helpers) |
|
|
795
|
+
| `useSlotSelection()` | `UseSlotSelectionReturn` | Drag-to-select hook |
|
|
796
|
+
| `useCalendarTimeConfig()` | `UseCalendarTimeConfigReturn` | Slot duration, visible hours, working hours |
|
|
797
|
+
| `usePreferences()` | `UsePreferencesReturn` | Basic preferences hook |
|
|
798
|
+
| `useAdvancedPreferences()` | `IUsePreferencesReturn` | Full preferences with locking support |
|
|
799
|
+
| `useDragDrop()` | `IDragDropContext` | Drag-drop state and handlers |
|
|
800
|
+
|
|
801
|
+
### Selective Context Hooks
|
|
802
|
+
|
|
803
|
+
These read only a slice of the context for performance:
|
|
804
|
+
|
|
805
|
+
| Hook | Returns |
|
|
806
|
+
|---|---|
|
|
807
|
+
| `useCalendarDate()` | Current date + navigation |
|
|
808
|
+
| `useCalendarView()` | Current view + view change |
|
|
809
|
+
| `useCalendarEvents()` | Events array + setEvents |
|
|
810
|
+
| `useCalendarFilters()` | Filter state + setters |
|
|
811
|
+
| `useCalendarPreferences()` | Preferences from context |
|
|
812
|
+
| `useInnoCalendarView()` | View from InnoCalendar context |
|
|
813
|
+
| `useInnoCalendarEvents()` | Events from InnoCalendar context |
|
|
814
|
+
| `useInnoCalendarFilters()` | Filters from InnoCalendar context |
|
|
815
|
+
| `useInnoCalendarTimeConfig()` | Time config from InnoCalendar context |
|
|
816
|
+
|
|
817
|
+
### Optional Hooks (no throw if outside provider)
|
|
818
|
+
|
|
819
|
+
| Hook | Returns |
|
|
820
|
+
|---|---|
|
|
821
|
+
| `useOptionalCalendar()` | `context │ undefined` |
|
|
822
|
+
| `useOptionalCalendarContext()` | `context │ undefined` |
|
|
823
|
+
| `useOptionalInnoCalendar()` | `context │ undefined` |
|
|
824
|
+
| `useOptionalDragDrop()` | `context │ undefined` |
|
|
825
|
+
| `useOptionalSlotSelection()` | `context │ undefined` |
|
|
826
|
+
|
|
827
|
+
### Legacy Aliases (backward compat)
|
|
828
|
+
|
|
829
|
+
| Alias | Maps To |
|
|
830
|
+
|---|---|
|
|
831
|
+
| `IntegratedCalendarProvider` | `InnoCalendarProvider` |
|
|
832
|
+
| `useIntegratedCalendar()` | `useInnoCalendar()` |
|
|
833
|
+
| `useIntegratedCalendarEvents()` | `useInnoCalendarEvents()` |
|
|
834
|
+
| `useIntegratedCalendarFilters()` | `useInnoCalendarFilters()` |
|
|
835
|
+
| `useIntegratedCalendarView()` | `useInnoCalendarView()` |
|
|
836
|
+
| `useIntegratedCalendarTimeConfig()` | `useInnoCalendarTimeConfig()` |
|
|
837
|
+
| `useOptionalIntegratedCalendar()` | `useOptionalInnoCalendar()` |
|
|
838
|
+
| `IntegratedCalendar` | `InnoCalendar` |
|
|
839
|
+
|
|
840
|
+
---
|
|
841
|
+
|
|
842
|
+
## 15. Drag & Drop
|
|
843
|
+
|
|
844
|
+
Located in `src/core/context/drag-drop-context.tsx`.
|
|
505
845
|
|
|
506
|
-
###
|
|
846
|
+
### DragDropProvider
|
|
507
847
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
848
|
+
Manages drag state for event repositioning. Wraps the calendar tree automatically when using `<InnoCalendar>`.
|
|
849
|
+
|
|
850
|
+
```typescript
|
|
851
|
+
interface IDragState<TData = Record<string, unknown>> {
|
|
852
|
+
event: CalendarEvent<TData>;
|
|
853
|
+
originalStartDate: Date;
|
|
854
|
+
originalEndDate: Date;
|
|
855
|
+
previewDate?: Date; // current preview position
|
|
856
|
+
previewHour?: number; // for time-based views
|
|
857
|
+
previewMinute?: number;
|
|
858
|
+
originalResourceId?: string; // for resource/timeline views
|
|
859
|
+
targetResourceId?: string; // current drag target resource
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
interface IDropResult<TData = Record<string, unknown>> {
|
|
863
|
+
event: CalendarEvent<TData>;
|
|
864
|
+
newStartDate: Date;
|
|
865
|
+
newEndDate: Date; // maintains original duration
|
|
866
|
+
newResourceId?: string; // set when dropped on different resource
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
interface IDragDropContext<TData = Record<string, unknown>> {
|
|
870
|
+
dragState: IDragState<TData> | null;
|
|
871
|
+
isDragging: boolean;
|
|
872
|
+
startDrag: (event: CalendarEvent<any>) => void;
|
|
873
|
+
updateDragPreview: (date: Date, hour?: number, minute?: number, resourceId?: string) => void;
|
|
874
|
+
endDrag: () => IDropResult<TData> | null;
|
|
875
|
+
cancelDrag: () => void;
|
|
876
|
+
}
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
### Timeline / Resource View Drag & Drop
|
|
880
|
+
|
|
881
|
+
`TimelineView` supports HTML5 drag & drop out of the box:
|
|
882
|
+
|
|
883
|
+
- Events render with `enableDrag={true}` by default
|
|
884
|
+
- Each resource row acts as a drop zone
|
|
885
|
+
- Dragging across rows updates `targetResourceId` in the drag state
|
|
886
|
+
- Dropping calls `endDrag()` which fires `onEventDrop` on `DragDropProvider`
|
|
511
887
|
|
|
512
888
|
```tsx
|
|
513
889
|
<InnoCalendar
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
)
|
|
890
|
+
events={events}
|
|
891
|
+
initialView="timeline-week"
|
|
892
|
+
onEventDrop={(result) => {
|
|
893
|
+
console.log(result.event.id, result.newStartDate, result.newResourceId);
|
|
894
|
+
// Update your backend
|
|
895
|
+
}}
|
|
517
896
|
/>
|
|
518
897
|
```
|
|
519
898
|
|
|
520
|
-
|
|
899
|
+
---
|
|
900
|
+
|
|
901
|
+
## 16. Core Utilities
|
|
902
|
+
|
|
903
|
+
Located in `src/core/utils/`. All exported from `src/core/utils/index.ts`.
|
|
904
|
+
|
|
905
|
+
### date-utils.ts
|
|
906
|
+
|
|
907
|
+
Date manipulation using native Date API:
|
|
908
|
+
|
|
909
|
+
| Function | Signature |
|
|
910
|
+
|---|---|
|
|
911
|
+
| `startOfDay` | `(date: Date) => Date` |
|
|
912
|
+
| `endOfDay` | `(date: Date) => Date` |
|
|
913
|
+
| `startOfWeek` | `(date: Date, firstDay?: number) => Date` |
|
|
914
|
+
| `endOfWeek` | `(date: Date, firstDay?: number) => Date` |
|
|
915
|
+
| `startOfMonth` | `(date: Date) => Date` |
|
|
916
|
+
| `endOfMonth` | `(date: Date) => Date` |
|
|
917
|
+
| `startOfYear` | `(date: Date) => Date` |
|
|
918
|
+
| `endOfYear` | `(date: Date) => Date` |
|
|
919
|
+
| `addDays` | `(date: Date, days: number) => Date` |
|
|
920
|
+
| `addMonths` | `(date: Date, months: number) => Date` |
|
|
921
|
+
| `addYears` | `(date: Date, years: number) => Date` |
|
|
922
|
+
| `addHours` | `(date: Date, hours: number) => Date` |
|
|
923
|
+
| `addMinutes` | `(date: Date, minutes: number) => Date` |
|
|
924
|
+
| `isSameDay` | `(a: Date, b: Date) => boolean` |
|
|
925
|
+
| `isSameMonth` | `(a: Date, b: Date) => boolean` |
|
|
926
|
+
| `isSameYear` | `(a: Date, b: Date) => boolean` |
|
|
927
|
+
| `isToday` | `(date: Date) => boolean` |
|
|
928
|
+
| `isWeekend` | `(date: Date) => boolean` |
|
|
929
|
+
| `isBefore` | `(a: Date, b: Date) => boolean` |
|
|
930
|
+
| `isAfter` | `(a: Date, b: Date) => boolean` |
|
|
931
|
+
| `isBetween` | `(date: Date, start: Date, end: Date) => boolean` |
|
|
932
|
+
| `getWeekDays` | `(start: Date, count?: number) => Date[]` |
|
|
933
|
+
| `getMonthDays` | `(date: Date, firstDay?: number) => Date[]` |
|
|
934
|
+
| `getYearMonths` | `(date: Date) => Date[]` |
|
|
935
|
+
| `getDaysBetween` | `(start: Date, end: Date) => number` |
|
|
936
|
+
| `getMinutesBetween` | `(start: Date, end: Date) => number` |
|
|
937
|
+
| `formatTime` | `(date: Date, format: '12h'│'24h') => string` |
|
|
938
|
+
| `formatDate` | `(date: Date, options?: Intl.DateTimeFormatOptions) => string` |
|
|
939
|
+
| `formatDateRange` | `(start: Date, end: Date) => string` |
|
|
940
|
+
| `getWeekNumber` | `(date: Date) => number` |
|
|
941
|
+
| `getDaysInMonth` | `(date: Date) => number` |
|
|
942
|
+
| `clampDate` | `(date: Date, min: Date, max: Date) => Date` |
|
|
943
|
+
|
|
944
|
+
### event-utils.ts
|
|
945
|
+
|
|
946
|
+
Event filtering, sorting, and classification:
|
|
947
|
+
|
|
948
|
+
| Function | Purpose |
|
|
949
|
+
|---|---|
|
|
950
|
+
| `isMultiDayEvent` | Checks if event spans midnight |
|
|
951
|
+
| `isAllDayEvent` | Checks if event is all-day |
|
|
952
|
+
| `getEventsForDay` | Filters events that overlap a given day |
|
|
953
|
+
| `getEventsForRange` | Filters events that overlap a date range |
|
|
954
|
+
| `sortEvents` | Sort by start date, then duration |
|
|
955
|
+
| `groupEventsByDay` | Groups events by calendar date |
|
|
956
|
+
| `groupEventsByResource` | Groups events by resourceId |
|
|
957
|
+
| `filterEventsByScheduleType` | Client-side schedule type filter |
|
|
958
|
+
| `filterEventsByParticipant` | Client-side participant filter |
|
|
959
|
+
| `searchEvents` | Text search across event fields |
|
|
960
|
+
| `getEventDurationMinutes` | Returns event duration in minutes |
|
|
961
|
+
| `splitMultiDayEvent` | Splits multi-day event into per-day segments |
|
|
962
|
+
|
|
963
|
+
### grid-utils.ts
|
|
964
|
+
|
|
965
|
+
Grid layout calculations:
|
|
966
|
+
|
|
967
|
+
| Function | Purpose |
|
|
968
|
+
|---|---|
|
|
969
|
+
| `generateTimeSlots` | Creates time slot array for given hour range |
|
|
970
|
+
| `getHourHeight` | Pixel height for one hour from slot config |
|
|
971
|
+
| `getSlotHeight` | Pixel height for one slot |
|
|
972
|
+
| `timeToPixels` | Converts time to vertical pixel offset |
|
|
973
|
+
| `pixelsToTime` | Converts vertical pixel offset to time |
|
|
974
|
+
| `getColumnsForDay` | Column count for a day in week view |
|
|
975
|
+
| `snapToSlot` | Snaps a time to nearest slot boundary |
|
|
976
|
+
|
|
977
|
+
### position-utils.ts
|
|
978
|
+
|
|
979
|
+
Event overlap and positioning:
|
|
980
|
+
|
|
981
|
+
| Function | Purpose |
|
|
982
|
+
|---|---|
|
|
983
|
+
| `calculateEventPositions` | Computes non-overlapping layout for events in a column |
|
|
984
|
+
| `findOverlappingGroups` | Groups events that share time ranges |
|
|
985
|
+
| `calculateColumnSpans` | Assigns columns and spans within overlap groups |
|
|
521
986
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
987
|
+
---
|
|
988
|
+
|
|
989
|
+
## 17. Presets
|
|
990
|
+
|
|
991
|
+
Located in `src/presets/`.
|
|
525
992
|
|
|
526
|
-
###
|
|
993
|
+
### DefaultCalendar
|
|
527
994
|
|
|
528
|
-
|
|
529
|
-
menus, multiple create buttons per event type, etc.):
|
|
995
|
+
Pre-configured calendar with sensible defaults:
|
|
530
996
|
|
|
531
997
|
```tsx
|
|
532
|
-
|
|
998
|
+
import { DefaultCalendar } from '@innosolutions/inno-calendar';
|
|
999
|
+
|
|
1000
|
+
<DefaultCalendar events={events} />
|
|
533
1001
|
```
|
|
534
1002
|
|
|
535
|
-
###
|
|
1003
|
+
### TailwindCalendar
|
|
536
1004
|
|
|
537
|
-
|
|
538
|
-
date range. The consumer decides what happens next — open a form directly, show a
|
|
539
|
-
dialog to pick the event type, or create an event immediately:
|
|
1005
|
+
Tailwind-optimized calendar preset with carefully chosen utility classes:
|
|
540
1006
|
|
|
541
1007
|
```tsx
|
|
542
|
-
|
|
543
|
-
// Option A: open a create form directly with pre-filled dates
|
|
544
|
-
openCreateForm({ startDate: selection.startDate, endDate: selection.endDate });
|
|
1008
|
+
import { TailwindCalendar } from '@innosolutions/inno-calendar';
|
|
545
1009
|
|
|
546
|
-
|
|
547
|
-
openEventTypePicker(selection);
|
|
548
|
-
}}
|
|
1010
|
+
<TailwindCalendar events={events} />
|
|
549
1011
|
```
|
|
550
1012
|
|
|
551
|
-
|
|
552
|
-
event categories. Single-type apps can skip straight to the form.
|
|
1013
|
+
---
|
|
553
1014
|
|
|
554
|
-
|
|
1015
|
+
## 18. CSS & Theming
|
|
555
1016
|
|
|
556
|
-
|
|
557
|
-
working hours, badge variant). Pass custom content via `settingsContent` to
|
|
558
|
-
extend or replace them:
|
|
1017
|
+
Styles are in `src/styles/calendar.css` (imported via `@innosolutions/inno-calendar/styles`).
|
|
559
1018
|
|
|
560
|
-
|
|
561
|
-
<InnoCalendar settingsContent={<MySettingsPanel />} />
|
|
562
|
-
```
|
|
1019
|
+
### CSS Custom Properties
|
|
563
1020
|
|
|
564
|
-
|
|
565
|
-
|
|
1021
|
+
```css
|
|
1022
|
+
:root {
|
|
1023
|
+
/* Surfaces */
|
|
1024
|
+
--calendar-bg: #ffffff;
|
|
1025
|
+
--calendar-surface: #f8fafc;
|
|
1026
|
+
|
|
1027
|
+
/* Borders */
|
|
1028
|
+
--calendar-border: #e2e8f0;
|
|
1029
|
+
--calendar-border-strong: #cbd5e1;
|
|
1030
|
+
|
|
1031
|
+
/* Text */
|
|
1032
|
+
--calendar-text: #0f172a;
|
|
1033
|
+
--calendar-text-muted: #64748b;
|
|
1034
|
+
--calendar-text-inverted: #ffffff;
|
|
1035
|
+
|
|
1036
|
+
/* Interactive */
|
|
1037
|
+
--calendar-primary: #3b82f6;
|
|
1038
|
+
--calendar-primary-hover: #2563eb;
|
|
1039
|
+
--calendar-hover: #f1f5f9;
|
|
1040
|
+
|
|
1041
|
+
/* Grid */
|
|
1042
|
+
--calendar-hour-height: 64px;
|
|
1043
|
+
--calendar-slot-height: 32px;
|
|
1044
|
+
--calendar-header-height: 56px;
|
|
1045
|
+
--calendar-gutter-width: 72px;
|
|
1046
|
+
|
|
1047
|
+
/* Selection */
|
|
1048
|
+
--calendar-selection-bg: rgba(59, 130, 246, 0.1);
|
|
1049
|
+
--calendar-selection-border: rgba(59, 130, 246, 0.3);
|
|
1050
|
+
--calendar-selection-handle: #3b82f6;
|
|
1051
|
+
|
|
1052
|
+
/* Current time */
|
|
1053
|
+
--calendar-now-line: #ef4444;
|
|
1054
|
+
--calendar-now-dot: #ef4444;
|
|
1055
|
+
|
|
1056
|
+
/* Working hours */
|
|
1057
|
+
--calendar-working-bg: transparent;
|
|
1058
|
+
--calendar-non-working-bg: #f8fafc;
|
|
1059
|
+
|
|
1060
|
+
/* Sidebar / filter */
|
|
1061
|
+
--calendar-sidebar-width: 280px;
|
|
1062
|
+
}
|
|
1063
|
+
```
|
|
566
1064
|
|
|
567
|
-
###
|
|
1065
|
+
### Dark Mode
|
|
568
1066
|
|
|
569
|
-
|
|
570
|
-
The consumer provides the hatching/dimming style:
|
|
1067
|
+
Override with `.dark` class:
|
|
571
1068
|
|
|
572
1069
|
```css
|
|
573
|
-
.
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
1070
|
+
.dark {
|
|
1071
|
+
--calendar-bg: #0f172a;
|
|
1072
|
+
--calendar-surface: #1e293b;
|
|
1073
|
+
--calendar-border: #334155;
|
|
1074
|
+
--calendar-text: #f1f5f9;
|
|
1075
|
+
--calendar-text-muted: #94a3b8;
|
|
1076
|
+
/* ... etc */
|
|
580
1077
|
}
|
|
581
1078
|
```
|
|
582
1079
|
|
|
583
|
-
|
|
584
|
-
|
|
1080
|
+
### Key CSS Classes
|
|
1081
|
+
|
|
1082
|
+
| Class | Purpose |
|
|
1083
|
+
|---|---|
|
|
1084
|
+
| `.calendar-loading-overlay` | Translucent loading indicator overlay |
|
|
1085
|
+
| `.calendar-scroll-container` | Scrollable grid container |
|
|
1086
|
+
| `.calendar-time-gutter` | Hour labels column |
|
|
1087
|
+
| `.calendar-current-time-indicator` | Red line + dot for current time |
|
|
1088
|
+
| `.calendar-selection-overlay` | Blue drag selection highlight |
|
|
1089
|
+
| `.day-events-expansion-backdrop` | Backdrop for expanded month cell |
|
|
1090
|
+
| `.day-events-expansion-panel` | Expanded events panel in month view |
|
|
585
1091
|
|
|
586
1092
|
---
|
|
587
1093
|
|
|
588
|
-
##
|
|
1094
|
+
## 19. Constants
|
|
589
1095
|
|
|
590
|
-
|
|
1096
|
+
Located in `src/core/constants.ts`.
|
|
591
1097
|
|
|
592
|
-
|
|
1098
|
+
### View List
|
|
593
1099
|
|
|
594
1100
|
```typescript
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
1101
|
+
const CALENDAR_VIEWS: TCalendarView[] = [
|
|
1102
|
+
'day', 'week', 'month', 'year', 'agenda',
|
|
1103
|
+
'resource-day', 'resource-week',
|
|
1104
|
+
'timeline-day', 'timeline-week'
|
|
1105
|
+
];
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
### Default Preferences
|
|
1109
|
+
|
|
1110
|
+
```typescript
|
|
1111
|
+
const DEFAULT_PREFERENCES: ICalendarPreferences = {
|
|
1112
|
+
startHour: 8,
|
|
1113
|
+
endHour: 18,
|
|
1114
|
+
firstDayOfWeek: 1, // Monday
|
|
1115
|
+
slotDuration: 30,
|
|
1116
|
+
showWeekends: true,
|
|
1117
|
+
timeFormat: '24h',
|
|
1118
|
+
badgeVariant: 'colored',
|
|
1119
|
+
showCanceledEvents: false,
|
|
1120
|
+
};
|
|
1121
|
+
```
|
|
1122
|
+
|
|
1123
|
+
### Color Palette
|
|
1124
|
+
|
|
1125
|
+
```typescript
|
|
1126
|
+
const EVENT_COLORS: Record<TEventColor, string> = {
|
|
1127
|
+
blue: '#3b82f6', green: '#22c55e', red: '#ef4444',
|
|
1128
|
+
yellow: '#eab308', purple: '#a855f7', orange: '#f97316',
|
|
1129
|
+
pink: '#ec4899', teal: '#14b8a6', gray: '#6b7280', indigo: '#6366f1',
|
|
1130
|
+
};
|
|
1131
|
+
|
|
1132
|
+
const EVENT_COLOR_CLASSES: Record<TEventColor, { bg, text, border }>;
|
|
1133
|
+
const HEX_TO_EVENT_COLOR: Record<string, TEventColor>;
|
|
1134
|
+
```
|
|
1135
|
+
|
|
1136
|
+
### Grid Constants
|
|
1137
|
+
|
|
1138
|
+
| Constant | Value |
|
|
1139
|
+
|---|---|
|
|
1140
|
+
| `DEFAULT_HOUR_HEIGHT` | 64px |
|
|
1141
|
+
| `DEFAULT_SLOT_HEIGHT` | 32px |
|
|
1142
|
+
| `DEFAULT_HEADER_HEIGHT` | 56px |
|
|
1143
|
+
| `DEFAULT_RESOURCE_WIDTH` | 200px |
|
|
1144
|
+
| `MIN_EVENT_HEIGHT` | 20px |
|
|
1145
|
+
| `HOURS_IN_DAY` | 24 |
|
|
1146
|
+
| `MINUTES_IN_HOUR` | 60 |
|
|
1147
|
+
| `MS_PER_MINUTE` | 60000 |
|
|
1148
|
+
| `MS_PER_HOUR` | 3600000 |
|
|
1149
|
+
| `MS_PER_DAY` | 86400000 |
|
|
1150
|
+
|
|
1151
|
+
### Time Defaults
|
|
1152
|
+
|
|
1153
|
+
```typescript
|
|
1154
|
+
const DEFAULT_VISIBLE_HOURS: IVisibleHours = { startHour: 0, endHour: 24 };
|
|
1155
|
+
const DEFAULT_BUSINESS_HOURS: IVisibleHours = { startHour: 8, endHour: 18 };
|
|
1156
|
+
```
|
|
1157
|
+
|
|
1158
|
+
---
|
|
1159
|
+
|
|
1160
|
+
## 20. Integration Guide
|
|
1161
|
+
|
|
1162
|
+
### Minimal Setup
|
|
1163
|
+
|
|
1164
|
+
```tsx
|
|
1165
|
+
import { InnoCalendar, type CalendarEvent } from '@innosolutions/inno-calendar';
|
|
1166
|
+
import '@innosolutions/inno-calendar/styles';
|
|
1167
|
+
|
|
1168
|
+
const events: CalendarEvent[] = [
|
|
1169
|
+
{
|
|
1170
|
+
id: '1',
|
|
1171
|
+
title: 'Team Meeting',
|
|
1172
|
+
startDate: new Date('2026-02-12T09:00:00'),
|
|
1173
|
+
endDate: new Date('2026-02-12T10:00:00'),
|
|
1174
|
+
color: 'blue',
|
|
1175
|
+
},
|
|
1176
|
+
];
|
|
1177
|
+
|
|
1178
|
+
function App() {
|
|
1179
|
+
return (
|
|
1180
|
+
<InnoCalendar
|
|
1181
|
+
events={events}
|
|
1182
|
+
onEventClick={(event) => console.log('Clicked:', event.title)}
|
|
1183
|
+
onSlotSelect={(sel) => console.log('Selected:', sel.startDate, sel.endDate)}
|
|
1184
|
+
/>
|
|
1185
|
+
);
|
|
623
1186
|
}
|
|
624
1187
|
```
|
|
625
1188
|
|
|
1189
|
+
### With Custom Popover
|
|
1190
|
+
|
|
1191
|
+
```tsx
|
|
1192
|
+
<InnoCalendar
|
|
1193
|
+
events={events}
|
|
1194
|
+
renderPopover={({ event, onClose }) => (
|
|
1195
|
+
<div className="p-4 space-y-2">
|
|
1196
|
+
<h3 className="font-bold">{event.title}</h3>
|
|
1197
|
+
<p>{event.description}</p>
|
|
1198
|
+
<button onClick={onClose}>Close</button>
|
|
1199
|
+
</div>
|
|
1200
|
+
)}
|
|
1201
|
+
/>
|
|
1202
|
+
```
|
|
1203
|
+
|
|
1204
|
+
### With Loading State
|
|
1205
|
+
|
|
1206
|
+
```tsx
|
|
1207
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1208
|
+
|
|
1209
|
+
<InnoCalendar
|
|
1210
|
+
events={events}
|
|
1211
|
+
isLoading={isLoading}
|
|
1212
|
+
onDateChange={async (date, view) => {
|
|
1213
|
+
setIsLoading(true);
|
|
1214
|
+
const newEvents = await fetchEvents(date, view);
|
|
1215
|
+
setEvents(newEvents);
|
|
1216
|
+
setIsLoading(false);
|
|
1217
|
+
}}
|
|
1218
|
+
/>
|
|
1219
|
+
```
|
|
1220
|
+
|
|
1221
|
+
### With Deep-Linking
|
|
1222
|
+
|
|
1223
|
+
```tsx
|
|
1224
|
+
// URL: /calendar?eventId=appointment-42
|
|
1225
|
+
const eventId = useSearchParams().get('eventId');
|
|
1226
|
+
|
|
1227
|
+
<InnoCalendar events={events} focusEventId={eventId} />
|
|
1228
|
+
```
|
|
1229
|
+
|
|
1230
|
+
### Imperative Control
|
|
1231
|
+
|
|
1232
|
+
```tsx
|
|
1233
|
+
const ref = useRef<ICalendarRefHandle>(null);
|
|
1234
|
+
|
|
1235
|
+
<Button onClick={() => ref.current?.scrollToWorkingHours()}>
|
|
1236
|
+
Go to Work Hours
|
|
1237
|
+
</Button>
|
|
1238
|
+
|
|
1239
|
+
<InnoCalendar ref={ref} events={events} />
|
|
1240
|
+
```
|
|
1241
|
+
|
|
1242
|
+
### Resource / Timeline Views
|
|
1243
|
+
|
|
1244
|
+
```tsx
|
|
1245
|
+
const resources: IResource[] = [
|
|
1246
|
+
{ id: 'room-a', name: 'Meeting Room A', color: 'blue' },
|
|
1247
|
+
{ id: 'room-b', name: 'Board Room', color: 'green' },
|
|
1248
|
+
];
|
|
1249
|
+
|
|
1250
|
+
<InnoCalendar
|
|
1251
|
+
events={events}
|
|
1252
|
+
initialView="timeline-week"
|
|
1253
|
+
onEventDrop={(result) => {
|
|
1254
|
+
// result.newResourceId is set when event is dragged to a different row
|
|
1255
|
+
api.updateEvent(result.event.id, {
|
|
1256
|
+
startDate: result.newStartDate,
|
|
1257
|
+
endDate: result.newEndDate,
|
|
1258
|
+
resourceId: result.newResourceId,
|
|
1259
|
+
});
|
|
1260
|
+
}}
|
|
1261
|
+
/>
|
|
1262
|
+
```
|
|
1263
|
+
|
|
1264
|
+
### Header Configuration
|
|
1265
|
+
|
|
1266
|
+
```tsx
|
|
1267
|
+
// Hide resource views and settings — only show calendar views + Today + navigation
|
|
1268
|
+
<InnoCalendar
|
|
1269
|
+
events={events}
|
|
1270
|
+
headerConfig={{
|
|
1271
|
+
showResourceViews: false,
|
|
1272
|
+
showSettings: false,
|
|
1273
|
+
showFilters: false,
|
|
1274
|
+
}}
|
|
1275
|
+
/>
|
|
1276
|
+
```
|
|
1277
|
+
|
|
1278
|
+
### Event Detail Mode
|
|
1279
|
+
|
|
1280
|
+
```tsx
|
|
1281
|
+
// Show event details in a slide-in sheet instead of default popover
|
|
1282
|
+
<InnoCalendar
|
|
1283
|
+
events={events}
|
|
1284
|
+
eventDetailMode="sheet"
|
|
1285
|
+
/>
|
|
1286
|
+
|
|
1287
|
+
// Fully custom event detail container
|
|
1288
|
+
<InnoCalendar
|
|
1289
|
+
events={events}
|
|
1290
|
+
eventDetailMode="custom"
|
|
1291
|
+
renderEventDetail={({ event, onClose }) => (
|
|
1292
|
+
<MyCustomDrawer event={event} onClose={onClose} />
|
|
1293
|
+
)}
|
|
1294
|
+
/>
|
|
1295
|
+
```
|
|
1296
|
+
|
|
1297
|
+
### With Preferences Locking
|
|
1298
|
+
|
|
1299
|
+
```tsx
|
|
1300
|
+
<InnoCalendar
|
|
1301
|
+
events={events}
|
|
1302
|
+
preferencesConfig={{
|
|
1303
|
+
defaults: { view: 'month', badgeVariant: 'dot' },
|
|
1304
|
+
modes: {
|
|
1305
|
+
slotDuration: 'locked',
|
|
1306
|
+
showWeekends: 'locked',
|
|
1307
|
+
},
|
|
1308
|
+
locked: {
|
|
1309
|
+
slotDuration: 30,
|
|
1310
|
+
showWeekends: false,
|
|
1311
|
+
},
|
|
1312
|
+
}}
|
|
1313
|
+
/>
|
|
1314
|
+
```
|
|
1315
|
+
|
|
1316
|
+
### Widget Embedding
|
|
1317
|
+
|
|
1318
|
+
```tsx
|
|
1319
|
+
import { AgendaWidget, AgendaDropdown } from '@innosolutions/inno-calendar';
|
|
1320
|
+
|
|
1321
|
+
// Dashboard card
|
|
1322
|
+
<AgendaWidget events={todayEvents} onEventClick={openEvent} />
|
|
1323
|
+
|
|
1324
|
+
// Navbar notification bell
|
|
1325
|
+
<AgendaDropdown events={upcomingEvents} onEventClick={openEvent} />
|
|
1326
|
+
```
|
|
1327
|
+
|
|
1328
|
+
### Custom Slot Example
|
|
1329
|
+
|
|
1330
|
+
```tsx
|
|
1331
|
+
<InnoCalendar
|
|
1332
|
+
events={events}
|
|
1333
|
+
slots={{
|
|
1334
|
+
dayCellFooter: ({ date, events }) => (
|
|
1335
|
+
<div className="text-xs text-muted-foreground border-t mt-1 pt-1">
|
|
1336
|
+
{events.length} events
|
|
1337
|
+
</div>
|
|
1338
|
+
),
|
|
1339
|
+
columnHeader: ({ date, isToday, onDayClick }) => (
|
|
1340
|
+
<button
|
|
1341
|
+
className={cn('text-center', isToday && 'font-bold text-primary')}
|
|
1342
|
+
onClick={() => onDayClick?.(date)}
|
|
1343
|
+
>
|
|
1344
|
+
{date.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric' })}
|
|
1345
|
+
</button>
|
|
1346
|
+
),
|
|
1347
|
+
}}
|
|
1348
|
+
/>
|
|
1349
|
+
```
|
|
1350
|
+
|
|
626
1351
|
---
|
|
627
1352
|
|
|
628
|
-
##
|
|
1353
|
+
## 21. Full Export Catalog
|
|
1354
|
+
|
|
1355
|
+
### From `src/components/`
|
|
1356
|
+
|
|
1357
|
+
**Main Components:**
|
|
1358
|
+
- `InnoCalendar`, `InnoCalendarProps`
|
|
1359
|
+
- `Calendar`, `CalendarProps`
|
|
1360
|
+
- `IntegratedCalendar` (alias), `IntegratedCalendarProps` (alias)
|
|
1361
|
+
|
|
1362
|
+
**Event Components:**
|
|
1363
|
+
- `EventCard`, `EventCardProps`
|
|
1364
|
+
- `EventBlock`, `EventBlockProps`
|
|
1365
|
+
- `EventPopover`, `EventPopoverProps`, `IEventPopoverLabels`, `IPopoverEvent`, `IEventParticipant`
|
|
1366
|
+
- `MultiDayEventBar`, `MultiDayEventBarProps`
|
|
1367
|
+
- `getEventColorClasses`
|
|
1368
|
+
|
|
1369
|
+
**Header Components:**
|
|
1370
|
+
- `CalendarHeader`, `CalendarHeaderProps`
|
|
1371
|
+
- `DateNavigator`, `DateNavigatorProps`
|
|
1372
|
+
- `TodayButton`, `TodayButtonProps`
|
|
1373
|
+
|
|
1374
|
+
**View Components:**
|
|
1375
|
+
- `DayView`, `DayViewProps`
|
|
1376
|
+
- `WeekView`, `WeekViewProps`
|
|
1377
|
+
- `MonthView`, `MonthViewProps`
|
|
1378
|
+
- `YearView`, `YearViewProps`
|
|
1379
|
+
- `AgendaView`, `AgendaViewProps`
|
|
1380
|
+
- `TimelineView`, `TimelineViewProps`
|
|
1381
|
+
- `DayEventsExpansion`, `DayEventsExpansionProps`
|
|
1382
|
+
|
|
1383
|
+
**Primitive Components:**
|
|
1384
|
+
- `DaySlot`, `DaySlotProps`
|
|
1385
|
+
- `TimeSlot`
|
|
1386
|
+
- `MultiDayBanner`, `MultiDayBannerProps`
|
|
1387
|
+
|
|
1388
|
+
**Settings Components:**
|
|
1389
|
+
- `BadgeVariantSetting`, `BadgeVariantSettingProps`
|
|
1390
|
+
- `SlotDurationSetting`, `SlotDurationSettingProps`
|
|
1391
|
+
- `VisibleHoursSetting`, `VisibleHoursSettingProps`
|
|
1392
|
+
- `WorkingHoursSetting`, `WorkingHoursSettingProps`
|
|
1393
|
+
- `DEFAULT_WEEK_WORKING_HOURS`, `IDayWorkingHours`, `IWeekWorkingHours`, `TDayOfWeek`
|
|
1394
|
+
|
|
1395
|
+
**Filter Components:**
|
|
1396
|
+
- `CalendarFilterSidebar`, `CalendarFilterSidebarProps`, `CalendarFilterSidebarLabels`
|
|
1397
|
+
- `ScheduleTypeFilter`, `ScheduleTypeFilterProps`, `IScheduleTypeOption`
|
|
1398
|
+
- `UserFilter`, `UserFilterProps`, `IUserOption`
|
|
1399
|
+
|
|
1400
|
+
**Widget Components:**
|
|
1401
|
+
- `AgendaWidget`, `AgendaWidgetProps`
|
|
1402
|
+
- `AgendaDropdown`, `AgendaDropdownProps`
|
|
1403
|
+
|
|
1404
|
+
**UI Primitives:**
|
|
1405
|
+
- `Button`, `ButtonProps`, `buttonVariants`
|
|
1406
|
+
- `Dialog`, `DialogContent`, `DialogTrigger`, `DialogClose`, `DialogHeader`, `DialogTitle`, `DialogDescription`
|
|
1407
|
+
- `Popover`, `PopoverContent`, `PopoverTrigger`, `PopoverAnchor`
|
|
1408
|
+
- `Sheet`, `SheetContent`, `SheetTrigger`, `SheetClose`, `SheetHeader`, `SheetTitle`, `SheetDescription`
|
|
1409
|
+
- `Tooltip`, `TooltipContent`, `TooltipProvider`, `TooltipTrigger`
|
|
1410
|
+
|
|
1411
|
+
### From `src/core/`
|
|
1412
|
+
|
|
1413
|
+
**Types** (60+ type exports) — see Section 4.
|
|
1414
|
+
|
|
1415
|
+
**Context Providers:**
|
|
1416
|
+
- `InnoCalendarProvider`, `InnoCalendarProviderProps`
|
|
1417
|
+
- `CalendarProvider`, `CalendarProviderProps`
|
|
1418
|
+
- `SlotSelectionProvider`, `SlotSelectionProviderProps`
|
|
1419
|
+
- `DragDropProvider`, `DragDropProviderProps`
|
|
1420
|
+
- `IntegratedCalendarProvider` (alias), `IntegratedCalendarProviderProps` (alias)
|
|
1421
|
+
|
|
1422
|
+
**Hooks** — see Section 14.
|
|
1423
|
+
|
|
1424
|
+
**Constants** — see Section 19.
|
|
1425
|
+
|
|
1426
|
+
**Utilities** — see Section 16. All re-exported via `export * from './utils'`.
|
|
1427
|
+
|
|
1428
|
+
**Preferences:**
|
|
1429
|
+
- `IPreferences`, `IPreferencesConfig`, `IUsePreferencesReturn`
|
|
1430
|
+
- `TPreferenceMode`, `TPreferenceModes`, `TLockedPreferences`, `TPartialPreferences`, `TPreferenceKey`
|
|
1431
|
+
- `IVisibleHoursConfig`, `TWorkingHoursConfig`
|
|
1432
|
+
- `PREFERENCES_STORAGE_KEY`, default constants
|
|
1433
|
+
- `useAdvancedPreferences` (re-exported as alias)
|
|
1434
|
+
|
|
1435
|
+
### From `src/presets/`
|
|
629
1436
|
|
|
630
|
-
|
|
1437
|
+
- `DefaultCalendar`
|
|
1438
|
+
- `TailwindCalendar`
|
|
631
1439
|
|
|
632
|
-
|
|
633
|
-
| ---------- | ------------------------ | ------------------------------------------ |
|
|
634
|
-
| Components | `kebab-case.tsx` | `event-card.tsx`, `week-view.tsx` |
|
|
635
|
-
| Hooks | `use-kebab-case.ts` | `use-calendar.ts`, `use-slot-selection.ts` |
|
|
636
|
-
| Context | `kebab-case-context.tsx` | `calendar-context.tsx` |
|
|
637
|
-
| Types | `types.ts` | `types.ts` |
|
|
638
|
-
| Utilities | `kebab-case.ts` | `date-utils.ts`, `position-utils.ts` |
|
|
639
|
-
| Constants | `constants.ts` | `constants.ts` |
|
|
640
|
-
| Index | `index.ts` | `index.ts` |
|
|
641
|
-
| Styles | `kebab-case.css` | `calendar.css` |
|
|
1440
|
+
### From `src/lib/`
|
|
642
1441
|
|
|
643
|
-
|
|
1442
|
+
- `cn` — `clsx(...inputs) | twMerge` className composer
|
|
644
1443
|
|
|
645
1444
|
---
|
|
646
1445
|
|
|
647
|
-
##
|
|
1446
|
+
## 22. File Naming Conventions
|
|
648
1447
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
1448
|
+
**All files and folders use kebab-case.**
|
|
1449
|
+
|
|
1450
|
+
| Type | Pattern | Example |
|
|
1451
|
+
|---|---|---|
|
|
1452
|
+
| Components | `kebab-case.tsx` | `event-card.tsx` |
|
|
1453
|
+
| Hooks | `use-kebab-case.ts` | `use-calendar.ts` |
|
|
1454
|
+
| Context | `kebab-case.tsx` | `calendar-context.tsx` |
|
|
1455
|
+
| Types | `types.ts` | `types.ts` |
|
|
1456
|
+
| Utilities | `kebab-case.ts` | `date-utils.ts` |
|
|
1457
|
+
| Constants | `constants.ts` | `constants.ts` |
|
|
1458
|
+
| Barrel exports | `index.ts` | `index.ts` |
|
|
1459
|
+
| Styles | `kebab-case.css` | `calendar.css` |
|
|
1460
|
+
|
|
1461
|
+
---
|
|
1462
|
+
|
|
1463
|
+
## 23. Styling Patterns
|
|
1464
|
+
|
|
1465
|
+
### className Composition
|
|
1466
|
+
|
|
1467
|
+
```tsx
|
|
1468
|
+
import { cn } from '../../lib/utils';
|
|
1469
|
+
|
|
1470
|
+
<div className={cn('base-styles', isActive && 'active-styles', className)} />
|
|
1471
|
+
```
|
|
1472
|
+
|
|
1473
|
+
### CVA for Variants
|
|
1474
|
+
|
|
1475
|
+
```tsx
|
|
1476
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
1477
|
+
|
|
1478
|
+
const buttonVariants = cva('base-classes', {
|
|
1479
|
+
variants: {
|
|
1480
|
+
variant: { default: '...', outline: '...' },
|
|
1481
|
+
size: { sm: '...', md: '...' },
|
|
1482
|
+
},
|
|
1483
|
+
defaultVariants: { variant: 'default', size: 'md' },
|
|
1484
|
+
});
|
|
1485
|
+
```
|
|
1486
|
+
|
|
1487
|
+
---
|
|
1488
|
+
|
|
1489
|
+
## 24. Biome Configuration
|
|
1490
|
+
|
|
1491
|
+
From `biome.json`:
|
|
1492
|
+
|
|
1493
|
+
- **Formatter**: tabs, single quotes, 100 line width
|
|
1494
|
+
- **Linter**: recommended + `noUnusedVariables`, `noExplicitAny`
|
|
1495
|
+
- **Organize imports**: enabled
|
|
1496
|
+
|
|
1497
|
+
```bash
|
|
1498
|
+
npm run lint # Check
|
|
1499
|
+
npm run format # Auto-fix
|
|
1500
|
+
```
|
|
655
1501
|
|
|
656
1502
|
---
|
|
657
1503
|
|
|
658
|
-
##
|
|
1504
|
+
## 25. State Synchronization Guide
|
|
1505
|
+
|
|
1506
|
+
How state flows through the calendar and guidance for integrating with consumer projects.
|
|
1507
|
+
|
|
1508
|
+
### Data Flow
|
|
1509
|
+
|
|
1510
|
+
```
|
|
1511
|
+
Consumer App
|
|
1512
|
+
└─ <InnoCalendar events={events} onEventDrop={...} ...>
|
|
1513
|
+
└─ DragDropProvider (drag state, onEventDrop callback)
|
|
1514
|
+
└─ InnoCalendarProvider (view, date, preferences, filtered events)
|
|
1515
|
+
└─ SlotSelectionProvider (drag-to-select state)
|
|
1516
|
+
└─ InnerCalendar (renders header + active view)
|
|
1517
|
+
├─ CalendarHeader (nav, view switcher, settings)
|
|
1518
|
+
└─ Active View (DayView, WeekView, TimelineView, etc.)
|
|
1519
|
+
└─ EventCard (popover/dialog/sheet/custom)
|
|
1520
|
+
```
|
|
1521
|
+
|
|
1522
|
+
### Events: BYO Approach
|
|
1523
|
+
|
|
1524
|
+
The package **never fetches data**. The consumer owns the events array and updates it:
|
|
1525
|
+
|
|
1526
|
+
```tsx
|
|
1527
|
+
// Using React Query
|
|
1528
|
+
const { data: events } = useQuery(['events', dateRange], fetchEvents);
|
|
1529
|
+
const updateEvent = useMutation(api.updateEvent, {
|
|
1530
|
+
onSuccess: () => queryClient.invalidateQueries(['events']),
|
|
1531
|
+
});
|
|
1532
|
+
|
|
1533
|
+
<InnoCalendar
|
|
1534
|
+
events={events ?? []}
|
|
1535
|
+
onEventDrop={(result) => updateEvent.mutate({
|
|
1536
|
+
id: result.event.id,
|
|
1537
|
+
startDate: result.newStartDate,
|
|
1538
|
+
endDate: result.newEndDate,
|
|
1539
|
+
resourceId: result.newResourceId,
|
|
1540
|
+
})}
|
|
1541
|
+
onSlotSelect={(sel) => openCreateDialog(sel.startDate, sel.endDate)}
|
|
1542
|
+
/>
|
|
1543
|
+
```
|
|
1544
|
+
|
|
1545
|
+
### Preferences Persistence
|
|
659
1546
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
1547
|
+
Preferences (view, slot duration, badge variant, etc.) persist to `localStorage` by default. The key is configurable:
|
|
1548
|
+
|
|
1549
|
+
```tsx
|
|
1550
|
+
<InnoCalendar
|
|
1551
|
+
preferencesConfig={{
|
|
1552
|
+
storageKey: 'my-app-calendar-prefs', // default: 'inno-calendar-preferences'
|
|
1553
|
+
defaults: { view: 'week', badgeVariant: 'colored' },
|
|
1554
|
+
modes: { slotDuration: 'locked' }, // prevent user from changing
|
|
1555
|
+
locked: { slotDuration: 30 },
|
|
1556
|
+
}}
|
|
1557
|
+
/>
|
|
1558
|
+
```
|
|
665
1559
|
|
|
666
|
-
|
|
1560
|
+
### Cross-Project State Patterns
|
|
1561
|
+
|
|
1562
|
+
For apps that embed InnoCalendar (e.g., promosport-erp, generationimmo-erp):
|
|
1563
|
+
|
|
1564
|
+
1. **URL Sync** — Sync view/date with URL params so back-button works:
|
|
1565
|
+
```tsx
|
|
1566
|
+
<InnoCalendar
|
|
1567
|
+
initialView={searchParams.get('view') as TCalendarView ?? 'week'}
|
|
1568
|
+
initialDate={searchParams.get('date') ? new Date(searchParams.get('date')!) : undefined}
|
|
1569
|
+
onViewChange={(v) => setSearchParams({ view: v })}
|
|
1570
|
+
onDateChange={(d) => setSearchParams({ date: d.toISOString() })}
|
|
1571
|
+
/>
|
|
1572
|
+
```
|
|
1573
|
+
|
|
1574
|
+
2. **Deep-linking events** — Navigate to a specific event from another page:
|
|
1575
|
+
```tsx
|
|
1576
|
+
<InnoCalendar events={events} focusEventId={eventIdFromUrl} />
|
|
1577
|
+
```
|
|
1578
|
+
|
|
1579
|
+
3. **External filter state** — The calendar's built-in filtering is opt-in. You can filter server-side and just pass filtered `events`:
|
|
1580
|
+
```tsx
|
|
1581
|
+
const filteredEvents = useFilteredEvents(allEvents, externalFilters);
|
|
1582
|
+
<InnoCalendar events={filteredEvents} />
|
|
1583
|
+
```
|
|
1584
|
+
|
|
1585
|
+
4. **Imperative control** — Use ref for programmatic scroll/navigation:
|
|
1586
|
+
```tsx
|
|
1587
|
+
const ref = useRef<ICalendarRefHandle>(null);
|
|
1588
|
+
ref.current?.scrollToToday();
|
|
1589
|
+
ref.current?.scrollToWorkingHours();
|
|
1590
|
+
ref.current?.focusEvent(eventId);
|
|
1591
|
+
```
|
|
667
1592
|
|
|
668
1593
|
---
|
|
669
1594
|
|
|
670
|
-
##
|
|
671
|
-
|
|
672
|
-
1. **
|
|
673
|
-
2. **
|
|
674
|
-
3. **
|
|
675
|
-
4. **
|
|
676
|
-
5. **
|
|
677
|
-
6. **
|
|
678
|
-
7. **
|
|
679
|
-
8. **Run `npm run typecheck
|
|
680
|
-
9. **
|
|
681
|
-
10. **
|
|
1595
|
+
## 26. Critical Rules
|
|
1596
|
+
|
|
1597
|
+
1. **CalendarEvent must stay domain-agnostic** — no new top-level fields. Use `data?: TData` for domain data.
|
|
1598
|
+
2. **Deprecated fields stay** until next major version — never remove them prematurely.
|
|
1599
|
+
3. **Built-in components read `event.data` first**, then fall back to deprecated top-level fields.
|
|
1600
|
+
4. **Core module has zero external dependencies** — only React + native APIs.
|
|
1601
|
+
5. **Follow existing patterns** before inventing new ones (composition, slot props, CVA).
|
|
1602
|
+
6. **kebab-case everywhere** — files, folders, no exceptions.
|
|
1603
|
+
7. **JSDoc on every public export** — summary + `@param` + `@example` + `@default`.
|
|
1604
|
+
8. **Run `npm run typecheck`** before committing.
|
|
1605
|
+
9. **Update AGENT.md, README.md, and copilot-instructions.md** when public API changes.
|
|
1606
|
+
10. **Section separators** in type files: `// ============================================================================`
|
|
1607
|
+
11. **Performance**: `useMemo`, `useCallback`, memoize context values, stable keys.
|
|
1608
|
+
12. **Accessibility**: semantic HTML, ARIA attributes, keyboard navigation (Arrow keys, Enter/Space, Escape, Tab).
|
|
1609
|
+
13. **No inline objects/arrays in JSX props** — extract or memoize them.
|
|
1610
|
+
14. **Generic type propagation** — maintain `<TData>` through hooks, context, and component boundaries.
|