@lazerlen/legend-calendar 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.eslintrc.js +29 -0
  2. package/.turbo/turbo-build.log +19 -0
  3. package/.turbo/turbo-lint.log +14 -0
  4. package/.turbo/turbo-test.log +261 -0
  5. package/.turbo/turbo-typecheck.log +1 -0
  6. package/CHANGELOG.md +127 -0
  7. package/dist/index.d.mts +679 -0
  8. package/dist/index.d.ts +679 -0
  9. package/dist/index.js +2 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/index.mjs +2 -0
  12. package/dist/index.mjs.map +1 -0
  13. package/package.json +47 -0
  14. package/src/components/Calendar.stories.tsx +226 -0
  15. package/src/components/Calendar.tsx +224 -0
  16. package/src/components/CalendarItemDay.tsx +385 -0
  17. package/src/components/CalendarItemEmpty.tsx +30 -0
  18. package/src/components/CalendarItemWeekName.tsx +67 -0
  19. package/src/components/CalendarList.stories.tsx +326 -0
  20. package/src/components/CalendarList.tsx +373 -0
  21. package/src/components/CalendarRowMonth.tsx +62 -0
  22. package/src/components/CalendarRowWeek.tsx +46 -0
  23. package/src/components/CalendarThemeProvider.tsx +43 -0
  24. package/src/components/HStack.tsx +67 -0
  25. package/src/components/VStack.tsx +67 -0
  26. package/src/components/index.ts +108 -0
  27. package/src/developer/decorators.tsx +54 -0
  28. package/src/developer/loggginHandler.tsx +6 -0
  29. package/src/developer/useRenderCount.ts +7 -0
  30. package/src/helpers/dates.test.ts +567 -0
  31. package/src/helpers/dates.ts +163 -0
  32. package/src/helpers/functions.ts +327 -0
  33. package/src/helpers/numbers.ts +11 -0
  34. package/src/helpers/strings.ts +2 -0
  35. package/src/helpers/tokens.ts +71 -0
  36. package/src/helpers/types.ts +3 -0
  37. package/src/hooks/useCalendar.test.ts +381 -0
  38. package/src/hooks/useCalendar.ts +351 -0
  39. package/src/hooks/useCalendarList.test.ts +382 -0
  40. package/src/hooks/useCalendarList.tsx +291 -0
  41. package/src/hooks/useDateRange.test.ts +128 -0
  42. package/src/hooks/useDateRange.ts +94 -0
  43. package/src/hooks/useOptimizedDayMetadata.test.ts +582 -0
  44. package/src/hooks/useOptimizedDayMetadata.ts +93 -0
  45. package/src/hooks/useTheme.ts +14 -0
  46. package/src/index.ts +24 -0
  47. package/tsconfig.json +13 -0
  48. package/tsup.config.ts +15 -0
