uikit-react-public 0.11.24 → 0.17.4
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/README.md +4 -2
- package/dist/components/Accordion/Accordion.Heading.d.ts +4 -4
- package/dist/components/Accordion/Accordion.Panel.d.ts +2 -2
- package/dist/components/Accordion/Accordion.d.ts +1 -1
- package/dist/components/Accordion/Accordion.stories.d.ts +57 -0
- package/dist/components/Accordion/index.d.ts +2 -0
- package/dist/components/Avatar/Avatar.stories.d.ts +107 -1
- 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 +3 -1
- package/dist/components/Calendar/index.d.ts +1 -1
- 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 +1 -1
- package/dist/components/Datepicker/Datepicker.stories.d.ts +4 -3
- package/dist/components/Datepicker/Datepicker.types.d.ts +4 -5
- package/dist/components/Datepicker/subcomponents/CustomDatepicker.d.ts +4 -1
- package/dist/components/Datepicker/subcomponents/DatepickerInput.d.ts +15 -2
- package/dist/components/Datepicker/subcomponents/Panel.d.ts +1 -1
- package/dist/components/Datepicker/subcomponents/VisibleField.d.ts +6 -1
- package/dist/components/Datepicker/subcomponents/index.d.ts +0 -1
- package/dist/components/Datepicker/utils/index.d.ts +0 -1
- package/dist/components/Dialog/BaseDialog.d.ts +8 -2
- package/dist/components/Dialog/Dialog.d.ts +2 -0
- package/dist/components/FileInput/FileInput.d.ts +8 -0
- package/dist/components/FileInput/FileInput.stories.d.ts +16 -0
- package/dist/components/FileInput/index.d.ts +2 -0
- package/dist/components/Header/Header.d.ts +7 -1
- package/dist/components/Header/Header.stories.d.ts +40 -0
- 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/Main/Main.d.ts +21 -0
- package/dist/components/Main/Main.stories.d.ts +15 -0
- package/dist/components/Main/__tests__/Main.test.d.ts +1 -0
- package/dist/components/Main/index.d.ts +2 -0
- package/dist/components/Menu/MenuContent.d.ts +1 -1
- package/dist/components/Menu/MenuItem.d.ts +2 -0
- package/dist/components/Menu/MenuSection.d.ts +1 -1
- package/dist/components/NativeDatepicker/NativeDatepicker.d.ts +3 -0
- package/dist/components/NativeDatepicker/NativeDatepicker.stories.d.ts +36 -0
- package/dist/components/NativeDatepicker/NativeDatepicker.types.d.ts +10 -0
- package/dist/components/NativeDatepicker/index.d.ts +2 -0
- package/dist/components/{Datepicker → NativeDatepicker}/utils/dateToLocaleISOString/dateToLocaleISOString.d.ts +1 -1
- package/dist/components/NativeDatepicker/utils/dateToLocaleISOString/dateToLocaleISOString.test.d.ts +1 -0
- package/dist/components/NativeDatepicker/utils/index.d.ts +1 -0
- 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 +157 -9
- package/dist/components/Select/Select.types.d.ts +66 -32
- package/dist/components/Select/subcomponents/CustomOption.d.ts +1 -1
- package/dist/components/Select/subcomponents/CustomSelect.d.ts +3 -3
- package/dist/components/Select/subcomponents/FilterInput.d.ts +14 -0
- package/dist/components/Select/subcomponents/NativeSelect.d.ts +5 -1
- package/dist/components/Select/subcomponents/Panel.d.ts +1 -1
- package/dist/components/Select/subcomponents/VisibleField.d.ts +6 -4
- package/dist/components/Select/subcomponents/index.d.ts +1 -0
- 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/WeekPicker.stories.d.ts +41 -0
- package/dist/components/WeekPicker/WeekPicker.types.d.ts +16 -0
- package/dist/components/WeekPicker/index.d.ts +2 -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 +19 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/useFocusTrap.d.ts +10 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6460 -4607
- package/dist/theme/defaultTheme.d.ts +7 -0
- package/dist/theme/useTheme.d.ts +14 -0
- package/dist/utils/__tests__/announce.test.d.ts +1 -0
- package/dist/utils/__tests__/capitalise.test.d.ts +1 -0
- package/dist/utils/announce.d.ts +6 -0
- package/dist/utils/capitalise.d.ts +2 -0
- package/dist/utils/index.d.ts +1 -0
- package/lib/components/Accordion/Accordion.Heading.tsx +27 -8
- package/lib/components/Accordion/Accordion.Panel.tsx +11 -3
- package/lib/components/Accordion/Accordion.stories.tsx +139 -0
- package/lib/components/Accordion/Accordion.tsx +10 -8
- package/lib/components/Accordion/__tests__/__snapshots__/Accordion.test.tsx.snap +7 -7
- package/lib/components/Accordion/index.ts +2 -0
- package/lib/components/Alert/Alert.stories.tsx +1 -1
- package/lib/components/Alert/Alert.tsx +7 -1
- package/lib/components/Alert/__tests__/__snapshots__/Alert.test.tsx.snap +4 -0
- package/lib/components/Avatar/Avatar.mdx +117 -0
- package/lib/components/Avatar/Avatar.stories.tsx +110 -2
- 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/Blanket/Blanket.stories.tsx +1 -1
- package/lib/components/Breadcrumbs/__tests__/__snapshots__/Breadcrumbs.test.tsx.snap +4 -4
- package/lib/components/Button/Button.stories.tsx +1 -1
- package/lib/components/Button/Button.tsx +6 -2
- package/lib/components/Calendar/Calendar.stories.tsx +12 -32
- package/lib/components/Calendar/__tests__/Calendar.test.tsx +23 -15
- package/lib/components/Calendar/index.ts +1 -5
- package/lib/components/Calendar/subcomponents/AcademicWeeks.tsx +2 -1
- package/lib/components/Calendar/subcomponents/ColumnHeading.tsx +5 -1
- package/lib/components/Calendar/subcomponents/EventDot.tsx +2 -1
- package/lib/components/Calendar/subcomponents/Grid.tsx +0 -1
- package/lib/components/Calendar/subcomponents/index.ts +1 -1
- package/lib/components/Calendar/utils/getDatesForCalendarGrid/getDatesForCalendarGrid.ts +43 -11
- package/lib/components/Calendar/utils/normaliseMonth/normaliseMonth.test.ts +5 -5
- package/lib/components/CookieNotice/CookieNotice.tsx +114 -0
- package/lib/components/CookieNotice/index.ts +2 -0
- package/lib/components/Datepicker/Datepicker.lld.md +108 -0
- package/lib/components/Datepicker/Datepicker.stories.tsx +44 -5
- package/lib/components/Datepicker/Datepicker.tsx +14 -36
- package/lib/components/Datepicker/Datepicker.types.ts +5 -14
- package/lib/components/Datepicker/__tests__/Datepicker.test.tsx +150 -8
- package/lib/components/Datepicker/__tests__/__snapshots__/Datepicker.test.tsx.snap +10 -4
- package/lib/components/Datepicker/subcomponents/CustomDatepicker.tsx +39 -5
- package/lib/components/Datepicker/subcomponents/DatepickerInput.tsx +30 -17
- package/lib/components/Datepicker/subcomponents/Panel.tsx +6 -2
- package/lib/components/Datepicker/subcomponents/VisibleField.tsx +40 -3
- package/lib/components/Datepicker/subcomponents/index.ts +0 -1
- package/lib/components/Datepicker/utils/index.ts +0 -1
- package/lib/components/Dialog/BaseDialog.tsx +55 -4
- package/lib/components/Dialog/Dialog.tsx +8 -1
- package/lib/components/Dialog/DialogBody.tsx +5 -1
- package/lib/components/Dialog/DialogHeader.tsx +2 -1
- package/lib/components/Divider/Divider.stories.tsx +1 -1
- package/lib/components/Field/ErrorText.tsx +1 -0
- package/lib/components/Field/Field.stories.tsx +1 -1
- package/lib/components/Field/__tests__/Field.test.tsx +161 -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.stories.tsx +1 -1
- package/lib/components/Footer/__tests__/__snapshots__/Footer.test.tsx.snap +28 -28
- package/lib/components/Header/Header.mdx +52 -0
- package/lib/components/Header/Header.stories.tsx +98 -0
- package/lib/components/Header/Header.tsx +65 -3
- package/lib/components/Header/__tests__/Header.test.tsx +17 -1
- 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.stories.tsx +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/Icon/Icon.stories.tsx +1 -1
- package/lib/components/IconButton/IconButton.stories.tsx +1 -1
- package/lib/components/Input/Input.stories.tsx +1 -1
- package/lib/components/Label/Label.stories.tsx +1 -1
- 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/Main/Main.stories.tsx +36 -0
- package/lib/components/Main/Main.tsx +46 -0
- package/lib/components/Main/__tests__/Main.test.tsx +80 -0
- package/lib/components/Main/__tests__/__snapshots__/Main.test.tsx.snap +33 -0
- package/lib/components/Main/index.ts +2 -0
- package/lib/components/Menu/Menu.context.tsx +3 -1
- package/lib/components/Menu/Menu.tsx +2 -2
- package/lib/components/Menu/MenuContent.tsx +5 -5
- package/lib/components/Menu/MenuItem.tsx +20 -3
- package/lib/components/Menu/MenuSection.tsx +4 -3
- package/lib/components/NativeDatepicker/NativeDatepicker.stories.tsx +100 -0
- package/lib/components/{Datepicker/subcomponents → NativeDatepicker}/NativeDatepicker.tsx +14 -15
- package/lib/components/NativeDatepicker/NativeDatepicker.types.ts +19 -0
- package/lib/components/NativeDatepicker/index.ts +2 -0
- package/lib/components/{Datepicker → NativeDatepicker}/utils/dateToLocaleISOString/dateToLocaleISOString.ts +1 -1
- package/lib/components/NativeDatepicker/utils/index.ts +1 -0
- package/lib/components/Pagination/PaginationControls.tsx +56 -15
- package/lib/components/Pagination/PaginationInfo.tsx +5 -1
- package/lib/components/Paragraph/Paragraph.stories.tsx +1 -1
- package/lib/components/Search/Search.stories.tsx +41 -0
- package/lib/components/Search/Search.tsx +170 -0
- package/lib/components/Search/__tests__/Search.test.tsx +112 -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.mdx +169 -0
- package/lib/components/Select/Select.stories.tsx +198 -77
- package/lib/components/Select/Select.tsx +37 -13
- package/lib/components/Select/Select.types.ts +77 -54
- package/lib/components/Select/__tests__/Select.test.tsx +448 -7
- package/lib/components/Select/__tests__/__snapshots__/Select.test.tsx.snap +3 -3
- package/lib/components/Select/subcomponents/CustomOption.tsx +24 -10
- package/lib/components/Select/subcomponents/CustomSelect.tsx +333 -52
- package/lib/components/Select/subcomponents/FilterInput.tsx +80 -0
- package/lib/components/Select/subcomponents/NativeSelect.tsx +13 -1
- package/lib/components/Select/subcomponents/Panel.tsx +4 -5
- package/lib/components/Select/subcomponents/VisibleField.tsx +36 -24
- package/lib/components/Select/subcomponents/index.tsx +1 -0
- package/lib/components/Snackbar/Snackbar.stories.tsx +1 -1
- package/lib/components/Spinner/Spinner.stories.tsx +1 -1
- 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/Textarea/Textarea.stories.tsx +1 -1
- package/lib/components/Timepicker/Timepicker.stories.tsx +43 -0
- package/lib/components/Timepicker/Timepicker.tsx +100 -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/Toggle/Toggle.stories.tsx +1 -1
- package/lib/components/Tooltip/Tooltip.stories.tsx +1 -1
- package/lib/components/WeekPicker/WeekPicker.stories.tsx +147 -0
- package/lib/components/WeekPicker/WeekPicker.tsx +26 -0
- package/lib/components/WeekPicker/WeekPicker.types.ts +21 -0
- package/lib/components/WeekPicker/index.ts +2 -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/common/Common.mdx +1 -1
- package/lib/components/index.ts +28 -2
- package/lib/hooks/index.ts +2 -0
- package/lib/hooks/useFocusTrap.ts +159 -0
- package/lib/index.ts +2 -0
- package/lib/theme/defaultTheme.ts +7 -0
- package/lib/utils/__tests__/announce.test.ts +121 -0
- package/lib/utils/__tests__/capitalise.test.ts +40 -0
- package/lib/utils/announce.ts +134 -0
- package/lib/utils/capitalise.ts +4 -0
- package/lib/utils/index.ts +1 -0
- package/package.json +3 -6
- package/dist/components/Datepicker/subcomponents/NativeDatepicker.d.ts +0 -6
- package/lib/components/Accordion/Accordion.stories.tsx.NOT_READY +0 -93
- package/lib/components/Field/__tests__/__snapshots__/Field.test.tsx.snap +0 -300
- /package/dist/components/{Datepicker/utils/dateToLocaleISOString/dateToLocaleISOString.test.d.ts → FileInput/__tests__/FileInput.test.d.ts} +0 -0
- /package/lib/components/{Datepicker → NativeDatepicker}/utils/dateToLocaleISOString/dateToLocaleISOString.test.ts +0 -0
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { describe, test, expect, vi } from 'vitest';
|
|
1
|
+
import { describe, test, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
3
4
|
import { ThemeContextProvider } from '../../../theme';
|
|
4
5
|
import Datepicker from '..';
|
|
6
|
+
import announce from '../../../utils/announce';
|
|
7
|
+
|
|
8
|
+
// Mock the announce utility
|
|
9
|
+
vi.mock('../../../utils/announce');
|
|
5
10
|
|
|
6
11
|
const defaultTestId = 'ucl-uikit-datepicker';
|
|
7
12
|
|
|
@@ -14,8 +19,11 @@ const customRender = (ui: React.ReactElement) => {
|
|
|
14
19
|
};
|
|
15
20
|
|
|
16
21
|
describe('Datepicker', () => {
|
|
17
|
-
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
vi.clearAllMocks();
|
|
24
|
+
});
|
|
18
25
|
|
|
26
|
+
// #region Snapshot tests
|
|
19
27
|
test('Snapshot: no date provided', () => {
|
|
20
28
|
const renderResult = customRender(<Datepicker />);
|
|
21
29
|
expect(renderResult.container.firstChild).toMatchSnapshot();
|
|
@@ -23,13 +31,13 @@ describe('Datepicker', () => {
|
|
|
23
31
|
|
|
24
32
|
test('Snapshot: with date provided', () => {
|
|
25
33
|
const renderResult = customRender(
|
|
26
|
-
<Datepicker value={new Date(
|
|
34
|
+
<Datepicker value={new Date(2025, 2, 10)} />
|
|
27
35
|
);
|
|
28
36
|
expect(renderResult.container.firstChild).toMatchSnapshot();
|
|
29
37
|
});
|
|
38
|
+
// #endregion
|
|
30
39
|
|
|
31
|
-
//
|
|
32
|
-
|
|
40
|
+
// #region Functional tests
|
|
33
41
|
test('Can be found via default test ID', () => {
|
|
34
42
|
customRender(<Datepicker />);
|
|
35
43
|
const datepicker = screen.getByTestId(defaultTestId);
|
|
@@ -43,8 +51,55 @@ describe('Datepicker', () => {
|
|
|
43
51
|
expect(datepicker).toBeInTheDocument();
|
|
44
52
|
});
|
|
45
53
|
|
|
54
|
+
test('Parses date on ENTER key press', async () => {
|
|
55
|
+
const onValueChange = vi.fn();
|
|
56
|
+
const user = userEvent.setup();
|
|
57
|
+
customRender(<Datepicker onValueChange={onValueChange} />);
|
|
58
|
+
|
|
59
|
+
// Internal <input> (the user types into)
|
|
60
|
+
const datepickerInput = screen.getByRole('textbox');
|
|
61
|
+
await user.type(datepickerInput, '10/03/2025');
|
|
62
|
+
// Internal input holds typed value before parsing
|
|
63
|
+
expect(datepickerInput).toHaveValue('10/03/2025');
|
|
64
|
+
|
|
65
|
+
// Wait for ENTER key press to trigger parsing
|
|
66
|
+
expect(onValueChange).not.toHaveBeenCalled();
|
|
67
|
+
|
|
68
|
+
// Trigger user-entered date parsing
|
|
69
|
+
await user.keyboard('{Enter}');
|
|
70
|
+
|
|
71
|
+
expect(onValueChange).toHaveBeenCalledWith(
|
|
72
|
+
new Date('2025-03-10'),
|
|
73
|
+
expect.anything() // 2nd parameter is the event object (which we're not testing here)
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('Parses date on TAB key press', async () => {
|
|
78
|
+
const onValueChange = vi.fn();
|
|
79
|
+
const user = userEvent.setup();
|
|
80
|
+
customRender(<Datepicker onValueChange={onValueChange} />);
|
|
81
|
+
|
|
82
|
+
// Internal <input> (the user types into)
|
|
83
|
+
const datepickerInput = screen.getByRole('textbox');
|
|
84
|
+
await user.type(datepickerInput, '10/03/2025');
|
|
85
|
+
// Internal input holds typed value before parsing
|
|
86
|
+
expect(datepickerInput).toHaveValue('10/03/2025');
|
|
87
|
+
|
|
88
|
+
// Wait for TAB key press to trigger parsing
|
|
89
|
+
expect(onValueChange).not.toHaveBeenCalled();
|
|
90
|
+
|
|
91
|
+
// Trigger user-entered date parsing
|
|
92
|
+
await user.keyboard('{Tab}');
|
|
93
|
+
|
|
94
|
+
expect(onValueChange).toHaveBeenCalledWith(
|
|
95
|
+
new Date('2025-03-10'),
|
|
96
|
+
expect.anything() // 2nd parameter is the event object (which we're not testing here)
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
46
100
|
test('Input value reflects the date', () => {
|
|
47
|
-
|
|
101
|
+
// 10th March 2025 (2 is March, because month is 0-indexed in Date constructor)
|
|
102
|
+
customRender(<Datepicker value={new Date(2025, 2, 10)} />);
|
|
48
103
|
const input = screen.getByTestId('ucl-uikit-datepicker__input');
|
|
49
104
|
expect(input).toHaveValue('10/03/2025');
|
|
50
105
|
});
|
|
@@ -66,7 +121,7 @@ describe('Datepicker', () => {
|
|
|
66
121
|
const onValueChange = vi.fn();
|
|
67
122
|
customRender(
|
|
68
123
|
<Datepicker
|
|
69
|
-
value={new Date(
|
|
124
|
+
value={new Date(2025, 0, 10)} // Before minDate (0 is January, because month is 0-indexed in Date constructor)
|
|
70
125
|
minDate='2025-02-01'
|
|
71
126
|
onValueChange={onValueChange}
|
|
72
127
|
/>
|
|
@@ -78,11 +133,98 @@ describe('Datepicker', () => {
|
|
|
78
133
|
const onValueChange = vi.fn();
|
|
79
134
|
customRender(
|
|
80
135
|
<Datepicker
|
|
81
|
-
value={new Date(
|
|
136
|
+
value={new Date(2025, 2, 10)} // After maxDate (2 is March, because month is 0-indexed in Date constructor)
|
|
82
137
|
maxDate='2025-01-31'
|
|
83
138
|
onValueChange={onValueChange}
|
|
84
139
|
/>
|
|
85
140
|
);
|
|
86
141
|
expect(onValueChange).toHaveBeenCalledWith(null);
|
|
87
142
|
});
|
|
143
|
+
|
|
144
|
+
test('Input can receive additional HTML attributes via inputProps', () => {
|
|
145
|
+
customRender(
|
|
146
|
+
<Datepicker
|
|
147
|
+
inputProps={{
|
|
148
|
+
'aria-describedby': 'customValue',
|
|
149
|
+
'aria-required': 'true',
|
|
150
|
+
}}
|
|
151
|
+
/>
|
|
152
|
+
);
|
|
153
|
+
const input = screen.getByTestId('ucl-uikit-datepicker__input');
|
|
154
|
+
expect(input).toHaveAttribute('aria-describedby', 'customValue');
|
|
155
|
+
expect(input).toHaveAttribute('aria-required', 'true');
|
|
156
|
+
});
|
|
157
|
+
// #endregion
|
|
158
|
+
|
|
159
|
+
// #region Accessibility tests
|
|
160
|
+
test("Input can be found via getByRole('textbox')", () => {
|
|
161
|
+
customRender(<Datepicker />);
|
|
162
|
+
const input = screen.getByRole('textbox');
|
|
163
|
+
expect(input).toBeInTheDocument();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('Announces date on input text parse', () => {
|
|
167
|
+
customRender(<Datepicker />);
|
|
168
|
+
const input = screen.getByTestId('ucl-uikit-datepicker__input');
|
|
169
|
+
fireEvent.change(input, { target: { value: '15/03/2026' } });
|
|
170
|
+
fireEvent.keyDown(input, { key: 'Enter' });
|
|
171
|
+
|
|
172
|
+
expect(announce).toHaveBeenCalledWith(
|
|
173
|
+
'Selected date ' +
|
|
174
|
+
// March is month 2 in 0-indexed Date constructor
|
|
175
|
+
new Date(2026, 2, 15).toLocaleDateString('en-GB', {
|
|
176
|
+
weekday: 'long',
|
|
177
|
+
day: 'numeric',
|
|
178
|
+
month: 'long',
|
|
179
|
+
year: 'numeric',
|
|
180
|
+
})
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("Announces picked date on Calendar 'pick'", () => {
|
|
185
|
+
customRender(<Datepicker value={new Date(2026, 2, 10)} />);
|
|
186
|
+
// TODO: Separate this button from the 'clear date' button - [Datepicker_improv_005]
|
|
187
|
+
const openCalendarButton = screen.getByTestId(
|
|
188
|
+
'ucl-uikit-datepicker__visible-field-icon-button'
|
|
189
|
+
);
|
|
190
|
+
fireEvent.click(openCalendarButton);
|
|
191
|
+
|
|
192
|
+
const days = screen.getAllByTestId('ucl-uikit-calendar__day');
|
|
193
|
+
// Pick 15th March 2026:
|
|
194
|
+
// 6 days of previous month
|
|
195
|
+
// + 15 days of current month (1st to 15th)
|
|
196
|
+
// - 1 because of 0-indexing in the array
|
|
197
|
+
const dateArrayIndex = 6 + 15 - 1;
|
|
198
|
+
fireEvent.click(days[dateArrayIndex]);
|
|
199
|
+
|
|
200
|
+
expect(announce).toHaveBeenCalledWith(
|
|
201
|
+
'Selected date ' +
|
|
202
|
+
// March is month 2 in 0-indexed Date constructor
|
|
203
|
+
new Date(2026, 2, 15).toLocaleDateString('en-GB', {
|
|
204
|
+
weekday: 'long',
|
|
205
|
+
day: 'numeric',
|
|
206
|
+
month: 'long',
|
|
207
|
+
year: 'numeric',
|
|
208
|
+
})
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('Input can be found via label text when associated via id', () => {
|
|
213
|
+
customRender(
|
|
214
|
+
<>
|
|
215
|
+
<label htmlFor='my-datepicker'>Date of event</label>
|
|
216
|
+
<Datepicker inputProps={{ id: 'my-datepicker' }} />
|
|
217
|
+
</>
|
|
218
|
+
);
|
|
219
|
+
const input = screen.getByLabelText('Date of event');
|
|
220
|
+
expect(input).toHaveAttribute('data-testid', 'ucl-uikit-datepicker__input');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('Custom aria-label can be set via inputProps', () => {
|
|
224
|
+
const customAriaLabel = 'Custom aria label';
|
|
225
|
+
customRender(<Datepicker inputProps={{ 'aria-label': customAriaLabel }} />);
|
|
226
|
+
const input = screen.getByTestId('ucl-uikit-datepicker__input');
|
|
227
|
+
expect(input).toHaveAttribute('aria-label', customAriaLabel);
|
|
228
|
+
});
|
|
229
|
+
// #endregion
|
|
88
230
|
});
|
|
@@ -10,7 +10,6 @@ exports[`Datepicker > Snapshot: no date provided 1`] = `
|
|
|
10
10
|
data-testid="ucl-uikit-datepicker__visible-field"
|
|
11
11
|
>
|
|
12
12
|
<input
|
|
13
|
-
aria-label="Currently selected date: "
|
|
14
13
|
class="ucl-uikit-datepicker__input css-1k3pose"
|
|
15
14
|
data-testid="ucl-uikit-datepicker__input"
|
|
16
15
|
inputmode="numeric"
|
|
@@ -19,7 +18,11 @@ exports[`Datepicker > Snapshot: no date provided 1`] = `
|
|
|
19
18
|
value=""
|
|
20
19
|
/>
|
|
21
20
|
<div
|
|
22
|
-
|
|
21
|
+
aria-label="Open calendar"
|
|
22
|
+
class="css-nnfy6l"
|
|
23
|
+
data-testid="ucl-uikit-datepicker__visible-field-icon-button"
|
|
24
|
+
role="button"
|
|
25
|
+
tabindex="0"
|
|
23
26
|
>
|
|
24
27
|
<svg
|
|
25
28
|
class="ucl-uikit-icon css-1u4xgls"
|
|
@@ -76,7 +79,6 @@ exports[`Datepicker > Snapshot: with date provided 1`] = `
|
|
|
76
79
|
data-testid="ucl-uikit-datepicker__visible-field"
|
|
77
80
|
>
|
|
78
81
|
<input
|
|
79
|
-
aria-label="Currently selected date: 10/03/2025"
|
|
80
82
|
class="ucl-uikit-datepicker__input css-1k3pose"
|
|
81
83
|
data-testid="ucl-uikit-datepicker__input"
|
|
82
84
|
inputmode="numeric"
|
|
@@ -85,7 +87,11 @@ exports[`Datepicker > Snapshot: with date provided 1`] = `
|
|
|
85
87
|
value="10/03/2025"
|
|
86
88
|
/>
|
|
87
89
|
<div
|
|
88
|
-
|
|
90
|
+
aria-label="Open calendar"
|
|
91
|
+
class="css-nnfy6l"
|
|
92
|
+
data-testid="ucl-uikit-datepicker__visible-field-icon-button"
|
|
93
|
+
role="button"
|
|
94
|
+
tabindex="0"
|
|
89
95
|
>
|
|
90
96
|
<svg
|
|
91
97
|
class="ucl-uikit-icon css-1u4xgls"
|
|
@@ -3,8 +3,10 @@ import { css, cx } from '@emotion/css';
|
|
|
3
3
|
import { VisibleField, Panel } from '.';
|
|
4
4
|
import { Calendar } from '../..';
|
|
5
5
|
import { parseInputValue } from '../utils';
|
|
6
|
+
import announce from '../../../utils/announce';
|
|
6
7
|
import type { DatepickerValue } from '../Datepicker.types';
|
|
7
8
|
import type { CalendarEvent, AcademicWeek } from '../../Calendar';
|
|
9
|
+
import type { InputProps } from './DatepickerInput';
|
|
8
10
|
|
|
9
11
|
interface CustomDatepickerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
10
12
|
value?: DatepickerValue;
|
|
@@ -15,12 +17,14 @@ interface CustomDatepickerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
15
17
|
minDate?: string | null; // ISO date string: YYYY-MM-DD
|
|
16
18
|
maxDate?: string | null; // ISO date string: YYYY-MM-DD
|
|
17
19
|
disabled?: boolean;
|
|
20
|
+
clearable?: boolean;
|
|
18
21
|
events?: CalendarEvent[];
|
|
19
22
|
showAcademicWeeks?: boolean;
|
|
20
23
|
academicWeeks?: AcademicWeek[];
|
|
21
24
|
testId?: string;
|
|
22
25
|
ref?: React.RefObject<HTMLDivElement>;
|
|
23
26
|
inputRef?: React.RefObject<HTMLInputElement>;
|
|
27
|
+
inputProps?: InputProps;
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
const NAME = 'ucl-uikit-datepicker';
|
|
@@ -31,6 +35,7 @@ const CustomDatepicker = ({
|
|
|
31
35
|
minDate,
|
|
32
36
|
maxDate,
|
|
33
37
|
disabled = false,
|
|
38
|
+
clearable = false,
|
|
34
39
|
events,
|
|
35
40
|
showAcademicWeeks,
|
|
36
41
|
academicWeeks = [],
|
|
@@ -38,6 +43,7 @@ const CustomDatepicker = ({
|
|
|
38
43
|
className,
|
|
39
44
|
ref,
|
|
40
45
|
inputRef,
|
|
46
|
+
inputProps = {},
|
|
41
47
|
...props
|
|
42
48
|
}: CustomDatepickerProps) => {
|
|
43
49
|
if (value && isNaN(value.getTime())) {
|
|
@@ -75,9 +81,18 @@ const CustomDatepicker = ({
|
|
|
75
81
|
|
|
76
82
|
const handleParseInput = (event: React.SyntheticEvent) => {
|
|
77
83
|
// `parseInputValue` checks the date is valid and within min/max range.
|
|
78
|
-
const
|
|
79
|
-
if (
|
|
80
|
-
|
|
84
|
+
const parsedDate = parseInputValue(inputValue, minDate, maxDate);
|
|
85
|
+
if (parsedDate) {
|
|
86
|
+
announce(
|
|
87
|
+
'Selected date ' +
|
|
88
|
+
parsedDate.toLocaleDateString('en-GB', {
|
|
89
|
+
weekday: 'long',
|
|
90
|
+
day: 'numeric',
|
|
91
|
+
month: 'long',
|
|
92
|
+
year: 'numeric',
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
onValueChange(parsedDate, event);
|
|
81
96
|
} else {
|
|
82
97
|
resetField();
|
|
83
98
|
}
|
|
@@ -91,7 +106,8 @@ const CustomDatepicker = ({
|
|
|
91
106
|
effectiveInputRef.current?.blur();
|
|
92
107
|
setPanelOpen(false);
|
|
93
108
|
} else if (event.key === 'Tab') {
|
|
94
|
-
|
|
109
|
+
// Parse input on TAB (same as ENTER) for better accessibility
|
|
110
|
+
handleParseInput(event);
|
|
95
111
|
setPanelOpen(false);
|
|
96
112
|
}
|
|
97
113
|
};
|
|
@@ -149,6 +165,15 @@ const CustomDatepicker = ({
|
|
|
149
165
|
event?: React.SyntheticEvent
|
|
150
166
|
) => {
|
|
151
167
|
if (onValueChange) {
|
|
168
|
+
announce(
|
|
169
|
+
'Selected date ' +
|
|
170
|
+
date?.toLocaleDateString('en-GB', {
|
|
171
|
+
weekday: 'long',
|
|
172
|
+
day: 'numeric',
|
|
173
|
+
month: 'long',
|
|
174
|
+
year: 'numeric',
|
|
175
|
+
})
|
|
176
|
+
);
|
|
152
177
|
onValueChange(date, event);
|
|
153
178
|
}
|
|
154
179
|
setPanelOpen(false);
|
|
@@ -164,6 +189,11 @@ const CustomDatepicker = ({
|
|
|
164
189
|
setPanelOpen((prev) => !prev);
|
|
165
190
|
};
|
|
166
191
|
|
|
192
|
+
const handleClear = (event: React.SyntheticEvent) => {
|
|
193
|
+
event.stopPropagation(); // Prevent opening the calendar panel
|
|
194
|
+
onValueChange(null);
|
|
195
|
+
};
|
|
196
|
+
|
|
167
197
|
const baseStyle = css`
|
|
168
198
|
width: 196px;
|
|
169
199
|
height: 48px;
|
|
@@ -171,7 +201,7 @@ const CustomDatepicker = ({
|
|
|
171
201
|
position: relative;
|
|
172
202
|
`;
|
|
173
203
|
|
|
174
|
-
const style = cx(NAME,
|
|
204
|
+
const style = cx(NAME, baseStyle, className);
|
|
175
205
|
|
|
176
206
|
return (
|
|
177
207
|
<div
|
|
@@ -186,8 +216,12 @@ const CustomDatepicker = ({
|
|
|
186
216
|
onInputKeyDown={handleInputKeyDown}
|
|
187
217
|
onInputFocus={handleShowPanel}
|
|
188
218
|
onButtonClick={handleTogglePanel}
|
|
219
|
+
onClear={handleClear}
|
|
189
220
|
disabled={disabled}
|
|
221
|
+
clearable={clearable}
|
|
222
|
+
hasValue={!!value}
|
|
190
223
|
inputRef={effectiveInputRef}
|
|
224
|
+
inputProps={inputProps}
|
|
191
225
|
/>
|
|
192
226
|
{panelOpen && (
|
|
193
227
|
<Panel>
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
import { css, cx } from '@emotion/css';
|
|
2
2
|
import { useTheme } from '../../../theme';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* HTML attributes that consumers can pass to the underlying `<input>` via the
|
|
6
|
+
* `inputProps` prop on `<Datepicker>`.
|
|
7
|
+
*
|
|
8
|
+
* Controlled and internally-managed attributes are omitted to prevent conflicts.
|
|
9
|
+
*/
|
|
10
|
+
export type InputProps = Omit<
|
|
11
|
+
React.InputHTMLAttributes<HTMLInputElement>,
|
|
12
|
+
'value' | 'onChange' | 'onKeyDown' | 'onFocus' | 'disabled' | 'type'
|
|
13
|
+
> & {
|
|
14
|
+
testId?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Internal props for `<DatepickerInput>`, combining consumer-facing
|
|
19
|
+
* input attributes with controlled props managed by `<VisibleField>`.
|
|
20
|
+
*/
|
|
21
|
+
interface DatepickerInputProps extends InputProps {
|
|
5
22
|
value: string;
|
|
6
23
|
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
7
24
|
onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
@@ -13,12 +30,12 @@ interface DatepickerInputProps {
|
|
|
13
30
|
const NAME = 'ucl-uikit-datepicker__input';
|
|
14
31
|
|
|
15
32
|
const DatepickerInput = ({
|
|
16
|
-
value,
|
|
17
|
-
onChange,
|
|
18
|
-
onKeyDown,
|
|
19
|
-
onFocus,
|
|
20
33
|
disabled,
|
|
21
|
-
|
|
34
|
+
placeholder = 'DD/MM/YYYY',
|
|
35
|
+
inputMode = 'numeric',
|
|
36
|
+
testId = NAME,
|
|
37
|
+
className,
|
|
38
|
+
...props
|
|
22
39
|
}: DatepickerInputProps) => {
|
|
23
40
|
const [theme] = useTheme();
|
|
24
41
|
|
|
@@ -51,22 +68,18 @@ const DatepickerInput = ({
|
|
|
51
68
|
}
|
|
52
69
|
`;
|
|
53
70
|
|
|
54
|
-
const style = cx(NAME, baseStyle, disabled && disabledStyle);
|
|
71
|
+
const style = cx(NAME, baseStyle, disabled && disabledStyle, className);
|
|
55
72
|
|
|
56
73
|
return (
|
|
57
74
|
<input
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
75
|
+
placeholder={placeholder}
|
|
76
|
+
inputMode={inputMode}
|
|
77
|
+
data-testid={testId}
|
|
78
|
+
className={style}
|
|
79
|
+
{...props}
|
|
80
|
+
// Controlled attributes -- not exposed to consumers to prevent conflicts
|
|
62
81
|
disabled={disabled}
|
|
63
82
|
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
83
|
/>
|
|
71
84
|
);
|
|
72
85
|
};
|
|
@@ -7,7 +7,11 @@ interface PanelProps {
|
|
|
7
7
|
|
|
8
8
|
const NAME = 'ucl-uikit-datepicker__panel';
|
|
9
9
|
|
|
10
|
-
const Panel = ({
|
|
10
|
+
const Panel = ({
|
|
11
|
+
zIndex = 10,
|
|
12
|
+
children,
|
|
13
|
+
}: PanelProps) => {
|
|
14
|
+
|
|
11
15
|
const datepickerHeight = 48;
|
|
12
16
|
const gapFromDatepicker = 8;
|
|
13
17
|
|
|
@@ -27,6 +31,6 @@ const Panel = ({ zIndex = 10, children }: PanelProps) => {
|
|
|
27
31
|
{children}
|
|
28
32
|
</div>
|
|
29
33
|
);
|
|
30
|
-
}
|
|
34
|
+
}
|
|
31
35
|
|
|
32
36
|
export default Panel;
|
|
@@ -3,6 +3,7 @@ import { DatepickerInput } from './';
|
|
|
3
3
|
import { Icon } from '../../..';
|
|
4
4
|
import { useTheme } from '../../../theme';
|
|
5
5
|
import React from 'react';
|
|
6
|
+
import type { InputProps } from './DatepickerInput';
|
|
6
7
|
|
|
7
8
|
interface VisibleFieldProps {
|
|
8
9
|
inputValue: string;
|
|
@@ -10,8 +11,12 @@ interface VisibleFieldProps {
|
|
|
10
11
|
onInputKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
11
12
|
onInputFocus: () => void;
|
|
12
13
|
onButtonClick: () => void;
|
|
14
|
+
onClear: (event: React.SyntheticEvent) => void;
|
|
13
15
|
disabled: boolean;
|
|
16
|
+
clearable?: boolean;
|
|
17
|
+
hasValue?: boolean;
|
|
14
18
|
inputRef: React.RefObject<HTMLInputElement | null>;
|
|
19
|
+
inputProps: InputProps;
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
const NAME = 'ucl-uikit-datepicker__visible-field';
|
|
@@ -22,8 +27,12 @@ const VisibleField = ({
|
|
|
22
27
|
onInputKeyDown,
|
|
23
28
|
onInputFocus,
|
|
24
29
|
onButtonClick,
|
|
30
|
+
onClear,
|
|
25
31
|
disabled,
|
|
32
|
+
clearable = false,
|
|
33
|
+
hasValue = false,
|
|
26
34
|
inputRef,
|
|
35
|
+
inputProps,
|
|
27
36
|
}: VisibleFieldProps) => {
|
|
28
37
|
const [theme] = useTheme();
|
|
29
38
|
|
|
@@ -61,9 +70,15 @@ const VisibleField = ({
|
|
|
61
70
|
display: flex;
|
|
62
71
|
align-items: center;
|
|
63
72
|
justify-content: center;
|
|
64
|
-
padding: 0 16px
|
|
73
|
+
padding: 0 16px;
|
|
74
|
+
// padding: 0 16px 0 4px;
|
|
65
75
|
height: 100%;
|
|
66
76
|
cursor: pointer;
|
|
77
|
+
|
|
78
|
+
&:focus-visible {
|
|
79
|
+
outline: none;
|
|
80
|
+
box-shadow: ${theme.boxShadow.focus};
|
|
81
|
+
}
|
|
67
82
|
`;
|
|
68
83
|
|
|
69
84
|
// `min-width` here accounts for a recurring problem,
|
|
@@ -76,6 +91,9 @@ const VisibleField = ({
|
|
|
76
91
|
: '#8C8C8C'}; // TODO: Needs a design token
|
|
77
92
|
`;
|
|
78
93
|
|
|
94
|
+
// Determine which icon to show
|
|
95
|
+
const showClearIcon = clearable && hasValue && !disabled;
|
|
96
|
+
|
|
79
97
|
const style = cx(NAME, baseStyle, disabled && disabledStyle);
|
|
80
98
|
|
|
81
99
|
return (
|
|
@@ -90,12 +108,31 @@ const VisibleField = ({
|
|
|
90
108
|
onFocus={onInputFocus}
|
|
91
109
|
disabled={disabled}
|
|
92
110
|
ref={inputRef}
|
|
111
|
+
{...inputProps}
|
|
93
112
|
/>
|
|
94
113
|
<div
|
|
95
|
-
onClick={onButtonClick}
|
|
114
|
+
onClick={showClearIcon ? onClear : onButtonClick}
|
|
115
|
+
onKeyDown={(e) => {
|
|
116
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
117
|
+
e.preventDefault();
|
|
118
|
+
if (showClearIcon) {
|
|
119
|
+
onClear(e);
|
|
120
|
+
} else {
|
|
121
|
+
onButtonClick();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}}
|
|
96
125
|
className={iconButtonStyle}
|
|
126
|
+
tabIndex={0}
|
|
127
|
+
role='button'
|
|
128
|
+
aria-label={showClearIcon ? 'Clear date' : 'Open calendar'}
|
|
129
|
+
data-testid={`${NAME}-icon-button`}
|
|
97
130
|
>
|
|
98
|
-
|
|
131
|
+
{showClearIcon ? (
|
|
132
|
+
<Icon.X className={iconStyle} />
|
|
133
|
+
) : (
|
|
134
|
+
<Icon.Calendar className={iconStyle} />
|
|
135
|
+
)}
|
|
99
136
|
</div>
|
|
100
137
|
</div>
|
|
101
138
|
);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { default as CustomDatepicker } from './CustomDatepicker';
|
|
2
|
-
export { default as NativeDatepicker } from './NativeDatepicker';
|
|
3
2
|
export { default as VisibleField } from './VisibleField';
|
|
4
3
|
export { default as DatepickerInput } from './DatepickerInput';
|
|
5
4
|
export { default as Panel } from './Panel';
|
|
@@ -4,9 +4,12 @@ import React, {
|
|
|
4
4
|
memo,
|
|
5
5
|
useEffect,
|
|
6
6
|
useRef,
|
|
7
|
+
useContext,
|
|
7
8
|
} from 'react';
|
|
8
9
|
import { css, cx } from '@emotion/css';
|
|
9
10
|
import useTheme from '../../theme/useTheme';
|
|
11
|
+
import { useFocusTrap } from '../../hooks/useFocusTrap';
|
|
12
|
+
import { DialogContext } from './Dialog';
|
|
10
13
|
|
|
11
14
|
export const NAME = 'ucl-uikit-base-dialog';
|
|
12
15
|
export const SMALL_WIDTH = 495;
|
|
@@ -19,7 +22,14 @@ export interface BaseDialogProps extends HTMLAttributes<HTMLDialogElement> {
|
|
|
19
22
|
modal?: boolean;
|
|
20
23
|
closeOnClickOutside?: boolean;
|
|
21
24
|
closeOnClickOutsideStopPropagation?: boolean;
|
|
22
|
-
|
|
25
|
+
nonModalCloseOnEscape?: boolean;
|
|
26
|
+
onClose?: (ev: React.MouseEvent | KeyboardEvent) => void;
|
|
27
|
+
// Focus trap related props
|
|
28
|
+
initialFocusRef?: React.RefObject<HTMLElement>;
|
|
29
|
+
finalFocusRef?: React.RefObject<HTMLElement>;
|
|
30
|
+
disableFocusTrap?: boolean;
|
|
31
|
+
restoreFocus?: boolean;
|
|
32
|
+
skipCloseOnInitialFocus?: boolean;
|
|
23
33
|
testId?: string;
|
|
24
34
|
}
|
|
25
35
|
|
|
@@ -29,10 +39,16 @@ const BaseDialog = ({
|
|
|
29
39
|
modal = true,
|
|
30
40
|
closeOnClickOutside = true,
|
|
31
41
|
closeOnClickOutsideStopPropagation = true,
|
|
42
|
+
nonModalCloseOnEscape = false,
|
|
32
43
|
onClose,
|
|
33
|
-
testId = NAME,
|
|
34
44
|
className,
|
|
35
45
|
children,
|
|
46
|
+
initialFocusRef,
|
|
47
|
+
finalFocusRef,
|
|
48
|
+
disableFocusTrap = false,
|
|
49
|
+
restoreFocus = true,
|
|
50
|
+
skipCloseOnInitialFocus = false,
|
|
51
|
+
testId = NAME,
|
|
36
52
|
...props
|
|
37
53
|
}: BaseDialogProps) => {
|
|
38
54
|
const width = {
|
|
@@ -46,6 +62,20 @@ const BaseDialog = ({
|
|
|
46
62
|
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
47
63
|
const previousActiveElement = useRef<HTMLElement | null>(null);
|
|
48
64
|
|
|
65
|
+
const context = useContext(DialogContext);
|
|
66
|
+
const dialogHeaderId = context?.dialogHeaderId;
|
|
67
|
+
const dialogBodyId = context?.dialogBodyId;
|
|
68
|
+
|
|
69
|
+
// Use the focus trap hook
|
|
70
|
+
useFocusTrap({
|
|
71
|
+
isActive: open && modal && !disableFocusTrap,
|
|
72
|
+
containerRef: dialogRef,
|
|
73
|
+
initialFocusRef,
|
|
74
|
+
finalFocusRef,
|
|
75
|
+
restoreFocus,
|
|
76
|
+
skipFirstFocusable: skipCloseOnInitialFocus,
|
|
77
|
+
});
|
|
78
|
+
|
|
49
79
|
const hideBodyScroll = css`
|
|
50
80
|
overflow: hidden;
|
|
51
81
|
`;
|
|
@@ -75,9 +105,27 @@ const BaseDialog = ({
|
|
|
75
105
|
}
|
|
76
106
|
} else if (!open && dialogElement.hasAttribute('open')) {
|
|
77
107
|
dialogElement.close();
|
|
78
|
-
|
|
108
|
+
// Focus restoration is handled by the focus trap hook for modal dialogs,
|
|
109
|
+
// but we keep the fallback for non-modal dialogs or when focus trap is disabled
|
|
110
|
+
if ((!modal || disableFocusTrap) && restoreFocus) {
|
|
111
|
+
previousActiveElement.current?.focus();
|
|
112
|
+
}
|
|
79
113
|
}
|
|
80
|
-
}, [open, modal]);
|
|
114
|
+
}, [open, modal, disableFocusTrap, restoreFocus]);
|
|
115
|
+
|
|
116
|
+
// Handle Escape key to close dialog
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
if (!open || modal || !nonModalCloseOnEscape) return;
|
|
119
|
+
|
|
120
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
121
|
+
if (event.key === 'Escape' && onClose) {
|
|
122
|
+
onClose(event);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
127
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
128
|
+
}, [open, modal, nonModalCloseOnEscape, onClose]);
|
|
81
129
|
|
|
82
130
|
const handleClick = useCallback(
|
|
83
131
|
(ev: React.MouseEvent<HTMLDialogElement>) => {
|
|
@@ -150,6 +198,9 @@ const BaseDialog = ({
|
|
|
150
198
|
data-testid={testId}
|
|
151
199
|
onClick={handleClick}
|
|
152
200
|
onClose={handleDialogClose}
|
|
201
|
+
aria-modal={modal ? 'true' : 'false'}
|
|
202
|
+
aria-labelledby={dialogHeaderId}
|
|
203
|
+
aria-describedby={dialogBodyId}
|
|
153
204
|
{...props}
|
|
154
205
|
>
|
|
155
206
|
{children}
|