uikit-react-public 0.11.16 → 0.11.24

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 (142) hide show
  1. package/dist/components/Calendar/Calendar.d.ts +3 -0
  2. package/dist/components/Calendar/Calendar.stories.d.ts +42 -0
  3. package/dist/components/Calendar/Calendar.types.d.ts +18 -0
  4. package/dist/components/Calendar/index.d.ts +2 -0
  5. package/dist/components/Calendar/subcomponents/AcademicWeek.d.ts +5 -0
  6. package/dist/components/Calendar/subcomponents/AcademicWeeks.d.ts +7 -0
  7. package/dist/components/Calendar/subcomponents/ColumnHeading.d.ts +7 -0
  8. package/dist/components/Calendar/subcomponents/Controls.d.ts +6 -0
  9. package/dist/components/Calendar/subcomponents/Day.d.ts +12 -0
  10. package/dist/components/{Datepicker/subcomponents/Day → Calendar/subcomponents}/Day.stories.d.ts +9 -1
  11. package/dist/components/Calendar/subcomponents/EventDot.d.ts +6 -0
  12. package/dist/components/Calendar/subcomponents/Grid.d.ts +11 -0
  13. package/dist/components/Calendar/subcomponents/index.d.ts +7 -0
  14. package/dist/components/Calendar/utils/getAcademicWeekNumbers/getAcademicWeekNumbers.d.ts +24 -0
  15. package/dist/components/Calendar/utils/index.d.ts +4 -0
  16. package/dist/components/Calendar/utils/normaliseMonth/normaliseMonth.d.ts +9 -0
  17. package/dist/components/Calendar/utils/normaliseMonth/normaliseMonth.test.d.ts +1 -0
  18. package/dist/components/Calendar/utils/parseDateFromString/parseDateFromString.d.ts +9 -0
  19. package/dist/components/Calendar/utils/parseDateFromString/parseDateFromString.test.d.ts +1 -0
  20. package/dist/components/Datepicker/Datepicker.d.ts +3 -12
  21. package/dist/components/Datepicker/Datepicker.stories.d.ts +16 -3
  22. package/dist/components/Datepicker/Datepicker.types.d.ts +23 -0
  23. package/dist/components/Datepicker/index.d.ts +1 -1
  24. package/dist/components/Datepicker/subcomponents/CustomDatepicker.d.ts +17 -0
  25. package/dist/components/Datepicker/subcomponents/DatepickerInput.d.ts +10 -0
  26. package/dist/components/Datepicker/subcomponents/NativeDatepicker.d.ts +6 -0
  27. package/dist/components/Datepicker/subcomponents/Panel.d.ts +6 -0
  28. package/dist/components/Datepicker/subcomponents/VisibleField.d.ts +12 -0
  29. package/dist/components/Datepicker/subcomponents/index.d.ts +5 -7
  30. package/dist/components/Datepicker/utils/dateToLocaleISOString/dateToLocaleISOString.d.ts +17 -0
  31. package/dist/components/Datepicker/utils/dateToLocaleISOString/dateToLocaleISOString.test.d.ts +1 -0
  32. package/dist/components/Datepicker/utils/index.d.ts +2 -2
  33. package/dist/components/Datepicker/utils/parseInputValue/parseInputValue.d.ts +11 -0
  34. package/dist/components/Datepicker/utils/parseInputValue/parseInputValue.test.d.ts +1 -0
  35. package/dist/components/Footer/Footer.d.ts +1 -1
  36. package/dist/components/Header/Header.d.ts +5 -4
  37. package/dist/components/Header/index.d.ts +1 -1
  38. package/dist/components/Menu/Menu.d.ts +2 -1
  39. package/dist/components/Menu/MenuContent.d.ts +1 -0
  40. package/dist/components/Select/Select.stories.d.ts +1 -1
  41. package/dist/components/Select/Select.types.d.ts +10 -50
  42. package/dist/components/Select/index.d.ts +1 -1
  43. package/dist/components/Select/subcomponents/CustomSelect.d.ts +2 -1
  44. package/dist/components/index.d.ts +2 -0
  45. package/dist/index.js +3332 -3063
  46. package/lib/components/Calendar/Calendar.stories.tsx +209 -0
  47. package/lib/components/Calendar/Calendar.tsx +121 -0
  48. package/lib/components/Calendar/Calendar.types.ts +21 -0
  49. package/lib/components/Calendar/__tests__/Calendar.test.tsx +71 -0
  50. package/lib/components/Calendar/__tests__/__snapshots__/Calendar.test.tsx.snap +1218 -0
  51. package/lib/components/Calendar/index.ts +6 -0
  52. package/lib/components/Calendar/subcomponents/AcademicWeek.tsx +36 -0
  53. package/lib/components/Calendar/subcomponents/AcademicWeeks.tsx +46 -0
  54. package/lib/components/Calendar/subcomponents/ColumnHeading.tsx +40 -0
  55. package/lib/components/{Datepicker/subcomponents/MonthSelector/MonthSelector.tsx → Calendar/subcomponents/Controls.tsx} +17 -17
  56. package/lib/components/{Datepicker/subcomponents/Day → Calendar/subcomponents}/Day.stories.tsx +30 -7
  57. package/lib/components/Calendar/subcomponents/Day.tsx +130 -0
  58. package/lib/components/Calendar/subcomponents/EventDot.tsx +40 -0
  59. package/lib/components/Calendar/subcomponents/Grid.tsx +117 -0
  60. package/lib/components/Calendar/subcomponents/index.ts +7 -0
  61. package/lib/components/Calendar/utils/getAcademicWeekNumbers/getAcademicWeekNumbers.test.ts +104 -0
  62. package/lib/components/Calendar/utils/getAcademicWeekNumbers/getAcademicWeekNumbers.ts +85 -0
  63. package/lib/components/{Datepicker → Calendar}/utils/getDatesForCalendarGrid/getDatesForCalendarGrid.test.ts +29 -65
  64. package/lib/components/{Datepicker → Calendar}/utils/getDatesForCalendarGrid/getDatesForCalendarGrid.ts +11 -43
  65. package/lib/components/Calendar/utils/index.ts +4 -0
  66. package/lib/components/Calendar/utils/normaliseMonth/normaliseMonth.test.ts +40 -0
  67. package/lib/components/Calendar/utils/normaliseMonth/normaliseMonth.ts +16 -0
  68. package/lib/components/Calendar/utils/parseDateFromString/parseDateFromString.test.ts +15 -0
  69. package/lib/components/Calendar/utils/parseDateFromString/parseDateFromString.ts +19 -0
  70. package/lib/components/Datepicker/Datepicker.stories.tsx +220 -23
  71. package/lib/components/Datepicker/Datepicker.tsx +34 -137
  72. package/lib/components/Datepicker/Datepicker.types.ts +38 -0
  73. package/lib/components/Datepicker/__tests__/Datepicker.test.tsx +53 -112
  74. package/lib/components/Datepicker/__tests__/__snapshots__/Datepicker.test.tsx.snap +92 -638
  75. package/lib/components/Datepicker/index.ts +1 -1
  76. package/lib/components/Datepicker/subcomponents/CustomDatepicker.tsx +209 -0
  77. package/lib/components/Datepicker/subcomponents/DatepickerInput.tsx +74 -0
  78. package/lib/components/Datepicker/subcomponents/NativeDatepicker.tsx +70 -0
  79. package/lib/components/Datepicker/subcomponents/Panel.tsx +32 -0
  80. package/lib/components/Datepicker/subcomponents/VisibleField.tsx +104 -0
  81. package/lib/components/Datepicker/subcomponents/index.ts +5 -7
  82. package/lib/components/Datepicker/utils/dateToLocaleISOString/dateToLocaleISOString.test.ts +32 -0
  83. package/lib/components/Datepicker/utils/dateToLocaleISOString/dateToLocaleISOString.ts +23 -0
  84. package/lib/components/Datepicker/utils/index.ts +2 -2
  85. package/lib/components/Datepicker/utils/parseInputValue/parseInputValue.test.ts +110 -0
  86. package/lib/components/Datepicker/utils/parseInputValue/parseInputValue.ts +57 -0
  87. package/lib/components/Footer/Footer.tsx +3 -3
  88. package/lib/components/Footer/__tests__/__snapshots__/Footer.test.tsx.snap +6 -6
  89. package/lib/components/Header/Header.tsx +32 -33
  90. package/lib/components/Header/HeaderMenu.tsx +9 -2
  91. package/lib/components/Header/__tests__/__snapshots__/Header.test.tsx.snap +40 -48
  92. package/lib/components/Header/index.ts +5 -1
  93. package/lib/components/Menu/Menu.tsx +3 -0
  94. package/lib/components/Menu/MenuContent.tsx +4 -1
  95. package/lib/components/Select/Select.stories.tsx +38 -39
  96. package/lib/components/Select/Select.tsx +4 -18
  97. package/lib/components/Select/Select.types.ts +30 -69
  98. package/lib/components/Select/__tests__/Select.test.tsx +6 -6
  99. package/lib/components/Select/__tests__/__snapshots__/Select.test.tsx.snap +1 -1
  100. package/lib/components/Select/index.ts +1 -1
  101. package/lib/components/Select/subcomponents/CustomSelect.tsx +22 -12
  102. package/lib/components/Select/subcomponents/NativeSelect.tsx +7 -3
  103. package/lib/components/Select/subcomponents/Panel.tsx +4 -4
  104. package/lib/components/Select/subcomponents/VisibleField.tsx +1 -1
  105. package/lib/components/index.ts +3 -0
  106. package/package.json +4 -4
  107. package/LICENSE +0 -9
  108. package/dist/components/Datepicker/subcomponents/CalendarGrid/CalendarGrid.d.ts +0 -6
  109. package/dist/components/Datepicker/subcomponents/CalendarGrid/index.d.ts +0 -1
  110. package/dist/components/Datepicker/subcomponents/CalendarMenu/CalendarMenu.d.ts +0 -8
  111. package/dist/components/Datepicker/subcomponents/CalendarMenu/index.d.ts +0 -1
  112. package/dist/components/Datepicker/subcomponents/ColumnHeadings/ColumnHeadings.d.ts +0 -2
  113. package/dist/components/Datepicker/subcomponents/ColumnHeadings/index.d.ts +0 -1
  114. package/dist/components/Datepicker/subcomponents/DateField/DateField.d.ts +0 -7
  115. package/dist/components/Datepicker/subcomponents/DateField/index.d.ts +0 -1
  116. package/dist/components/Datepicker/subcomponents/Day/Day.d.ts +0 -10
  117. package/dist/components/Datepicker/subcomponents/Day/index.d.ts +0 -1
  118. package/dist/components/Datepicker/subcomponents/MonthSelector/MonthSelector.d.ts +0 -6
  119. package/dist/components/Datepicker/subcomponents/MonthSelector/index.d.ts +0 -1
  120. package/dist/components/Datepicker/subcomponents/Native/Native.d.ts +0 -9
  121. package/dist/components/Datepicker/subcomponents/Native/index.d.ts +0 -1
  122. package/dist/components/Datepicker/utils/parseDateForDateField/parseDateForDateField.d.ts +0 -20
  123. package/lib/components/Datepicker/subcomponents/CalendarGrid/CalendarGrid.tsx +0 -59
  124. package/lib/components/Datepicker/subcomponents/CalendarGrid/index.ts +0 -1
  125. package/lib/components/Datepicker/subcomponents/CalendarMenu/CalendarMenu.tsx +0 -64
  126. package/lib/components/Datepicker/subcomponents/CalendarMenu/index.ts +0 -1
  127. package/lib/components/Datepicker/subcomponents/ColumnHeadings/ColumnHeadings.tsx +0 -35
  128. package/lib/components/Datepicker/subcomponents/ColumnHeadings/index.ts +0 -1
  129. package/lib/components/Datepicker/subcomponents/DateField/DateField.tsx +0 -155
  130. package/lib/components/Datepicker/subcomponents/DateField/__tests__/DateField.test.tsx +0 -191
  131. package/lib/components/Datepicker/subcomponents/DateField/index.ts +0 -1
  132. package/lib/components/Datepicker/subcomponents/Day/Day.tsx +0 -94
  133. package/lib/components/Datepicker/subcomponents/Day/index.ts +0 -1
  134. package/lib/components/Datepicker/subcomponents/MonthSelector/index.ts +0 -1
  135. package/lib/components/Datepicker/subcomponents/Native/Native.tsx +0 -59
  136. package/lib/components/Datepicker/subcomponents/Native/index.ts +0 -1
  137. package/lib/components/Datepicker/utils/parseDateForDateField/parseDateForDateField.test.ts +0 -41
  138. package/lib/components/Datepicker/utils/parseDateForDateField/parseDateForDateField.ts +0 -48
  139. /package/dist/components/{Datepicker/subcomponents/DateField/__tests__/DateField.test.d.ts → Calendar/__tests__/Calendar.test.d.ts} +0 -0
  140. /package/dist/components/{Datepicker/utils/getDatesForCalendarGrid/getDatesForCalendarGrid.test.d.ts → Calendar/utils/getAcademicWeekNumbers/getAcademicWeekNumbers.test.d.ts} +0 -0
  141. /package/dist/components/{Datepicker → Calendar}/utils/getDatesForCalendarGrid/getDatesForCalendarGrid.d.ts +0 -0
  142. /package/dist/components/{Datepicker/utils/parseDateForDateField/parseDateForDateField.test.d.ts → Calendar/utils/getDatesForCalendarGrid/getDatesForCalendarGrid.test.d.ts} +0 -0
