@gridui/calendar 0.0.0-stage → 0.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.js ADDED
@@ -0,0 +1,943 @@
1
+ // src/calendar/style.ts
2
+ import { cva } from "class-variance-authority";
3
+ var calendarStyles = cva("gridui-calendar min-w-0 bg-background text-foreground", {
4
+ variants: {
5
+ frame: {
6
+ none: "",
7
+ outline: "border border-solid border-border"
8
+ }
9
+ },
10
+ defaultVariants: { frame: "none" }
11
+ });
12
+
13
+ // src/calendar/calendar.tsx
14
+ import { cn } from "@gridui/style-utils";
15
+ import { jsx } from "react/jsx-runtime";
16
+ var Calendar = (props) => {
17
+ const { frame, className, ...restProps } = props;
18
+ return /* @__PURE__ */ jsx(
19
+ "div",
20
+ {
21
+ ...restProps,
22
+ className: cn(calendarStyles({ frame }), className)
23
+ }
24
+ );
25
+ };
26
+
27
+ // src/calendar-event/style.ts
28
+ import { cva as cva2 } from "class-variance-authority";
29
+ var eventStyles = cva2("gridui-calendar-event gridui-hover-surface gridui-focus-contained box-border min-w-0 w-full cursor-pointer text-left type-supporting disabled:cursor-not-allowed disabled:opacity-50", {
30
+ variants: {
31
+ appearance: {
32
+ block: "gridui-calendar-event-block",
33
+ compact: "gridui-calendar-event-compact"
34
+ }
35
+ },
36
+ defaultVariants: {
37
+ appearance: "block"
38
+ }
39
+ });
40
+
41
+ // src/calendar-event/calendar-event.tsx
42
+ import { cn as cn2 } from "@gridui/style-utils";
43
+ import { jsx as jsx2 } from "react/jsx-runtime";
44
+ var CalendarEvent = (props) => {
45
+ const { colors, appearance, selected, type = "button", className, style, ...restProps } = props;
46
+ return /* @__PURE__ */ jsx2(
47
+ "button",
48
+ {
49
+ ...restProps,
50
+ type,
51
+ "aria-pressed": selected,
52
+ "data-selected": selected || void 0,
53
+ className: cn2(eventStyles({
54
+ appearance
55
+ }), className),
56
+ style: {
57
+ "--gridui-calendar-event-accent": colors?.accent,
58
+ "--gridui-calendar-event-background": colors?.background,
59
+ "--gridui-calendar-event-foreground": colors?.foreground,
60
+ ...style
61
+ }
62
+ }
63
+ );
64
+ };
65
+
66
+ // src/shared/date.ts
67
+ var parseDate = (value) => {
68
+ const date = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
69
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || !Number.isFinite(date.getTime()) || date.toISOString().slice(0, 10) !== value) {
70
+ throw new RangeError(`Invalid calendar date: ${value}`);
71
+ }
72
+ return date;
73
+ };
74
+ var addDays = (value, amount) => {
75
+ const date = parseDate(value);
76
+ date.setUTCDate(date.getUTCDate() + amount);
77
+ return date.toISOString().slice(0, 10);
78
+ };
79
+ var addMonths = (value, amount) => {
80
+ const date = parseDate(value);
81
+ const day = date.getUTCDate();
82
+ date.setUTCDate(1);
83
+ date.setUTCMonth(date.getUTCMonth() + amount);
84
+ const last = new Date(date);
85
+ last.setUTCMonth(last.getUTCMonth() + 1, 0);
86
+ date.setUTCDate(Math.min(day, last.getUTCDate()));
87
+ return date.toISOString().slice(0, 10);
88
+ };
89
+ var startOfWeek = (value, weekStartsOn) => {
90
+ if (!Number.isInteger(weekStartsOn) || weekStartsOn < 0 || weekStartsOn > 6) {
91
+ throw new RangeError("weekStartsOn must be an integer from 0 to 6.");
92
+ }
93
+ return addDays(value, -((parseDate(value).getUTCDay() - weekStartsOn + 7) % 7));
94
+ };
95
+ var formatDate = (value, locale, options) => {
96
+ return new Intl.DateTimeFormat(locale, {
97
+ ...options,
98
+ timeZone: "UTC"
99
+ }).format(parseDate(value));
100
+ };
101
+ var formatMinute = (minute) => {
102
+ return `${String(Math.floor(minute / 60)).padStart(2, "0")}:${String(minute % 60).padStart(2, "0")}`;
103
+ };
104
+
105
+ // src/shared/events.ts
106
+ var parseTime = (value, allDay) => {
107
+ if (allDay) {
108
+ return parseDate(value).getTime() / 6e4;
109
+ }
110
+ if (!/^\d{4}-\d{2}-\d{2}T(?:[01]\d|2[0-3]):[0-5]\d$/.test(value)) {
111
+ throw new RangeError(`Invalid calendar time: ${value}`);
112
+ }
113
+ return parseDate(value.slice(0, 10)).getTime() / 6e4 + Number(value.slice(11, 13)) * 60 + Number(value.slice(14, 16));
114
+ };
115
+ var validateEvents = (events) => {
116
+ const ids = /* @__PURE__ */ new Set();
117
+ for (const event of events) {
118
+ if (!event.id || ids.has(event.id)) {
119
+ throw new RangeError("Calendar event IDs must be nonempty and unique.");
120
+ }
121
+ ids.add(event.id);
122
+ if (parseTime(event.end, event.allDay === true) <= parseTime(event.start, event.allDay === true)) {
123
+ throw new RangeError(`Calendar event ${event.id} must end after it starts.`);
124
+ }
125
+ }
126
+ };
127
+ var segmentsForDate = (events, date) => {
128
+ const start = parseDate(date).getTime() / 6e4;
129
+ const end = parseDate(addDays(date, 1)).getTime() / 6e4;
130
+ return events.flatMap((event) => {
131
+ const from = parseTime(event.start, event.allDay === true);
132
+ const to = parseTime(event.end, event.allDay === true);
133
+ if (from >= end || to <= start) {
134
+ return [];
135
+ }
136
+ return [
137
+ {
138
+ event,
139
+ date,
140
+ start: Math.max(from - start, 0),
141
+ end: Math.min(to - start, 1440),
142
+ continuesBefore: from < start,
143
+ continuesAfter: to > end
144
+ }
145
+ ];
146
+ }).sort((a, b) => {
147
+ return Number(b.event.allDay ?? false) - Number(a.event.allDay ?? false) || a.start - b.start || b.end - a.end || a.event.id.localeCompare(b.event.id);
148
+ });
149
+ };
150
+ var positionEvents = (segments, start, end) => {
151
+ const visible = segments.filter((segment) => {
152
+ return !segment.event.allDay && segment.start < end && segment.end > start;
153
+ }).map((segment) => {
154
+ return {
155
+ ...segment,
156
+ start: Math.max(segment.start, start),
157
+ end: Math.min(segment.end, end)
158
+ };
159
+ }).sort((a, b) => {
160
+ return a.start - b.start || b.end - a.end || a.event.id.localeCompare(b.event.id);
161
+ });
162
+ const groups = [];
163
+ for (const segment of visible) {
164
+ let group = groups.at(-1);
165
+ if (!group || segment.start >= group.end) {
166
+ group = {
167
+ end: segment.end,
168
+ segments: []
169
+ };
170
+ groups.push(group);
171
+ }
172
+ group.end = Math.max(group.end, segment.end);
173
+ group.segments.push(segment);
174
+ }
175
+ return groups.flatMap((group) => {
176
+ const ends = [];
177
+ const positioned = group.segments.map((segment) => {
178
+ let column = ends.findIndex((columnEnd) => {
179
+ return columnEnd <= segment.start;
180
+ });
181
+ if (column < 0) {
182
+ column = ends.length;
183
+ }
184
+ ends[column] = segment.end;
185
+ return {
186
+ ...segment,
187
+ column
188
+ };
189
+ });
190
+ return positioned.map((segment) => {
191
+ return {
192
+ ...segment,
193
+ columns: ends.length
194
+ };
195
+ });
196
+ });
197
+ };
198
+
199
+ // src/calendar-agenda-view/calendar-agenda-view.tsx
200
+ import { cn as cn3 } from "@gridui/style-utils";
201
+ import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
202
+ var CalendarAgendaView = (props) => {
203
+ const {
204
+ date,
205
+ events,
206
+ locale = "en-US",
207
+ today,
208
+ days = 7,
209
+ empty = "No events",
210
+ allDayLabel = "All day",
211
+ formatTime = formatMinute,
212
+ selectedEventId,
213
+ onEventSelect,
214
+ renderEvent,
215
+ getEventLabel,
216
+ className,
217
+ ...restProps
218
+ } = props;
219
+ validateEvents(events);
220
+ if (!Number.isInteger(days) || days < 1 || days > 366) {
221
+ throw new RangeError("CalendarAgendaView days must be an integer from 1 to 366.");
222
+ }
223
+ const groups = Array.from({ length: days }, (_, index) => {
224
+ const day = addDays(date, index);
225
+ return {
226
+ day,
227
+ segments: segmentsForDate(events, day)
228
+ };
229
+ }).filter((group) => {
230
+ return group.segments.length > 0;
231
+ });
232
+ return /* @__PURE__ */ jsx3(
233
+ "div",
234
+ {
235
+ ...restProps,
236
+ className: cn3("gridui-calendar-agenda", className),
237
+ role: restProps.role ?? "region",
238
+ "aria-label": restProps["aria-label"] ?? formatDate(date, locale, { dateStyle: "full" }),
239
+ children: groups.length === 0 ? empty : groups.map(({ day, segments }) => {
240
+ return /* @__PURE__ */ jsxs(
241
+ "section",
242
+ {
243
+ className: "gridui-calendar-agenda-day",
244
+ "aria-label": formatDate(day, locale, { dateStyle: "full" }),
245
+ children: [
246
+ /* @__PURE__ */ jsx3(
247
+ "h3",
248
+ {
249
+ className: "m-0 type-ui",
250
+ "aria-current": day === today ? "date" : void 0,
251
+ children: formatDate(day, locale, {
252
+ month: "short",
253
+ day: "numeric",
254
+ weekday: "short"
255
+ })
256
+ }
257
+ ),
258
+ /* @__PURE__ */ jsx3(
259
+ "ol",
260
+ {
261
+ className: "gridui-calendar-agenda-events",
262
+ children: segments.map((segment) => {
263
+ const context = {
264
+ date: day,
265
+ view: "agenda",
266
+ continuesBefore: segment.continuesBefore,
267
+ continuesAfter: segment.continuesAfter
268
+ };
269
+ return /* @__PURE__ */ jsx3(
270
+ "li",
271
+ {
272
+ children: /* @__PURE__ */ jsx3(
273
+ CalendarEvent,
274
+ {
275
+ colors: segment.event.colors,
276
+ selected: selectedEventId === segment.event.id,
277
+ disabled: segment.event.disabled,
278
+ className: "gridui-calendar-agenda-event",
279
+ "aria-label": getEventLabel?.(segment.event, context) ?? `${segment.event.title}, ${segment.event.start} \u2013 ${segment.event.end}`,
280
+ onClick: () => {
281
+ onEventSelect?.(segment.event, context);
282
+ },
283
+ children: renderEvent?.(segment.event, context) ?? /* @__PURE__ */ jsxs(Fragment, { children: [
284
+ /* @__PURE__ */ jsx3(
285
+ "span",
286
+ {
287
+ className: "type-caption tabular-nums",
288
+ children: segment.event.allDay ? allDayLabel : `${formatTime(segment.start)}\u2013${formatTime(segment.end)}`
289
+ }
290
+ ),
291
+ /* @__PURE__ */ jsx3(
292
+ "span",
293
+ {
294
+ className: "wrap-anywhere",
295
+ children: segment.event.title
296
+ }
297
+ )
298
+ ] })
299
+ }
300
+ )
301
+ },
302
+ segment.event.id
303
+ );
304
+ })
305
+ }
306
+ )
307
+ ]
308
+ },
309
+ day
310
+ );
311
+ })
312
+ }
313
+ );
314
+ };
315
+
316
+ // src/shared/time-grid.tsx
317
+ import { cn as cn4 } from "@gridui/style-utils";
318
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
319
+ var TimeGrid = (props) => {
320
+ const {
321
+ columns,
322
+ view,
323
+ date,
324
+ events,
325
+ locale = "en-US",
326
+ today,
327
+ selectedEventId,
328
+ onEventSelect,
329
+ renderEvent,
330
+ getEventLabel,
331
+ startHour = 8,
332
+ endHour = 18,
333
+ hourHeight = 56,
334
+ allDayLabel = "All day",
335
+ formatTime = formatMinute,
336
+ onTimeSelect,
337
+ getTimeLabel,
338
+ className,
339
+ style,
340
+ ...restProps
341
+ } = props;
342
+ validateEvents(events);
343
+ if (!columns.length || new Set(columns.map((column) => {
344
+ return column.id;
345
+ })).size !== columns.length) {
346
+ throw new RangeError("Calendar columns must have unique IDs and cannot be empty.");
347
+ }
348
+ if (!Number.isInteger(startHour) || !Number.isInteger(endHour) || startHour < 0 || endHour > 24 || startHour >= endHour || !Number.isFinite(hourHeight) || hourHeight <= 0) {
349
+ throw new RangeError("Calendar hours must form an ordered range within 0\u201324; hourHeight must be positive.");
350
+ }
351
+ const hours = Array.from({ length: endHour - startHour }, (_, index) => {
352
+ return startHour + index;
353
+ });
354
+ const lanes = columns.map((column) => {
355
+ const segments = segmentsForDate(events, column.date).filter((segment) => {
356
+ return column.resourceId === void 0 || segment.event.resourceId === column.resourceId;
357
+ });
358
+ return {
359
+ column,
360
+ segments
361
+ };
362
+ });
363
+ const context = (segment) => {
364
+ return {
365
+ date: segment.date,
366
+ view,
367
+ continuesBefore: segment.continuesBefore,
368
+ continuesAfter: segment.continuesAfter
369
+ };
370
+ };
371
+ const label = (segment) => {
372
+ return getEventLabel?.(segment.event, context(segment)) ?? `${segment.event.title}, ${segment.event.start} \u2013 ${segment.event.end}`;
373
+ };
374
+ return /* @__PURE__ */ jsxs2(
375
+ "div",
376
+ {
377
+ ...restProps,
378
+ role: restProps.role ?? "region",
379
+ "aria-label": restProps["aria-label"] ?? formatDate(date, locale, { dateStyle: "full" }),
380
+ className: cn4("gridui-calendar-time-grid", className),
381
+ style: {
382
+ "--gridui-calendar-columns": columns.length,
383
+ "--gridui-calendar-hour-height": `${hourHeight}px`,
384
+ "--gridui-calendar-hours": hours.length,
385
+ ...style
386
+ },
387
+ children: [
388
+ /* @__PURE__ */ jsx4(
389
+ "div",
390
+ {
391
+ className: "gridui-calendar-time-corner"
392
+ }
393
+ ),
394
+ columns.map((column) => {
395
+ return /* @__PURE__ */ jsxs2(
396
+ "div",
397
+ {
398
+ className: "gridui-calendar-time-heading",
399
+ "data-today": column.date === today || void 0,
400
+ children: [
401
+ /* @__PURE__ */ jsx4(
402
+ "div",
403
+ {
404
+ className: "type-ui",
405
+ children: column.label
406
+ }
407
+ ),
408
+ column.description !== void 0 && /* @__PURE__ */ jsx4(
409
+ "div",
410
+ {
411
+ className: "type-caption text-muted",
412
+ children: column.description
413
+ }
414
+ )
415
+ ]
416
+ },
417
+ column.id
418
+ );
419
+ }),
420
+ lanes.some((lane) => {
421
+ return lane.segments.some((segment) => {
422
+ return segment.event.allDay;
423
+ });
424
+ }) && /* @__PURE__ */ jsxs2(Fragment2, { children: [
425
+ /* @__PURE__ */ jsx4(
426
+ "div",
427
+ {
428
+ className: "gridui-calendar-all-day-label type-caption text-muted",
429
+ children: allDayLabel
430
+ }
431
+ ),
432
+ lanes.map(({ column, segments }) => {
433
+ return /* @__PURE__ */ jsx4(
434
+ "div",
435
+ {
436
+ className: "gridui-calendar-all-day",
437
+ children: segments.filter((segment) => {
438
+ return segment.event.allDay;
439
+ }).map((segment) => {
440
+ return /* @__PURE__ */ jsx4(
441
+ CalendarEvent,
442
+ {
443
+ colors: segment.event.colors,
444
+ appearance: "compact",
445
+ selected: selectedEventId === segment.event.id,
446
+ disabled: segment.event.disabled,
447
+ "aria-label": label(segment),
448
+ onClick: () => {
449
+ onEventSelect?.(segment.event, context(segment));
450
+ },
451
+ children: renderEvent?.(segment.event, context(segment)) ?? segment.event.title
452
+ },
453
+ segment.event.id
454
+ );
455
+ })
456
+ },
457
+ column.id
458
+ );
459
+ })
460
+ ] }),
461
+ /* @__PURE__ */ jsx4(
462
+ "div",
463
+ {
464
+ className: "gridui-calendar-time-axis",
465
+ "aria-hidden": "true",
466
+ children: hours.map((hour) => {
467
+ return /* @__PURE__ */ jsx4(
468
+ "span",
469
+ {
470
+ className: "type-caption text-muted tabular-nums",
471
+ children: formatTime(hour * 60)
472
+ },
473
+ hour
474
+ );
475
+ })
476
+ }
477
+ ),
478
+ lanes.map(({ column, segments }) => {
479
+ return /* @__PURE__ */ jsxs2(
480
+ "div",
481
+ {
482
+ className: "gridui-calendar-time-lane",
483
+ children: [
484
+ onTimeSelect && hours.map((hour) => {
485
+ const selection = {
486
+ date: column.date,
487
+ minute: hour * 60,
488
+ ...column.resourceId === void 0 ? {} : { resourceId: column.resourceId }
489
+ };
490
+ return /* @__PURE__ */ jsx4(
491
+ "button",
492
+ {
493
+ type: "button",
494
+ className: "gridui-calendar-time-slot gridui-focus-contained gridui-hover-surface",
495
+ "aria-label": getTimeLabel?.(selection) ?? `${column.date} ${formatTime(hour * 60)}${column.resourceId ? `, ${column.resourceId}` : ""}`,
496
+ onClick: () => {
497
+ onTimeSelect(selection);
498
+ }
499
+ },
500
+ hour
501
+ );
502
+ }),
503
+ positionEvents(segments, startHour * 60, endHour * 60).map((segment) => {
504
+ const range = (endHour - startHour) * 60;
505
+ return /* @__PURE__ */ jsx4(
506
+ CalendarEvent,
507
+ {
508
+ colors: segment.event.colors,
509
+ selected: selectedEventId === segment.event.id,
510
+ disabled: segment.event.disabled,
511
+ "aria-label": label(segment),
512
+ "data-event-id": segment.event.id,
513
+ "data-continues-before": segment.continuesBefore || void 0,
514
+ "data-continues-after": segment.continuesAfter || void 0,
515
+ className: "gridui-calendar-positioned-event",
516
+ style: {
517
+ top: `${(segment.start - startHour * 60) / range * 100}%`,
518
+ height: `calc(${(segment.end - segment.start) / range * 100}% - 2px)`,
519
+ insetInlineStart: `calc(${segment.column / segment.columns * 100}% + 2px)`,
520
+ width: `calc(${100 / segment.columns}% - 4px)`
521
+ },
522
+ onClick: () => {
523
+ onEventSelect?.(segment.event, context(segment));
524
+ },
525
+ children: renderEvent?.(segment.event, context(segment)) ?? /* @__PURE__ */ jsxs2(Fragment2, { children: [
526
+ /* @__PURE__ */ jsx4(
527
+ "span",
528
+ {
529
+ className: "gridui-calendar-event-title",
530
+ children: segment.event.title
531
+ }
532
+ ),
533
+ /* @__PURE__ */ jsxs2(
534
+ "span",
535
+ {
536
+ className: "gridui-calendar-event-time type-caption tabular-nums",
537
+ children: [
538
+ formatTime(segment.start),
539
+ "\u2013",
540
+ formatTime(segment.end)
541
+ ]
542
+ }
543
+ )
544
+ ] })
545
+ },
546
+ segment.event.id
547
+ );
548
+ })
549
+ ]
550
+ },
551
+ column.id
552
+ );
553
+ })
554
+ ]
555
+ }
556
+ );
557
+ };
558
+
559
+ // src/calendar-day-view/calendar-day-view.tsx
560
+ import { jsx as jsx5 } from "react/jsx-runtime";
561
+ var CalendarDayView = (props) => {
562
+ const { resources, ...restProps } = props;
563
+ const columns = resources?.map((resource) => {
564
+ return {
565
+ id: resource.id,
566
+ resourceId: resource.id,
567
+ date: props.date,
568
+ label: resource.label,
569
+ description: resource.description
570
+ };
571
+ }) ?? [
572
+ {
573
+ id: props.date,
574
+ date: props.date,
575
+ label: formatDate(props.date, props.locale ?? "en-US", {
576
+ weekday: "long",
577
+ month: "short",
578
+ day: "numeric"
579
+ })
580
+ }
581
+ ];
582
+ return /* @__PURE__ */ jsx5(
583
+ TimeGrid,
584
+ {
585
+ ...restProps,
586
+ columns,
587
+ view: "day"
588
+ }
589
+ );
590
+ };
591
+
592
+ // src/calendar-month-view/calendar-month-view.tsx
593
+ import { cn as cn5 } from "@gridui/style-utils";
594
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
595
+ var CalendarMonthView = (props) => {
596
+ const { date, events, locale = "en-US", today, weekStartsOn = 1, maxEvents = 3, selectedEventId, onDateSelect, onEventSelect, renderEvent, getEventLabel, getMoreLabel, className, ...restProps } = props;
597
+ validateEvents(events);
598
+ if (!Number.isInteger(maxEvents) || maxEvents < 1) {
599
+ throw new RangeError("CalendarMonthView maxEvents must be a positive integer.");
600
+ }
601
+ const month = date.slice(0, 7);
602
+ parseDate(date);
603
+ const start = startOfWeek(`${month}-01`, weekStartsOn);
604
+ return /* @__PURE__ */ jsx6(
605
+ "div",
606
+ {
607
+ ...restProps,
608
+ className: cn5("gridui-calendar-month", className),
609
+ role: restProps.role ?? "region",
610
+ "aria-label": restProps["aria-label"] ?? formatDate(date, locale, {
611
+ month: "long",
612
+ year: "numeric"
613
+ }),
614
+ children: /* @__PURE__ */ jsxs3(
615
+ "table",
616
+ {
617
+ className: "gridui-calendar-month-table",
618
+ children: [
619
+ /* @__PURE__ */ jsx6("thead", { children: /* @__PURE__ */ jsx6("tr", { children: Array.from({ length: 7 }, (_, index) => {
620
+ const day = addDays(start, index);
621
+ return /* @__PURE__ */ jsx6(
622
+ "th",
623
+ {
624
+ scope: "col",
625
+ className: "type-caption text-muted",
626
+ children: formatDate(day, locale, { weekday: "short" })
627
+ },
628
+ day
629
+ );
630
+ }) }) }),
631
+ /* @__PURE__ */ jsx6("tbody", { children: Array.from({ length: 6 }, (_, row) => {
632
+ return /* @__PURE__ */ jsx6(
633
+ "tr",
634
+ {
635
+ children: Array.from({ length: 7 }, (_column, col) => {
636
+ const day = addDays(start, row * 7 + col);
637
+ const segments = segmentsForDate(events, day);
638
+ const shown = onDateSelect ? segments.slice(0, maxEvents) : segments;
639
+ return /* @__PURE__ */ jsxs3(
640
+ "td",
641
+ {
642
+ "data-outside": !day.startsWith(month) || void 0,
643
+ children: [
644
+ /* @__PURE__ */ jsx6(
645
+ "button",
646
+ {
647
+ type: "button",
648
+ className: "gridui-calendar-day-number gridui-hover-surface gridui-focus-contained",
649
+ "aria-label": formatDate(day, locale, { dateStyle: "full" }),
650
+ "aria-current": day === today ? "date" : void 0,
651
+ "aria-pressed": day === date,
652
+ onClick: () => {
653
+ onDateSelect?.(day);
654
+ },
655
+ children: parseDate(day).getUTCDate()
656
+ }
657
+ ),
658
+ /* @__PURE__ */ jsxs3(
659
+ "div",
660
+ {
661
+ className: "gridui-calendar-month-events",
662
+ children: [
663
+ shown.map((segment) => {
664
+ const context = {
665
+ date: day,
666
+ view: "month",
667
+ continuesBefore: segment.continuesBefore,
668
+ continuesAfter: segment.continuesAfter
669
+ };
670
+ return /* @__PURE__ */ jsx6(
671
+ CalendarEvent,
672
+ {
673
+ appearance: "compact",
674
+ colors: segment.event.colors,
675
+ selected: selectedEventId === segment.event.id,
676
+ disabled: segment.event.disabled,
677
+ "aria-label": getEventLabel?.(segment.event, context) ?? `${segment.event.title}, ${segment.event.start} \u2013 ${segment.event.end}`,
678
+ onClick: () => {
679
+ onEventSelect?.(segment.event, context);
680
+ },
681
+ children: renderEvent?.(segment.event, context) ?? segment.event.title
682
+ },
683
+ segment.event.id
684
+ );
685
+ }),
686
+ segments.length > shown.length && /* @__PURE__ */ jsx6(
687
+ "button",
688
+ {
689
+ type: "button",
690
+ className: "gridui-calendar-more gridui-hover-surface gridui-focus-contained type-caption",
691
+ onClick: () => {
692
+ onDateSelect?.(day);
693
+ },
694
+ children: getMoreLabel?.(segments.length - shown.length, day) ?? `+${segments.length - shown.length} more`
695
+ }
696
+ )
697
+ ]
698
+ }
699
+ )
700
+ ]
701
+ },
702
+ day
703
+ );
704
+ })
705
+ },
706
+ row
707
+ );
708
+ }) })
709
+ ]
710
+ }
711
+ )
712
+ }
713
+ );
714
+ };
715
+
716
+ // src/calendar-toolbar/calendar-toolbar.tsx
717
+ import { Button } from "@gridui/button";
718
+ import { cn as cn6 } from "@gridui/style-utils";
719
+ import { ChevronLeft, ChevronRight } from "lucide-react";
720
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
721
+ var defaults = {
722
+ previous: "Previous period",
723
+ next: "Next period",
724
+ today: "Today",
725
+ views: "Calendar views",
726
+ day: "Day",
727
+ week: "Week",
728
+ month: "Month",
729
+ agenda: "Agenda"
730
+ };
731
+ var CalendarToolbar = (props) => {
732
+ const { date, view, onNavigate, onViewChange, onToday, locale = "en-US", weekStartsOn = 1, views = [
733
+ "day",
734
+ "week",
735
+ "month",
736
+ "agenda"
737
+ ], labels, title, children, className, ...restProps } = props;
738
+ const text = {
739
+ ...defaults,
740
+ ...labels
741
+ };
742
+ const start = view === "week" ? startOfWeek(date, weekStartsOn) : date;
743
+ let caption = formatDate(date, locale, {
744
+ month: "long",
745
+ day: "numeric",
746
+ year: "numeric"
747
+ });
748
+ if (view === "month") {
749
+ caption = formatDate(date, locale, {
750
+ month: "long",
751
+ year: "numeric"
752
+ });
753
+ } else if (view === "week") {
754
+ const first = formatDate(start, locale, {
755
+ month: "short",
756
+ day: "numeric"
757
+ });
758
+ const last = formatDate(addDays(start, 6), locale, {
759
+ month: "short",
760
+ day: "numeric",
761
+ year: "numeric"
762
+ });
763
+ caption = `${first} \u2013 ${last}`;
764
+ }
765
+ return /* @__PURE__ */ jsxs4(
766
+ "div",
767
+ {
768
+ ...restProps,
769
+ className: cn6("gridui-calendar-toolbar flex min-w-0 flex-wrap items-center gap-gridui-8", className),
770
+ children: [
771
+ /* @__PURE__ */ jsxs4(
772
+ "div",
773
+ {
774
+ className: "flex min-w-0 flex-wrap items-center gap-gridui-4",
775
+ children: [
776
+ /* @__PURE__ */ jsx7(
777
+ Button,
778
+ {
779
+ variant: "quiet",
780
+ layout: "icon",
781
+ size: "small",
782
+ "aria-label": text.previous,
783
+ onClick: () => {
784
+ onNavigate(-1);
785
+ },
786
+ children: /* @__PURE__ */ jsx7(
787
+ ChevronLeft,
788
+ {
789
+ "aria-hidden": "true"
790
+ }
791
+ )
792
+ }
793
+ ),
794
+ /* @__PURE__ */ jsx7(
795
+ Button,
796
+ {
797
+ variant: "quiet",
798
+ layout: "icon",
799
+ size: "small",
800
+ "aria-label": text.next,
801
+ onClick: () => {
802
+ onNavigate(1);
803
+ },
804
+ children: /* @__PURE__ */ jsx7(
805
+ ChevronRight,
806
+ {
807
+ "aria-hidden": "true"
808
+ }
809
+ )
810
+ }
811
+ ),
812
+ onToday && /* @__PURE__ */ jsx7(
813
+ Button,
814
+ {
815
+ variant: "secondary",
816
+ size: "small",
817
+ onClick: onToday,
818
+ children: text.today
819
+ }
820
+ )
821
+ ]
822
+ }
823
+ ),
824
+ /* @__PURE__ */ jsx7(
825
+ "div",
826
+ {
827
+ className: "min-w-0 flex-1 type-ui wrap-anywhere",
828
+ "aria-live": "polite",
829
+ children: title ?? caption
830
+ }
831
+ ),
832
+ /* @__PURE__ */ jsx7(
833
+ "div",
834
+ {
835
+ className: "flex flex-wrap gap-gridui-4",
836
+ role: "group",
837
+ "aria-label": text.views,
838
+ children: views.map((item) => {
839
+ return /* @__PURE__ */ jsx7(
840
+ Button,
841
+ {
842
+ size: "small",
843
+ variant: item === view ? "primary" : "quiet",
844
+ "aria-pressed": item === view,
845
+ onClick: () => {
846
+ onViewChange(item);
847
+ },
848
+ children: text[item]
849
+ },
850
+ item
851
+ );
852
+ })
853
+ }
854
+ ),
855
+ children
856
+ ]
857
+ }
858
+ );
859
+ };
860
+
861
+ // src/calendar-week-view/calendar-week-view.tsx
862
+ import { jsx as jsx8 } from "react/jsx-runtime";
863
+ var CalendarWeekView = (props) => {
864
+ const { weekStartsOn = 1, days = 7, ...restProps } = props;
865
+ if (!Number.isInteger(days) || days < 1 || days > 7) {
866
+ throw new RangeError("CalendarWeekView days must be an integer from 1 to 7.");
867
+ }
868
+ const start = startOfWeek(props.date, weekStartsOn);
869
+ const columns = Array.from({ length: days }, (_, index) => {
870
+ const date = addDays(start, index);
871
+ return {
872
+ id: date,
873
+ date,
874
+ label: formatDate(date, props.locale ?? "en-US", { weekday: "short" }),
875
+ description: formatDate(date, props.locale ?? "en-US", { day: "numeric" })
876
+ };
877
+ });
878
+ return /* @__PURE__ */ jsx8(
879
+ TimeGrid,
880
+ {
881
+ ...restProps,
882
+ columns,
883
+ view: "week"
884
+ }
885
+ );
886
+ };
887
+
888
+ // src/use-calendar/use-calendar.ts
889
+ import { useState } from "react";
890
+ var useCalendar = (options) => {
891
+ const { defaultDate, date: controlledDate, defaultView = "day", view: controlledView, onDateChange, onViewChange } = options;
892
+ parseDate(controlledDate ?? defaultDate);
893
+ const [
894
+ localDate,
895
+ setLocalDate
896
+ ] = useState(defaultDate);
897
+ const [
898
+ localView,
899
+ setLocalView
900
+ ] = useState(defaultView);
901
+ const date = controlledDate ?? localDate;
902
+ const view = controlledView ?? localView;
903
+ const setDate = (next) => {
904
+ parseDate(next);
905
+ if (next === date) {
906
+ return;
907
+ }
908
+ if (controlledDate === void 0) {
909
+ setLocalDate(next);
910
+ }
911
+ onDateChange?.(next);
912
+ };
913
+ const setView = (next) => {
914
+ if (next === view) {
915
+ return;
916
+ }
917
+ if (controlledView === void 0) {
918
+ setLocalView(next);
919
+ }
920
+ onViewChange?.(next);
921
+ };
922
+ const navigate = (direction) => {
923
+ setDate(view === "month" ? addMonths(date, direction) : addDays(date, direction * (view === "day" ? 1 : 7)));
924
+ };
925
+ return {
926
+ date,
927
+ view,
928
+ setDate,
929
+ setView,
930
+ navigate
931
+ };
932
+ };
933
+ export {
934
+ Calendar,
935
+ CalendarAgendaView,
936
+ CalendarDayView,
937
+ CalendarEvent,
938
+ CalendarMonthView,
939
+ CalendarToolbar,
940
+ CalendarWeekView,
941
+ useCalendar
942
+ };
943
+ //# sourceMappingURL=index.js.map