@hectorbliss/denik-calendar 0.0.3 → 0.0.4

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.d.ts CHANGED
@@ -1,6 +1,18 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode, RefObject } from 'react';
3
3
 
4
+ /**
5
+ * Resource definition for day/resource view mode
6
+ * Use this to represent courts, rooms, employees, etc.
7
+ */
8
+ interface Resource {
9
+ /** Unique identifier for the resource */
10
+ id: string;
11
+ /** Display name */
12
+ name: string;
13
+ /** Optional icon/avatar */
14
+ icon?: ReactNode;
15
+ }
4
16
  /**
5
17
  * Props passed to custom column header renderer
6
18
  * Use this to build custom headers for resources (courts, rooms, employees, etc.)
@@ -8,12 +20,14 @@ import { ReactNode, RefObject } from 'react';
8
20
  interface ColumnHeaderProps {
9
21
  /** The date for this column */
10
22
  date: Date;
11
- /** Column index (0-6) */
23
+ /** Column index */
12
24
  index: number;
13
25
  /** Whether this column represents today */
14
26
  isToday: boolean;
15
27
  /** The configured locale */
16
28
  locale: string;
29
+ /** Resource data (only in resource mode) */
30
+ resource?: Resource;
17
31
  }
18
32
  /**
19
33
  * Generic calendar event - decoupled from any ORM
@@ -27,6 +41,8 @@ interface CalendarEvent {
27
41
  service?: {
28
42
  name: string;
29
43
  } | null;
44
+ /** Resource ID for day/resource view (court, room, etc.) */
45
+ resourceId?: string;
30
46
  }
31
47
  /**
32
48
  * Calendar configuration options
@@ -70,6 +86,18 @@ interface CalendarProps {
70
86
  date?: Date;
71
87
  /** Array of events to display */
72
88
  events?: CalendarEvent[];
89
+ /**
90
+ * Resources for day/resource view mode.
91
+ * When provided, columns represent resources instead of weekdays.
92
+ * Events are filtered by date and grouped by resourceId.
93
+ *
94
+ * @example
95
+ * resources={[
96
+ * { id: "court-1", name: "Cancha 1", icon: <PadelIcon /> },
97
+ * { id: "court-2", name: "Cancha 2", icon: <PadelIcon /> },
98
+ * ]}
99
+ */
100
+ resources?: Resource[];
73
101
  /** Callback when an event is clicked */
74
102
  onEventClick?: (event: CalendarEvent) => void;
75
103
  /** Callback when an event is moved via drag & drop */
@@ -84,7 +112,92 @@ interface CalendarProps {
84
112
  config?: CalendarConfig;
85
113
  }
86
114
 
