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.
Files changed (301) hide show
  1. package/README.md +4 -2
  2. package/dist/components/Accordion/Accordion.Heading.d.ts +4 -4
  3. package/dist/components/Accordion/Accordion.Panel.d.ts +2 -2
  4. package/dist/components/Accordion/Accordion.d.ts +1 -1
  5. package/dist/components/Accordion/Accordion.stories.d.ts +57 -0
  6. package/dist/components/Accordion/index.d.ts +2 -0
  7. package/dist/components/Avatar/Avatar.stories.d.ts +107 -1
  8. package/dist/components/Badge/Badge.d.ts +6 -0
  9. package/dist/components/Badge/Badge.stories.d.ts +15 -0
  10. package/dist/components/Badge/index.d.ts +2 -0
  11. package/dist/components/Button/Button.d.ts +3 -1
  12. package/dist/components/Calendar/index.d.ts +1 -1
  13. package/dist/components/CookieNotice/CookieNotice.d.ts +16 -0
  14. package/dist/components/CookieNotice/index.d.ts +2 -0
  15. package/dist/components/Datepicker/Datepicker.d.ts +1 -1
  16. package/dist/components/Datepicker/Datepicker.stories.d.ts +4 -3
  17. package/dist/components/Datepicker/Datepicker.types.d.ts +4 -5
  18. package/dist/components/Datepicker/subcomponents/CustomDatepicker.d.ts +4 -1
  19. package/dist/components/Datepicker/subcomponents/DatepickerInput.d.ts +15 -2
  20. package/dist/components/Datepicker/subcomponents/Panel.d.ts +1 -1
  21. package/dist/components/Datepicker/subcomponents/VisibleField.d.ts +6 -1
  22. package/dist/components/Datepicker/subcomponents/index.d.ts +0 -1
  23. package/dist/components/Datepicker/utils/index.d.ts +0 -1
  24. package/dist/components/Dialog/BaseDialog.d.ts +8 -2
  25. package/dist/components/Dialog/Dialog.d.ts +2 -0
  26. package/dist/components/FileInput/FileInput.d.ts +8 -0
  27. package/dist/components/FileInput/FileInput.stories.d.ts +16 -0
  28. package/dist/components/FileInput/index.d.ts +2 -0
  29. package/dist/components/Header/Header.d.ts +7 -1
  30. package/dist/components/Header/Header.stories.d.ts +40 -0
  31. package/dist/components/Heading/Heading.d.ts +1 -1
  32. package/dist/components/Link/BaseLink.d.ts +10 -0
  33. package/dist/components/Link/Link.d.ts +5 -10
  34. package/dist/components/Link/Link.stories.d.ts +1 -1
  35. package/dist/components/Link/index.d.ts +1 -1
  36. package/dist/components/Main/Main.d.ts +21 -0
  37. package/dist/components/Main/Main.stories.d.ts +15 -0
  38. package/dist/components/Main/__tests__/Main.test.d.ts +1 -0
  39. package/dist/components/Main/index.d.ts +2 -0
  40. package/dist/components/Menu/MenuContent.d.ts +1 -1
  41. package/dist/components/Menu/MenuItem.d.ts +2 -0
  42. package/dist/components/Menu/MenuSection.d.ts +1 -1
  43. package/dist/components/NativeDatepicker/NativeDatepicker.d.ts +3 -0
  44. package/dist/components/NativeDatepicker/NativeDatepicker.stories.d.ts +36 -0
  45. package/dist/components/NativeDatepicker/NativeDatepicker.types.d.ts +10 -0
  46. package/dist/components/NativeDatepicker/index.d.ts +2 -0
  47. package/dist/components/{Datepicker → NativeDatepicker}/utils/dateToLocaleISOString/dateToLocaleISOString.d.ts +1 -1
  48. package/dist/components/NativeDatepicker/utils/dateToLocaleISOString/dateToLocaleISOString.test.d.ts +1 -0
  49. package/dist/components/NativeDatepicker/utils/index.d.ts +1 -0
  50. package/dist/components/Search/Search.d.ts +16 -0
  51. package/dist/components/Search/Search.stories.d.ts +34 -0
  52. package/dist/components/Search/__tests__/Search.test.d.ts +1 -0
  53. package/dist/components/Search/index.d.ts +2 -0
  54. package/dist/components/Select/Select.d.ts +1 -1
  55. package/dist/components/Select/Select.stories.d.ts +157 -9
  56. package/dist/components/Select/Select.types.d.ts +66 -32
  57. package/dist/components/Select/subcomponents/CustomOption.d.ts +1 -1
  58. package/dist/components/Select/subcomponents/CustomSelect.d.ts +3 -3
  59. package/dist/components/Select/subcomponents/FilterInput.d.ts +14 -0
  60. package/dist/components/Select/subcomponents/NativeSelect.d.ts +5 -1
  61. package/dist/components/Select/subcomponents/Panel.d.ts +1 -1
  62. package/dist/components/Select/subcomponents/VisibleField.d.ts +6 -4
  63. package/dist/components/Select/subcomponents/index.d.ts +1 -0
  64. package/dist/components/StandaloneLink/StandaloneLink.d.ts +12 -0
  65. package/dist/components/StandaloneLink/StandaloneLink.stories.d.ts +13 -0
  66. package/dist/components/StandaloneLink/__tests__/StandaloneLink.test.d.ts +1 -0
  67. package/dist/components/StandaloneLink/index.d.ts +2 -0
  68. package/dist/components/Table/Table.d.ts +10 -8
  69. package/dist/components/Table/Table.stories.d.ts +21 -0
  70. package/dist/components/Table/Table.types.d.ts +11 -0
  71. package/dist/components/Table/__tests__/Table.test.d.ts +1 -0
  72. package/dist/components/Table/index.d.ts +2 -1
  73. package/dist/components/Table/subcomponents/Body.d.ts +4 -0
  74. package/dist/components/Table/subcomponents/Cell/Cell.d.ts +12 -0
  75. package/dist/components/Table/subcomponents/Cell/Cell.stories.d.ts +313 -0
  76. package/dist/components/Table/subcomponents/Cell/CellContent.d.ts +10 -0
  77. package/dist/components/Table/subcomponents/Cell/__tests__/Cell.test.d.ts +1 -0
  78. package/dist/components/Table/subcomponents/Head.d.ts +4 -0
  79. package/dist/components/Table/subcomponents/HeadCell/HeadCell.d.ts +13 -0
  80. package/dist/components/Table/subcomponents/HeadCell/HeadCell.stories.d.ts +312 -0
  81. package/dist/components/Table/subcomponents/HeadCell/HeadCellContent.d.ts +10 -0
  82. package/dist/components/Table/subcomponents/HeadCell/__tests__/HeadCell.test.d.ts +1 -0
  83. package/dist/components/Table/subcomponents/Row.d.ts +5 -0
  84. package/dist/components/Table/subcomponents/SortIcon.d.ts +7 -0
  85. package/dist/components/Table/subcomponents/index.d.ts +10 -0
  86. package/dist/components/Tabs/Tab.d.ts +1 -1
  87. package/dist/components/Tabs/TabContext.d.ts +1 -0
  88. package/dist/components/Tabs/Tabs.d.ts +3 -1
  89. package/dist/components/Tabs/Tabs.stories.d.ts +3 -0
  90. package/dist/components/Timepicker/Timepicker.d.ts +10 -0
  91. package/dist/components/Timepicker/Timepicker.stories.d.ts +7 -0
  92. package/dist/components/Timepicker/__tests__/Timepicker.test.d.ts +1 -0
  93. package/dist/components/Timepicker/index.d.ts +2 -0
  94. package/dist/components/Timepicker/utils/convertDateToTimeString.d.ts +2 -0
  95. package/dist/components/Timepicker/utils/convertDateToTimeString.test.d.ts +1 -0
  96. package/dist/components/Timepicker/utils/index.d.ts +1 -0
  97. package/dist/components/WeekPicker/WeekPicker.d.ts +3 -0
  98. package/dist/components/WeekPicker/WeekPicker.stories.d.ts +41 -0
  99. package/dist/components/WeekPicker/WeekPicker.types.d.ts +16 -0
  100. package/dist/components/WeekPicker/index.d.ts +2 -0
  101. package/dist/components/WeekPicker/subcomponents/CustomDatepicker.d.ts +17 -0
  102. package/dist/components/WeekPicker/subcomponents/DatepickerInput.d.ts +13 -0
  103. package/dist/components/WeekPicker/subcomponents/VisibleField.d.ts +15 -0
  104. package/dist/components/WeekPicker/subcomponents/index.d.ts +3 -0
  105. package/dist/components/index.d.ts +19 -0
  106. package/dist/hooks/index.d.ts +2 -0
  107. package/dist/hooks/useFocusTrap.d.ts +10 -0
  108. package/dist/index.d.ts +2 -0
  109. package/dist/index.js +6460 -4607
  110. package/dist/theme/defaultTheme.d.ts +7 -0
  111. package/dist/theme/useTheme.d.ts +14 -0
  112. package/dist/utils/__tests__/announce.test.d.ts +1 -0
  113. package/dist/utils/__tests__/capitalise.test.d.ts +1 -0
  114. package/dist/utils/announce.d.ts +6 -0
  115. package/dist/utils/capitalise.d.ts +2 -0
  116. package/dist/utils/index.d.ts +1 -0
  117. package/lib/components/Accordion/Accordion.Heading.tsx +27 -8
  118. package/lib/components/Accordion/Accordion.Panel.tsx +11 -3
  119. package/lib/components/Accordion/Accordion.stories.tsx +139 -0
  120. package/lib/components/Accordion/Accordion.tsx +10 -8
  121. package/lib/components/Accordion/__tests__/__snapshots__/Accordion.test.tsx.snap +7 -7
  122. package/lib/components/Accordion/index.ts +2 -0
  123. package/lib/components/Alert/Alert.stories.tsx +1 -1
  124. package/lib/components/Alert/Alert.tsx +7 -1
  125. package/lib/components/Alert/__tests__/__snapshots__/Alert.test.tsx.snap +4 -0
  126. package/lib/components/Avatar/Avatar.mdx +117 -0
  127. package/lib/components/Avatar/Avatar.stories.tsx +110 -2
  128. package/lib/components/Badge/Badge.stories.tsx +19 -0
  129. package/lib/components/Badge/Badge.tsx +48 -0
  130. package/lib/components/Badge/index.ts +2 -0
  131. package/lib/components/Blanket/Blanket.stories.tsx +1 -1
  132. package/lib/components/Breadcrumbs/__tests__/__snapshots__/Breadcrumbs.test.tsx.snap +4 -4
  133. package/lib/components/Button/Button.stories.tsx +1 -1
  134. package/lib/components/Button/Button.tsx +6 -2
  135. package/lib/components/Calendar/Calendar.stories.tsx +12 -32
  136. package/lib/components/Calendar/__tests__/Calendar.test.tsx +23 -15
  137. package/lib/components/Calendar/index.ts +1 -5
  138. package/lib/components/Calendar/subcomponents/AcademicWeeks.tsx +2 -1
  139. package/lib/components/Calendar/subcomponents/ColumnHeading.tsx +5 -1
  140. package/lib/components/Calendar/subcomponents/EventDot.tsx +2 -1
  141. package/lib/components/Calendar/subcomponents/Grid.tsx +0 -1
  142. package/lib/components/Calendar/subcomponents/index.ts +1 -1
  143. package/lib/components/Calendar/utils/getDatesForCalendarGrid/getDatesForCalendarGrid.ts +43 -11
  144. package/lib/components/Calendar/utils/normaliseMonth/normaliseMonth.test.ts +5 -5
  145. package/lib/components/CookieNotice/CookieNotice.tsx +114 -0
  146. package/lib/components/CookieNotice/index.ts +2 -0
  147. package/lib/components/Datepicker/Datepicker.lld.md +108 -0
  148. package/lib/components/Datepicker/Datepicker.stories.tsx +44 -5
  149. package/lib/components/Datepicker/Datepicker.tsx +14 -36
  150. package/lib/components/Datepicker/Datepicker.types.ts +5 -14
  151. package/lib/components/Datepicker/__tests__/Datepicker.test.tsx +150 -8
  152. package/lib/components/Datepicker/__tests__/__snapshots__/Datepicker.test.tsx.snap +10 -4
  153. package/lib/components/Datepicker/subcomponents/CustomDatepicker.tsx +39 -5
  154. package/lib/components/Datepicker/subcomponents/DatepickerInput.tsx +30 -17
  155. package/lib/components/Datepicker/subcomponents/Panel.tsx +6 -2
  156. package/lib/components/Datepicker/subcomponents/VisibleField.tsx +40 -3
  157. package/lib/components/Datepicker/subcomponents/index.ts +0 -1
  158. package/lib/components/Datepicker/utils/index.ts +0 -1
  159. package/lib/components/Dialog/BaseDialog.tsx +55 -4
  160. package/lib/components/Dialog/Dialog.tsx +8 -1
  161. package/lib/components/Dialog/DialogBody.tsx +5 -1
  162. package/lib/components/Dialog/DialogHeader.tsx +2 -1
  163. package/lib/components/Divider/Divider.stories.tsx +1 -1
  164. package/lib/components/Field/ErrorText.tsx +1 -0
  165. package/lib/components/Field/Field.stories.tsx +1 -1
  166. package/lib/components/Field/__tests__/Field.test.tsx +161 -148
  167. package/lib/components/FileInput/FileInput.stories.tsx +70 -0
  168. package/lib/components/FileInput/FileInput.tsx +68 -0
  169. package/lib/components/FileInput/__tests__/FileInput.test.tsx +99 -0
  170. package/lib/components/FileInput/__tests__/__snapshots__/FileInput.test.tsx.snap +91 -0
  171. package/lib/components/FileInput/index.ts +2 -0
  172. package/lib/components/Footer/Footer.stories.tsx +1 -1
  173. package/lib/components/Footer/__tests__/__snapshots__/Footer.test.tsx.snap +28 -28
  174. package/lib/components/Header/Header.mdx +52 -0
  175. package/lib/components/Header/Header.stories.tsx +98 -0
  176. package/lib/components/Header/Header.tsx +65 -3
  177. package/lib/components/Header/__tests__/Header.test.tsx +17 -1
  178. package/lib/components/Header/__tests__/__snapshots__/Header.test.tsx.snap +4 -4
  179. package/lib/components/Heading/Documentation.mdx +1 -1
  180. package/lib/components/Heading/Heading.stories.tsx +1 -1
  181. package/lib/components/Heading/Heading.tsx +1 -1
  182. package/lib/components/Heading/__tests__/Heading.test.tsx +7 -19
  183. package/lib/components/Heading/__tests__/__snapshots__/Heading.test.tsx.snap +7 -7
  184. package/lib/components/Icon/Icon.stories.tsx +1 -1
  185. package/lib/components/IconButton/IconButton.stories.tsx +1 -1
  186. package/lib/components/Input/Input.stories.tsx +1 -1
  187. package/lib/components/Label/Label.stories.tsx +1 -1
  188. package/lib/components/Label/Label.tsx +0 -2
  189. package/lib/components/Label/__tests__/__snapshots__/Label.test.tsx.snap +7 -7
  190. package/lib/components/Link/BaseLink.tsx +84 -0
  191. package/lib/components/Link/Link.tsx +72 -32
  192. package/lib/components/Link/__tests__/__snapshots__/link.test.tsx.snap +3 -3
  193. package/lib/components/Link/__tests__/link.test.tsx +6 -13
  194. package/lib/components/Link/index.ts +1 -1
  195. package/lib/components/Main/Main.stories.tsx +36 -0
  196. package/lib/components/Main/Main.tsx +46 -0
  197. package/lib/components/Main/__tests__/Main.test.tsx +80 -0
  198. package/lib/components/Main/__tests__/__snapshots__/Main.test.tsx.snap +33 -0
  199. package/lib/components/Main/index.ts +2 -0
  200. package/lib/components/Menu/Menu.context.tsx +3 -1
  201. package/lib/components/Menu/Menu.tsx +2 -2
  202. package/lib/components/Menu/MenuContent.tsx +5 -5
  203. package/lib/components/Menu/MenuItem.tsx +20 -3
  204. package/lib/components/Menu/MenuSection.tsx +4 -3
  205. package/lib/components/NativeDatepicker/NativeDatepicker.stories.tsx +100 -0
  206. package/lib/components/{Datepicker/subcomponents → NativeDatepicker}/NativeDatepicker.tsx +14 -15
  207. package/lib/components/NativeDatepicker/NativeDatepicker.types.ts +19 -0
  208. package/lib/components/NativeDatepicker/index.ts +2 -0
  209. package/lib/components/{Datepicker → NativeDatepicker}/utils/dateToLocaleISOString/dateToLocaleISOString.ts +1 -1
  210. package/lib/components/NativeDatepicker/utils/index.ts +1 -0
  211. package/lib/components/Pagination/PaginationControls.tsx +56 -15
  212. package/lib/components/Pagination/PaginationInfo.tsx +5 -1
  213. package/lib/components/Paragraph/Paragraph.stories.tsx +1 -1
  214. package/lib/components/Search/Search.stories.tsx +41 -0
  215. package/lib/components/Search/Search.tsx +170 -0
  216. package/lib/components/Search/__tests__/Search.test.tsx +112 -0
  217. package/lib/components/Search/__tests__/__snapshots__/Search.test.tsx.snap +179 -0
  218. package/lib/components/Search/index.ts +2 -0
  219. package/lib/components/Select/Select.mdx +169 -0
  220. package/lib/components/Select/Select.stories.tsx +198 -77
  221. package/lib/components/Select/Select.tsx +37 -13
  222. package/lib/components/Select/Select.types.ts +77 -54
  223. package/lib/components/Select/__tests__/Select.test.tsx +448 -7
  224. package/lib/components/Select/__tests__/__snapshots__/Select.test.tsx.snap +3 -3
  225. package/lib/components/Select/subcomponents/CustomOption.tsx +24 -10
  226. package/lib/components/Select/subcomponents/CustomSelect.tsx +333 -52
  227. package/lib/components/Select/subcomponents/FilterInput.tsx +80 -0
  228. package/lib/components/Select/subcomponents/NativeSelect.tsx +13 -1
  229. package/lib/components/Select/subcomponents/Panel.tsx +4 -5
  230. package/lib/components/Select/subcomponents/VisibleField.tsx +36 -24
  231. package/lib/components/Select/subcomponents/index.tsx +1 -0
  232. package/lib/components/Snackbar/Snackbar.stories.tsx +1 -1
  233. package/lib/components/Spinner/Spinner.stories.tsx +1 -1
  234. package/lib/components/StandaloneLink/StandaloneLink.stories.tsx +32 -0
  235. package/lib/components/StandaloneLink/StandaloneLink.tsx +183 -0
  236. package/lib/components/StandaloneLink/__tests__/StandaloneLink.test.tsx +57 -0
  237. package/lib/components/StandaloneLink/__tests__/__snapshots__/StandaloneLink.test.tsx.snap +19 -0
  238. package/lib/components/StandaloneLink/index.ts +2 -0
  239. package/lib/components/Table/Table.stories.tsx +337 -0
  240. package/lib/components/Table/Table.tsx +42 -67
  241. package/lib/components/Table/Table.types.ts +14 -0
  242. package/lib/components/Table/__tests__/Table.test.tsx +121 -0
  243. package/lib/components/Table/__tests__/__snapshots__/Table.test.tsx.snap +210 -0
  244. package/lib/components/Table/index.ts +8 -1
  245. package/lib/components/Table/subcomponents/Body.tsx +18 -0
  246. package/lib/components/Table/subcomponents/Cell/Cell.stories.tsx +151 -0
  247. package/lib/components/Table/subcomponents/Cell/Cell.tsx +72 -0
  248. package/lib/components/Table/subcomponents/Cell/CellContent.tsx +91 -0
  249. package/lib/components/Table/subcomponents/Cell/__tests__/Cell.test.tsx +115 -0
  250. package/lib/components/Table/subcomponents/Cell/__tests__/__snapshots__/Cell.test.tsx.snap +107 -0
  251. package/lib/components/Table/subcomponents/Head.tsx +34 -0
  252. package/lib/components/Table/subcomponents/HeadCell/HeadCell.stories.tsx +85 -0
  253. package/lib/components/Table/subcomponents/HeadCell/HeadCell.tsx +99 -0
  254. package/lib/components/Table/subcomponents/HeadCell/HeadCellContent.tsx +61 -0
  255. package/lib/components/Table/subcomponents/HeadCell/__tests__/HeadCell.test.tsx +137 -0
  256. package/lib/components/Table/subcomponents/HeadCell/__tests__/__snapshots__/HeadCell.test.tsx.snap +110 -0
  257. package/lib/components/Table/subcomponents/Row.tsx +49 -0
  258. package/lib/components/Table/subcomponents/SortIcon.tsx +63 -0
  259. package/lib/components/Table/subcomponents/index.ts +14 -0
  260. package/lib/components/Tabs/Tab.tsx +3 -3
  261. package/lib/components/Tabs/TabContext.tsx +1 -0
  262. package/lib/components/Tabs/Tabs.stories.tsx +9 -3
  263. package/lib/components/Tabs/Tabs.tsx +10 -32
  264. package/lib/components/Tabs/__tests__/Tabs.test.tsx +10 -4
  265. package/lib/components/Tabs/__tests__/__snapshots__/Tabs.test.tsx.snap +32 -32
  266. package/lib/components/Textarea/Textarea.stories.tsx +1 -1
  267. package/lib/components/Timepicker/Timepicker.stories.tsx +43 -0
  268. package/lib/components/Timepicker/Timepicker.tsx +100 -0
  269. package/lib/components/Timepicker/__tests__/Timepicker.test.tsx +55 -0
  270. package/lib/components/Timepicker/__tests__/__snapshots__/Timepicker.test.tsx.snap +19 -0
  271. package/lib/components/Timepicker/index.tsx +2 -0
  272. package/lib/components/Timepicker/utils/convertDateToTimeString.test.ts +54 -0
  273. package/lib/components/Timepicker/utils/convertDateToTimeString.ts +10 -0
  274. package/lib/components/Timepicker/utils/index.ts +1 -0
  275. package/lib/components/Toggle/Toggle.stories.tsx +1 -1
  276. package/lib/components/Tooltip/Tooltip.stories.tsx +1 -1
  277. package/lib/components/WeekPicker/WeekPicker.stories.tsx +147 -0
  278. package/lib/components/WeekPicker/WeekPicker.tsx +26 -0
  279. package/lib/components/WeekPicker/WeekPicker.types.ts +21 -0
  280. package/lib/components/WeekPicker/index.ts +2 -0
  281. package/lib/components/WeekPicker/subcomponents/CustomDatepicker.tsx +298 -0
  282. package/lib/components/WeekPicker/subcomponents/DatepickerInput.tsx +111 -0
  283. package/lib/components/WeekPicker/subcomponents/VisibleField.tsx +126 -0
  284. package/lib/components/WeekPicker/subcomponents/index.ts +3 -0
  285. package/lib/components/common/Common.mdx +1 -1
  286. package/lib/components/index.ts +28 -2
  287. package/lib/hooks/index.ts +2 -0
  288. package/lib/hooks/useFocusTrap.ts +159 -0
  289. package/lib/index.ts +2 -0
  290. package/lib/theme/defaultTheme.ts +7 -0
  291. package/lib/utils/__tests__/announce.test.ts +121 -0
  292. package/lib/utils/__tests__/capitalise.test.ts +40 -0
  293. package/lib/utils/announce.ts +134 -0
  294. package/lib/utils/capitalise.ts +4 -0
  295. package/lib/utils/index.ts +1 -0
  296. package/package.json +3 -6
  297. package/dist/components/Datepicker/subcomponents/NativeDatepicker.d.ts +0 -6
  298. package/lib/components/Accordion/Accordion.stories.tsx.NOT_READY +0 -93
  299. package/lib/components/Field/__tests__/__snapshots__/Field.test.tsx.snap +0 -300
  300. /package/dist/components/{Datepicker/utils/dateToLocaleISOString/dateToLocaleISOString.test.d.ts → FileInput/__tests__/FileInput.test.d.ts} +0 -0
  301. /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
