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