87
- declare function Calendar({ date, events, onEventClick, onNewEvent, onEventMove, onAddBlock, onRemoveBlock, config, }: CalendarProps): react_jsx_runtime.JSX.Element;
115
+ declare function Calendar({ date, events, resources, onEventClick, onNewEvent, onEventMove, onAddBlock, onRemoveBlock, config, }: CalendarProps): react_jsx_runtime.JSX.Element;
116
+
117
+ type CalendarView = "week" | "day";
118
+ interface UseCalendarControlsOptions {
119
+ /** Initial date (default: today) */
120
+ initialDate?: Date;
121
+ /** Initial view mode (default: "week") */
122
+ initialView?: CalendarView;
123
+ /** Locale for date formatting (default: "es-MX") */
124
+ locale?: string;
125
+ }
126
+ interface CalendarControls$1 {
127
+ /** Current date */
128
+ date: Date;
129
+ /** Current view mode */
130
+ view: CalendarView;
131
+ /** Week days array (Mon-Sun) */
132
+ week: Date[];
133
+ /** Formatted label for current date/week */
134
+ label: string;
135
+ /** Navigate to today */
136
+ goToToday: () => void;
137
+ /** Navigate to previous period (week or day) */
138
+ goToPrev: () => void;
139
+ /** Navigate to next period (week or day) */
140
+ goToNext: () => void;
141
+ /** Toggle between week and day view */
142
+ toggleView: () => void;
143
+ /** Set specific date */
144
+ setDate: (date: Date) => void;
145
+ /** Set specific view */
146
+ setView: (view: CalendarView) => void;
147
+ /** Check if current date is today */
148
+ isToday: boolean;
149
+ }
150
+ /**
151
+ * Hook for calendar navigation controls
152
+ *
153
+ * @example
154
+ * const controls = useCalendarControls();
155
+ *
156
+ * <button onClick={controls.goToToday}>HOY</button>
157
+ * <button onClick={controls.goToPrev}>←</button>
158
+ * <button onClick={controls.goToNext}>→</button>
159
+ * <span>{controls.label}</span>
160
+ *
161
+ * <Calendar
162
+ * date={controls.date}
163
+ * resources={controls.view === "day" ? courts : undefined}
164
+ * />
165
+ */
166
+ declare function useCalendarControls(options?: UseCalendarControlsOptions): CalendarControls$1;
167
+
168
+ interface CalendarControlsProps {
169
+ /** Controls from useCalendarControls hook */
170
+ controls: CalendarControls$1;
171
+ /** Custom "Today" button label */
172
+ todayLabel?: string;
173
+ /** Custom "Week" label */
174
+ weekLabel?: string;
175
+ /** Custom "Day" label */
176
+ dayLabel?: string;
177
+ /** Show view toggle (default: true) */
178
+ showViewToggle?: boolean;
179
+ /** Custom prev icon */
180
+ prevIcon?: ReactNode;
181
+ /** Custom next icon */
182
+ nextIcon?: ReactNode;
183
+ /** Additional action buttons (export, add, etc.) */
184
+ actions?: ReactNode;
185
+ /** Custom class name */
186
+ className?: string;
187
+ }
188
+ /**
189
+ * Pre-built calendar controls component
190
+ *
191
+ * @example
192
+ * const controls = useCalendarControls();
193
+ *
194
+ * <CalendarControls
195
+ * controls={controls}
196
+ * actions={<button>Add Event</button>}
197
+ * />
198
+ * <Calendar date={controls.date} />
199
+ */
200
+ declare function CalendarControls({ controls, todayLabel, weekLabel, dayLabel, showViewToggle, prevIcon, nextIcon, actions, className, }: CalendarControlsProps): react_jsx_runtime.JSX.Element;
88
201
 
89
202
  /**
90
203
  * Hook for managing calendar events - overlap detection, filtering, and availability
@@ -159,4 +272,4 @@ declare function useClickOutside<T extends HTMLElement>({ isActive, onOutsideCli
159
272
  */
160
273
  declare function formatDate(date: Date, locale?: string): string;
161
274
 
162
- export { Calendar, type CalendarConfig, type CalendarEvent, type CalendarProps, type ColumnHeaderProps, Calendar as SimpleBigWeekView, addDaysToDate, addMinutesToDate, areSameDates, completeWeek, formatDate, fromDateToTimeString, fromMinsToLocaleTimeString, fromMinsToTimeString, generateHours, getDaysInMonth, getMonday, isToday, useCalendarEvents, useClickOutside, useEventOverlap };
275
+ export { Calendar, type CalendarConfig, CalendarControls, type CalendarControlsProps, type CalendarControls$1 as CalendarControlsState, type CalendarEvent, type CalendarProps, type CalendarView, type ColumnHeaderProps, type Resource, Calendar as SimpleBigWeekView, type UseCalendarControlsOptions, addDaysToDate, addMinutesToDate, areSameDates, completeWeek, formatDate, fromDateToTimeString, fromMinsToLocaleTimeString, fromMinsToTimeString, generateHours, getDaysInMonth, getMonday, isToday, useCalendarControls, useCalendarEvents, useClickOutside, useEventOverlap };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { useCallback, useRef, useEffect, useState } from 'react';
1
+ import { useCallback, useRef, useEffect, useState, useMemo } from 'react';
2
2
  import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, closestCenter, DragOverlay, useDroppable, useDraggable } from '@dnd-kit/core';
