beautify-event 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,2130 @@
1
+ import * as React2 from 'react';
2
+ import { createContext, useContext, useState, useMemo, useCallback, useEffect, useRef } from 'react';
3
+ import { clsx } from 'clsx';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
+ import { Calendar, ChevronUp, ChevronDown, ChevronLeft, ChevronRight, Loader2Icon } from 'lucide-react';
7
+ import { DndContext, pointerWithin, useDraggable, useDroppable, DragOverlay } from '@dnd-kit/core';
8
+ import { format, startOfWeek, startOfMonth, endOfWeek, endOfMonth, eachDayOfInterval, startOfDay, endOfDay, isWithinInterval, isSameDay, addDays, subDays, addWeeks, subWeeks, addMonths, subMonths, setMinutes, setHours, isSameMonth, getHours, getMinutes, differenceInMinutes, addMinutes } from 'date-fns';
9
+ import { enUS, ar, fr } from 'date-fns/locale';
10
+ import { Slot } from '@radix-ui/react-slot';
11
+ import { cva } from 'class-variance-authority';
12
+ import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
13
+ import '@radix-ui/react-toggle';
14
+
15
+ var __defProp = Object.defineProperty;
16
+ var __defProps = Object.defineProperties;
17
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
18
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
19
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
20
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
21
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
22
+ var __spreadValues = (a, b) => {
23
+ for (var prop in b || (b = {}))
24
+ if (__hasOwnProp.call(b, prop))
25
+ __defNormalProp(a, prop, b[prop]);
26
+ if (__getOwnPropSymbols)
27
+ for (var prop of __getOwnPropSymbols(b)) {
28
+ if (__propIsEnum.call(b, prop))
29
+ __defNormalProp(a, prop, b[prop]);
30
+ }
31
+ return a;
32
+ };
33
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
34
+ var __objRest = (source, exclude) => {
35
+ var target = {};
36
+ for (var prop in source)
37
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
38
+ target[prop] = source[prop];
39
+ if (source != null && __getOwnPropSymbols)
40
+ for (var prop of __getOwnPropSymbols(source)) {
41
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
42
+ target[prop] = source[prop];
43
+ }
44
+ return target;
45
+ };
46
+ function cn(...inputs) {
47
+ return twMerge(clsx(inputs));
48
+ }
49
+ function Card(_a) {
50
+ var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
51
+ return /* @__PURE__ */ jsx(
52
+ "div",
53
+ __spreadValues({
54
+ "data-slot": "card",
55
+ className: cn(
56
+ "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
57
+ className
58
+ )
59
+ }, props)
60
+ );
61
+ }
62
+ function Spinner(_a) {
63
+ var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
64
+ return /* @__PURE__ */ jsx(
65
+ Loader2Icon,
66
+ __spreadValues({
67
+ role: "status",
68
+ "aria-label": "Loading",
69
+ className: cn("size-4 animate-spin", className)
70
+ }, props)
71
+ );
72
+ }
73
+ function useSystemTheme() {
74
+ const [systemTheme, setSystemTheme] = React2.useState("light");
75
+ React2.useEffect(() => {
76
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
77
+ const handler = () => setSystemTheme(mq.matches ? "dark" : "light");
78
+ handler();
79
+ mq.addEventListener("change", handler);
80
+ return () => mq.removeEventListener("change", handler);
81
+ }, []);
82
+ return systemTheme;
83
+ }
84
+ function normalizeThemeVars(vars) {
85
+ if (!vars || Object.keys(vars).length === 0) return {};
86
+ return Object.fromEntries(
87
+ Object.entries(vars).map(([key, value]) => [
88
+ key.startsWith("--") ? key : `--${key}`,
89
+ value
90
+ ])
91
+ );
92
+ }
93
+ function EventCalendarThemeProvider({
94
+ theme = "system",
95
+ themeVars,
96
+ children
97
+ }) {
98
+ const systemTheme = useSystemTheme();
99
+ const resolvedTheme = theme === "system" ? systemTheme : theme;
100
+ const isDark = resolvedTheme === "dark";
101
+ const customStyle = normalizeThemeVars(themeVars);
102
+ return /* @__PURE__ */ jsx(
103
+ "div",
104
+ {
105
+ className: `event-calendar-root ${isDark ? "dark" : ""}`,
106
+ "data-event-calendar-theme": resolvedTheme,
107
+ style: customStyle,
108
+ children
109
+ }
110
+ );
111
+ }
112
+ var LOCALE_MAP = {
113
+ en: enUS,
114
+ "en-US": enUS,
115
+ fr,
116
+ ar
117
+ };
118
+ function getDateFnsLocale(localeCode) {
119
+ var _a;
120
+ return (_a = LOCALE_MAP[localeCode]) != null ? _a : enUS;
121
+ }
122
+ function formatDate(date, formatStr, localeCode = "en-US") {
123
+ const locale = getDateFnsLocale(localeCode);
124
+ return format(date, formatStr, { locale });
125
+ }
126
+ function getMonthDays(date, weekStartsOn = 0) {
127
+ const start = startOfWeek(startOfMonth(date), { weekStartsOn });
128
+ const end = endOfWeek(endOfMonth(date), { weekStartsOn });
129
+ return eachDayOfInterval({ start, end });
130
+ }
131
+ function getWeekDays(date, weekStartsOn = 0) {
132
+ const start = startOfWeek(date, { weekStartsOn });
133
+ const end = endOfWeek(date, { weekStartsOn });
134
+ return eachDayOfInterval({ start, end });
135
+ }
136
+ function getTwoDays(date, _weekStartsOn = 0) {
137
+ const start = startOfDay(date);
138
+ return [start, addDays(start, 1)];
139
+ }
140
+ function getEventsForDay(events, day) {
141
+ return events.filter((event) => {
142
+ const eventStart = startOfDay(event.start);
143
+ const eventEnd = endOfDay(event.end);
144
+ return isWithinInterval(day, { start: eventStart, end: eventEnd }) || isSameDay(event.start, day) || isSameDay(event.end, day);
145
+ });
146
+ }
147
+ function navigateDate(date, view, direction) {
148
+ switch (view) {
149
+ case "month":
150
+ return direction === "next" ? addMonths(date, 1) : subMonths(date, 1);
151
+ case "week":
152
+ case "resource":
153
+ return direction === "next" ? addWeeks(date, 1) : subWeeks(date, 1);
154
+ case "day":
155
+ return direction === "next" ? addDays(date, 1) : subDays(date, 1);
156
+ case "2day":
157
+ return direction === "next" ? addDays(date, 2) : subDays(date, 2);
158
+ default:
159
+ return date;
160
+ }
161
+ }
162
+ function getViewTitle(date, view, weekStartsOn = 0, localeCode = "en-US") {
163
+ const locale = getDateFnsLocale(localeCode);
164
+ const fmt = (d, str) => format(d, str, { locale });
165
+ switch (view) {
166
+ case "month":
167
+ return fmt(date, "MMMM yyyy");
168
+ case "week":
169
+ case "resource": {
170
+ const weekStart = startOfWeek(date, { weekStartsOn });
171
+ const weekEnd = endOfWeek(date, { weekStartsOn });
172
+ return `${fmt(weekStart, "MMM d")} - ${fmt(weekEnd, "MMM d, yyyy")}`;
173
+ }
174
+ case "day":
175
+ return fmt(date, "EEEE, MMMM d, yyyy");
176
+ case "2day": {
177
+ const days = getTwoDays(date, weekStartsOn);
178
+ return `${fmt(days[0], "MMM d")} \u2013 ${fmt(days[1], "MMM d, yyyy")}`;
179
+ }
180
+ default:
181
+ return fmt(date, "MMMM yyyy");
182
+ }
183
+ }
184
+ function getViewDateRange(date, view, weekStartsOn = 0) {
185
+ switch (view) {
186
+ case "month":
187
+ return {
188
+ start: startOfWeek(startOfMonth(date), { weekStartsOn }),
189
+ end: endOfWeek(endOfMonth(date), { weekStartsOn })
190
+ };
191
+ case "week":
192
+ case "resource":
193
+ return {
194
+ start: startOfWeek(date, { weekStartsOn }),
195
+ end: endOfWeek(date, { weekStartsOn })
196
+ };
197
+ case "day":
198
+ return {
199
+ start: startOfDay(date),
200
+ end: endOfDay(date)
201
+ };
202
+ case "2day": {
203
+ const days = getTwoDays(date, weekStartsOn);
204
+ return {
205
+ start: startOfDay(days[0]),
206
+ end: endOfDay(days[1])
207
+ };
208
+ }
209
+ default:
210
+ return {
211
+ start: startOfMonth(date),
212
+ end: endOfMonth(date)
213
+ };
214
+ }
215
+ }
216
+ function getEventPosition(event, hourHeight = 64, startHour = 0) {
217
+ const eventStartHour = getHours(event.start);
218
+ const eventStartMinutes = getMinutes(event.start);
219
+ const startMinutes = (eventStartHour - startHour) * 60 + eventStartMinutes;
220
+ const duration = differenceInMinutes(event.end, event.start);
221
+ const top = startMinutes / 60 * hourHeight;
222
+ const height = Math.max(duration / 60 * hourHeight, 24);
223
+ return { top: Math.max(top, 0), height };
224
+ }
225
+ function createSlotDate(day, hour, minute = 0) {
226
+ return setMinutes(setHours(day, hour), minute);
227
+ }
228
+ function getWeekdayNames(weekStartsOn = 0, short = true, localeCode = "en-US") {
229
+ const locale = getDateFnsLocale(localeCode);
230
+ const baseDate = new Date(2024, 0, 7);
231
+ const days = [];
232
+ for (let i = 0; i < 7; i++) {
233
+ const dayIndex = (weekStartsOn + i) % 7;
234
+ const date = addDays(baseDate, dayIndex);
235
+ days.push(format(date, short ? "EEE" : "EEEE", { locale }));
236
+ }
237
+ return days;
238
+ }
239
+ function getHoursArray(startHour = 0, endHour = 24) {
240
+ const hours = [];
241
+ for (let i = startHour; i < endHour; i++) {
242
+ hours.push(i);
243
+ }
244
+ return hours;
245
+ }
246
+ function getCurrentTimeLineTop(now, startHour, endHour, hourHeight, topPadding = 16) {
247
+ const currentHour = getHours(now);
248
+ const currentMinute = getMinutes(now);
249
+ const currentTimeInHours = currentHour + currentMinute / 60;
250
+ if (currentTimeInHours < startHour || currentTimeInHours >= endHour) return null;
251
+ const top = topPadding + (currentTimeInHours - startHour) * hourHeight;
252
+ return top;
253
+ }
254
+ function formatHour(hour, timeFormat = "12h", localeCode = "en-US") {
255
+ if (timeFormat === "24h") {
256
+ return `${hour.toString().padStart(2, "0")}:00`;
257
+ }
258
+ const locale = getDateFnsLocale(localeCode);
259
+ const d = new Date(2e3, 0, 1, hour, 0, 0);
260
+ return format(d, "h a", { locale });
261
+ }
262
+ var defaultConfig = {
263
+ weekStartsOn: 0,
264
+ startHour: 0,
265
+ // 12:00 AM - full day start
266
+ endHour: 24,
267
+ // 11:59 PM - full day end
268
+ hourHeight: 64,
269
+ timeSeriesMaxHeight: 400,
270
+ showCurrentTimeLine: true,
271
+ maxEventsPerDay: 3,
272
+ defaultEventColor: "#3b82f6",
273
+ defaultResourceColor: "#6366f1",
274
+ locale: "en-US",
275
+ timeFormat: "12h"
276
+ };
277
+
278
+ // components/event-calendar/dnd/helpers.ts
279
+ function slotDataToSlotInfo(data, endHour) {
280
+ switch (data.view) {
281
+ case "day":
282
+ case "week": {
283
+ const start = createSlotDate(data.day, data.hour);
284
+ const end = createSlotDate(data.day, data.hour + 1);
285
+ return { start, end };
286
+ }
287
+ case "month": {
288
+ const start = startOfDay(data.day);
289
+ const end = endOfDay(data.day);
290
+ return { start, end };
291
+ }
292
+ case "resource": {
293
+ const start = createSlotDate(data.day, data.hour);
294
+ const end = createSlotDate(data.day, data.hour + 1);
295
+ return { start, end, resourceId: data.resourceId };
296
+ }
297
+ }
298
+ }
299
+ function computeCreateSlotInfo(from, to) {
300
+ if (from.view === "month" && to.view === "month") {
301
+ const start = startOfDay(to.day);
302
+ const end = endOfDay(to.day);
303
+ return { start, end };
304
+ }
305
+ if ((from.view === "day" || from.view === "week") && (to.view === "day" || to.view === "week")) {
306
+ const startDate = from.day.getTime() < to.day.getTime() ? from.day : to.day;
307
+ const endDate = from.day.getTime() < to.day.getTime() ? to.day : from.day;
308
+ const startHour = from.day.getTime() === startDate.getTime() ? from.hour : to.hour;
309
+ const endHour = from.day.getTime() === endDate.getTime() ? from.hour : to.hour;
310
+ const start = createSlotDate(startDate, Math.min(startHour, endHour));
311
+ const end = createSlotDate(endDate, Math.max(startHour, endHour) + 1);
312
+ return { start, end };
313
+ }
314
+ if (from.view === "resource" && to.view === "resource" && from.resourceId === to.resourceId) {
315
+ const startDate = from.day.getTime() < to.day.getTime() ? from.day : to.day;
316
+ const endDate = from.day.getTime() < to.day.getTime() ? to.day : from.day;
317
+ const startHour = from.day.getTime() === startDate.getTime() ? from.hour : to.hour;
318
+ const endHour = from.day.getTime() === endDate.getTime() ? from.hour : to.hour;
319
+ const start = createSlotDate(startDate, Math.min(startHour, endHour));
320
+ const end = createSlotDate(endDate, Math.max(startHour, endHour) + 1);
321
+ return { start, end, resourceId: from.resourceId };
322
+ }
323
+ return slotDataToSlotInfo(to);
324
+ }
325
+ function computeMoveEvent(event, dropData) {
326
+ const duration = differenceInMinutes(event.end, event.start);
327
+ switch (dropData.view) {
328
+ case "day":
329
+ case "week": {
330
+ const newStart = createSlotDate(dropData.day, dropData.hour);
331
+ const newEnd = addMinutes(newStart, duration);
332
+ return { newStart, newEnd };
333
+ }
334
+ case "month": {
335
+ const newStart = setMinutes(
336
+ setHours(dropData.day, event.start.getHours()),
337
+ event.start.getMinutes()
338
+ );
339
+ const newEnd = addMinutes(newStart, duration);
340
+ return { newStart, newEnd };
341
+ }
342
+ case "resource": {
343
+ const newStart = createSlotDate(dropData.day, dropData.hour);
344
+ const newEnd = addMinutes(newStart, duration);
345
+ return { newStart, newEnd, resourceId: dropData.resourceId };
346
+ }
347
+ }
348
+ }
349
+ function CalendarDragOverlay({
350
+ activeEvent,
351
+ activeCreateSlotInfo,
352
+ defaultEventColor,
353
+ locale = "en-US",
354
+ renderEvent
355
+ }) {
356
+ return /* @__PURE__ */ jsx(DragOverlay, { dropAnimation: null, children: activeEvent ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-border bg-card px-3 py-2 shadow-lg opacity-90", children: renderEvent ? renderEvent(activeEvent, "day") : /* @__PURE__ */ jsxs(Fragment, { children: [
357
+ /* @__PURE__ */ jsx(
358
+ "div",
359
+ {
360
+ className: "font-semibold text-sm",
361
+ style: {
362
+ color: activeEvent.color || defaultEventColor
363
+ },
364
+ children: activeEvent.title
365
+ }
366
+ ),
367
+ /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground mt-0.5", children: [
368
+ formatDate(activeEvent.start, "h:mm a", locale),
369
+ " -",
370
+ " ",
371
+ formatDate(activeEvent.end, "h:mm a", locale)
372
+ ] })
373
+ ] }) }) : activeCreateSlotInfo ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border-2 border-dashed border-primary/50 bg-primary/10 px-3 py-2 shadow-lg", children: /* @__PURE__ */ jsxs("div", { className: "text-sm font-medium text-primary", children: [
374
+ formatDate(activeCreateSlotInfo.start, "h:mm a", locale),
375
+ " -",
376
+ " ",
377
+ formatDate(activeCreateSlotInfo.end, "h:mm a", locale)
378
+ ] }) }) : null });
379
+ }
380
+ function EventCalendarDndProvider({
381
+ enableDragToCreate = false,
382
+ enableDragToMove = false,
383
+ onEventCreate,
384
+ onEventMove,
385
+ defaultEventColor,
386
+ locale = "en-US",
387
+ renderEvent,
388
+ children
389
+ }) {
390
+ const [activeEvent, setActiveEvent] = React2.useState(null);
391
+ const [activeCreateSlotInfo, setActiveCreateSlotInfo] = React2.useState(null);
392
+ const handleDragStart = React2.useCallback(
393
+ (event) => {
394
+ const data = event.active.data.current;
395
+ if (!data) return;
396
+ if (typeof data !== "object" || data === null) return;
397
+ if ("event" in data && data.event) {
398
+ setActiveEvent(data.event);
399
+ } else if ("slotData" in data && data.slotData) {
400
+ const slotData = data.slotData;
401
+ const info = slotDataToSlotInfo(slotData);
402
+ setActiveCreateSlotInfo({ start: info.start, end: info.end });
403
+ }
404
+ },
405
+ []
406
+ );
407
+ const handleDragEnd = React2.useCallback(
408
+ (event) => {
409
+ var _a;
410
+ const activeData = event.active.data.current;
411
+ const overData = (_a = event.over) == null ? void 0 : _a.data.current;
412
+ setActiveEvent(null);
413
+ setActiveCreateSlotInfo(null);
414
+ if (!overData || typeof overData !== "object") return;
415
+ const overSlot = overData;
416
+ if (activeData && typeof activeData === "object" && "event" in activeData && enableDragToMove && onEventMove) {
417
+ const eventData = activeData.event;
418
+ const { newStart, newEnd, resourceId } = computeMoveEvent(
419
+ eventData,
420
+ overSlot
421
+ );
422
+ onEventMove({ event: eventData, newStart, newEnd, resourceId });
423
+ } else if (activeData && typeof activeData === "object" && "slotData" in activeData && enableDragToCreate && onEventCreate) {
424
+ const fromSlot = activeData.slotData;
425
+ const slotInfo = computeCreateSlotInfo(fromSlot, overSlot);
426
+ onEventCreate(slotInfo);
427
+ }
428
+ },
429
+ [enableDragToCreate, enableDragToMove, onEventCreate, onEventMove]
430
+ );
431
+ return /* @__PURE__ */ jsxs(
432
+ DndContext,
433
+ {
434
+ onDragStart: handleDragStart,
435
+ onDragEnd: handleDragEnd,
436
+ collisionDetection: pointerWithin,
437
+ children: [
438
+ children,
439
+ /* @__PURE__ */ jsx(
440
+ CalendarDragOverlay,
441
+ {
442
+ activeEvent,
443
+ activeCreateSlotInfo,
444
+ defaultEventColor,
445
+ locale,
446
+ renderEvent
447
+ }
448
+ )
449
+ ]
450
+ }
451
+ );
452
+ }
453
+ var BREAKPOINTS = {
454
+ sm: 640,
455
+ md: 768,
456
+ lg: 1024
457
+ };
458
+ function useMediaQuery(breakpoint) {
459
+ const query = `(max-width: ${BREAKPOINTS[breakpoint] - 1}px)`;
460
+ const [matches, setMatches] = useState(false);
461
+ useEffect(() => {
462
+ if (typeof window === "undefined") return;
463
+ const media = window.matchMedia(query);
464
+ setMatches(media.matches);
465
+ const listener = (e) => setMatches(e.matches);
466
+ media.addEventListener("change", listener);
467
+ return () => media.removeEventListener("change", listener);
468
+ }, [query]);
469
+ return matches;
470
+ }
471
+ var CalendarContext = createContext(null);
472
+ function useCalendar() {
473
+ const context = useContext(CalendarContext);
474
+ if (!context) {
475
+ throw new Error("useCalendar must be used within an EventCalendar");
476
+ }
477
+ return context;
478
+ }
479
+ function CalendarProvider({
480
+ children,
481
+ value
482
+ }) {
483
+ return /* @__PURE__ */ jsx(CalendarContext.Provider, { value, children });
484
+ }
485
+ var buttonVariants = cva(
486
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
487
+ {
488
+ variants: {
489
+ variant: {
490
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
491
+ outline: "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
492
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
493
+ ghost: "hover:bg-accent hover:text-accent-foreground"
494
+ },
495
+ size: {
496
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
497
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
498
+ icon: "size-9",
499
+ "icon-sm": "size-8"
500
+ }
501
+ },
502
+ defaultVariants: {
503
+ variant: "outline",
504
+ size: "default"
505
+ }
506
+ }
507
+ );
508
+ function Button(_a) {
509
+ var _b = _a, {
510
+ className,
511
+ variant,
512
+ size,
513
+ asChild = false
514
+ } = _b, props = __objRest(_b, [
515
+ "className",
516
+ "variant",
517
+ "size",
518
+ "asChild"
519
+ ]);
520
+ const Comp = asChild ? Slot : "button";
521
+ return /* @__PURE__ */ jsx(
522
+ Comp,
523
+ __spreadValues({
524
+ "data-slot": "button",
525
+ className: cn(buttonVariants({ variant, size, className }))
526
+ }, props)
527
+ );
528
+ }
529
+ var toggleVariants = cva(
530
+ "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] whitespace-nowrap",
531
+ {
532
+ variants: {
533
+ variant: {
534
+ default: "bg-transparent",
535
+ outline: "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground"
536
+ },
537
+ size: {
538
+ default: "h-9 px-2 min-w-9",
539
+ sm: "h-8 px-1.5 min-w-8"
540
+ }
541
+ },
542
+ defaultVariants: {
543
+ variant: "default",
544
+ size: "default"
545
+ }
546
+ }
547
+ );
548
+ var ToggleGroupContext = React2.createContext({
549
+ size: "default",
550
+ variant: "default"
551
+ });
552
+ function ToggleGroup(_a) {
553
+ var _b = _a, {
554
+ className,
555
+ variant,
556
+ size,
557
+ children
558
+ } = _b, props = __objRest(_b, [
559
+ "className",
560
+ "variant",
561
+ "size",
562
+ "children"
563
+ ]);
564
+ return /* @__PURE__ */ jsx(
565
+ ToggleGroupPrimitive.Root,
566
+ __spreadProps(__spreadValues({
567
+ "data-slot": "toggle-group",
568
+ "data-variant": variant,
569
+ "data-size": size,
570
+ className: cn(
571
+ "group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",
572
+ className
573
+ )
574
+ }, props), {
575
+ children: /* @__PURE__ */ jsx(ToggleGroupContext.Provider, { value: { variant, size }, children })
576
+ })
577
+ );
578
+ }
579
+ function ToggleGroupItem(_a) {
580
+ var _b = _a, {
581
+ className,
582
+ children,
583
+ variant,
584
+ size
585
+ } = _b, props = __objRest(_b, [
586
+ "className",
587
+ "children",
588
+ "variant",
589
+ "size"
590
+ ]);
591
+ var _a2, _b2, _c, _d;
592
+ const context = React2.useContext(ToggleGroupContext);
593
+ return /* @__PURE__ */ jsx(
594
+ ToggleGroupPrimitive.Item,
595
+ __spreadProps(__spreadValues({
596
+ "data-slot": "toggle-group-item",
597
+ "data-variant": (_a2 = context.variant) != null ? _a2 : variant,
598
+ "data-size": (_b2 = context.size) != null ? _b2 : size,
599
+ className: cn(
600
+ toggleVariants({
601
+ variant: (_c = context.variant) != null ? _c : variant,
602
+ size: (_d = context.size) != null ? _d : size
603
+ }),
604
+ "min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
605
+ className
606
+ )
607
+ }, props), {
608
+ children
609
+ })
610
+ );
611
+ }
612
+ function MobileWeekStrip({
613
+ selectedDate,
614
+ onDaySelect,
615
+ onPrevWeek,
616
+ onNextWeek,
617
+ weekStartsOn = 0,
618
+ locale = "en-US",
619
+ className
620
+ }) {
621
+ const days = getWeekDays(selectedDate, weekStartsOn);
622
+ const today = /* @__PURE__ */ new Date();
623
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-0.5", className), children: [
624
+ /* @__PURE__ */ jsx(
625
+ Button,
626
+ {
627
+ variant: "ghost",
628
+ size: "icon",
629
+ onClick: onPrevWeek,
630
+ className: "h-7 w-7 shrink-0",
631
+ "aria-label": "Previous week",
632
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
633
+ }
634
+ ),
635
+ /* @__PURE__ */ jsx("div", { className: "flex flex-1 justify-between gap-0 min-w-0 overflow-visible", children: days.map((day) => {
636
+ const isToday = isSameDay(day, today);
637
+ const isSelected = isSameDay(day, selectedDate);
638
+ return /* @__PURE__ */ jsxs(
639
+ "button",
640
+ {
641
+ type: "button",
642
+ onClick: () => onDaySelect(day),
643
+ className: cn(
644
+ "flex flex-col items-center justify-center min-w-[32px] flex-1 py-1 px-0.5 text-[11px] font-medium transition-colors overflow-visible",
645
+ !isToday && !isSelected && "text-muted-foreground hover:text-foreground",
646
+ isToday && !isSelected && "text-primary font-semibold",
647
+ isSelected && "text-primary-foreground font-semibold"
648
+ ),
649
+ children: [
650
+ /* @__PURE__ */ jsx(
651
+ "span",
652
+ {
653
+ className: cn(
654
+ "text-[9px] uppercase leading-tight",
655
+ isSelected ? "text-primary-foreground" : "text-inherit opacity-80"
656
+ ),
657
+ children: formatDate(day, "EEE", locale)
658
+ }
659
+ ),
660
+ /* @__PURE__ */ jsx(
661
+ "span",
662
+ {
663
+ className: cn(
664
+ "mt-0.5 w-6 h-6 shrink-0 flex items-center justify-center rounded-full text-[11px] font-semibold tabular-nums",
665
+ isSelected && "bg-primary text-primary-foreground",
666
+ isToday && !isSelected && "bg-primary/15 text-primary",
667
+ !isSelected && !isToday && "text-foreground"
668
+ ),
669
+ children: formatDate(day, "d", locale)
670
+ }
671
+ )
672
+ ]
673
+ },
674
+ day.toISOString()
675
+ );
676
+ }) }),
677
+ /* @__PURE__ */ jsx(
678
+ Button,
679
+ {
680
+ variant: "ghost",
681
+ size: "icon",
682
+ onClick: onNextWeek,
683
+ className: "h-7 w-7 shrink-0",
684
+ "aria-label": "Next week",
685
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
686
+ }
687
+ )
688
+ ] });
689
+ }
690
+ function MobileMonthPicker({
691
+ selectedDate,
692
+ onDaySelect,
693
+ weekStartsOn = 0,
694
+ locale = "en-US",
695
+ className
696
+ }) {
697
+ const [viewMonth, setViewMonth] = useState(() => startOfMonth(selectedDate));
698
+ useEffect(() => {
699
+ setViewMonth((prev) => {
700
+ const target = startOfMonth(selectedDate);
701
+ return isSameMonth(prev, target) ? prev : target;
702
+ });
703
+ }, [selectedDate]);
704
+ const days = getMonthDays(viewMonth, weekStartsOn);
705
+ const weekdays = getWeekdayNames(weekStartsOn, true, locale);
706
+ const today = /* @__PURE__ */ new Date();
707
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-2", className), children: [
708
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-1", children: [
709
+ /* @__PURE__ */ jsx(
710
+ Button,
711
+ {
712
+ variant: "ghost",
713
+ size: "icon",
714
+ onClick: () => setViewMonth((m) => subMonths(m, 1)),
715
+ className: "h-8 w-8 shrink-0",
716
+ "aria-label": "Previous month",
717
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
718
+ }
719
+ ),
720
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: formatDate(viewMonth, "MMMM yyyy", locale) }),
721
+ /* @__PURE__ */ jsx(
722
+ Button,
723
+ {
724
+ variant: "ghost",
725
+ size: "icon",
726
+ onClick: () => setViewMonth((m) => addMonths(m, 1)),
727
+ className: "h-8 w-8 shrink-0",
728
+ "aria-label": "Next month",
729
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
730
+ }
731
+ )
732
+ ] }),
733
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-0.5", children: weekdays.map((day) => /* @__PURE__ */ jsx(
734
+ "div",
735
+ {
736
+ className: "py-1 text-center text-[10px] font-medium text-muted-foreground uppercase",
737
+ children: day
738
+ },
739
+ day
740
+ )) }),
741
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-0.5", children: days.map((day) => {
742
+ const isCurrentMonth = isSameMonth(day, viewMonth);
743
+ const isToday = isSameDay(day, today);
744
+ const isSelected = isSameDay(day, selectedDate);
745
+ return /* @__PURE__ */ jsx(
746
+ "button",
747
+ {
748
+ type: "button",
749
+ onClick: () => onDaySelect(day),
750
+ className: cn(
751
+ "aspect-square flex items-center justify-center rounded-full text-xs font-medium transition-colors",
752
+ !isCurrentMonth && "text-muted-foreground/50",
753
+ isCurrentMonth && !isToday && !isSelected && "text-foreground hover:bg-muted",
754
+ isToday && !isSelected && "bg-primary/20 text-primary font-semibold",
755
+ isSelected && "bg-primary text-primary-foreground font-semibold"
756
+ ),
757
+ children: formatDate(day, "d", locale)
758
+ },
759
+ day.toISOString()
760
+ );
761
+ }) })
762
+ ] });
763
+ }
764
+ var VIEW_LABELS = {
765
+ month: "Month",
766
+ week: "Week",
767
+ day: "Day",
768
+ "2day": "2 Day",
769
+ resource: "Resource"
770
+ };
771
+ function CalendarHeader({
772
+ currentDate,
773
+ view,
774
+ views,
775
+ title,
776
+ showNavigation = true,
777
+ showViewSwitcher = true,
778
+ showTodayButton = true,
779
+ isMobile = false,
780
+ showMobileMonthPicker = true,
781
+ weekStartsOn = 0,
782
+ locale = "en-US",
783
+ onNavigate,
784
+ onViewChange,
785
+ onToday,
786
+ onDateSelect,
787
+ onWeekNavigate,
788
+ renderHeader
789
+ }) {
790
+ const [monthPickerExpanded, setMonthPickerExpanded] = useState(false);
791
+ if (renderHeader) {
792
+ return /* @__PURE__ */ jsx(Fragment, { children: renderHeader({
793
+ date: currentDate,
794
+ view,
795
+ views,
796
+ title,
797
+ onNavigate,
798
+ onViewChange,
799
+ onToday
800
+ }) });
801
+ }
802
+ if (isMobile) {
803
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
804
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
805
+ /* @__PURE__ */ jsx(Calendar, { className: "h-4 w-4 text-primary shrink-0" }),
806
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 flex-1 min-w-0", children: [
807
+ showMobileMonthPicker && onDateSelect && onWeekNavigate ? /* @__PURE__ */ jsx(
808
+ "button",
809
+ {
810
+ type: "button",
811
+ onClick: () => setMonthPickerExpanded((v) => !v),
812
+ className: "text-base font-semibold text-foreground truncate text-left hover:underline",
813
+ children: title
814
+ }
815
+ ) : /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-foreground truncate", children: title }),
816
+ showMobileMonthPicker && onDateSelect && onWeekNavigate && /* @__PURE__ */ jsx(
817
+ Button,
818
+ {
819
+ variant: "ghost",
820
+ size: "icon",
821
+ onClick: () => setMonthPickerExpanded((v) => !v),
822
+ className: "h-7 w-7 shrink-0",
823
+ "aria-expanded": monthPickerExpanded,
824
+ "aria-label": monthPickerExpanded ? "Collapse month view" : "Expand month view",
825
+ children: monthPickerExpanded ? /* @__PURE__ */ jsx(ChevronUp, { className: "h-4 w-4 text-muted-foreground" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4 text-muted-foreground" })
826
+ }
827
+ )
828
+ ] })
829
+ ] }),
830
+ showMobileMonthPicker && onDateSelect && onWeekNavigate && /* @__PURE__ */ jsxs("div", { className: "flex flex-col overflow-hidden", children: [
831
+ /* @__PURE__ */ jsx(
832
+ "div",
833
+ {
834
+ className: cn(
835
+ "flex items-center rounded-lg px-1.5 py-1 bg-muted/30",
836
+ monthPickerExpanded && "rounded-b-none"
837
+ ),
838
+ children: /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: !monthPickerExpanded && /* @__PURE__ */ jsx(
839
+ MobileWeekStrip,
840
+ {
841
+ selectedDate: currentDate,
842
+ onDaySelect: onDateSelect,
843
+ onPrevWeek: () => onWeekNavigate("prev"),
844
+ onNextWeek: () => onWeekNavigate("next"),
845
+ weekStartsOn,
846
+ locale
847
+ }
848
+ ) })
849
+ }
850
+ ),
851
+ /* @__PURE__ */ jsx(
852
+ "div",
853
+ {
854
+ className: "overflow-hidden transition-[max-height] duration-300 ease-in-out",
855
+ style: { maxHeight: monthPickerExpanded ? 400 : 0 },
856
+ children: /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("div", { className: "rounded-b-lg bg-muted/30 px-2 pt-1 pb-2 border-t border-border/50", children: /* @__PURE__ */ jsx(
857
+ MobileMonthPicker,
858
+ {
859
+ selectedDate: currentDate,
860
+ onDaySelect: onDateSelect,
861
+ weekStartsOn,
862
+ locale
863
+ }
864
+ ) }) })
865
+ }
866
+ )
867
+ ] })
868
+ ] });
869
+ }
870
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between", children: [
871
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
872
+ /* @__PURE__ */ jsx(Calendar, { className: "h-5 w-5 text-primary" }),
873
+ /* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold text-foreground", children: title })
874
+ ] }) }),
875
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 sm:gap-4", children: [
876
+ showNavigation && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
877
+ /* @__PURE__ */ jsxs(
878
+ Button,
879
+ {
880
+ variant: "outline",
881
+ size: "icon",
882
+ onClick: () => onNavigate("prev"),
883
+ className: "h-8 w-8",
884
+ children: [
885
+ /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" }),
886
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Previous" })
887
+ ]
888
+ }
889
+ ),
890
+ showTodayButton && /* @__PURE__ */ jsx(
891
+ Button,
892
+ {
893
+ variant: "outline",
894
+ size: "sm",
895
+ onClick: onToday,
896
+ className: "h-8 px-3 text-xs font-medium",
897
+ children: "Today"
898
+ }
899
+ ),
900
+ /* @__PURE__ */ jsxs(
901
+ Button,
902
+ {
903
+ variant: "outline",
904
+ size: "icon",
905
+ onClick: () => onNavigate("next"),
906
+ className: "h-8 w-8",
907
+ children: [
908
+ /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" }),
909
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Next" })
910
+ ]
911
+ }
912
+ )
913
+ ] }),
914
+ showViewSwitcher && views.length > 1 && /* @__PURE__ */ jsx(
915
+ ToggleGroup,
916
+ {
917
+ type: "single",
918
+ value: view,
919
+ onValueChange: (value) => value && onViewChange(value),
920
+ className: "border border-border rounded-lg p-0.5",
921
+ children: views.map((v) => /* @__PURE__ */ jsx(
922
+ ToggleGroupItem,
923
+ {
924
+ value: v,
925
+ className: "h-7 px-3 text-xs data-[state=on]:bg-primary data-[state=on]:text-primary-foreground",
926
+ children: VIEW_LABELS[v]
927
+ },
928
+ v
929
+ ))
930
+ }
931
+ )
932
+ ] })
933
+ ] });
934
+ }
935
+
936
+ // components/event-calendar/dnd/types.ts
937
+ function createEventId(event) {
938
+ return `event-${event.id}`;
939
+ }
940
+ function createSlotId(data) {
941
+ switch (data.view) {
942
+ case "day":
943
+ return `slot-day-${data.day.getTime()}-${data.hour}`;
944
+ case "week":
945
+ return `slot-week-${data.day.getTime()}-${data.hour}`;
946
+ case "month":
947
+ return `slot-month-${data.day.getTime()}`;
948
+ case "resource":
949
+ return `slot-resource-${data.resourceId}-${data.day.getTime()}-${data.hour}`;
950
+ }
951
+ }
952
+ function DroppableSlot({
953
+ data,
954
+ children,
955
+ className,
956
+ style
957
+ }) {
958
+ const id = createSlotId(data);
959
+ const { setNodeRef, isOver } = useDroppable({
960
+ id,
961
+ data
962
+ });
963
+ return /* @__PURE__ */ jsx(
964
+ "div",
965
+ {
966
+ ref: setNodeRef,
967
+ className,
968
+ style: __spreadValues(__spreadValues({}, style), isOver ? { backgroundColor: "var(--accent)" } : {}),
969
+ children
970
+ }
971
+ );
972
+ }
973
+ function DraggableCreateSlot({
974
+ data,
975
+ children,
976
+ disabled = false,
977
+ className,
978
+ style
979
+ }) {
980
+ const id = `create-${createSlotId(data)}`;
981
+ const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
982
+ id,
983
+ data: { type: "create", slotData: data },
984
+ disabled
985
+ });
986
+ return /* @__PURE__ */ jsx(
987
+ "div",
988
+ __spreadProps(__spreadValues(__spreadValues({
989
+ ref: setNodeRef
990
+ }, listeners), attributes), {
991
+ className,
992
+ style: __spreadProps(__spreadValues({}, style), {
993
+ opacity: isDragging ? 0.5 : 1,
994
+ cursor: disabled ? "default" : "crosshair"
995
+ }),
996
+ children
997
+ })
998
+ );
999
+ }
1000
+ function DraggableEvent({
1001
+ event,
1002
+ children,
1003
+ disabled = false,
1004
+ className,
1005
+ style
1006
+ }) {
1007
+ const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
1008
+ id: createEventId(event),
1009
+ data: { type: "event", event },
1010
+ disabled
1011
+ });
1012
+ return /* @__PURE__ */ jsx(
1013
+ "div",
1014
+ __spreadProps(__spreadValues(__spreadValues({
1015
+ ref: setNodeRef
1016
+ }, listeners), attributes), {
1017
+ className,
1018
+ style: __spreadProps(__spreadValues({}, style), {
1019
+ opacity: isDragging ? 0.5 : 1,
1020
+ cursor: disabled ? "default" : "grab",
1021
+ position: "absolute"
1022
+ }),
1023
+ children
1024
+ })
1025
+ );
1026
+ }
1027
+ function MonthView({ currentDate, events }) {
1028
+ const {
1029
+ config,
1030
+ onEventClick,
1031
+ onEventDoubleClick,
1032
+ onSlotClick,
1033
+ onSlotDoubleClick,
1034
+ onDayClick,
1035
+ renderEvent,
1036
+ enableDragToCreate = false,
1037
+ enableDragToMove = false
1038
+ } = useCalendar();
1039
+ const days = getMonthDays(currentDate, config.weekStartsOn);
1040
+ const weekdays = getWeekdayNames(config.weekStartsOn, true, config.locale);
1041
+ const today = /* @__PURE__ */ new Date();
1042
+ const handleSlotClick = (day) => {
1043
+ if (onDayClick) {
1044
+ onDayClick(day);
1045
+ }
1046
+ if (onSlotClick) {
1047
+ const slotInfo = {
1048
+ start: startOfDay(day),
1049
+ end: endOfDay(day)
1050
+ };
1051
+ onSlotClick(slotInfo);
1052
+ }
1053
+ };
1054
+ const handleSlotDoubleClick = (day) => {
1055
+ if (onSlotDoubleClick) {
1056
+ const slotInfo = {
1057
+ start: startOfDay(day),
1058
+ end: endOfDay(day)
1059
+ };
1060
+ onSlotDoubleClick(slotInfo);
1061
+ }
1062
+ };
1063
+ const handleEventClick = (e, event) => {
1064
+ e.stopPropagation();
1065
+ onEventClick == null ? void 0 : onEventClick(event);
1066
+ };
1067
+ const handleEventDoubleClick = (e, event) => {
1068
+ e.stopPropagation();
1069
+ onEventDoubleClick == null ? void 0 : onEventDoubleClick(event);
1070
+ };
1071
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", children: [
1072
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 border-b border-border", children: weekdays.map((day) => /* @__PURE__ */ jsx(
1073
+ "div",
1074
+ {
1075
+ className: "py-3 text-center text-xs font-medium text-muted-foreground uppercase tracking-wider",
1076
+ children: day
1077
+ },
1078
+ day
1079
+ )) }),
1080
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 flex-1 auto-rows-fr", children: days.map((day, index) => {
1081
+ const dayEvents = getEventsForDay(events, day);
1082
+ const isCurrentMonth = isSameMonth(day, currentDate);
1083
+ const isToday = isSameDay(day, today);
1084
+ const slotData = { view: "month", day };
1085
+ const cellContent = /* @__PURE__ */ jsxs(Fragment, { children: [
1086
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between mb-1", children: /* @__PURE__ */ jsx(
1087
+ "span",
1088
+ {
1089
+ className: cn(
1090
+ "inline-flex items-center justify-center w-7 h-7 text-sm font-medium rounded-full",
1091
+ isToday && "bg-primary text-primary-foreground",
1092
+ !isToday && isCurrentMonth && "text-foreground",
1093
+ !isToday && !isCurrentMonth && "text-muted-foreground"
1094
+ ),
1095
+ children: formatDate(day, "d", config.locale)
1096
+ }
1097
+ ) }),
1098
+ /* @__PURE__ */ jsxs("div", { className: "space-y-0.5 overflow-hidden", children: [
1099
+ dayEvents.slice(0, config.maxEventsPerDay).map((event) => {
1100
+ const eventColor = event.color || config.defaultEventColor;
1101
+ const eventContent = renderEvent ? renderEvent(event, "month") : /* @__PURE__ */ jsx("span", { children: event.title });
1102
+ const eventEl = /* @__PURE__ */ jsx(
1103
+ "div",
1104
+ {
1105
+ className: cn(
1106
+ "px-1.5 py-0.5 text-xs font-medium rounded truncate transition-opacity hover:opacity-80",
1107
+ !enableDragToMove && "cursor-pointer"
1108
+ ),
1109
+ style: {
1110
+ backgroundColor: `${eventColor}20`,
1111
+ color: eventColor,
1112
+ borderLeft: `2px solid ${eventColor}`
1113
+ },
1114
+ title: event.title,
1115
+ onClick: (e) => handleEventClick(e, event),
1116
+ onDoubleClick: (e) => handleEventDoubleClick(e, event),
1117
+ children: eventContent
1118
+ }
1119
+ );
1120
+ return enableDragToMove ? /* @__PURE__ */ jsx(DraggableEvent, { event, children: eventEl }, event.id) : /* @__PURE__ */ jsx("div", { children: eventEl }, event.id);
1121
+ }),
1122
+ dayEvents.length > config.maxEventsPerDay && /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground pl-1.5 font-medium", children: [
1123
+ "+",
1124
+ dayEvents.length - config.maxEventsPerDay,
1125
+ " more"
1126
+ ] })
1127
+ ] })
1128
+ ] });
1129
+ const cell = /* @__PURE__ */ jsx(
1130
+ "div",
1131
+ {
1132
+ className: cn(
1133
+ "min-h-[100px] border-b border-r border-border p-1.5 transition-colors hover:bg-muted/50 cursor-pointer h-full",
1134
+ !isCurrentMonth && "bg-muted/30",
1135
+ index % 7 === 0 && "border-l-0",
1136
+ index < 7 && "border-t-0"
1137
+ ),
1138
+ onClick: () => handleSlotClick(day),
1139
+ onDoubleClick: () => handleSlotDoubleClick(day),
1140
+ children: cellContent
1141
+ }
1142
+ );
1143
+ return enableDragToCreate || enableDragToMove ? /* @__PURE__ */ jsx(DroppableSlot, { data: slotData, className: "min-h-[100px]", children: enableDragToCreate ? /* @__PURE__ */ jsx(DraggableCreateSlot, { data: slotData, className: "h-full", children: cell }) : cell }, index) : /* @__PURE__ */ jsx("div", { children: cell }, index);
1144
+ }) })
1145
+ ] });
1146
+ }
1147
+ function WeekView({ currentDate, events, dayCount, isMobile = false }) {
1148
+ var _a;
1149
+ const {
1150
+ config,
1151
+ onEventClick,
1152
+ onEventDoubleClick,
1153
+ onSlotClick,
1154
+ onSlotDoubleClick,
1155
+ renderEvent,
1156
+ enableDragToCreate = false,
1157
+ enableDragToMove = false
1158
+ } = useCalendar();
1159
+ const days = dayCount === 2 ? getTwoDays(currentDate, config.weekStartsOn) : getWeekDays(currentDate, config.weekStartsOn);
1160
+ const hours = getHoursArray(config.startHour, config.endHour);
1161
+ const today = /* @__PURE__ */ new Date();
1162
+ const totalHours = hours.length;
1163
+ const showCurrentTimeLine = (_a = config.showCurrentTimeLine) != null ? _a : true;
1164
+ const includesToday = days.some((d) => isSameDay(d, today));
1165
+ const [now, setNow] = useState(() => /* @__PURE__ */ new Date());
1166
+ const columnRefs = useRef(/* @__PURE__ */ new Map());
1167
+ useEffect(() => {
1168
+ if (!showCurrentTimeLine || !includesToday) return;
1169
+ const id = setInterval(() => setNow(/* @__PURE__ */ new Date()), 6e4);
1170
+ return () => clearInterval(id);
1171
+ }, [showCurrentTimeLine, includesToday]);
1172
+ useEffect(() => {
1173
+ const key = currentDate.toISOString().slice(0, 10);
1174
+ const el = columnRefs.current.get(key);
1175
+ if (el) {
1176
+ el.scrollIntoView({ behavior: "smooth", inline: "center", block: "nearest" });
1177
+ }
1178
+ }, [currentDate]);
1179
+ const currentTimeLineTop = showCurrentTimeLine && includesToday ? getCurrentTimeLineTop(now, config.startHour, config.endHour, config.hourHeight, 0) : null;
1180
+ const dayColumnMinWidth = isMobile && dayCount !== 2 ? "min(45vw, 200px)" : "140px";
1181
+ const handleSlotClick = (day, hour) => {
1182
+ if (onSlotClick) {
1183
+ const slotInfo = {
1184
+ start: createSlotDate(day, hour),
1185
+ end: createSlotDate(day, hour + 1)
1186
+ };
1187
+ onSlotClick(slotInfo);
1188
+ }
1189
+ };
1190
+ const handleSlotDoubleClick = (day, hour) => {
1191
+ if (onSlotDoubleClick) {
1192
+ const slotInfo = {
1193
+ start: createSlotDate(day, hour),
1194
+ end: createSlotDate(day, hour + 1)
1195
+ };
1196
+ onSlotDoubleClick(slotInfo);
1197
+ }
1198
+ };
1199
+ const handleEventClick = (e, event) => {
1200
+ e.stopPropagation();
1201
+ onEventClick == null ? void 0 : onEventClick(event);
1202
+ };
1203
+ const handleEventDoubleClick = (e, event) => {
1204
+ e.stopPropagation();
1205
+ onEventDoubleClick == null ? void 0 : onEventDoubleClick(event);
1206
+ };
1207
+ const maxHeightStyle = config.timeSeriesMaxHeight != null ? typeof config.timeSeriesMaxHeight === "number" ? `${config.timeSeriesMaxHeight}px` : config.timeSeriesMaxHeight : void 0;
1208
+ return /* @__PURE__ */ jsx(
1209
+ "div",
1210
+ {
1211
+ className: "h-full overflow-auto",
1212
+ style: { maxHeight: maxHeightStyle },
1213
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-w-max flex-1", children: [
1214
+ /* @__PURE__ */ jsxs("div", { className: "flex border-b border-border shrink-0 bg-background z-10 sticky top-0 shadow-[0_4px_12px_-2px_rgba(0,0,0,0.08)] dark:shadow-[0_4px_12px_-2px_rgba(0,0,0,0.2)]", children: [
1215
+ /* @__PURE__ */ jsx("div", { className: "w-16 shrink-0 border-r border-border sticky left-0 z-10 bg-background shadow-[4px_0_12px_-2px_rgba(0,0,0,0.08),6px_0_16px_-4px_rgba(0,0,0,0.06)] dark:shadow-[4px_0_16px_-2px_rgba(0,0,0,0.25),6px_0_24px_-4px_rgba(0,0,0,0.2)]" }),
1216
+ days.map((day, index) => {
1217
+ const isToday = isSameDay(day, today);
1218
+ const dayKey = day.toISOString().slice(0, 10);
1219
+ return /* @__PURE__ */ jsxs(
1220
+ "div",
1221
+ {
1222
+ ref: (el) => {
1223
+ if (el) columnRefs.current.set(dayKey, el);
1224
+ },
1225
+ className: cn(
1226
+ "flex-1 py-3 text-center border-r border-border last:border-r-0",
1227
+ isToday && "bg-primary/5"
1228
+ ),
1229
+ style: { minWidth: dayColumnMinWidth },
1230
+ children: [
1231
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-medium text-muted-foreground uppercase", children: formatDate(day, "EEE", config.locale) }),
1232
+ /* @__PURE__ */ jsx(
1233
+ "div",
1234
+ {
1235
+ className: cn(
1236
+ "mt-1 inline-flex items-center justify-center w-8 h-8 text-lg font-semibold rounded-full",
1237
+ isToday && "bg-primary text-primary-foreground",
1238
+ !isToday && "text-foreground"
1239
+ ),
1240
+ children: formatDate(day, "d", config.locale)
1241
+ }
1242
+ )
1243
+ ]
1244
+ },
1245
+ index
1246
+ );
1247
+ })
1248
+ ] }),
1249
+ /* @__PURE__ */ jsxs(
1250
+ "div",
1251
+ {
1252
+ className: "flex flex-1 relative",
1253
+ style: { minHeight: totalHours * config.hourHeight },
1254
+ children: [
1255
+ currentTimeLineTop != null && /* @__PURE__ */ jsx(
1256
+ "div",
1257
+ {
1258
+ className: "absolute left-16 right-0 h-0.5 bg-sky-300 z-20 pointer-events-none",
1259
+ style: { top: currentTimeLineTop },
1260
+ "aria-hidden": true,
1261
+ children: /* @__PURE__ */ jsx("span", { className: "absolute -left-1 top-1/2 -translate-y-1/2 w-2 h-2 rounded-full bg-sky-300" })
1262
+ }
1263
+ ),
1264
+ /* @__PURE__ */ jsx("div", { className: "w-16 shrink-0 border-r border-border sticky left-0 z-10 bg-background shadow-[4px_0_12px_-2px_rgba(0,0,0,0.08),6px_0_16px_-4px_rgba(0,0,0,0.06)] dark:shadow-[4px_0_16px_-2px_rgba(0,0,0,0.25),6px_0_24px_-4px_rgba(0,0,0,0.2)]", children: hours.map((hour) => /* @__PURE__ */ jsx(
1265
+ "div",
1266
+ {
1267
+ className: "pr-2 flex items-start justify-end",
1268
+ style: { height: `${config.hourHeight}px` },
1269
+ children: /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground -mt-2", children: formatHour(hour, config.timeFormat, config.locale) })
1270
+ },
1271
+ hour
1272
+ )) }),
1273
+ days.map((day, dayIndex) => {
1274
+ const dayEvents = getEventsForDay(events, day);
1275
+ const isToday = isSameDay(day, today);
1276
+ return /* @__PURE__ */ jsxs(
1277
+ "div",
1278
+ {
1279
+ className: cn(
1280
+ "flex-1 border-r border-border last:border-r-0 relative",
1281
+ isToday && "bg-primary/5"
1282
+ ),
1283
+ style: { minWidth: dayColumnMinWidth },
1284
+ children: [
1285
+ hours.map((hour, index) => {
1286
+ const slotData = { view: "week", day, hour };
1287
+ const slotContent = /* @__PURE__ */ jsx(
1288
+ "div",
1289
+ {
1290
+ className: cn(
1291
+ "cursor-pointer hover:bg-muted/30 transition-colors h-full w-full",
1292
+ index < totalHours - 1 && "border-b border-border"
1293
+ ),
1294
+ style: { height: `${config.hourHeight}px` },
1295
+ onClick: () => handleSlotClick(day, hour),
1296
+ onDoubleClick: () => handleSlotDoubleClick(day, hour)
1297
+ }
1298
+ );
1299
+ return enableDragToCreate || enableDragToMove ? /* @__PURE__ */ jsx(
1300
+ DroppableSlot,
1301
+ {
1302
+ data: slotData,
1303
+ style: { height: `${config.hourHeight}px` },
1304
+ className: cn(index < totalHours - 1 && "border-b border-border"),
1305
+ children: enableDragToCreate ? /* @__PURE__ */ jsx(DraggableCreateSlot, { data: slotData, className: "h-full w-full", children: slotContent }) : slotContent
1306
+ },
1307
+ hour
1308
+ ) : /* @__PURE__ */ jsx(
1309
+ "div",
1310
+ {
1311
+ className: cn(
1312
+ "cursor-pointer hover:bg-muted/30 transition-colors",
1313
+ index < totalHours - 1 && "border-b border-border"
1314
+ ),
1315
+ style: { height: `${config.hourHeight}px` },
1316
+ onClick: () => handleSlotClick(day, hour),
1317
+ onDoubleClick: () => handleSlotDoubleClick(day, hour)
1318
+ },
1319
+ hour
1320
+ );
1321
+ }),
1322
+ /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 h-px bg-border" }),
1323
+ dayEvents.map((event) => {
1324
+ if (!isSameDay(event.start, day)) return null;
1325
+ const { top, height } = getEventPosition(event, config.hourHeight, config.startHour);
1326
+ const eventColor = event.color || config.defaultEventColor;
1327
+ const eventContent = renderEvent ? renderEvent(event, "week") : /* @__PURE__ */ jsxs(Fragment, { children: [
1328
+ /* @__PURE__ */ jsx("div", { className: "font-semibold truncate", children: event.title }),
1329
+ /* @__PURE__ */ jsxs("div", { className: "text-[10px] opacity-80", children: [
1330
+ formatDate(event.start, "h:mm a", config.locale),
1331
+ " - ",
1332
+ formatDate(event.end, "h:mm a", config.locale)
1333
+ ] })
1334
+ ] });
1335
+ const eventEl = /* @__PURE__ */ jsx(
1336
+ "div",
1337
+ {
1338
+ className: cn(
1339
+ "px-1.5 py-1 rounded text-xs font-medium overflow-hidden transition-opacity hover:opacity-90 h-full w-full",
1340
+ !enableDragToMove && "cursor-pointer"
1341
+ ),
1342
+ style: {
1343
+ backgroundColor: `${eventColor}15`,
1344
+ borderLeft: `3px solid ${eventColor}`,
1345
+ color: eventColor
1346
+ },
1347
+ title: event.title,
1348
+ onClick: (e) => handleEventClick(e, event),
1349
+ onDoubleClick: (e) => handleEventDoubleClick(e, event),
1350
+ children: eventContent
1351
+ }
1352
+ );
1353
+ return enableDragToMove ? /* @__PURE__ */ jsx(
1354
+ DraggableEvent,
1355
+ {
1356
+ event,
1357
+ className: "absolute left-0.5 right-0.5",
1358
+ style: { top: `${top}px`, height: `${height}px` },
1359
+ children: eventEl
1360
+ },
1361
+ event.id
1362
+ ) : /* @__PURE__ */ jsx(
1363
+ "div",
1364
+ {
1365
+ className: "absolute left-0.5 right-0.5",
1366
+ style: { top: `${top}px`, height: `${height}px` },
1367
+ children: eventEl
1368
+ },
1369
+ event.id
1370
+ );
1371
+ })
1372
+ ]
1373
+ },
1374
+ dayIndex
1375
+ );
1376
+ })
1377
+ ]
1378
+ }
1379
+ )
1380
+ ] })
1381
+ }
1382
+ );
1383
+ }
1384
+ function DayView({ currentDate, events }) {
1385
+ var _a;
1386
+ const {
1387
+ config,
1388
+ onEventClick,
1389
+ onEventDoubleClick,
1390
+ onSlotClick,
1391
+ onSlotDoubleClick,
1392
+ renderEvent,
1393
+ enableDragToCreate = false,
1394
+ enableDragToMove = false
1395
+ } = useCalendar();
1396
+ const dayEvents = getEventsForDay(events, currentDate);
1397
+ const hours = getHoursArray(config.startHour, config.endHour);
1398
+ const today = /* @__PURE__ */ new Date();
1399
+ const isToday = isSameDay(currentDate, today);
1400
+ const totalHours = hours.length;
1401
+ const showCurrentTimeLine = (_a = config.showCurrentTimeLine) != null ? _a : true;
1402
+ const [now, setNow] = useState(() => /* @__PURE__ */ new Date());
1403
+ useEffect(() => {
1404
+ if (!showCurrentTimeLine || !isToday) return;
1405
+ const id = setInterval(() => setNow(/* @__PURE__ */ new Date()), 6e4);
1406
+ return () => clearInterval(id);
1407
+ }, [showCurrentTimeLine, isToday]);
1408
+ const currentTimeLineTop = showCurrentTimeLine && isToday ? getCurrentTimeLineTop(now, config.startHour, config.endHour, config.hourHeight, 0) : null;
1409
+ const handleSlotClick = (hour) => {
1410
+ if (onSlotClick) {
1411
+ const slotInfo = {
1412
+ start: createSlotDate(currentDate, hour),
1413
+ end: createSlotDate(currentDate, hour + 1)
1414
+ };
1415
+ onSlotClick(slotInfo);
1416
+ }
1417
+ };
1418
+ const handleSlotDoubleClick = (hour) => {
1419
+ if (onSlotDoubleClick) {
1420
+ const slotInfo = {
1421
+ start: createSlotDate(currentDate, hour),
1422
+ end: createSlotDate(currentDate, hour + 1)
1423
+ };
1424
+ onSlotDoubleClick(slotInfo);
1425
+ }
1426
+ };
1427
+ const handleEventClick = (e, event) => {
1428
+ e.stopPropagation();
1429
+ onEventClick == null ? void 0 : onEventClick(event);
1430
+ };
1431
+ const handleEventDoubleClick = (e, event) => {
1432
+ e.stopPropagation();
1433
+ onEventDoubleClick == null ? void 0 : onEventDoubleClick(event);
1434
+ };
1435
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full overflow-hidden", children: [
1436
+ /* @__PURE__ */ jsxs("div", { className: "flex border-b border-border shrink-0 bg-background z-10 shadow-[0_4px_12px_-2px_rgba(0,0,0,0.08)] dark:shadow-[0_4px_12px_-2px_rgba(0,0,0,0.2)]", children: [
1437
+ /* @__PURE__ */ jsx("div", { className: "w-20 shrink-0 border-r border-border" }),
1438
+ /* @__PURE__ */ jsxs(
1439
+ "div",
1440
+ {
1441
+ className: cn(
1442
+ "flex-1 py-4 text-center border-r border-border",
1443
+ isToday && "bg-primary/5"
1444
+ ),
1445
+ children: [
1446
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-muted-foreground uppercase", children: formatDate(currentDate, "EEEE", config.locale) }),
1447
+ /* @__PURE__ */ jsx(
1448
+ "div",
1449
+ {
1450
+ className: cn(
1451
+ "mt-1 inline-flex items-center justify-center w-10 h-10 text-xl font-semibold rounded-full",
1452
+ isToday && "bg-primary text-primary-foreground",
1453
+ !isToday && "text-foreground"
1454
+ ),
1455
+ children: formatDate(currentDate, "d", config.locale)
1456
+ }
1457
+ )
1458
+ ]
1459
+ }
1460
+ )
1461
+ ] }),
1462
+ /* @__PURE__ */ jsx(
1463
+ "div",
1464
+ {
1465
+ className: "flex flex-1 overflow-auto min-h-0",
1466
+ style: {
1467
+ maxHeight: config.timeSeriesMaxHeight != null ? typeof config.timeSeriesMaxHeight === "number" ? `${config.timeSeriesMaxHeight}px` : config.timeSeriesMaxHeight : void 0
1468
+ },
1469
+ children: /* @__PURE__ */ jsxs(
1470
+ "div",
1471
+ {
1472
+ className: "flex min-w-full relative",
1473
+ style: { minHeight: totalHours * config.hourHeight },
1474
+ children: [
1475
+ currentTimeLineTop != null && /* @__PURE__ */ jsx(
1476
+ "div",
1477
+ {
1478
+ className: "absolute left-20 right-0 h-0.5 bg-sky-300 z-20 pointer-events-none",
1479
+ style: { top: currentTimeLineTop },
1480
+ "aria-hidden": true,
1481
+ children: /* @__PURE__ */ jsx("span", { className: "absolute -left-1 top-1/2 -translate-y-1/2 w-2 h-2 rounded-full bg-sky-300" })
1482
+ }
1483
+ ),
1484
+ /* @__PURE__ */ jsx("div", { className: "w-20 shrink-0 border-r border-border relative", children: hours.map((hour) => /* @__PURE__ */ jsx(
1485
+ "div",
1486
+ {
1487
+ className: "pr-3 flex items-start justify-end",
1488
+ style: { height: `${config.hourHeight}px` },
1489
+ children: /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground -mt-2", children: formatHour(hour, config.timeFormat, config.locale) })
1490
+ },
1491
+ hour
1492
+ )) }),
1493
+ /* @__PURE__ */ jsxs("div", { className: cn("flex-1 relative border-r border-border", isToday && "bg-primary/5"), children: [
1494
+ hours.map((hour, index) => {
1495
+ const slotData = { view: "day", day: currentDate, hour };
1496
+ const slotContent = /* @__PURE__ */ jsx(
1497
+ "div",
1498
+ {
1499
+ className: cn(
1500
+ "cursor-pointer hover:bg-muted/30 transition-colors h-full w-full",
1501
+ index < totalHours - 1 && "border-b border-border"
1502
+ ),
1503
+ style: { height: `${config.hourHeight}px` },
1504
+ onClick: () => handleSlotClick(hour),
1505
+ onDoubleClick: () => handleSlotDoubleClick(hour)
1506
+ }
1507
+ );
1508
+ return enableDragToCreate || enableDragToMove ? /* @__PURE__ */ jsx(
1509
+ DroppableSlot,
1510
+ {
1511
+ data: slotData,
1512
+ style: { height: `${config.hourHeight}px` },
1513
+ className: cn(index < totalHours - 1 && "border-b border-border"),
1514
+ children: enableDragToCreate ? /* @__PURE__ */ jsx(DraggableCreateSlot, { data: slotData, className: "h-full w-full", children: slotContent }) : slotContent
1515
+ },
1516
+ hour
1517
+ ) : /* @__PURE__ */ jsx(
1518
+ "div",
1519
+ {
1520
+ className: cn(
1521
+ "cursor-pointer hover:bg-muted/30 transition-colors",
1522
+ index < totalHours - 1 && "border-b border-border"
1523
+ ),
1524
+ style: { height: `${config.hourHeight}px` },
1525
+ onClick: () => handleSlotClick(hour),
1526
+ onDoubleClick: () => handleSlotDoubleClick(hour)
1527
+ },
1528
+ hour
1529
+ );
1530
+ }),
1531
+ /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 h-px bg-border" }),
1532
+ dayEvents.map((event) => {
1533
+ if (!isSameDay(event.start, currentDate)) return null;
1534
+ const { top, height } = getEventPosition(event, config.hourHeight, config.startHour);
1535
+ const eventColor = event.color || config.defaultEventColor;
1536
+ const eventContent = renderEvent ? renderEvent(event, "day") : /* @__PURE__ */ jsxs(Fragment, { children: [
1537
+ /* @__PURE__ */ jsx("div", { className: "font-semibold text-sm", style: { color: eventColor }, children: event.title }),
1538
+ /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground mt-0.5", children: [
1539
+ formatDate(event.start, "h:mm a", config.locale),
1540
+ " - ",
1541
+ formatDate(event.end, "h:mm a", config.locale)
1542
+ ] }),
1543
+ event.description && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground mt-1 line-clamp-2", children: event.description })
1544
+ ] });
1545
+ const eventEl = /* @__PURE__ */ jsx(
1546
+ "div",
1547
+ {
1548
+ className: cn(
1549
+ "px-3 py-2 rounded-lg overflow-hidden transition-all hover:shadow-md h-full w-full",
1550
+ !enableDragToMove && "cursor-pointer"
1551
+ ),
1552
+ style: {
1553
+ backgroundColor: `${eventColor}15`,
1554
+ borderLeft: `4px solid ${eventColor}`
1555
+ },
1556
+ title: event.title,
1557
+ onClick: (e) => handleEventClick(e, event),
1558
+ onDoubleClick: (e) => handleEventDoubleClick(e, event),
1559
+ children: eventContent
1560
+ }
1561
+ );
1562
+ return enableDragToMove ? /* @__PURE__ */ jsx(
1563
+ DraggableEvent,
1564
+ {
1565
+ event,
1566
+ className: "absolute left-2 right-2",
1567
+ style: { top: `${top}px`, height: `${height}px` },
1568
+ children: eventEl
1569
+ },
1570
+ event.id
1571
+ ) : /* @__PURE__ */ jsx("div", { className: "absolute left-2 right-2", style: { top: `${top}px`, height: `${height}px` }, children: eventEl }, event.id);
1572
+ })
1573
+ ] })
1574
+ ]
1575
+ }
1576
+ )
1577
+ }
1578
+ )
1579
+ ] });
1580
+ }
1581
+ function ResourceView({ currentDate, events, resources }) {
1582
+ const {
1583
+ config,
1584
+ onEventClick,
1585
+ onEventDoubleClick,
1586
+ onSlotClick,
1587
+ onSlotDoubleClick,
1588
+ renderEvent,
1589
+ enableDragToCreate = false,
1590
+ enableDragToMove = false
1591
+ } = useCalendar();
1592
+ const days = getWeekDays(currentDate, config.weekStartsOn);
1593
+ const hours = getHoursArray(config.startHour, config.endHour);
1594
+ const today = /* @__PURE__ */ new Date();
1595
+ const hourHeight = 56;
1596
+ const totalHours = hours.length;
1597
+ const getResourceDayEvents = (resourceId, day) => {
1598
+ return events.filter(
1599
+ (event) => event.resourceId === resourceId && isSameDay(event.start, day)
1600
+ );
1601
+ };
1602
+ const handleSlotClick = (day, hour, resourceId) => {
1603
+ if (onSlotClick) {
1604
+ const slotInfo = {
1605
+ start: createSlotDate(day, hour),
1606
+ end: createSlotDate(day, hour + 1),
1607
+ resourceId
1608
+ };
1609
+ onSlotClick(slotInfo);
1610
+ }
1611
+ };
1612
+ const handleSlotDoubleClick = (day, hour, resourceId) => {
1613
+ if (onSlotDoubleClick) {
1614
+ const slotInfo = {
1615
+ start: createSlotDate(day, hour),
1616
+ end: createSlotDate(day, hour + 1),
1617
+ resourceId
1618
+ };
1619
+ onSlotDoubleClick(slotInfo);
1620
+ }
1621
+ };
1622
+ const handleEventClick = (e, event) => {
1623
+ e.stopPropagation();
1624
+ onEventClick == null ? void 0 : onEventClick(event);
1625
+ };
1626
+ const handleEventDoubleClick = (e, event) => {
1627
+ e.stopPropagation();
1628
+ onEventDoubleClick == null ? void 0 : onEventDoubleClick(event);
1629
+ };
1630
+ if (resources.length === 0) {
1631
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full text-muted-foreground", children: "No resources available. Add resources to use the resource view." });
1632
+ }
1633
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full overflow-hidden", children: [
1634
+ /* @__PURE__ */ jsxs("div", { className: "flex border-b border-border shrink-0 bg-background z-10", children: [
1635
+ /* @__PURE__ */ jsx("div", { className: "w-16 shrink-0 border-r border-border" }),
1636
+ resources.map((resource) => {
1637
+ const resourceColor = resource.color || config.defaultResourceColor;
1638
+ return /* @__PURE__ */ jsx(
1639
+ "div",
1640
+ {
1641
+ className: "flex-1 py-3 px-2 text-center border-r border-border last:border-r-0",
1642
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2", children: [
1643
+ /* @__PURE__ */ jsx(
1644
+ "div",
1645
+ {
1646
+ className: "w-3 h-3 rounded-full shrink-0",
1647
+ style: { backgroundColor: resourceColor }
1648
+ }
1649
+ ),
1650
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground truncate", children: resource.name })
1651
+ ] })
1652
+ },
1653
+ resource.id
1654
+ );
1655
+ })
1656
+ ] }),
1657
+ /* @__PURE__ */ jsxs("div", { className: "flex border-b border-border shrink-0 bg-muted/50 z-10", children: [
1658
+ /* @__PURE__ */ jsx("div", { className: "w-16 shrink-0 border-r border-border py-2 px-2 text-xs font-medium text-muted-foreground", children: "Week View" }),
1659
+ resources.map((resource) => /* @__PURE__ */ jsx("div", { className: "flex-1 flex border-r border-border last:border-r-0", children: days.map((day, dayIndex) => {
1660
+ const isToday = isSameDay(day, today);
1661
+ return /* @__PURE__ */ jsxs(
1662
+ "div",
1663
+ {
1664
+ className: cn(
1665
+ "flex-1 py-2 text-center border-r border-border/50 last:border-r-0",
1666
+ isToday && "bg-primary/5"
1667
+ ),
1668
+ children: [
1669
+ /* @__PURE__ */ jsx("div", { className: "text-[10px] text-muted-foreground uppercase", children: formatDate(day, "EEE", config.locale) }),
1670
+ /* @__PURE__ */ jsx(
1671
+ "div",
1672
+ {
1673
+ className: cn(
1674
+ "text-xs font-medium",
1675
+ isToday && "text-primary",
1676
+ !isToday && "text-foreground"
1677
+ ),
1678
+ children: formatDate(day, "d", config.locale)
1679
+ }
1680
+ )
1681
+ ]
1682
+ },
1683
+ dayIndex
1684
+ );
1685
+ }) }, resource.id))
1686
+ ] }),
1687
+ /* @__PURE__ */ jsx("div", { className: "flex flex-1 overflow-y-auto min-h-0", children: /* @__PURE__ */ jsxs(
1688
+ "div",
1689
+ {
1690
+ className: "flex min-w-full",
1691
+ style: { minHeight: totalHours * hourHeight },
1692
+ children: [
1693
+ /* @__PURE__ */ jsxs("div", { className: "w-16 shrink-0 border-r border-border relative", children: [
1694
+ hours.map((hour, index) => /* @__PURE__ */ jsx(
1695
+ "div",
1696
+ {
1697
+ className: cn(
1698
+ "pr-2 flex items-start justify-end",
1699
+ index < totalHours - 1 && "border-b border-border"
1700
+ ),
1701
+ style: { height: `${hourHeight}px` },
1702
+ children: /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground -mt-1.5", children: formatHour(hour, config.timeFormat, config.locale) })
1703
+ },
1704
+ hour
1705
+ )),
1706
+ /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 h-px bg-border" })
1707
+ ] }),
1708
+ resources.map((resource) => {
1709
+ const resourceColor = resource.color || config.defaultResourceColor;
1710
+ return /* @__PURE__ */ jsx(
1711
+ "div",
1712
+ {
1713
+ className: "flex-1 flex border-r border-border last:border-r-0",
1714
+ children: days.map((day, dayIndex) => {
1715
+ const dayEvents = getResourceDayEvents(resource.id, day);
1716
+ const isToday = isSameDay(day, today);
1717
+ return /* @__PURE__ */ jsxs(
1718
+ "div",
1719
+ {
1720
+ className: cn(
1721
+ "flex-1 border-r border-border/50 last:border-r-0 relative",
1722
+ isToday && "bg-primary/5"
1723
+ ),
1724
+ children: [
1725
+ hours.map((hour, index) => {
1726
+ const slotData = {
1727
+ view: "resource",
1728
+ day,
1729
+ hour,
1730
+ resourceId: resource.id
1731
+ };
1732
+ const slotContent = /* @__PURE__ */ jsx(
1733
+ "div",
1734
+ {
1735
+ className: cn(
1736
+ "cursor-pointer hover:bg-muted/30 transition-colors h-full w-full",
1737
+ index < totalHours - 1 && "border-b border-border"
1738
+ ),
1739
+ style: { height: `${hourHeight}px` },
1740
+ onClick: () => handleSlotClick(day, hour, resource.id),
1741
+ onDoubleClick: () => handleSlotDoubleClick(day, hour, resource.id)
1742
+ }
1743
+ );
1744
+ return enableDragToCreate || enableDragToMove ? /* @__PURE__ */ jsx(
1745
+ DroppableSlot,
1746
+ {
1747
+ data: slotData,
1748
+ style: { height: `${hourHeight}px` },
1749
+ className: cn(index < totalHours - 1 && "border-b border-border"),
1750
+ children: enableDragToCreate ? /* @__PURE__ */ jsx(DraggableCreateSlot, { data: slotData, className: "h-full w-full", children: slotContent }) : slotContent
1751
+ },
1752
+ hour
1753
+ ) : /* @__PURE__ */ jsx(
1754
+ "div",
1755
+ {
1756
+ className: cn(
1757
+ "cursor-pointer hover:bg-muted/30 transition-colors",
1758
+ index < totalHours - 1 && "border-b border-border"
1759
+ ),
1760
+ style: { height: `${hourHeight}px` },
1761
+ onClick: () => handleSlotClick(day, hour, resource.id),
1762
+ onDoubleClick: () => handleSlotDoubleClick(day, hour, resource.id)
1763
+ },
1764
+ hour
1765
+ );
1766
+ }),
1767
+ /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 h-px bg-border" }),
1768
+ dayEvents.map((event) => {
1769
+ const { top, height } = getEventPosition(event, config.hourHeight, config.startHour);
1770
+ const scaledTop = top / config.hourHeight * hourHeight;
1771
+ const scaledHeight = height / config.hourHeight * hourHeight;
1772
+ const eventColor = event.color || resourceColor;
1773
+ const eventContent = renderEvent ? renderEvent(event, "resource") : /* @__PURE__ */ jsxs(Fragment, { children: [
1774
+ /* @__PURE__ */ jsx("div", { className: "truncate font-semibold", children: event.title }),
1775
+ /* @__PURE__ */ jsx("div", { className: "truncate opacity-80", children: formatDate(event.start, "h:mm", config.locale) })
1776
+ ] });
1777
+ const eventEl = /* @__PURE__ */ jsx(
1778
+ "div",
1779
+ {
1780
+ className: cn(
1781
+ "px-1 py-0.5 rounded text-[10px] font-medium overflow-hidden transition-opacity hover:opacity-90 h-full w-full",
1782
+ !enableDragToMove && "cursor-pointer"
1783
+ ),
1784
+ style: {
1785
+ backgroundColor: `${eventColor}20`,
1786
+ borderLeft: `2px solid ${eventColor}`,
1787
+ color: eventColor
1788
+ },
1789
+ title: `${event.title} - ${resource.name}`,
1790
+ onClick: (e) => handleEventClick(e, event),
1791
+ onDoubleClick: (e) => handleEventDoubleClick(e, event),
1792
+ children: eventContent
1793
+ }
1794
+ );
1795
+ return enableDragToMove ? /* @__PURE__ */ jsx(
1796
+ DraggableEvent,
1797
+ {
1798
+ event,
1799
+ className: "absolute left-0.5 right-0.5",
1800
+ style: { top: `${scaledTop}px`, height: `${scaledHeight}px` },
1801
+ children: eventEl
1802
+ },
1803
+ event.id
1804
+ ) : /* @__PURE__ */ jsx(
1805
+ "div",
1806
+ {
1807
+ className: "absolute left-0.5 right-0.5",
1808
+ style: { top: `${scaledTop}px`, height: `${scaledHeight}px` },
1809
+ children: eventEl
1810
+ },
1811
+ event.id
1812
+ );
1813
+ })
1814
+ ]
1815
+ },
1816
+ dayIndex
1817
+ );
1818
+ })
1819
+ },
1820
+ resource.id
1821
+ );
1822
+ })
1823
+ ]
1824
+ }
1825
+ ) })
1826
+ ] });
1827
+ }
1828
+ function EventCalendar({
1829
+ events,
1830
+ resources = [],
1831
+ date,
1832
+ defaultDate = /* @__PURE__ */ new Date(),
1833
+ view,
1834
+ defaultView = "month",
1835
+ views = ["month", "week", "day", "resource"],
1836
+ calendarOptions = {},
1837
+ showNavigation = true,
1838
+ showViewSwitcher = true,
1839
+ showTodayButton = true,
1840
+ className,
1841
+ loading = false,
1842
+ theme = "system",
1843
+ themeVars,
1844
+ enableDragToCreate = false,
1845
+ enableDragToMove = false,
1846
+ mobileBreakpoint = "md",
1847
+ showMobileMonthPicker = true,
1848
+ onEventClick,
1849
+ onEventDoubleClick,
1850
+ onSlotClick,
1851
+ onSlotDoubleClick,
1852
+ onDateChange,
1853
+ onViewChange,
1854
+ onNavigate,
1855
+ onRangeChange,
1856
+ onDayClick,
1857
+ onEventCreate,
1858
+ onEventMove,
1859
+ renderEvent,
1860
+ renderDayCell,
1861
+ renderHeader,
1862
+ renderResourceHeader,
1863
+ renderTimeSlot,
1864
+ renderEmpty,
1865
+ renderSelectedEvent,
1866
+ renderSelectedSlot
1867
+ }) {
1868
+ const [internalDate, setInternalDate] = useState(defaultDate);
1869
+ const [selectedEvent, setSelectedEvent] = useState(null);
1870
+ const [selectedSlot, setSelectedSlot] = useState(null);
1871
+ const currentDate = date != null ? date : internalDate;
1872
+ const isMobile = useMediaQuery(mobileBreakpoint);
1873
+ const [internalView, setInternalView] = useState(defaultView);
1874
+ const currentView = view != null ? view : internalView;
1875
+ const config = useMemo(
1876
+ () => {
1877
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
1878
+ return {
1879
+ weekStartsOn: (_a = calendarOptions.weekStartsOn) != null ? _a : defaultConfig.weekStartsOn,
1880
+ startHour: (_b = calendarOptions.startHour) != null ? _b : defaultConfig.startHour,
1881
+ endHour: (_c = calendarOptions.endHour) != null ? _c : defaultConfig.endHour,
1882
+ hourHeight: (_d = calendarOptions.hourHeight) != null ? _d : defaultConfig.hourHeight,
1883
+ maxEventsPerDay: (_e = calendarOptions.maxEventsPerDay) != null ? _e : defaultConfig.maxEventsPerDay,
1884
+ timeSeriesMaxHeight: (_f = calendarOptions.timeSeriesMaxHeight) != null ? _f : defaultConfig.timeSeriesMaxHeight,
1885
+ showCurrentTimeLine: (_g = calendarOptions.showCurrentTimeLine) != null ? _g : defaultConfig.showCurrentTimeLine,
1886
+ defaultEventColor: (_h = calendarOptions.defaultEventColor) != null ? _h : defaultConfig.defaultEventColor,
1887
+ defaultResourceColor: (_i = calendarOptions.defaultResourceColor) != null ? _i : defaultConfig.defaultResourceColor,
1888
+ locale: (_j = calendarOptions.locale) != null ? _j : defaultConfig.locale,
1889
+ timeFormat: (_k = calendarOptions.timeFormat) != null ? _k : defaultConfig.timeFormat
1890
+ };
1891
+ },
1892
+ [calendarOptions]
1893
+ );
1894
+ const effectiveView = isMobile ? ["week", "day", "2day"].includes(currentView) ? currentView : "week" : currentView;
1895
+ const handleNavigate = useCallback(
1896
+ (direction) => {
1897
+ const newDate = navigateDate(currentDate, effectiveView, direction);
1898
+ if (date === void 0) {
1899
+ setInternalDate(newDate);
1900
+ }
1901
+ onDateChange == null ? void 0 : onDateChange(newDate);
1902
+ onNavigate == null ? void 0 : onNavigate(newDate, direction);
1903
+ },
1904
+ [currentDate, effectiveView, date, onDateChange, onNavigate]
1905
+ );
1906
+ const handleWeekNavigate = useCallback(
1907
+ (direction) => {
1908
+ const newDate = direction === "next" ? addWeeks(currentDate, 1) : subWeeks(currentDate, 1);
1909
+ if (date === void 0) {
1910
+ setInternalDate(newDate);
1911
+ }
1912
+ onDateChange == null ? void 0 : onDateChange(newDate);
1913
+ },
1914
+ [currentDate, date, onDateChange]
1915
+ );
1916
+ const handleDateSelect = useCallback(
1917
+ (newDate) => {
1918
+ if (date === void 0) {
1919
+ setInternalDate(newDate);
1920
+ }
1921
+ onDateChange == null ? void 0 : onDateChange(newDate);
1922
+ },
1923
+ [date, onDateChange]
1924
+ );
1925
+ const handleToday = useCallback(() => {
1926
+ const today = /* @__PURE__ */ new Date();
1927
+ if (date === void 0) {
1928
+ setInternalDate(today);
1929
+ }
1930
+ onDateChange == null ? void 0 : onDateChange(today);
1931
+ onNavigate == null ? void 0 : onNavigate(today, "today");
1932
+ }, [date, onDateChange, onNavigate]);
1933
+ const handleViewChange = useCallback(
1934
+ (newView) => {
1935
+ if (view === void 0) {
1936
+ setInternalView(newView);
1937
+ }
1938
+ onViewChange == null ? void 0 : onViewChange(newView);
1939
+ },
1940
+ [view, onViewChange]
1941
+ );
1942
+ const handleEventClick = useCallback(
1943
+ (event) => {
1944
+ onEventClick == null ? void 0 : onEventClick(event);
1945
+ if (renderSelectedEvent) {
1946
+ setSelectedEvent(event);
1947
+ setSelectedSlot(null);
1948
+ }
1949
+ },
1950
+ [onEventClick, renderSelectedEvent]
1951
+ );
1952
+ const handleSlotClick = useCallback(
1953
+ (slot) => {
1954
+ onSlotClick == null ? void 0 : onSlotClick(slot);
1955
+ if (renderSelectedSlot) {
1956
+ setSelectedSlot(slot);
1957
+ setSelectedEvent(null);
1958
+ }
1959
+ },
1960
+ [onSlotClick, renderSelectedSlot]
1961
+ );
1962
+ const handleCloseSelected = useCallback(() => {
1963
+ setSelectedEvent(null);
1964
+ setSelectedSlot(null);
1965
+ }, []);
1966
+ const contextValue = useMemo(
1967
+ () => ({
1968
+ currentDate,
1969
+ view: currentView,
1970
+ events,
1971
+ resources,
1972
+ config,
1973
+ onEventClick: renderSelectedEvent ? handleEventClick : onEventClick,
1974
+ onEventDoubleClick,
1975
+ onSlotClick: renderSelectedSlot ? handleSlotClick : onSlotClick,
1976
+ onSlotDoubleClick,
1977
+ onDayClick,
1978
+ renderEvent,
1979
+ enableDragToCreate,
1980
+ enableDragToMove,
1981
+ onEventCreate,
1982
+ onEventMove
1983
+ }),
1984
+ [
1985
+ currentDate,
1986
+ currentView,
1987
+ events,
1988
+ resources,
1989
+ config,
1990
+ renderSelectedEvent,
1991
+ renderSelectedSlot,
1992
+ handleEventClick,
1993
+ handleSlotClick,
1994
+ onEventClick,
1995
+ onEventDoubleClick,
1996
+ onSlotClick,
1997
+ onSlotDoubleClick,
1998
+ onDayClick,
1999
+ renderEvent,
2000
+ enableDragToCreate,
2001
+ enableDragToMove,
2002
+ onEventCreate,
2003
+ onEventMove
2004
+ ]
2005
+ );
2006
+ const mobileViews = ["week", "day", "2day"];
2007
+ const title = getViewTitle(currentDate, effectiveView, config.weekStartsOn, config.locale);
2008
+ useEffect(() => {
2009
+ if (onRangeChange) {
2010
+ const range = getViewDateRange(currentDate, effectiveView, config.weekStartsOn);
2011
+ onRangeChange(range);
2012
+ }
2013
+ }, [currentDate, effectiveView, config.weekStartsOn, onRangeChange]);
2014
+ const availableViews = (isMobile ? mobileViews : views).filter((v) => {
2015
+ if (v === "resource" && resources.length === 0) return false;
2016
+ return true;
2017
+ });
2018
+ const calendarContent = /* @__PURE__ */ jsx(CalendarProvider, { value: contextValue, children: /* @__PURE__ */ jsxs(
2019
+ Card,
2020
+ {
2021
+ className: cn(
2022
+ "flex flex-col h-full overflow-hidden border-border",
2023
+ isMobile && "gap-0 py-0",
2024
+ className
2025
+ ),
2026
+ children: [
2027
+ (showNavigation || showViewSwitcher) && /* @__PURE__ */ jsx(
2028
+ "div",
2029
+ {
2030
+ className: cn(
2031
+ "bg-card",
2032
+ isMobile ? "px-3 py-2 border-b border-border relative z-10 rounded-t-xl shadow-[0_4px_24px_rgba(0,0,0,0.12),0_8px_40px_rgba(0,0,0,0.1),0_12px_48px_rgba(0,0,0,0.08)] dark:shadow-[0_4px_24px_rgba(0,0,0,0.35),0_8px_40px_rgba(0,0,0,0.3),0_12px_48px_rgba(0,0,0,0.2)]" : "p-4 border-b border-border"
2033
+ ),
2034
+ children: /* @__PURE__ */ jsx(
2035
+ CalendarHeader,
2036
+ {
2037
+ currentDate,
2038
+ view: effectiveView,
2039
+ views: availableViews,
2040
+ title,
2041
+ showNavigation,
2042
+ showViewSwitcher,
2043
+ showTodayButton,
2044
+ isMobile,
2045
+ showMobileMonthPicker,
2046
+ weekStartsOn: config.weekStartsOn,
2047
+ locale: config.locale,
2048
+ onNavigate: handleNavigate,
2049
+ onViewChange: handleViewChange,
2050
+ onToday: handleToday,
2051
+ onDateSelect: isMobile ? handleDateSelect : void 0,
2052
+ onWeekNavigate: isMobile ? handleWeekNavigate : void 0,
2053
+ renderHeader
2054
+ }
2055
+ )
2056
+ }
2057
+ ),
2058
+ /* @__PURE__ */ jsxs(
2059
+ "div",
2060
+ {
2061
+ className: cn(
2062
+ "flex-1 overflow-hidden relative",
2063
+ isMobile && "rounded-b-xl bg-background shadow-[inset_0_8px_12px_-6px_rgba(0,0,0,0.1),0_2px_8px_rgba(0,0,0,0.06)] dark:shadow-[inset_0_8px_12px_-6px_rgba(0,0,0,0.25),0_2px_8px_rgba(0,0,0,0.15)]"
2064
+ ),
2065
+ children: [
2066
+ loading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-background/50 flex items-center justify-center z-20", children: /* @__PURE__ */ jsx(Spinner, { className: "h-8 w-8" }) }),
2067
+ events.length === 0 && renderEmpty ? renderEmpty() : /* @__PURE__ */ jsxs(Fragment, { children: [
2068
+ effectiveView === "month" && /* @__PURE__ */ jsx(MonthView, { currentDate, events }),
2069
+ (effectiveView === "week" || effectiveView === "2day") && /* @__PURE__ */ jsx(
2070
+ WeekView,
2071
+ {
2072
+ currentDate,
2073
+ events,
2074
+ dayCount: effectiveView === "2day" ? 2 : void 0,
2075
+ isMobile
2076
+ }
2077
+ ),
2078
+ effectiveView === "day" && /* @__PURE__ */ jsx(DayView, { currentDate, events }),
2079
+ effectiveView === "resource" && /* @__PURE__ */ jsx(
2080
+ ResourceView,
2081
+ {
2082
+ currentDate,
2083
+ events,
2084
+ resources
2085
+ }
2086
+ )
2087
+ ] })
2088
+ ]
2089
+ }
2090
+ ),
2091
+ renderSelectedEvent && /* @__PURE__ */ jsx(Fragment, { children: renderSelectedEvent({
2092
+ event: selectedEvent,
2093
+ onClose: handleCloseSelected
2094
+ }) }),
2095
+ renderSelectedSlot && /* @__PURE__ */ jsx(Fragment, { children: renderSelectedSlot({
2096
+ slot: selectedSlot,
2097
+ onClose: handleCloseSelected
2098
+ }) })
2099
+ ]
2100
+ }
2101
+ ) });
2102
+ const wrappedContent = enableDragToCreate || enableDragToMove ? /* @__PURE__ */ jsx(
2103
+ EventCalendarDndProvider,
2104
+ {
2105
+ enableDragToCreate,
2106
+ enableDragToMove,
2107
+ onEventCreate,
2108
+ onEventMove,
2109
+ defaultEventColor: config.defaultEventColor,
2110
+ locale: config.locale,
2111
+ renderEvent,
2112
+ children: calendarContent
2113
+ }
2114
+ ) : calendarContent;
2115
+ if (theme || themeVars) {
2116
+ return /* @__PURE__ */ jsx(
2117
+ EventCalendarThemeProvider,
2118
+ {
2119
+ theme: theme != null ? theme : "system",
2120
+ themeVars,
2121
+ children: wrappedContent
2122
+ }
2123
+ );
2124
+ }
2125
+ return wrappedContent;
2126
+ }
2127
+
2128
+ export { EventCalendar, EventCalendarThemeProvider, createSlotDate, formatDate, formatHour, getDateFnsLocale, getEventsForDay, getHoursArray, getMonthDays, getViewDateRange, getViewTitle, getWeekDays, navigateDate, useCalendar };
2129
+ //# sourceMappingURL=index.mjs.map
2130
+ //# sourceMappingURL=index.mjs.map