uikit-react-public 0.11.20 → 0.14.21
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/Badge/Badge.d.ts +6 -0
- package/dist/components/Badge/Badge.stories.d.ts +15 -0
- package/dist/components/Badge/index.d.ts +2 -0
- package/dist/components/Button/Button.d.ts +2 -1
- 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/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/{Datepicker/subcomponents/Day → Calendar/subcomponents}/Day.d.ts +3 -3
- package/dist/components/{Datepicker → Calendar}/subcomponents/EventDot.d.ts +2 -2
- package/dist/components/Calendar/subcomponents/Grid.d.ts +11 -0
- package/dist/components/Calendar/subcomponents/index.d.ts +7 -0
- package/dist/components/{Datepicker → Calendar}/utils/getAcademicWeekNumbers/getAcademicWeekNumbers.d.ts +1 -1
- 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/parseDateFromString/parseDateFromString.d.ts +9 -0
- package/dist/components/Calendar/utils/parseDateFromString/parseDateFromString.test.d.ts +1 -0
- package/dist/components/CookieNotice/CookieNotice.d.ts +16 -0
- package/dist/components/CookieNotice/index.d.ts +2 -0
- package/dist/components/Datepicker/Datepicker.d.ts +3 -16
- package/dist/components/Datepicker/Datepicker.stories.d.ts +9 -3
- package/dist/components/Datepicker/Datepicker.types.d.ts +23 -7
- package/dist/components/Datepicker/index.d.ts +1 -2
- 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 -3
- 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/Dialog/BaseDialog.d.ts +7 -2
- package/dist/components/FileInput/FileInput.d.ts +8 -0
- package/dist/components/FileInput/FileInput.stories.d.ts +16 -0
- package/dist/components/FileInput/__tests__/FileInput.test.d.ts +1 -0
- package/dist/components/FileInput/index.d.ts +2 -0
- package/dist/components/Footer/Footer.d.ts +1 -1
- package/dist/components/Header/Header.d.ts +4 -1
- package/dist/components/Heading/Heading.d.ts +1 -1
- package/dist/components/Link/BaseLink.d.ts +10 -0
- package/dist/components/Link/Link.d.ts +5 -10
- package/dist/components/Link/Link.stories.d.ts +1 -1
- package/dist/components/Link/index.d.ts +1 -1
- package/dist/components/Menu/Menu.d.ts +2 -1
- package/dist/components/Menu/MenuContent.d.ts +2 -1
- package/dist/components/Menu/MenuItem.d.ts +2 -0
- package/dist/components/Menu/MenuSection.d.ts +1 -1
- package/dist/components/Search/Search.d.ts +16 -0
- package/dist/components/Search/Search.stories.d.ts +34 -0
- package/dist/components/Search/__tests__/Search.test.d.ts +1 -0
- package/dist/components/Search/index.d.ts +2 -0
- package/dist/components/Select/Select.d.ts +1 -1
- package/dist/components/Select/Select.stories.d.ts +3 -7
- package/dist/components/Select/Select.types.d.ts +19 -14
- package/dist/components/Select/subcomponents/CustomOption.d.ts +1 -1
- package/dist/components/Select/subcomponents/CustomSelect.d.ts +1 -2
- package/dist/components/Select/subcomponents/Panel.d.ts +1 -1
- package/dist/components/Select/subcomponents/VisibleField.d.ts +4 -4
- package/dist/components/StandaloneLink/StandaloneLink.d.ts +12 -0
- package/dist/components/StandaloneLink/StandaloneLink.stories.d.ts +13 -0
- package/dist/components/StandaloneLink/__tests__/StandaloneLink.test.d.ts +1 -0
- package/dist/components/StandaloneLink/index.d.ts +2 -0
- package/dist/components/Table/Table.d.ts +10 -8
- package/dist/components/Table/Table.stories.d.ts +21 -0
- package/dist/components/Table/Table.types.d.ts +11 -0
- package/dist/components/Table/__tests__/Table.test.d.ts +1 -0
- package/dist/components/Table/index.d.ts +2 -1
- package/dist/components/Table/subcomponents/Body.d.ts +4 -0
- package/dist/components/Table/subcomponents/Cell/Cell.d.ts +12 -0
- package/dist/components/Table/subcomponents/Cell/Cell.stories.d.ts +313 -0
- package/dist/components/Table/subcomponents/Cell/CellContent.d.ts +10 -0
- package/dist/components/Table/subcomponents/Cell/__tests__/Cell.test.d.ts +1 -0
- package/dist/components/Table/subcomponents/Head.d.ts +4 -0
- package/dist/components/Table/subcomponents/HeadCell/HeadCell.d.ts +13 -0
- package/dist/components/Table/subcomponents/HeadCell/HeadCell.stories.d.ts +312 -0
- package/dist/components/Table/subcomponents/HeadCell/HeadCellContent.d.ts +10 -0
- package/dist/components/Table/subcomponents/HeadCell/__tests__/HeadCell.test.d.ts +1 -0
- package/dist/components/Table/subcomponents/Row.d.ts +5 -0
- package/dist/components/Table/subcomponents/SortIcon.d.ts +7 -0
- package/dist/components/Table/subcomponents/index.d.ts +10 -0
- package/dist/components/Tabs/Tab.d.ts +1 -1
- package/dist/components/Tabs/TabContext.d.ts +1 -0
- package/dist/components/Tabs/Tabs.d.ts +3 -1
- package/dist/components/Tabs/Tabs.stories.d.ts +3 -0
- package/dist/components/Timepicker/Timepicker.d.ts +10 -0
- package/dist/components/Timepicker/Timepicker.stories.d.ts +7 -0
- package/dist/components/Timepicker/__tests__/Timepicker.test.d.ts +1 -0
- package/dist/components/Timepicker/index.d.ts +2 -0
- package/dist/components/Timepicker/utils/convertDateToTimeString.d.ts +2 -0
- package/dist/components/Timepicker/utils/convertDateToTimeString.test.d.ts +1 -0
- package/dist/components/Timepicker/utils/index.d.ts +1 -0
- package/dist/components/WeekPicker/WeekPicker.d.ts +3 -0
- package/dist/components/WeekPicker/index.d.ts +1 -0
- package/dist/components/WeekPicker/subcomponents/CustomDatepicker.d.ts +17 -0
- package/dist/components/WeekPicker/subcomponents/DatepickerInput.d.ts +13 -0
- package/dist/components/WeekPicker/subcomponents/VisibleField.d.ts +15 -0
- package/dist/components/WeekPicker/subcomponents/index.d.ts +3 -0
- package/dist/components/index.d.ts +13 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/useFocusTrap.d.ts +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6006 -4599
- package/dist/theme/defaultTheme.d.ts +7 -0
- package/dist/theme/useTheme.d.ts +14 -0
- package/dist/utils/__tests__/capitalise.test.d.ts +1 -0
- package/dist/utils/capitalise.d.ts +2 -0
- package/lib/components/Alert/Alert.tsx +7 -1
- package/lib/components/Alert/__tests__/__snapshots__/Alert.test.tsx.snap +4 -0
- package/lib/components/Badge/Badge.stories.tsx +19 -0
- package/lib/components/Badge/Badge.tsx +48 -0
- package/lib/components/Badge/index.ts +2 -0
- package/lib/components/Breadcrumbs/__tests__/__snapshots__/Breadcrumbs.test.tsx.snap +4 -4
- package/lib/components/Button/Button.tsx +5 -2
- 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/{Datepicker → Calendar}/subcomponents/AcademicWeek.tsx +1 -1
- package/lib/components/{Datepicker → Calendar}/subcomponents/AcademicWeeks.tsx +9 -7
- 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 +8 -8
- package/lib/components/{Datepicker/subcomponents/Day → Calendar/subcomponents}/Day.tsx +28 -16
- package/lib/components/{Datepicker → Calendar}/subcomponents/EventDot.tsx +3 -3
- package/lib/components/Calendar/subcomponents/Grid.tsx +116 -0
- package/lib/components/Calendar/subcomponents/index.ts +7 -0
- package/lib/components/{Datepicker → Calendar}/utils/getAcademicWeekNumbers/getAcademicWeekNumbers.test.ts +1 -1
- package/lib/components/{Datepicker → Calendar}/utils/getAcademicWeekNumbers/getAcademicWeekNumbers.ts +1 -1
- 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/CookieNotice/CookieNotice.tsx +114 -0
- package/lib/components/CookieNotice/index.ts +2 -0
- package/lib/components/Datepicker/Datepicker.stories.tsx +128 -64
- package/lib/components/Datepicker/Datepicker.tsx +34 -114
- package/lib/components/Datepicker/Datepicker.types.ts +38 -9
- package/lib/components/Datepicker/__tests__/Datepicker.test.tsx +53 -112
- package/lib/components/Datepicker/__tests__/__snapshots__/Datepicker.test.tsx.snap +92 -747
- package/lib/components/Datepicker/index.ts +1 -2
- 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 -3
- package/lib/components/Datepicker/utils/parseInputValue/parseInputValue.test.ts +110 -0
- package/lib/components/Datepicker/utils/parseInputValue/parseInputValue.ts +57 -0
- package/lib/components/Dialog/BaseDialog.tsx +44 -4
- package/lib/components/Field/__tests__/Field.test.tsx +148 -148
- package/lib/components/FileInput/FileInput.stories.tsx +70 -0
- package/lib/components/FileInput/FileInput.tsx +68 -0
- package/lib/components/FileInput/__tests__/FileInput.test.tsx +99 -0
- package/lib/components/FileInput/__tests__/__snapshots__/FileInput.test.tsx.snap +91 -0
- package/lib/components/FileInput/index.ts +2 -0
- package/lib/components/Footer/Footer.tsx +3 -3
- package/lib/components/Footer/__tests__/__snapshots__/Footer.test.tsx.snap +31 -31
- package/lib/components/Header/Header.tsx +19 -2
- package/lib/components/Header/__tests__/__snapshots__/Header.test.tsx.snap +4 -4
- package/lib/components/Heading/Documentation.mdx +1 -1
- package/lib/components/Heading/Heading.tsx +1 -1
- package/lib/components/Heading/__tests__/Heading.test.tsx +7 -19
- package/lib/components/Heading/__tests__/__snapshots__/Heading.test.tsx.snap +7 -7
- package/lib/components/Label/Label.tsx +0 -2
- package/lib/components/Label/__tests__/__snapshots__/Label.test.tsx.snap +7 -7
- package/lib/components/Link/BaseLink.tsx +84 -0
- package/lib/components/Link/Link.tsx +72 -32
- package/lib/components/Link/__tests__/__snapshots__/link.test.tsx.snap +3 -3
- package/lib/components/Link/__tests__/link.test.tsx +6 -13
- package/lib/components/Link/index.ts +1 -1
- package/lib/components/Menu/Menu.context.tsx +3 -1
- package/lib/components/Menu/Menu.tsx +5 -2
- package/lib/components/Menu/MenuContent.tsx +9 -6
- package/lib/components/Menu/MenuItem.tsx +20 -3
- package/lib/components/Menu/MenuSection.tsx +4 -3
- package/lib/components/Pagination/PaginationControls.tsx +1 -3
- package/lib/components/Search/Search.stories.tsx +41 -0
- package/lib/components/Search/Search.tsx +167 -0
- package/lib/components/Search/__tests__/Search.test.tsx +94 -0
- package/lib/components/Search/__tests__/__snapshots__/Search.test.tsx.snap +179 -0
- package/lib/components/Search/index.ts +2 -0
- package/lib/components/Select/Select.stories.tsx +8 -35
- package/lib/components/Select/Select.tsx +2 -2
- package/lib/components/Select/Select.types.ts +20 -15
- package/lib/components/Select/__tests__/__snapshots__/Select.test.tsx.snap +3 -3
- package/lib/components/Select/subcomponents/CustomOption.tsx +22 -9
- package/lib/components/Select/subcomponents/CustomSelect.tsx +31 -20
- package/lib/components/Select/subcomponents/Panel.tsx +4 -5
- package/lib/components/Select/subcomponents/VisibleField.tsx +26 -22
- package/lib/components/StandaloneLink/StandaloneLink.stories.tsx +32 -0
- package/lib/components/StandaloneLink/StandaloneLink.tsx +183 -0
- package/lib/components/StandaloneLink/__tests__/StandaloneLink.test.tsx +57 -0
- package/lib/components/StandaloneLink/__tests__/__snapshots__/StandaloneLink.test.tsx.snap +19 -0
- package/lib/components/StandaloneLink/index.ts +2 -0
- package/lib/components/Table/Table.stories.tsx +337 -0
- package/lib/components/Table/Table.tsx +42 -67
- package/lib/components/Table/Table.types.ts +14 -0
- package/lib/components/Table/__tests__/Table.test.tsx +121 -0
- package/lib/components/Table/__tests__/__snapshots__/Table.test.tsx.snap +210 -0
- package/lib/components/Table/index.ts +8 -1
- package/lib/components/Table/subcomponents/Body.tsx +18 -0
- package/lib/components/Table/subcomponents/Cell/Cell.stories.tsx +151 -0
- package/lib/components/Table/subcomponents/Cell/Cell.tsx +72 -0
- package/lib/components/Table/subcomponents/Cell/CellContent.tsx +91 -0
- package/lib/components/Table/subcomponents/Cell/__tests__/Cell.test.tsx +115 -0
- package/lib/components/Table/subcomponents/Cell/__tests__/__snapshots__/Cell.test.tsx.snap +107 -0
- package/lib/components/Table/subcomponents/Head.tsx +34 -0
- package/lib/components/Table/subcomponents/HeadCell/HeadCell.stories.tsx +85 -0
- package/lib/components/Table/subcomponents/HeadCell/HeadCell.tsx +99 -0
- package/lib/components/Table/subcomponents/HeadCell/HeadCellContent.tsx +61 -0
- package/lib/components/Table/subcomponents/HeadCell/__tests__/HeadCell.test.tsx +137 -0
- package/lib/components/Table/subcomponents/HeadCell/__tests__/__snapshots__/HeadCell.test.tsx.snap +110 -0
- package/lib/components/Table/subcomponents/Row.tsx +49 -0
- package/lib/components/Table/subcomponents/SortIcon.tsx +63 -0
- package/lib/components/Table/subcomponents/index.ts +14 -0
- package/lib/components/Tabs/Tab.tsx +3 -3
- package/lib/components/Tabs/TabContext.tsx +1 -0
- package/lib/components/Tabs/Tabs.stories.tsx +9 -3
- package/lib/components/Tabs/Tabs.tsx +10 -32
- package/lib/components/Tabs/__tests__/Tabs.test.tsx +10 -4
- package/lib/components/Tabs/__tests__/__snapshots__/Tabs.test.tsx.snap +32 -32
- package/lib/components/Timepicker/Timepicker.stories.tsx +43 -0
- package/lib/components/Timepicker/Timepicker.tsx +96 -0
- package/lib/components/Timepicker/__tests__/Timepicker.test.tsx +55 -0
- package/lib/components/Timepicker/__tests__/__snapshots__/Timepicker.test.tsx.snap +19 -0
- package/lib/components/Timepicker/index.tsx +2 -0
- package/lib/components/Timepicker/utils/convertDateToTimeString.test.ts +54 -0
- package/lib/components/Timepicker/utils/convertDateToTimeString.ts +10 -0
- package/lib/components/Timepicker/utils/index.ts +1 -0
- package/lib/components/WeekPicker/WeekPicker.tsx +26 -0
- package/lib/components/WeekPicker/index.ts +1 -0
- package/lib/components/WeekPicker/subcomponents/CustomDatepicker.tsx +298 -0
- package/lib/components/WeekPicker/subcomponents/DatepickerInput.tsx +111 -0
- package/lib/components/WeekPicker/subcomponents/VisibleField.tsx +126 -0
- package/lib/components/WeekPicker/subcomponents/index.ts +3 -0
- package/lib/components/index.ts +20 -0
- package/lib/hooks/index.ts +2 -0
- package/lib/hooks/useFocusTrap.ts +123 -0
- package/lib/index.ts +1 -0
- package/lib/theme/defaultTheme.ts +7 -0
- package/lib/utils/__tests__/capitalise.test.ts +40 -0
- package/lib/utils/capitalise.ts +4 -0
- package/package.json +2 -2
- package/dist/components/Datepicker/subcomponents/AcademicWeeks.d.ts +0 -7
- package/dist/components/Datepicker/subcomponents/CalendarGrid/CalendarGrid.d.ts +0 -8
- package/dist/components/Datepicker/subcomponents/CalendarGrid/index.d.ts +0 -1
- package/dist/components/Datepicker/subcomponents/CalendarMenu/CalendarMenu.d.ts +0 -12
- 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/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 -54
- package/lib/components/Datepicker/subcomponents/CalendarGrid/index.ts +0 -1
- package/lib/components/Datepicker/subcomponents/CalendarMenu/CalendarMenu.tsx +0 -90
- 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/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/lib/components/Field/__tests__/__snapshots__/Field.test.tsx.snap +0 -300
- /package/dist/components/{Datepicker/subcomponents/DateField/__tests__/DateField.test.d.ts → Calendar/__tests__/Calendar.test.d.ts} +0 -0
- /package/dist/components/{Datepicker → Calendar}/subcomponents/AcademicWeek.d.ts +0 -0
- /package/dist/components/{Datepicker/subcomponents/Day → Calendar/subcomponents}/Day.stories.d.ts +0 -0
- /package/dist/components/{Datepicker → 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 → Calendar}/utils/getDatesForCalendarGrid/getDatesForCalendarGrid.test.d.ts +0 -0
- /package/dist/components/{Datepicker/utils/parseDateForDateField/parseDateForDateField.test.d.ts → Calendar/utils/normaliseMonth/normaliseMonth.test.d.ts} +0 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { useRef, useEffect, useState, useCallback } from 'react';
|
|
2
|
+
import { css, cx } from '@emotion/css';
|
|
3
|
+
import { VisibleField, Panel } from '.';
|
|
4
|
+
import { Calendar } from '../..';
|
|
5
|
+
import { parseInputValue } from '../utils';
|
|
6
|
+
import type { DatepickerValue } from '../Datepicker.types';
|
|
7
|
+
import type { CalendarEvent, AcademicWeek } from '../../Calendar';
|
|
8
|
+
|
|
9
|
+
interface CustomDatepickerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
10
|
+
value?: DatepickerValue;
|
|
11
|
+
onValueChange?: (
|
|
12
|
+
value: DatepickerValue,
|
|
13
|
+
event?: React.SyntheticEvent
|
|
14
|
+
) => void;
|
|
15
|
+
minDate?: string | null; // ISO date string: YYYY-MM-DD
|
|
16
|
+
maxDate?: string | null; // ISO date string: YYYY-MM-DD
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
events?: CalendarEvent[];
|
|
19
|
+
showAcademicWeeks?: boolean;
|
|
20
|
+
academicWeeks?: AcademicWeek[];
|
|
21
|
+
testId?: string;
|
|
22
|
+
ref?: React.RefObject<HTMLDivElement>;
|
|
23
|
+
inputRef?: React.RefObject<HTMLInputElement>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const NAME = 'ucl-uikit-datepicker';
|
|
27
|
+
|
|
28
|
+
const CustomDatepicker = ({
|
|
29
|
+
value = null,
|
|
30
|
+
onValueChange = () => {},
|
|
31
|
+
minDate,
|
|
32
|
+
maxDate,
|
|
33
|
+
disabled = false,
|
|
34
|
+
events,
|
|
35
|
+
showAcademicWeeks,
|
|
36
|
+
academicWeeks = [],
|
|
37
|
+
testId = NAME,
|
|
38
|
+
className,
|
|
39
|
+
ref,
|
|
40
|
+
inputRef,
|
|
41
|
+
...props
|
|
42
|
+
}: CustomDatepickerProps) => {
|
|
43
|
+
if (value && isNaN(value.getTime())) {
|
|
44
|
+
console.warn('CustomDatepicker: value is invalid, defaulting to null');
|
|
45
|
+
value = null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const internalRef = useRef<HTMLDivElement>(null);
|
|
49
|
+
const effectiveRef = ref || internalRef;
|
|
50
|
+
const internalInputRef = useRef<HTMLInputElement>(null);
|
|
51
|
+
const effectiveInputRef = inputRef || internalInputRef;
|
|
52
|
+
|
|
53
|
+
const [panelOpen, setPanelOpen] = useState(false);
|
|
54
|
+
|
|
55
|
+
// Derived props (tidier than using `date?.getDate()`, etc, everywhere.)
|
|
56
|
+
const day = value?.getDate().toString().padStart(2, '0') ?? '';
|
|
57
|
+
const month = value ? (value.getMonth() + 1).toString().padStart(2, '0') : '';
|
|
58
|
+
const year = value?.getFullYear().toString() ?? '';
|
|
59
|
+
const formattedDateString = value ? `${day}/${month}/${year}` : '';
|
|
60
|
+
|
|
61
|
+
const [inputValue, setInputValue] = useState(formattedDateString);
|
|
62
|
+
|
|
63
|
+
const resetField = useCallback(() => {
|
|
64
|
+
setInputValue(formattedDateString);
|
|
65
|
+
}, [setInputValue, formattedDateString]);
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
// Reset the input field when the value changes.
|
|
69
|
+
resetField();
|
|
70
|
+
}, [value, resetField]);
|
|
71
|
+
|
|
72
|
+
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
73
|
+
setInputValue(event.target.value);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const handleParseInput = (event: React.SyntheticEvent) => {
|
|
77
|
+
// `parseInputValue` checks the date is valid and within min/max range.
|
|
78
|
+
const parseDate = parseInputValue(inputValue, minDate, maxDate);
|
|
79
|
+
if (parseDate) {
|
|
80
|
+
onValueChange(parseDate, event);
|
|
81
|
+
} else {
|
|
82
|
+
resetField();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const handleInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
87
|
+
if (event.key === 'Enter') {
|
|
88
|
+
handleParseInput(event);
|
|
89
|
+
} else if (event.key === 'Escape') {
|
|
90
|
+
resetField();
|
|
91
|
+
effectiveInputRef.current?.blur();
|
|
92
|
+
setPanelOpen(false);
|
|
93
|
+
} else if (event.key === 'Tab') {
|
|
94
|
+
resetField();
|
|
95
|
+
setPanelOpen(false);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// If `value` is out of range, automatically set it to null.
|
|
100
|
+
// This is validation to prevent parent components passing invalid dates.
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
const isDateOutOfRange = (date: Date | null): boolean => {
|
|
103
|
+
if (!date) return false;
|
|
104
|
+
|
|
105
|
+
const normaliseDate = (date: Date) => {
|
|
106
|
+
date.setHours(0, 0, 0, 0); // Normalize to UTC midnight to avoid time zone issues
|
|
107
|
+
return date;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const parsedMinDate = minDate ? normaliseDate(new Date(minDate)) : null;
|
|
111
|
+
const parsedMaxDate = maxDate ? normaliseDate(new Date(maxDate)) : null;
|
|
112
|
+
const normalisedDate = normaliseDate(date);
|
|
113
|
+
|
|
114
|
+
if (parsedMinDate && normalisedDate < parsedMinDate) return true;
|
|
115
|
+
if (parsedMaxDate && normalisedDate > parsedMaxDate) return true;
|
|
116
|
+
return false;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
if (value && isDateOutOfRange(value)) {
|
|
120
|
+
console.warn('CustomDatepicker: value is out of range, setting to null');
|
|
121
|
+
onValueChange(null);
|
|
122
|
+
}
|
|
123
|
+
}, [value, minDate, maxDate, onValueChange]);
|
|
124
|
+
|
|
125
|
+
// Close the panel & reset the input field if we click away.
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
128
|
+
if (
|
|
129
|
+
effectiveRef.current &&
|
|
130
|
+
!effectiveRef.current.contains(event.target as Node)
|
|
131
|
+
) {
|
|
132
|
+
setPanelOpen(false);
|
|
133
|
+
resetField();
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
137
|
+
return () => {
|
|
138
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
139
|
+
};
|
|
140
|
+
}, [effectiveRef, setPanelOpen, resetField]);
|
|
141
|
+
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
// Close the panel if the <Datepicker> becomes disabled
|
|
144
|
+
if (disabled && panelOpen) setPanelOpen(false);
|
|
145
|
+
}, [disabled, panelOpen]);
|
|
146
|
+
|
|
147
|
+
const handlePickCalendarDate = (
|
|
148
|
+
date: Date | null,
|
|
149
|
+
event?: React.SyntheticEvent
|
|
150
|
+
) => {
|
|
151
|
+
if (onValueChange) {
|
|
152
|
+
onValueChange(date, event);
|
|
153
|
+
}
|
|
154
|
+
setPanelOpen(false);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const handleShowPanel = () => {
|
|
158
|
+
if (disabled) return;
|
|
159
|
+
setPanelOpen(true);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const handleTogglePanel = () => {
|
|
163
|
+
if (disabled) return;
|
|
164
|
+
setPanelOpen((prev) => !prev);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const baseStyle = css`
|
|
168
|
+
width: 196px;
|
|
169
|
+
height: 48px;
|
|
170
|
+
box-sizing: border-box;
|
|
171
|
+
position: relative;
|
|
172
|
+
`;
|
|
173
|
+
|
|
174
|
+
const style = cx(NAME, className, baseStyle);
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<div
|
|
178
|
+
className={style}
|
|
179
|
+
data-testid={testId}
|
|
180
|
+
ref={effectiveRef}
|
|
181
|
+
{...props}
|
|
182
|
+
>
|
|
183
|
+
<VisibleField
|
|
184
|
+
inputValue={inputValue}
|
|
185
|
+
onInputChange={handleInputChange}
|
|
186
|
+
onInputKeyDown={handleInputKeyDown}
|
|
187
|
+
onInputFocus={handleShowPanel}
|
|
188
|
+
onButtonClick={handleTogglePanel}
|
|
189
|
+
disabled={disabled}
|
|
190
|
+
inputRef={effectiveInputRef}
|
|
191
|
+
/>
|
|
192
|
+
{panelOpen && (
|
|
193
|
+
<Panel>
|
|
194
|
+
<Calendar
|
|
195
|
+
pickedDate={value}
|
|
196
|
+
onDatePick={handlePickCalendarDate}
|
|
197
|
+
minDate={minDate}
|
|
198
|
+
maxDate={maxDate}
|
|
199
|
+
events={events}
|
|
200
|
+
showAcademicWeeks={showAcademicWeeks}
|
|
201
|
+
academicWeeks={academicWeeks}
|
|
202
|
+
/>
|
|
203
|
+
</Panel>
|
|
204
|
+
)}
|
|
205
|
+
</div>
|
|
206
|
+
);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export default CustomDatepicker;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { css, cx } from '@emotion/css';
|
|
2
|
+
import { useTheme } from '../../../theme';
|
|
3
|
+
|
|
4
|
+
interface DatepickerInputProps {
|
|
5
|
+
value: string;
|
|
6
|
+
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
7
|
+
onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
8
|
+
onFocus: () => void;
|
|
9
|
+
disabled: boolean;
|
|
10
|
+
ref: React.RefObject<HTMLInputElement | null>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const NAME = 'ucl-uikit-datepicker__input';
|
|
14
|
+
|
|
15
|
+
const DatepickerInput = ({
|
|
16
|
+
value,
|
|
17
|
+
onChange,
|
|
18
|
+
onKeyDown,
|
|
19
|
+
onFocus,
|
|
20
|
+
disabled,
|
|
21
|
+
ref,
|
|
22
|
+
}: DatepickerInputProps) => {
|
|
23
|
+
const [theme] = useTheme();
|
|
24
|
+
|
|
25
|
+
const baseStyle = css`
|
|
26
|
+
width: 100%;
|
|
27
|
+
height: 100%;
|
|
28
|
+
padding: 0 0 0 16px;
|
|
29
|
+
border: none;
|
|
30
|
+
color: ${theme.color.text.primary};
|
|
31
|
+
font-family: ${theme.font.family.primary};
|
|
32
|
+
font-size: ${theme.font.size.f16};
|
|
33
|
+
letter-spacing: 1px;
|
|
34
|
+
caret-color: ${theme.color.text.primary};
|
|
35
|
+
|
|
36
|
+
&:focus {
|
|
37
|
+
outline: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&::placeholder {
|
|
41
|
+
color: #8c8c8c; // TODO: Needs a design token -- Figma says 'color/text/tertiary'
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
const disabledStyle = css`
|
|
46
|
+
color: ${theme.color.text.disabled};
|
|
47
|
+
background-color: ${theme.color.neutral.white};
|
|
48
|
+
|
|
49
|
+
&::placeholder {
|
|
50
|
+
color: ${theme.color.text.disabled};
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
const style = cx(NAME, baseStyle, disabled && disabledStyle);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<input
|
|
58
|
+
value={value}
|
|
59
|
+
onChange={onChange}
|
|
60
|
+
onKeyDown={onKeyDown}
|
|
61
|
+
onFocus={onFocus}
|
|
62
|
+
disabled={disabled}
|
|
63
|
+
type='text'
|
|
64
|
+
inputMode='numeric'
|
|
65
|
+
placeholder='DD/MM/YYYY'
|
|
66
|
+
className={style}
|
|
67
|
+
data-testid={NAME}
|
|
68
|
+
ref={ref}
|
|
69
|
+
aria-label={`Currently selected date: ${value}`}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export default DatepickerInput;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { css, cx } from '@emotion/css';
|
|
2
|
+
import { theme } from '../../../theme';
|
|
3
|
+
|
|
4
|
+
interface NativeDatepickerProps
|
|
5
|
+
extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
6
|
+
testId?: string;
|
|
7
|
+
ref?: React.RefObject<HTMLInputElement>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const NAME = 'ucl-uikit-datepicker--native';
|
|
11
|
+
|
|
12
|
+
const NativeDatepicker = ({
|
|
13
|
+
value,
|
|
14
|
+
onChange,
|
|
15
|
+
min,
|
|
16
|
+
max,
|
|
17
|
+
className,
|
|
18
|
+
disabled,
|
|
19
|
+
testId = NAME,
|
|
20
|
+
ref,
|
|
21
|
+
...props
|
|
22
|
+
}: NativeDatepickerProps) => {
|
|
23
|
+
const baseStyle = css`
|
|
24
|
+
width: 196px;
|
|
25
|
+
height: 48px;
|
|
26
|
+
box-sizing: border-box;
|
|
27
|
+
padding: 0 16px;
|
|
28
|
+
border: 1px solid ${theme.color.neutral.grey60};
|
|
29
|
+
color: #1a1a1a;
|
|
30
|
+
font-size: 16px;
|
|
31
|
+
font-family: sans-serif;
|
|
32
|
+
text-transform: uppercase;
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
|
|
35
|
+
&:focus-visible {
|
|
36
|
+
outline: none;
|
|
37
|
+
box-shadow: ${theme.boxShadow.focus};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&::placeholder {
|
|
41
|
+
color: #8c8c8c; // TODO: Needs a design token -- Figma says 'color/text/tertiary'
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
const disabledStyle = css`
|
|
46
|
+
color: ${theme.color.text.disabled};
|
|
47
|
+
background-color: ${theme.color.neutral.white};
|
|
48
|
+
border-color: ${theme.color.neutral.grey20};
|
|
49
|
+
cursor: not-allowed;
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
const style = cx(NAME, baseStyle, disabled && disabledStyle, className);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<input
|
|
56
|
+
type='date'
|
|
57
|
+
value={value}
|
|
58
|
+
onChange={onChange}
|
|
59
|
+
min={min}
|
|
60
|
+
max={max}
|
|
61
|
+
disabled={disabled}
|
|
62
|
+
className={style}
|
|
63
|
+
data-testid={testId}
|
|
64
|
+
ref={ref}
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export default NativeDatepicker;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { css, cx } from '@emotion/css';
|
|
2
|
+
|
|
3
|
+
interface PanelProps {
|
|
4
|
+
zIndex?: number;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const NAME = 'ucl-uikit-datepicker__panel';
|
|
9
|
+
|
|
10
|
+
const Panel = ({ zIndex = 10, children }: PanelProps) => {
|
|
11
|
+
const datepickerHeight = 48;
|
|
12
|
+
const gapFromDatepicker = 8;
|
|
13
|
+
|
|
14
|
+
const baseStyle = css`
|
|
15
|
+
position: absolute;
|
|
16
|
+
top: ${datepickerHeight + gapFromDatepicker}px;
|
|
17
|
+
z-index: ${zIndex};
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
const style = cx(NAME, baseStyle);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
data-testid={NAME}
|
|
25
|
+
className={style}
|
|
26
|
+
>
|
|
27
|
+
{children}
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default Panel;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { css, cx } from '@emotion/css';
|
|
2
|
+
import { DatepickerInput } from './';
|
|
3
|
+
import { Icon } from '../../..';
|
|
4
|
+
import { useTheme } from '../../../theme';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
interface VisibleFieldProps {
|
|
8
|
+
inputValue: string;
|
|
9
|
+
onInputChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
10
|
+
onInputKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
11
|
+
onInputFocus: () => void;
|
|
12
|
+
onButtonClick: () => void;
|
|
13
|
+
disabled: boolean;
|
|
14
|
+
inputRef: React.RefObject<HTMLInputElement | null>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const NAME = 'ucl-uikit-datepicker__visible-field';
|
|
18
|
+
|
|
19
|
+
const VisibleField = ({
|
|
20
|
+
inputValue,
|
|
21
|
+
onInputChange,
|
|
22
|
+
onInputKeyDown,
|
|
23
|
+
onInputFocus,
|
|
24
|
+
onButtonClick,
|
|
25
|
+
disabled,
|
|
26
|
+
inputRef,
|
|
27
|
+
}: VisibleFieldProps) => {
|
|
28
|
+
const [theme] = useTheme();
|
|
29
|
+
|
|
30
|
+
const baseStyle = css`
|
|
31
|
+
display: flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
justify-content: space-between;
|
|
34
|
+
width: 100%;
|
|
35
|
+
height: 100%;
|
|
36
|
+
box-sizing: border-box;
|
|
37
|
+
border: 1px solid ${theme.color.neutral.grey60};
|
|
38
|
+
background-color: ${theme.color.neutral.white};
|
|
39
|
+
|
|
40
|
+
&:focus-within {
|
|
41
|
+
box-shadow: ${theme.boxShadow.focus};
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
const disabledStyle = css`
|
|
46
|
+
color: ${theme.color.text.disabled};
|
|
47
|
+
background-color: ${theme.color.neutral.white};
|
|
48
|
+
border-color: ${theme.color.neutral.grey20};
|
|
49
|
+
|
|
50
|
+
cursor: not-allowed;
|
|
51
|
+
// And child elements
|
|
52
|
+
& * {
|
|
53
|
+
cursor: not-allowed;
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
// The container for the <Icon.Calendar> is a <div> not an <IconButton>,
|
|
58
|
+
// so that the orange border only appears when the input has focus and the user can type.
|
|
59
|
+
// The container increases the clickable area of the icon.
|
|
60
|
+
const iconButtonStyle = css`
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
justify-content: center;
|
|
64
|
+
padding: 0 16px 0 4px;
|
|
65
|
+
height: 100%;
|
|
66
|
+
cursor: pointer;
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
// `min-width` here accounts for a recurring problem,
|
|
70
|
+
// in which icons shrink when horizontal space is limited.
|
|
71
|
+
// TODO: This ought to be fixed in the <Icon> component itself.
|
|
72
|
+
const iconStyle = css`
|
|
73
|
+
min-width: 24px;
|
|
74
|
+
color: ${disabled
|
|
75
|
+
? theme.color.text.disabled
|
|
76
|
+
: '#8C8C8C'}; // TODO: Needs a design token
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
const style = cx(NAME, baseStyle, disabled && disabledStyle);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div
|
|
83
|
+
className={style}
|
|
84
|
+
data-testid={NAME}
|
|
85
|
+
>
|
|
86
|
+
<DatepickerInput
|
|
87
|
+
value={inputValue}
|
|
88
|
+
onChange={onInputChange}
|
|
89
|
+
onKeyDown={onInputKeyDown}
|
|
90
|
+
onFocus={onInputFocus}
|
|
91
|
+
disabled={disabled}
|
|
92
|
+
ref={inputRef}
|
|
93
|
+
/>
|
|
94
|
+
<div
|
|
95
|
+
onClick={onButtonClick}
|
|
96
|
+
className={iconButtonStyle}
|
|
97
|
+
>
|
|
98
|
+
<Icon.Calendar className={iconStyle} />
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export default VisibleField;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
export { default as
|
|
2
|
-
export { default as
|
|
3
|
-
export { default as
|
|
4
|
-
export { default as
|
|
5
|
-
export { default as
|
|
6
|
-
export { default as Native } from './Native';
|
|
7
|
-
export { default as MonthSelector } from './MonthSelector';
|
|
1
|
+
export { default as CustomDatepicker } from './CustomDatepicker';
|
|
2
|
+
export { default as NativeDatepicker } from './NativeDatepicker';
|
|
3
|
+
export { default as VisibleField } from './VisibleField';
|
|
4
|
+
export { default as DatepickerInput } from './DatepickerInput';
|
|
5
|
+
export { default as Panel } from './Panel';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import dateToLocaleISOString from './dateToLocaleISOString';
|
|
3
|
+
|
|
4
|
+
describe('dateToLocaleISOString', () => {
|
|
5
|
+
test('Should return null if the date is null', () => {
|
|
6
|
+
expect(dateToLocaleISOString(null)).toBeNull();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('Should return null if the date is undefined', () => {
|
|
10
|
+
expect(dateToLocaleISOString(undefined)).toBeNull();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('Should return a YYYY-MM-DD string for a valid date', () => {
|
|
14
|
+
const date = new Date('2025-06-28');
|
|
15
|
+
expect(dateToLocaleISOString(date)).toBe('2025-06-28');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('Should handle dates explicitly set to midnight', () => {
|
|
19
|
+
const date = new Date('2025-01-01T00:00:00');
|
|
20
|
+
expect(dateToLocaleISOString(date)).toBe('2025-01-01');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('Should handle dates explicitly set just before the end of the day', () => {
|
|
24
|
+
const date = new Date('2025-12-31T23:59:59');
|
|
25
|
+
expect(dateToLocaleISOString(date)).toBe('2025-12-31');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('Should correctly format a leap day', () => {
|
|
29
|
+
const date = new Date('2024-02-29');
|
|
30
|
+
expect(dateToLocaleISOString(date)).toBe('2024-02-29');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a Date object to a `YYYY-MM-DD` string based on the user's local timezone.
|
|
3
|
+
*
|
|
4
|
+
* Used for passing dates to <NativeDatepicker> in `Datepicker.tsx`
|
|
5
|
+
*
|
|
6
|
+
* This function uses the Swedish (`sv-SE`) locale with `toLocaleDateString`
|
|
7
|
+
* as it conveniently defaults to the ISO 8601 `YYYY-MM-DD` format, while respecting
|
|
8
|
+
* the local date. This avoids timezone conversion issues that can arise from
|
|
9
|
+
* using `toISOString()`.
|
|
10
|
+
*
|
|
11
|
+
* This implementation should handle daylight saving time, etc, correctly.
|
|
12
|
+
*
|
|
13
|
+
* @param date The date to convert.
|
|
14
|
+
* @returns A string in `YYYY-MM-DD` format, or `null` if the input is `null`.
|
|
15
|
+
*/
|
|
16
|
+
const dateToLocaleISOString = (
|
|
17
|
+
date: Date | null | undefined
|
|
18
|
+
): string | null => {
|
|
19
|
+
if (!date) return null;
|
|
20
|
+
return date.toLocaleDateString('sv-SE');
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default dateToLocaleISOString;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
export { default as
|
|
2
|
-
export { default as
|
|
3
|
-
export { default as getAcademicWeekNumbers } from './getAcademicWeekNumbers/getAcademicWeekNumbers';
|
|
1
|
+
export { default as dateToLocaleISOString } from './dateToLocaleISOString/dateToLocaleISOString';
|
|
2
|
+
export { default as parseInputValue } from './parseInputValue/parseInputValue';
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import parseInputValue from './parseInputValue';
|
|
3
|
+
|
|
4
|
+
describe('parseInputValue', () => {
|
|
5
|
+
test('Returns a date from an expected string format', () => {
|
|
6
|
+
const dateWithSlashes = parseInputValue('01/03/2025') as Date;
|
|
7
|
+
expect(dateWithSlashes).toBeInstanceOf(Date);
|
|
8
|
+
expect(dateWithSlashes.getFullYear()).toBe(2025);
|
|
9
|
+
expect(dateWithSlashes.getMonth()).toBe(2); // March is month 2 (0-indexed)
|
|
10
|
+
expect(dateWithSlashes.getDate()).toBe(1);
|
|
11
|
+
|
|
12
|
+
const dateWithDashes = parseInputValue('11-06-2026') as Date;
|
|
13
|
+
expect(dateWithDashes).toBeInstanceOf(Date);
|
|
14
|
+
expect(dateWithDashes.getFullYear()).toBe(2026);
|
|
15
|
+
expect(dateWithDashes.getMonth()).toBe(5); // June is month 5 (0-indexed)
|
|
16
|
+
expect(dateWithDashes.getDate()).toBe(11);
|
|
17
|
+
|
|
18
|
+
const dateWithSpaces = parseInputValue('12 10 2027') as Date;
|
|
19
|
+
expect(dateWithSpaces).toBeInstanceOf(Date);
|
|
20
|
+
expect(dateWithSpaces.getFullYear()).toBe(2027);
|
|
21
|
+
expect(dateWithSpaces.getMonth()).toBe(9); // October is month 9 (0-indexed)
|
|
22
|
+
expect(dateWithSpaces.getDate()).toBe(12);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('Returns null for invalid date strings', () => {
|
|
26
|
+
expect(parseInputValue('invalid-date')).toBeNull();
|
|
27
|
+
expect(parseInputValue('')).toBeNull(); // Empty string
|
|
28
|
+
expect(parseInputValue(' ')).toBeNull(); // Just white space
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('Handles single-digit days and months correctly', () => {
|
|
32
|
+
const dateWithSingleDigit = parseInputValue('1/2/2023') as Date;
|
|
33
|
+
expect(dateWithSingleDigit).toBeInstanceOf(Date);
|
|
34
|
+
expect(dateWithSingleDigit.getFullYear()).toBe(2023);
|
|
35
|
+
expect(dateWithSingleDigit.getMonth()).toBe(1); // February is month 1 (0-indexed)
|
|
36
|
+
expect(dateWithSingleDigit.getDate()).toBe(1);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('Rejects dates before minDate', () => {
|
|
40
|
+
const minDate1 = '2020-01-01';
|
|
41
|
+
const dateBeforeMin1 = parseInputValue('31/12/2019', minDate1) as Date;
|
|
42
|
+
expect(dateBeforeMin1).toBeNull();
|
|
43
|
+
|
|
44
|
+
const minDate2 = '2020-06-15';
|
|
45
|
+
const dateBeforeMin2 = parseInputValue('14/06/2020', minDate2) as Date;
|
|
46
|
+
expect(dateBeforeMin2).toBeNull();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('Rejects dates after maxDate', () => {
|
|
50
|
+
const maxDate = '2025-12-31';
|
|
51
|
+
const dateAfterMax = parseInputValue(
|
|
52
|
+
'01/01/2026',
|
|
53
|
+
undefined,
|
|
54
|
+
maxDate
|
|
55
|
+
) as Date;
|
|
56
|
+
expect(dateAfterMax).toBeNull();
|
|
57
|
+
|
|
58
|
+
const maxDate2 = '2023-06-15';
|
|
59
|
+
const dateAfterMax2 = parseInputValue(
|
|
60
|
+
'16/06/2023',
|
|
61
|
+
undefined,
|
|
62
|
+
maxDate2
|
|
63
|
+
) as Date;
|
|
64
|
+
expect(dateAfterMax2).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('Accepts dates within minDate and maxDate range', () => {
|
|
68
|
+
const minDate1 = '2020-01-01';
|
|
69
|
+
const maxDate1 = '2025-12-31';
|
|
70
|
+
const validDate1 = parseInputValue(
|
|
71
|
+
'15/06/2023',
|
|
72
|
+
minDate1,
|
|
73
|
+
maxDate1
|
|
74
|
+
) as Date;
|
|
75
|
+
expect(validDate1).toBeInstanceOf(Date);
|
|
76
|
+
expect(validDate1.getFullYear()).toBe(2023);
|
|
77
|
+
expect(validDate1.getMonth()).toBe(5); // June is month 5 (0-indexed)
|
|
78
|
+
expect(validDate1.getDate()).toBe(15);
|
|
79
|
+
|
|
80
|
+
const minDate2 = '2025-06-01';
|
|
81
|
+
const maxDate2 = '2025-06-30';
|
|
82
|
+
const validDate2 = parseInputValue(
|
|
83
|
+
'15/06/2025',
|
|
84
|
+
minDate2,
|
|
85
|
+
maxDate2
|
|
86
|
+
) as Date;
|
|
87
|
+
expect(validDate2).toBeInstanceOf(Date);
|
|
88
|
+
expect(validDate2.getFullYear()).toBe(2025);
|
|
89
|
+
expect(validDate2.getMonth()).toBe(5); // June is month 5 (0-indexed)
|
|
90
|
+
expect(validDate2.getDate()).toBe(15);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('Accepts dates after minDate without maxDate', () => {
|
|
94
|
+
const minDate = '2020-01-01';
|
|
95
|
+
const validDate = parseInputValue('15/06/2023', minDate) as Date;
|
|
96
|
+
expect(validDate).toBeInstanceOf(Date);
|
|
97
|
+
expect(validDate.getFullYear()).toBe(2023);
|
|
98
|
+
expect(validDate.getMonth()).toBe(5); // June is month 5 (0-indexed)
|
|
99
|
+
expect(validDate.getDate()).toBe(15);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('Accepts dates before maxDate without minDate', () => {
|
|
103
|
+
const maxDate = '2025-12-31';
|
|
104
|
+
const validDate = parseInputValue('15/06/2025', undefined, maxDate) as Date;
|
|
105
|
+
expect(validDate).toBeInstanceOf(Date);
|
|
106
|
+
expect(validDate.getFullYear()).toBe(2025);
|
|
107
|
+
expect(validDate.getMonth()).toBe(5); // June is month 5 (0-indexed)
|
|
108
|
+
expect(validDate.getDate()).toBe(15);
|
|
109
|
+
});
|
|
110
|
+
});
|