@innosolutions/inno-calendar 1.0.49 → 1.0.51

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