- // Snapshot tests
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('2025-03-10')} />
34
+ <Datepicker value={new Date(2025, 2, 10)} />
27
35
  );
28
36
  expect(renderResult.container.firstChild).toMatchSnapshot();
29
37
  });
38
+ // #endregion
30
39
 
31
- // Unit tests
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
- customRender(<Datepicker value={new Date('2025-03-10')} />);
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('2025-01-10')} // Before minDate
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('2025-03-10')} // After maxDate
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
- class="css-3tb0p0"
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
- class="css-3tb0p0"
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 parseDate = parseInputValue(inputValue, minDate, maxDate);
79
- if (parseDate) {
80
- onValueChange(parseDate, event);
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
- resetField();
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, className, baseStyle);
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
- interface DatepickerInputProps {
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
- ref,
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
- value={value}
59
- onChange={onChange}
60
- onKeyDown={onKeyDown}
61
- onFocus={onFocus}
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 = ({ zIndex = 10, children }: PanelProps) => {
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 0 4px;
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
- <Icon.Calendar className={iconStyle} />
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';
@@ -1,2 +1 @@
1
- export { default as dateToLocaleISOString } from './dateToLocaleISOString/dateToLocaleISOString';
2
1
  export { default as parseInputValue } from './parseInputValue/parseInputValue';
@@ -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
- onClose?: (ev: React.MouseEvent) => void;
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
- previousActiveElement.current?.focus();
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}