@innosolutions/inno-calendar 1.0.59 → 1.0.61

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