@innosolutions/inno-calendar 1.0.72 → 2.0.1

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 (200) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/LICENSE +48 -48
  3. package/README.md +333 -305
  4. package/dist/components/event/event-card.d.ts +4 -28
  5. package/dist/components/event/event-card.d.ts.map +1 -1
  6. package/dist/components/header/calendar-header.d.ts +14 -3
  7. package/dist/components/header/calendar-header.d.ts.map +1 -1
  8. package/dist/components/index.cjs +1 -1
  9. package/dist/components/index.d.ts +7 -6
  10. package/dist/components/index.d.ts.map +1 -1
  11. package/dist/components/index.mjs +48 -57
  12. package/dist/components/index.mjs.map +1 -1
  13. package/dist/components/inno-calendar.d.ts +139 -72
  14. package/dist/components/inno-calendar.d.ts.map +1 -1
  15. package/dist/components/primitives/field-layer.d.ts +22 -0
  16. package/dist/components/primitives/field-layer.d.ts.map +1 -0
  17. package/dist/components/primitives/index.d.ts +3 -0
  18. package/dist/components/primitives/index.d.ts.map +1 -1
  19. package/dist/components/primitives/multi-day-banner.d.ts.map +1 -1
  20. package/dist/components/primitives/quick-create.d.ts +44 -0
  21. package/dist/components/primitives/quick-create.d.ts.map +1 -0
  22. package/dist/components/primitives/recurrence-field.d.ts +102 -0
  23. package/dist/components/primitives/recurrence-field.d.ts.map +1 -0
  24. package/dist/components/primitives/week-all-day-row.d.ts.map +1 -1
  25. package/dist/components/primitives/when-row.d.ts +41 -0
  26. package/dist/components/primitives/when-row.d.ts.map +1 -0
  27. package/dist/components/shell/calendar-settings.d.ts +28 -0
  28. package/dist/components/shell/calendar-settings.d.ts.map +1 -0
  29. package/dist/components/shell/calendar-sidebar.d.ts +27 -0
  30. package/dist/components/shell/calendar-sidebar.d.ts.map +1 -0
  31. package/dist/components/shell/filter-panel.d.ts +10 -0
  32. package/dist/components/shell/filter-panel.d.ts.map +1 -0
  33. package/dist/components/shell/index.d.ts +5 -0
  34. package/dist/components/shell/index.d.ts.map +1 -0
  35. package/dist/components/shell/mini-month.d.ts +16 -0
  36. package/dist/components/shell/mini-month.d.ts.map +1 -0
  37. package/dist/components/ui/index.d.ts +0 -2
  38. package/dist/components/ui/index.d.ts.map +1 -1
  39. package/dist/components/views/agenda-view.d.ts +14 -15
  40. package/dist/components/views/agenda-view.d.ts.map +1 -1
  41. package/dist/components/views/day-events-expansion.d.ts +1 -1
  42. package/dist/components/views/day-events-expansion.d.ts.map +1 -1
  43. package/dist/components/views/day-view.d.ts +2 -6
  44. package/dist/components/views/day-view.d.ts.map +1 -1
  45. package/dist/components/views/index.d.ts +2 -1
  46. package/dist/components/views/index.d.ts.map +1 -1
  47. package/dist/components/views/month-view.d.ts +2 -12
  48. package/dist/components/views/month-view.d.ts.map +1 -1
  49. package/dist/components/views/resource-columns-view.d.ts +23 -0
  50. package/dist/components/views/resource-columns-view.d.ts.map +1 -0
  51. package/dist/components/views/timeline-view.d.ts +2 -6
  52. package/dist/components/views/timeline-view.d.ts.map +1 -1
  53. package/dist/components/views/week-view.d.ts +10 -6
  54. package/dist/components/views/week-view.d.ts.map +1 -1
  55. package/dist/components/views/year-view.d.ts +2 -6
  56. package/dist/components/views/year-view.d.ts.map +1 -1
  57. package/dist/core/constants.d.ts +39 -1
  58. package/dist/core/constants.d.ts.map +1 -1
  59. package/dist/core/context/drag-drop-context.d.ts +15 -1
  60. package/dist/core/context/drag-drop-context.d.ts.map +1 -1
  61. package/dist/core/context/event-card-context.d.ts +16 -0
  62. package/dist/core/context/event-card-context.d.ts.map +1 -0
  63. package/dist/core/context/inno-calendar-provider.d.ts +10 -1
  64. package/dist/core/context/inno-calendar-provider.d.ts.map +1 -1
  65. package/dist/core/context/slot-selection-context.d.ts +16 -0
  66. package/dist/core/context/slot-selection-context.d.ts.map +1 -1
  67. package/dist/core/index.cjs +1 -1
  68. package/dist/core/index.d.ts +3 -1
  69. package/dist/core/index.d.ts.map +1 -1
  70. package/dist/core/index.mjs +155 -149
  71. package/dist/core/index.mjs.map +1 -1
  72. package/dist/core/types.d.ts +44 -514
  73. package/dist/core/types.d.ts.map +1 -1
  74. package/dist/core/utils/date-input.d.ts +8 -0
  75. package/dist/core/utils/date-input.d.ts.map +1 -0
  76. package/dist/core/utils/grid-utils.d.ts.map +1 -1
  77. package/dist/core/utils/index.d.ts +1 -0
  78. package/dist/core/utils/index.d.ts.map +1 -1
  79. package/dist/core/utils/time-format.d.ts +33 -0
  80. package/dist/core/utils/time-format.d.ts.map +1 -0
  81. package/dist/core/utils/view-storage.d.ts +2 -2
  82. package/dist/core/utils/view-storage.d.ts.map +1 -1
  83. package/dist/core/z-layers.d.ts +21 -0
  84. package/dist/core/z-layers.d.ts.map +1 -0
  85. package/dist/index.cjs +1 -1
  86. package/dist/index.d.ts +0 -1
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.mjs +228 -234
  89. package/dist/index.mjs.map +1 -1
  90. package/dist/inno-calendar-DFEaWcSi.cjs +11 -0
  91. package/dist/inno-calendar-DFEaWcSi.cjs.map +1 -0
  92. package/dist/inno-calendar-R7LPGSJU.js +7454 -0
  93. package/dist/inno-calendar-R7LPGSJU.js.map +1 -0
  94. package/dist/lib/index.cjs +1 -1
  95. package/dist/lib/index.mjs +1 -1
  96. package/dist/position-utils-BH6T1FL5.js +1125 -0
  97. package/dist/position-utils-BH6T1FL5.js.map +1 -0
  98. package/dist/position-utils-BVt-Bp93.cjs +2 -0
  99. package/dist/position-utils-BVt-Bp93.cjs.map +1 -0
  100. package/dist/styles/index.css +1 -1
  101. package/dist/use-slot-selection-CSsSyfAi.cjs +2 -0
  102. package/dist/use-slot-selection-CSsSyfAi.cjs.map +1 -0
  103. package/dist/use-slot-selection-CT1z9v9X.js +357 -0
  104. package/dist/use-slot-selection-CT1z9v9X.js.map +1 -0
  105. package/dist/utils-B6yFEsav.js +9 -0
  106. package/dist/utils-B6yFEsav.js.map +1 -0
  107. package/dist/utils-IjLH3w2e.cjs +2 -0
  108. package/dist/utils-IjLH3w2e.cjs.map +1 -0
  109. package/dist/utils.cjs +1 -1
  110. package/dist/utils.d.ts +1 -1
  111. package/dist/utils.d.ts.map +1 -1
  112. package/dist/utils.mjs +108 -104
  113. package/dist/z-layers-CpHPAMdp.js +637 -0
  114. package/dist/z-layers-CpHPAMdp.js.map +1 -0
  115. package/dist/z-layers-D1DivrJg.cjs +2 -0
  116. package/dist/z-layers-D1DivrJg.cjs.map +1 -0
  117. package/package.json +117 -138
  118. package/AGENT.md +0 -1783
  119. package/dist/agenda-widget-B0t3TCcV.js +0 -4141
  120. package/dist/agenda-widget-B0t3TCcV.js.map +0 -1
  121. package/dist/agenda-widget-i2tjGr5U.cjs +0 -2
  122. package/dist/agenda-widget-i2tjGr5U.cjs.map +0 -1
  123. package/dist/components/calendar.d.ts +0 -45
  124. package/dist/components/calendar.d.ts.map +0 -1
  125. package/dist/components/event/event-page-transition.d.ts +0 -32
  126. package/dist/components/event/event-page-transition.d.ts.map +0 -1
  127. package/dist/components/filters/calendar-filter-sidebar.d.ts +0 -54
  128. package/dist/components/filters/calendar-filter-sidebar.d.ts.map +0 -1
  129. package/dist/components/filters/index.d.ts +0 -9
  130. package/dist/components/filters/index.d.ts.map +0 -1
  131. package/dist/components/filters/schedule-type-filter.d.ts +0 -31
  132. package/dist/components/filters/schedule-type-filter.d.ts.map +0 -1
  133. package/dist/components/filters/user-filter.d.ts +0 -31
  134. package/dist/components/filters/user-filter.d.ts.map +0 -1
  135. package/dist/components/header/components/index.d.ts +0 -8
  136. package/dist/components/header/components/index.d.ts.map +0 -1
  137. package/dist/components/integrated-calendar.d.ts +0 -37
  138. package/dist/components/integrated-calendar.d.ts.map +0 -1
  139. package/dist/components/settings/badge-variant-setting.d.ts +0 -25
  140. package/dist/components/settings/badge-variant-setting.d.ts.map +0 -1
  141. package/dist/components/settings/index.d.ts +0 -10
  142. package/dist/components/settings/index.d.ts.map +0 -1
  143. package/dist/components/settings/slot-duration-setting.d.ts +0 -20
  144. package/dist/components/settings/slot-duration-setting.d.ts.map +0 -1
  145. package/dist/components/settings/visible-hours-setting.d.ts +0 -23
  146. package/dist/components/settings/visible-hours-setting.d.ts.map +0 -1
  147. package/dist/components/settings/working-hours-setting.d.ts +0 -41
  148. package/dist/components/settings/working-hours-setting.d.ts.map +0 -1
  149. package/dist/components/ui/dialog.d.ts +0 -35
  150. package/dist/components/ui/dialog.d.ts.map +0 -1
  151. package/dist/components/ui/select.d.ts +0 -12
  152. package/dist/components/ui/select.d.ts.map +0 -1
  153. package/dist/components/widget/agenda-dropdown.d.ts +0 -20
  154. package/dist/components/widget/agenda-dropdown.d.ts.map +0 -1
  155. package/dist/components/widget/agenda-widget.d.ts +0 -27
  156. package/dist/components/widget/agenda-widget.d.ts.map +0 -1
  157. package/dist/components/widget/index.d.ts +0 -6
  158. package/dist/components/widget/index.d.ts.map +0 -1
  159. package/dist/index-D2U2F80P.cjs +0 -2
  160. package/dist/index-D2U2F80P.cjs.map +0 -1
  161. package/dist/index-DtaLkIY8.js +0 -2777
  162. package/dist/index-DtaLkIY8.js.map +0 -1
  163. package/dist/position-utils-D_KpwRus.js +0 -1053
  164. package/dist/position-utils-D_KpwRus.js.map +0 -1
  165. package/dist/position-utils-ksZ_IjXC.cjs +0 -2
  166. package/dist/position-utils-ksZ_IjXC.cjs.map +0 -1
  167. package/dist/presets/default/default-calendar.d.ts +0 -44
  168. package/dist/presets/default/default-calendar.d.ts.map +0 -1
  169. package/dist/presets/default/index.d.ts +0 -2
  170. package/dist/presets/default/index.d.ts.map +0 -1
  171. package/dist/presets/index.cjs +0 -2
  172. package/dist/presets/index.cjs.map +0 -1
  173. package/dist/presets/index.d.ts +0 -3
  174. package/dist/presets/index.d.ts.map +0 -1
  175. package/dist/presets/index.mjs +0 -6
  176. package/dist/presets/index.mjs.map +0 -1
  177. package/dist/presets/tailwind/index.d.ts +0 -2
  178. package/dist/presets/tailwind/index.d.ts.map +0 -1
  179. package/dist/presets/tailwind/tailwind-calendar.d.ts +0 -122
  180. package/dist/presets/tailwind/tailwind-calendar.d.ts.map +0 -1
  181. package/dist/slot-selection-context-CLYZU6ul.cjs +0 -2
  182. package/dist/slot-selection-context-CLYZU6ul.cjs.map +0 -1
  183. package/dist/slot-selection-context-Ct5cVcKW.js +0 -611
  184. package/dist/slot-selection-context-Ct5cVcKW.js.map +0 -1
  185. package/dist/tailwind-calendar-BbUb61uY.cjs +0 -2
  186. package/dist/tailwind-calendar-BbUb61uY.cjs.map +0 -1
  187. package/dist/tailwind-calendar-DrwGVSF-.js +0 -400
  188. package/dist/tailwind-calendar-DrwGVSF-.js.map +0 -1
  189. package/dist/use-calendar-BK0nQYJq.cjs +0 -2
  190. package/dist/use-calendar-BK0nQYJq.cjs.map +0 -1
  191. package/dist/use-calendar-DJI35SSd.js +0 -175
  192. package/dist/use-calendar-DJI35SSd.js.map +0 -1
  193. package/dist/use-slot-selection-CCE11R4j.cjs +0 -2
  194. package/dist/use-slot-selection-CCE11R4j.cjs.map +0 -1
  195. package/dist/use-slot-selection-CX1w8EdM.js +0 -188
  196. package/dist/use-slot-selection-CX1w8EdM.js.map +0 -1
  197. package/dist/week-view-BXmaRChB.cjs +0 -11
  198. package/dist/week-view-BXmaRChB.cjs.map +0 -1
  199. package/dist/week-view-BeasFcyR.js +0 -3520
  200. package/dist/week-view-BeasFcyR.js.map +0 -1
