@multicomma/arkcn 0.1.2 → 0.1.3

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 (148) hide show
  1. package/package.json +1 -1
  2. package/registry/index.json +1 -1
  3. package/registry/items/accordion.json +302 -0
  4. package/registry/items/activity-feed.json +408 -0
  5. package/registry/items/alert-dialog.json +676 -0
  6. package/registry/items/alert.json +113 -0
  7. package/registry/items/angle-slider.json +378 -0
  8. package/registry/items/app-shell.json +2654 -1
  9. package/registry/items/aspect-ratio.json +36 -0
  10. package/registry/items/attachment.json +299 -1
  11. package/registry/items/avatar.json +213 -0
  12. package/registry/items/badge.json +44 -0
  13. package/registry/items/breadcrumb.json +191 -0
  14. package/registry/items/bubble.json +146 -0
  15. package/registry/items/button-group.json +110 -0
  16. package/registry/items/button.json +52 -0
  17. package/registry/items/calendar.json +499 -1
  18. package/registry/items/canvas.json +446 -1
  19. package/registry/items/card.json +192 -0
  20. package/registry/items/carousel.json +208 -1
  21. package/registry/items/cascader.json +797 -1
  22. package/registry/items/chart.json +1764 -1
  23. package/registry/items/chat.json +993 -0
  24. package/registry/items/checkbox.json +503 -0
  25. package/registry/items/clipboard.json +308 -0
  26. package/registry/items/collapsible.json +226 -0
  27. package/registry/items/color-picker.json +1261 -0
  28. package/registry/items/combobox.json +1123 -1
  29. package/registry/items/command.json +837 -1
  30. package/registry/items/comment-thread.json +952 -1
  31. package/registry/items/context-menu.json +1344 -1
  32. package/registry/items/data-grid.json +449 -1
  33. package/registry/items/data-table.json +1310 -1
  34. package/registry/items/date-input.json +476 -0
  35. package/registry/items/dialog.json +592 -0
  36. package/registry/items/direction.json +29 -1
  37. package/registry/items/drawer.json +797 -0
  38. package/registry/items/dropdown-menu.json +1311 -1
  39. package/registry/items/editable.json +503 -0
  40. package/registry/items/empty.json +166 -0
  41. package/registry/items/field.json +286 -1
  42. package/registry/items/file-upload.json +638 -0
  43. package/registry/items/floating-panel.json +720 -0
  44. package/registry/items/floating-toolbar.json +43 -0
  45. package/registry/items/format.json +109 -0
  46. package/registry/items/gantt.json +773 -1
  47. package/registry/items/hooks/use-mobile.json +1 -0
  48. package/registry/items/hotkeys.json +99 -1
  49. package/registry/items/hover-card.json +357 -0
  50. package/registry/items/image-cropper.json +431 -0
  51. package/registry/items/input-group.json +189 -0
  52. package/registry/items/input-otp.json +181 -0
  53. package/registry/items/input.json +28 -0
  54. package/registry/items/item.json +308 -0
  55. package/registry/items/json-tree-view.json +336 -0
  56. package/registry/items/kanban.json +624 -1
  57. package/registry/items/kbd.json +54 -0
  58. package/registry/items/label.json +28 -0
  59. package/registry/items/lib/controllable.json +1 -0
  60. package/registry/items/lib/data-table-adapter.json +1 -0
  61. package/registry/items/lib/drag-preview.json +1 -0
  62. package/registry/items/lib/form.json +1 -0
  63. package/registry/items/lib/history.json +1 -0
  64. package/registry/items/lib/row-layout.json +1 -0
  65. package/registry/items/lib/table.json +1 -0
  66. package/registry/items/lib/time.json +1 -0
  67. package/registry/items/lib/utils.json +1 -0
  68. package/registry/items/live-region.json +43 -0
  69. package/registry/items/marker.json +96 -0
  70. package/registry/items/marquee.json +300 -0
  71. package/registry/items/menubar.json +1282 -1
  72. package/registry/items/message.json +166 -0
  73. package/registry/items/navigation-menu.json +561 -1
  74. package/registry/items/node-graph.json +1037 -1
  75. package/registry/items/number-input.json +519 -0
  76. package/registry/items/pagination.json +244 -1
  77. package/registry/items/password-input.json +311 -0
  78. package/registry/items/popconfirm.json +566 -0
  79. package/registry/items/popover.json +615 -0
  80. package/registry/items/progress.json +516 -0
  81. package/registry/items/qr-code.json +249 -0
  82. package/registry/items/query-builder.json +1368 -1
  83. package/registry/items/radio-group.json +362 -0
  84. package/registry/items/rating-group.json +304 -0
  85. package/registry/items/resizable.json +242 -1
  86. package/registry/items/rich-text-editor.json +934 -1
  87. package/registry/items/scheduler.json +1013 -1
  88. package/registry/items/scroll-area.json +227 -0
  89. package/registry/items/segment-group.json +363 -0
  90. package/registry/items/select.json +956 -0
  91. package/registry/items/separator.json +45 -1
  92. package/registry/items/sheet.json +592 -0
  93. package/registry/items/sidebar.json +785 -1
  94. package/registry/items/signature-pad.json +330 -0
  95. package/registry/items/skeleton.json +28 -0
  96. package/registry/items/slider.json +799 -0
  97. package/registry/items/sonner.json +135 -0
  98. package/registry/items/spinner.json +28 -0
  99. package/registry/items/steps.json +483 -0
  100. package/registry/items/swap.json +107 -0
  101. package/registry/items/switch.json +362 -0
  102. package/registry/items/table.json +210 -0
  103. package/registry/items/tabs.json +326 -0
  104. package/registry/items/tags-input.json +640 -0
  105. package/registry/items/textarea.json +28 -0
  106. package/registry/items/timer.json +289 -0
  107. package/registry/items/toc.json +372 -0
  108. package/registry/items/toggle-group.json +214 -1
  109. package/registry/items/toggle.json +124 -0
  110. package/registry/items/tooltip.json +417 -0
  111. package/registry/items/tour.json +498 -0
  112. package/registry/items/transfer-list.json +609 -1
  113. package/registry/items/tree-select.json +742 -1
  114. package/registry/items/tree-view.json +794 -0
  115. package/registry/items/virtual-list.json +214 -1
  116. package/src/components/ui/app-shell.tsx +11 -0
  117. package/src/components/ui/attachment.tsx +1 -0
  118. package/src/components/ui/calendar.tsx +1 -0
  119. package/src/components/ui/canvas.tsx +8 -0
  120. package/src/components/ui/carousel.tsx +3 -0
  121. package/src/components/ui/cascader.tsx +1 -0
  122. package/src/components/ui/chart.tsx +10 -0
  123. package/src/components/ui/combobox.tsx +3 -0
  124. package/src/components/ui/command.tsx +1 -0
  125. package/src/components/ui/comment-thread.tsx +11 -0
  126. package/src/components/ui/context-menu.tsx +6 -0
  127. package/src/components/ui/data-grid.tsx +8 -0
  128. package/src/components/ui/data-table.tsx +6 -0
  129. package/src/components/ui/direction.tsx +2 -0
  130. package/src/components/ui/dropdown-menu.tsx +6 -0
  131. package/src/components/ui/field.tsx +1 -0
  132. package/src/components/ui/gantt.tsx +15 -0
  133. package/src/components/ui/hotkeys.tsx +3 -0
  134. package/src/components/ui/kanban.tsx +3 -0
  135. package/src/components/ui/menubar.tsx +6 -0
  136. package/src/components/ui/navigation-menu.tsx +1 -0
  137. package/src/components/ui/node-graph.tsx +23 -0
  138. package/src/components/ui/pagination.tsx +1 -0
  139. package/src/components/ui/query-builder.tsx +7 -0
  140. package/src/components/ui/resizable.tsx +1 -0
  141. package/src/components/ui/rich-text-editor.tsx +54 -0
  142. package/src/components/ui/scheduler.tsx +26 -0
  143. package/src/components/ui/separator.tsx +1 -0
  144. package/src/components/ui/sidebar.tsx +5 -0
  145. package/src/components/ui/toggle-group.tsx +3 -0
  146. package/src/components/ui/transfer-list.tsx +11 -0
  147. package/src/components/ui/tree-select.tsx +5 -0
  148. package/src/components/ui/virtual-list.tsx +7 -0
@@ -34,6 +34,1018 @@
34
34
  "layoutLanes",
35
35
  "visibleRange"
36
36
  ],