@@ -0,0 +1,85 @@
1
+ import type { AcademicWeek } from '../../';
2
+
3
+ // The 'calendar period' is defined here as all the dates the calendar displays,
4
+ // which may include some dates in the previous month and some dates in the next month.
5
+ // This is because we display complete weeks in the calendar.
6
+
7
+ /**
8
+ * Returns the Monday for the week of the given date.
9
+ * For example:
10
+ * - If the input date is a Wednesday, it returns the Monday of that week.
11
+ * - If the input date is already a Monday, it returns that date.
12
+ * @param {Date} d - The input date.
13
+ * @returns {Date} The Monday of the week of the input date.
14
+ */
15
+ export const getMonday = (d: Date) => {
16
+ const date = new Date(d);
17
+ const day = date.getDay();
18
+ const delta = (day + 6) % 7;
19
+ date.setDate(date.getDate() - delta);
20
+ date.setHours(0, 0, 0, 0);
21
+ return date;
22
+ };
23
+
24
+ /**
25
+ * Calculates the academic week numbers for each week displayed in a calendar month.
26
+ * Used by `<AcademicWeeks />` component in the Datepicker.
27
+ * It determines the Mondays of each week shown in the calendar for the `targetDate`'s month.
28
+ * Then, it maps these Mondays to the corresponding academic week number from the `academicWeeks` array.
29
+ * If an academic week is not found for a given Monday, `undefined` is used, in place in the returned array.
30
+ *
31
+ * @param {AcademicWeek[]} academicWeeks - An array of academic week objects, each with a `start` date and `number`.
32
+ * @param {Date} targetDate - The date used to generate the calendar display.
33
+ * @returns {(number | undefined)[]} An array of academic week numbers or `undefined` for each week in the calendar display.
34
+ * Returns an empty array if `academicWeeks` is empty or `targetDate` is invalid, with console warnings, in the interest of safe usage.
35
+ */
36
+ const getAcademicWeekNumbers = (
37
+ academicWeeks: AcademicWeek[],
38
+ targetDate: Date
39
+ ) => {
40
+ if (!academicWeeks || academicWeeks.length === 0) {
41
+ console.warn('Datepicker: No academic weeks provided');
42
+ return [];
43
+ }
44
+ if (
45
+ !targetDate ||
46
+ !(targetDate instanceof Date) ||
47
+ Number.isNaN(targetDate.getTime())
48
+ ) {
49
+ console.warn('Datepicker: Invalid target date provided');
50
+ return [];
51
+ }
52
+
53
+ // Normalise to the first Monday of calendar period
54
+ const targetYear = targetDate.getFullYear();
55
+ const targetMonth = targetDate.getMonth();
56
+ const firstOfMonth = new Date(targetYear, targetMonth, 1);
57
+ const firstMonday = getMonday(firstOfMonth);
58
+
59
+ const calendarWeeks: Date[] = [];
60
+ calendarWeeks.push(firstMonday);
61
+
62
+ // Increment to the next Monday of the calendar period
63
+ const nextMonday = new Date(firstMonday);
64
+ nextMonday.setDate(firstMonday.getDate() + 7);
65
+
66
+ // Then keep incrementing until the end of the calendar period
67
+ while (nextMonday.getMonth() === targetMonth) {
68
+ calendarWeeks.push(new Date(nextMonday));
69
+ nextMonday.setDate(nextMonday.getDate() + 7);
70
+ }
71
+
72
+ // We need an array of actual week numbers to pass to the UI
73
+ const weekNumbers: (number | undefined)[] = calendarWeeks.map((date) => {
74
+ // `Array.find()` will return `undefined` if no match is found.
75
+ // `undefined` is used by `<AcademicWeek />` for row placement.
76
+ const matchedWeek = academicWeeks.find(
77
+ (week) => new Date(week.start).toDateString() === date.toDateString()
78
+ );
79
+ return matchedWeek?.number;
80
+ });
81
+
82
+ return weekNumbers;
83
+ };
84
+
85
+ export default getAcademicWeekNumbers;
@@ -1,78 +1,51 @@
1
1
  import { describe, expect, test } from 'vitest';
