@savvycal/calendar 0.1.0

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 (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +472 -0
  3. package/dist/components/resource-grid-view/AllDayRow.d.ts +12 -0
  4. package/dist/components/resource-grid-view/AllDayRow.d.ts.map +1 -0
  5. package/dist/components/resource-grid-view/AllDayRow.js +54 -0
  6. package/dist/components/resource-grid-view/EventChip.d.ts +22 -0
  7. package/dist/components/resource-grid-view/EventChip.d.ts.map +1 -0
  8. package/dist/components/resource-grid-view/EventChip.js +112 -0
  9. package/dist/components/resource-grid-view/GridHeader.d.ts +13 -0
  10. package/dist/components/resource-grid-view/GridHeader.d.ts.map +1 -0
  11. package/dist/components/resource-grid-view/GridHeader.js +31 -0
  12. package/dist/components/resource-grid-view/NowIndicator.d.ts +13 -0
  13. package/dist/components/resource-grid-view/NowIndicator.d.ts.map +1 -0
  14. package/dist/components/resource-grid-view/NowIndicator.js +43 -0
  15. package/dist/components/resource-grid-view/ResourceColumn.d.ts +22 -0
  16. package/dist/components/resource-grid-view/ResourceColumn.d.ts.map +1 -0
  17. package/dist/components/resource-grid-view/ResourceColumn.js +52 -0
  18. package/dist/components/resource-grid-view/ResourceGridView.d.ts +3 -0
  19. package/dist/components/resource-grid-view/ResourceGridView.d.ts.map +1 -0
  20. package/dist/components/resource-grid-view/ResourceGridView.js +342 -0
  21. package/dist/components/resource-grid-view/SelectionOverlay.d.ts +22 -0
  22. package/dist/components/resource-grid-view/SelectionOverlay.d.ts.map +1 -0
  23. package/dist/components/resource-grid-view/SelectionOverlay.js +87 -0
  24. package/dist/components/resource-grid-view/SlotInteractionLayer.d.ts +28 -0
  25. package/dist/components/resource-grid-view/SlotInteractionLayer.d.ts.map +1 -0
  26. package/dist/components/resource-grid-view/SlotInteractionLayer.js +213 -0
  27. package/dist/components/resource-grid-view/TimeGutter.d.ts +11 -0
  28. package/dist/components/resource-grid-view/TimeGutter.d.ts.map +1 -0
  29. package/dist/components/resource-grid-view/TimeGutter.js +25 -0
  30. package/dist/components/resource-grid-view/UnavailabilityOverlay.d.ts +10 -0
  31. package/dist/components/resource-grid-view/UnavailabilityOverlay.d.ts.map +1 -0
  32. package/dist/components/resource-grid-view/UnavailabilityOverlay.js +37 -0
  33. package/dist/components/resource-grid-view/defaults.d.ts +3 -0
  34. package/dist/components/resource-grid-view/defaults.d.ts.map +1 -0
  35. package/dist/components/resource-grid-view/defaults.js +28 -0
  36. package/dist/components/resource-grid-view/index.d.ts +3 -0
  37. package/dist/components/resource-grid-view/index.d.ts.map +1 -0
  38. package/dist/components/resource-grid-view/useAnnouncer.d.ts +5 -0
  39. package/dist/components/resource-grid-view/useAnnouncer.d.ts.map +1 -0
  40. package/dist/components/resource-grid-view/useAnnouncer.js +12 -0
  41. package/dist/components/resource-grid-view/useEffectiveHourHeight.d.ts +15 -0
  42. package/dist/components/resource-grid-view/useEffectiveHourHeight.d.ts.map +1 -0
  43. package/dist/components/resource-grid-view/useEffectiveHourHeight.js +20 -0
  44. package/dist/components.css +31 -0
  45. package/dist/index.d.ts +6 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +10 -0
  48. package/dist/lib/availability.d.ts +12 -0
  49. package/dist/lib/availability.d.ts.map +1 -0
  50. package/dist/lib/availability.js +56 -0
  51. package/dist/lib/overlap.d.ts +16 -0
  52. package/dist/lib/overlap.d.ts.map +1 -0
  53. package/dist/lib/overlap.js +80 -0
  54. package/dist/lib/selection.d.ts +4 -0
  55. package/dist/lib/selection.d.ts.map +1 -0
  56. package/dist/lib/selection.js +14 -0
  57. package/dist/lib/time.d.ts +21 -0
  58. package/dist/lib/time.d.ts.map +1 -0
  59. package/dist/lib/time.js +52 -0
  60. package/dist/lib/utils.d.ts +3 -0
  61. package/dist/lib/utils.d.ts.map +1 -0
  62. package/dist/lib/utils.js +8 -0
  63. package/dist/preset.css +93 -0
  64. package/dist/types/calendar.d.ts +128 -0
  65. package/dist/types/calendar.d.ts.map +1 -0
  66. package/package.json +60 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 SavvyCal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,472 @@
1
+ # @savvycal/calendar
2
+
3
+ A fully-featured resource grid calendar component built with React, Tailwind CSS v4, and the Temporal API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @savvycal/calendar
9
+ ```
10
+
11
+ ### Peer dependencies
12
+
13
+ - `react` ^18 || ^19
14
+ - `react-dom` ^18 || ^19
15
+
16
+ The package ships [`temporal-polyfill`](https://www.npmjs.com/package/temporal-polyfill) as a direct dependency, so you don't need to install it separately.
17
+
18
+ ## CSS setup (Tailwind CSS v4)
19
+
20
+ Add three imports to your app's CSS **in this exact order**:
21
+
22
+ ```css
23
+ @import '@savvycal/calendar/preset.css';
24
+ @import 'tailwindcss';
25
+ @import '@savvycal/calendar/components.css';
26
+ ```
27
+
28
+ **Why order matters:** The preset registers `@theme` tokens and a `@source` directive _before_ Tailwind processes them, so Tailwind can generate the utility classes the library uses. The components stylesheet must come _after_ Tailwind so its custom CSS (e.g. the unavailable-time cross-hatch pattern) can reference Tailwind's generated values.
29
+
30
+ ## Quick start
31
+
32
+ ```tsx
33
+ import { ResourceGridView, Temporal } from '@savvycal/calendar';
34
+
35
+ const today = Temporal.Now.plainDateISO();
36
+
37
+ function App() {
38
+ return (
39
+ <ResourceGridView
40
+ date={today}
41
+ timeZone="America/Chicago"
42
+ resources={[
43
+ { id: '1', name: 'Alice', color: '#3b82f6' },
44
+ { id: '2', name: 'Bob', color: '#8b5cf6' },
45
+ ]}
46
+ events={[
47
+ {
48
+ id: 'evt-1',
49
+ title: 'Meeting',
50
+ resourceId: '1',
51
+ startTime: today
52
+ .toPlainDateTime({ hour: 10 })
53
+ .toZonedDateTime('America/Chicago'),
54
+ endTime: today
55
+ .toPlainDateTime({ hour: 11 })
56
+ .toZonedDateTime('America/Chicago'),
57
+ },
58
+ ]}
59
+ />
60
+ );
61
+ }
62
+ ```
63
+
64
+ ## ResourceGridView props
65
+
66
+ ### Required
67
+
68
+ | Prop | Type | Description |
69
+ | ----------- | -------------------- | ----------------------------------------------------- |
70
+ | `date` | `Temporal.PlainDate` | The date to display. |
71
+ | `timeZone` | `string` | IANA time zone identifier (e.g. `"America/Chicago"`). |
72
+ | `resources` | `CalendarResource[]` | Array of resources (columns). |
73
+ | `events` | `CalendarEvent[]` | Array of timed and/or all-day events. |
74
+
75
+ ### Time axis
76
+
77
+ | Prop | Type | Default | Description |
78
+ | ---------- | ---------------- | ---------------------------------------------------- | ---------------------------------------------------- |
79
+ | `timeAxis` | `TimeAxisConfig` | `{ startHour: 0, endHour: 24, intervalMinutes: 60 }` | Controls the visible hour range and gutter interval. |
80
+
81
+ ### Layout
82
+
83
+ | Prop | Type | Default | Description |
84
+ | ---------------- | ---------------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
85
+ | `hourHeight` | `number` | `60` | Height in pixels for one hour. |
86
+ | `columnMinWidth` | `number` | `120` | Minimum width in pixels for each resource column. |
87
+ | `eventLayout` | `'columns' \| 'stacked'` | `'columns'` | How overlapping events are laid out. `'columns'` places them side-by-side; `'stacked'` offsets them. |
88
+ | `eventGap` | `number` | — | Vertical gap in pixels between the edge of events and column borders. |
89
+ | `stackOffset` | `number` | `8` | Horizontal pixel offset for each stacked event (only applies when `eventLayout` is `'stacked'`). |
90
+ | `className` | `string` | — | Class name applied to the root element. |
91
+ | `classNames` | `ResourceGridViewClassNames` | See [Customizing styles](#customizing-styles). | Override default class names for internal elements. |
92
+
93
+ ### Events & interaction
94
+
95
+ | Prop | Type | Default | Description |
96
+ | --------------------- | -------------------------------------------------- | ------- | -------------------------------------------------------------------- |
97
+ | `onEventClick` | `(event: CalendarEvent) => void` | — | Called when an event is clicked. |
98
+ | `onSlotClick` | `(info: { resource, startTime, endTime }) => void` | — | Called when an empty time slot is clicked. |
99
+ | `snapDuration` | `number` | — | Snap interval in minutes for drag selection. |
100
+ | `placeholderDuration` | `number` | `15` | Duration in minutes for the hover placeholder shown before dragging. |
101
+
102
+ ### Selection
103
+
104
+ | Prop | Type | Default | Description |
105
+ | ----------------------- | ---------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
106
+ | `selectedRange` | `SelectedRange \| null` | — | The currently selected time range (controlled). |
107
+ | `onSelect` | `(range: SelectedRange \| null) => void` | — | Called when the user selects or clears a range by dragging. |
108
+ | `selectionAppearance` | `SelectionAppearance` | — | How the selection is rendered. `'highlight'` shows a translucent overlay; `{ style: 'event', eventData? }` renders it as a phantom event. |
109
+ | `dragPreviewAppearance` | `SelectionAppearance` | — | Appearance of the drag preview while the user is actively dragging (before releasing). Falls back to `selectionAppearance` if not set. |
110
+ | `selectionRef` | `Ref<HTMLDivElement>` | — | Ref attached to the selection element, useful for positioning popovers (e.g. with [Floating UI](https://floating-ui.com)). |
111
+ | `selectedEventId` | `string \| null` | — | ID of the currently selected event (controlled). Applies selected styling and enables `selectedEventRef`. |
112
+ | `selectedEventRef` | `Ref<HTMLDivElement>` | — | Ref attached to the selected event element, useful for positioning popovers. |
113
+
114
+ ### Availability
115
+
116
+ | Prop | Type | Default | Description |
117
+ | ---------------- | ------------------------------------- | ------- | ------------------------------------------------------------------------------------------------- |
118
+ | `availability` | `Record<string, AvailabilityRange[]>` | — | Map of resource ID to available time ranges. Times outside these ranges are shown as unavailable. |
119
+ | `unavailability` | `Record<string, AvailabilityRange[]>` | — | Map of resource ID to explicitly unavailable time ranges. Applied on top of availability. |
120
+
121
+ ### Render props
122
+
123
+ | Prop | Type | Description |
124
+ | -------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
125
+ | `renderHeader` | `(props: { resource: CalendarResource }) => ReactNode` | Custom renderer for resource column headers. |
126
+ | `renderEvent` | `(props: { event: TimedCalendarEvent, position: PositionedEvent }) => ReactNode` | Custom renderer for timed events. |
127
+ | `renderCorner` | `() => ReactNode` | Custom renderer for the top-left corner cell (e.g. time zone label). |
128
+
129
+ ## Data types
130
+
131
+ ### CalendarResource
132
+
133
+ ```ts
134
+ interface CalendarResource {
135
+ id: string;
136
+ name: string;
137
+ avatarUrl?: string;
138
+ color?: string;
139
+ }
140
+ ```
141
+
142
+ ### CalendarEvent
143
+
144
+ A discriminated union of `TimedCalendarEvent` and `AllDayCalendarEvent`:
145
+
146
+ ```ts
147
+ interface TimedCalendarEvent {
148
+ id: string;
149
+ title: string;
150
+ resourceId: string;
151
+ startTime: Temporal.ZonedDateTime;
152
+ endTime: Temporal.ZonedDateTime;
153
+ allDay?: false;
154
+ color?: string;
155
+ clientName?: string;
156
+ status?: 'confirmed' | 'canceled' | 'tentative';
157
+ metadata?: Record<string, unknown>;
158
+ }
159
+
160
+ interface AllDayCalendarEvent {
161
+ id: string;
162
+ title: string;
163
+ resourceId: string;
164
+ startDate: Temporal.PlainDate;
165
+ endDate: Temporal.PlainDate;
166
+ allDay: true;
167
+ color?: string;
168
+ clientName?: string;
169
+ status?: 'confirmed' | 'canceled' | 'tentative';
170
+ metadata?: Record<string, unknown>;
171
+ }
172
+ ```
173
+
174
+ ### AvailabilityRange
175
+
176
+ ```ts
177
+ interface AvailabilityRange {
178
+ startTime: Temporal.ZonedDateTime;
179
+ endTime: Temporal.ZonedDateTime;
180
+ }
181
+ ```
182
+
183
+ ### SelectedRange
184
+
185
+ ```ts
186
+ interface SelectedRange {
187
+ resourceId: string;
188
+ startTime: Temporal.ZonedDateTime;
189
+ endTime: Temporal.ZonedDateTime;
190
+ }
191
+ ```
192
+
193
+ ### TimeAxisConfig
194
+
195
+ ```ts
196
+ interface TimeAxisConfig {
197
+ startHour?: number; // default: 0
198
+ endHour?: number; // default: 24
199
+ intervalMinutes?: number; // default: 60
200
+ }
201
+ ```
202
+
203
+ ### PositionedEvent
204
+
205
+ Provided to the `renderEvent` render prop with layout information:
206
+
207
+ ```ts
208
+ interface PositionedEvent {
209
+ event: TimedCalendarEvent;
210
+ top: number;
211
+ height: number;
212
+ subColumn: number;
213
+ totalSubColumns: number;
214
+ }
215
+ ```
216
+
217
+ ## Customizing styles
218
+
219
+ ### CSS custom properties
220
+
221
+ The preset defines a set of `--color-cal-*` CSS custom properties under `@theme`. Override them in your own CSS to change the calendar's color palette:
222
+
223
+ | Variable | Light default | Description |
224
+ | --------------------------------- | ---------------- | --------------------------------- |
225
+ | `--color-cal-surface` | `white` | Background color of the calendar. |
226
+ | `--color-cal-border` | `zinc-300` | Border color for grid lines. |
227
+ | `--color-cal-text` | `zinc-950` | Primary text color (headers). |
228
+ | `--color-cal-text-body` | `zinc-900` | Body text color (event titles). |
229
+ | `--color-cal-text-muted` | `zinc-600` | Muted text color (times, labels). |
230
+ | `--color-cal-event-bg` | `zinc-100 @ 90%` | Event background. |
231
+ | `--color-cal-event-ring` | `zinc-900 @ 15%` | Event border ring. |
232
+ | `--color-cal-event-ring-selected` | `zinc-900 @ 30%` | Ring color for selected events. |
233
+ | `--color-cal-event-shadow` | `black @ 10%` | Shadow color for selected events. |
234
+ | `--color-cal-now` | `orange-500` | Now indicator line color. |
235
+ | `--color-cal-slot-highlight` | `blue-400 @ 15%` | Hover slot highlight. |
236
+ | `--color-cal-selection` | `blue-400 @ 25%` | Drag selection highlight. |
237
+
238
+ Example override:
239
+
240
+ ```css
241
+ @import '@savvycal/calendar/preset.css';
242
+ @import 'tailwindcss';
243
+ @import '@savvycal/calendar/components.css';
244
+
245
+ @theme {
246
+ --color-cal-now: var(--color-red-500);
247
+ --color-cal-selection: color-mix(
248
+ in oklab,
249
+ var(--color-indigo-400) 25%,
250
+ transparent
251
+ );
252
+ }
253
+ ```
254
+
255
+ ### Dark mode
256
+
257
+ The preset uses a `.dark` class convention (matching Tailwind's `@custom-variant dark`). Add the `dark` class to a parent element to switch to dark mode. All `--color-cal-*` variables are automatically overridden in the `.dark` scope.
258
+
259
+ ### The `classNames` prop
260
+
261
+ Every internal element can be restyled via the `classNames` prop. Each key maps to a specific part of the calendar:
262
+
263
+ | Key | Description |
264
+ | -------------------- | ------------------------------------------------ |
265
+ | `root` | Outermost wrapper (scroll container). |
266
+ | `grid` | The CSS grid element. |
267
+ | `cornerCell` | Top-left corner cell (sticky). |
268
+ | `headerCell` | Resource column header cell (sticky). |
269
+ | `headerName` | Resource name text inside the header. |
270
+ | `headerAvatar` | Avatar image inside the header. |
271
+ | `gutterCell` | Time gutter cell for hour labels (sticky). |
272
+ | `gutterCellMinor` | Time gutter cell for sub-hour intervals. |
273
+ | `gutterLabel` | Text label inside gutter cells. |
274
+ | `bodyCell` | Hour-start body cell in the grid. |
275
+ | `bodyCellMinor` | Sub-hour body cell in the grid. |
276
+ | `eventColumn` | Container for events within a resource column. |
277
+ | `event` | Individual event element. |
278
+ | `eventSelected` | Additional classes applied to a selected event. |
279
+ | `eventColorBar` | Vertical color bar on the left edge of an event. |
280
+ | `eventTitle` | Event title text. |
281
+ | `eventTime` | Event time text. |
282
+ | `eventClientName` | Client name text on the event. |
283
+ | `nowIndicator` | Horizontal "now" indicator line. |
284
+ | `slotHighlight` | Hover highlight on time slots. |
285
+ | `selectionHighlight` | Drag selection highlight overlay. |
286
+ | `allDayCell` | All-day event row cell. |
287
+ | `unavailableOverlay` | Unavailable time cross-hatch overlay. |
288
+
289
+ ### `resourceGridViewDefaults`
290
+
291
+ The library exports `resourceGridViewDefaults`, an object containing the default Tailwind classes for every `classNames` key. Use it to extend rather than replace defaults:
292
+
293
+ ```tsx
294
+ import {
295
+ ResourceGridView,
296
+ resourceGridViewDefaults,
297
+ cn,
298
+ } from '@savvycal/calendar';
299
+
300
+ <ResourceGridView
301
+ classNames={{
302
+ event: cn(resourceGridViewDefaults.event, 'rounded-lg'),
303
+ headerCell: cn(resourceGridViewDefaults.headerCell, 'border-b'),
304
+ }}
305
+ // ...other props
306
+ />;
307
+ ```
308
+
309
+ The `cn()` utility (re-exported from the library) merges Tailwind classes with conflict resolution via `tailwind-merge`.
310
+
311
+ ## Selection & interaction
312
+
313
+ ### Controlled selection
314
+
315
+ Selection is controlled via `selectedRange` and `onSelect`:
316
+
317
+ ```tsx
318
+ const [selectedRange, setSelectedRange] = useState<SelectedRange | null>(null);
319
+
320
+ <ResourceGridView
321
+ selectedRange={selectedRange}
322
+ onSelect={setSelectedRange}
323
+ // ...
324
+ />;
325
+ ```
326
+
327
+ ### Selection appearance
328
+
329
+ - `'highlight'` — renders a translucent overlay on the selected time range.
330
+ - `{ style: 'event', eventData? }` — renders the selection as a phantom event. Pass `eventData` to customize its title, color, etc.
331
+
332
+ ```tsx
333
+ <ResourceGridView
334
+ selectionAppearance={{
335
+ style: 'event',
336
+ eventData: {
337
+ title: 'New appointment',
338
+ color: '#3b82f6',
339
+ },
340
+ }}
341
+ // ...
342
+ />
343
+ ```
344
+
345
+ ### Popover positioning with `selectionRef`
346
+
347
+ Attach `selectionRef` to use [Floating UI](https://floating-ui.com) (or a similar library) to position a popover next to the selection:
348
+
349
+ ```tsx
350
+ import {
351
+ useFloating,
352
+ autoUpdate,
353
+ flip,
354
+ shift,
355
+ offset,
356
+ } from '@floating-ui/react';
357
+
358
+ const { refs, floatingStyles } = useFloating({
359
+ open: selectedRange !== null,
360
+ middleware: [flip(), shift(), offset(5)],
361
+ placement: 'right',
362
+ whileElementsMounted: autoUpdate,
363
+ });
364
+
365
+ <ResourceGridView
366
+ selectionRef={refs.setReference}
367
+ selectedRange={selectedRange}
368
+ onSelect={setSelectedRange}
369
+ // ...
370
+ />;
371
+
372
+ {
373
+ selectedRange && (
374
+ <div ref={refs.setFloating} style={floatingStyles}>
375
+ {/* Popover content */}
376
+ </div>
377
+ );
378
+ }
379
+ ```
380
+
381
+ ### Event selection & popover positioning
382
+
383
+ Use `selectedEventId` and `selectedEventRef` to track which event is selected and position a popover next to it:
384
+
385
+ ```tsx
386
+ const [selectedEventId, setSelectedEventId] = useState<string | null>(null);
387
+
388
+ const { refs, floatingStyles } = useFloating({
389
+ open: selectedEventId !== null,
390
+ onOpenChange: (open) => {
391
+ if (!open) setSelectedEventId(null);
392
+ },
393
+ middleware: [flip(), shift(), offset(5)],
394
+ placement: 'right',
395
+ whileElementsMounted: autoUpdate,
396
+ });
397
+
398
+ <ResourceGridView
399
+ selectedEventId={selectedEventId}
400
+ selectedEventRef={refs.setReference}
401
+ onEventClick={(event) => setSelectedEventId(event.id)}
402
+ // ...
403
+ />;
404
+
405
+ {
406
+ selectedEventId && (
407
+ <div ref={refs.setFloating} style={floatingStyles}>
408
+ {/* Event popover content */}
409
+ </div>
410
+ );
411
+ }
412
+ ```
413
+
414
+ ### Slot and event clicks
415
+
416
+ - `onSlotClick` fires when clicking an empty time slot, receiving the `resource`, `startTime`, and `endTime`.
417
+ - `onEventClick` fires when clicking an event.
418
+ - `snapDuration` (in minutes) controls the snap interval for drag-to-select.
419
+ - `placeholderDuration` (in minutes, default `15`) controls the height of the hover placeholder shown before the user starts dragging.
420
+
421
+ ## Exports
422
+
423
+ ```ts
424
+ // Components
425
+ export { ResourceGridView } from '@savvycal/calendar';
426
+ // Defaults
427
+ export { resourceGridViewDefaults } from '@savvycal/calendar';
428
+
429
+ // Temporal polyfill
430
+ export { Temporal } from '@savvycal/calendar';
431
+
432
+ // Utility
433
+ export { cn } from '@savvycal/calendar';
434
+
435
+ // Types
436
+ export type {
437
+ CalendarResource,
438
+ CalendarEvent,
439
+ TimedCalendarEvent,
440
+ AllDayCalendarEvent,
441
+ TimeSlot,
442
+ AvailabilityRange,
443
+ TimeAxisConfig,
444
+ ResourceGridViewProps,
445
+ ResourceGridViewClassNames,
446
+ PositionedEvent,
447
+ SelectedRange,
448
+ SelectionAppearance,
449
+ SelectionEventData,
450
+ EventLayout,
451
+ } from '@savvycal/calendar';
452
+ ```
453
+
454
+ ## Accessibility
455
+
456
+ The calendar includes built-in ARIA support for screen readers:
457
+
458
+ - **Grid region** — The grid container has `role="region"` with `aria-roledescription="calendar"` and a descriptive `aria-label` (e.g. "Schedule for Monday, February 9, 2026").
459
+ - **Column headers** — Each resource header has `role="columnheader"`.
460
+ - **Event labels** — Interactive events include an `aria-label` with the event title, time range, client name, and status (if canceled or tentative). All-day events are labeled with the title and "all day".
461
+ - **Live region announcements** — A visually-hidden `aria-live="polite"` region announces state changes:
462
+ - Selecting an event: `"Selected: Meeting, 2 pm to 3 pm, Jane Doe"`
463
+ - Selecting a time range: `"Selected time: 2 pm to 3 pm, Dr. Smith"`
464
+ - **Decorative overlays** — The now indicator, unavailability overlays, selection overlay, and slot highlights are marked `aria-hidden="true"` to reduce noise.
465
+
466
+ ### Custom renderers
467
+
468
+ When using `renderEvent`, the library does **not** add `aria-label` or `role` attributes to the wrapper `<div>`. Your custom renderer is responsible for providing its own accessible markup.
469
+
470
+ ## License
471
+
472
+ MIT
@@ -0,0 +1,12 @@
1
+ import { Ref } from 'react';
2
+ import { AllDayCalendarEvent, CalendarEvent, ResourceGridViewClassNames } from '../../types/calendar';
3
+ interface AllDayRowProps {
4
+ events: AllDayCalendarEvent[];
5
+ cls: (key: keyof ResourceGridViewClassNames) => string;
6
+ onEventClick?: (event: CalendarEvent) => void;
7
+ selectedEventId?: string | null;
8
+ selectedEventRef?: Ref<HTMLDivElement>;
9
+ }
10
+ export declare const AllDayRow: import('react').NamedExoticComponent<AllDayRowProps>;
11
+ export {};
12
+ //# sourceMappingURL=AllDayRow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AllDayRow.d.ts","sourceRoot":"","sources":["../../../src/components/resource-grid-view/AllDayRow.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,GAAG,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EACb,0BAA0B,EAC3B,MAAM,kBAAkB,CAAC;AAE1B,UAAU,cAAc;IACtB,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC9B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,0BAA0B,KAAK,MAAM,CAAC;IACvD,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC9C,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;CACxC;AAED,eAAO,MAAM,SAAS,sDAwDpB,CAAC"}
@@ -0,0 +1,54 @@
1
+ import { jsx as l, Fragment as m, jsxs as d } from "react/jsx-runtime";
2
+ import { memo as u } from "react";
3
+ import { cn as f } from "../../lib/utils.js";
4
+ const x = u(function({
5
+ events: o,
6
+ cls: i,
7
+ onEventClick: a,
8
+ selectedEventId: c,
9
+ selectedEventRef: s
10
+ }) {
11
+ return o.length === 0 ? null : /* @__PURE__ */ l(m, { children: o.map((t) => {
12
+ const r = t.id === c, e = [t.title, "all day"];
13
+ t.clientName && e.push(t.clientName);
14
+ const n = /* @__PURE__ */ d(
15
+ "button",
16
+ {
17
+ type: "button",
18
+ "aria-label": e.join(", "),
19
+ className: f(i("event"), r && i("eventSelected")),
20
+ style: {
21
+ position: "relative",
22
+ inset: "unset",
23
+ cursor: "pointer",
24
+ flex: "1 1 0",
25
+ minWidth: 0
26
+ },
27
+ onClick: () => a?.(t),
28
+ children: [
29
+ t.color && /* @__PURE__ */ l(
30
+ "div",
31
+ {
32
+ className: i("eventColorBar"),
33
+ style: { backgroundColor: t.color }
34
+ }
35
+ ),
36
+ /* @__PURE__ */ l("div", { className: i("eventTitle"), children: t.title })
37
+ ]
38
+ },
39
+ t.id
40
+ );
41
+ return r ? /* @__PURE__ */ l(
42
+ "div",
43
+ {
44
+ ref: s,
45
+ style: { flex: "1 1 0", minWidth: 0 },
46
+ children: n
47
+ },
48
+ t.id
49
+ ) : n;
50
+ }) });
51
+ });
52
+ export {
53
+ x as AllDayRow
54
+ };
@@ -0,0 +1,22 @@
1
+ import { Ref } from 'react';
2
+ import { CalendarEvent, CalendarResource, EventLayout, PositionedEvent, ResourceGridViewClassNames, TimedCalendarEvent } from '../../types/calendar';
3
+ interface EventChipProps {
4
+ positioned: PositionedEvent;
5
+ resource: CalendarResource;
6
+ timeZone: string;
7
+ cls: (key: keyof ResourceGridViewClassNames) => string;
8
+ onClick?: (event: CalendarEvent) => void;
9
+ renderEvent?: (props: {
10
+ event: TimedCalendarEvent;
11
+ position: PositionedEvent;
12
+ }) => React.ReactNode;
13
+ interactive?: boolean;
14
+ eventGap?: number;
15
+ eventLayout?: EventLayout;
16
+ stackOffset?: number;
17
+ isSelected?: boolean;
18
+ selectedEventRef?: Ref<HTMLDivElement>;
19
+ }
20
+ export declare const EventChip: import('react').NamedExoticComponent<EventChipProps>;
21
+ export {};
22
+ //# sourceMappingURL=EventChip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventChip.d.ts","sourceRoot":"","sources":["../../../src/components/resource-grid-view/EventChip.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,GAAG,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,0BAA0B,EAC1B,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;AAG1B,UAAU,cAAc;IACtB,UAAU,EAAE,eAAe,CAAC;IAC5B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,0BAA0B,KAAK,MAAM,CAAC;IACvD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACzC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE;QACpB,KAAK,EAAE,kBAAkB,CAAC;QAC1B,QAAQ,EAAE,eAAe,CAAC;KAC3B,KAAK,KAAK,CAAC,SAAS,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gBAAgB,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;CACxC;AAED,eAAO,MAAM,SAAS,sDAsJpB,CAAC"}