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.
- package/dist/components/Calendar/Calendar.d.ts +3 -0
- package/dist/components/Calendar/Calendar.stories.d.ts +42 -0
- package/dist/components/Calendar/Calendar.types.d.ts +18 -0
- package/dist/components/Calendar/index.d.ts +2 -0
- package/dist/components/Calendar/subcomponents/AcademicWeek.d.ts +5 -0
- package/dist/components/Calendar/subcomponents/AcademicWeeks.d.ts +7 -0
- package/dist/components/Calendar/subcomponents/ColumnHeading.d.ts +7 -0
- package/dist/components/Calendar/subcomponents/Controls.d.ts +6 -0
- package/dist/components/Calendar/subcomponents/Day.d.ts +12 -0
- package/dist/components/{Datepicker/subcomponents/Day → Calendar/subcomponents}/Day.stories.d.ts +9 -1
- package/dist/components/Calendar/subcomponents/EventDot.d.ts +6 -0
- package/dist/components/Calendar/subcomponents/Grid.d.ts +11 -0
- package/dist/components/Calendar/subcomponents/index.d.ts +7 -0
- package/dist/components/Calendar/utils/getAcademicWeekNumbers/getAcademicWeekNumbers.d.ts +24 -0
- package/dist/components/Calendar/utils/index.d.ts +4 -0
- package/dist/components/Calendar/utils/normaliseMonth/normaliseMonth.d.ts +9 -0
- package/dist/components/Calendar/utils/normaliseMonth/normaliseMonth.test.d.ts +1 -0
- package/dist/components/Calendar/utils/parseDateFromString/parseDateFromString.d.ts +9 -0
- package/dist/components/Calendar/utils/parseDateFromString/parseDateFromString.test.d.ts +1 -0
- package/dist/components/Datepicker/Datepicker.d.ts +3 -12
- package/dist/components/Datepicker/Datepicker.stories.d.ts +16 -3
- package/dist/components/Datepicker/Datepicker.types.d.ts +23 -0
- package/dist/components/Datepicker/index.d.ts +1 -1
- package/dist/components/Datepicker/subcomponents/CustomDatepicker.d.ts +17 -0
- package/dist/components/Datepicker/subcomponents/DatepickerInput.d.ts +10 -0
- package/dist/components/Datepicker/subcomponents/NativeDatepicker.d.ts +6 -0
- package/dist/components/Datepicker/subcomponents/Panel.d.ts +6 -0
- package/dist/components/Datepicker/subcomponents/VisibleField.d.ts +12 -0
- package/dist/components/Datepicker/subcomponents/index.d.ts +5 -7
- package/dist/components/Datepicker/utils/dateToLocaleISOString/dateToLocaleISOString.d.ts +17 -0
- package/dist/components/Datepicker/utils/dateToLocaleISOString/dateToLocaleISOString.test.d.ts +1 -0
- package/dist/components/Datepicker/utils/index.d.ts +2 -2
- package/dist/components/Datepicker/utils/parseInputValue/parseInputValue.d.ts +11 -0
- package/dist/components/Datepicker/utils/parseInputValue/parseInputValue.test.d.ts +1 -0
- package/dist/components/Footer/Footer.d.ts +1 -1
- package/dist/components/Header/Header.d.ts +5 -4
- package/dist/components/Header/index.d.ts +1 -1
- package/dist/components/Menu/Menu.d.ts +2 -1
- package/dist/components/Menu/MenuContent.d.ts +1 -0
- package/dist/components/Select/Select.stories.d.ts +1 -1
- package/dist/components/Select/Select.types.d.ts +10 -50
- package/dist/components/Select/index.d.ts +1 -1
- package/dist/components/Select/subcomponents/CustomSelect.d.ts +2 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/index.js +3332 -3063
- package/lib/components/Calendar/Calendar.stories.tsx +209 -0
- package/lib/components/Calendar/Calendar.tsx +121 -0
- package/lib/components/Calendar/Calendar.types.ts +21 -0
- package/lib/components/Calendar/__tests__/Calendar.test.tsx +71 -0
- package/lib/components/Calendar/__tests__/__snapshots__/Calendar.test.tsx.snap +1218 -0
- package/lib/components/Calendar/index.ts +6 -0
- package/lib/components/Calendar/subcomponents/AcademicWeek.tsx +36 -0
- package/lib/components/Calendar/subcomponents/AcademicWeeks.tsx +46 -0
- package/lib/components/Calendar/subcomponents/ColumnHeading.tsx +40 -0
- package/lib/components/{Datepicker/subcomponents/MonthSelector/MonthSelector.tsx → Calendar/subcomponents/Controls.tsx} +17 -17
- package/lib/components/{Datepicker/subcomponents/Day → Calendar/subcomponents}/Day.stories.tsx +30 -7
- package/lib/components/Calendar/subcomponents/Day.tsx +130 -0
- package/lib/components/Calendar/subcomponents/EventDot.tsx +40 -0
- package/lib/components/Calendar/subcomponents/Grid.tsx +117 -0
- package/lib/components/Calendar/subcomponents/index.ts +7 -0
- package/lib/components/Calendar/utils/getAcademicWeekNumbers/getAcademicWeekNumbers.test.ts +104 -0
- package/lib/components/Calendar/utils/getAcademicWeekNumbers/getAcademicWeekNumbers.ts +85 -0
- package/lib/components/{Datepicker → Calendar}/utils/getDatesForCalendarGrid/getDatesForCalendarGrid.test.ts +29 -65
- package/lib/components/{Datepicker → Calendar}/utils/getDatesForCalendarGrid/getDatesForCalendarGrid.ts +11 -43
- package/lib/components/Calendar/utils/index.ts +4 -0
- package/lib/components/Calendar/utils/normaliseMonth/normaliseMonth.test.ts +40 -0
- package/lib/components/Calendar/utils/normaliseMonth/normaliseMonth.ts +16 -0
- package/lib/components/Calendar/utils/parseDateFromString/parseDateFromString.test.ts +15 -0
- package/lib/components/Calendar/utils/parseDateFromString/parseDateFromString.ts +19 -0
- package/lib/components/Datepicker/Datepicker.stories.tsx +220 -23
- package/lib/components/Datepicker/Datepicker.tsx +34 -137
- package/lib/components/Datepicker/Datepicker.types.ts +38 -0
- package/lib/components/Datepicker/__tests__/Datepicker.test.tsx +53 -112
- package/lib/components/Datepicker/__tests__/__snapshots__/Datepicker.test.tsx.snap +92 -638
- package/lib/components/Datepicker/index.ts +1 -1
- package/lib/components/Datepicker/subcomponents/CustomDatepicker.tsx +209 -0
- package/lib/components/Datepicker/subcomponents/DatepickerInput.tsx +74 -0
- package/lib/components/Datepicker/subcomponents/NativeDatepicker.tsx +70 -0
- package/lib/components/Datepicker/subcomponents/Panel.tsx +32 -0
- package/lib/components/Datepicker/subcomponents/VisibleField.tsx +104 -0
- package/lib/components/Datepicker/subcomponents/index.ts +5 -7
- package/lib/components/Datepicker/utils/dateToLocaleISOString/dateToLocaleISOString.test.ts +32 -0
- package/lib/components/Datepicker/utils/dateToLocaleISOString/dateToLocaleISOString.ts +23 -0
- package/lib/components/Datepicker/utils/index.ts +2 -2
- package/lib/components/Datepicker/utils/parseInputValue/parseInputValue.test.ts +110 -0
- package/lib/components/Datepicker/utils/parseInputValue/parseInputValue.ts +57 -0
- package/lib/components/Footer/Footer.tsx +3 -3
- package/lib/components/Footer/__tests__/__snapshots__/Footer.test.tsx.snap +6 -6
- package/lib/components/Header/Header.tsx +32 -33
- package/lib/components/Header/HeaderMenu.tsx +9 -2
- package/lib/components/Header/__tests__/__snapshots__/Header.test.tsx.snap +40 -48
- package/lib/components/Header/index.ts +5 -1
- package/lib/components/Menu/Menu.tsx +3 -0
- package/lib/components/Menu/MenuContent.tsx +4 -1
- package/lib/components/Select/Select.stories.tsx +38 -39
- package/lib/components/Select/Select.tsx +4 -18
- package/lib/components/Select/Select.types.ts +30 -69
- package/lib/components/Select/__tests__/Select.test.tsx +6 -6
- package/lib/components/Select/__tests__/__snapshots__/Select.test.tsx.snap +1 -1
- package/lib/components/Select/index.ts +1 -1
- package/lib/components/Select/subcomponents/CustomSelect.tsx +22 -12
- package/lib/components/Select/subcomponents/NativeSelect.tsx +7 -3
- package/lib/components/Select/subcomponents/Panel.tsx +4 -4
- package/lib/components/Select/subcomponents/VisibleField.tsx +1 -1
- package/lib/components/index.ts +3 -0
- package/package.json +4 -4
- package/LICENSE +0 -9
- package/dist/components/Datepicker/subcomponents/CalendarGrid/CalendarGrid.d.ts +0 -6
- package/dist/components/Datepicker/subcomponents/CalendarGrid/index.d.ts +0 -1
- package/dist/components/Datepicker/subcomponents/CalendarMenu/CalendarMenu.d.ts +0 -8
- package/dist/components/Datepicker/subcomponents/CalendarMenu/index.d.ts +0 -1
- package/dist/components/Datepicker/subcomponents/ColumnHeadings/ColumnHeadings.d.ts +0 -2
- package/dist/components/Datepicker/subcomponents/ColumnHeadings/index.d.ts +0 -1
- package/dist/components/Datepicker/subcomponents/DateField/DateField.d.ts +0 -7
- package/dist/components/Datepicker/subcomponents/DateField/index.d.ts +0 -1
- package/dist/components/Datepicker/subcomponents/Day/Day.d.ts +0 -10
- package/dist/components/Datepicker/subcomponents/Day/index.d.ts +0 -1
- package/dist/components/Datepicker/subcomponents/MonthSelector/MonthSelector.d.ts +0 -6
- package/dist/components/Datepicker/subcomponents/MonthSelector/index.d.ts +0 -1
- package/dist/components/Datepicker/subcomponents/Native/Native.d.ts +0 -9
- package/dist/components/Datepicker/subcomponents/Native/index.d.ts +0 -1
- package/dist/components/Datepicker/utils/parseDateForDateField/parseDateForDateField.d.ts +0 -20
- package/lib/components/Datepicker/subcomponents/CalendarGrid/CalendarGrid.tsx +0 -59
- package/lib/components/Datepicker/subcomponents/CalendarGrid/index.ts +0 -1
- package/lib/components/Datepicker/subcomponents/CalendarMenu/CalendarMenu.tsx +0 -64
- package/lib/components/Datepicker/subcomponents/CalendarMenu/index.ts +0 -1
- package/lib/components/Datepicker/subcomponents/ColumnHeadings/ColumnHeadings.tsx +0 -35
- package/lib/components/Datepicker/subcomponents/ColumnHeadings/index.ts +0 -1
- package/lib/components/Datepicker/subcomponents/DateField/DateField.tsx +0 -155
- package/lib/components/Datepicker/subcomponents/DateField/__tests__/DateField.test.tsx +0 -191
- package/lib/components/Datepicker/subcomponents/DateField/index.ts +0 -1
- package/lib/components/Datepicker/subcomponents/Day/Day.tsx +0 -94
- package/lib/components/Datepicker/subcomponents/Day/index.ts +0 -1
- package/lib/components/Datepicker/subcomponents/MonthSelector/index.ts +0 -1
- package/lib/components/Datepicker/subcomponents/Native/Native.tsx +0 -59
- package/lib/components/Datepicker/subcomponents/Native/index.ts +0 -1
- package/lib/components/Datepicker/utils/parseDateForDateField/parseDateForDateField.test.ts +0 -41
- package/lib/components/Datepicker/utils/parseDateForDateField/parseDateForDateField.ts +0 -48
- /package/dist/components/{Datepicker/subcomponents/DateField/__tests__/DateField.test.d.ts → Calendar/__tests__/Calendar.test.d.ts} +0 -0
- /package/dist/components/{Datepicker/utils/getDatesForCalendarGrid/getDatesForCalendarGrid.test.d.ts → Calendar/utils/getAcademicWeekNumbers/getAcademicWeekNumbers.test.d.ts} +0 -0
- /package/dist/components/{Datepicker → Calendar}/utils/getDatesForCalendarGrid/getDatesForCalendarGrid.d.ts +0 -0
- /package/dist/components/{Datepicker/utils/parseDateForDateField/parseDateForDateField.test.d.ts → Calendar/utils/getDatesForCalendarGrid/getDatesForCalendarGrid.test.d.ts} +0 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import Calendar from './Calendar';
|
|
3
|
+
import { useArgs } from '@storybook/preview-api';
|
|
4
|
+
import type { AcademicWeek } from './Calendar.types';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Components/Work in progress/Calendar',
|
|
8
|
+
component: Calendar,
|
|
9
|
+
parameters: { layout: 'padded' },
|
|
10
|
+
argTypes: {
|
|
11
|
+
pickedDate: { control: { type: 'date' } },
|
|
12
|
+
minDate: { control: { type: 'date' } },
|
|
13
|
+
maxDate: { control: { type: 'date' } },
|
|
14
|
+
showAcademicWeeks: { control: { type: 'boolean' } },
|
|
15
|
+
testId: { control: { type: 'text' } },
|
|
16
|
+
},
|
|
17
|
+
tags: ['autodocs'],
|
|
18
|
+
} satisfies Meta<typeof Calendar>;
|
|
19
|
+
|
|
20
|
+
export default meta;
|
|
21
|
+
type Story = StoryObj<typeof meta>;
|
|
22
|
+
|
|
23
|
+
// Convert UNIX timestamp from Storybook controls to `Date` object
|
|
24
|
+
// https://storybook.js.org/docs/essentials/controls#annotation
|
|
25
|
+
const parseDateFromUNIXTimestamp = (timestamp: number) => {
|
|
26
|
+
const date = new Date(timestamp);
|
|
27
|
+
return isNaN(date.getTime()) ? null : date;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const dateToISOString = (date: Date) => {
|
|
31
|
+
return date.toISOString().split('T')[0];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const Default: Story = {
|
|
35
|
+
render: () => {
|
|
36
|
+
const [args, updateArgs] = useArgs();
|
|
37
|
+
args.pickedDate = args.pickedDate
|
|
38
|
+
? parseDateFromUNIXTimestamp(args.pickedDate)
|
|
39
|
+
: null;
|
|
40
|
+
const onDatePick = (date: Date | null) => updateArgs({ pickedDate: date });
|
|
41
|
+
return (
|
|
42
|
+
<Calendar
|
|
43
|
+
{...args}
|
|
44
|
+
onDatePick={onDatePick}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Story repeated in Datepicker.stories.tsx
|
|
51
|
+
export const WithEvents: Story = {
|
|
52
|
+
name: 'With events',
|
|
53
|
+
args: {
|
|
54
|
+
// IIFE gives us event dots for the current month
|
|
55
|
+
events: (() => {
|
|
56
|
+
const currentDate = new Date();
|
|
57
|
+
const currentYear = currentDate.getFullYear();
|
|
58
|
+
const currentMonth = currentDate.getMonth();
|
|
59
|
+
return [
|
|
60
|
+
// Grey event dots
|
|
61
|
+
{ date: dateToISOString(new Date(currentYear, currentMonth, -2)) },
|
|
62
|
+
{ date: dateToISOString(new Date(currentYear, currentMonth, -1)) },
|
|
63
|
+
{ date: dateToISOString(new Date(currentYear, currentMonth, -1)) },
|
|
64
|
+
{ date: dateToISOString(new Date(currentYear, currentMonth, 0)) },
|
|
65
|
+
{ date: dateToISOString(new Date(currentYear, currentMonth, 0)) },
|
|
66
|
+
{ date: dateToISOString(new Date(currentYear, currentMonth, 0)) },
|
|
67
|
+
// Blue event dots
|
|
68
|
+
{ date: dateToISOString(new Date(currentYear, currentMonth, 1)) },
|
|
69
|
+
{ date: dateToISOString(new Date(currentYear, currentMonth, 2)) },
|
|
70
|
+
{ date: dateToISOString(new Date(currentYear, currentMonth, 2)) },
|
|
71
|
+
{ date: dateToISOString(new Date(currentYear, currentMonth, 3)) },
|
|
72
|
+
{ date: dateToISOString(new Date(currentYear, currentMonth, 3)) },
|
|
73
|
+
{ date: dateToISOString(new Date(currentYear, currentMonth, 3)) },
|
|
74
|
+
];
|
|
75
|
+
})(),
|
|
76
|
+
},
|
|
77
|
+
render: () => {
|
|
78
|
+
const [args, updateArgs] = useArgs();
|
|
79
|
+
args.pickedDate = args.pickedDate
|
|
80
|
+
? parseDateFromUNIXTimestamp(args.pickedDate)
|
|
81
|
+
: null;
|
|
82
|
+
const onDatePick = (date: Date | null) => updateArgs({ pickedDate: date });
|
|
83
|
+
return (
|
|
84
|
+
<Calendar
|
|
85
|
+
{...args}
|
|
86
|
+
onDatePick={onDatePick}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const academicWeeks: AcademicWeek[] = [
|
|
93
|
+
{ start: '2025-08-25', number: 1 },
|
|
94
|
+
{ start: '2025-09-01', number: 2 },
|
|
95
|
+
{ start: '2025-09-08', number: 3 },
|
|
96
|
+
{ start: '2025-09-15', number: 4 },
|
|
97
|
+
{ start: '2025-09-22', number: 5 },
|
|
98
|
+
{ start: '2025-09-29', number: 6 },
|
|
99
|
+
{ start: '2025-10-06', number: 7 },
|
|
100
|
+
{ start: '2025-10-13', number: 8 },
|
|
101
|
+
{ start: '2025-10-20', number: 9 },
|
|
102
|
+
{ start: '2025-10-27', number: 10 },
|
|
103
|
+
{ start: '2025-11-03', number: 11 },
|
|
104
|
+
{ start: '2025-11-10', number: 12 },
|
|
105
|
+
{ start: '2025-11-17', number: 13 },
|
|
106
|
+
{ start: '2025-11-24', number: 14 },
|
|
107
|
+
{ start: '2025-12-01', number: 15 },
|
|
108
|
+
{ start: '2025-12-08', number: 16 },
|
|
109
|
+
{ start: '2025-12-15', number: 17 },
|
|
110
|
+
{ start: '2025-12-22', number: 18 },
|
|
111
|
+
{ start: '2025-12-29', number: 19 },
|
|
112
|
+
{ start: '2026-01-05', number: 20 },
|
|
113
|
+
{ start: '2026-01-12', number: 21 },
|
|
114
|
+
{ start: '2026-01-19', number: 22 },
|
|
115
|
+
{ start: '2026-01-26', number: 23 },
|
|
116
|
+
{ start: '2026-02-02', number: 24 },
|
|
117
|
+
{ start: '2026-02-09', number: 25 },
|
|
118
|
+
{ start: '2026-02-16', number: 26 },
|
|
119
|
+
{ start: '2026-02-23', number: 27 },
|
|
120
|
+
{ start: '2026-03-02', number: 28 },
|
|
121
|
+
{ start: '2026-03-09', number: 29 },
|
|
122
|
+
{ start: '2026-03-16', number: 30 },
|
|
123
|
+
{ start: '2026-03-23', number: 31 },
|
|
124
|
+
{ start: '2026-03-30', number: 32 },
|
|
125
|
+
{ start: '2026-04-06', number: 33 },
|
|
126
|
+
{ start: '2026-04-13', number: 34 },
|
|
127
|
+
{ start: '2026-04-20', number: 35 },
|
|
128
|
+
{ start: '2026-04-27', number: 36 },
|
|
129
|
+
{ start: '2026-05-04', number: 37 },
|
|
130
|
+
{ start: '2026-05-11', number: 38 },
|
|
131
|
+
{ start: '2026-05-18', number: 39 },
|
|
132
|
+
{ start: '2026-05-25', number: 40 },
|
|
133
|
+
{ start: '2026-06-01', number: 41 },
|
|
134
|
+
{ start: '2026-06-08', number: 42 },
|
|
135
|
+
{ start: '2026-06-15', number: 43 },
|
|
136
|
+
{ start: '2026-06-22', number: 44 },
|
|
137
|
+
{ start: '2026-06-29', number: 45 },
|
|
138
|
+
{ start: '2026-07-06', number: 46 },
|
|
139
|
+
{ start: '2026-07-13', number: 47 },
|
|
140
|
+
{ start: '2026-07-20', number: 48 },
|
|
141
|
+
{ start: '2026-07-27', number: 49 },
|
|
142
|
+
{ start: '2026-08-03', number: 50 },
|
|
143
|
+
{ start: '2026-08-10', number: 51 },
|
|
144
|
+
{ start: '2026-08-17', number: 52 },
|
|
145
|
+
{ start: '2026-08-24', number: 53 },
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
// Story repeated in Datepicker.stories.tsx
|
|
149
|
+
export const WithAcademicWeeks: Story = {
|
|
150
|
+
name: 'With academic weeks',
|
|
151
|
+
args: {
|
|
152
|
+
showAcademicWeeks: true,
|
|
153
|
+
academicWeeks: academicWeeks,
|
|
154
|
+
pickedDate: new Date(academicWeeks[0].start), // So the week numbers appear on story mount
|
|
155
|
+
},
|
|
156
|
+
render: () => {
|
|
157
|
+
const [args, updateArgs] = useArgs();
|
|
158
|
+
args.pickedDate = args.pickedDate
|
|
159
|
+
? parseDateFromUNIXTimestamp(args.pickedDate)
|
|
160
|
+
: null;
|
|
161
|
+
const onDatePick = (date: Date | null) => updateArgs({ pickedDate: date });
|
|
162
|
+
return (
|
|
163
|
+
<Calendar
|
|
164
|
+
{...args}
|
|
165
|
+
onDatePick={onDatePick}
|
|
166
|
+
/>
|
|
167
|
+
);
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Story repeated in Datepicker.stories.tsx
|
|
172
|
+
export const MinMaxDates: Story = {
|
|
173
|
+
name: 'With min and max dates',
|
|
174
|
+
args: {
|
|
175
|
+
// Initialise min date as 5 days into the current month
|
|
176
|
+
minDate: (() => {
|
|
177
|
+
const now = new Date();
|
|
178
|
+
return new Date(now.getFullYear(), now.getMonth(), 5)
|
|
179
|
+
.toISOString()
|
|
180
|
+
.split('T')[0];
|
|
181
|
+
})(),
|
|
182
|
+
// Initialise max date as 5 days before the last day of the current month
|
|
183
|
+
maxDate: (() => {
|
|
184
|
+
const now = new Date();
|
|
185
|
+
// Get last day of current month
|
|
186
|
+
const lastDay = new Date(
|
|
187
|
+
now.getFullYear(),
|
|
188
|
+
now.getMonth() + 1,
|
|
189
|
+
0
|
|
190
|
+
).getDate();
|
|
191
|
+
return new Date(now.getFullYear(), now.getMonth(), lastDay - 5)
|
|
192
|
+
.toISOString()
|
|
193
|
+
.split('T')[0];
|
|
194
|
+
})(),
|
|
195
|
+
},
|
|
196
|
+
render: () => {
|
|
197
|
+
const [args, updateArgs] = useArgs();
|
|
198
|
+
args.pickedDate = args.pickedDate
|
|
199
|
+
? parseDateFromUNIXTimestamp(args.pickedDate)
|
|
200
|
+
: null;
|
|
201
|
+
const onDatePick = (date: Date | null) => updateArgs({ pickedDate: date });
|
|
202
|
+
return (
|
|
203
|
+
<Calendar
|
|
204
|
+
{...args}
|
|
205
|
+
onDatePick={onDatePick}
|
|
206
|
+
/>
|
|
207
|
+
);
|
|
208
|
+
},
|
|
209
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { css, cx } from '@emotion/css';
|
|
3
|
+
import { Controls, AcademicWeeks, Grid } from './subcomponents';
|
|
4
|
+
import { normaliseMonth, parseDateFromString } from './utils';
|
|
5
|
+
import { useTheme } from '../../theme';
|
|
6
|
+
import type { CalendarProps } from './Calendar.types';
|
|
7
|
+
|
|
8
|
+
const NAME = 'ucl-uikit-calendar';
|
|
9
|
+
|
|
10
|
+
const Calendar = ({
|
|
11
|
+
pickedDate = null,
|
|
12
|
+
onDatePick,
|
|
13
|
+
minDate = null,
|
|
14
|
+
maxDate = null,
|
|
15
|
+
events = [],
|
|
16
|
+
showAcademicWeeks = false,
|
|
17
|
+
academicWeeks = [],
|
|
18
|
+
testId = NAME,
|
|
19
|
+
className,
|
|
20
|
+
}: CalendarProps) => {
|
|
21
|
+
const [theme] = useTheme();
|
|
22
|
+
|
|
23
|
+
if (pickedDate && isNaN(pickedDate.getTime())) {
|
|
24
|
+
console.warn('Calendar: pickedDate is invalid, defaulting to null');
|
|
25
|
+
pickedDate = null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Used to track prop value changes
|
|
29
|
+
const pickedDateRef = useRef<Date | null>(pickedDate ?? null);
|
|
30
|
+
|
|
31
|
+
// Determines the month currently displayed in the calendar
|
|
32
|
+
const [displayMonth, setDisplayMonth] = useState<Date>(
|
|
33
|
+
// Display month initialises as either the picked date's month or the current month
|
|
34
|
+
normaliseMonth(pickedDate ?? new Date()) as Date
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// Parse min and max dates from strings to Date objects
|
|
38
|
+
const minDateParsed = minDate ? parseDateFromString(minDate) : null;
|
|
39
|
+
const maxDateParsed = maxDate ? parseDateFromString(maxDate) : null;
|
|
40
|
+
|
|
41
|
+
// Snap displayed month to the picked date's month if it changes
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (
|
|
44
|
+
// If the picked date is valid
|
|
45
|
+
pickedDate &&
|
|
46
|
+
!isNaN(pickedDate.getTime()) &&
|
|
47
|
+
// If the picked date has changed
|
|
48
|
+
pickedDateRef.current?.getTime() !== pickedDate.getTime()
|
|
49
|
+
) {
|
|
50
|
+
// If the picked date is not in the currently displayed month
|
|
51
|
+
if (
|
|
52
|
+
pickedDate.getMonth() !== displayMonth.getMonth() ||
|
|
53
|
+
pickedDate.getFullYear() !== displayMonth.getFullYear()
|
|
54
|
+
) {
|
|
55
|
+
// Update the display month to the picked date's month
|
|
56
|
+
setDisplayMonth(normaliseMonth(pickedDate) as Date);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Update the ref to the new picked date
|
|
60
|
+
pickedDateRef.current = pickedDate;
|
|
61
|
+
}, [pickedDate, displayMonth]);
|
|
62
|
+
|
|
63
|
+
const handleMonthChange = (change: number) => {
|
|
64
|
+
const newDate = new Date(displayMonth);
|
|
65
|
+
newDate.setMonth(newDate.getMonth() + change);
|
|
66
|
+
setDisplayMonth(normaliseMonth(newDate) as Date);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const width = showAcademicWeeks ? '370' : '312';
|
|
70
|
+
|
|
71
|
+
const baseStyle = css`
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-direction: column;
|
|
74
|
+
align-items: center;
|
|
75
|
+
gap: 16px;
|
|
76
|
+
width: ${width}px;
|
|
77
|
+
box-sizing: border-box;
|
|
78
|
+
border: 1px solid ${theme.color.neutral.grey20};
|
|
79
|
+
padding: 16px;
|
|
80
|
+
background-color: ${theme.color.neutral.white};
|
|
81
|
+
`;
|
|
82
|
+
|
|
83
|
+
const innerContainerStyle = css`
|
|
84
|
+
display: flex;
|
|
85
|
+
flex-direction: row;
|
|
86
|
+
gap: 8px;
|
|
87
|
+
width: 100%;
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
const style = cx(testId, baseStyle, className);
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div
|
|
94
|
+
className={style}
|
|
95
|
+
data-testid={testId}
|
|
96
|
+
>
|
|
97
|
+
<Controls
|
|
98
|
+
month={displayMonth}
|
|
99
|
+
changeMonth={handleMonthChange}
|
|
100
|
+
/>
|
|
101
|
+
<div className={innerContainerStyle}>
|
|
102
|
+
{showAcademicWeeks && (
|
|
103
|
+
<AcademicWeeks
|
|
104
|
+
date={displayMonth}
|
|
105
|
+
weeks={academicWeeks}
|
|
106
|
+
/>
|
|
107
|
+
)}
|
|
108
|
+
<Grid
|
|
109
|
+
month={displayMonth}
|
|
110
|
+
pickedDate={pickedDate}
|
|
111
|
+
onDatePick={onDatePick}
|
|
112
|
+
minDate={minDateParsed}
|
|
113
|
+
maxDate={maxDateParsed}
|
|
114
|
+
events={events}
|
|
115
|
+
/>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export default Calendar;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Used to display EventDots inside `<Day>` component
|
|
2
|
+
export type CalendarEvent = {
|
|
3
|
+
date: string; // Date string in YYYY-MM-DD format
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type AcademicWeek = {
|
|
7
|
+
start: string; // ISO date string: YYYY-MM-DD format
|
|
8
|
+
number: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export interface CalendarProps {
|
|
12
|
+
pickedDate?: Date | null;
|
|
13
|
+
onDatePick?: (date: Date | null, event?: React.SyntheticEvent) => void;
|
|
14
|
+
minDate?: string | null; // ISO date string: YYYY-MM-DD format
|
|
15
|
+
maxDate?: string | null; // ISO date string: YYYY-MM-DD format
|
|
16
|
+
events?: CalendarEvent[];
|
|
17
|
+
showAcademicWeeks?: boolean;
|
|
18
|
+
academicWeeks?: AcademicWeek[];
|
|
19
|
+
testId?: string;
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, test, expect, vi, beforeAll } from 'vitest';
|
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
3
|
+
import { ThemeContextProvider } from '../../../theme';
|
|
4
|
+
import Calendar from '../Calendar';
|
|
5
|
+
|
|
6
|
+
const defaultTestId = 'ucl-uikit-calendar';
|
|
7
|
+
|
|
8
|
+
const customRender = (ui: React.ReactElement) => {
|
|
9
|
+
return render(ui, {
|
|
10
|
+
wrapper: ({ children }) => (
|
|
11
|
+
<ThemeContextProvider>{children}</ThemeContextProvider>
|
|
12
|
+
),
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe('Calendar', () => {
|
|
17
|
+
beforeAll(() => {
|
|
18
|
+
// Snapshot tests will fail because the Calendar Grid includes styling for "today's date".
|
|
19
|
+
// Therefore, we need to use Vitest to mock the current date.
|
|
20
|
+
vi.useFakeTimers();
|
|
21
|
+
vi.setSystemTime(new Date('2025-03-10')); // Arbitrary fixed date -- Alex's birthday :)
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Snapshot tests
|
|
25
|
+
|
|
26
|
+
test('Snapshot: no date provided', () => {
|
|
27
|
+
const renderResult = customRender(<Calendar />);
|
|
28
|
+
expect(renderResult.container.firstChild).toMatchSnapshot();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('Snapshot: with date provided', () => {
|
|
32
|
+
const renderResult = customRender(
|
|
33
|
+
<Calendar pickedDate={new Date('2025-01-06')} />
|
|
34
|
+
);
|
|
35
|
+
expect(renderResult.container.firstChild).toMatchSnapshot();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Unit tests
|
|
39
|
+
|
|
40
|
+
test('Can be found via default test id', () => {
|
|
41
|
+
customRender(<Calendar />);
|
|
42
|
+
const calendar = screen.getByTestId(defaultTestId);
|
|
43
|
+
expect(calendar).toBeInTheDocument();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('Can be found custom test id', () => {
|
|
47
|
+
const customTestId = '123';
|
|
48
|
+
customRender(<Calendar testId={customTestId} />);
|
|
49
|
+
const calendar = screen.getByTestId(customTestId);
|
|
50
|
+
expect(calendar).toBeInTheDocument();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('Can pick a date', () => {
|
|
54
|
+
const dateToPick = new Date('2025-03-22');
|
|
55
|
+
const onDatePick = vi.fn();
|
|
56
|
+
customRender(
|
|
57
|
+
<Calendar
|
|
58
|
+
pickedDate={new Date('2025-03-01')} // To ensure the calendar is displayed for March 2025
|
|
59
|
+
onDatePick={onDatePick}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
const dayButton = screen.getByText('22');
|
|
63
|
+
|
|
64
|
+
fireEvent.click(dayButton);
|
|
65
|
+
// OnDatePick = (date: Date | null, event: React.SyntheticEvent) => void
|
|
66
|
+
const [firstArg, secondArg] = onDatePick.mock.calls[0];
|
|
67
|
+
expect(firstArg).toEqual(dateToPick);
|
|
68
|
+
expect(secondArg).toHaveProperty('type', 'click');
|
|
69
|
+
expect(secondArg).toHaveProperty('target');
|
|
70
|
+
});
|
|
71
|
+
});
|