3
3
  import { CSS } from '@dnd-kit/utilities';
4
4
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -219,11 +219,18 @@ var DayHeader = ({
219
219
  date,
220
220
  locale,
221
221
  index,
222
+ resource,
222
223
  renderColumnHeader
223
224
  }) => {
224
225
  const isToday2 = isToday(date);
225
226
  if (renderColumnHeader) {
226
- return /* @__PURE__ */ jsx("div", { className: "grid place-items-center", children: renderColumnHeader({ date, index, isToday: isToday2, locale }) });
227
+ return /* @__PURE__ */ jsx("div", { className: "grid place-items-center", children: renderColumnHeader({ date, index, isToday: isToday2, locale, resource }) });
228
+ }
229
+ if (resource) {
230
+ return /* @__PURE__ */ jsxs("div", { className: "grid place-items-center gap-1", children: [
231
+ resource.icon && /* @__PURE__ */ jsx("div", { className: "w-8 h-8 flex items-center justify-center", children: resource.icon }),
232
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: resource.name })
233
+ ] });
227
234
  }
228
235
  return /* @__PURE__ */ jsxs("p", { className: "grid place-items-center", children: [
229
236
  /* @__PURE__ */ jsx("span", { className: "capitalize", children: date.toLocaleDateString(locale, { weekday: "short" }) }),
@@ -241,6 +248,7 @@ var DayHeader = ({
241
248
  function Calendar({
242
249
  date = /* @__PURE__ */ new Date(),
243
250
  events = [],
251
+ resources,
244
252
  onEventClick,
245
253
  onNewEvent,
246
254
  onEventMove,
@@ -252,6 +260,8 @@ function Calendar({
252
260
  const week = completeWeek(date);
253
261
  const [activeId, setActiveId] = useState(null);
254
262
  const { canMove } = useCalendarEvents(events);
263
+ const isResourceMode = !!resources && resources.length > 0;
264
+ const columnCount = isResourceMode ? resources.length : 7;
255
265
  const sensors = useSensors(
256
266
  useSensor(PointerSensor, {
257
267
  activationConstraint: { distance: 8 }
@@ -266,10 +276,10 @@ function Calendar({
266
276
  setActiveId(null);
267
277
  if (!over) return;
268
278
  const eventId = active.id.toString().replace("event-", "");
269
- const [, dayIndexStr, hourStr] = over.id.toString().split("-");
270
- const dayIndex = parseInt(dayIndexStr);
279
+ const [, colIndexStr, hourStr] = over.id.toString().split("-");
280
+ const colIndex = parseInt(colIndexStr);
271
281
  const hour = parseInt(hourStr);
272
- const targetDay = week[dayIndex];
282
+ const targetDay = isResourceMode ? date : week[colIndex];
273
283
  const newStart = new Date(targetDay);
274
284
  newStart.setHours(hour, 0, 0, 0);
275
285
  const movedEvent = events.find((e) => e.id === eventId);
@@ -288,6 +298,24 @@ function Calendar({
288
298
  setActiveId(null);
289
299
  };
290
300
  const activeEvent = activeId ? events.find((e) => `event-${e.id}` === activeId) : null;
301
+ const getColumnEvents = (colIndex) => {
302
+ if (isResourceMode) {
303
+ const resourceId = resources[colIndex].id;
304
+ return events.filter((event) => {
305
+ const eventDate = new Date(event.start);
306
+ return event.resourceId === resourceId && areSameDates(eventDate, date);
307
+ });
308
+ }
309
+ const dayOfWeek = week[colIndex];
310
+ return events.filter((event) => {
311
+ const eventDate = new Date(event.start);
312
+ return eventDate.getDate() === dayOfWeek.getDate() && eventDate.getMonth() === dayOfWeek.getMonth();
313
+ });
314
+ };
315
+ const gridStyle = {
316
+ display: "grid",
317
+ gridTemplateColumns: `auto repeat(${columnCount}, minmax(120px, 1fr))`
318
+ };
291
319
  return /* @__PURE__ */ jsxs(
292
320
  DndContext,
293
321
  {
@@ -297,41 +325,66 @@ function Calendar({
297
325
  onDragEnd: handleDragEnd,
298
326
  onDragCancel: handleDragCancel,
299
327
  children: [
300
- /* @__PURE__ */ jsxs("article", { className: "w-full bg-white shadow rounded-xl", children: [
301
- /* @__PURE__ */ jsxs("section", { className: "grid grid-cols-8 place-items-center py-4", children: [
302
- /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-500", children: Intl.DateTimeFormat().resolvedOptions().timeZone }) }),
303
- week.map((day, index) => /* @__PURE__ */ jsx(
304
- DayHeader,
305
- {
306
- date: day,
307
- locale,
308
- index,
309
- renderColumnHeader
310
- },
311
- day.toISOString()
312
- ))
313
- ] }),
314
- /* @__PURE__ */ jsxs("section", { className: "grid grid-cols-8 max-h-[80vh] overflow-y-auto", children: [
315
- /* @__PURE__ */ jsx(TimeColumn, {}),
316
- week.map((dayOfWeek, dayIndex) => /* @__PURE__ */ jsx(
317
- Column,
318
- {
319
- dayIndex,
320
- dayOfWeek,
321
- events: events.filter((event) => {
322
- const eventDate = new Date(event.start);
323
- return eventDate.getDate() === dayOfWeek.getDate() && eventDate.getMonth() === dayOfWeek.getMonth();
324
- }),
325
- onNewEvent,
326
- onAddBlock,
327
- onRemoveBlock,
328
- onEventClick,
329
- locale,
330
- icons
331
- },
332
- dayOfWeek.toISOString()
333
- ))
334
- ] })
328
+ /* @__PURE__ */ jsxs("article", { className: "w-full bg-white shadow rounded-xl overflow-hidden", children: [
329
+ /* @__PURE__ */ jsxs(
330
+ "section",
331
+ {
332
+ style: gridStyle,
333
+ className: "place-items-center py-4 border-b",
334
+ children: [
335
+ /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-500", children: Intl.DateTimeFormat().resolvedOptions().timeZone }) }),
336
+ isResourceMode ? resources.map((resource, index) => /* @__PURE__ */ jsx(
337
+ DayHeader,
338
+ {
339
+ date,
340
+ locale,
341
+ index,
342
+ resource,
343
+ renderColumnHeader
344
+ },
345
+ resource.id
346
+ )) : week.map((day, index) => /* @__PURE__ */ jsx(
347
+ DayHeader,
348
+ {
349
+ date: day,
350
+ locale,
351
+ index,
352
+ renderColumnHeader
353
+ },
354
+ day.toISOString()
355
+ ))
356
+ ]
357
+ }
358
+ ),
359
+ /* @__PURE__ */ jsxs(
360
+ "section",
361
+ {
362
+ style: gridStyle,
363
+ className: cn(
364
+ "max-h-[80vh] overflow-y-auto",
365
+ isResourceMode && "overflow-x-auto"
366
+ ),
367
+ children: [
368
+ /* @__PURE__ */ jsx(TimeColumn, {}),
369
+ Array.from({ length: columnCount }, (_, colIndex) => /* @__PURE__ */ jsx(
370
+ Column,
371
+ {
372
+ dayIndex: colIndex,
373
+ dayOfWeek: isResourceMode ? date : week[colIndex],
374
+ events: getColumnEvents(colIndex),
375
+ onNewEvent,
376
+ onAddBlock,
377
+ onRemoveBlock,
378
+ onEventClick,
379
+ locale,
380
+ icons,
381
+ resourceId: isResourceMode ? resources[colIndex].id : void 0
382
+ },
383
+ isResourceMode ? resources[colIndex].id : week[colIndex].toISOString()
384
+ ))
385
+ ]
386
+ }
387
+ )
335
388
  ] }),
336
389
  /* @__PURE__ */ jsx(DragOverlay, { children: activeEvent ? /* @__PURE__ */ jsx(EventOverlay, { event: activeEvent }) : null })
337
390
  ]
@@ -429,6 +482,42 @@ var EmptyButton = ({
429
482
  }
430
483
  );
431
484
  };
485
+ var calculateOverlapPositions = (events) => {
486
+ if (events.length === 0) return [];
487
+ const sorted = [...events].sort(
488
+ (a, b) => new Date(a.start).getTime() - new Date(b.start).getTime()
489
+ );
490
+ const positions = /* @__PURE__ */ new Map();
491
+ const groups = [];
492
+ let currentGroup = [];
493
+ let groupEnd = 0;
494
+ for (const event of sorted) {
495
+ const eventStart = new Date(event.start);
496
+ const startHour = eventStart.getHours() + eventStart.getMinutes() / 60;
497
+ const endHour = startHour + event.duration / 60;
498
+ if (currentGroup.length === 0 || startHour < groupEnd) {
499
+ currentGroup.push(event);
500
+ groupEnd = Math.max(groupEnd, endHour);
501
+ } else {
502
+ groups.push(currentGroup);
503
+ currentGroup = [event];
504
+ groupEnd = endHour;
505
+ }
506
+ }
507
+ if (currentGroup.length > 0) {
508
+ groups.push(currentGroup);
509
+ }
510
+ for (const group of groups) {
511
+ const totalColumns = group.length;
512
+ group.forEach((event, index) => {
513
+ positions.set(event.id, { column: index, totalColumns });
514
+ });
515
+ }
516
+ return sorted.map((event) => ({
517
+ event,
518
+ ...positions.get(event.id)
519
+ }));
520
+ };
432
521
  var Column = ({
433
522
  onEventClick,
434
523
  events = [],
@@ -438,9 +527,11 @@ var Column = ({
438
527
  onRemoveBlock,
439
528
  dayIndex,
440
529
  locale,
441
- icons
530
+ icons,
531
+ resourceId
442
532
  }) => {
443
533
  const columnRef = useRef(null);
534
+ const eventsWithPositions = calculateOverlapPositions(events);
444
535
  useEffect(() => {
445
536
  if (!columnRef.current) return;
446
537
  const today = /* @__PURE__ */ new Date();
@@ -454,21 +545,24 @@ var Column = ({
454
545
  }, 100);
455
546
  }
456
547
  }, [dayOfWeek]);
457
- const findEvent = (hours) => {
458
- const eventStartsHere = events.find(
459
- (event) => new Date(event.start).getHours() === hours
548
+ const findEventsAtHour = (hours) => {
549
+ const eventsStartingHere = eventsWithPositions.filter(
550
+ ({ event }) => new Date(event.start).getHours() === hours
460
551
  );
461
- if (eventStartsHere) {
462
- return /* @__PURE__ */ jsx(
552
+ if (eventsStartingHere.length > 0) {
553
+ return eventsStartingHere.map(({ event, column, totalColumns }) => /* @__PURE__ */ jsx(
463
554
  DraggableEvent,
464
555
  {
465
- onClick: () => onEventClick?.(eventStartsHere),
466
- event: eventStartsHere,
556
+ onClick: () => onEventClick?.(event),
557
+ event,
467
558
  onRemoveBlock,
468
559
  locale,
469
- icons
470
- }
471
- );
560
+ icons,
561
+ overlapColumn: column,
562
+ overlapTotal: totalColumns
563
+ },
564
+ event.id
565
+ ));
472
566
  }
473
567
  const eventSpansHere = events.find((event) => {
474
568
  const eventStart = new Date(event.start);
@@ -494,7 +588,7 @@ var Column = ({
494
588
  date: dayOfWeek,
495
589
  className: "relative",
496
590
  dayIndex,
497
- children: findEvent(hours)
591
+ children: findEventsAtHour(hours)
498
592
  },
499
593
  hours
500
594
  )) });
@@ -505,16 +599,22 @@ var DraggableEvent = ({
505
599
  onClick,
506
600
  onRemoveBlock,
507
601
  locale,
508
- icons
602
+ icons,
603
+ overlapColumn = 0,
604
+ overlapTotal = 1
509
605
  }) => {
510
606
  const [showOptions, setShowOptions] = useState(false);
511
607
  const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
512
608
  id: `event-${event.id}`,
513
609
  disabled: event.type === "BLOCK"
514
610
  });
611
+ const widthPercent = event.type === "BLOCK" ? 100 : 90 / overlapTotal;
612
+ const leftPercent = event.type === "BLOCK" ? 0 : overlapColumn * (90 / overlapTotal);
515
613
  const style = {
516
614
  height: event.duration / 60 * 64,
517
- transform: transform ? CSS.Translate.toString(transform) : void 0
615
+ transform: transform ? CSS.Translate.toString(transform) : void 0,
616
+ width: `${widthPercent}%`,
617
+ left: `${leftPercent}%`
518
618
  };
519
619
  return /* @__PURE__ */ jsxs(Fragment, { children: [
520
620
  /* @__PURE__ */ jsxs(
@@ -527,7 +627,7 @@ var DraggableEvent = ({
527
627
  ...attributes,
528
628
  className: cn(
529
629
  "border grid gap-y-1 overflow-hidden place-content-start",
530
- "text-xs text-left pl-1 absolute top-0 left-0 bg-blue-500 text-white rounded-md z-10 w-[90%]",
630
+ "text-xs text-left pl-1 absolute top-0 bg-blue-500 text-white rounded-md z-10",
531
631
  event.type === "BLOCK" && "bg-gray-300 h-full w-full text-center cursor-not-allowed relative p-0",
532
632
  event.type !== "BLOCK" && "cursor-grab",
533
633
  isDragging && event.type !== "BLOCK" && "cursor-grabbing opacity-50"
@@ -625,5 +725,132 @@ var Options = ({
625
725
  }
626
726
  );
627
727
  };
728
+ var DefaultPrevIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "w-5 h-5", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" }) });
729
+ var DefaultNextIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "w-5 h-5", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" }) });
730
+ function CalendarControls({
731
+ controls,
732
+ todayLabel = "HOY",
733
+ weekLabel = "SEMANA",
734
+ dayLabel = "D\xCDA",
735
+ showViewToggle = true,
736
+ prevIcon,
737
+ nextIcon,
738
+ actions,
739
+ className = ""
740
+ }) {
741
+ const { label, goToToday, goToPrev, goToNext, view, toggleView, isToday: isToday2 } = controls;
742
+ return /* @__PURE__ */ jsxs(
743
+ "div",
744
+ {
745
+ className: `flex items-center justify-between gap-4 py-3 ${className}`,
746
+ children: [
747
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
748
+ /* @__PURE__ */ jsx(
749
+ "button",
750
+ {
751
+ onClick: goToToday,
752
+ disabled: isToday2,
753
+ className: `px-4 py-2 text-sm font-medium rounded-full border transition-colors ${isToday2 ? "bg-gray-100 text-gray-400 cursor-not-allowed" : "bg-blue-500 text-white hover:bg-blue-600"}`,
754
+ children: todayLabel
755
+ }
756
+ ),
757
+ /* @__PURE__ */ jsx(
758
+ "button",
759
+ {
760
+ onClick: goToPrev,
761
+ className: "p-2 rounded-full hover:bg-gray-100 transition-colors",
762
+ "aria-label": "Previous",
763
+ children: prevIcon ?? /* @__PURE__ */ jsx(DefaultPrevIcon, {})
764
+ }
765
+ ),
766
+ /* @__PURE__ */ jsx(
767
+ "button",
768
+ {
769
+ onClick: goToNext,
770
+ className: "p-2 rounded-full hover:bg-gray-100 transition-colors",
771
+ "aria-label": "Next",
772
+ children: nextIcon ?? /* @__PURE__ */ jsx(DefaultNextIcon, {})
773
+ }
774
+ ),
775
+ /* @__PURE__ */ jsx("span", { className: "text-lg font-medium capitalize ml-2", children: label })
776
+ ] }),
777
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
778
+ showViewToggle && /* @__PURE__ */ jsxs(
779
+ "select",
780
+ {
781
+ value: view,
782
+ onChange: toggleView,
783
+ className: "px-4 py-2 text-sm font-medium border rounded-lg bg-white hover:bg-gray-50 cursor-pointer",
784
+ children: [
785
+ /* @__PURE__ */ jsx("option", { value: "week", children: weekLabel }),
786
+ /* @__PURE__ */ jsx("option", { value: "day", children: dayLabel })
787
+ ]
788
+ }
789
+ ),
790
+ actions
791
+ ] })
792
+ ]
793
+ }
794
+ );
795
+ }
796
+ function useCalendarControls(options = {}) {
797
+ const {
798
+ initialDate = /* @__PURE__ */ new Date(),
799
+ initialView = "week",
800
+ locale = "es-MX"
801
+ } = options;
802
+ const [date, setDate] = useState(initialDate);
803
+ const [view, setView] = useState(initialView);
804
+ const week = useMemo(() => completeWeek(date), [date]);
805
+ const goToToday = useCallback(() => {
806
+ setDate(/* @__PURE__ */ new Date());
807
+ }, []);
808
+ const goToPrev = useCallback(() => {
809
+ setDate((d) => addDaysToDate(d, view === "week" ? -7 : -1));
810
+ }, [view]);
811
+ const goToNext = useCallback(() => {
812
+ setDate((d) => addDaysToDate(d, view === "week" ? 7 : 1));
813
+ }, [view]);
814
+ const toggleView = useCallback(() => {
815
+ setView((v) => v === "week" ? "day" : "week");
816
+ }, []);
817
+ const isToday2 = useMemo(() => {
818
+ const today = /* @__PURE__ */ new Date();
819
+ return date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear();
820
+ }, [date]);
821
+ const label = useMemo(() => {
822
+ if (view === "week") {
823
+ const monthName = week[0].toLocaleDateString(locale, { month: "long" });
824
+ const year = week[0].getFullYear();
825
+ const endMonth = week[6].getMonth();
826
+ if (week[0].getMonth() !== endMonth) {
827
+ const endMonthName = week[6].toLocaleDateString(locale, {
828
+ month: "short"
829
+ });
830
+ return `${week[0].getDate()} ${week[0].toLocaleDateString(locale, { month: "short" })} - ${week[6].getDate()} ${endMonthName} ${year}`;
831
+ }
832
+ return `${week[0].getDate()} - ${week[6].getDate()} ${monthName} ${year}`;
833
+ }
834
+ return date.toLocaleDateString(locale, {
835
+ weekday: "long",
836
+ day: "numeric",
837
+ month: "long",
838
+ year: "numeric"
839
+ });
840
+ }, [view, week, date, locale]);
841
+ return {
842
+ date,
843
+ view,
844
+ week,
845
+ label,
846
+ goToToday,
847
+ goToPrev,
848
+ goToNext,
849
+ toggleView,
850
+ setDate,
851
+ setView,
852
+ isToday: isToday2
853
+ };
854
+ }
628
855
 
629
- export { Calendar, Calendar as SimpleBigWeekView, addDaysToDate, addMinutesToDate, areSameDates, completeWeek, formatDate, fromDateToTimeString, fromMinsToLocaleTimeString, fromMinsToTimeString, generateHours, getDaysInMonth, getMonday, isToday, useCalendarEvents, useClickOutside, useEventOverlap };
856
+ export { Calendar, CalendarControls, Calendar as SimpleBigWeekView, addDaysToDate, addMinutesToDate, areSameDates, completeWeek, formatDate, fromDateToTimeString, fromMinsToLocaleTimeString, fromMinsToTimeString, generateHours, getDaysInMonth, getMonday, isToday, useCalendarControls, useCalendarEvents, useClickOutside, useEventOverlap };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hectorbliss/denik-calendar",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "private": false,
5
5
  "description": "A React drag-and-drop week calendar component with overlap detection. Built with @dnd-kit.",
6
6
  "type": "module",