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,36 @@
|
|
|
1
|
+
import { css, cx } from '@emotion/css';
|
|
2
|
+
import { useTheme } from '../../../theme';
|
|
3
|
+
|
|
4
|
+
const NAME = 'ucl-uikit-calendar__academic-week';
|
|
5
|
+
|
|
6
|
+
interface AcademicWeekProps {
|
|
7
|
+
weekNumber?: number | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const AcademicWeek = ({ weekNumber }: AcademicWeekProps) => {
|
|
11
|
+
const [theme] = useTheme();
|
|
12
|
+
|
|
13
|
+
const baseStyle = css`
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: center;
|
|
17
|
+
width: 100%;
|
|
18
|
+
height: 40px;
|
|
19
|
+
font-family: ${theme.font.family.primary};
|
|
20
|
+
font-size: ${theme.font.size.f14};
|
|
21
|
+
color: #6345a5; // TODO: Needs a sensible design token
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
const style = cx(NAME, baseStyle);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
data-testid={NAME}
|
|
29
|
+
className={style}
|
|
30
|
+
>
|
|
31
|
+
{weekNumber && 'W ' + weekNumber.toString().padStart(2, '0')}
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default AcademicWeek;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { css, cx } from '@emotion/css';
|
|
2
|
+
import { AcademicWeek } from './';
|
|
3
|
+
import { getAcademicWeekNumbers } from '../utils';
|
|
4
|
+
import { useTheme } from '../../../theme';
|
|
5
|
+
|
|
6
|
+
import type { AcademicWeek as AcademicWeekType } from '../Calendar.types';
|
|
7
|
+
|
|
8
|
+
const NAME = 'ucl-uikit-calendar__academic-weeks';
|
|
9
|
+
|
|
10
|
+
interface AcademicWeeksProps {
|
|
11
|
+
date: Date;
|
|
12
|
+
weeks: AcademicWeekType[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const AcademicWeeks = ({ date, weeks }: AcademicWeeksProps) => {
|
|
16
|
+
const [theme] = useTheme();
|
|
17
|
+
|
|
18
|
+
const academicWeekNumbers = getAcademicWeekNumbers(weeks, date);
|
|
19
|
+
|
|
20
|
+
const baseStyle = css`
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
padding-top: 32px;
|
|
24
|
+
width: 50px;
|
|
25
|
+
background-color: ${theme.color.interaction.blue5};
|
|
26
|
+
user-select: none;
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
const style = cx(NAME, baseStyle);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
data-testid={NAME}
|
|
34
|
+
className={style}
|
|
35
|
+
>
|
|
36
|
+
{academicWeekNumbers.map((weekNumber) => (
|
|
37
|
+
<AcademicWeek
|
|
38
|
+
key={weekNumber}
|
|
39
|
+
weekNumber={weekNumber}
|
|
40
|
+
/>
|
|
41
|
+
))}
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default AcademicWeeks;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { css, cx } from '@emotion/css';
|
|
2
|
+
import { useTheme } from '../../../theme';
|
|
3
|
+
|
|
4
|
+
interface ColumnHeadingProps {
|
|
5
|
+
index: number;
|
|
6
|
+
day: string; // 'M', 'T', 'W', 'T', 'F', 'S', 'S'
|
|
7
|
+
isWeekend: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const NAME = 'ucl-uikit-calendar__column-heading';
|
|
11
|
+
|
|
12
|
+
const ColumnHeading = ({ index, day, isWeekend }: ColumnHeadingProps) => {
|
|
13
|
+
const [theme] = useTheme();
|
|
14
|
+
|
|
15
|
+
const baseStyle = css`
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
justify-content: center;
|
|
19
|
+
width: 40px;
|
|
20
|
+
height: 32px;
|
|
21
|
+
color: ${isWeekend
|
|
22
|
+
? theme.color.system.orange100
|
|
23
|
+
: theme.color.neutral.grey60};
|
|
24
|
+
font-weight: 700;
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
const style = cx(NAME, baseStyle);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div
|
|
31
|
+
className={style}
|
|
32
|
+
data-testid={NAME}
|
|
33
|
+
key={index}
|
|
34
|
+
>
|
|
35
|
+
{day}
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default ColumnHeading;
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import { css } from '@emotion/css';
|
|
1
|
+
import { css, cx } from '@emotion/css';
|
|
2
2
|
import { Icon } from '../../..';
|
|
3
|
-
import { useTheme } from '
|
|
3
|
+
import { useTheme } from '../../../theme';
|
|
4
4
|
|
|
5
|
-
interface
|
|
6
|
-
|
|
5
|
+
interface ControlsProps {
|
|
6
|
+
month: Date;
|
|
7
7
|
changeMonth: (change: number) => void;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}: MonthSelectorProps) => {
|
|
10
|
+
const NAME = 'ucl-uikit-calendar__controls';
|
|
11
|
+
|
|
12
|
+
const Controls = ({ month, changeMonth }: ControlsProps) => {
|
|
14
13
|
const [theme] = useTheme();
|
|
15
14
|
|
|
16
15
|
const baseStyle = css`
|
|
@@ -37,27 +36,28 @@ const MonthSelector = ({
|
|
|
37
36
|
}
|
|
38
37
|
`;
|
|
39
38
|
|
|
39
|
+
const style = cx(NAME, baseStyle);
|
|
40
|
+
|
|
40
41
|
return (
|
|
41
|
-
<div
|
|
42
|
+
<div
|
|
43
|
+
className={style}
|
|
44
|
+
data-testid={NAME}
|
|
45
|
+
>
|
|
42
46
|
<Icon.ChevronLeft
|
|
43
47
|
className={chevronIconStyle}
|
|
44
48
|
onClick={() => changeMonth(-1)}
|
|
45
49
|
/>
|
|
46
50
|
<span className={monthAndYearStyle}>
|
|
47
51
|
<span>
|
|
48
|
-
{
|
|
49
|
-
?
|
|
52
|
+
{month
|
|
53
|
+
? month.toLocaleDateString('default', {
|
|
50
54
|
month: 'long',
|
|
51
55
|
})
|
|
52
56
|
: new Date().toLocaleDateString('default', {
|
|
53
57
|
month: 'long',
|
|
54
58
|
})}
|
|
55
59
|
</span>
|
|
56
|
-
<span>
|
|
57
|
-
{date
|
|
58
|
-
? date.getFullYear()
|
|
59
|
-
: new Date().getFullYear()}
|
|
60
|
-
</span>
|
|
60
|
+
<span>{month ? month.getFullYear() : new Date().getFullYear()}</span>
|
|
61
61
|
</span>
|
|
62
62
|
<Icon.ChevronRight
|
|
63
63
|
className={chevronIconStyle}
|
|
@@ -67,4 +67,4 @@ const MonthSelector = ({
|
|
|
67
67
|
);
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
export default
|
|
70
|
+
export default Controls;
|
package/lib/components/{Datepicker/subcomponents/Day → Calendar/subcomponents}/Day.stories.tsx
RENAMED
|
@@ -2,8 +2,16 @@ import type { Meta, StoryObj } from '@storybook/react';
|
|
|
2
2
|
import Day from './Day';
|
|
3
3
|
|
|
4
4
|
const meta = {
|
|
5
|
-
title: 'Components/Work in progress/
|
|
5
|
+
title: 'Components/Work in progress/Calendar/Day',
|
|
6
6
|
component: Day,
|
|
7
|
+
argTypes: {
|
|
8
|
+
events: {
|
|
9
|
+
table: {
|
|
10
|
+
// We don't want to show an empty array in the controls
|
|
11
|
+
disable: true,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
7
15
|
} satisfies Meta<typeof Day>;
|
|
8
16
|
|
|
9
17
|
export default meta;
|
|
@@ -14,7 +22,7 @@ export const Default: Story = {
|
|
|
14
22
|
date: new Date(),
|
|
15
23
|
},
|
|
16
24
|
render: (args) => {
|
|
17
|
-
args.date =
|
|
25
|
+
args.date = new Date(args.date);
|
|
18
26
|
return <Day {...args} />;
|
|
19
27
|
},
|
|
20
28
|
};
|
|
@@ -25,7 +33,7 @@ export const Selected: Story = {
|
|
|
25
33
|
isSelected: true,
|
|
26
34
|
},
|
|
27
35
|
render: (args) => {
|
|
28
|
-
args.date =
|
|
36
|
+
args.date = new Date(args.date);
|
|
29
37
|
return <Day {...args} />;
|
|
30
38
|
},
|
|
31
39
|
};
|
|
@@ -36,7 +44,7 @@ export const Today: Story = {
|
|
|
36
44
|
isToday: true,
|
|
37
45
|
},
|
|
38
46
|
render: (args) => {
|
|
39
|
-
args.date =
|
|
47
|
+
args.date = new Date(args.date);
|
|
40
48
|
return <Day {...args} />;
|
|
41
49
|
},
|
|
42
50
|
};
|
|
@@ -47,7 +55,7 @@ export const Disabled: Story = {
|
|
|
47
55
|
isDisabled: true,
|
|
48
56
|
},
|
|
49
57
|
render: (args) => {
|
|
50
|
-
args.date =
|
|
58
|
+
args.date = new Date(args.date);
|
|
51
59
|
return <Day {...args} />;
|
|
52
60
|
},
|
|
53
61
|
};
|
|
@@ -58,7 +66,22 @@ export const NotInCurrentMonth: Story = {
|
|
|
58
66
|
isInCurrentMonth: false,
|
|
59
67
|
},
|
|
60
68
|
render: (args) => {
|
|
61
|
-
args.date =
|
|
69
|
+
args.date = new Date(args.date);
|
|
70
|
+
return <Day {...args} />;
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const WithEventDots: Story = {
|
|
75
|
+
args: {
|
|
76
|
+
date: new Date(),
|
|
77
|
+
events: [
|
|
78
|
+
{ date: new Date().toISOString() },
|
|
79
|
+
{ date: new Date().toISOString() },
|
|
80
|
+
{ date: new Date().toISOString() },
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
render: (args) => {
|
|
84
|
+
args.date = new Date(args.date);
|
|
62
85
|
return <Day {...args} />;
|
|
63
86
|
},
|
|
64
87
|
};
|
|
@@ -69,7 +92,7 @@ export const AlertOnPick: Story = {
|
|
|
69
92
|
date: new Date(),
|
|
70
93
|
},
|
|
71
94
|
render: (args) => {
|
|
72
|
-
args.date =
|
|
95
|
+
args.date = new Date(args.date);
|
|
73
96
|
const onPick = (date: Date) => alert(`Picked date: ${date.toDateString()}`);
|
|
74
97
|
return (
|
|
75
98
|
<Day
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { css, cx } from '@emotion/css';
|
|
2
|
+
import { EventDot } from './';
|
|
3
|
+
import { useTheme } from '../../../theme';
|
|
4
|
+
import type { CalendarEvent } from '../Calendar.types';
|
|
5
|
+
|
|
6
|
+
export interface DayProps {
|
|
7
|
+
date: Date;
|
|
8
|
+
onPick?: (date: Date, event: React.SyntheticEvent) => void;
|
|
9
|
+
isSelected?: boolean;
|
|
10
|
+
isToday?: boolean;
|
|
11
|
+
isInCurrentMonth?: boolean;
|
|
12
|
+
isDisabled?: boolean;
|
|
13
|
+
events?: CalendarEvent[]; // Max 3 events are displayed as dots
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const NAME = 'ucl-uikit-calendar__day';
|
|
17
|
+
|
|
18
|
+
const Day = ({
|
|
19
|
+
date,
|
|
20
|
+
onPick,
|
|
21
|
+
isSelected = false,
|
|
22
|
+
isToday = false,
|
|
23
|
+
isInCurrentMonth = true,
|
|
24
|
+
isDisabled = false,
|
|
25
|
+
events = [],
|
|
26
|
+
}: DayProps) => {
|
|
27
|
+
const [theme] = useTheme();
|
|
28
|
+
|
|
29
|
+
// More than 3 dots displayed breaks the layout
|
|
30
|
+
const displayedEvents = events.slice(0, 3);
|
|
31
|
+
|
|
32
|
+
const handlePick = (event: React.MouseEvent) => {
|
|
33
|
+
if (date && !isDisabled && onPick) {
|
|
34
|
+
onPick(date, event);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const backgroundStyle = css`
|
|
39
|
+
display: flex;
|
|
40
|
+
justify-content: center;
|
|
41
|
+
align-items: center;
|
|
42
|
+
position: relative;
|
|
43
|
+
width: 40px;
|
|
44
|
+
height: 40px;
|
|
45
|
+
background-color: ${theme.color.neutral.white};
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
outline: none;
|
|
48
|
+
|
|
49
|
+
&:hover {
|
|
50
|
+
background-color: ${theme.color.neutral.grey10};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
${isSelected &&
|
|
54
|
+
css`
|
|
55
|
+
/* background-color: ${theme.color.interaction.blue70}; */
|
|
56
|
+
background-color: ${isDisabled
|
|
57
|
+
? theme.color.neutral.grey10
|
|
58
|
+
: theme.color.interaction.blue70};
|
|
59
|
+
|
|
60
|
+
color: ${theme.color.text.inverted};
|
|
61
|
+
|
|
62
|
+
&:hover {
|
|
63
|
+
background-color: ${theme.color.interaction.blue100};
|
|
64
|
+
}
|
|
65
|
+
`}
|
|
66
|
+
${isDisabled &&
|
|
67
|
+
css`
|
|
68
|
+
cursor: not-allowed;
|
|
69
|
+
|
|
70
|
+
&:hover {
|
|
71
|
+
/* background-color: ${theme.color.neutral.white}; */
|
|
72
|
+
background-color: ${isSelected
|
|
73
|
+
? theme.color.neutral.grey10
|
|
74
|
+
: theme.color.neutral.white};
|
|
75
|
+
}
|
|
76
|
+
`}
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
const foregroundStyle = css`
|
|
80
|
+
font-family: ${theme.font.family.primary};
|
|
81
|
+
user-select: none;
|
|
82
|
+
font-size: 16px;
|
|
83
|
+
|
|
84
|
+
${!isInCurrentMonth &&
|
|
85
|
+
css`
|
|
86
|
+
color: ${theme.color.neutral.grey40};
|
|
87
|
+
`}
|
|
88
|
+
${isToday &&
|
|
89
|
+
css`
|
|
90
|
+
font-weight: 700;
|
|
91
|
+
`}
|
|
92
|
+
${isDisabled &&
|
|
93
|
+
css`
|
|
94
|
+
color: ${theme.color.neutral.grey20};
|
|
95
|
+
`}
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
const eventDotsContainerStyle = css`
|
|
99
|
+
display: flex;
|
|
100
|
+
justify-content: center;
|
|
101
|
+
align-items: center;
|
|
102
|
+
gap: 3px;
|
|
103
|
+
position: absolute;
|
|
104
|
+
bottom: 3px;
|
|
105
|
+
width: 100%;
|
|
106
|
+
`;
|
|
107
|
+
|
|
108
|
+
const style = cx(NAME, backgroundStyle);
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<div
|
|
112
|
+
onClick={handlePick}
|
|
113
|
+
className={style}
|
|
114
|
+
data-testid={NAME}
|
|
115
|
+
>
|
|
116
|
+
<div className={foregroundStyle}>{date.getDate()}</div>
|
|
117
|
+
<div className={eventDotsContainerStyle}>
|
|
118
|
+
{displayedEvents.map((_event, index) => (
|
|
119
|
+
<EventDot
|
|
120
|
+
key={index}
|
|
121
|
+
inverted={isSelected}
|
|
122
|
+
inCurrentMonth={isInCurrentMonth}
|
|
123
|
+
/>
|
|
124
|
+
))}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export default Day;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { css, cx } from '@emotion/css';
|
|
2
|
+
import { useTheme } from '../../../theme';
|
|
3
|
+
|
|
4
|
+
const NAME = 'ucl-uikit-calendar__event-dot';
|
|
5
|
+
|
|
6
|
+
interface EventDotProps {
|
|
7
|
+
inverted: boolean;
|
|
8
|
+
inCurrentMonth: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const EventDot = ({ inverted, inCurrentMonth }: EventDotProps) => {
|
|
12
|
+
const [theme] = useTheme();
|
|
13
|
+
|
|
14
|
+
const invertedColour = theme.color.neutral.white;
|
|
15
|
+
const outOfCurrentMonthColour = '#8C8C8C'; // TODO: Needs adding to `defaultTheme.ts`, as a design token
|
|
16
|
+
|
|
17
|
+
const backgroundColour = inCurrentMonth
|
|
18
|
+
? inverted
|
|
19
|
+
? invertedColour
|
|
20
|
+
: theme.color.interaction.blue70
|
|
21
|
+
: outOfCurrentMonthColour;
|
|
22
|
+
|
|
23
|
+
const baseStyle = css`
|
|
24
|
+
width: 6px;
|
|
25
|
+
height: 6px;
|
|
26
|
+
border-radius: 50%;
|
|
27
|
+
background-color: ${backgroundColour};
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
const style = cx(NAME, baseStyle);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
data-testid={NAME}
|
|
35
|
+
className={style}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default EventDot;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { css, cx } from '@emotion/css';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { ColumnHeading, Day } from './';
|
|
4
|
+
import { getDatesForCalendarGrid } from '../utils';
|
|
5
|
+
import { useTheme } from '../../../theme';
|
|
6
|
+
import type { CalendarEvent } from '../Calendar.types';
|
|
7
|
+
|
|
8
|
+
interface GridProps {
|
|
9
|
+
month: Date; // 1st day of the month
|
|
10
|
+
pickedDate: Date | null;
|
|
11
|
+
onDatePick?: (date: Date, event: React.SyntheticEvent) => void;
|
|
12
|
+
minDate: Date | null;
|
|
13
|
+
maxDate: Date | null;
|
|
14
|
+
events: CalendarEvent[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const NAME = 'ucl-uikit-calendar__grid';
|
|
18
|
+
|
|
19
|
+
// Helper function: Convert ISO string 'yyyy-mm-dd' to local Date without timezone shift
|
|
20
|
+
function toLocalDate(dateStr: string): Date {
|
|
21
|
+
const [year, month, day] = dateStr.split('-').map(Number);
|
|
22
|
+
return new Date(year, month - 1, day);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getDateKey(date: Date): string {
|
|
26
|
+
return date.toISOString().split('T')[0];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Create events map grouped by date
|
|
30
|
+
function createEventsMap(
|
|
31
|
+
events: CalendarEvent[]
|
|
32
|
+
): Map<string, CalendarEvent[]> {
|
|
33
|
+
const eventsMap = new Map<string, CalendarEvent[]>();
|
|
34
|
+
|
|
35
|
+
events.forEach((event) => {
|
|
36
|
+
const eventDate = toLocalDate(event.date);
|
|
37
|
+
const dateKey = getDateKey(eventDate);
|
|
38
|
+
|
|
39
|
+
if (!eventsMap.has(dateKey)) {
|
|
40
|
+
eventsMap.set(dateKey, []);
|
|
41
|
+
}
|
|
42
|
+
eventsMap.get(dateKey)!.push(event);
|
|
43
|
+
});
|
|
44
|
+
console.log('Events map created:', eventsMap);
|
|
45
|
+
return eventsMap;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const Grid = ({
|
|
49
|
+
month,
|
|
50
|
+
pickedDate,
|
|
51
|
+
onDatePick = () => {},
|
|
52
|
+
minDate,
|
|
53
|
+
maxDate,
|
|
54
|
+
events,
|
|
55
|
+
}: GridProps) => {
|
|
56
|
+
// Fix minDate timezone issues by zeroing time if valid
|
|
57
|
+
if (minDate && !isNaN(minDate.getTime())) minDate.setHours(0, 0, 0, 0);
|
|
58
|
+
|
|
59
|
+
const [theme] = useTheme();
|
|
60
|
+
|
|
61
|
+
const daysOfTheWeek = ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
|
|
62
|
+
const dates = getDatesForCalendarGrid(month);
|
|
63
|
+
|
|
64
|
+
const eventsMap = useMemo(() => createEventsMap(events), [events]);
|
|
65
|
+
|
|
66
|
+
const baseStyle = css`
|
|
67
|
+
display: grid;
|
|
68
|
+
grid-template-columns: repeat(7, 1fr);
|
|
69
|
+
gap: 0;
|
|
70
|
+
text-align: center;
|
|
71
|
+
font-family: ${theme.font.family.primary};
|
|
72
|
+
font-weight: 400;
|
|
73
|
+
user-select: none;
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
const style = cx(NAME, baseStyle);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div
|
|
80
|
+
className={style}
|
|
81
|
+
data-testid={NAME}
|
|
82
|
+
>
|
|
83
|
+
{daysOfTheWeek.map((day, index) => (
|
|
84
|
+
<ColumnHeading
|
|
85
|
+
key={index}
|
|
86
|
+
index={index}
|
|
87
|
+
day={day}
|
|
88
|
+
isWeekend={index >= 5}
|
|
89
|
+
/>
|
|
90
|
+
))}
|
|
91
|
+
{dates.map((mappedDate) => {
|
|
92
|
+
const dateKey = getDateKey(mappedDate);
|
|
93
|
+
const dateEvents = eventsMap.get(dateKey) || [];
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<Day
|
|
97
|
+
key={mappedDate.toISOString()}
|
|
98
|
+
date={mappedDate}
|
|
99
|
+
isSelected={
|
|
100
|
+
pickedDate?.toDateString() === mappedDate.toDateString()
|
|
101
|
+
}
|
|
102
|
+
isToday={mappedDate.toDateString() === new Date().toDateString()}
|
|
103
|
+
isInCurrentMonth={month.getMonth() === mappedDate.getMonth()}
|
|
104
|
+
isDisabled={
|
|
105
|
+
(minDate !== null && mappedDate < minDate) ||
|
|
106
|
+
(maxDate !== null && mappedDate > maxDate)
|
|
107
|
+
}
|
|
108
|
+
onPick={onDatePick}
|
|
109
|
+
events={dateEvents}
|
|
110
|
+
/>
|
|
111
|
+
);
|
|
112
|
+
})}
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export default Grid;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as Controls } from './Controls';
|
|
2
|
+
export { default as AcademicWeeks } from './AcademicWeeks';
|
|
3
|
+
export { default as AcademicWeek } from './AcademicWeek';
|
|
4
|
+
export { default as Grid } from './Grid';
|
|
5
|
+
export { default as ColumnHeading } from './ColumnHeading';
|
|
6
|
+
export { default as Day } from './Day';
|
|
7
|
+
export { default as EventDot } from './EventDot';
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import getAcademicWeekNumbers, { getMonday } from './getAcademicWeekNumbers';
|
|
3
|
+
import type { AcademicWeek } from '../../';
|
|
4
|
+
|
|
5
|
+
describe('getMonday', () => {
|
|
6
|
+
test('Returns the Monday for the week of the given date', () => {
|
|
7
|
+
const date = new Date('2025-09-03'); // Wednesday
|
|
8
|
+
const monday = getMonday(date);
|
|
9
|
+
expect(monday.getFullYear()).toBe(2025);
|
|
10
|
+
expect(monday.getMonth()).toBe(8); // September (0-indexed)
|
|
11
|
+
expect(monday.getDate()).toBe(1); // 1st September 2025
|
|
12
|
+
expect(monday.getDay()).toBe(1); // Monday
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('Works for a date that is already a Monday', () => {
|
|
16
|
+
const date = new Date('2025-10-06'); // Monday
|
|
17
|
+
const monday = getMonday(date);
|
|
18
|
+
expect(monday.getFullYear()).toBe(2025);
|
|
19
|
+
expect(monday.getMonth()).toBe(9); // October (0-indexed)
|
|
20
|
+
expect(monday.getDate()).toBe(6); // 6th October 2025
|
|
21
|
+
expect(monday.getDay()).toBe(1); // Monday
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('getAcademicWeekNumbers', () => {
|
|
26
|
+
test('Returns an array of the correct numbers (works as expected)', () => {
|
|
27
|
+
const targetDate = new Date('2025-09-01'); // Monday 1st September 2025
|
|
28
|
+
const academicWeeks: AcademicWeek[] = [
|
|
29
|
+
{ start: '2025-08-25', number: 1 },
|
|
30
|
+
{ start: '2025-09-01', number: 2 },
|
|
31
|
+
{ start: '2025-09-08', number: 3 },
|
|
32
|
+
{ start: '2025-09-15', number: 4 },
|
|
33
|
+
{ start: '2025-09-22', number: 5 },
|
|
34
|
+
{ start: '2025-09-29', number: 6 },
|
|
35
|
+
{ start: '2025-10-06', number: 7 },
|
|
36
|
+
{ start: '2025-10-13', number: 8 },
|
|
37
|
+
{ start: '2025-10-20', number: 9 },
|
|
38
|
+
{ start: '2025-10-27', number: 10 },
|
|
39
|
+
{ start: '2025-11-03', number: 11 },
|
|
40
|
+
{ start: '2025-11-10', number: 12 },
|
|
41
|
+
{ start: '2025-11-17', number: 13 },
|
|
42
|
+
{ start: '2025-11-24', number: 14 },
|
|
43
|
+
// Etc ...
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const result = getAcademicWeekNumbers(academicWeeks, targetDate);
|
|
47
|
+
expect(result).toEqual([2, 3, 4, 5, 6]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Empty array for `academicWeeks` is default parameter value in `Datepicker.tsx`
|
|
51
|
+
test('Returns empty array if academicWeeks are empty', () => {
|
|
52
|
+
console.warn = vi.fn();
|
|
53
|
+
const emptyArray: AcademicWeek[] = [];
|
|
54
|
+
const result = getAcademicWeekNumbers(emptyArray, new Date());
|
|
55
|
+
expect(result).toEqual([]);
|
|
56
|
+
expect(console.warn).toHaveBeenCalledWith(
|
|
57
|
+
'Datepicker: No academic weeks provided'
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('Returns empty array if targetDate is invalid', () => {
|
|
62
|
+
const result = getAcademicWeekNumbers(
|
|
63
|
+
[{ start: '2025-08-25', number: 1 }],
|
|
64
|
+
new Date('invalid-date')
|
|
65
|
+
);
|
|
66
|
+
expect(result).toEqual([]);
|
|
67
|
+
|
|
68
|
+
const result2 = getAcademicWeekNumbers(
|
|
69
|
+
[{ start: '2025-08-25', number: 1 }],
|
|
70
|
+
null as unknown as Date
|
|
71
|
+
);
|
|
72
|
+
expect(result2).toEqual([]);
|
|
73
|
+
|
|
74
|
+
const result3 = getAcademicWeekNumbers(
|
|
75
|
+
[{ start: '2025-08-25', number: 1 }],
|
|
76
|
+
undefined as unknown as Date
|
|
77
|
+
);
|
|
78
|
+
expect(result3).toEqual([]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('Returns array of undefined if no academic weeks match the current date', () => {
|
|
82
|
+
// A pretend very short example for academic weeks
|
|
83
|
+
const academicWeeks: AcademicWeek[] = [
|
|
84
|
+
{ start: '2025-09-01', number: 1 },
|
|
85
|
+
{ start: '2025-09-08', number: 2 },
|
|
86
|
+
{ start: '2025-09-15', number: 3 },
|
|
87
|
+
{ start: '2025-09-22', number: 4 },
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
// The date is in October, which has no matching academic weeks
|
|
91
|
+
const targetDate = new Date('2025-10-01');
|
|
92
|
+
|
|
93
|
+
const result = getAcademicWeekNumbers(academicWeeks, targetDate);
|
|
94
|
+
|
|
95
|
+
// We expect 5 weeks displayed in the calendar, with no matching academic weeks
|
|
96
|
+
expect(result).toEqual([
|
|
97
|
+
undefined,
|
|
98
|
+
undefined,
|
|
99
|
+
undefined,
|
|
100
|
+
undefined,
|
|
101
|
+
undefined,
|
|
102
|
+
]);
|
|
103
|
+
});
|
|
104
|
+
});
|