@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.cjs CHANGED
@@ -221,11 +221,18 @@ var DayHeader = ({
221
221
  date,
222
222
  locale,
223
223
  index,
224
+ resource,
224
225
  renderColumnHeader
225
226
  }) => {
226
227
  const isToday2 = isToday(date);
227
228
  if (renderColumnHeader) {
228
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid place-items-center", children: renderColumnHeader({ date, index, isToday: isToday2, locale }) });
229
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid place-items-center", children: renderColumnHeader({ date, index, isToday: isToday2, locale, resource }) });
230
+ }
231
+ if (resource) {
232
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid place-items-center gap-1", children: [
233
+ resource.icon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-8 h-8 flex items-center justify-center", children: resource.icon }),
234
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: resource.name })
235
+ ] });
229
236
  }
230
237
  return /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "grid place-items-center", children: [
231
238
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "capitalize", children: date.toLocaleDateString(locale, { weekday: "short" }) }),
@@ -243,6 +250,7 @@ var DayHeader = ({
243
250
  function Calendar({
244
251
  date = /* @__PURE__ */ new Date(),
245
252
  events = [],
253
+ resources,
246
254
  onEventClick,
247
255
  onNewEvent,
248
256
  onEventMove,
@@ -254,6 +262,8 @@ function Calendar({
254
262
  const week = completeWeek(date);
255
263
  const [activeId, setActiveId] = react.useState(null);
256
264
  const { canMove } = useCalendarEvents(events);
265
+ const isResourceMode = !!resources && resources.length > 0;
266
+ const columnCount = isResourceMode ? resources.length : 7;
257
267
  const sensors = core.useSensors(
258
268
  core.useSensor(core.PointerSensor, {
259
269
  activationConstraint: { distance: 8 }
@@ -268,10 +278,10 @@ function Calendar({
268
278
  setActiveId(null);
269
279
  if (!over) return;
270
280
  const eventId = active.id.toString().replace("event-", "");
271
- const [, dayIndexStr, hourStr] = over.id.toString().split("-");
272
- const dayIndex = parseInt(dayIndexStr);
281
+ const [, colIndexStr, hourStr] = over.id.toString().split("-");
282
+ const colIndex = parseInt(colIndexStr);
273
283
  const hour = parseInt(hourStr);
274
- const targetDay = week[dayIndex];
284
+ const targetDay = isResourceMode ? date : week[colIndex];
275
285
  const newStart = new Date(targetDay);
276
286
  newStart.setHours(hour, 0, 0, 0);
277
287
  const movedEvent = events.find((e) => e.id === eventId);
@@ -290,6 +300,24 @@ function Calendar({
290
300
  setActiveId(null);
291
301
  };
292
302
  const activeEvent = activeId ? events.find((e) => `event-${e.id}` === activeId) : null;
303
+ const getColumnEvents = (colIndex) => {
304
+ if (isResourceMode) {
305
+ const resourceId = resources[colIndex].id;
306
+ return events.filter((event) => {
307
+ const eventDate = new Date(event.start);
308
+ return event.resourceId === resourceId && areSameDates(eventDate, date);
309
+ });
310
+ }
311
+ const dayOfWeek = week[colIndex];
312
+ return events.filter((event) => {
313
+ const eventDate = new Date(event.start);
314
+ return eventDate.getDate() === dayOfWeek.getDate() && eventDate.getMonth() === dayOfWeek.getMonth();
315
+ });
316
+ };
317
+ const gridStyle = {
318
+ display: "grid",
319
+ gridTemplateColumns: `auto repeat(${columnCount}, minmax(120px, 1fr))`
320
+ };
293
321
  return /* @__PURE__ */ jsxRuntime.jsxs(
294
322
  core.DndContext,
295
323
  {
@@ -299,41 +327,66 @@ function Calendar({
299
327
  onDragEnd: handleDragEnd,
300
328
  onDragCancel: handleDragCancel,
301
329
  children: [
302
- /* @__PURE__ */ jsxRuntime.jsxs("article", { className: "w-full bg-white shadow rounded-xl", children: [
303
- /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "grid grid-cols-8 place-items-center py-4", children: [
304
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500", children: Intl.DateTimeFormat().resolvedOptions().timeZone }) }),
305
- week.map((day, index) => /* @__PURE__ */ jsxRuntime.jsx(
306
- DayHeader,
307
- {
308
- date: day,
309
- locale,
310
- index,
311
- renderColumnHeader
312
- },
313
- day.toISOString()
314
- ))
315
- ] }),
316
- /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "grid grid-cols-8 max-h-[80vh] overflow-y-auto", children: [
317
- /* @__PURE__ */ jsxRuntime.jsx(TimeColumn, {}),
318
- week.map((dayOfWeek, dayIndex) => /* @__PURE__ */ jsxRuntime.jsx(
319
- Column,
320
- {
321
- dayIndex,
322
- dayOfWeek,
323
- events: events.filter((event) => {
324
- const eventDate = new Date(event.start);
325
- return eventDate.getDate() === dayOfWeek.getDate() && eventDate.getMonth() === dayOfWeek.getMonth();
326
- }),
327
- onNewEvent,
328
- onAddBlock,
329
- onRemoveBlock,
330
- onEventClick,
331
- locale,
332
- icons
333
- },
334
- dayOfWeek.toISOString()
335
- ))
336
- ] })
330
+ /* @__PURE__ */ jsxRuntime.jsxs("article", { className: "w-full bg-white shadow rounded-xl overflow-hidden", children: [
331
+ /* @__PURE__ */ jsxRuntime.jsxs(
332
+ "section",
333
+ {
334
+ style: gridStyle,
335
+ className: "place-items-center py-4 border-b",
336
+ children: [
337
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500", children: Intl.DateTimeFormat().resolvedOptions().timeZone }) }),
338
+ isResourceMode ? resources.map((resource, index) => /* @__PURE__ */ jsxRuntime.jsx(
339
+ DayHeader,
340
+ {
341
+ date,
342
+ locale,
343
+ index,
344
+ resource,
345
+ renderColumnHeader
346
+ },
347
+ resource.id
348
+ )) : week.map((day, index) => /* @__PURE__ */ jsxRuntime.jsx(
349
+ DayHeader,
350
+ {
351
+ date: day,
352
+ locale,
353
+ index,
354
+ renderColumnHeader
355
+ },
356
+ day.toISOString()
357
+ ))
358
+ ]
359
+ }
360
+ ),
361
+ /* @__PURE__ */ jsxRuntime.jsxs(
362
+ "section",
363
+ {
364
+ style: gridStyle,
365
+ className: cn(
366
+ "max-h-[80vh] overflow-y-auto",
367
+ isResourceMode && "overflow-x-auto"
368
+ ),
369
+ children: [
370
+ /* @__PURE__ */ jsxRuntime.jsx(TimeColumn, {}),
371
+ Array.from({ length: columnCount }, (_, colIndex) => /* @__PURE__ */ jsxRuntime.jsx(
372
+ Column,
373
+ {
374
+ dayIndex: colIndex,
375
+ dayOfWeek: isResourceMode ? date : week[colIndex],
376
+ events: getColumnEvents(colIndex),
377
+ onNewEvent,
378
+ onAddBlock,
379
+ onRemoveBlock,
380
+ onEventClick,
381
+ locale,
382
+ icons,
383
+ resourceId: isResourceMode ? resources[colIndex].id : void 0
384
+ },
385
+ isResourceMode ? resources[colIndex].id : week[colIndex].toISOString()
386
+ ))
387
+ ]
388
+ }
389
+ )
337
390
  ] }),
338
391
  /* @__PURE__ */ jsxRuntime.jsx(core.DragOverlay, { children: activeEvent ? /* @__PURE__ */ jsxRuntime.jsx(EventOverlay, { event: activeEvent }) : null })
339
392
  ]
@@ -431,6 +484,42 @@ var EmptyButton = ({
431
484
  }
432
485
  );
433
486
  };
487
+ var calculateOverlapPositions = (events) => {
488
+ if (events.length === 0) return [];
489
+ const sorted = [...events].sort(
490
+ (a, b) => new Date(a.start).getTime() - new Date(b.start).getTime()
491
+ );
492
+ const positions = /* @__PURE__ */ new Map();
493
+ const groups = [];
494
+ let currentGroup = [];
495
+ let groupEnd = 0;
496
+ for (const event of sorted) {
497
+ const eventStart = new Date(event.start);
498
+ const startHour = eventStart.getHours() + eventStart.getMinutes() / 60;
499
+ const endHour = startHour + event.duration / 60;
500
+ if (currentGroup.length === 0 || startHour < groupEnd) {
501
+ currentGroup.push(event);
502
+ groupEnd = Math.max(groupEnd, endHour);
503
+ } else {
504
+ groups.push(currentGroup);
505
+ currentGroup = [event];
506
+ groupEnd = endHour;
507
+ }
508
+ }
509
+ if (currentGroup.length > 0) {
510
+ groups.push(currentGroup);
511
+ }
512
+ for (const group of groups) {
513
+ const totalColumns = group.length;
514
+ group.forEach((event, index) => {
515
+ positions.set(event.id, { column: index, totalColumns });
516
+ });
517
+ }
518
+ return sorted.map((event) => ({
519
+ event,
520
+ ...positions.get(event.id)
521
+ }));
522
+ };
434
523
  var Column = ({
435
524
  onEventClick,
436
525
  events = [],
@@ -440,9 +529,11 @@ var Column = ({
440
529
  onRemoveBlock,
441
530
  dayIndex,
442
531
  locale,
443
- icons
532
+ icons,
533
+ resourceId
444
534
  }) => {
445
535
  const columnRef = react.useRef(null);
536
+ const eventsWithPositions = calculateOverlapPositions(events);
446
537
  react.useEffect(() => {
447
538
  if (!columnRef.current) return;
448
539
  const today = /* @__PURE__ */ new Date();
@@ -456,21 +547,24 @@ var Column = ({
456
547
  }, 100);
457
548
  }
458
549
  }, [dayOfWeek]);
459
- const findEvent = (hours) => {
460
- const eventStartsHere = events.find(
461
- (event) => new Date(event.start).getHours() === hours
550
+ const findEventsAtHour = (hours) => {
551
+ const eventsStartingHere = eventsWithPositions.filter(
552
+ ({ event }) => new Date(event.start).getHours() === hours
462
553
  );
463
- if (eventStartsHere) {
464
- return /* @__PURE__ */ jsxRuntime.jsx(
554
+ if (eventsStartingHere.length > 0) {
555
+ return eventsStartingHere.map(({ event, column, totalColumns }) => /* @__PURE__ */ jsxRuntime.jsx(
465
556
  DraggableEvent,
466
557
  {
467
- onClick: () => onEventClick?.(eventStartsHere),
468
- event: eventStartsHere,
558
+ onClick: () => onEventClick?.(event),
559
+ event,
469
560
  onRemoveBlock,
470
561
  locale,
471
- icons
472
- }
473
- );
562
+ icons,
563
+ overlapColumn: column,
564
+ overlapTotal: totalColumns
565
+ },
566
+ event.id
567
+ ));
474
568
  }
475
569
  const eventSpansHere = events.find((event) => {
476
570
  const eventStart = new Date(event.start);
@@ -496,7 +590,7 @@ var Column = ({
496
590
  date: dayOfWeek,
497
591
  className: "relative",
498
592
  dayIndex,
499
- children: findEvent(hours)
593
+ children: findEventsAtHour(hours)
500
594
  },
501
595
  hours
502
596
  )) });
@@ -507,16 +601,22 @@ var DraggableEvent = ({
507
601
  onClick,
508
602
  onRemoveBlock,
509
603
  locale,
510
- icons
604
+ icons,
605
+ overlapColumn = 0,
606
+ overlapTotal = 1
511
607
  }) => {
512
608
  const [showOptions, setShowOptions] = react.useState(false);
513
609
  const { attributes, listeners, setNodeRef, transform, isDragging } = core.useDraggable({
514
610
  id: `event-${event.id}`,
515
611
  disabled: event.type === "BLOCK"
516
612
  });
613
+ const widthPercent = event.type === "BLOCK" ? 100 : 90 / overlapTotal;
614
+ const leftPercent = event.type === "BLOCK" ? 0 : overlapColumn * (90 / overlapTotal);
517
615
  const style = {
518
616
  height: event.duration / 60 * 64,
519
- transform: transform ? utilities.CSS.Translate.toString(transform) : void 0
617
+ transform: transform ? utilities.CSS.Translate.toString(transform) : void 0,
618
+ width: `${widthPercent}%`,
619
+ left: `${leftPercent}%`
520
620
  };
521
621
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
522
622
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -529,7 +629,7 @@ var DraggableEvent = ({
529
629
  ...attributes,
530
630
  className: cn(
531
631
  "border grid gap-y-1 overflow-hidden place-content-start",
532
- "text-xs text-left pl-1 absolute top-0 left-0 bg-blue-500 text-white rounded-md z-10 w-[90%]",
632
+ "text-xs text-left pl-1 absolute top-0 bg-blue-500 text-white rounded-md z-10",
533
633
  event.type === "BLOCK" && "bg-gray-300 h-full w-full text-center cursor-not-allowed relative p-0",
534
634
  event.type !== "BLOCK" && "cursor-grab",
535
635
  isDragging && event.type !== "BLOCK" && "cursor-grabbing opacity-50"
@@ -627,8 +727,136 @@ var Options = ({
627
727
  }
628
728
  );
629
729
  };
730
+ var DefaultPrevIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", className: "w-5 h-5", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" }) });
731
+ var DefaultNextIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", className: "w-5 h-5", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" }) });
732
+ function CalendarControls({
733
+ controls,
734
+ todayLabel = "HOY",
735
+ weekLabel = "SEMANA",
736
+ dayLabel = "D\xCDA",
737
+ showViewToggle = true,
738
+ prevIcon,
739
+ nextIcon,
740
+ actions,
741
+ className = ""
742
+ }) {
743
+ const { label, goToToday, goToPrev, goToNext, view, toggleView, isToday: isToday2 } = controls;
744
+ return /* @__PURE__ */ jsxRuntime.jsxs(
745
+ "div",
746
+ {
747
+ className: `flex items-center justify-between gap-4 py-3 ${className}`,
748
+ children: [
749
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
750
+ /* @__PURE__ */ jsxRuntime.jsx(
751
+ "button",
752
+ {
753
+ onClick: goToToday,
754
+ disabled: isToday2,
755
+ 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"}`,
756
+ children: todayLabel
757
+ }
758
+ ),
759
+ /* @__PURE__ */ jsxRuntime.jsx(
760
+ "button",
761
+ {
762
+ onClick: goToPrev,
763
+ className: "p-2 rounded-full hover:bg-gray-100 transition-colors",
764
+ "aria-label": "Previous",
765
+ children: prevIcon ?? /* @__PURE__ */ jsxRuntime.jsx(DefaultPrevIcon, {})
766
+ }
767
+ ),
768
+ /* @__PURE__ */ jsxRuntime.jsx(
769
+ "button",
770
+ {
771
+ onClick: goToNext,
772
+ className: "p-2 rounded-full hover:bg-gray-100 transition-colors",
773
+ "aria-label": "Next",
774
+ children: nextIcon ?? /* @__PURE__ */ jsxRuntime.jsx(DefaultNextIcon, {})
775
+ }
776
+ ),
777
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-lg font-medium capitalize ml-2", children: label })
778
+ ] }),
779
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
780
+ showViewToggle && /* @__PURE__ */ jsxRuntime.jsxs(
781
+ "select",
782
+ {
783
+ value: view,
784
+ onChange: toggleView,
785
+ className: "px-4 py-2 text-sm font-medium border rounded-lg bg-white hover:bg-gray-50 cursor-pointer",
786
+ children: [
787
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "week", children: weekLabel }),
788
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "day", children: dayLabel })
789
+ ]
790
+ }
791
+ ),
792
+ actions
793
+ ] })
794
+ ]
795
+ }
796
+ );
797
+ }
798
+ function useCalendarControls(options = {}) {
799
+ const {
800
+ initialDate = /* @__PURE__ */ new Date(),
801
+ initialView = "week",
802
+ locale = "es-MX"
803
+ } = options;
804
+ const [date, setDate] = react.useState(initialDate);
805
+ const [view, setView] = react.useState(initialView);
806
+ const week = react.useMemo(() => completeWeek(date), [date]);
807
+ const goToToday = react.useCallback(() => {
808
+ setDate(/* @__PURE__ */ new Date());
809
+ }, []);
810
+ const goToPrev = react.useCallback(() => {
811
+ setDate((d) => addDaysToDate(d, view === "week" ? -7 : -1));
812
+ }, [view]);
813
+ const goToNext = react.useCallback(() => {
814
+ setDate((d) => addDaysToDate(d, view === "week" ? 7 : 1));
815
+ }, [view]);
816
+ const toggleView = react.useCallback(() => {
817
+ setView((v) => v === "week" ? "day" : "week");
818
+ }, []);
819
+ const isToday2 = react.useMemo(() => {
820
+ const today = /* @__PURE__ */ new Date();
821
+ return date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear();
822
+ }, [date]);
823
+ const label = react.useMemo(() => {
824
+ if (view === "week") {
825
+ const monthName = week[0].toLocaleDateString(locale, { month: "long" });
826
+ const year = week[0].getFullYear();
827
+ const endMonth = week[6].getMonth();
828
+ if (week[0].getMonth() !== endMonth) {
829
+ const endMonthName = week[6].toLocaleDateString(locale, {
830
+ month: "short"
831
+ });
832
+ return `${week[0].getDate()} ${week[0].toLocaleDateString(locale, { month: "short" })} - ${week[6].getDate()} ${endMonthName} ${year}`;
833
+ }
834
+ return `${week[0].getDate()} - ${week[6].getDate()} ${monthName} ${year}`;
835
+ }
836
+ return date.toLocaleDateString(locale, {
837
+ weekday: "long",
838
+ day: "numeric",
839
+ month: "long",
840
+ year: "numeric"
841
+ });
842
+ }, [view, week, date, locale]);
843
+ return {
844
+ date,
845
+ view,
846
+ week,
847
+ label,
848
+ goToToday,
849
+ goToPrev,
850
+ goToNext,
851
+ toggleView,
852
+ setDate,
853
+ setView,
854
+ isToday: isToday2
855
+ };
856
+ }
630
857
 
631
858
  exports.Calendar = Calendar;
859
+ exports.CalendarControls = CalendarControls;
632
860
  exports.SimpleBigWeekView = Calendar;
633
861
  exports.addDaysToDate = addDaysToDate;
634
862
  exports.addMinutesToDate = addMinutesToDate;
@@ -642,6 +870,7 @@ exports.generateHours = generateHours;
642
870
  exports.getDaysInMonth = getDaysInMonth;
643
871
  exports.getMonday = getMonday;
644
872
  exports.isToday = isToday;
873
+ exports.useCalendarControls = useCalendarControls;
645
874
  exports.useCalendarEvents = useCalendarEvents;
646
875
  exports.useClickOutside = useClickOutside;
647
876
  exports.useEventOverlap = useEventOverlap;
package/dist/index.d.cts 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 };