2
2
  import getDatesForCalendarGrid from './getDatesForCalendarGrid';
3
3
 
4
+ // Note by Alex HF (2025-07-04): Some of these tests seem a bit excessive.
5
+ // I pasted them in from my orginal <Datepicker> work.
6
+
4
7
  describe('getDatesForCalendarGrid', () => {
5
8
  test('Returns an array of Date objects', () => {
6
9
  const result = getDatesForCalendarGrid(new Date());
7
- expect(
8
- result.every((date) => date instanceof Date)
9
- ).toBe(true);
10
+ expect(result.every((date) => date instanceof Date)).toBe(true);
10
11
  });
11
12
 
12
13
  test('Throws an error if no date is provided', () => {
13
14
  // @ts-expect-error - Expected behaviour is an error to be thrown
14
- expect(() => getDatesForCalendarGrid()).toThrowError(
15
+ expect(() => getDatesForCalendarGrid()).toThrowError('No date provided');
16
+ // @ts-expect-error - Expected behaviour is an error to be thrown
17
+ expect(() => getDatesForCalendarGrid(null)).toThrowError(
15
18
  'No date provided'
16
19
  );
17
- expect(() =>
18
- // @ts-expect-error - Expected behaviour is an error to be thrown
19
- getDatesForCalendarGrid(null)
20
- ).toThrowError('No date provided');
21
- expect(() =>
22
- // @ts-expect-error - Expected behaviour is an error to be thrown
23
- getDatesForCalendarGrid(undefined)
24
- ).toThrowError('No date provided');
25
20
  // @ts-expect-error - Expected behaviour is an error to be thrown
26
- expect(() => getDatesForCalendarGrid('')).toThrowError(
21
+ expect(() => getDatesForCalendarGrid(undefined)).toThrowError(
27
22
  'No date provided'
28
23
  );
29
24
  // @ts-expect-error - Expected behaviour is an error to be thrown
30
- expect(() => getDatesForCalendarGrid(0)).toThrowError(
25
+ expect(() => getDatesForCalendarGrid('')).toThrowError('No date provided');
26
+ // @ts-expect-error - Expected behaviour is an error to be thrown
27
+ expect(() => getDatesForCalendarGrid(0)).toThrowError('No date provided');
28
+ // @ts-expect-error - Expected behaviour is an error to be thrown
29
+ expect(() => getDatesForCalendarGrid('1st of March')).toThrowError(
31
30
  'No date provided'
32
31
  );
33
- expect(() =>
34
- // @ts-expect-error - Expected behaviour is an error to be thrown
35
- getDatesForCalendarGrid('1st of March')
36
- ).toThrowError('No date provided');
37
32
  });
38
33
 
39
34
  test('Total dates returned are always divisible by 7', () => {
40
- expect(
41
- getDatesForCalendarGrid(new Date(2025, 1, 1)).length %
42
- 7
43
- ).toBe(0);
44
- expect(
45
- getDatesForCalendarGrid(new Date(2025, 2, 5)).length %
46
- 7
47
- ).toBe(0);
48
- expect(
49
- getDatesForCalendarGrid(new Date(2025, 3, 15))
50
- .length % 7
51
- ).toBe(0);
52
- expect(
53
- getDatesForCalendarGrid(new Date(2025, 4, 20))
54
- .length % 7
55
- ).toBe(0);
56
- expect(
57
- getDatesForCalendarGrid(new Date(2025, 5, 25))
58
- .length % 7
59
- ).toBe(0);
60
- expect(
61
- getDatesForCalendarGrid(new Date(2025, 6, 30))
62
- .length % 7
63
- ).toBe(0);
64
- expect(
65
- getDatesForCalendarGrid(new Date(2025, 12, 0))
66
- .length % 7
67
- ).toBe(0);
35
+ expect(getDatesForCalendarGrid(new Date(2025, 1, 1)).length % 7).toBe(0);
36
+ expect(getDatesForCalendarGrid(new Date(2025, 2, 5)).length % 7).toBe(0);
37
+ expect(getDatesForCalendarGrid(new Date(2025, 3, 15)).length % 7).toBe(0);
38
+ expect(getDatesForCalendarGrid(new Date(2025, 4, 20)).length % 7).toBe(0);
39
+ expect(getDatesForCalendarGrid(new Date(2025, 5, 25)).length % 7).toBe(0);
40
+ expect(getDatesForCalendarGrid(new Date(2025, 6, 30)).length % 7).toBe(0);
41
+ expect(getDatesForCalendarGrid(new Date(2025, 12, 0)).length % 7).toBe(0);
68
42
  });
69
43
 
70
- test('If date is in Jan 2025, returns all dates in January 2024', () => {
44
+ test('If date is in Jan 2025, returns all dates in January 2025', () => {
71
45
  const date = new Date(2025, 0, 1);
72
46
  const result = getDatesForCalendarGrid(date);
73
47
  const janDates = [];
74
- for (let i = 1; i <= 31; i++)
75
- janDates.push(new Date(2025, 0, i));
48
+ for (let i = 1; i <= 31; i++) janDates.push(new Date(2025, 0, i));
76
49
  janDates.forEach((dateInJan) => {
77
50
  expect(result).toContainEqual(dateInJan);
78
51
  });
@@ -88,12 +61,8 @@ describe('getDatesForCalendarGrid', () => {
88
61
  test('If date is in Jan 2025, returns 1st & 2nd Feb 2025 at the end', () => {
89
62
  const date = new Date(2025, 0, 1);
90
63
  const result = getDatesForCalendarGrid(date);
91
- expect(result[result.length - 2]).toEqual(
92
- new Date(2025, 1, 1)
93
- );
94
- expect(result[result.length - 1]).toEqual(
95
- new Date(2025, 1, 2)
96
- );
64
+ expect(result[result.length - 2]).toEqual(new Date(2025, 1, 1));
65
+ expect(result[result.length - 1]).toEqual(new Date(2025, 1, 2));
97
66
  });
98
67
 
99
68
  test('If month starts on a Sunday, the total dates returned are divisible by 7', () => {
@@ -101,12 +70,12 @@ describe('getDatesForCalendarGrid', () => {
101
70
  const result = getDatesForCalendarGrid(date);
102
71
  expect(result.length % 7).toBe(0);
103
72
  });
73
+
104
74
  test('handles February in leap year correctly', () => {
105
75
  const leapYear = new Date(2024, 1, 1); // February 2024
106
76
  const result = getDatesForCalendarGrid(leapYear);
107
77
  const febDates = [];
108
- for (let i = 1; i <= 29; i++)
109
- febDates.push(new Date(2024, 1, i));
78
+ for (let i = 1; i <= 29; i++) febDates.push(new Date(2024, 1, i));
110
79
  febDates.forEach((date) => {
111
80
  expect(result).toContainEqual(date);
112
81
  });
@@ -116,8 +85,7 @@ describe('getDatesForCalendarGrid', () => {
116
85
  const nonLeapYear = new Date(2025, 1, 1); // February 2025
117
86
  const result = getDatesForCalendarGrid(nonLeapYear);
118
87
  const febDates = [];
119
- for (let i = 1; i <= 28; i++)
120
- febDates.push(new Date(2025, 1, i));
88
+ for (let i = 1; i <= 28; i++) febDates.push(new Date(2025, 1, i));
121
89
  febDates.forEach((date) => {
122
90
  expect(result).toContainEqual(date);
123
91
  });
@@ -127,18 +95,14 @@ describe('getDatesForCalendarGrid', () => {
127
95
  const date = new Date(2025, 3, 1); // April 2025
128
96
  const result = getDatesForCalendarGrid(date);
129
97
  for (let i = 1; i < result.length; i++) {
130
- expect(result[i].getTime()).toBeGreaterThan(
131
- result[i - 1].getTime()
132
- );
98
+ expect(result[i].getTime()).toBeGreaterThan(result[i - 1].getTime());
133
99
  }
134
100
  });
135
101
 
136
102
  test('correctly handles months with 31 days', () => {
137
103
  const date = new Date(2025, 0, 1); // January 2025
138
104
  const result = getDatesForCalendarGrid(date);
139
- expect(
140
- result.filter((d) => d.getMonth() === 0).length
141
- ).toBe(31);
105
+ expect(result.filter((d) => d.getMonth() === 0).length).toBe(31);
142
106
  });
143
107
 
144
108
  test('week always starts with Monday', () => {
@@ -17,24 +17,15 @@
17
17
  * ```
18
18
  */
19
19
  const getDatesForCalendarGrid = (date: Date): Date[] => {
20
- if (!date || !(date instanceof Date))
21
- throw new Error('No date provided');
20
+ if (!date || !(date instanceof Date)) throw new Error('No date provided');
22
21
 
23
22
  const month = date.getMonth();
24
- const daysInMonth = new Date(
25
- date.getFullYear(),
26
- month + 1,
27
- 0
28
- ).getDate();
23
+ const daysInMonth = new Date(date.getFullYear(), month + 1, 0).getDate();
29
24
 
30
25
  // Get all days in current month
31
26
  const dates: Date[] = [];
32
27
  for (let day = 1; day <= daysInMonth; day++) {
33
- const newDate = new Date(
34
- date.getFullYear(),
35
- month,
36
- day
37
- );
28
+ const newDate = new Date(date.getFullYear(), month, day);
38
29
  if (newDate.getMonth() !== month) break;
39
30
  dates.push(newDate);
40
31
  }
@@ -43,28 +34,14 @@ const getDatesForCalendarGrid = (date: Date): Date[] => {
43
34
  // Sunday (0) should become 6
44
35
  // Monday (1) should become 0
45
36
  // Tuesday (2) should become 1, etc.
46
- const adjustDay = (day: number): number =>
47
- day === 0 ? 6 : day - 1;
37
+ const adjustDay = (day: number): number => (day === 0 ? 6 : day - 1);
48
38
 
49
39
  // Calculate previous month's "grey days"
50
40
  const prevMonthGreyDays = [];
51
- const prevMonth = new Date(
52
- date.getFullYear(),
53
- month - 1,
54
- 1
55
- );
56
- const firstDayOfMonth = new Date(
57
- date.getFullYear(),
58
- month,
59
- 1
60
- ).getDay();
61
- const numberOfDaysFromPrevMonth =
62
- adjustDay(firstDayOfMonth);
63
- const totalDaysInPrevMonth = new Date(
64
- date.getFullYear(),
65
- month,
66
- 0
67
- ).getDate();
41
+ const prevMonth = new Date(date.getFullYear(), month - 1, 1);
42
+ const firstDayOfMonth = new Date(date.getFullYear(), month, 1).getDay();
43
+ const numberOfDaysFromPrevMonth = adjustDay(firstDayOfMonth);
44
+ const totalDaysInPrevMonth = new Date(date.getFullYear(), month, 0).getDate();
68
45
 
69
46
  for (let i = numberOfDaysFromPrevMonth; i > 0; i--) {
70
47
  prevMonthGreyDays.push(
@@ -78,26 +55,17 @@ const getDatesForCalendarGrid = (date: Date): Date[] => {
78
55
 
79
56
  // Calculate next month's "grey days"
80
57
  const nextMonthGreyDays = [];
81
- const nextMonth = new Date(
82
- date.getFullYear(),
83
- month + 1,
84
- 1
85
- );
58
+ const nextMonth = new Date(date.getFullYear(), month + 1, 1);
86
59
  const lastDayOfMonth = new Date(
87
60
  date.getFullYear(),
88
61
  month,
89
62
  daysInMonth
90
63
  ).getDay();
91
- const numberOfDaysFromNextMonth =
92
- 6 - adjustDay(lastDayOfMonth);
64
+ const numberOfDaysFromNextMonth = 6 - adjustDay(lastDayOfMonth);
93
65
 
94
66
  for (let i = 1; i <= numberOfDaysFromNextMonth; i++) {
95
67
  nextMonthGreyDays.push(
96
- new Date(
97
- nextMonth.getFullYear(),
98
- nextMonth.getMonth(),
99
- i
100
- )
68
+ new Date(nextMonth.getFullYear(), nextMonth.getMonth(), i)
101
69
  );
102
70
  }
103
71
 
@@ -0,0 +1,4 @@
1
+ export { default as normaliseMonth } from './normaliseMonth/normaliseMonth';
2
+ export { default as parseDateFromString } from './parseDateFromString/parseDateFromString';
3
+ export { default as getAcademicWeekNumbers } from './getAcademicWeekNumbers/getAcademicWeekNumbers';
4
+ export { default as getDatesForCalendarGrid } from './getDatesForCalendarGrid/getDatesForCalendarGrid';
@@ -0,0 +1,40 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import normaliseMonth from './normaliseMonth';
3
+
4
+ describe('Calendar: normaliseMonth', () => {
5
+ test('Should return null if given undefined', () => {
6
+ expect(normaliseMonth(undefined)).toBeNull();
7
+ });
8
+
9
+ test('Should return null if given null', () => {
10
+ expect(normaliseMonth(null)).toBeNull();
11
+ });
12
+
13
+ test('Returns a Date object set to the first of the month', () => {
14
+ const date = new Date('2025-03-10');
15
+ const normalisedDate = normaliseMonth(date);
16
+ expect(normalisedDate).toBeInstanceOf(Date);
17
+ expect(normalisedDate?.getFullYear()).toBe(2025);
18
+ expect(normalisedDate?.getMonth()).toBe(2); // March (0-indexed)
19
+ expect(normalisedDate?.getDate()).toBe(1);
20
+ });
21
+
22
+ test('Works for a date within daylight saving time', () => {
23
+ const date = new Date('2025-06-15');
24
+ const normalisedDate = normaliseMonth(date);
25
+ expect(normalisedDate).toBeInstanceOf(Date);
26
+ expect(normalisedDate?.getFullYear()).toBe(2025);
27
+ expect(normalisedDate?.getMonth()).toBe(5); // June (0-indexed)
28
+ expect(normalisedDate?.getDate()).toBe(1);
29
+ });
30
+
31
+ test('Returned date is normalised for midnight', () => {
32
+ const date = new Date('2025-01-01T12:34:56');
33
+ const normalisedDate = normaliseMonth(date);
34
+ expect(normalisedDate).toBeInstanceOf(Date);
35
+ expect(normalisedDate?.getFullYear()).toBe(2025);
36
+ expect(normalisedDate?.getMonth()).toBe(0); // January (0-indexed)
37
+ expect(normalisedDate?.getDate()).toBe(1);
38
+ expect(normalisedDate?.getHours()).toBe(0);
39
+ });
40
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Normalises a given date to the first day of its month (with the time set to midnight).
3
+ * If the input date is undefined, it returns null.
4
+ * Used in <Calendar> to track the displayed month.
5
+ * @param date The date to normalise.
6
+ * @returns A new Date object set to the first day of the month with the time at midnight, or null if the input was null or undefined.
7
+ */
8
+ const normaliseMonth = (date: Date | null | undefined) => {
9
+ if (!date) return null;
10
+ const newDate = new Date(date);
11
+ newDate.setDate(1);
12
+ newDate.setHours(0, 0, 0, 0);
13
+ return newDate;
14
+ };
15
+
16
+ export default normaliseMonth;
@@ -0,0 +1,15 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import parseDateFromString from './parseDateFromString';
3
+
4
+ describe('Calendar: parseDateFromString', () => {
5
+ test('Should return null for an invalid date string', () => {
6
+ expect(parseDateFromString('invalid-date')).toBeNull();
7
+ expect(parseDateFromString('')).toBeNull();
8
+ expect(parseDateFromString(' ')).toBeNull();
9
+ });
10
+
11
+ test('Should return a valid date for a valid date string', () => {
12
+ expect(parseDateFromString('2023-03-15')).toEqual(new Date('2023-03-15'));
13
+ expect(parseDateFromString('2025-06-30')).toEqual(new Date('2025-06-30'));
14
+ });
15
+ });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Parses a date string into a Date object.
3
+ * Returns null if the input is invalid.
4
+ *
5
+ * @param dateString - The date string to parse (can be in ISO format or UNIX timestamp).
6
+ * @returns A Date object, or null if invalid.
7
+ */
8
+ const parseDateFromString = (dateString: string): Date | null => {
9
+ const date = new Date(dateString);
10
+ if (isNaN(date.getTime())) {
11
+ console.warn(
12
+ `Calendar: Invalid date string provided: "${dateString}". Expected format is ISO 8601 (YYYY-MM-DD)`
13
+ );
14
+ return null;
15
+ }
16
+ return date;
17
+ };
18
+
19
+ export default parseDateFromString;