37
+ "parts": [
38
+ {
39
+ "name": "Scheduler",
40
+ "props": [
41
+ {
42
+ "name": "date",
43
+ "type": "Date",
44
+ "required": false,
45
+ "description": "The date this part represents.",
46
+ "from": "arkcn"
47
+ },
48
+ {
49
+ "name": "defaultDate",
50
+ "type": "Date",
51
+ "required": false,
52
+ "description": "Initial anchor date when uncontrolled.",
53
+ "from": "arkcn"
54
+ },
55
+ {
56
+ "name": "defaultView",
57
+ "type": "SchedulerView",
58
+ "required": false,
59
+ "default": "\"week\"",
60
+ "description": "Initial view when uncontrolled.",
61
+ "from": "arkcn"
62
+ },
63
+ {
64
+ "name": "editable",
65
+ "type": "boolean",
66
+ "required": false,
67
+ "default": "true",
68
+ "description": "Allow editing.",
69
+ "from": "arkcn"
70
+ },
71
+ {
72
+ "name": "hourHeight",
73
+ "type": "number",
74
+ "required": false,
75
+ "default": "56",
76
+ "description": "Pixels per hour in the time grid.",
77
+ "from": "arkcn"
78
+ },
79
+ {
80
+ "name": "maxHour",
81
+ "type": "number",
82
+ "required": false,
83
+ "default": "24",
84
+ "description": "Last hour shown in the time grid.",
85
+ "from": "arkcn"
86
+ },
87
+ {
88
+ "name": "minHour",
89
+ "type": "number",
90
+ "required": false,
91
+ "default": "0",
92
+ "description": "First hour shown in the time grid.",
93
+ "from": "arkcn"
94
+ },
95
+ {
96
+ "name": "onCreate",
97
+ "type": "((range: { start: Date; end: Date; allDay?: boolean | undefined; }) => void)",
98
+ "required": false,
99
+ "description": "Drag on empty time (or a month cell click) proposes a new event.",
100
+ "from": "arkcn"
101
+ },
102
+ {
103
+ "name": "onDateChange",
104
+ "type": "((date: Date) => void)",
105
+ "required": false,
106
+ "description": "Called when the anchor date changes.",
107
+ "from": "arkcn"
108
+ },
109
+ {
110
+ "name": "onEventChange",
111
+ "type": "((change: SchedulerEventChange) => void)",
112
+ "required": false,
113
+ "description": "Called with `{ id, start, end }` snapped to `slotMinutes` after a move, resize, or keyboard change.",
114
+ "from": "arkcn"
115
+ },
116
+ {
117
+ "name": "onViewChange",
118
+ "type": "((view: SchedulerView) => void)",
119
+ "required": false,
120
+ "description": "Called when the view changes.",
121
+ "from": "arkcn"
122
+ },
123
+ {
124
+ "name": "slotMinutes",
125
+ "type": "number",
126
+ "required": false,
127
+ "default": "15",
128
+ "description": "Snap and keyboard step in minutes.",
129
+ "from": "arkcn"
130
+ },
131
+ {
132
+ "name": "view",
133
+ "type": "SchedulerView",
134
+ "required": false,
135
+ "description": "Controlled view: `day`, `week`, or `month`.",
136
+ "from": "arkcn"
137
+ },
138
+ {
139
+ "name": "weekStartsOn",
140
+ "type": "0 | 1 | 2 | 3 | 4 | 5 | 6",
141
+ "required": false,
142
+ "default": "1",
143
+ "description": "First day of the week, 0 for Sunday.",
144
+ "from": "arkcn"
145
+ },
146
+ {
147
+ "name": "children",
148
+ "type": "ReactNode",
149
+ "required": false,
150
+ "description": "Content rendered inside the part.",
151
+ "from": "@types/react"
152
+ },
153
+ {
154
+ "name": "className",
155
+ "type": "string",
156
+ "required": false,
157
+ "description": "Extra classes merged into the part's own classes with `cn`.",
158
+ "from": "@types/react"
159
+ },
160
+ {
161
+ "name": "style",
162
+ "type": "CSSProperties",
163
+ "required": false,
164
+ "description": "Inline styles applied to the rendered element.",
165
+ "from": "@types/react"
166
+ }
167
+ ]
168
+ },
169
+ {
170
+ "name": "SchedulerToolbar",
171
+ "props": [
172
+ {
173
+ "name": "children",
174
+ "type": "ReactNode",
175
+ "required": false,
176
+ "description": "Content rendered inside the part.",
177
+ "from": "@types/react"
178
+ },
179
+ {
180
+ "name": "className",
181
+ "type": "string",
182
+ "required": false,
183
+ "description": "Extra classes merged into the part's own classes with `cn`.",
184
+ "from": "@types/react"
185
+ },
186
+ {
187
+ "name": "style",
188
+ "type": "CSSProperties",
189
+ "required": false,
190
+ "description": "Inline styles applied to the rendered element.",
191
+ "from": "@types/react"
192
+ }
193
+ ]
194
+ },
195
+ {
196
+ "name": "SchedulerPrevTrigger",
197
+ "props": [
198
+ {
199
+ "name": "asChild",
200
+ "type": "boolean",
201
+ "required": false,
202
+ "description": "Render the child element instead of the default one, merging props and behaviour onto it.",
203
+ "from": "arkcn"
204
+ },
205
+ {
206
+ "name": "size",
207
+ "type": "\"default\" | \"xs\" | \"sm\" | \"lg\" | \"icon\" | \"icon-xs\" | \"icon-sm\" | \"icon-lg\" | null",
208
+ "required": false,
209
+ "description": "Size variant; sets a `data-size` attribute.",
210
+ "from": "arkcn"
211
+ },
212
+ {
213
+ "name": "variant",
214
+ "type": "\"link\" | \"default\" | \"outline\" | \"secondary\" | \"ghost\" | \"destructive\" | null",
215
+ "required": false,
216
+ "description": "Visual variant, styled with `cva`; sets a `data-variant` attribute.",
217
+ "from": "arkcn"
218
+ },
219
+ {
220
+ "name": "children",
221
+ "type": "ReactNode",
222
+ "required": false,
223
+ "description": "Content rendered inside the part.",
224
+ "from": "@types/react"
225
+ },
226
+ {
227
+ "name": "className",
228
+ "type": "string",
229
+ "required": false,
230
+ "description": "Extra classes merged into the part's own classes with `cn`.",
231
+ "from": "@types/react"
232
+ },
233
+ {
234
+ "name": "style",
235
+ "type": "CSSProperties",
236
+ "required": false,
237
+ "description": "Inline styles applied to the rendered element.",
238
+ "from": "@types/react"
239
+ }
240
+ ]
241
+ },
242
+ {
243
+ "name": "SchedulerNextTrigger",
244
+ "props": [
245
+ {
246
+ "name": "asChild",
247
+ "type": "boolean",
248
+ "required": false,
249
+ "description": "Render the child element instead of the default one, merging props and behaviour onto it.",
250
+ "from": "arkcn"
251
+ },
252
+ {
253
+ "name": "size",
254
+ "type": "\"default\" | \"xs\" | \"sm\" | \"lg\" | \"icon\" | \"icon-xs\" | \"icon-sm\" | \"icon-lg\" | null",
255
+ "required": false,
256
+ "description": "Size variant; sets a `data-size` attribute.",
257
+ "from": "arkcn"
258
+ },
259
+ {
260
+ "name": "variant",
261
+ "type": "\"link\" | \"default\" | \"outline\" | \"secondary\" | \"ghost\" | \"destructive\" | null",
262
+ "required": false,
263
+ "description": "Visual variant, styled with `cva`; sets a `data-variant` attribute.",
264
+ "from": "arkcn"
265
+ },
266
+ {
267
+ "name": "children",
268
+ "type": "ReactNode",
269
+ "required": false,
270
+ "description": "Content rendered inside the part.",
271
+ "from": "@types/react"
272
+ },
273
+ {
274
+ "name": "className",
275
+ "type": "string",
276
+ "required": false,
277
+ "description": "Extra classes merged into the part's own classes with `cn`.",
278
+ "from": "@types/react"
279
+ },
280
+ {
281
+ "name": "style",
282
+ "type": "CSSProperties",
283
+ "required": false,
284
+ "description": "Inline styles applied to the rendered element.",
285
+ "from": "@types/react"
286
+ }
287
+ ]
288
+ },
289
+ {
290
+ "name": "SchedulerTodayTrigger",
291
+ "props": [
292
+ {
293
+ "name": "asChild",
294
+ "type": "boolean",
295
+ "required": false,
296
+ "description": "Render the child element instead of the default one, merging props and behaviour onto it.",
297
+ "from": "arkcn"
298
+ },
299
+ {
300
+ "name": "size",
301
+ "type": "\"default\" | \"xs\" | \"sm\" | \"lg\" | \"icon\" | \"icon-xs\" | \"icon-sm\" | \"icon-lg\" | null",
302
+ "required": false,
303
+ "description": "Size variant; sets a `data-size` attribute.",
304
+ "from": "arkcn"
305
+ },
306
+ {
307
+ "name": "variant",
308
+ "type": "\"link\" | \"default\" | \"outline\" | \"secondary\" | \"ghost\" | \"destructive\" | null",
309
+ "required": false,
310
+ "description": "Visual variant, styled with `cva`; sets a `data-variant` attribute.",
311
+ "from": "arkcn"
312
+ },
313
+ {
314
+ "name": "children",
315
+ "type": "ReactNode",
316
+ "required": false,
317
+ "description": "Content rendered inside the part.",
318
+ "from": "@types/react"
319
+ },
320
+ {
321
+ "name": "className",
322
+ "type": "string",
323
+ "required": false,
324
+ "description": "Extra classes merged into the part's own classes with `cn`.",
325
+ "from": "@types/react"
326
+ },
327
+ {
328
+ "name": "style",
329
+ "type": "CSSProperties",
330
+ "required": false,
331
+ "description": "Inline styles applied to the rendered element.",
332
+ "from": "@types/react"
333
+ }
334
+ ]
335
+ },
336
+ {
337
+ "name": "SchedulerTitle",
338
+ "props": [
339
+ {
340
+ "name": "children",
341
+ "type": "ReactNode",
342
+ "required": false,
343
+ "description": "Content rendered inside the part.",
344
+ "from": "@types/react"
345
+ },
346
+ {
347
+ "name": "className",
348
+ "type": "string",
349
+ "required": false,
350
+ "description": "Extra classes merged into the part's own classes with `cn`.",
351
+ "from": "@types/react"
352
+ },
353
+ {
354
+ "name": "style",
355
+ "type": "CSSProperties",
356
+ "required": false,
357
+ "description": "Inline styles applied to the rendered element.",
358
+ "from": "@types/react"
359
+ }
360
+ ]
361
+ },
362
+ {
363
+ "name": "SchedulerViewSelect",
364
+ "props": [
365
+ {
366
+ "name": "asChild",
367
+ "type": "boolean",
368
+ "required": false,
369
+ "description": "Use the provided child element as the default rendered element, combining their props and behavior.",
370
+ "from": "@ark-ui/react"
371
+ },
372
+ {
373
+ "name": "children",
374
+ "type": "ReactNode",
375
+ "required": false,
376
+ "description": "Content rendered inside the part.",
377
+ "from": "@types/react"
378
+ },
379
+ {
380
+ "name": "className",
381
+ "type": "string",
382
+ "required": false,
383
+ "description": "Extra classes merged into the part's own classes with `cn`.",
384
+ "from": "@types/react"
385
+ },
386
+ {
387
+ "name": "defaultValue",
388
+ "type": "string | null",
389
+ "required": false,
390
+ "description": "The initial value of the checked radio when rendered.\nUse when you don't need to control the value of the radio group.",
391
+ "from": "@zag-js/radio-group"
392
+ },
393
+ {
394
+ "name": "disabled",
395
+ "type": "boolean",
396
+ "required": false,
397
+ "description": "If `true`, the radio group will be disabled",
398
+ "from": "@zag-js/radio-group"
399
+ },
400
+ {
401
+ "name": "form",
402
+ "type": "string",
403
+ "required": false,
404
+ "description": "The associate form of the underlying input.",
405
+ "from": "@zag-js/radio-group"
406
+ },
407
+ {
408
+ "name": "id",
409
+ "type": "string",
410
+ "required": false,
411
+ "description": "The unique identifier of the machine.",
412
+ "from": "@zag-js/types"
413
+ },
414
+ {
415
+ "name": "ids",
416
+ "type": "ElementIds",
417
+ "required": false,
418
+ "description": "The ids of the elements in the radio. Useful for composition.",
419
+ "from": "@zag-js/radio-group"
420
+ },
421
+ {
422
+ "name": "invalid",
423
+ "type": "boolean",
424
+ "required": false,
425
+ "description": "If `true`, the radio group is marked as invalid.",
426
+ "from": "@zag-js/radio-group"
427
+ },
428
+ {
429
+ "name": "name",
430
+ "type": "string",
431
+ "required": false,
432
+ "description": "The name of the input fields in the radio\n(Useful for form submission).",
433
+ "from": "@zag-js/radio-group"
434
+ },
435
+ {
436
+ "name": "orientation",
437
+ "type": "\"horizontal\" | \"vertical\"",
438
+ "required": false,
439
+ "description": "Orientation of the radio group",
440
+ "from": "@zag-js/radio-group"
441
+ },
442
+ {
443
+ "name": "readOnly",
444
+ "type": "boolean",
445
+ "required": false,
446
+ "description": "Whether the radio group is read-only",
447
+ "from": "@zag-js/radio-group"
448
+ },
449
+ {
450
+ "name": "required",
451
+ "type": "boolean",
452
+ "required": false,
453
+ "description": "If `true`, the radio group is marked as required.",
454
+ "from": "@zag-js/radio-group"
455
+ },
456
+ {
457
+ "name": "style",
458
+ "type": "CSSProperties",
459
+ "required": false,
460
+ "description": "Inline styles applied to the rendered element.",
461
+ "from": "@types/react"
462
+ }
463
+ ]
464
+ },
465
+ {
466
+ "name": "SchedulerTimeGrid",
467
+ "props": [
468
+ {
469
+ "name": "children",
470
+ "type": "ReactNode",
471
+ "required": false,
472
+ "description": "Content rendered inside the part.",
473
+ "from": "@types/react"
474
+ },
475
+ {
476
+ "name": "className",
477
+ "type": "string",
478
+ "required": false,
479
+ "description": "Extra classes merged into the part's own classes with `cn`.",
480
+ "from": "@types/react"
481
+ },
482
+ {
483
+ "name": "style",
484
+ "type": "CSSProperties",
485
+ "required": false,
486
+ "description": "Inline styles applied to the rendered element.",
487
+ "from": "@types/react"
488
+ }
489
+ ]
490
+ },
491
+ {
492
+ "name": "SchedulerTimeGridHeader",
493
+ "props": [
494
+ {
495
+ "name": "children",
496
+ "type": "ReactNode",
497
+ "required": false,
498
+ "description": "Content rendered inside the part.",
499
+ "from": "@types/react"
500
+ },
501
+ {
502
+ "name": "className",
503
+ "type": "string",
504
+ "required": false,
505
+ "description": "Extra classes merged into the part's own classes with `cn`.",
506
+ "from": "@types/react"
507
+ },
508
+ {
509
+ "name": "style",
510
+ "type": "CSSProperties",
511
+ "required": false,
512
+ "description": "Inline styles applied to the rendered element.",
513
+ "from": "@types/react"
514
+ }
515
+ ]
516
+ },
517
+ {
518
+ "name": "SchedulerDayHeadings",
519
+ "props": [
520
+ {
521
+ "name": "children",
522
+ "type": "((string | number | bigint | boolean | ReactElement<unknown, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | Promise<AwaitedReactN…",
523
+ "required": false,
524
+ "description": "Content rendered inside the part.",
525
+ "from": "@types/react"
526
+ },
527
+ {
528
+ "name": "className",
529
+ "type": "string",
530
+ "required": false,
531
+ "description": "Extra classes merged into the part's own classes with `cn`.",
532
+ "from": "@types/react"
533
+ },
534
+ {
535
+ "name": "style",
536
+ "type": "CSSProperties",
537
+ "required": false,
538
+ "description": "Inline styles applied to the rendered element.",
539
+ "from": "@types/react"
540
+ }
541
+ ]
542
+ },
543
+ {
544
+ "name": "SchedulerAllDayRow",
545
+ "props": [
546
+ {
547
+ "name": "children",
548
+ "type": "(day: Date) => ReactNode",
549
+ "required": true,
550
+ "description": "Content rendered inside the part.",
551
+ "from": "arkcn"
552
+ },
553
+ {
554
+ "name": "className",
555
+ "type": "string",
556
+ "required": false,
557
+ "description": "Extra classes merged into the part's own classes with `cn`.",
558
+ "from": "@types/react"
559
+ },
560
+ {
561
+ "name": "style",
562
+ "type": "CSSProperties",
563
+ "required": false,
564
+ "description": "Inline styles applied to the rendered element.",
565
+ "from": "@types/react"
566
+ }
567
+ ]
568
+ },
569
+ {
570
+ "name": "SchedulerTimeGridBody",
571
+ "props": [
572
+ {
573
+ "name": "children",
574
+ "type": "ReactNode",
575
+ "required": false,
576
+ "description": "Content rendered inside the part.",
577
+ "from": "@types/react"
578
+ },
579
+ {
580
+ "name": "className",
581
+ "type": "string",
582
+ "required": false,
583
+ "description": "Extra classes merged into the part's own classes with `cn`.",
584
+ "from": "@types/react"
585
+ },
586
+ {
587
+ "name": "style",
588
+ "type": "CSSProperties",
589
+ "required": false,
590
+ "description": "Inline styles applied to the rendered element.",
591
+ "from": "@types/react"
592
+ }
593
+ ]
594
+ },
595
+ {
596
+ "name": "SchedulerTimeGutter",
597
+ "props": [
598
+ {
599
+ "name": "children",
600
+ "type": "ReactNode",
601
+ "required": false,
602
+ "description": "Content rendered inside the part.",
603
+ "from": "@types/react"
604
+ },
605
+ {
606
+ "name": "className",
607
+ "type": "string",
608
+ "required": false,
609
+ "description": "Extra classes merged into the part's own classes with `cn`.",
610
+ "from": "@types/react"
611
+ },
612
+ {
613
+ "name": "style",
614
+ "type": "CSSProperties",
615
+ "required": false,
616
+ "description": "Inline styles applied to the rendered element.",
617
+ "from": "@types/react"
618
+ }
619
+ ]
620
+ },
621
+ {
622
+ "name": "SchedulerDayColumns",
623
+ "props": [
624
+ {
625
+ "name": "children",
626
+ "type": "ReactNode",
627
+ "required": false,
628
+ "description": "Content rendered inside the part.",
629
+ "from": "@types/react"
630
+ },
631
+ {
632
+ "name": "className",
633
+ "type": "string",
634
+ "required": false,
635
+ "description": "Extra classes merged into the part's own classes with `cn`.",
636
+ "from": "@types/react"
637
+ },
638
+ {
639
+ "name": "style",
640
+ "type": "CSSProperties",
641
+ "required": false,
642
+ "description": "Inline styles applied to the rendered element.",
643
+ "from": "@types/react"
644
+ }
645
+ ]
646
+ },
647
+ {
648
+ "name": "SchedulerDayColumn",
649
+ "props": [
650
+ {
651
+ "name": "children",
652
+ "type": "(event: T) => ReactNode",
653
+ "required": true,
654
+ "description": "Content rendered inside the part.",
655
+ "from": "arkcn"
656
+ },
657
+ {
658
+ "name": "date",
659
+ "type": "Date",
660
+ "required": true,
661
+ "description": "The date this part represents.",
662
+ "from": "arkcn"
663
+ },
664
+ {
665
+ "name": "events",
666
+ "type": "T[]",
667
+ "required": true,
668
+ "description": "Events shown in this column; overlaps are laid out side by side.",
669
+ "from": "arkcn"
670
+ },
671
+ {
672
+ "name": "className",
673
+ "type": "string",
674
+ "required": false,
675
+ "description": "Extra classes merged into the part's own classes with `cn`.",
676
+ "from": "@types/react"
677
+ },
678
+ {
679
+ "name": "style",
680
+ "type": "CSSProperties",
681
+ "required": false,
682
+ "description": "Inline styles applied to the rendered element.",
683
+ "from": "@types/react"
684
+ }
685
+ ]
686
+ },
687
+ {
688
+ "name": "SchedulerEvent",
689
+ "props": [
690
+ {
691
+ "name": "end",
692
+ "type": "Date",
693
+ "required": true,
694
+ "description": "Event end.",
695
+ "from": "arkcn"
696
+ },
697
+ {
698
+ "name": "start",
699
+ "type": "Date",
700
+ "required": true,
701
+ "description": "Event start.",
702
+ "from": "arkcn"
703
+ },
704
+ {
705
+ "name": "value",
706
+ "type": "string",
707
+ "required": true,
708
+ "description": "The controlled value.",
709
+ "from": "arkcn"
710
+ },
711
+ {
712
+ "name": "children",
713
+ "type": "ReactNode",
714
+ "required": false,
715
+ "description": "Content rendered inside the part.",
716
+ "from": "@types/react"
717
+ },
718
+ {
719
+ "name": "className",
720
+ "type": "string",
721
+ "required": false,
722
+ "description": "Extra classes merged into the part's own classes with `cn`.",
723
+ "from": "@types/react"
724
+ },
725
+ {
726
+ "name": "style",
727
+ "type": "CSSProperties",
728
+ "required": false,
729
+ "description": "Inline styles applied to the rendered element.",
730
+ "from": "@types/react"
731
+ }
732
+ ]
733
+ },
734
+ {
735
+ "name": "SchedulerEventTitle",
736
+ "props": [
737
+ {
738
+ "name": "children",
739
+ "type": "ReactNode",
740
+ "required": false,
741
+ "description": "Content rendered inside the part.",
742
+ "from": "@types/react"
743
+ },
744
+ {
745
+ "name": "className",
746
+ "type": "string",
747
+ "required": false,
748
+ "description": "Extra classes merged into the part's own classes with `cn`.",
749
+ "from": "@types/react"
750
+ },
751
+ {
752
+ "name": "style",
753
+ "type": "CSSProperties",
754
+ "required": false,
755
+ "description": "Inline styles applied to the rendered element.",
756
+ "from": "@types/react"
757
+ }
758
+ ]
759
+ },
760
+ {
761
+ "name": "SchedulerEventTime",
762
+ "props": [
763
+ {
764
+ "name": "children",
765
+ "type": "ReactNode",
766
+ "required": false,
767
+ "description": "Content rendered inside the part.",
768
+ "from": "@types/react"
769
+ },
770
+ {
771
+ "name": "className",
772
+ "type": "string",
773
+ "required": false,
774
+ "description": "Extra classes merged into the part's own classes with `cn`.",
775
+ "from": "@types/react"
776
+ },
777
+ {
778
+ "name": "style",
779
+ "type": "CSSProperties",
780
+ "required": false,
781
+ "description": "Inline styles applied to the rendered element.",
782
+ "from": "@types/react"
783
+ }
784
+ ]
785
+ },
786
+ {
787
+ "name": "SchedulerEventResizeHandle",
788
+ "props": [
789
+ {
790
+ "name": "children",
791
+ "type": "ReactNode",
792
+ "required": false,
793
+ "description": "Content rendered inside the part.",
794
+ "from": "@types/react"
795
+ },
796
+ {
797
+ "name": "className",
798
+ "type": "string",
799
+ "required": false,
800
+ "description": "Extra classes merged into the part's own classes with `cn`.",
801
+ "from": "@types/react"
802
+ },
803
+ {
804
+ "name": "style",
805
+ "type": "CSSProperties",
806
+ "required": false,
807
+ "description": "Inline styles applied to the rendered element.",
808
+ "from": "@types/react"
809
+ }
810
+ ]
811
+ },
812
+ {
813
+ "name": "SchedulerCreatePreview",
814
+ "props": [
815
+ {
816
+ "name": "children",
817
+ "type": "ReactNode",
818
+ "required": false,
819
+ "description": "Content rendered inside the part.",
820
+ "from": "@types/react"
821
+ },
822
+ {
823
+ "name": "className",
824
+ "type": "string",
825
+ "required": false,
826
+ "description": "Extra classes merged into the part's own classes with `cn`.",
827
+ "from": "@types/react"
828
+ },
829
+ {
830
+ "name": "style",
831
+ "type": "CSSProperties",
832
+ "required": false,
833
+ "description": "Inline styles applied to the rendered element.",
834
+ "from": "@types/react"
835
+ }
836
+ ]
837
+ },
838
+ {
839
+ "name": "SchedulerNowIndicator",
840
+ "props": [
841
+ {
842
+ "name": "children",
843
+ "type": "ReactNode",
844
+ "required": false,
845
+ "description": "Content rendered inside the part.",
846
+ "from": "@types/react"
847
+ },
848
+ {
849
+ "name": "className",
850
+ "type": "string",
851
+ "required": false,
852
+ "description": "Extra classes merged into the part's own classes with `cn`.",
853
+ "from": "@types/react"
854
+ },
855
+ {
856
+ "name": "style",
857
+ "type": "CSSProperties",
858
+ "required": false,
859
+ "description": "Inline styles applied to the rendered element.",
860
+ "from": "@types/react"
861
+ }
862
+ ]
863
+ },
864
+ {
865
+ "name": "SchedulerMonthGrid",
866
+ "props": [
867
+ {
868
+ "name": "children",
869
+ "type": "ReactNode",
870
+ "required": false,
871
+ "description": "Content rendered inside the part.",
872
+ "from": "@types/react"
873
+ },
874
+ {
875
+ "name": "className",
876
+ "type": "string",
877
+ "required": false,
878
+ "description": "Extra classes merged into the part's own classes with `cn`.",
879
+ "from": "@types/react"
880
+ },
881
+ {
882
+ "name": "style",
883
+ "type": "CSSProperties",
884
+ "required": false,
885
+ "description": "Inline styles applied to the rendered element.",
886
+ "from": "@types/react"
887
+ }
888
+ ]
889
+ },
890
+ {
891
+ "name": "SchedulerMonthHeader",
892
+ "props": [
893
+ {
894
+ "name": "children",
895
+ "type": "ReactNode",
896
+ "required": false,
897
+ "description": "Content rendered inside the part.",
898
+ "from": "@types/react"
899
+ },
900
+ {
901
+ "name": "className",
902
+ "type": "string",
903
+ "required": false,
904
+ "description": "Extra classes merged into the part's own classes with `cn`.",
905
+ "from": "@types/react"
906
+ },
907
+ {
908
+ "name": "style",
909
+ "type": "CSSProperties",
910
+ "required": false,
911
+ "description": "Inline styles applied to the rendered element.",
912
+ "from": "@types/react"
913
+ }
914
+ ]
915
+ },
916
+ {
917
+ "name": "SchedulerMonthBody",
918
+ "props": [
919
+ {
920
+ "name": "children",
921
+ "type": "ReactNode",
922
+ "required": false,
923
+ "description": "Content rendered inside the part.",
924
+ "from": "@types/react"
925
+ },
926
+ {
927
+ "name": "className",
928
+ "type": "string",
929
+ "required": false,
930
+ "description": "Extra classes merged into the part's own classes with `cn`.",
931
+ "from": "@types/react"
932
+ },
933
+ {
934
+ "name": "style",
935
+ "type": "CSSProperties",
936
+ "required": false,
937
+ "description": "Inline styles applied to the rendered element.",
938
+ "from": "@types/react"
939
+ }
940
+ ]
941
+ },
942
+ {
943
+ "name": "SchedulerMonthCell",
944
+ "props": [
945
+ {
946
+ "name": "date",
947
+ "type": "Date",
948
+ "required": true,
949
+ "description": "The date this part represents.",
950
+ "from": "arkcn"
951
+ },
952
+ {
953
+ "name": "children",
954
+ "type": "ReactNode",
955
+ "required": false,
956
+ "description": "Content rendered inside the part.",
957
+ "from": "@types/react"
958
+ },
959
+ {
960
+ "name": "className",
961
+ "type": "string",
962
+ "required": false,
963
+ "description": "Extra classes merged into the part's own classes with `cn`.",
964
+ "from": "@types/react"
965
+ },
966
+ {
967
+ "name": "style",
968
+ "type": "CSSProperties",
969
+ "required": false,
970
+ "description": "Inline styles applied to the rendered element.",
971
+ "from": "@types/react"
972
+ }
973
+ ]
974
+ },
975
+ {
976
+ "name": "SchedulerMonthEvent",
977
+ "props": [
978
+ {
979
+ "name": "end",
980
+ "type": "Date",
981
+ "required": true,
982
+ "description": "Event end.",
983
+ "from": "arkcn"
984
+ },
985
+ {
986
+ "name": "start",
987
+ "type": "Date",
988
+ "required": true,
989
+ "description": "Event start.",
990
+ "from": "arkcn"
991
+ },
992
+ {
993
+ "name": "value",
994
+ "type": "string",
995
+ "required": true,
996
+ "description": "The controlled value.",
997
+ "from": "arkcn"
998
+ },
999
+ {
1000
+ "name": "children",
1001
+ "type": "ReactNode",
1002
+ "required": false,
1003
+ "description": "Content rendered inside the part.",
1004
+ "from": "@types/react"
1005
+ },
1006
+ {
1007
+ "name": "className",
1008
+ "type": "string",
1009
+ "required": false,
1010
+ "description": "Extra classes merged into the part's own classes with `cn`.",
1011
+ "from": "@types/react"
1012
+ },
1013
+ {
1014
+ "name": "style",
1015
+ "type": "CSSProperties",
1016
+ "required": false,
1017
+ "description": "Inline styles applied to the rendered element.",
1018
+ "from": "@types/react"
1019
+ }
1020
+ ]
1021
+ },
1022
+ {
1023
+ "name": "SchedulerMonthMore",
1024
+ "props": [
1025
+ {
1026
+ "name": "children",
1027
+ "type": "ReactNode",
1028
+ "required": false,
1029
+ "description": "Content rendered inside the part.",
1030
+ "from": "@types/react"
1031
+ },
1032
+ {
1033
+ "name": "className",
1034
+ "type": "string",
1035
+ "required": false,
1036
+ "description": "Extra classes merged into the part's own classes with `cn`.",
1037
+ "from": "@types/react"
1038
+ },
1039
+ {
1040
+ "name": "style",
1041
+ "type": "CSSProperties",
1042
+ "required": false,
1043
+ "description": "Inline styles applied to the rendered element.",
1044
+ "from": "@types/react"
1045
+ }
1046
+ ]
1047
+ }
1048
+ ],
37
1049
  "dependencies": {
38
1050
  "date-fns": "^4.4.0",
39
1051
  "lucide-react": "^1.41.0"
@@ -48,7 +1060,7 @@
48
1060
  "files": [
49
1061
  {
50
1062
  "path": "components/ui/scheduler.tsx",
51
- "content": "import * as React from \"react\"\nimport {\n addDays,\n addMinutes,\n addMonths,\n addWeeks,\n differenceInCalendarDays,\n differenceInMinutes,\n eachDayOfInterval,\n endOfMonth,\n endOfWeek,\n format,\n isSameDay,\n isSameMonth,\n isToday,\n setHours,\n setMinutes,\n startOfDay,\n startOfMonth,\n startOfWeek,\n} from \"date-fns\"\nimport { CalendarIcon, ChevronLeftIcon, ChevronRightIcon } from \"lucide-react\"\nimport { Button } from \"@/components/ui/button\"\nimport { SegmentGroup, SegmentGroupIndicator, SegmentGroupItem } from \"@/components/ui/segment-group\"\nimport { cn } from \"@/lib/utils\"\nimport { useControllable } from \"@/lib/controllable\"\nimport { LiveRegion, useLiveRegion } from \"@/components/ui/live-region\"\n\n/**\n * Scheduler — a compositional, data-agnostic calendar. The consumer owns the\n * events and renders them with the parts; the root owns the anchor `date`,\n * the `view` (day/week/month), the time-grid geometry and the interactions:\n * drag to move, drag the bottom edge to resize, drag on empty time to create,\n * keyboard moves. Everything is reported as intents (`onEventChange`,\n * `onCreate`) snapped to `slotMinutes`.\n *\n * Anatomy:\n * Scheduler { date, view, onEventChange, onCreate }\n * SchedulerToolbar > SchedulerPrevTrigger / SchedulerTodayTrigger / SchedulerNextTrigger / SchedulerTitle / SchedulerViewSelect\n * SchedulerTimeGrid (day, week)\n * SchedulerTimeGridHeader > SchedulerDayHeading date=… (+ SchedulerAllDayRow)\n * SchedulerTimeGridBody > SchedulerTimeGutter + SchedulerDayColumn date= events=\n * children: (event) => <SchedulerEvent value= start= end=> … <SchedulerEventResizeHandle/> </SchedulerEvent>\n * SchedulerNowIndicator, SchedulerCreatePreview\n * SchedulerMonthGrid > SchedulerMonthHeader + SchedulerMonthBody > SchedulerMonthCell date= > SchedulerMonthEvent value=…\n *\n * Keyboard on an event: Space/Enter picks up, ↑/↓ move by a slot, ←/→ move a\n * day, Shift+↑/↓ change the end, Escape cancels.\n */\n\ntype SchedulerView = \"day\" | \"week\" | \"month\"\ntype EventLike = { id: string; start: Date; end: Date; allDay?: boolean }\ntype SchedulerEventChange = { id: string; start: Date; end: Date }\ntype Interaction = { id: string; type: \"move\" | \"resize\"; start: Date; end: Date }\ntype CreateState = { start: Date; end: Date } | null\n\ntype SchedulerContextValue = {\n date: Date\n setDate: (date: Date) => void\n view: SchedulerView\n setView: (view: SchedulerView) => void\n range: { start: Date; end: Date }\n days: Date[]\n weekStartsOn: 0 | 1 | 2 | 3 | 4 | 5 | 6\n hourHeight: number\n slotMinutes: number\n minHour: number\n maxHour: number\n editable: boolean\n interaction: Interaction | null\n creating: CreateState\n grabbed: string | null\n timeToY: (date: Date) => number\n yToMinutes: (y: number) => number\n bodyRef: React.RefObject<HTMLDivElement | null>\n startMove: (event: EventLike, pointer: React.PointerEvent, columnDates: Date[]) => void\n startResize: (event: EventLike, pointer: React.PointerEvent) => void\n startCreate: (day: Date, pointer: React.PointerEvent, columnDates: Date[]) => void\n startMonthMove: (event: EventLike, pointer: React.PointerEvent) => void\n grab: (id: string | null) => void\n cancelGrab: () => void\n nudge: (event: EventLike, deltaMinutes: number, deltaDays: number, part: \"both\" | \"end\") => void\n announce: (message: string) => void\n}\n\nconst SchedulerContext = React.createContext<SchedulerContextValue | null>(null)\nfunction useScheduler() {\n const ctx = React.useContext(SchedulerContext)\n if (!ctx) throw new Error(\"Scheduler parts must be used within <Scheduler>\")\n return ctx\n}\n\nconst clamp = (v: number, min: number, max: number) => Math.min(max, Math.max(min, v))\nconst minutesOfDay = (d: Date) => d.getHours() * 60 + d.getMinutes()\nconst withMinutes = (day: Date, minutes: number) => setMinutes(setHours(startOfDay(day), 0), minutes)\n\nfunction visibleRange(date: Date, view: SchedulerView, weekStartsOn: SchedulerContextValue[\"weekStartsOn\"]) {\n if (view === \"day\") return { start: startOfDay(date), end: startOfDay(date) }\n if (view === \"week\") return { start: startOfWeek(date, { weekStartsOn }), end: endOfWeek(date, { weekStartsOn }) }\n return {\n start: startOfWeek(startOfMonth(date), { weekStartsOn }),\n end: endOfWeek(endOfMonth(date), { weekStartsOn }),\n }\n}\n\n/* ---------------------------------------------------------------------------\n * Root\n * ------------------------------------------------------------------------- */\n\nfunction Scheduler({\n date: dateProp,\n defaultDate,\n onDateChange,\n view: viewProp,\n defaultView = \"week\",\n onViewChange,\n weekStartsOn = 1,\n hourHeight = 56,\n slotMinutes = 15,\n minHour = 0,\n maxHour = 24,\n editable = true,\n onEventChange,\n onCreate,\n className,\n children,\n ...props\n}: Omit<React.ComponentProps<\"div\">, \"onChange\"> & {\n date?: Date\n defaultDate?: Date\n onDateChange?: (date: Date) => void\n view?: SchedulerView\n defaultView?: SchedulerView\n onViewChange?: (view: SchedulerView) => void\n weekStartsOn?: SchedulerContextValue[\"weekStartsOn\"]\n hourHeight?: number\n slotMinutes?: number\n minHour?: number\n maxHour?: number\n editable?: boolean\n onEventChange?: (change: SchedulerEventChange) => void\n /** Drag on empty time (or a month cell click) proposes a new event. */\n onCreate?: (range: { start: Date; end: Date; allDay?: boolean }) => void\n}) {\n const [initialDate] = React.useState(() => startOfDay(defaultDate ?? new Date()))\n const [date, setDate] = useControllable(dateProp, initialDate, onDateChange)\n const [view, setView] = useControllable<SchedulerView>(viewProp, defaultView, onViewChange)\n\n const range = React.useMemo(() => visibleRange(date, view, weekStartsOn), [date, view, weekStartsOn])\n const days = React.useMemo(() => eachDayOfInterval(range), [range])\n\n const bodyRef = React.useRef<HTMLDivElement>(null)\n const minuteHeight = hourHeight / 60\n const timeToY = React.useCallback(\n (d: Date) => (minutesOfDay(d) - minHour * 60) * minuteHeight,\n [minHour, minuteHeight]\n )\n const yToMinutes = React.useCallback(\n (y: number) =>\n clamp(Math.round((y / minuteHeight + minHour * 60) / slotMinutes) * slotMinutes, minHour * 60, maxHour * 60),\n [minuteHeight, minHour, maxHour, slotMinutes]\n )\n\n const [interaction, setInteraction] = React.useState<Interaction | null>(null)\n const [creating, setCreating] = React.useState<CreateState>(null)\n const [grabbed, setGrabbed] = React.useState<string | null>(null)\n const { message: announcement, announce } = useLiveRegion()\n const onEventChangeRef = React.useRef(onEventChange)\n const onCreateRef = React.useRef(onCreate)\n React.useEffect(() => {\n onEventChangeRef.current = onEventChange\n onCreateRef.current = onCreate\n }, [onEventChange, onCreate])\n const grabOrigin = React.useRef<SchedulerEventChange | null>(null)\n\n const track = React.useCallback((onMove: (e: PointerEvent) => void, onUp: (e: PointerEvent) => void) => {\n const up = (e: PointerEvent) => {\n window.removeEventListener(\"pointermove\", onMove)\n window.removeEventListener(\"pointerup\", up)\n window.removeEventListener(\"pointercancel\", up)\n onUp(e)\n }\n window.addEventListener(\"pointermove\", onMove)\n window.addEventListener(\"pointerup\", up)\n window.addEventListener(\"pointercancel\", up)\n }, [])\n\n /** Which day column a client x falls in, from the column elements' rects. */\n const dayAtX = React.useCallback((x: number, columnDates: Date[]) => {\n const columns = Array.from(bodyRef.current?.querySelectorAll<HTMLElement>(\"[data-slot=scheduler-day-column]\") ?? [])\n for (const [i, col] of columns.entries()) {\n const r = col.getBoundingClientRect()\n if (x >= r.left && x < r.right) return columnDates[i] ?? null\n }\n return null\n }, [])\n\n const startMove = React.useCallback(\n (event: EventLike, pointer: React.PointerEvent, columnDates: Date[]) => {\n if (!editable) return\n pointer.preventDefault()\n pointer.stopPropagation()\n const originY = pointer.clientY\n const originDay = startOfDay(event.start)\n let current: Interaction = { id: event.id, type: \"move\", start: event.start, end: event.end }\n let moved = false\n setInteraction(current)\n track(\n (e) => {\n const dy = e.clientY - originY\n const deltaMinutes = Math.round(dy / minuteHeight / slotMinutes) * slotMinutes\n const day = dayAtX(e.clientX, columnDates) ?? originDay\n const dayDelta = differenceInCalendarDays(day, originDay)\n const duration = differenceInMinutes(event.end, event.start)\n const startMin = clamp(minutesOfDay(event.start) + deltaMinutes, minHour * 60, maxHour * 60 - duration)\n const start = addDays(withMinutes(originDay, startMin), dayDelta)\n current = { id: event.id, type: \"move\", start, end: addMinutes(start, duration) }\n if (Math.abs(dy) > 3 || dayDelta !== 0) moved = true\n setInteraction(current)\n },\n () => {\n setInteraction(null)\n if (\n moved &&\n (current.start.getTime() !== event.start.getTime() || current.end.getTime() !== event.end.getTime())\n ) {\n onEventChangeRef.current?.({ id: event.id, start: current.start, end: current.end })\n }\n }\n )\n },\n [editable, track, dayAtX, minuteHeight, slotMinutes, minHour, maxHour]\n )\n\n const startResize = React.useCallback(\n (event: EventLike, pointer: React.PointerEvent) => {\n if (!editable) return\n pointer.preventDefault()\n pointer.stopPropagation()\n const originY = pointer.clientY\n let current: Interaction = { id: event.id, type: \"resize\", start: event.start, end: event.end }\n setInteraction(current)\n track(\n (e) => {\n const deltaMinutes = Math.round((e.clientY - originY) / minuteHeight / slotMinutes) * slotMinutes\n const endMin = clamp(\n minutesOfDay(event.end) + deltaMinutes,\n minutesOfDay(event.start) + slotMinutes,\n maxHour * 60\n )\n current = { ...current, end: withMinutes(event.start, endMin) }\n setInteraction(current)\n },\n () => {\n setInteraction(null)\n if (current.end.getTime() !== event.end.getTime()) {\n onEventChangeRef.current?.({ id: event.id, start: event.start, end: current.end })\n }\n }\n )\n },\n [editable, track, minuteHeight, slotMinutes, maxHour]\n )\n\n const startCreate = React.useCallback(\n (day: Date, pointer: React.PointerEvent, columnDates: Date[]) => {\n if (!editable || !onCreateRef.current) return\n const column = (pointer.currentTarget as HTMLElement).getBoundingClientRect()\n const originMin = yToMinutes(pointer.clientY - column.top)\n let current: NonNullable<CreateState> = {\n start: withMinutes(day, originMin),\n end: withMinutes(day, originMin + slotMinutes),\n }\n let dragged = false\n setCreating(current)\n track(\n (e) => {\n const min = yToMinutes(e.clientY - column.top)\n const targetDay = dayAtX(e.clientX, columnDates) ?? day\n const a = Math.min(originMin, min)\n const b = Math.max(originMin, min, a + slotMinutes)\n current = { start: withMinutes(targetDay, a), end: withMinutes(targetDay, b) }\n dragged = true\n setCreating(current)\n },\n () => {\n setCreating(null)\n onCreateRef.current?.(dragged ? current : { start: current.start, end: addMinutes(current.start, 60) })\n }\n )\n },\n [editable, track, yToMinutes, slotMinutes, dayAtX]\n )\n\n /** Month view: drag a chip onto another cell to shift the event by whole days. */\n const startMonthMove = React.useCallback(\n (event: EventLike, pointer: React.PointerEvent) => {\n if (!editable) return\n pointer.preventDefault()\n pointer.stopPropagation()\n const originDay = startOfDay(event.start)\n let current: Interaction = { id: event.id, type: \"move\", start: event.start, end: event.end }\n let moved = false\n setInteraction(current)\n const cellAt = (x: number, y: number) =>\n document.elementFromPoint(x, y)?.closest<HTMLElement>(\"[data-slot=scheduler-month-cell]\")?.dataset.date\n track(\n (e) => {\n const target = cellAt(e.clientX, e.clientY)\n if (!target) return\n const delta = differenceInCalendarDays(new Date(target), originDay)\n current = { ...current, start: addDays(event.start, delta), end: addDays(event.end, delta) }\n moved = true\n setInteraction(current)\n },\n () => {\n setInteraction(null)\n if (moved && current.start.getTime() !== event.start.getTime()) {\n onEventChangeRef.current?.({ id: event.id, start: current.start, end: current.end })\n }\n }\n )\n },\n [editable, track]\n )\n\n const grab = React.useCallback(\n (id: string | null) => {\n if (id) {\n setGrabbed(id)\n announce(\"Picked up. Arrows move; Shift+Up/Down change the end. Space drops, Escape cancels.\")\n } else {\n setGrabbed(null)\n grabOrigin.current = null\n announce(\"Dropped.\")\n }\n },\n [announce]\n )\n const cancelGrab = React.useCallback(() => {\n if (grabOrigin.current) onEventChangeRef.current?.(grabOrigin.current)\n setGrabbed(null)\n grabOrigin.current = null\n announce(\"Cancelled.\")\n }, [announce])\n\n const nudge = React.useCallback(\n (event: EventLike, deltaMinutes: number, deltaDays: number, part: \"both\" | \"end\") => {\n if (!grabOrigin.current || grabOrigin.current.id !== event.id) {\n grabOrigin.current = { id: event.id, start: event.start, end: event.end }\n }\n const next =\n part === \"end\"\n ? {\n start: event.start,\n end: addMinutes(\n event.end,\n Math.max(deltaMinutes, slotMinutes - differenceInMinutes(event.end, event.start))\n ),\n }\n : {\n start: addDays(addMinutes(event.start, deltaMinutes), deltaDays),\n end: addDays(addMinutes(event.end, deltaMinutes), deltaDays),\n }\n onEventChangeRef.current?.({ id: event.id, ...next })\n announce(`${format(next.start, \"EEE d, p\")} to ${format(next.end, \"p\")}.`)\n },\n [announce, slotMinutes]\n )\n\n const ctx = React.useMemo<SchedulerContextValue>(\n () => ({\n date,\n setDate,\n view,\n setView,\n range,\n days,\n weekStartsOn,\n hourHeight,\n slotMinutes,\n minHour,\n maxHour,\n editable,\n interaction,\n creating,\n grabbed,\n timeToY,\n yToMinutes,\n bodyRef,\n startMove,\n startResize,\n startCreate,\n startMonthMove,\n grab,\n cancelGrab,\n nudge,\n announce,\n }),\n [\n date,\n setDate,\n view,\n setView,\n range,\n days,\n weekStartsOn,\n hourHeight,\n slotMinutes,\n minHour,\n maxHour,\n editable,\n interaction,\n creating,\n grabbed,\n timeToY,\n yToMinutes,\n startMove,\n startResize,\n startCreate,\n startMonthMove,\n grab,\n cancelGrab,\n nudge,\n announce,\n ]\n )\n\n return (\n <SchedulerContext.Provider value={ctx}>\n <div\n data-slot=\"scheduler\"\n data-view={view}\n data-interacting={interaction?.type ?? (creating ? \"create\" : undefined)}\n className={cn(\"flex min-h-0 flex-1 flex-col gap-3\", className)}\n {...props}\n >\n {children}\n <LiveRegion data-slot=\"scheduler-live-region\" message={announcement} />\n </div>\n </SchedulerContext.Provider>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Toolbar\n * ------------------------------------------------------------------------- */\n\nfunction SchedulerToolbar({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div data-slot=\"scheduler-toolbar\" className={cn(\"flex flex-wrap items-center gap-2\", className)} {...props} />\n}\n\nfunction shift(date: Date, view: SchedulerView, direction: 1 | -1) {\n if (view === \"day\") return addDays(date, direction)\n if (view === \"week\") return addWeeks(date, direction)\n return addMonths(date, direction)\n}\n\nfunction SchedulerPrevTrigger({ asChild, children, onClick, ...props }: React.ComponentProps<typeof Button>) {\n const { date, view, setDate } = useScheduler()\n return (\n <Button\n data-slot=\"scheduler-prev-trigger\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label={`Previous ${view}`}\n asChild={asChild}\n onClick={(event) => {\n onClick?.(event)\n if (!event.defaultPrevented) setDate(shift(date, view, -1))\n }}\n {...props}\n >\n {asChild ? children : (children ?? <ChevronLeftIcon />)}\n </Button>\n )\n}\n\nfunction SchedulerNextTrigger({ asChild, children, onClick, ...props }: React.ComponentProps<typeof Button>) {\n const { date, view, setDate } = useScheduler()\n return (\n <Button\n data-slot=\"scheduler-next-trigger\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label={`Next ${view}`}\n asChild={asChild}\n onClick={(event) => {\n onClick?.(event)\n if (!event.defaultPrevented) setDate(shift(date, view, 1))\n }}\n {...props}\n >\n {asChild ? children : (children ?? <ChevronRightIcon />)}\n </Button>\n )\n}\n\nfunction SchedulerTodayTrigger({ asChild, children, onClick, ...props }: React.ComponentProps<typeof Button>) {\n const { setDate } = useScheduler()\n return (\n <Button\n data-slot=\"scheduler-today-trigger\"\n variant=\"outline\"\n size=\"sm\"\n asChild={asChild}\n onClick={(event) => {\n onClick?.(event)\n if (!event.defaultPrevented) setDate(startOfDay(new Date()))\n }}\n {...props}\n >\n {asChild\n ? children\n : (children ?? (\n <>\n <CalendarIcon /> Today\n </>\n ))}\n </Button>\n )\n}\n\n/** \"September 2026\", \"Sep 7 – 13, 2026\" or \"Mon, Sep 7\". */\nfunction SchedulerTitle({ className, children, ...props }: React.ComponentProps<\"h2\">) {\n const { date, view, range } = useScheduler()\n const text =\n view === \"month\"\n ? format(date, \"MMMM yyyy\")\n : view === \"day\"\n ? format(date, \"EEEE, MMM d, yyyy\")\n : isSameMonth(range.start, range.end)\n ? `${format(range.start, \"MMM d\")} – ${format(range.end, \"d, yyyy\")}`\n : `${format(range.start, \"MMM d\")} – ${format(range.end, \"MMM d, yyyy\")}`\n return (\n <h2 data-slot=\"scheduler-title\" className={cn(\"text-base font-semibold tabular-nums\", className)} {...props}>\n {children ?? text}\n </h2>\n )\n}\n\nfunction SchedulerViewSelect({\n className,\n ...props\n}: Omit<React.ComponentProps<typeof SegmentGroup>, \"value\" | \"onValueChange\">) {\n const { view, setView } = useScheduler()\n return (\n <SegmentGroup\n data-slot=\"scheduler-view-select\"\n value={view}\n onValueChange={({ value }) => value && setView(value as SchedulerView)}\n className={className}\n {...props}\n >\n <SegmentGroupIndicator />\n <SegmentGroupItem value=\"day\">Day</SegmentGroupItem>\n <SegmentGroupItem value=\"week\">Week</SegmentGroupItem>\n <SegmentGroupItem value=\"month\">Month</SegmentGroupItem>\n </SegmentGroup>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Time grid (day / week)\n * ------------------------------------------------------------------------- */\n\nconst GUTTER = \"3.5rem\"\n\nfunction SchedulerTimeGrid({ className, style, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"scheduler-time-grid\"\n className={cn(\"flex min-h-0 flex-1 flex-col overflow-hidden rounded-xl border bg-background\", className)}\n style={{ [\"--scheduler-gutter\" as string]: GUTTER, ...style }}\n {...props}\n />\n )\n}\n\nfunction SchedulerTimeGridHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"scheduler-time-grid-header\"\n className={cn(\"grid shrink-0 border-b bg-muted/50\", className)}\n style={{ gridTemplateColumns: \"var(--scheduler-gutter) 1fr\" }}\n {...props}\n />\n )\n}\n\n/** Day headings across the top, one per visible day. */\nfunction SchedulerDayHeadings({\n className,\n children,\n ...props\n}: React.ComponentProps<\"div\"> & { children?: (day: Date) => React.ReactNode }) {\n const { days, setDate, setView } = useScheduler()\n return (\n <>\n <div aria-hidden className=\"border-e\" />\n <div\n data-slot=\"scheduler-day-headings\"\n className={cn(\"grid\", className)}\n style={{ gridTemplateColumns: `repeat(${days.length}, minmax(0, 1fr))` }}\n {...props}\n >\n {days.map((day) => (\n <button\n key={day.toISOString()}\n type=\"button\"\n data-slot=\"scheduler-day-heading\"\n data-today={isToday(day) ? \"\" : undefined}\n className=\"group/heading flex flex-col items-center gap-0.5 border-e py-2 text-xs text-muted-foreground outline-none last:border-e-0 hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-inset\"\n onClick={() => {\n setDate(day)\n setView(\"day\")\n }}\n >\n {children ? (\n children(day)\n ) : (\n <>\n <span>{format(day, \"EEE\")}</span>\n <span className=\"flex size-7 items-center justify-center rounded-full text-base font-semibold text-foreground group-data-today/heading:bg-primary group-data-today/heading:text-primary-foreground\">\n {format(day, \"d\")}\n </span>\n </>\n )}\n </button>\n ))}\n </div>\n </>\n )\n}\n\n/** Strip under the headings for all-day events; render chips inside per day. */\nfunction SchedulerAllDayRow({\n className,\n children,\n ...props\n}: Omit<React.ComponentProps<\"div\">, \"children\"> & { children: (day: Date) => React.ReactNode }) {\n const { days } = useScheduler()\n return (\n <>\n <div className=\"flex items-start justify-end border-e border-t px-1.5 py-1 text-[10px] text-muted-foreground uppercase\">\n all day\n </div>\n <div\n data-slot=\"scheduler-all-day-row\"\n className={cn(\"grid border-t\", className)}\n style={{ gridTemplateColumns: `repeat(${days.length}, minmax(0, 1fr))` }}\n {...props}\n >\n {days.map((day) => (\n <div\n key={day.toISOString()}\n data-slot=\"scheduler-all-day-cell\"\n className=\"flex min-h-7 flex-col gap-0.5 border-e p-0.5 last:border-e-0\"\n >\n {children(day)}\n </div>\n ))}\n </div>\n </>\n )\n}\n\nfunction SchedulerTimeGridBody({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const { bodyRef, hourHeight, minHour, maxHour } = useScheduler()\n // Scroll to a sensible hour on mount.\n React.useEffect(() => {\n const el = bodyRef.current\n if (el) el.scrollTop = Math.max(0, (8 - minHour) * hourHeight - 8)\n }, [bodyRef, hourHeight, minHour])\n return (\n <div\n ref={bodyRef}\n data-slot=\"scheduler-time-grid-body\"\n className={cn(\"relative min-h-0 flex-1 overflow-y-auto\", className)}\n {...props}\n >\n <div\n className=\"grid\"\n style={{ gridTemplateColumns: \"var(--scheduler-gutter) 1fr\", height: (maxHour - minHour) * hourHeight }}\n >\n {children}\n </div>\n </div>\n )\n}\n\nfunction SchedulerTimeGutter({ className, ...props }: React.ComponentProps<\"div\">) {\n const { hourHeight, minHour, maxHour } = useScheduler()\n const hours = Array.from({ length: maxHour - minHour }, (_, i) => minHour + i)\n return (\n <div data-slot=\"scheduler-time-gutter\" className={cn(\"relative border-e\", className)} {...props}>\n {hours.map((h) => (\n <div\n key={h}\n className=\"absolute inset-e-1.5 -translate-y-1/2 text-[11px] text-muted-foreground tabular-nums\"\n style={{ top: (h - minHour) * hourHeight }}\n >\n {h > minHour ? format(setHours(new Date(), h), \"h a\") : \"\"}\n </div>\n ))}\n </div>\n )\n}\n\n/** The columns container; hour lines are drawn behind. */\nfunction SchedulerDayColumns({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const { days, hourHeight, minHour, maxHour } = useScheduler()\n const hours = Array.from({ length: maxHour - minHour }, (_, i) => i)\n return (\n <div\n data-slot=\"scheduler-day-columns\"\n className={cn(\"relative grid\", className)}\n style={{ gridTemplateColumns: `repeat(${days.length}, minmax(0, 1fr))` }}\n {...props}\n >\n <div aria-hidden className=\"pointer-events-none absolute inset-0\">\n {hours.map((i) => (\n <div key={i} className=\"absolute inset-x-0 border-t border-border/70\" style={{ top: i * hourHeight }} />\n ))}\n </div>\n {children}\n </div>\n )\n}\n\n/** Side-by-side lanes for overlapping events. */\nfunction layoutLanes(events: EventLike[]) {\n const sorted = [...events]\n .filter((e) => !e.allDay)\n .sort((a, b) => a.start.getTime() - b.start.getTime() || b.end.getTime() - a.end.getTime())\n const result = new Map<string, { lane: number; lanes: number }>()\n let cluster: EventLike[] = []\n let clusterEnd = 0\n const flush = () => {\n const laneEnds: number[] = []\n const assigned: { id: string; lane: number }[] = []\n for (const e of cluster) {\n let lane = laneEnds.findIndex((end) => end <= e.start.getTime())\n if (lane === -1) lane = laneEnds.length\n laneEnds[lane] = e.end.getTime()\n assigned.push({ id: e.id, lane })\n }\n for (const a of assigned) result.set(a.id, { lane: a.lane, lanes: laneEnds.length })\n cluster = []\n clusterEnd = 0\n }\n for (const e of sorted) {\n if (cluster.length && e.start.getTime() >= clusterEnd) flush()\n cluster.push(e)\n clusterEnd = Math.max(clusterEnd, e.end.getTime())\n }\n if (cluster.length) flush()\n return result\n}\n\nconst ColumnContext = React.createContext<{ day: Date; lanes: Map<string, { lane: number; lanes: number }> } | null>(\n null\n)\n\n/** One day. `events` are the ones on this day (for overlap layout); `children` renders each. */\nfunction SchedulerDayColumn<T extends EventLike>({\n date,\n events,\n className,\n children,\n onPointerDown,\n ...props\n}: Omit<React.ComponentProps<\"div\">, \"children\"> & {\n date: Date\n events: T[]\n children: (event: T) => React.ReactNode\n}) {\n const scheduler = useScheduler()\n const lanes = React.useMemo(() => layoutLanes(events), [events])\n const ctx = React.useMemo(() => ({ day: date, lanes }), [date, lanes])\n return (\n <ColumnContext.Provider value={ctx}>\n <div\n data-slot=\"scheduler-day-column\"\n data-date={date.toISOString()}\n data-today={isToday(date) ? \"\" : undefined}\n className={cn(\"relative border-e last:border-e-0 data-today:bg-primary/3\", className)}\n onPointerDown={(event) => {\n onPointerDown?.(event)\n if (event.defaultPrevented || event.button !== 0) return\n if ((event.target as HTMLElement).closest(\"[data-slot=scheduler-event]\")) return\n scheduler.startCreate(date, event, scheduler.days)\n }}\n {...props}\n >\n {events.map((event) => (\n <React.Fragment key={event.id}>{children(event)}</React.Fragment>\n ))}\n </div>\n </ColumnContext.Provider>\n )\n}\n\nconst EventContext = React.createContext<EventLike | null>(null)\n\nfunction SchedulerEvent({\n value,\n start,\n end,\n className,\n style,\n children,\n onPointerDown,\n onKeyDown,\n onBlur,\n ...props\n}: React.ComponentProps<\"div\"> & { value: string; start: Date; end: Date }) {\n const s = useScheduler()\n const column = React.useContext(ColumnContext)\n const live = s.interaction?.id === value ? s.interaction : null\n const shownStart = live ? live.start : start\n const shownEnd = live ? live.end : end\n // While a move crosses days the event stays in its original column but shifts visually.\n const dayOffset = column ? differenceInCalendarDays(startOfDay(shownStart), column.day) : 0\n const lane = column?.lanes.get(value) ?? { lane: 0, lanes: 1 }\n const top = s.timeToY(shownStart)\n const height = Math.max(s.hourHeight / 4, (differenceInMinutes(shownEnd, shownStart) * s.hourHeight) / 60)\n const isGrabbed = s.grabbed === value\n const eventLike = React.useMemo(() => ({ id: value, start, end }), [value, start, end])\n return (\n <EventContext.Provider value={eventLike}>\n <div\n {...props}\n data-slot=\"scheduler-event\"\n data-value={value}\n data-dragging={live?.type}\n data-grabbed={isGrabbed ? \"\" : undefined}\n tabIndex={s.editable ? 0 : undefined}\n aria-pressed={s.editable ? isGrabbed : undefined}\n aria-label={`${format(shownStart, \"EEE d, p\")} to ${format(shownEnd, \"p\")}`}\n className={cn(\n \"group/event absolute flex flex-col overflow-hidden rounded-md border border-primary/30 bg-primary/15 px-1.5 py-1 text-xs text-foreground outline-none select-none\",\n s.editable && \"cursor-grab active:cursor-grabbing\",\n \"focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-ring/50 data-dragging:z-20 data-dragging:shadow-lg data-grabbed:ring-2 data-grabbed:ring-primary\",\n className\n )}\n style={{\n top,\n height,\n left: `calc(${(lane.lane / lane.lanes) * 100}% + 2px)`,\n width: `calc(${100 / lane.lanes}% - 4px)`,\n transform: dayOffset ? `translateX(calc(${dayOffset * 100}% + ${dayOffset * 1}px))` : undefined,\n ...style,\n }}\n onPointerDown={(event) => {\n onPointerDown?.(event)\n if (event.defaultPrevented || event.button !== 0) return\n if ((event.target as HTMLElement).closest(\"[data-slot=scheduler-event-resize-handle], button, a\")) return\n s.startMove(eventLike, event, s.days)\n }}\n onKeyDown={(event) => {\n onKeyDown?.(event)\n if (event.defaultPrevented || !s.editable || event.target !== event.currentTarget) return\n if (event.key === \" \" || event.key === \"Enter\") {\n event.preventDefault()\n s.grab(isGrabbed ? null : value)\n return\n }\n if (!isGrabbed) return\n const step = s.slotMinutes\n if (event.key === \"ArrowUp\" || event.key === \"ArrowDown\") {\n event.preventDefault()\n const delta = event.key === \"ArrowUp\" ? -step : step\n s.nudge(eventLike, delta, 0, event.shiftKey ? \"end\" : \"both\")\n } else if (event.key === \"ArrowLeft\" || event.key === \"ArrowRight\") {\n event.preventDefault()\n s.nudge(eventLike, 0, event.key === \"ArrowLeft\" ? -1 : 1, \"both\")\n } else if (event.key === \"Escape\") {\n event.preventDefault()\n s.cancelGrab()\n }\n }}\n onBlur={(event) => {\n onBlur?.(event)\n if (isGrabbed && !event.currentTarget.contains(event.relatedTarget as Node | null)) s.grab(null)\n }}\n >\n {children}\n </div>\n </EventContext.Provider>\n )\n}\n\nfunction SchedulerEventTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div data-slot=\"scheduler-event-title\" className={cn(\"truncate font-medium\", className)} {...props} />\n}\n\n/** \"9:00 – 9:45 AM\"; reflects the live drag. */\nfunction SchedulerEventTime({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const s = useScheduler()\n const e = React.useContext(EventContext)\n if (!e) return null\n const live = s.interaction?.id === e.id ? s.interaction : null\n const start = live ? live.start : e.start\n const end = live ? live.end : e.end\n return (\n <div\n data-slot=\"scheduler-event-time\"\n className={cn(\"truncate text-[11px] tabular-nums opacity-80\", className)}\n {...props}\n >\n {children ?? `${format(start, \"h:mm\")} – ${format(end, \"h:mm a\")}`}\n </div>\n )\n}\n\nfunction SchedulerEventResizeHandle({ className, onPointerDown, ...props }: React.ComponentProps<\"div\">) {\n const s = useScheduler()\n const e = React.useContext(EventContext)\n if (!s.editable || !e) return null\n return (\n <div\n data-slot=\"scheduler-event-resize-handle\"\n role=\"separator\"\n aria-orientation=\"horizontal\"\n className={cn(\n \"absolute inset-x-0 bottom-0 h-2 cursor-ns-resize touch-none after:absolute after:inset-x-1/2 after:bottom-0.5 after:h-0.5 after:w-6 after:-translate-x-1/2 after:rounded-full after:bg-primary/50 after:opacity-0 group-hover/event:after:opacity-100\",\n className\n )}\n onPointerDown={(event) => {\n onPointerDown?.(event)\n if (event.defaultPrevented || event.button !== 0) return\n s.startResize(e, event)\n }}\n {...props}\n />\n )\n}\n\n/** Outline of the event being created by dragging; renders nothing when idle. */\nfunction SchedulerCreatePreview({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const s = useScheduler()\n if (!s.creating) return null\n const index = s.days.findIndex((d) => isSameDay(d, s.creating!.start))\n if (index === -1) return null\n const top = s.timeToY(s.creating.start)\n const height = (differenceInMinutes(s.creating.end, s.creating.start) * s.hourHeight) / 60\n return (\n <div\n data-slot=\"scheduler-create-preview\"\n aria-hidden\n className={cn(\n \"pointer-events-none absolute z-20 rounded-md border-2 border-dashed border-primary/60 bg-primary/10 px-1.5 py-1 text-[11px] text-primary tabular-nums\",\n className\n )}\n style={{\n top,\n height,\n left: `calc(${(index / s.days.length) * 100}% + 2px)`,\n width: `calc(${100 / s.days.length}% - 4px)`,\n }}\n {...props}\n >\n {children ?? `${format(s.creating.start, \"h:mm\")} – ${format(s.creating.end, \"h:mm a\")}`}\n </div>\n )\n}\n\n/** Red line at the current time in today's column. */\nfunction SchedulerNowIndicator({ className, ...props }: React.ComponentProps<\"div\">) {\n const s = useScheduler()\n const [now, setNow] = React.useState(() => new Date())\n React.useEffect(() => {\n const timer = setInterval(() => setNow(new Date()), 60_000)\n return () => clearInterval(timer)\n }, [])\n const index = s.days.findIndex((d) => isToday(d))\n if (index === -1) return null\n return (\n <div\n data-slot=\"scheduler-now-indicator\"\n aria-hidden\n className={cn(\"pointer-events-none absolute z-10 h-px bg-destructive\", className)}\n style={{ top: s.timeToY(now), left: `${(index / s.days.length) * 100}%`, width: `${100 / s.days.length}%` }}\n {...props}\n >\n <span className=\"absolute -top-1 -left-1 size-2 rounded-full bg-destructive\" />\n </div>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Month grid\n * ------------------------------------------------------------------------- */\n\nfunction SchedulerMonthGrid({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"scheduler-month-grid\"\n className={cn(\"flex min-h-0 flex-1 flex-col overflow-hidden rounded-xl border bg-background\", className)}\n {...props}\n />\n )\n}\n\nfunction SchedulerMonthHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n const { days } = useScheduler()\n return (\n <div\n data-slot=\"scheduler-month-header\"\n className={cn(\"grid grid-cols-7 border-b bg-muted/50 text-xs text-muted-foreground\", className)}\n {...props}\n >\n {days.slice(0, 7).map((d) => (\n <div key={d.toISOString()} className=\"border-e px-2 py-1.5 text-center font-medium last:border-e-0\">\n {format(d, \"EEE\")}\n </div>\n ))}\n </div>\n )\n}\n\nfunction SchedulerMonthBody({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const { days } = useScheduler()\n return (\n <div\n data-slot=\"scheduler-month-body\"\n className={cn(\"grid min-h-0 flex-1 grid-cols-7 overflow-y-auto\", className)}\n style={{ gridTemplateRows: `repeat(${Math.ceil(days.length / 7)}, minmax(6rem, 1fr))` }}\n {...props}\n >\n {children}\n </div>\n )\n}\n\nfunction SchedulerMonthCell({\n date,\n className,\n children,\n onClick,\n ...props\n}: React.ComponentProps<\"div\"> & { date: Date }) {\n const s = useScheduler()\n const outside = !isSameMonth(date, s.date)\n return (\n <div\n data-slot=\"scheduler-month-cell\"\n data-date={date.toISOString()}\n data-today={isToday(date) ? \"\" : undefined}\n data-outside={outside ? \"\" : undefined}\n className={cn(\n \"group/cell flex min-h-0 flex-col gap-0.5 border-e border-b p-1 text-xs data-outside:bg-muted/30 data-outside:text-muted-foreground nth-[7n]:border-e-0\",\n className\n )}\n onClick={(event) => {\n onClick?.(event)\n if (event.defaultPrevented) return\n if ((event.target as HTMLElement).closest(\"[data-slot=scheduler-month-event], button, a\")) return\n s.setDate(date)\n }}\n {...props}\n >\n <button\n type=\"button\"\n data-slot=\"scheduler-month-day\"\n className=\"mb-0.5 flex size-6 items-center justify-center self-start rounded-full text-xs font-medium outline-none group-data-today/cell:bg-primary group-data-today/cell:text-primary-foreground hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring/50\"\n onClick={(event) => {\n event.stopPropagation()\n s.setDate(date)\n s.setView(\"day\")\n }}\n >\n {format(date, \"d\")}\n </button>\n <div className=\"flex min-h-0 flex-1 flex-col gap-0.5 overflow-hidden\">{children}</div>\n </div>\n )\n}\n\n/** Compact chip for the month view; draggable between cells. */\nfunction SchedulerMonthEvent({\n value,\n start,\n end,\n className,\n children,\n onPointerDown,\n ...props\n}: React.ComponentProps<\"div\"> & { value: string; start: Date; end: Date }) {\n const s = useScheduler()\n const live = s.interaction?.id === value ? s.interaction : null\n const eventLike = React.useMemo(() => ({ id: value, start, end }), [value, start, end])\n return (\n <div\n {...props}\n data-slot=\"scheduler-month-event\"\n data-value={value}\n data-dragging={live ? \"\" : undefined}\n tabIndex={s.editable ? 0 : undefined}\n aria-label={`${format(start, \"EEE d, p\")}`}\n className={cn(\n \"flex cursor-grab items-center gap-1 truncate rounded-sm border border-primary/30 bg-primary/15 px-1.5 py-px text-[11px]/4 outline-none select-none focus-visible:ring-2 focus-visible:ring-ring/50 active:cursor-grabbing data-dragging:opacity-50\",\n className\n )}\n onPointerDown={(event) => {\n onPointerDown?.(event)\n if (event.defaultPrevented || event.button !== 0) return\n s.startMonthMove(eventLike, event)\n }}\n >\n {children}\n </div>\n )\n}\n\nfunction SchedulerMonthMore({ className, ...props }: React.ComponentProps<\"button\">) {\n return (\n <button\n type=\"button\"\n data-slot=\"scheduler-month-more\"\n className={cn(\n \"self-start rounded-sm px-1 text-[11px] text-muted-foreground hover:bg-muted hover:text-foreground\",\n className\n )}\n {...props}\n />\n )\n}\n\nexport {\n Scheduler,\n SchedulerToolbar,\n SchedulerPrevTrigger,\n SchedulerNextTrigger,\n SchedulerTodayTrigger,\n SchedulerTitle,\n SchedulerViewSelect,\n SchedulerTimeGrid,\n SchedulerTimeGridHeader,\n SchedulerDayHeadings,\n SchedulerAllDayRow,\n SchedulerTimeGridBody,\n SchedulerTimeGutter,\n SchedulerDayColumns,\n SchedulerDayColumn,\n SchedulerEvent,\n SchedulerEventTitle,\n SchedulerEventTime,\n SchedulerEventResizeHandle,\n SchedulerCreatePreview,\n SchedulerNowIndicator,\n SchedulerMonthGrid,\n SchedulerMonthHeader,\n SchedulerMonthBody,\n SchedulerMonthCell,\n SchedulerMonthEvent,\n SchedulerMonthMore,\n useScheduler,\n layoutLanes,\n visibleRange,\n type SchedulerView,\n type SchedulerEventChange,\n}\n"
1063
+ "content": "import * as React from \"react\"\nimport {\n addDays,\n addMinutes,\n addMonths,\n addWeeks,\n differenceInCalendarDays,\n differenceInMinutes,\n eachDayOfInterval,\n endOfMonth,\n endOfWeek,\n format,\n isSameDay,\n isSameMonth,\n isToday,\n setHours,\n setMinutes,\n startOfDay,\n startOfMonth,\n startOfWeek,\n} from \"date-fns\"\nimport { CalendarIcon, ChevronLeftIcon, ChevronRightIcon } from \"lucide-react\"\nimport { Button } from \"@/components/ui/button\"\nimport { SegmentGroup, SegmentGroupIndicator, SegmentGroupItem } from \"@/components/ui/segment-group\"\nimport { cn } from \"@/lib/utils\"\nimport { useControllable } from \"@/lib/controllable\"\nimport { LiveRegion, useLiveRegion } from \"@/components/ui/live-region\"\n\n/**\n * Scheduler — a compositional, data-agnostic calendar. The consumer owns the\n * events and renders them with the parts; the root owns the anchor `date`,\n * the `view` (day/week/month), the time-grid geometry and the interactions:\n * drag to move, drag the bottom edge to resize, drag on empty time to create,\n * keyboard moves. Everything is reported as intents (`onEventChange`,\n * `onCreate`) snapped to `slotMinutes`.\n *\n * Anatomy:\n * Scheduler { date, view, onEventChange, onCreate }\n * SchedulerToolbar > SchedulerPrevTrigger / SchedulerTodayTrigger / SchedulerNextTrigger / SchedulerTitle / SchedulerViewSelect\n * SchedulerTimeGrid (day, week)\n * SchedulerTimeGridHeader > SchedulerDayHeading date=… (+ SchedulerAllDayRow)\n * SchedulerTimeGridBody > SchedulerTimeGutter + SchedulerDayColumn date= events=\n * children: (event) => <SchedulerEvent value= start= end=> … <SchedulerEventResizeHandle/> </SchedulerEvent>\n * SchedulerNowIndicator, SchedulerCreatePreview\n * SchedulerMonthGrid > SchedulerMonthHeader + SchedulerMonthBody > SchedulerMonthCell date= > SchedulerMonthEvent value=…\n *\n * Keyboard on an event: Space/Enter picks up, ↑/↓ move by a slot, ←/→ move a\n * day, Shift+↑/↓ change the end, Escape cancels.\n */\n\ntype SchedulerView = \"day\" | \"week\" | \"month\"\ntype EventLike = { id: string; start: Date; end: Date; allDay?: boolean }\ntype SchedulerEventChange = { id: string; start: Date; end: Date }\ntype Interaction = { id: string; type: \"move\" | \"resize\"; start: Date; end: Date }\ntype CreateState = { start: Date; end: Date } | null\n\ntype SchedulerContextValue = {\n date: Date\n setDate: (date: Date) => void\n /** Controlled view: `day`, `week`, or `month`. */\n view: SchedulerView\n setView: (view: SchedulerView) => void\n range: { start: Date; end: Date }\n days: Date[]\n /** First day of the week, 0 for Sunday. */\n weekStartsOn: 0 | 1 | 2 | 3 | 4 | 5 | 6\n /** Pixels per hour in the time grid. */\n hourHeight: number\n /** Snap and keyboard step in minutes. */\n slotMinutes: number\n /** First hour shown in the time grid. */\n minHour: number\n /** Last hour shown in the time grid. */\n maxHour: number\n editable: boolean\n interaction: Interaction | null\n creating: CreateState\n grabbed: string | null\n timeToY: (date: Date) => number\n yToMinutes: (y: number) => number\n bodyRef: React.RefObject<HTMLDivElement | null>\n startMove: (event: EventLike, pointer: React.PointerEvent, columnDates: Date[]) => void\n startResize: (event: EventLike, pointer: React.PointerEvent) => void\n startCreate: (day: Date, pointer: React.PointerEvent, columnDates: Date[]) => void\n startMonthMove: (event: EventLike, pointer: React.PointerEvent) => void\n grab: (id: string | null) => void\n cancelGrab: () => void\n nudge: (event: EventLike, deltaMinutes: number, deltaDays: number, part: \"both\" | \"end\") => void\n announce: (message: string) => void\n}\n\nconst SchedulerContext = React.createContext<SchedulerContextValue | null>(null)\nfunction useScheduler() {\n const ctx = React.useContext(SchedulerContext)\n if (!ctx) throw new Error(\"Scheduler parts must be used within <Scheduler>\")\n return ctx\n}\n\nconst clamp = (v: number, min: number, max: number) => Math.min(max, Math.max(min, v))\nconst minutesOfDay = (d: Date) => d.getHours() * 60 + d.getMinutes()\nconst withMinutes = (day: Date, minutes: number) => setMinutes(setHours(startOfDay(day), 0), minutes)\n\nfunction visibleRange(date: Date, view: SchedulerView, weekStartsOn: SchedulerContextValue[\"weekStartsOn\"]) {\n if (view === \"day\") return { start: startOfDay(date), end: startOfDay(date) }\n if (view === \"week\") return { start: startOfWeek(date, { weekStartsOn }), end: endOfWeek(date, { weekStartsOn }) }\n return {\n /** Event start. */\n start: startOfWeek(startOfMonth(date), { weekStartsOn }),\n /** Event end. */\n end: endOfWeek(endOfMonth(date), { weekStartsOn }),\n }\n}\n\n/* ---------------------------------------------------------------------------\n * Root\n * ------------------------------------------------------------------------- */\n\nfunction Scheduler({\n date: dateProp,\n defaultDate,\n onDateChange,\n view: viewProp,\n defaultView = \"week\",\n onViewChange,\n weekStartsOn = 1,\n hourHeight = 56,\n slotMinutes = 15,\n minHour = 0,\n maxHour = 24,\n editable = true,\n onEventChange,\n onCreate,\n className,\n children,\n ...props\n}: Omit<React.ComponentProps<\"div\">, \"onChange\"> & {\n date?: Date\n /** Initial anchor date when uncontrolled. */\n defaultDate?: Date\n /** Called when the anchor date changes. */\n onDateChange?: (date: Date) => void\n /** Controlled view: `day`, `week`, or `month`. */\n view?: SchedulerView\n /** Initial view when uncontrolled. */\n defaultView?: SchedulerView\n /** Called when the view changes. */\n onViewChange?: (view: SchedulerView) => void\n /** First day of the week, 0 for Sunday. */\n weekStartsOn?: SchedulerContextValue[\"weekStartsOn\"]\n /** Pixels per hour in the time grid. */\n hourHeight?: number\n /** Snap and keyboard step in minutes. */\n slotMinutes?: number\n /** First hour shown in the time grid. */\n minHour?: number\n /** Last hour shown in the time grid. */\n maxHour?: number\n editable?: boolean\n /** Called with `{ id, start, end }` snapped to `slotMinutes` after a move, resize, or keyboard change. */\n onEventChange?: (change: SchedulerEventChange) => void\n /** Drag on empty time (or a month cell click) proposes a new event. */\n onCreate?: (range: { start: Date; end: Date; allDay?: boolean }) => void\n}) {\n const [initialDate] = React.useState(() => startOfDay(defaultDate ?? new Date()))\n const [date, setDate] = useControllable(dateProp, initialDate, onDateChange)\n const [view, setView] = useControllable<SchedulerView>(viewProp, defaultView, onViewChange)\n\n const range = React.useMemo(() => visibleRange(date, view, weekStartsOn), [date, view, weekStartsOn])\n const days = React.useMemo(() => eachDayOfInterval(range), [range])\n\n const bodyRef = React.useRef<HTMLDivElement>(null)\n const minuteHeight = hourHeight / 60\n const timeToY = React.useCallback(\n (d: Date) => (minutesOfDay(d) - minHour * 60) * minuteHeight,\n [minHour, minuteHeight]\n )\n const yToMinutes = React.useCallback(\n (y: number) =>\n clamp(Math.round((y / minuteHeight + minHour * 60) / slotMinutes) * slotMinutes, minHour * 60, maxHour * 60),\n [minuteHeight, minHour, maxHour, slotMinutes]\n )\n\n const [interaction, setInteraction] = React.useState<Interaction | null>(null)\n const [creating, setCreating] = React.useState<CreateState>(null)\n const [grabbed, setGrabbed] = React.useState<string | null>(null)\n const { message: announcement, announce } = useLiveRegion()\n const onEventChangeRef = React.useRef(onEventChange)\n const onCreateRef = React.useRef(onCreate)\n React.useEffect(() => {\n onEventChangeRef.current = onEventChange\n onCreateRef.current = onCreate\n }, [onEventChange, onCreate])\n const grabOrigin = React.useRef<SchedulerEventChange | null>(null)\n\n const track = React.useCallback((onMove: (e: PointerEvent) => void, onUp: (e: PointerEvent) => void) => {\n const up = (e: PointerEvent) => {\n window.removeEventListener(\"pointermove\", onMove)\n window.removeEventListener(\"pointerup\", up)\n window.removeEventListener(\"pointercancel\", up)\n onUp(e)\n }\n window.addEventListener(\"pointermove\", onMove)\n window.addEventListener(\"pointerup\", up)\n window.addEventListener(\"pointercancel\", up)\n }, [])\n\n /** Which day column a client x falls in, from the column elements' rects. */\n const dayAtX = React.useCallback((x: number, columnDates: Date[]) => {\n const columns = Array.from(bodyRef.current?.querySelectorAll<HTMLElement>(\"[data-slot=scheduler-day-column]\") ?? [])\n for (const [i, col] of columns.entries()) {\n const r = col.getBoundingClientRect()\n if (x >= r.left && x < r.right) return columnDates[i] ?? null\n }\n return null\n }, [])\n\n const startMove = React.useCallback(\n (event: EventLike, pointer: React.PointerEvent, columnDates: Date[]) => {\n if (!editable) return\n pointer.preventDefault()\n pointer.stopPropagation()\n const originY = pointer.clientY\n const originDay = startOfDay(event.start)\n let current: Interaction = { id: event.id, type: \"move\", start: event.start, end: event.end }\n let moved = false\n setInteraction(current)\n track(\n (e) => {\n const dy = e.clientY - originY\n const deltaMinutes = Math.round(dy / minuteHeight / slotMinutes) * slotMinutes\n const day = dayAtX(e.clientX, columnDates) ?? originDay\n const dayDelta = differenceInCalendarDays(day, originDay)\n const duration = differenceInMinutes(event.end, event.start)\n const startMin = clamp(minutesOfDay(event.start) + deltaMinutes, minHour * 60, maxHour * 60 - duration)\n const start = addDays(withMinutes(originDay, startMin), dayDelta)\n current = { id: event.id, type: \"move\", start, end: addMinutes(start, duration) }\n if (Math.abs(dy) > 3 || dayDelta !== 0) moved = true\n setInteraction(current)\n },\n () => {\n setInteraction(null)\n if (\n moved &&\n (current.start.getTime() !== event.start.getTime() || current.end.getTime() !== event.end.getTime())\n ) {\n onEventChangeRef.current?.({ id: event.id, start: current.start, end: current.end })\n }\n }\n )\n },\n [editable, track, dayAtX, minuteHeight, slotMinutes, minHour, maxHour]\n )\n\n const startResize = React.useCallback(\n (event: EventLike, pointer: React.PointerEvent) => {\n if (!editable) return\n pointer.preventDefault()\n pointer.stopPropagation()\n const originY = pointer.clientY\n let current: Interaction = { id: event.id, type: \"resize\", start: event.start, end: event.end }\n setInteraction(current)\n track(\n (e) => {\n const deltaMinutes = Math.round((e.clientY - originY) / minuteHeight / slotMinutes) * slotMinutes\n const endMin = clamp(\n minutesOfDay(event.end) + deltaMinutes,\n minutesOfDay(event.start) + slotMinutes,\n maxHour * 60\n )\n current = { ...current, end: withMinutes(event.start, endMin) }\n setInteraction(current)\n },\n () => {\n setInteraction(null)\n if (current.end.getTime() !== event.end.getTime()) {\n onEventChangeRef.current?.({ id: event.id, start: event.start, end: current.end })\n }\n }\n )\n },\n [editable, track, minuteHeight, slotMinutes, maxHour]\n )\n\n const startCreate = React.useCallback(\n (day: Date, pointer: React.PointerEvent, columnDates: Date[]) => {\n if (!editable || !onCreateRef.current) return\n const column = (pointer.currentTarget as HTMLElement).getBoundingClientRect()\n const originMin = yToMinutes(pointer.clientY - column.top)\n let current: NonNullable<CreateState> = {\n /** Event start. */\n start: withMinutes(day, originMin),\n /** Event end. */\n end: withMinutes(day, originMin + slotMinutes),\n }\n let dragged = false\n setCreating(current)\n track(\n (e) => {\n const min = yToMinutes(e.clientY - column.top)\n const targetDay = dayAtX(e.clientX, columnDates) ?? day\n const a = Math.min(originMin, min)\n const b = Math.max(originMin, min, a + slotMinutes)\n current = { start: withMinutes(targetDay, a), end: withMinutes(targetDay, b) }\n dragged = true\n setCreating(current)\n },\n () => {\n setCreating(null)\n onCreateRef.current?.(dragged ? current : { start: current.start, end: addMinutes(current.start, 60) })\n }\n )\n },\n [editable, track, yToMinutes, slotMinutes, dayAtX]\n )\n\n /** Month view: drag a chip onto another cell to shift the event by whole days. */\n const startMonthMove = React.useCallback(\n (event: EventLike, pointer: React.PointerEvent) => {\n if (!editable) return\n pointer.preventDefault()\n pointer.stopPropagation()\n const originDay = startOfDay(event.start)\n let current: Interaction = { id: event.id, type: \"move\", start: event.start, end: event.end }\n let moved = false\n setInteraction(current)\n const cellAt = (x: number, y: number) =>\n document.elementFromPoint(x, y)?.closest<HTMLElement>(\"[data-slot=scheduler-month-cell]\")?.dataset.date\n track(\n (e) => {\n const target = cellAt(e.clientX, e.clientY)\n if (!target) return\n const delta = differenceInCalendarDays(new Date(target), originDay)\n current = { ...current, start: addDays(event.start, delta), end: addDays(event.end, delta) }\n moved = true\n setInteraction(current)\n },\n () => {\n setInteraction(null)\n if (moved && current.start.getTime() !== event.start.getTime()) {\n onEventChangeRef.current?.({ id: event.id, start: current.start, end: current.end })\n }\n }\n )\n },\n [editable, track]\n )\n\n const grab = React.useCallback(\n (id: string | null) => {\n if (id) {\n setGrabbed(id)\n announce(\"Picked up. Arrows move; Shift+Up/Down change the end. Space drops, Escape cancels.\")\n } else {\n setGrabbed(null)\n grabOrigin.current = null\n announce(\"Dropped.\")\n }\n },\n [announce]\n )\n const cancelGrab = React.useCallback(() => {\n if (grabOrigin.current) onEventChangeRef.current?.(grabOrigin.current)\n setGrabbed(null)\n grabOrigin.current = null\n announce(\"Cancelled.\")\n }, [announce])\n\n const nudge = React.useCallback(\n (event: EventLike, deltaMinutes: number, deltaDays: number, part: \"both\" | \"end\") => {\n if (!grabOrigin.current || grabOrigin.current.id !== event.id) {\n grabOrigin.current = { id: event.id, start: event.start, end: event.end }\n }\n const next =\n part === \"end\"\n ? {\n /** Event start. */\n start: event.start,\n /** Event end. */\n end: addMinutes(\n event.end,\n Math.max(deltaMinutes, slotMinutes - differenceInMinutes(event.end, event.start))\n ),\n }\n : {\n /** Event start. */\n start: addDays(addMinutes(event.start, deltaMinutes), deltaDays),\n /** Event end. */\n end: addDays(addMinutes(event.end, deltaMinutes), deltaDays),\n }\n onEventChangeRef.current?.({ id: event.id, ...next })\n announce(`${format(next.start, \"EEE d, p\")} to ${format(next.end, \"p\")}.`)\n },\n [announce, slotMinutes]\n )\n\n const ctx = React.useMemo<SchedulerContextValue>(\n () => ({\n date,\n setDate,\n view,\n setView,\n range,\n days,\n weekStartsOn,\n hourHeight,\n slotMinutes,\n minHour,\n maxHour,\n editable,\n interaction,\n creating,\n grabbed,\n timeToY,\n yToMinutes,\n bodyRef,\n startMove,\n startResize,\n startCreate,\n startMonthMove,\n grab,\n cancelGrab,\n nudge,\n announce,\n }),\n [\n date,\n setDate,\n view,\n setView,\n range,\n days,\n weekStartsOn,\n hourHeight,\n slotMinutes,\n minHour,\n maxHour,\n editable,\n interaction,\n creating,\n grabbed,\n timeToY,\n yToMinutes,\n startMove,\n startResize,\n startCreate,\n startMonthMove,\n grab,\n cancelGrab,\n nudge,\n announce,\n ]\n )\n\n return (\n <SchedulerContext.Provider value={ctx}>\n <div\n data-slot=\"scheduler\"\n data-view={view}\n data-interacting={interaction?.type ?? (creating ? \"create\" : undefined)}\n className={cn(\"flex min-h-0 flex-1 flex-col gap-3\", className)}\n {...props}\n >\n {children}\n <LiveRegion data-slot=\"scheduler-live-region\" message={announcement} />\n </div>\n </SchedulerContext.Provider>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Toolbar\n * ------------------------------------------------------------------------- */\n\nfunction SchedulerToolbar({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div data-slot=\"scheduler-toolbar\" className={cn(\"flex flex-wrap items-center gap-2\", className)} {...props} />\n}\n\nfunction shift(date: Date, view: SchedulerView, direction: 1 | -1) {\n if (view === \"day\") return addDays(date, direction)\n if (view === \"week\") return addWeeks(date, direction)\n return addMonths(date, direction)\n}\n\nfunction SchedulerPrevTrigger({ asChild, children, onClick, ...props }: React.ComponentProps<typeof Button>) {\n const { date, view, setDate } = useScheduler()\n return (\n <Button\n data-slot=\"scheduler-prev-trigger\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label={`Previous ${view}`}\n asChild={asChild}\n onClick={(event) => {\n onClick?.(event)\n if (!event.defaultPrevented) setDate(shift(date, view, -1))\n }}\n {...props}\n >\n {asChild ? children : (children ?? <ChevronLeftIcon />)}\n </Button>\n )\n}\n\nfunction SchedulerNextTrigger({ asChild, children, onClick, ...props }: React.ComponentProps<typeof Button>) {\n const { date, view, setDate } = useScheduler()\n return (\n <Button\n data-slot=\"scheduler-next-trigger\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label={`Next ${view}`}\n asChild={asChild}\n onClick={(event) => {\n onClick?.(event)\n if (!event.defaultPrevented) setDate(shift(date, view, 1))\n }}\n {...props}\n >\n {asChild ? children : (children ?? <ChevronRightIcon />)}\n </Button>\n )\n}\n\nfunction SchedulerTodayTrigger({ asChild, children, onClick, ...props }: React.ComponentProps<typeof Button>) {\n const { setDate } = useScheduler()\n return (\n <Button\n data-slot=\"scheduler-today-trigger\"\n variant=\"outline\"\n size=\"sm\"\n asChild={asChild}\n onClick={(event) => {\n onClick?.(event)\n if (!event.defaultPrevented) setDate(startOfDay(new Date()))\n }}\n {...props}\n >\n {asChild\n ? children\n : (children ?? (\n <>\n <CalendarIcon /> Today\n </>\n ))}\n </Button>\n )\n}\n\n/** \"September 2026\", \"Sep 7 – 13, 2026\" or \"Mon, Sep 7\". */\nfunction SchedulerTitle({ className, children, ...props }: React.ComponentProps<\"h2\">) {\n const { date, view, range } = useScheduler()\n const text =\n view === \"month\"\n ? format(date, \"MMMM yyyy\")\n : view === \"day\"\n ? format(date, \"EEEE, MMM d, yyyy\")\n : isSameMonth(range.start, range.end)\n ? `${format(range.start, \"MMM d\")} – ${format(range.end, \"d, yyyy\")}`\n : `${format(range.start, \"MMM d\")} – ${format(range.end, \"MMM d, yyyy\")}`\n return (\n <h2 data-slot=\"scheduler-title\" className={cn(\"text-base font-semibold tabular-nums\", className)} {...props}>\n {children ?? text}\n </h2>\n )\n}\n\nfunction SchedulerViewSelect({\n className,\n ...props\n}: Omit<React.ComponentProps<typeof SegmentGroup>, \"value\" | \"onValueChange\">) {\n const { view, setView } = useScheduler()\n return (\n <SegmentGroup\n data-slot=\"scheduler-view-select\"\n value={view}\n onValueChange={({ value }) => value && setView(value as SchedulerView)}\n className={className}\n {...props}\n >\n <SegmentGroupIndicator />\n <SegmentGroupItem value=\"day\">Day</SegmentGroupItem>\n <SegmentGroupItem value=\"week\">Week</SegmentGroupItem>\n <SegmentGroupItem value=\"month\">Month</SegmentGroupItem>\n </SegmentGroup>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Time grid (day / week)\n * ------------------------------------------------------------------------- */\n\nconst GUTTER = \"3.5rem\"\n\nfunction SchedulerTimeGrid({ className, style, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"scheduler-time-grid\"\n className={cn(\"flex min-h-0 flex-1 flex-col overflow-hidden rounded-xl border bg-background\", className)}\n style={{ [\"--scheduler-gutter\" as string]: GUTTER, ...style }}\n {...props}\n />\n )\n}\n\nfunction SchedulerTimeGridHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"scheduler-time-grid-header\"\n className={cn(\"grid shrink-0 border-b bg-muted/50\", className)}\n style={{ gridTemplateColumns: \"var(--scheduler-gutter) 1fr\" }}\n {...props}\n />\n )\n}\n\n/** Day headings across the top, one per visible day. */\nfunction SchedulerDayHeadings({\n className,\n children,\n ...props\n}: React.ComponentProps<\"div\"> & { children?: (day: Date) => React.ReactNode }) {\n const { days, setDate, setView } = useScheduler()\n return (\n <>\n <div aria-hidden className=\"border-e\" />\n <div\n data-slot=\"scheduler-day-headings\"\n className={cn(\"grid\", className)}\n style={{ gridTemplateColumns: `repeat(${days.length}, minmax(0, 1fr))` }}\n {...props}\n >\n {days.map((day) => (\n <button\n key={day.toISOString()}\n type=\"button\"\n data-slot=\"scheduler-day-heading\"\n data-today={isToday(day) ? \"\" : undefined}\n className=\"group/heading flex flex-col items-center gap-0.5 border-e py-2 text-xs text-muted-foreground outline-none last:border-e-0 hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-inset\"\n onClick={() => {\n setDate(day)\n setView(\"day\")\n }}\n >\n {children ? (\n children(day)\n ) : (\n <>\n <span>{format(day, \"EEE\")}</span>\n <span className=\"flex size-7 items-center justify-center rounded-full text-base font-semibold text-foreground group-data-today/heading:bg-primary group-data-today/heading:text-primary-foreground\">\n {format(day, \"d\")}\n </span>\n </>\n )}\n </button>\n ))}\n </div>\n </>\n )\n}\n\n/** Strip under the headings for all-day events; render chips inside per day. */\nfunction SchedulerAllDayRow({\n className,\n children,\n ...props\n}: Omit<React.ComponentProps<\"div\">, \"children\"> & { children: (day: Date) => React.ReactNode }) {\n const { days } = useScheduler()\n return (\n <>\n <div className=\"flex items-start justify-end border-e border-t px-1.5 py-1 text-[10px] text-muted-foreground uppercase\">\n all day\n </div>\n <div\n data-slot=\"scheduler-all-day-row\"\n className={cn(\"grid border-t\", className)}\n style={{ gridTemplateColumns: `repeat(${days.length}, minmax(0, 1fr))` }}\n {...props}\n >\n {days.map((day) => (\n <div\n key={day.toISOString()}\n data-slot=\"scheduler-all-day-cell\"\n className=\"flex min-h-7 flex-col gap-0.5 border-e p-0.5 last:border-e-0\"\n >\n {children(day)}\n </div>\n ))}\n </div>\n </>\n )\n}\n\nfunction SchedulerTimeGridBody({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const { bodyRef, hourHeight, minHour, maxHour } = useScheduler()\n // Scroll to a sensible hour on mount.\n React.useEffect(() => {\n const el = bodyRef.current\n if (el) el.scrollTop = Math.max(0, (8 - minHour) * hourHeight - 8)\n }, [bodyRef, hourHeight, minHour])\n return (\n <div\n ref={bodyRef}\n data-slot=\"scheduler-time-grid-body\"\n className={cn(\"relative min-h-0 flex-1 overflow-y-auto\", className)}\n {...props}\n >\n <div\n className=\"grid\"\n style={{ gridTemplateColumns: \"var(--scheduler-gutter) 1fr\", height: (maxHour - minHour) * hourHeight }}\n >\n {children}\n </div>\n </div>\n )\n}\n\nfunction SchedulerTimeGutter({ className, ...props }: React.ComponentProps<\"div\">) {\n const { hourHeight, minHour, maxHour } = useScheduler()\n const hours = Array.from({ length: maxHour - minHour }, (_, i) => minHour + i)\n return (\n <div data-slot=\"scheduler-time-gutter\" className={cn(\"relative border-e\", className)} {...props}>\n {hours.map((h) => (\n <div\n key={h}\n className=\"absolute inset-e-1.5 -translate-y-1/2 text-[11px] text-muted-foreground tabular-nums\"\n style={{ top: (h - minHour) * hourHeight }}\n >\n {h > minHour ? format(setHours(new Date(), h), \"h a\") : \"\"}\n </div>\n ))}\n </div>\n )\n}\n\n/** The columns container; hour lines are drawn behind. */\nfunction SchedulerDayColumns({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const { days, hourHeight, minHour, maxHour } = useScheduler()\n const hours = Array.from({ length: maxHour - minHour }, (_, i) => i)\n return (\n <div\n data-slot=\"scheduler-day-columns\"\n className={cn(\"relative grid\", className)}\n style={{ gridTemplateColumns: `repeat(${days.length}, minmax(0, 1fr))` }}\n {...props}\n >\n <div aria-hidden className=\"pointer-events-none absolute inset-0\">\n {hours.map((i) => (\n <div key={i} className=\"absolute inset-x-0 border-t border-border/70\" style={{ top: i * hourHeight }} />\n ))}\n </div>\n {children}\n </div>\n )\n}\n\n/** Side-by-side lanes for overlapping events. */\nfunction layoutLanes(events: EventLike[]) {\n const sorted = [...events]\n .filter((e) => !e.allDay)\n .sort((a, b) => a.start.getTime() - b.start.getTime() || b.end.getTime() - a.end.getTime())\n const result = new Map<string, { lane: number; lanes: number }>()\n let cluster: EventLike[] = []\n let clusterEnd = 0\n const flush = () => {\n const laneEnds: number[] = []\n const assigned: { id: string; lane: number }[] = []\n for (const e of cluster) {\n let lane = laneEnds.findIndex((end) => end <= e.start.getTime())\n if (lane === -1) lane = laneEnds.length\n laneEnds[lane] = e.end.getTime()\n assigned.push({ id: e.id, lane })\n }\n for (const a of assigned) result.set(a.id, { lane: a.lane, lanes: laneEnds.length })\n cluster = []\n clusterEnd = 0\n }\n for (const e of sorted) {\n if (cluster.length && e.start.getTime() >= clusterEnd) flush()\n cluster.push(e)\n clusterEnd = Math.max(clusterEnd, e.end.getTime())\n }\n if (cluster.length) flush()\n return result\n}\n\nconst ColumnContext = React.createContext<{ day: Date; lanes: Map<string, { lane: number; lanes: number }> } | null>(\n null\n)\n\n/** One day. `events` are the ones on this day (for overlap layout); `children` renders each. */\nfunction SchedulerDayColumn<T extends EventLike>({\n date,\n events,\n className,\n children,\n onPointerDown,\n ...props\n}: Omit<React.ComponentProps<\"div\">, \"children\"> & {\n date: Date\n /** Events shown in this column; overlaps are laid out side by side. */\n events: T[]\n children: (event: T) => React.ReactNode\n}) {\n const scheduler = useScheduler()\n const lanes = React.useMemo(() => layoutLanes(events), [events])\n const ctx = React.useMemo(() => ({ day: date, lanes }), [date, lanes])\n return (\n <ColumnContext.Provider value={ctx}>\n <div\n data-slot=\"scheduler-day-column\"\n data-date={date.toISOString()}\n data-today={isToday(date) ? \"\" : undefined}\n className={cn(\"relative border-e last:border-e-0 data-today:bg-primary/3\", className)}\n onPointerDown={(event) => {\n onPointerDown?.(event)\n if (event.defaultPrevented || event.button !== 0) return\n if ((event.target as HTMLElement).closest(\"[data-slot=scheduler-event]\")) return\n scheduler.startCreate(date, event, scheduler.days)\n }}\n {...props}\n >\n {events.map((event) => (\n <React.Fragment key={event.id}>{children(event)}</React.Fragment>\n ))}\n </div>\n </ColumnContext.Provider>\n )\n}\n\nconst EventContext = React.createContext<EventLike | null>(null)\n\nfunction SchedulerEvent({\n value,\n start,\n end,\n className,\n style,\n children,\n onPointerDown,\n onKeyDown,\n onBlur,\n ...props\n}: React.ComponentProps<\"div\"> & { value: string; start: Date; end: Date }) {\n const s = useScheduler()\n const column = React.useContext(ColumnContext)\n const live = s.interaction?.id === value ? s.interaction : null\n const shownStart = live ? live.start : start\n const shownEnd = live ? live.end : end\n // While a move crosses days the event stays in its original column but shifts visually.\n const dayOffset = column ? differenceInCalendarDays(startOfDay(shownStart), column.day) : 0\n const lane = column?.lanes.get(value) ?? { lane: 0, lanes: 1 }\n const top = s.timeToY(shownStart)\n const height = Math.max(s.hourHeight / 4, (differenceInMinutes(shownEnd, shownStart) * s.hourHeight) / 60)\n const isGrabbed = s.grabbed === value\n const eventLike = React.useMemo(() => ({ id: value, start, end }), [value, start, end])\n return (\n <EventContext.Provider value={eventLike}>\n <div\n {...props}\n data-slot=\"scheduler-event\"\n data-value={value}\n data-dragging={live?.type}\n data-grabbed={isGrabbed ? \"\" : undefined}\n tabIndex={s.editable ? 0 : undefined}\n aria-pressed={s.editable ? isGrabbed : undefined}\n aria-label={`${format(shownStart, \"EEE d, p\")} to ${format(shownEnd, \"p\")}`}\n className={cn(\n \"group/event absolute flex flex-col overflow-hidden rounded-md border border-primary/30 bg-primary/15 px-1.5 py-1 text-xs text-foreground outline-none select-none\",\n s.editable && \"cursor-grab active:cursor-grabbing\",\n \"focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-ring/50 data-dragging:z-20 data-dragging:shadow-lg data-grabbed:ring-2 data-grabbed:ring-primary\",\n className\n )}\n style={{\n top,\n height,\n left: `calc(${(lane.lane / lane.lanes) * 100}% + 2px)`,\n width: `calc(${100 / lane.lanes}% - 4px)`,\n transform: dayOffset ? `translateX(calc(${dayOffset * 100}% + ${dayOffset * 1}px))` : undefined,\n ...style,\n }}\n onPointerDown={(event) => {\n onPointerDown?.(event)\n if (event.defaultPrevented || event.button !== 0) return\n if ((event.target as HTMLElement).closest(\"[data-slot=scheduler-event-resize-handle], button, a\")) return\n s.startMove(eventLike, event, s.days)\n }}\n onKeyDown={(event) => {\n onKeyDown?.(event)\n if (event.defaultPrevented || !s.editable || event.target !== event.currentTarget) return\n if (event.key === \" \" || event.key === \"Enter\") {\n event.preventDefault()\n s.grab(isGrabbed ? null : value)\n return\n }\n if (!isGrabbed) return\n const step = s.slotMinutes\n if (event.key === \"ArrowUp\" || event.key === \"ArrowDown\") {\n event.preventDefault()\n const delta = event.key === \"ArrowUp\" ? -step : step\n s.nudge(eventLike, delta, 0, event.shiftKey ? \"end\" : \"both\")\n } else if (event.key === \"ArrowLeft\" || event.key === \"ArrowRight\") {\n event.preventDefault()\n s.nudge(eventLike, 0, event.key === \"ArrowLeft\" ? -1 : 1, \"both\")\n } else if (event.key === \"Escape\") {\n event.preventDefault()\n s.cancelGrab()\n }\n }}\n onBlur={(event) => {\n onBlur?.(event)\n if (isGrabbed && !event.currentTarget.contains(event.relatedTarget as Node | null)) s.grab(null)\n }}\n >\n {children}\n </div>\n </EventContext.Provider>\n )\n}\n\nfunction SchedulerEventTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div data-slot=\"scheduler-event-title\" className={cn(\"truncate font-medium\", className)} {...props} />\n}\n\n/** \"9:00 – 9:45 AM\"; reflects the live drag. */\nfunction SchedulerEventTime({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const s = useScheduler()\n const e = React.useContext(EventContext)\n if (!e) return null\n const live = s.interaction?.id === e.id ? s.interaction : null\n const start = live ? live.start : e.start\n const end = live ? live.end : e.end\n return (\n <div\n data-slot=\"scheduler-event-time\"\n className={cn(\"truncate text-[11px] tabular-nums opacity-80\", className)}\n {...props}\n >\n {children ?? `${format(start, \"h:mm\")} – ${format(end, \"h:mm a\")}`}\n </div>\n )\n}\n\nfunction SchedulerEventResizeHandle({ className, onPointerDown, ...props }: React.ComponentProps<\"div\">) {\n const s = useScheduler()\n const e = React.useContext(EventContext)\n if (!s.editable || !e) return null\n return (\n <div\n data-slot=\"scheduler-event-resize-handle\"\n role=\"separator\"\n aria-orientation=\"horizontal\"\n className={cn(\n \"absolute inset-x-0 bottom-0 h-2 cursor-ns-resize touch-none after:absolute after:inset-x-1/2 after:bottom-0.5 after:h-0.5 after:w-6 after:-translate-x-1/2 after:rounded-full after:bg-primary/50 after:opacity-0 group-hover/event:after:opacity-100\",\n className\n )}\n onPointerDown={(event) => {\n onPointerDown?.(event)\n if (event.defaultPrevented || event.button !== 0) return\n s.startResize(e, event)\n }}\n {...props}\n />\n )\n}\n\n/** Outline of the event being created by dragging; renders nothing when idle. */\nfunction SchedulerCreatePreview({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const s = useScheduler()\n if (!s.creating) return null\n const index = s.days.findIndex((d) => isSameDay(d, s.creating!.start))\n if (index === -1) return null\n const top = s.timeToY(s.creating.start)\n const height = (differenceInMinutes(s.creating.end, s.creating.start) * s.hourHeight) / 60\n return (\n <div\n data-slot=\"scheduler-create-preview\"\n aria-hidden\n className={cn(\n \"pointer-events-none absolute z-20 rounded-md border-2 border-dashed border-primary/60 bg-primary/10 px-1.5 py-1 text-[11px] text-primary tabular-nums\",\n className\n )}\n style={{\n top,\n height,\n left: `calc(${(index / s.days.length) * 100}% + 2px)`,\n width: `calc(${100 / s.days.length}% - 4px)`,\n }}\n {...props}\n >\n {children ?? `${format(s.creating.start, \"h:mm\")} – ${format(s.creating.end, \"h:mm a\")}`}\n </div>\n )\n}\n\n/** Red line at the current time in today's column. */\nfunction SchedulerNowIndicator({ className, ...props }: React.ComponentProps<\"div\">) {\n const s = useScheduler()\n const [now, setNow] = React.useState(() => new Date())\n React.useEffect(() => {\n const timer = setInterval(() => setNow(new Date()), 60_000)\n return () => clearInterval(timer)\n }, [])\n const index = s.days.findIndex((d) => isToday(d))\n if (index === -1) return null\n return (\n <div\n data-slot=\"scheduler-now-indicator\"\n aria-hidden\n className={cn(\"pointer-events-none absolute z-10 h-px bg-destructive\", className)}\n style={{ top: s.timeToY(now), left: `${(index / s.days.length) * 100}%`, width: `${100 / s.days.length}%` }}\n {...props}\n >\n <span className=\"absolute -top-1 -left-1 size-2 rounded-full bg-destructive\" />\n </div>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Month grid\n * ------------------------------------------------------------------------- */\n\nfunction SchedulerMonthGrid({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"scheduler-month-grid\"\n className={cn(\"flex min-h-0 flex-1 flex-col overflow-hidden rounded-xl border bg-background\", className)}\n {...props}\n />\n )\n}\n\nfunction SchedulerMonthHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n const { days } = useScheduler()\n return (\n <div\n data-slot=\"scheduler-month-header\"\n className={cn(\"grid grid-cols-7 border-b bg-muted/50 text-xs text-muted-foreground\", className)}\n {...props}\n >\n {days.slice(0, 7).map((d) => (\n <div key={d.toISOString()} className=\"border-e px-2 py-1.5 text-center font-medium last:border-e-0\">\n {format(d, \"EEE\")}\n </div>\n ))}\n </div>\n )\n}\n\nfunction SchedulerMonthBody({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const { days } = useScheduler()\n return (\n <div\n data-slot=\"scheduler-month-body\"\n className={cn(\"grid min-h-0 flex-1 grid-cols-7 overflow-y-auto\", className)}\n style={{ gridTemplateRows: `repeat(${Math.ceil(days.length / 7)}, minmax(6rem, 1fr))` }}\n {...props}\n >\n {children}\n </div>\n )\n}\n\nfunction SchedulerMonthCell({\n date,\n className,\n children,\n onClick,\n ...props\n}: React.ComponentProps<\"div\"> & { date: Date }) {\n const s = useScheduler()\n const outside = !isSameMonth(date, s.date)\n return (\n <div\n data-slot=\"scheduler-month-cell\"\n data-date={date.toISOString()}\n data-today={isToday(date) ? \"\" : undefined}\n data-outside={outside ? \"\" : undefined}\n className={cn(\n \"group/cell flex min-h-0 flex-col gap-0.5 border-e border-b p-1 text-xs data-outside:bg-muted/30 data-outside:text-muted-foreground nth-[7n]:border-e-0\",\n className\n )}\n onClick={(event) => {\n onClick?.(event)\n if (event.defaultPrevented) return\n if ((event.target as HTMLElement).closest(\"[data-slot=scheduler-month-event], button, a\")) return\n s.setDate(date)\n }}\n {...props}\n >\n <button\n type=\"button\"\n data-slot=\"scheduler-month-day\"\n className=\"mb-0.5 flex size-6 items-center justify-center self-start rounded-full text-xs font-medium outline-none group-data-today/cell:bg-primary group-data-today/cell:text-primary-foreground hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring/50\"\n onClick={(event) => {\n event.stopPropagation()\n s.setDate(date)\n s.setView(\"day\")\n }}\n >\n {format(date, \"d\")}\n </button>\n <div className=\"flex min-h-0 flex-1 flex-col gap-0.5 overflow-hidden\">{children}</div>\n </div>\n )\n}\n\n/** Compact chip for the month view; draggable between cells. */\nfunction SchedulerMonthEvent({\n value,\n start,\n end,\n className,\n children,\n onPointerDown,\n ...props\n}: React.ComponentProps<\"div\"> & { value: string; start: Date; end: Date }) {\n const s = useScheduler()\n const live = s.interaction?.id === value ? s.interaction : null\n const eventLike = React.useMemo(() => ({ id: value, start, end }), [value, start, end])\n return (\n <div\n {...props}\n data-slot=\"scheduler-month-event\"\n data-value={value}\n data-dragging={live ? \"\" : undefined}\n tabIndex={s.editable ? 0 : undefined}\n aria-label={`${format(start, \"EEE d, p\")}`}\n className={cn(\n \"flex cursor-grab items-center gap-1 truncate rounded-sm border border-primary/30 bg-primary/15 px-1.5 py-px text-[11px]/4 outline-none select-none focus-visible:ring-2 focus-visible:ring-ring/50 active:cursor-grabbing data-dragging:opacity-50\",\n className\n )}\n onPointerDown={(event) => {\n onPointerDown?.(event)\n if (event.defaultPrevented || event.button !== 0) return\n s.startMonthMove(eventLike, event)\n }}\n >\n {children}\n </div>\n )\n}\n\nfunction SchedulerMonthMore({ className, ...props }: React.ComponentProps<\"button\">) {\n return (\n <button\n type=\"button\"\n data-slot=\"scheduler-month-more\"\n className={cn(\n \"self-start rounded-sm px-1 text-[11px] text-muted-foreground hover:bg-muted hover:text-foreground\",\n className\n )}\n {...props}\n />\n )\n}\n\nexport {\n Scheduler,\n SchedulerToolbar,\n SchedulerPrevTrigger,\n SchedulerNextTrigger,\n SchedulerTodayTrigger,\n SchedulerTitle,\n SchedulerViewSelect,\n SchedulerTimeGrid,\n SchedulerTimeGridHeader,\n SchedulerDayHeadings,\n SchedulerAllDayRow,\n SchedulerTimeGridBody,\n SchedulerTimeGutter,\n SchedulerDayColumns,\n SchedulerDayColumn,\n SchedulerEvent,\n SchedulerEventTitle,\n SchedulerEventTime,\n SchedulerEventResizeHandle,\n SchedulerCreatePreview,\n SchedulerNowIndicator,\n SchedulerMonthGrid,\n SchedulerMonthHeader,\n SchedulerMonthBody,\n SchedulerMonthCell,\n SchedulerMonthEvent,\n SchedulerMonthMore,\n useScheduler,\n layoutLanes,\n visibleRange,\n type SchedulerView,\n type SchedulerEventChange,\n}\n"
52
1064
  }
53
1065
  ],
54
1066
  "docs": "## Scheduler (calendar)\n`src/components/ui/scheduler.tsx` is a compositional, data-agnostic calendar. The consumer owns\nevents and renders them with the parts; the root owns the anchor `date` and `view`\n(day|week|month, both controllable), the time-grid geometry (`hourHeight`, `slotMinutes`,\n`minHour`/`maxHour`, `weekStartsOn`), the interactions, and reports `onEventChange({ id, start,\nend })` and `onCreate({ start, end })` snapped to `slotMinutes`. Parts: `SchedulerToolbar` >\n`Prev/Next/TodayTrigger`, `SchedulerTitle`, `SchedulerViewSelect` (SegmentGroup);\n`SchedulerTimeGrid` > `SchedulerTimeGridHeader` > `SchedulerDayHeadings` (click a day → day view)\n+ `SchedulerAllDayRow` (render prop per day) and `SchedulerTimeGridBody` (scrolls to 8am on mount)\n> `SchedulerTimeGutter` + `SchedulerDayColumns` > `SchedulerDayColumn date= events=` (render-prop\nchildren per event; computes side-by-side lanes for overlaps via `layoutLanes`; pointer-down on\nempty time starts drag-to-create) > `SchedulerEvent value= start= end=` (absolute by time;\ndraggable to another time/day, `data-dragging` move|resize, `data-grabbed`) > `SchedulerEventTitle`,\n`SchedulerEventTime` (reflects the live drag), `SchedulerEventResizeHandle`; `SchedulerNowIndicator`,\n`SchedulerCreatePreview`. `SchedulerMonthGrid` > `SchedulerMonthHeader` + `SchedulerMonthBody` >\n`SchedulerMonthCell date=` (`data-today`/`data-outside`, day number → day view) > `SchedulerMonthEvent`\n(chip draggable between cells; shifts by whole days) + `SchedulerMonthMore`. Keyboard on an event:\nSpace/Enter picks up, ↑/↓ move by a slot, ←/→ move a day, Shift+↑/↓ change the end, Escape cancels;\na live region announces. Event parts spread `props` first so an `asChild` wrapper (e.g.\n`PopoverTrigger`) cannot override their `data-slot` or pointer handlers. `useScheduler()` exposes\n`days`, `range`, `timeToY`, `setDate`, `setView`. `src/demo/calendar/` is the reference\n(`/projects/calendar`): categories with colors, all-day strip, overlapping meetings in lanes, event popover\n(title, category, delete), new-event dialog from drag-to-create or the toolbar, undo/redo."