@hectorbliss/denik-calendar 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # denik-calendar
2
+
3
+ A **React** drag-and-drop week calendar component with overlap detection.
4
+
5
+ Built with [@dnd-kit](https://dndkit.com/) for smooth drag interactions.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install denik-calendar
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```tsx
16
+ import { Calendar } from "denik-calendar";
17
+
18
+ function App() {
19
+ const [events, setEvents] = useState([
20
+ { id: "1", start: new Date(), duration: 60, title: "Meeting" },
21
+ ]);
22
+
23
+ return (
24
+ <Calendar
25
+ events={events}
26
+ onEventMove={(eventId, newStart) => {
27
+ // Handle event move
28
+ setEvents(prev =>
29
+ prev.map(e => (e.id === eventId ? { ...e, start: newStart } : e))
30
+ );
31
+ }}
32
+ onAddBlock={(start) => {
33
+ // Handle block creation
34
+ }}
35
+ onRemoveBlock={(eventId) => {
36
+ // Handle block removal
37
+ }}
38
+ onEventClick={(event) => {
39
+ // Handle event click
40
+ }}
41
+ />
42
+ );
43
+ }
44
+ ```
45
+
46
+ ## Headless Hook
47
+
48
+ Use the overlap detection logic without the UI:
49
+
50
+ ```tsx
51
+ import { useEventOverlap } from "denik-calendar";
52
+
53
+ function MyCustomCalendar({ events }) {
54
+ const { canMove, hasOverlap, findConflicts } = useEventOverlap(events);
55
+
56
+ const handleDrop = (eventId, newStart) => {
57
+ if (canMove(eventId, newStart)) {
58
+ // Safe to move
59
+ updateEvent(eventId, newStart);
60
+ } else {
61
+ // Conflict detected
62
+ toast.error("Time slot occupied");
63
+ }
64
+ };
65
+ }
66
+ ```
67
+
68
+ ## Props
69
+
70
+ ### Calendar
71
+
72
+ | Prop | Type | Description |
73
+ |------|------|-------------|
74
+ | `events` | `CalendarEvent[]` | Array of events to display |
75
+ | `date` | `Date` | Current week to display (default: today) |
76
+ | `onEventMove` | `(eventId, newStart) => void` | Called when event is dragged |
77
+ | `onAddBlock` | `(start) => void` | Called when creating a block |
78
+ | `onRemoveBlock` | `(eventId) => void` | Called when removing a block |
79
+ | `onEventClick` | `(event) => void` | Called when clicking an event |
80
+ | `onNewEvent` | `(start) => void` | Called when clicking empty slot |
81
+ | `config` | `CalendarConfig` | Configuration options |
82
+
83
+ ### CalendarEvent
84
+
85
+ ```typescript
86
+ interface CalendarEvent {
87
+ id: string;
88
+ start: Date;
89
+ duration: number; // minutes
90
+ title?: string;
91
+ type?: "BLOCK" | "EVENT";
92
+ service?: { name: string };
93
+ }
94
+ ```
95
+
96
+ ### CalendarConfig
97
+
98
+ ```typescript
99
+ interface CalendarConfig {
100
+ locale?: string; // default: "es-MX"
101
+ icons?: {
102
+ trash?: ReactNode;
103
+ edit?: ReactNode;
104
+ close?: ReactNode;
105
+ };
106
+ }
107
+ ```
108
+
109
+ ## Features
110
+
111
+ - Drag & drop events between time slots
112
+ - Automatic overlap detection
113
+ - Block time slots
114
+ - Week navigation
115
+ - Auto-scroll to current hour
116
+ - Customizable icons
117
+ - Locale support
118
+ - TypeScript support
119
+
120
+ ## Peer Dependencies
121
+
122
+ - React 18+ or 19+
123
+
124
+ ## License
125
+
126
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,585 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var core = require('@dnd-kit/core');
5
+ var utilities = require('@dnd-kit/utilities');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+
8
+ // src/Calendar.tsx
9
+ function useEventOverlap(events) {
10
+ const hasOverlap = react.useCallback(
11
+ (start, duration, excludeId) => {
12
+ const hour = start.getHours() + start.getMinutes() / 60;
13
+ const endHour = hour + duration / 60;
14
+ return events.some((existing) => {
15
+ if (excludeId && existing.id === excludeId) return false;
16
+ const existingStart = new Date(existing.start);
17
+ if (existingStart.getDate() !== start.getDate() || existingStart.getMonth() !== start.getMonth() || existingStart.getFullYear() !== start.getFullYear()) {
18
+ return false;
19
+ }
20
+ const existingHour = existingStart.getHours() + existingStart.getMinutes() / 60;
21
+ const existingEnd = existingHour + existing.duration / 60;
22
+ return hour >= existingHour && hour < existingEnd || endHour > existingHour && endHour <= existingEnd || hour <= existingHour && endHour >= existingEnd;
23
+ });
24
+ },
25
+ [events]
26
+ );
27
+ const findConflicts = react.useCallback(
28
+ (start, duration, excludeId) => {
29
+ const hour = start.getHours() + start.getMinutes() / 60;
30
+ const endHour = hour + duration / 60;
31
+ return events.filter((existing) => {
32
+ if (excludeId && existing.id === excludeId) return false;
33
+ const existingStart = new Date(existing.start);
34
+ if (existingStart.getDate() !== start.getDate() || existingStart.getMonth() !== start.getMonth() || existingStart.getFullYear() !== start.getFullYear()) {
35
+ return false;
36
+ }
37
+ const existingHour = existingStart.getHours() + existingStart.getMinutes() / 60;
38
+ const existingEnd = existingHour + existing.duration / 60;
39
+ return hour >= existingHour && hour < existingEnd || endHour > existingHour && endHour <= existingEnd || hour <= existingHour && endHour >= existingEnd;
40
+ });
41
+ },
42
+ [events]
43
+ );
44
+ const canMove = react.useCallback(
45
+ (eventId, newStart) => {
46
+ const event = events.find((e) => e.id === eventId);
47
+ if (!event) return false;
48
+ return !hasOverlap(newStart, event.duration, eventId);
49
+ },
50
+ [events, hasOverlap]
51
+ );
52
+ const getEventsForDay = react.useMemo(() => {
53
+ return (date) => {
54
+ return events.filter((event) => {
55
+ const eventDate = new Date(event.start);
56
+ return eventDate.getDate() === date.getDate() && eventDate.getMonth() === date.getMonth() && eventDate.getFullYear() === date.getFullYear();
57
+ });
58
+ };
59
+ }, [events]);
60
+ return {
61
+ hasOverlap,
62
+ findConflicts,
63
+ canMove,
64
+ getEventsForDay
65
+ };
66
+ }
67
+ function useClickOutside({
68
+ isActive,
69
+ onOutsideClick
70
+ }) {
71
+ const ref = react.useRef(null);
72
+ react.useEffect(() => {
73
+ if (!isActive) return;
74
+ const handleClickOutside = (event) => {
75
+ if (ref.current && !ref.current.contains(event.target)) {
76
+ onOutsideClick();
77
+ }
78
+ };
79
+ document.addEventListener("mousedown", handleClickOutside);
80
+ return () => {
81
+ document.removeEventListener("mousedown", handleClickOutside);
82
+ };
83
+ }, [isActive, onOutsideClick]);
84
+ return ref;
85
+ }
86
+ function formatDate(date, locale = "es-MX") {
87
+ return new Date(date).toLocaleDateString(locale, {
88
+ weekday: "long",
89
+ year: "numeric",
90
+ month: "long",
91
+ day: "numeric",
92
+ hour: "2-digit",
93
+ minute: "2-digit"
94
+ });
95
+ }
96
+
97
+ // src/utils.ts
98
+ var getMonday = (today = /* @__PURE__ */ new Date()) => {
99
+ const d = new Date(today);
100
+ const day = d.getDay();
101
+ const diff = d.getDate() - day + (day === 0 ? -6 : 1);
102
+ return new Date(d.setDate(diff));
103
+ };
104
+ var completeWeek = (date) => {
105
+ const startDate = new Date(date);
106
+ const day = startDate.getDay();
107
+ const offset = day === 0 ? -6 : 1 - day;
108
+ startDate.setDate(startDate.getDate() + offset);
109
+ return Array.from({ length: 7 }, (_, i) => {
110
+ const d = new Date(startDate);
111
+ d.setDate(d.getDate() + i);
112
+ return d;
113
+ });
114
+ };
115
+ var generateHours = ({
116
+ fromHour,
117
+ toHour
118
+ }) => {
119
+ return Array.from({ length: toHour - fromHour }, (_, index) => {
120
+ const hour = fromHour + index;
121
+ return hour < 10 ? `0${hour}:00` : `${hour}:00`;
122
+ });
123
+ };
124
+ var isToday = (date) => {
125
+ const today = /* @__PURE__ */ new Date();
126
+ return date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear();
127
+ };
128
+ var areSameDates = (d1, d2) => {
129
+ if (!d1 || !d2) return false;
130
+ return d1.getDate() === d2.getDate() && d1.getMonth() === d2.getMonth() && d1.getFullYear() === d2.getFullYear();
131
+ };
132
+ var addDaysToDate = (date, days) => {
133
+ const result = new Date(date);
134
+ result.setDate(result.getDate() + days);
135
+ return result;
136
+ };
137
+ var addMinutesToDate = (date, mins) => {
138
+ const result = new Date(date);
139
+ result.setMinutes(result.getMinutes() + mins);
140
+ return result;
141
+ };
142
+ var fromMinsToTimeString = (mins) => {
143
+ const h = Math.floor(mins / 60);
144
+ const m = mins % 60;
145
+ return `${h < 10 ? "0" + h : h}:${m < 10 ? "0" + m : m}`;
146
+ };
147
+ var fromMinsToLocaleTimeString = (mins, locale = "es-MX") => {
148
+ const h = Math.floor(mins / 60);
149
+ const m = mins % 60;
150
+ const today = /* @__PURE__ */ new Date();
151
+ today.setHours(h, m, 0, 0);
152
+ return today.toLocaleTimeString(locale);
153
+ };
154
+ var fromDateToTimeString = (date, locale = "es-MX") => {
155
+ return new Date(date).toLocaleTimeString(locale);
156
+ };
157
+ var getDaysInMonth = (date) => {
158
+ const firstDay = new Date(date.getFullYear(), date.getMonth(), 1);
159
+ const lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 0);
160
+ const numberOfMissing = 6 - lastDay.getDay();
161
+ const leftOffset = firstDay.getDay();
162
+ firstDay.setDate(firstDay.getDate() - leftOffset);
163
+ const days = [];
164
+ days.push(new Date(firstDay));
165
+ while (firstDay < lastDay) {
166
+ firstDay.setDate(firstDay.getDate() + 1);
167
+ days.push(new Date(firstDay));
168
+ }
169
+ for (let i = 0; i < numberOfMissing; i++) {
170
+ firstDay.setDate(firstDay.getDate() + 1);
171
+ days.push(new Date(firstDay));
172
+ }
173
+ return days;
174
+ };
175
+ var cn = (...classes) => classes.filter(Boolean).join(" ");
176
+ var DefaultTrashIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", className: "w-4 h-4", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" }) });
177
+ var DefaultEditIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", className: "w-4 h-4", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" }) });
178
+ var DefaultCloseIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", className: "w-4 h-4", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" }) });
179
+ var DayHeader = ({ date, locale }) => /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "grid place-items-center", children: [
180
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "capitalize", children: date.toLocaleDateString(locale, { weekday: "short" }) }),
181
+ /* @__PURE__ */ jsxRuntime.jsx(
182
+ "span",
183
+ {
184
+ className: cn(
185
+ isToday(date) && "bg-blue-500 rounded-full p-1 text-white"
186
+ ),
187
+ children: date.getDate()
188
+ }
189
+ )
190
+ ] });
191
+ function Calendar({
192
+ date = /* @__PURE__ */ new Date(),
193
+ events = [],
194
+ onEventClick,
195
+ onNewEvent,
196
+ onEventMove,
197
+ onAddBlock,
198
+ onRemoveBlock,
199
+ config = {}
200
+ }) {
201
+ const { locale = "es-MX", icons = {} } = config;
202
+ const week = completeWeek(date);
203
+ const [activeId, setActiveId] = react.useState(null);
204
+ const { canMove } = useEventOverlap(events);
205
+ const sensors = core.useSensors(
206
+ core.useSensor(core.PointerSensor, {
207
+ activationConstraint: { distance: 8 }
208
+ }),
209
+ core.useSensor(core.KeyboardSensor)
210
+ );
211
+ const handleDragStart = (event) => {
212
+ setActiveId(event.active.id);
213
+ };
214
+ const handleDragEnd = (event) => {
215
+ const { active, over } = event;
216
+ setActiveId(null);
217
+ if (!over) return;
218
+ const eventId = active.id.toString().replace("event-", "");
219
+ const [, dayIndexStr, hourStr] = over.id.toString().split("-");
220
+ const dayIndex = parseInt(dayIndexStr);
221
+ const hour = parseInt(hourStr);
222
+ const targetDay = week[dayIndex];
223
+ const newStart = new Date(targetDay);
224
+ newStart.setHours(hour, 0, 0, 0);
225
+ const movedEvent = events.find((e) => e.id === eventId);
226
+ if (!movedEvent) return;
227
+ const currentStart = new Date(movedEvent.start);
228
+ if (currentStart.getDate() === newStart.getDate() && currentStart.getHours() === newStart.getHours()) {
229
+ return;
230
+ }
231
+ if (!canMove(eventId, newStart)) {
232
+ console.warn("Cannot move event: time slot is occupied");
233
+ return;
234
+ }
235
+ onEventMove?.(eventId, newStart);
236
+ };
237
+ const handleDragCancel = () => {
238
+ setActiveId(null);
239
+ };
240
+ const activeEvent = activeId ? events.find((e) => `event-${e.id}` === activeId) : null;
241
+ return /* @__PURE__ */ jsxRuntime.jsxs(
242
+ core.DndContext,
243
+ {
244
+ sensors,
245
+ collisionDetection: core.closestCenter,
246
+ onDragStart: handleDragStart,
247
+ onDragEnd: handleDragEnd,
248
+ onDragCancel: handleDragCancel,
249
+ children: [
250
+ /* @__PURE__ */ jsxRuntime.jsxs("article", { className: "w-full bg-white shadow rounded-xl", children: [
251
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "grid grid-cols-8 place-items-center py-4", children: [
252
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500", children: Intl.DateTimeFormat().resolvedOptions().timeZone }) }),
253
+ week.map((day) => /* @__PURE__ */ jsxRuntime.jsx(DayHeader, { date: day, locale }, day.toISOString()))
254
+ ] }),
255
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "grid grid-cols-8 max-h-[80vh] overflow-y-auto", children: [
256
+ /* @__PURE__ */ jsxRuntime.jsx(TimeColumn, {}),
257
+ week.map((dayOfWeek, dayIndex) => /* @__PURE__ */ jsxRuntime.jsx(
258
+ Column,
259
+ {
260
+ dayIndex,
261
+ dayOfWeek,
262
+ events: events.filter((event) => {
263
+ const eventDate = new Date(event.start);
264
+ return eventDate.getDate() === dayOfWeek.getDate() && eventDate.getMonth() === dayOfWeek.getMonth();
265
+ }),
266
+ onNewEvent,
267
+ onAddBlock,
268
+ onRemoveBlock,
269
+ onEventClick,
270
+ locale,
271
+ icons
272
+ },
273
+ dayOfWeek.toISOString()
274
+ ))
275
+ ] })
276
+ ] }),
277
+ /* @__PURE__ */ jsxRuntime.jsx(core.DragOverlay, { children: activeEvent ? /* @__PURE__ */ jsxRuntime.jsx(EventOverlay, { event: activeEvent }) : null })
278
+ ]
279
+ }
280
+ );
281
+ }
282
+ var Cell = ({
283
+ date,
284
+ hours,
285
+ children,
286
+ onClick,
287
+ className,
288
+ dayIndex
289
+ }) => {
290
+ const isTodayCell = date ? isToday(date) : false;
291
+ const isThisHour = isTodayCell && hours === (/* @__PURE__ */ new Date()).getHours();
292
+ const { setNodeRef, isOver } = core.useDroppable({
293
+ id: dayIndex !== void 0 ? `cell-${dayIndex}-${hours}` : `time-${hours}`,
294
+ disabled: dayIndex === void 0
295
+ });
296
+ return /* @__PURE__ */ jsxRuntime.jsx(
297
+ "div",
298
+ {
299
+ ref: setNodeRef,
300
+ tabIndex: 0,
301
+ onKeyDown: (e) => e.code === "Space" && onClick?.(),
302
+ onClick,
303
+ role: "button",
304
+ className: cn(
305
+ "bg-slate-50 w-full h-16 border-gray-300 border-[.5px] border-dashed text-gray-500 flex justify-center items-start relative cursor-pointer",
306
+ isTodayCell && isThisHour && "border-t-2 border-t-blue-500",
307
+ isOver && dayIndex !== void 0 && "bg-blue-100",
308
+ className
309
+ ),
310
+ children: children || hours
311
+ }
312
+ );
313
+ };
314
+ var EmptyButton = ({
315
+ hours,
316
+ date,
317
+ onNewEvent,
318
+ onAddBlock
319
+ }) => {
320
+ const d = new Date(date);
321
+ d.setHours(hours, 0, 0, 0);
322
+ const [show, setShow] = react.useState(false);
323
+ const buttonRef = react.useRef(null);
324
+ const outsideRef = useClickOutside({
325
+ isActive: show,
326
+ onOutsideClick: () => setShow(false)
327
+ });
328
+ const handleReserva = (event) => {
329
+ event.stopPropagation();
330
+ onNewEvent?.(d);
331
+ setShow(false);
332
+ };
333
+ return /* @__PURE__ */ jsxRuntime.jsx(
334
+ "div",
335
+ {
336
+ role: "button",
337
+ tabIndex: 0,
338
+ ref: buttonRef,
339
+ className: "w-full h-full text-xs hover:bg-blue-50 relative",
340
+ onClick: () => setShow(true),
341
+ children: show && /* @__PURE__ */ jsxRuntime.jsxs(
342
+ "div",
343
+ {
344
+ ref: outsideRef,
345
+ className: "absolute border bg-white rounded-lg grid p-1 bottom-[-100%] left-0 z-20 shadow-lg",
346
+ children: [
347
+ /* @__PURE__ */ jsxRuntime.jsx(
348
+ "button",
349
+ {
350
+ onClick: handleReserva,
351
+ className: "hover:bg-blue-50 px-4 py-2 rounded-lg text-left",
352
+ children: "Reserve"
353
+ }
354
+ ),
355
+ /* @__PURE__ */ jsxRuntime.jsx(
356
+ "button",
357
+ {
358
+ onClick: (e) => {
359
+ e.stopPropagation();
360
+ onAddBlock?.(d);
361
+ setShow(false);
362
+ },
363
+ className: "hover:bg-blue-50 px-4 py-2 rounded-lg text-left",
364
+ children: "Block"
365
+ }
366
+ )
367
+ ]
368
+ }
369
+ )
370
+ }
371
+ );
372
+ };
373
+ var Column = ({
374
+ onEventClick,
375
+ events = [],
376
+ dayOfWeek,
377
+ onNewEvent,
378
+ onAddBlock,
379
+ onRemoveBlock,
380
+ dayIndex,
381
+ locale,
382
+ icons
383
+ }) => {
384
+ const columnRef = react.useRef(null);
385
+ react.useEffect(() => {
386
+ if (!columnRef.current) return;
387
+ const today = /* @__PURE__ */ new Date();
388
+ const isColumnToday = dayOfWeek.getDate() === today.getDate() && dayOfWeek.getMonth() === today.getMonth() && dayOfWeek.getFullYear() === today.getFullYear();
389
+ if (!isColumnToday) return;
390
+ const currentHour = today.getHours();
391
+ const currentCell = columnRef.current.children[currentHour];
392
+ if (currentCell) {
393
+ setTimeout(() => {
394
+ currentCell.scrollIntoView({ behavior: "smooth", block: "center" });
395
+ }, 100);
396
+ }
397
+ }, [dayOfWeek]);
398
+ const findEvent = (hours) => {
399
+ const eventStartsHere = events.find(
400
+ (event) => new Date(event.start).getHours() === hours
401
+ );
402
+ if (eventStartsHere) {
403
+ return /* @__PURE__ */ jsxRuntime.jsx(
404
+ DraggableEvent,
405
+ {
406
+ onClick: () => onEventClick?.(eventStartsHere),
407
+ event: eventStartsHere,
408
+ onRemoveBlock,
409
+ locale,
410
+ icons
411
+ }
412
+ );
413
+ }
414
+ const eventSpansHere = events.find((event) => {
415
+ const eventStart = new Date(event.start);
416
+ const startHour = eventStart.getHours();
417
+ const endHour = startHour + event.duration / 60;
418
+ return hours > startHour && hours < endHour;
419
+ });
420
+ if (eventSpansHere) return null;
421
+ return /* @__PURE__ */ jsxRuntime.jsx(
422
+ EmptyButton,
423
+ {
424
+ hours,
425
+ date: dayOfWeek,
426
+ onNewEvent,
427
+ onAddBlock
428
+ }
429
+ );
430
+ };
431
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: columnRef, className: "grid", children: Array.from({ length: 24 }, (_, hours) => /* @__PURE__ */ jsxRuntime.jsx(
432
+ Cell,
433
+ {
434
+ hours,
435
+ date: dayOfWeek,
436
+ className: "relative",
437
+ dayIndex,
438
+ children: findEvent(hours)
439
+ },
440
+ hours
441
+ )) });
442
+ };
443
+ var TimeColumn = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid", children: Array.from({ length: 24 }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx(Cell, { children: `${i < 10 ? "0" : ""}${i}:00` }, i)) });
444
+ var DraggableEvent = ({
445
+ event,
446
+ onClick,
447
+ onRemoveBlock,
448
+ locale,
449
+ icons
450
+ }) => {
451
+ const [showOptions, setShowOptions] = react.useState(false);
452
+ const { attributes, listeners, setNodeRef, transform, isDragging } = core.useDraggable({
453
+ id: `event-${event.id}`,
454
+ disabled: event.type === "BLOCK"
455
+ });
456
+ const style = {
457
+ height: event.duration / 60 * 64,
458
+ transform: transform ? utilities.CSS.Translate.toString(transform) : void 0
459
+ };
460
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
461
+ /* @__PURE__ */ jsxRuntime.jsxs(
462
+ "button",
463
+ {
464
+ ref: setNodeRef,
465
+ style,
466
+ onClick: event.type === "BLOCK" ? () => setShowOptions(true) : () => onClick?.(event),
467
+ ...listeners,
468
+ ...attributes,
469
+ className: cn(
470
+ "border grid gap-y-1 overflow-hidden place-content-start",
471
+ "text-xs text-left pl-1 absolute top-0 left-0 bg-blue-500 text-white rounded-md z-10 w-[90%]",
472
+ event.type === "BLOCK" && "bg-gray-300 h-full w-full text-center cursor-not-allowed relative p-0",
473
+ event.type !== "BLOCK" && "cursor-grab",
474
+ isDragging && event.type !== "BLOCK" && "cursor-grabbing opacity-50"
475
+ ),
476
+ children: [
477
+ event.type === "BLOCK" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-0 bottom-0 w-1 bg-gray-500 rounded-l-full pointer-events-none" }),
478
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: event.title }),
479
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-300", children: event.service?.name })
480
+ ]
481
+ }
482
+ ),
483
+ /* @__PURE__ */ jsxRuntime.jsx(
484
+ Options,
485
+ {
486
+ event,
487
+ onClose: () => setShowOptions(false),
488
+ isOpen: showOptions,
489
+ onRemoveBlock,
490
+ locale,
491
+ icons
492
+ }
493
+ )
494
+ ] });
495
+ };
496
+ var EventOverlay = ({ event }) => /* @__PURE__ */ jsxRuntime.jsxs(
497
+ "div",
498
+ {
499
+ className: cn(
500
+ "border grid gap-y-1 overflow-hidden place-content-start",
501
+ "text-xs text-left pl-1 bg-blue-500 text-white rounded-md w-[200px] opacity-90 shadow-lg",
502
+ event.type === "BLOCK" && "bg-gray-300"
503
+ ),
504
+ style: { height: event.duration / 60 * 64 },
505
+ children: [
506
+ event.type === "BLOCK" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-0 bottom-0 w-1 bg-gray-500 rounded-l-full pointer-events-none" }),
507
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: event.title }),
508
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-300", children: event.service?.name })
509
+ ]
510
+ }
511
+ );
512
+ var Options = ({
513
+ event,
514
+ onClose,
515
+ isOpen,
516
+ onRemoveBlock,
517
+ locale,
518
+ icons
519
+ }) => {
520
+ const mainRef = useClickOutside({
521
+ isActive: isOpen,
522
+ onOutsideClick: onClose
523
+ });
524
+ const eventDate = formatDate(event.start, locale);
525
+ const TrashIcon = icons?.trash ?? /* @__PURE__ */ jsxRuntime.jsx(DefaultTrashIcon, {});
526
+ const EditIcon = icons?.edit ?? /* @__PURE__ */ jsxRuntime.jsx(DefaultEditIcon, {});
527
+ const CloseIcon = icons?.close ?? /* @__PURE__ */ jsxRuntime.jsx(DefaultCloseIcon, {});
528
+ if (!isOpen) return null;
529
+ return /* @__PURE__ */ jsxRuntime.jsxs(
530
+ "div",
531
+ {
532
+ ref: mainRef,
533
+ style: { top: "-100%", left: "-350%" },
534
+ className: "text-left z-20 bg-white absolute border rounded-lg grid p-3 w-[264px] shadow-lg",
535
+ children: [
536
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { children: [
537
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-medium", children: "Blocked Time" }),
538
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500", children: eventDate })
539
+ ] }),
540
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute flex left-0 right-0 justify-end gap-3 px-2 py-2 overflow-hidden", children: [
541
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-l-2 border-b-2 rounded-full absolute pointer-events-none left-[65%] right-0 h-10 -top-2" }),
542
+ /* @__PURE__ */ jsxRuntime.jsx(
543
+ "button",
544
+ {
545
+ onClick: () => {
546
+ onRemoveBlock?.(event.id);
547
+ onClose();
548
+ },
549
+ type: "button",
550
+ className: "hover:bg-blue-50 rounded-lg p-1",
551
+ children: TrashIcon
552
+ }
553
+ ),
554
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "hover:bg-blue-50 rounded-lg p-1", children: EditIcon }),
555
+ /* @__PURE__ */ jsxRuntime.jsx(
556
+ "button",
557
+ {
558
+ onClick: onClose,
559
+ type: "button",
560
+ className: "hover:bg-blue-50 rounded-lg p-1",
561
+ children: CloseIcon
562
+ }
563
+ )
564
+ ] })
565
+ ]
566
+ }
567
+ );
568
+ };
569
+
570
+ exports.Calendar = Calendar;
571
+ exports.SimpleBigWeekView = Calendar;
572
+ exports.addDaysToDate = addDaysToDate;
573
+ exports.addMinutesToDate = addMinutesToDate;
574
+ exports.areSameDates = areSameDates;
575
+ exports.completeWeek = completeWeek;
576
+ exports.formatDate = formatDate;
577
+ exports.fromDateToTimeString = fromDateToTimeString;
578
+ exports.fromMinsToLocaleTimeString = fromMinsToLocaleTimeString;
579
+ exports.fromMinsToTimeString = fromMinsToTimeString;
580
+ exports.generateHours = generateHours;
581
+ exports.getDaysInMonth = getDaysInMonth;
582
+ exports.getMonday = getMonday;
583
+ exports.isToday = isToday;
584
+ exports.useClickOutside = useClickOutside;
585
+ exports.useEventOverlap = useEventOverlap;