@@ -0,0 +1,582 @@
1
+ import { renderHook, act } from "@testing-library/react-hooks";
2
+ import { describe, it, expect } from "bun:test";
3
+
4
+ import type { CalendarDayMetadata } from "@/hooks/useCalendar";
5
+ import { fromDateId } from "@/helpers/dates";
6
+ import {
7
+ activeDateRangesEmitter,
8
+ useOptimizedDayMetadata,
9
+ } from "@/hooks/useOptimizedDayMetadata";
10
+
11
+ describe("useOptimizedDayMetadata", () => {
12
+ it("return the base metadata as the initial value", () => {
13
+ const baseMetadata: CalendarDayMetadata = {
14
+ date: fromDateId("2024-02-16"),
15
+ displayLabel: "16",
16
+ id: "2024-02-16",
17
+ isDifferentMonth: false,
18
+ isDisabled: false,
19
+ isEndOfMonth: false,
20
+ isEndOfRange: false,
21
+ isEndOfWeek: false,
22
+ isRangeValid: false,
23
+ isStartOfMonth: false,
24
+ isStartOfRange: false,
25
+ isStartOfWeek: false,
26
+ isToday: false,
27
+ isWeekend: false,
28
+ state: "idle",
29
+ };
30
+
31
+ const { result } = renderHook(() => useOptimizedDayMetadata(baseMetadata));
32
+
33
+ expect(result.current).toEqual(baseMetadata);
34
+ expect(result.all).toHaveLength(1);
35
+ });
36
+
37
+ it("if the base changes, the returned value should match it.", () => {
38
+ const baseMetadata: CalendarDayMetadata = {
39
+ date: fromDateId("2024-02-16"),
40
+ displayLabel: "16",
41
+ id: "2024-02-16",
42
+ isDifferentMonth: false,
43
+ isDisabled: false,
44
+ isEndOfMonth: false,
45
+ isEndOfRange: false,
46
+ isEndOfWeek: false,
47
+ isRangeValid: false,
48
+ isStartOfMonth: false,
49
+ isStartOfRange: false,
50
+ isStartOfWeek: false,
51
+ isToday: false,
52
+ isWeekend: false,
53
+ state: "idle",
54
+ };
55
+
56
+ const { result, rerender } = renderHook(
57
+ (initial) => useOptimizedDayMetadata(initial),
58
+ {
59
+ initialProps: baseMetadata,
60
+ }
61
+ );
62
+
63
+ expect(result.current).toEqual(baseMetadata);
64
+ rerender({
65
+ ...baseMetadata,
66
+ isDisabled: true,
67
+ });
68
+
69
+ expect(result.current).toEqual({
70
+ ...baseMetadata,
71
+ isDisabled: true,
72
+ });
73
+ // `useEffect` is called after the hook renders with the new baseMetadata.
74
+ // Hence, the length of the `result.all` array is 3.
75
+ expect(result.all).toHaveLength(3);
76
+ });
77
+
78
+ it("endOfRange: returns the updated metadata once an event is emitted", () => {
79
+ const baseMetadata: CalendarDayMetadata = {
80
+ date: fromDateId("2024-02-16"),
81
+ displayLabel: "16",
82
+ id: "2024-02-16",
83
+ isDifferentMonth: false,
84
+ isDisabled: false,
85
+ isEndOfMonth: false,
86
+ isEndOfRange: false,
87
+ isEndOfWeek: false,
88
+ isRangeValid: false,
89
+ isStartOfMonth: false,
90
+ isStartOfRange: false,
91
+ isStartOfWeek: false,
92
+ isToday: false,
93
+ isWeekend: false,
94
+ state: "idle",
95
+ };
96
+
97
+ // Initial state
98
+ const { result } = renderHook(
99
+ (initial) => useOptimizedDayMetadata(initial),
100
+ {
101
+ initialProps: baseMetadata,
102
+ }
103
+ );
104
+ expect(result.current).toEqual(baseMetadata);
105
+
106
+ // Emit event
107
+ act(() => {
108
+ activeDateRangesEmitter.emit("onSetActiveDateRanges", {
109
+ ranges: [
110
+ {
111
+ startId: "2024-02-01",
112
+ endId: "2024-02-16",
113
+ },
114
+ ],
115
+ });
116
+ });
117
+
118
+ expect(result.current).toEqual({
119
+ ...baseMetadata,
120
+ state: "active",
121
+ isEndOfRange: true,
122
+ isRangeValid: true,
123
+ });
124
+ expect(result.all).toHaveLength(2);
125
+ });
126
+
127
+ it("startOfRange: returns the updated metadata once an event is emitted", () => {
128
+ const baseMetadata: CalendarDayMetadata = {
129
+ date: fromDateId("2024-02-01"),
130
+ displayLabel: "01",
131
+ id: "2024-02-01",
132
+ isDifferentMonth: false,
133
+ isDisabled: false,
134
+ isEndOfMonth: false,
135
+ isEndOfRange: false,
136
+ isEndOfWeek: false,
137
+ isRangeValid: false,
138
+ isStartOfMonth: false,
139
+ isStartOfRange: false,
140
+ isStartOfWeek: false,
141
+ isToday: false,
142
+ isWeekend: false,
143
+ state: "idle",
144
+ };
145
+
146
+ // Initial state
147
+ const { result } = renderHook(
148
+ (initial) => useOptimizedDayMetadata(initial),
149
+ {
150
+ initialProps: baseMetadata,
151
+ }
152
+ );
153
+ expect(result.current).toEqual(baseMetadata);
154
+
155
+ // Emit event
156
+ act(() => {
157
+ activeDateRangesEmitter.emit("onSetActiveDateRanges", {
158
+ ranges: [
159
+ {
160
+ startId: "2024-02-01",
161
+ endId: "2024-02-16",
162
+ },
163
+ ],
164
+ });
165
+ });
166
+
167
+ expect(result.current).toEqual({
168
+ ...baseMetadata,
169
+ state: "active",
170
+ isStartOfRange: true,
171
+ isRangeValid: true,
172
+ });
173
+ expect(result.all).toHaveLength(2);
174
+ });
175
+
176
+ it("startOfRange|endOfRange: returns the updated metadata once an event is emitted", () => {
177
+ const baseMetadata: CalendarDayMetadata = {
178
+ date: fromDateId("2024-02-01"),
179
+ displayLabel: "01",
180
+ id: "2024-02-01",
181
+ isDifferentMonth: false,
182
+ isDisabled: false,
183
+ isEndOfMonth: false,
184
+ isEndOfRange: false,
185
+ isEndOfWeek: false,
186
+ isRangeValid: false,
187
+ isStartOfMonth: false,
188
+ isStartOfRange: false,
189
+ isStartOfWeek: false,
190
+ isToday: false,
191
+ isWeekend: false,
192
+ state: "idle",
193
+ };
194
+
195
+ // Initial state
196
+ const { result } = renderHook(
197
+ (initial) => useOptimizedDayMetadata(initial),
198
+ {
199
+ initialProps: baseMetadata,
200
+ }
201
+ );
202
+ expect(result.current).toEqual(baseMetadata);
203
+
204
+ // Emit event
205
+ act(() => {
206
+ activeDateRangesEmitter.emit("onSetActiveDateRanges", {
207
+ ranges: [
208
+ {
209
+ startId: "2024-02-01",
210
+ endId: "2024-02-01",
211
+ },
212
+ ],
213
+ });
214
+ });
215
+
216
+ expect(result.current).toEqual({
217
+ ...baseMetadata,
218
+ state: "active",
219
+ isStartOfRange: true,
220
+ isEndOfRange: true,
221
+ isRangeValid: true,
222
+ });
223
+ expect(result.all).toHaveLength(2);
224
+ });
225
+
226
+ it("doesn't re-render if the active dates don't overlap", () => {
227
+ const baseMetadata: CalendarDayMetadata = {
228
+ date: fromDateId("2024-02-18"),
229
+ displayLabel: "18",
230
+ id: "2024-02-18",
231
+ isDifferentMonth: false,
232
+ isDisabled: false,
233
+ isEndOfMonth: false,
234
+ isEndOfRange: false,
235
+ isEndOfWeek: false,
236
+ isRangeValid: false,
237
+ isStartOfMonth: false,
238
+ isStartOfRange: false,
239
+ isStartOfWeek: false,
240
+ isToday: false,
241
+ isWeekend: false,
242
+ state: "idle",
243
+ };
244
+
245
+ // Initial state
246
+ const { result } = renderHook(
247
+ (initial) => useOptimizedDayMetadata(initial),
248
+ {
249
+ initialProps: baseMetadata,
250
+ }
251
+ );
252
+ expect(result.current).toEqual(baseMetadata);
253
+
254
+ // Emit event
255
+ act(() => {
256
+ activeDateRangesEmitter.emit("onSetActiveDateRanges", {
257
+ ranges: [
258
+ {
259
+ startId: "2024-02-01",
260
+ endId: "2024-02-17",
261
+ },
262
+ ],
263
+ });
264
+ });
265
+
266
+ expect(result.current).toEqual(baseMetadata);
267
+ expect(result.all).toHaveLength(1);
268
+
269
+ // Emit another event
270
+ act(() => {
271
+ activeDateRangesEmitter.emit("onSetActiveDateRanges", {
272
+ ranges: [
273
+ {
274
+ startId: "2024-02-19",
275
+ endId: "2024-02-23",
276
+ },
277
+ ],
278
+ });
279
+ });
280
+
281
+ expect(result.current).toEqual(baseMetadata);
282
+ expect(result.all).toHaveLength(1);
283
+
284
+ // Emit an incomplete range
285
+ act(() => {
286
+ activeDateRangesEmitter.emit("onSetActiveDateRanges", {
287
+ ranges: [
288
+ {
289
+ startId: "2024-02-01",
290
+ endId: undefined,
291
+ },
292
+ ],
293
+ });
294
+ });
295
+
296
+ expect(result.current).toEqual(baseMetadata);
297
+ expect(result.all).toHaveLength(1);
298
+
299
+ // Check if the metadata is updated when the range is complete
300
+ act(() => {
301
+ activeDateRangesEmitter.emit("onSetActiveDateRanges", {
302
+ ranges: [
303
+ {
304
+ startId: "2024-02-01",
305
+ endId: "2024-02-20",
306
+ },
307
+ ],
308
+ });
309
+ });
310
+ expect(result.current).toEqual({
311
+ ...baseMetadata,
312
+ state: "active",
313
+ isRangeValid: true,
314
+ });
315
+ expect(result.all).toHaveLength(2);
316
+ });
317
+
318
+ it("resets the state once a new range is selected", () => {
319
+ const baseMetadata: CalendarDayMetadata = {
320
+ date: fromDateId("2024-02-01"),
321
+ displayLabel: "01",
322
+ id: "2024-02-01",
323
+ isDifferentMonth: false,
324
+ isDisabled: false,
325
+ isEndOfMonth: false,
326
+ isEndOfRange: false,
327
+ isEndOfWeek: false,
328
+ isRangeValid: false,
329
+ isStartOfMonth: false,
330
+ isStartOfRange: false,
331
+ isStartOfWeek: false,
332
+ isToday: false,
333
+ isWeekend: false,
334
+ state: "idle",
335
+ };
336
+
337
+ // Initial state
338
+ const { result } = renderHook(
339
+ (initial) => useOptimizedDayMetadata(initial),
340
+ {
341
+ initialProps: baseMetadata,
342
+ }
343
+ );
344
+ expect(result.current).toEqual(baseMetadata);
345
+
346
+ // Emit event
347
+ act(() => {
348
+ activeDateRangesEmitter.emit("onSetActiveDateRanges", {
349
+ ranges: [
350
+ {
351
+ startId: "2024-02-01",
352
+ endId: "2024-02-01",
353
+ },
354
+ ],
355
+ });
356
+ });
357
+
358
+ expect(result.current).toEqual({
359
+ ...baseMetadata,
360
+ state: "active",
361
+ isStartOfRange: true,
362
+ isEndOfRange: true,
363
+ isRangeValid: true,
364
+ });
365
+ expect(result.all).toHaveLength(2);
366
+
367
+ // Emit another range
368
+ act(() => {
369
+ activeDateRangesEmitter.emit("onSetActiveDateRanges", {
370
+ ranges: [
371
+ {
372
+ startId: "2024-02-03",
373
+ endId: "2024-02-03",
374
+ },
375
+ ],
376
+ });
377
+ });
378
+
379
+ // Back to the initial state
380
+ expect(result.current).toEqual(baseMetadata);
381
+ expect(result.all).toHaveLength(3);
382
+ });
383
+ });
384
+
385
+ describe("useOptimizedDayMetadata with calendarInstanceId", () => {
386
+ const getBaseMetadata = (date: string): CalendarDayMetadata => ({
387
+ date: fromDateId(date),
388
+ displayLabel: date.split("-")[2],
389
+ id: date,
390
+ isDifferentMonth: false,
391
+ isDisabled: false,
392
+ isEndOfMonth: false,
393
+ isEndOfRange: false,
394
+ isEndOfWeek: false,
395
+ isRangeValid: false,
396
+ isStartOfMonth: false,
397
+ isStartOfRange: false,
398
+ isStartOfWeek: false,
399
+ isToday: false,
400
+ isWeekend: false,
401
+ state: "idle",
402
+ });
403
+
404
+ it("uses the default instance ID when not provided", () => {
405
+ const baseMetadata = getBaseMetadata("2024-02-16");
406
+ const { result } = renderHook(() => useOptimizedDayMetadata(baseMetadata));
407
+
408
+ act(() => {
409
+ activeDateRangesEmitter.emit("onSetActiveDateRanges", {
410
+ ranges: [
411
+ {
412
+ startId: "2024-02-16",
413
+ endId: "2024-02-16",
414
+ },
415
+ ],
416
+ });
417
+ });
418
+
419
+ expect(result.current).toEqual({
420
+ ...baseMetadata,
421
+ state: "active",
422
+ isStartOfRange: true,
423
+ isEndOfRange: true,
424
+ isRangeValid: true,
425
+ });
426
+ });
427
+
428
+ it("responds to events for the correct instance ID", () => {
429
+ const instanceId = "test-calendar-1";
430
+ const baseMetadata = getBaseMetadata("2024-02-16");
431
+ const { result } = renderHook(() =>
432
+ useOptimizedDayMetadata(baseMetadata, instanceId)
433
+ );
434
+
435
+ act(() => {
436
+ activeDateRangesEmitter.emit("onSetActiveDateRanges", {
437
+ instanceId,
438
+ ranges: [
439
+ {
440
+ startId: "2024-02-16",
441
+ endId: "2024-02-16",
442
+ },
443
+ ],
444
+ });
445
+ });
446
+
447
+ expect(result.current).toEqual({
448
+ ...baseMetadata,
449
+ state: "active",
450
+ isStartOfRange: true,
451
+ isEndOfRange: true,
452
+ isRangeValid: true,
453
+ });
454
+ });
455
+
456
+ it("ignores events for different instance IDs", () => {
457
+ const instanceId = "test-calendar-2";
458
+ const baseMetadata = getBaseMetadata("2024-02-16");
459
+ const { result } = renderHook(() =>
460
+ useOptimizedDayMetadata(baseMetadata, instanceId)
461
+ );
462
+
463
+ act(() => {
464
+ activeDateRangesEmitter.emit("onSetActiveDateRanges", {
465
+ instanceId: "different-calendar",
466
+ ranges: [
467
+ {
468
+ startId: "2024-02-16",
469
+ endId: "2024-02-16",
470
+ },
471
+ ],
472
+ });
473
+ });
474
+
475
+ // The metadata should not change
476
+ expect(result.current).toEqual(baseMetadata);
477
+ });
478
+
479
+ it("handles multiple instances correctly", () => {
480
+ const instanceId1 = "test-calendar-3";
481
+ const instanceId2 = "test-calendar-4";
482
+
483
+ const baseMetadata1 = getBaseMetadata("2024-02-16");
484
+ const baseMetadata2 = getBaseMetadata("2024-02-16");
485
+
486
+ const { result: result1 } = renderHook(() =>
487
+ useOptimizedDayMetadata(baseMetadata1, instanceId1)
488
+ );
489
+ const { result: result2 } = renderHook(() =>
490
+ useOptimizedDayMetadata(baseMetadata2, instanceId2)
491
+ );
492
+
493
+ act(() => {
494
+ activeDateRangesEmitter.emit("onSetActiveDateRanges", {
495
+ instanceId: instanceId1,
496
+ ranges: [
497
+ {
498
+ startId: "2024-02-16",
499
+ endId: "2024-02-16",
500
+ },
501
+ ],
502
+ });
503
+ });
504
+
505
+ expect(result1.current).toEqual({
506
+ ...baseMetadata1,
507
+ state: "active",
508
+ isStartOfRange: true,
509
+ isEndOfRange: true,
510
+ isRangeValid: true,
511
+ });
512
+ expect(result2.current).toEqual(baseMetadata2);
513
+
514
+ act(() => {
515
+ activeDateRangesEmitter.emit("onSetActiveDateRanges", {
516
+ instanceId: instanceId2,
517
+ ranges: [
518
+ {
519
+ startId: "2024-02-15",
520
+ endId: "2024-02-17",
521
+ },
522
+ ],
523
+ });
524
+ });
525
+
526
+ expect(result1.current).toEqual({
527
+ ...baseMetadata1,
528
+ state: "active",
529
+ isStartOfRange: true,
530
+ isEndOfRange: true,
531
+ isRangeValid: true,
532
+ });
533
+ expect(result2.current).toEqual({
534
+ ...baseMetadata2,
535
+ state: "active",
536
+ isRangeValid: true,
537
+ });
538
+ });
539
+
540
+ it("resets state for the correct instance when a new range is selected", () => {
541
+ const instanceId = "test-calendar-5";
542
+ const baseMetadata = getBaseMetadata("2024-02-16");
543
+ const { result } = renderHook(() =>
544
+ useOptimizedDayMetadata(baseMetadata, instanceId)
545
+ );
546
+
547
+ act(() => {
548
+ activeDateRangesEmitter.emit("onSetActiveDateRanges", {
549
+ instanceId,
550
+ ranges: [
551
+ {
552
+ startId: "2024-02-16",
553
+ endId: "2024-02-16",
554
+ },
555
+ ],
556
+ });
557
+ });
558
+
559
+ expect(result.current).toEqual({
560
+ ...baseMetadata,
561
+ state: "active",
562
+ isStartOfRange: true,
563
+ isEndOfRange: true,
564
+ isRangeValid: true,
565
+ });
566
+
567
+ act(() => {
568
+ activeDateRangesEmitter.emit("onSetActiveDateRanges", {
569
+ instanceId,
570
+ ranges: [
571
+ {
572
+ startId: "2024-02-18",
573
+ endId: "2024-02-20",
574
+ },
575
+ ],
576
+ });
577
+ });
578
+
579
+ // Should reset to base state
580
+ expect(result.current).toEqual(baseMetadata);
581
+ });
582
+ });
@@ -0,0 +1,93 @@
1
+ import mitt from "mitt";
2
+ import { useEffect, useState } from "react";
3
+
4
+ import {
5
+ getStateFields,
6
+ type CalendarActiveDateRange,
7
+ type CalendarDayMetadata,
8
+ } from "@/hooks/useCalendar";
9
+
10
+ interface OnSetActiveDateRangesPayload {
11
+ instanceId?: string;
12
+ ranges: CalendarActiveDateRange[];
13
+ }
14
+
15
+ /**
16
+ * An event emitter for the active date ranges. This notifies the calendar items
17
+ * when their state changes, allowing just the affected items to re-render.
18
+ *
19
+ * While this is an implementation detail focused on improving performance, it's
20
+ * exported in case you need to build your own calendar. Check the source code
21
+ * for a reference implementation.
22
+ */
23
+ export const activeDateRangesEmitter = mitt<{
24
+ onSetActiveDateRanges: OnSetActiveDateRangesPayload;
25
+ }>();
26
+
27
+ /**
28
+ * The default calendar instance ID. This is used when no instance ID is provided.
29
+ */
30
+ const DEFAULT_CALENDAR_INSTANCE_ID = "legend-calendar-default-instance";
31
+
32
+ /**
33
+ * Returns an optimized metadata for a particular day. This hook listens to the
34
+ * `activeDateRanges` emitter, enabling only the affected calendar items to
35
+ * re-render.
36
+ *
37
+ * While this is an implementation detail focused on improving performance, it's
38
+ * exported in case you need to build your own calendar. Check the source code
39
+ * for a reference implementation.
40
+ */
41
+ export const useOptimizedDayMetadata = (
42
+ baseMetadata: CalendarDayMetadata,
43
+ calendarInstanceId?: string
44
+ ) => {
45
+ const [metadata, setMetadata] = useState(baseMetadata);
46
+
47
+ const safeCalendarInstanceId =
48
+ calendarInstanceId ?? DEFAULT_CALENDAR_INSTANCE_ID;
49
+
50
+ // Ensure the metadata is updated when the base changes.
51
+ useEffect(() => {
52
+ setMetadata(baseMetadata);
53
+ }, [baseMetadata]);
54
+
55
+ useEffect(() => {
56
+ const handler = (payload: OnSetActiveDateRangesPayload) => {
57
+ const { ranges, instanceId = DEFAULT_CALENDAR_INSTANCE_ID } = payload;
58
+ if (instanceId !== safeCalendarInstanceId) {
59
+ // This event is not for this instance, ignore it.
60
+ return;
61
+ }
62
+
63
+ // We're only interested in the active date ranges, no need to worry about
64
+ // disabled states. These are already covered by the base metadata.
65
+ const { isStartOfRange, isEndOfRange, isRangeValid, state } =
66
+ getStateFields({
67
+ id: metadata.id,
68
+ calendarActiveDateRanges: ranges,
69
+ });
70
+
71
+ if (state === "active") {
72
+ setMetadata((prev) => ({
73
+ ...prev,
74
+ isStartOfRange,
75
+ isEndOfRange,
76
+ isRangeValid,
77
+ state,
78
+ }));
79
+ } else {
80
+ // Resets the state when it's no longer active.
81
+ setMetadata(baseMetadata);
82
+ }
83
+ };
84
+
85
+ activeDateRangesEmitter.on("onSetActiveDateRanges", handler);
86
+
87
+ return () => {
88
+ activeDateRangesEmitter.off("onSetActiveDateRanges", handler);
89
+ };
90
+ }, [baseMetadata, safeCalendarInstanceId, metadata]);
91
+
92
+ return metadata;
93
+ };
@@ -0,0 +1,14 @@
1
+ import { useColorScheme } from "react-native";
2
+
3
+ import type { BaseTheme } from "@/helpers/tokens";
4
+ import { darkTheme, lightTheme } from "@/helpers/tokens";
5
+ import { useCalendarTheme } from "@/components/CalendarThemeProvider";
6
+
7
+ export const useTheme = (): BaseTheme => {
8
+ const appearance = useColorScheme();
9
+ const { colorScheme } = useCalendarTheme();
10
+
11
+ const theme = colorScheme ?? appearance;
12
+
13
+ return theme === "dark" ? darkTheme : lightTheme;
14
+ };
package/src/index.ts ADDED
@@ -0,0 +1,24 @@
1
+ export * from "./components";
2
+ export { toDateId, fromDateId } from "./helpers/dates";
3
+
4
+ export {
5
+ type UseCalendarParams,
6
+ type CalendarDayMetadata,
7
+ type CalendarActiveDateRange,
8
+ useCalendar,
9
+ buildCalendar,
10
+ } from "./hooks/useCalendar";
11
+
12
+ export {
13
+ type CalendarMonth,
14
+ type UseCalendarListParams,
15
+ getHeightForMonth,
16
+ useCalendarList,
17
+ } from "./hooks/useCalendarList";
18
+
19
+ export {
20
+ useOptimizedDayMetadata,
21
+ activeDateRangesEmitter,
22
+ } from "./hooks/useOptimizedDayMetadata";
23
+
24
+ export { useDateRange } from "./hooks/useDateRange";