package/README.md CHANGED
@@ -1,305 +1,333 @@
1
- # @inno/calendar
2
-
3
- A headless-first, fully customizable React calendar for enterprise applications.
4
-
5
- [![npm version](https://img.shields.io/npm/v/@innosolutions/inno-calendar.svg)](https://www.npmjs.com/package/@innosolutions/inno-calendar)
6
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
7
- [![React](https://img.shields.io/badge/React-18+-61DAFB.svg)](https://reactjs.org/)
8
-
9
- > **🤖 AI Agents:** For comprehensive implementation details, copy the contents of [AGENT.md](./AGENT.md) into your context.
10
-
11
- ## Installation
12
-
13
- ```bash
14
- npm install @innosolutions/inno-calendar
15
- ```
16
-
17
- ```bash
18
- # Optional: Radix UI for enhanced popovers/tooltips
19
- npm install @radix-ui/react-popover @radix-ui/react-tooltip @radix-ui/react-dropdown-menu
20
- ```
21
-
22
- ## Quick Start
23
-
24
- ```tsx
25
- import { InnoCalendar, type CalendarEvent } from "@innosolutions/inno-calendar";
26
- import "@innosolutions/inno-calendar/styles";
27
-
28
- const events: CalendarEvent[] = [
29
- {
30
- id: "1",
31
- title: "Team Meeting",
32
- startDate: new Date("2026-02-04T09:00:00"),
33
- endDate: new Date("2026-02-04T10:00:00"),
34
- color: "blue",
35
- },
36
- ];
37
-
38
- function MyCalendar() {
39
- return (
40
- <InnoCalendar
41
- events={events}
42
- initialView="week"
43
- onEventClick={(event) => console.log(event)}
44
- onSlotSelect={(selection) =>
45
- console.log(selection.startDate, selection.endDate)
46
- }
47
- />
48
- );
49
- }
50
- ```
51
-
52
- ## Features
53
-
54
- - **8 View Modes** — Day, Week, Month, Year, Agenda, Timeline (1/3/7 day)
55
- - **Headless Architecture** — Use hooks directly or pre-built components
56
- - **Drag & Drop** — Drag to select time ranges, drag events to reschedule
57
- - **TypeScript** — Fully typed with generic event data support
58
- - **Customizable** — Custom popovers, settings panels, filters
59
- - **No External Dependencies** — Native Date API, minimal footprint
60
-
61
- ## Views
62
-
63
- | View | Description |
64
- | --------------- | -------------------------- |
65
- | `day` | Single day hourly grid |
66
- | `week` | 7-day grid with time slots |
67
- | `month` | Monthly calendar |
68
- | `year` | 12-month overview |
69
- | `agenda` | Scrollable event list |
70
- | `timeline-day` | Resource timeline (1 day) |
71
- | `timeline-3day` | Resource timeline (3 days) |
72
- | `timeline-week` | Resource timeline (7 days) |
73
- | `resource-day` | Resource view (1 day) |
74
- | `resource-week` | Resource view (7 days) |
75
-
76
- ## Event Structure
77
-
78
- ```tsx
79
- interface CalendarEvent<TData = Record<string, unknown>> {
80
- // Required
81
- id: string;
82
- title: ReactNode; // String or JSX
83
- startDate: Date;
84
- endDate: Date;
85
-
86
- // Optional display fields
87
- color?:
88
- | "blue"
89
- | "green"
90
- | "red"
91
- | "yellow"
92
- | "purple"
93
- | "orange"
94
- | "pink"
95
- | "teal"
96
- | "gray"
97
- | "indigo";
98
- hexColor?: string; // Hex override (takes precedence over color)
99
- description?: ReactNode; // String or JSX
100
- isCanceled?: boolean;
101
- isAllDay?: boolean;
102
- isMultiDay?: boolean;
103
- isRecurring?: boolean;
104
- resourceId?: string; // For resource/timeline views
105
-
106
- // Built-in filtering (optional — skip if you filter server-side)
107
- scheduleTypeId?: number;
108
- scheduleTypeName?: ReactNode; // String or JSX
109
- participants?: ICalendarUser[];
110
-
111
- // @deprecated — still functional, but migrate to `data` bag.
112
- // Will be removed in the next major version.
113
- meetingTookPlace?: boolean; // → data.meetingTookPlace
114
- isAccepted?: boolean; // → data.isAccepted
115
- companyId?: number; // → data.companyId
116
- cancelReason?: string | null; // → data.cancelReason
117
- user?: ICalendarUser; // → data.user
118
- // ... and others (see types.ts for full list)
119
-
120
- data?: TData; // Your custom domain-specific fields (RECOMMENDED)
121
- }
122
- ```
123
-
124
- ### ReactNode Props
125
-
126
- `title`, `description`, and `scheduleTypeName` accept `ReactNode` — you can pass plain strings (fully backwards compatible) or custom JSX:
127
-
128
- ```tsx
129
- const event: CalendarEvent = {
130
- id: "1",
131
- title: <span><UserIcon className="h-3 w-3 inline" /> John Doe</span>,
132
- description: <p className="italic">VIP client meeting</p>,
133
- startDate: new Date(),
134
- endDate: new Date(),
135
- };
136
- ```
137
-
138
- The built-in search and `aria-label` automatically extract plain text from JSX nodes via the `reactNodeToText` utility.
139
-
140
- ### Extended Props (data.extendedProps)
141
-
142
- For rendering an arbitrary number of custom lines on EventCard/EventBlock, use the `extendedProps` array in the `data` bag:
143
-
144
- ```tsx
145
- const event: CalendarEvent = {
146
- id: "1",
147
- title: "Practical Course",
148
- startDate: new Date(),
149
- endDate: new Date(),
150
- data: {
151
- extendedProps: [
152
- <p key="student" className="font-medium">Student: Jane Doe</p>,
153
- <p key="phone">Phone: +32 123 456 789</p>,
154
- <div key="info" className="flex items-center gap-1">
155
- <CarIcon className="h-3 w-3" />
156
- <span>Automatic transmission</span>
157
- </div>,
158
- ],
159
- },
160
- };
161
- ```
162
-
163
- Each entry renders as its own row. Visible on EventCard (full variant) and EventBlock (events >= 45min). Also shown in tooltips.
164
-
165
- ## Props
166
-
167
- ```tsx
168
- <InnoCalendar
169
- // Data
170
- events={events}
171
- users={users}
172
- alwaysShowResources={alwaysShowResources}
173
- scheduleTypes={scheduleTypes}
174
- // Initial State
175
- initialView="week"
176
- initialDate={new Date()}
177
- // Callbacks
178
- onEventClick={(event) => {}}
179
- onSlotSelect={(selection) => {}}
180
- onSlotClick={(date, hour) => {}}
181
- onAddEvent={() => {}}
182
- onEventDrop={(result) => {}}
183
- onDateChange={(date, view) => {}}
184
- onViewChange={(view) => {}}
185
- // Customization
186
- renderPopover={({ event, onClose }) => <CustomPopover />}
187
- settingsContent={<SettingsPanel />}
188
- filterContent={<FiltersRow />}
189
- showHeader={true}
190
- />
191
- ```
192
-
193
- ## Provider Pattern
194
-
195
- For shared state across components:
196
-
197
- ```tsx
198
- import {
199
- InnoCalendarProvider,
200
- useInnoCalendar,
201
- CalendarHeader,
202
- WeekView,
203
- } from "@innosolutions/inno-calendar";
204
-
205
- function App() {
206
- return (
207
- <InnoCalendarProvider initialEvents={events} initialView="week">
208
- <CalendarContent />
209
- </InnoCalendarProvider>
210
- );
211
- }
212
-
213
- function CalendarContent() {
214
- const { view, filteredEvents, selectedDate } = useInnoCalendar();
215
- return (
216
- <>
217
- <CalendarHeader />
218
- <WeekView events={filteredEvents} date={selectedDate} />
219
- </>
220
- );
221
- }
222
- ```
223
-
224
- ## Resource & Timeline Views
225
-
226
- The resource-based views (`resource-day`, `resource-week`, `timeline-day`, `timeline-3day`, `timeline-week`) group events by resource and render one row per resource.
227
-
228
- **Default flow** — resource rows are derived from the events: each unique resource referenced by an event (via `event.resourceId`, `event.data.user`, `event.user`, or `event.participants`) gets its own row. Resources without events are not shown.
229
-
230
- **Always-show resources** — pass `alwaysShowResources` to guarantee a row for a resource even when it has no events in the current data. Rows without events appear at the bottom so event-containing rows keep visual priority. If a resource listed here also has events, its row is positioned naturally among the event rows and the events are still attached to it.
231
-
232
- ```tsx
233
- <InnoCalendar
234
- events={events}
235
- initialView="timeline-week"
236
- alwaysShowResources={[
237
- { id: "room-1", name: "Meeting Room A", avatar: "/rooms/a.jpg" },
238
- { id: "room-2", name: "Meeting Room B" },
239
- ]}
240
- />
241
- ```
242
-
243
- Each entry accepts the full `ICalendarUser` shape — at minimum `id` and `name` are needed to render the row header; `avatar` and `email` are optional. Updating the array re-renders the view immediately.
244
-
245
- This prop applies to all resource-based views (`resource-day`, `resource-week`, `timeline-day`, `timeline-3day`, `timeline-week`) and is ignored in `day`, `week`, `month`, `year`, and `agenda` views.
246
-
247
- ## Working Hours
248
-
249
- ```tsx
250
- const workingHours = {
251
- 0: { enabled: false, from: 8, to: 17 }, // Sunday
252
- 1: { enabled: true, from: 9, to: 18 }, // Monday
253
- 2: { enabled: true, from: 9, to: 18 }, // Tuesday
254
- 3: { enabled: true, from: 9, to: 18 }, // Wednesday
255
- 4: { enabled: true, from: 9, to: 18 }, // Thursday
256
- 5: { enabled: true, from: 9, to: 17 }, // Friday
257
- 6: { enabled: false, from: 8, to: 12 }, // Saturday
258
- };
259
-
260
- <InnoCalendar
261
- events={events}
262
- preferencesConfig={{
263
- defaults: { workingHours },
264
- }}
265
- />;
266
- ```
267
-
268
- ## Styles
269
-
270
- Import the bundled styles for proper rendering:
271
-
272
- ```tsx
273
- import "@innosolutions/inno-calendar/styles";
274
- ```
275
-
276
- Or include CSS custom properties for theming:
277
-
278
- ```css
279
- :root {
280
- --inno-border-color: #e5e7eb;
281
- --inno-primary: #3b82f6;
282
- --inno-hour-height: 96px;
283
- }
284
- ```
285
-
286
- ## TypeScript
287
-
288
- ```tsx
289
- import type {
290
- CalendarEvent,
291
- TCalendarView,
292
- TEventColor,
293
- ICalendarUser,
294
- IScheduleType,
295
- ISelectionResult,
296
- TWorkingHours,
297
- } from "@innosolutions/inno-calendar";
298
-
299
- // Utility: extract plain text from any ReactNode (for search, labels, etc.)
300
- import { reactNodeToText } from "@innosolutions/inno-calendar";
301
- ```
302
-
303
- ## License
304
-
305
- MIT © [InnoSolutions](https://github.com/innosolutions)
1
+ # @innosolutions/inno-calendar
2
+
3
+ A React calendar that behaves like Google Calendar and lets your application
4
+ own everything domain-specific.
5
+
6
+ [![npm version](https://img.shields.io/npm/v/@innosolutions/inno-calendar.svg)](https://www.npmjs.com/package/@innosolutions/inno-calendar)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
8
+ [![React](https://img.shields.io/badge/React-18+-61DAFB.svg)](https://reactjs.org/)
9
+
10
+ > Upgrading from 1.x? See [CHANGELOG.md](./CHANGELOG.md) — 2.0.0 removes
11
+ > `slots`, `classNames` and the `/presets` entry point.
12
+
13
+ ## What it owns, and what it doesn't
14
+
15
+ The package owns the parts that are the same in every calendar: the grid, the
16
+ header, the floating card a drag opens, the detail popover, the filter rail,
17
+ the drag-and-drop, the placement maths.
18
+
19
+ Your application owns the parts that differ: what an event *is*, what a form
20
+ asks for, what a detail card shows, what you can filter by. You pass those in
21
+ as components. There is one extension mechanism — render props — not three.
22
+
23
+ ```tsx
24
+ <InnoCalendar
25
+ events={events}
26
+ eventCard={(event, { compact }) => <MyCard event={event} compact={compact} />}
27
+ eventDetail={(event, { close, edit }) => <MyDetail … />}
28
+ eventForm={({ selection, event }, { close, expand }) => <MyForm … />}
29
+ filters={({ close }) => <MyFilters onDone={close} />}
30
+ />
31
+ ```
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ npm install @innosolutions/inno-calendar
37
+ ```
38
+
39
+ `react` and `react-dom` (18 or 19) are the only peer dependencies. Everything
40
+ else the calendar needs — Radix popover, dropdown, tooltip and dialog, plus
41
+ `lucide-react` — comes with it.
42
+
43
+ ## Quick start
44
+
45
+ ```tsx
46
+ import { InnoCalendar, type CalendarEvent } from "@innosolutions/inno-calendar";
47
+ import "@innosolutions/inno-calendar/styles";
48
+
49
+ const events: CalendarEvent[] = [
50
+ {
51
+ id: "1",
52
+ title: "Team meeting",
53
+ startDate: new Date("2026-02-04T09:00:00"),
54
+ endDate: new Date("2026-02-04T10:00:00"),
55
+ color: "blue",
56
+ },
57
+ ];
58
+
59
+ function MyCalendar() {
60
+ return (
61
+ <InnoCalendar
62
+ events={events}
63
+ initialView="week"
64
+ onEventClick={(event) => console.log(event)}
65
+ onSlotSelect={({ startDate, endDate }) => console.log(startDate, endDate)}
66
+ />
67
+ );
68
+ }
69
+ ```
70
+
71
+ That renders a working calendar. Everything below is opt-in.
72
+
73
+ ## Views
74
+
75
+ | View | Description |
76
+ | --- | --- |
77
+ | `day` | Single day, hourly grid |
78
+ | `4day` | Four days |
79
+ | `week` | Seven days with time slots |
80
+ | `month` | Monthly grid |
81
+ | `year` | Twelve-month overview |
82
+ | `agenda` | Scrollable list |
83
+ | `resource-day` / `resource-week` | One column per resource |
84
+ | `timeline-day` / `timeline-3day` / `timeline-week` | One row per resource |
85
+
86
+ ## The event
87
+
88
+ ```tsx
89
+ interface CalendarEvent<TData = Record<string, unknown>> {
90
+ id: string;
91
+ title: ReactNode;
92
+ startDate: Date;
93
+ endDate: Date;
94
+
95
+ color?: "blue" | "green" | "red" | "yellow" | "purple"
96
+ | "orange" | "pink" | "teal" | "gray" | "indigo";
97
+ hexColor?: string; // overrides `color`
98
+ description?: ReactNode;
99
+ isCanceled?: boolean;
100
+ isAllDay?: boolean;
101
+ isRecurring?: boolean;
102
+ resourceId?: string; // resource and timeline views
103
+
104
+ // Client-side filtering. Skip it if you filter server-side.
105
+ scheduleTypeId?: number;
106
+ participants?: ICalendarUser[];
107
+
108
+ data?: TData; // your domain fields live here
109
+ }
110
+ ```
111
+
112
+ Put your own fields on `data`. The calendar never reads it — it hands the whole
113
+ event back to your `eventCard`, `eventDetail` and `eventForm`, which are the
114
+ only things that know what a "property" or an "opportunity" is.
115
+
116
+ ```tsx
117
+ type MyData = { raw: AgendaRow };
118
+
119
+ <InnoCalendar<MyData>
120
+ events={rows.map((row) => ({ …, data: { raw: row } }))}
121
+ eventCard={(event) => <MyCard row={event.data.raw} />}
122
+ />
123
+ ```
124
+
125
+ A handful of domain-shaped fields (`meetingTookPlace`, `companyId`, `user`, …)
126
+ still exist at the top level and are marked `@deprecated`. They will go in 3.0.
127
+
128
+ ## Extension points
129
+
130
+ Each is a function returning JSX. Pass one to take that surface over; leave it
131
+ out and the calendar handles it.
132
+
133
+ ### `eventCard` — the block in the grid
134
+
135
+ ```tsx
136
+ eventCard={(event, { compact }) => (
137
+ <MyCard event={event} compact={compact} />
138
+ )}
139
+ ```
140
+
141
+ `compact` is true in the month view and other tight rows.
142
+
143
+ ### `eventDetail` — what a click opens
144
+
145
+ ```tsx
146
+ eventDetail={(event, { close, edit }) => (
147
+ <MyDetail event={event} onClose={close} onEdit={edit} />
148
+ )}
149
+ ```
150
+
151
+ `edit` is present only when an `eventForm` is there to open. The calendar
152
+ supplies the container: a popover beside the event, or a bottom sheet on a
153
+ narrow screen, capped to the room available and scrolling inside.
154
+
155
+ ### `eventForm` — create and edit
156
+
157
+ ```tsx
158
+ eventForm={({ selection, event }, { close, expand }) => (
159
+ <MyForm
160
+ start={selection.startDate}
161
+ end={selection.endDate}
162
+ event={event} // present when editing
163
+ onDone={close}
164
+ onExpand={expand} // ask for the wider card
165
+ />
166
+ )}
167
+ ```
168
+
169
+ The calendar owns the card — placement, dragging it by its handle, Escape,
170
+ click-outside, the height cap, the phone sheet. You own what is inside it.
171
+ Pair it with the exported `WhenRow` and `RecurrenceField` so your form's time
172
+ row behaves like the built-in one.
173
+
174
+ A drag on the grid opens it on that range. So does the Add button, on the slot
175
+ happening now. `ref.current.openForm()` does the same from anywhere.
176
+
177
+ ### `filters` — the rail
178
+
179
+ ```tsx
180
+ filters={({ close }) => <MyFilters onDone={close} />}
181
+ ```
182
+
183
+ The calendar supplies the rail on wide screens and a sheet on narrow ones;
184
+ `close` puts the sheet away after a filter that applies on tap.
185
+
186
+ ## Props
187
+
188
+ ```tsx
189
+ <InnoCalendar
190
+ // Data
191
+ events={events}
192
+ users={users} // resource rows
193
+ isLoading={isFetching}
194
+
195
+ // Extension points
196
+ eventCard={…}
197
+ eventDetail={…}
198
+ eventForm={…}
199
+ filters={…}
200
+
201
+ // Initial state
202
+ initialView="week"
203
+ initialDate={new Date()}
204
+ locale="fr"
205
+
206
+ // Callbacks
207
+ onEventClick={(event) => {}}
208
+ onSlotSelect={(selection) => {}}
209
+ onSlotClick={(date, hour) => {}}
210
+ onAddEvent={() => {}}
211
+ onEventDrop={(result) => {}}
212
+ onDateChange={(date, view) => {}}
213
+ onViewChange={(view) => {}}
214
+
215
+ // Chrome
216
+ showHeader
217
+ showSidebar
218
+ createLabel="Add"
219
+ labels={{ header: {…}, agenda: {…}, filters: "Filters" }}
220
+ headerActions={<MyActions />}
221
+ settingsContent={<MySettings />}
222
+ sidebarContent={<MyRail />}
223
+
224
+ // Behaviour
225
+ minSelectionMinutes={30}
226
+ preferencesConfig={{ defaults: {…} }}
227
+ resourceColumnConfig={{…}}
228
+ alwaysShowResources={resources}
229
+ />
230
+ ```
231
+
232
+ `onEventDrop` is worth a note: without it, a drag or resize **opens the form**
233
+ on the new times rather than writing them, because a reschedule that mails the
234
+ participants should be confirmed. Pass a handler to take that over and get the
235
+ optimistic save with undo instead.
236
+
237
+ ## The ref
238
+
239
+ ```tsx
240
+ const calendar = useRef<ICalendarRefHandle>(null);
241
+
242
+ calendar.current?.openForm(); // new, on the slot happening now
243
+ calendar.current?.openForm({ start, end }); // new, on a range
244
+ calendar.current?.openForm({ event }); // edit, on its own times
245
+ calendar.current?.scrollToToday();
246
+ calendar.current?.focusEvent(id);
247
+ ```
248
+
249
+ ## Working hours and preferences
250
+
251
+ ```tsx
252
+ preferencesConfig={{
253
+ defaults: {
254
+ slotDuration: 30,
255
+ visibleHours: { from: 7, to: 20 },
256
+ showWorkingHoursOnly: true,
257
+ workingHours: { 1: { from: 9, to: 17 }, /* 0 = Sunday */ },
258
+ badgeVariant: "colored",
259
+ },
260
+ }}
261
+ ```
262
+
263
+ Preferences persist per user in `localStorage`; `defaults` is what a user who
264
+ has never changed anything sees.
265
+
266
+ ## Styling
267
+
268
+ ```tsx
269
+ import "@innosolutions/inno-calendar/styles";
270
+ ```
271
+
272
+ Theme and dimensions are CSS custom properties:
273
+
274
+ ```css
275
+ :root {
276
+ --inno-border-color: #e5e7eb;
277
+ --inno-background: #ffffff;
278
+ --inno-foreground: #111827;
279
+ --inno-muted: #f3f4f6;
280
+ --inno-primary: #3b82f6;
281
+
282
+ --inno-hour-height: 96px;
283
+ --inno-header-height: 56px;
284
+ --inno-sidebar-width: 200px;
285
+
286
+ --inno-event-blue: #3b82f6; /* …and green, red, yellow, purple,
287
+ orange, pink, teal, gray, indigo */
288
+ }
289
+ ```
290
+
291
+ Dark mode reads the same names under `.dark`.
292
+
293
+ Anything more specific than those is CSS against the package's class names,
294
+ which is not yet a stable contract — see the note in the changelog.
295
+
296
+ ### Stacking
297
+
298
+ Anything you portal from inside a calendar surface has to sit above it, and
299
+ guessing a number that works today is how that breaks later:
300
+
301
+ ```tsx
302
+ import { IC_Z_LAYER } from "@innosolutions/inno-calendar";
303
+
304
+ <SelectContent style={{ zIndex: IC_Z_LAYER }}>
305
+ ```
306
+
307
+ `IC_Z_CARD` (1000) is the floating card, `IC_Z_LAYER` (1100) menus and pickers
308
+ opened from one, `IC_Z_MODAL` (1200) a dialog on top of that.
309
+
310
+ ## TypeScript
311
+
312
+ ```tsx
313
+ import type {
314
+ CalendarEvent,
315
+ ICalendarRefHandle,
316
+ ICalendarUser,
317
+ IEventFormActions,
318
+ IEventFormArgs,
319
+ IRecurrence,
320
+ ISelectionResult,
321
+ TCalendarView,
322
+ TEventColor,
323
+ TWorkingHours,
324
+ } from "@innosolutions/inno-calendar";
325
+ ```
326
+
327
+ Exported components worth knowing: `WhenRow`, `RecurrenceField`, `FieldLayer`
328
+ and `QuickCreate` for building a form body, and `formatClockRange` /
329
+ `reactNodeToText` for labels.
330
+
331
+ ## License
332
+
333
+ MIT © [InnoSolutions](https://github.com/innosolutions)