uikit-react-public 0.14.21 → 0.21.8

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 (392) hide show
  1. package/README.md +2 -2
  2. package/dist/components/Accordion/Accordion.Heading.d.ts +5 -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/AppHeader/AppHeader.d.ts +1 -1
  8. package/dist/components/AppHeader/AppHeaderBottom.d.ts +1 -1
  9. package/dist/components/AppHeader/AppHeaderNav.d.ts +1 -1
  10. package/dist/components/AppHeader/AppHeaderTop.d.ts +1 -1
  11. package/dist/components/Avatar/Avatar.stories.d.ts +107 -1
  12. package/dist/components/Breadcrumbs/Breadcrumb.d.ts +3 -4
  13. package/dist/components/Breadcrumbs/Breadcrumbs.d.ts +1 -1
  14. package/dist/components/Breadcrumbs/Breadcrumbs.stories.d.ts +1 -1
  15. package/dist/components/Button/Button.d.ts +8 -3
  16. package/dist/components/Button/Button.stories.d.ts +17 -7
  17. package/dist/components/Button/style/buttonAccentStyle.d.ts +4 -0
  18. package/dist/components/Button/style/buttonPrimaryDestructiveStyle.d.ts +4 -0
  19. package/dist/components/Button/style/buttonPrimaryStyle.d.ts +4 -0
  20. package/dist/components/Button/style/buttonPrimarySubtleStyle.d.ts +4 -0
  21. package/dist/components/Button/style/buttonPrimaryWarningStyle.d.ts +4 -0
  22. package/dist/components/Button/style/buttonSecondaryDestructiveStyle.d.ts +4 -0
  23. package/dist/components/Button/style/buttonSecondaryStyle.d.ts +4 -0
  24. package/dist/components/Button/style/buttonSecondarySubtleStyle.d.ts +4 -0
  25. package/dist/components/Button/style/buttonTertiaryDestructiveStyle.d.ts +4 -0
  26. package/dist/components/Button/style/buttonTertiaryNoPaddingStyle.d.ts +4 -0
  27. package/dist/components/Button/style/buttonTertiaryStyle.d.ts +4 -0
  28. package/dist/components/Calendar/index.d.ts +1 -1
  29. package/dist/components/Checkbox/Checkbox.d.ts +1 -0
  30. package/dist/components/Datepicker/Datepicker.d.ts +1 -1
  31. package/dist/components/Datepicker/Datepicker.stories.d.ts +4 -3
  32. package/dist/components/Datepicker/Datepicker.types.d.ts +4 -5
  33. package/dist/components/Datepicker/subcomponents/CustomDatepicker.d.ts +4 -1
  34. package/dist/components/Datepicker/subcomponents/DatepickerInput.d.ts +15 -2
  35. package/dist/components/Datepicker/subcomponents/Panel.d.ts +1 -1
  36. package/dist/components/Datepicker/subcomponents/VisibleField.d.ts +6 -1
  37. package/dist/components/Datepicker/subcomponents/index.d.ts +0 -1
  38. package/dist/components/Datepicker/utils/index.d.ts +0 -1
  39. package/dist/components/Dialog/BaseDialog.d.ts +2 -1
  40. package/dist/components/Dialog/Dialog.d.ts +2 -0
  41. package/dist/components/FooterNew/BackToTop.d.ts +8 -0
  42. package/dist/components/FooterNew/Footer.d.ts +23 -0
  43. package/dist/components/FooterNew/FooterColumn.d.ts +8 -0
  44. package/dist/components/FooterNew/FooterLinks.d.ts +7 -0
  45. package/dist/components/FooterNew/FooterNavLink.d.ts +8 -0
  46. package/dist/components/FooterNew/LegalAndCopyright.d.ts +14 -0
  47. package/dist/components/FooterNew/LogoAddressAndSocial.d.ts +10 -0
  48. package/dist/components/FooterNew/SocialLink.d.ts +8 -0
  49. package/dist/components/FooterNew/index.d.ts +2 -0
  50. package/dist/components/Header/Header.d.ts +4 -1
  51. package/dist/components/Header/Header.stories.d.ts +40 -0
  52. package/dist/components/HeaderNew/Header.d.ts +18 -0
  53. package/dist/components/HeaderNew/HeaderBorder.d.ts +7 -0
  54. package/dist/components/HeaderNew/HeaderLogo.d.ts +9 -0
  55. package/dist/components/HeaderNew/HeaderMenuContainer.d.ts +7 -0
  56. package/dist/components/HeaderNew/HeaderTitle.d.ts +9 -0
  57. package/dist/components/HeaderNew/__tests__/Header.test.d.ts +1 -0
  58. package/dist/components/HeaderNew/constants.d.ts +3 -0
  59. package/dist/components/HeaderNew/index.d.ts +3 -0
  60. package/dist/components/HeadingNew/Heading.d.ts +13 -0
  61. package/dist/components/HeadingNew/index.d.ts +2 -0
  62. package/dist/components/Icon/svgImports.d.ts +7 -881
  63. package/dist/components/Link/BaseLink.d.ts +14 -5
  64. package/dist/components/Link/Link.d.ts +8 -3
  65. package/dist/components/Link/Link.stories.d.ts +3 -1
  66. package/dist/components/Main/Main.d.ts +21 -0
  67. package/dist/components/Main/Main.stories.d.ts +15 -0
  68. package/dist/components/Main/__tests__/Main.test.d.ts +1 -0
  69. package/dist/components/Main/index.d.ts +2 -0
  70. package/dist/components/MenuNew/Menu.context.d.ts +14 -0
  71. package/dist/components/MenuNew/Menu.d.ts +20 -0
  72. package/dist/components/MenuNew/MenuContent.d.ts +9 -0
  73. package/dist/components/MenuNew/MenuItem.d.ts +10 -0
  74. package/dist/components/MenuNew/MenuSection.d.ts +7 -0
  75. package/dist/components/MenuNew/index.d.ts +6 -0
  76. package/dist/components/MenuNew/trigger/ButtonMenuTrigger.d.ts +8 -0
  77. package/dist/components/MenuNew/trigger/IconMenuTrigger.d.ts +8 -0
  78. package/dist/components/NativeDatepicker/NativeDatepicker.d.ts +3 -0
  79. package/dist/components/NativeDatepicker/NativeDatepicker.stories.d.ts +36 -0
  80. package/dist/components/NativeDatepicker/NativeDatepicker.types.d.ts +10 -0
  81. package/dist/components/NativeDatepicker/index.d.ts +2 -0
  82. package/dist/components/{Datepicker → NativeDatepicker}/utils/dateToLocaleISOString/dateToLocaleISOString.d.ts +1 -1
  83. package/dist/components/NativeDatepicker/utils/dateToLocaleISOString/dateToLocaleISOString.test.d.ts +1 -0
  84. package/dist/components/NativeDatepicker/utils/index.d.ts +1 -0
  85. package/dist/components/Overlay/Overlay.stories.d.ts +12 -12
  86. package/dist/components/ParagraphNew/Paragraph.d.ts +13 -0
  87. package/dist/components/ParagraphNew/index.d.ts +4 -0
  88. package/dist/components/Select/Select.d.ts +2 -1
  89. package/dist/components/Select/Select.stories.d.ts +167 -3
  90. package/dist/components/Select/Select.types.d.ts +75 -19
  91. package/dist/components/Select/subcomponents/CustomOption.d.ts +1 -1
  92. package/dist/components/Select/subcomponents/CustomSelect.d.ts +3 -2
  93. package/dist/components/Select/subcomponents/FilterInput.d.ts +16 -0
  94. package/dist/components/Select/subcomponents/NativeSelect.d.ts +5 -1
  95. package/dist/components/Select/subcomponents/VisibleField.d.ts +6 -1
  96. package/dist/components/Select/subcomponents/index.d.ts +1 -0
  97. package/dist/components/Spinner/Spinner.d.ts +2 -0
  98. package/dist/components/StandaloneLink/StandaloneLink.d.ts +8 -5
  99. package/dist/components/StandaloneLink/StandaloneLink.stories.d.ts +3 -1
  100. package/dist/components/Table/Table.d.ts +3 -3
  101. package/dist/components/Table/Table.stories.d.ts +3 -3
  102. package/dist/components/Table/Table.types.d.ts +1 -0
  103. package/dist/components/Table/subcomponents/Cell/Cell.d.ts +5 -1
  104. package/dist/components/Table/subcomponents/Cell/Cell.stories.d.ts +15 -13
  105. package/dist/components/Table/subcomponents/Cell/CellContent.d.ts +5 -1
  106. package/dist/components/Table/subcomponents/HeadCell/HeadCell.d.ts +2 -1
  107. package/dist/components/Table/subcomponents/HeadCell/HeadCell.stories.d.ts +14 -13
  108. package/dist/components/Table/subcomponents/HeadCell/HeadCellContent.d.ts +2 -1
  109. package/dist/components/Table/subcomponents/__tests__/Row.test.d.ts +1 -0
  110. package/dist/components/UclLogoNew/UclLogo.d.ts +8 -0
  111. package/dist/components/UclLogoNew/index.d.ts +2 -0
  112. package/dist/components/WeekPicker/WeekPicker.d.ts +2 -2
  113. package/dist/components/WeekPicker/WeekPicker.stories.d.ts +41 -0
  114. package/dist/components/WeekPicker/WeekPicker.types.d.ts +16 -0
  115. package/dist/components/WeekPicker/index.d.ts +1 -0
  116. package/dist/components/WeekPicker/subcomponents/CustomDatepicker.d.ts +1 -1
  117. package/dist/components/index.d.ts +20 -0
  118. package/dist/hooks/useFocusTrap.d.ts +2 -1
  119. package/dist/index.d.ts +1 -0
  120. package/dist/index.js +22204 -16719
  121. package/dist/theme/__tests__/fonts.test.d.ts +1 -0
  122. package/dist/theme/common/themeCommon.d.ts +904 -0
  123. package/dist/theme/fonts.d.ts +18 -0
  124. package/dist/theme/index.d.ts +6 -3
  125. package/dist/theme/light/lightColour.d.ts +126 -0
  126. package/dist/theme/light/lightTheme.d.ts +3 -0
  127. package/dist/theme/original/color.d.ts +166 -0
  128. package/dist/theme/original/defaultTheme.d.ts +1340 -0
  129. package/dist/theme/original/originalColourNewStructure.d.ts +126 -0
  130. package/dist/theme/useTheme.d.ts +2174 -0
  131. package/dist/utils/__tests__/announce.test.d.ts +1 -0
  132. package/dist/utils/addAlphaToHex.d.ts +5 -0
  133. package/dist/utils/announce.d.ts +6 -0
  134. package/dist/utils/index.d.ts +1 -0
  135. package/dist/utils/scrollToTop.d.ts +2 -0
  136. package/lib/components/Accordion/Accordion.Heading.tsx +65 -34
  137. package/lib/components/Accordion/Accordion.Panel.tsx +11 -7
  138. package/lib/components/Accordion/Accordion.stories.tsx +139 -0
  139. package/lib/components/Accordion/Accordion.tsx +39 -31
  140. package/lib/components/Accordion/__tests__/__snapshots__/Accordion.test.tsx.snap +15 -13
  141. package/lib/components/Accordion/index.ts +2 -0
  142. package/lib/components/Alert/Alert.stories.tsx +1 -1
  143. package/lib/components/Alert/Alert.tsx +12 -12
  144. package/lib/components/Alert/__tests__/__snapshots__/Alert.test.tsx.snap +13 -39
  145. package/lib/components/AppHeader/AppHeader.tsx +6 -11
  146. package/lib/components/AppHeader/AppHeaderBottom.tsx +2 -3
  147. package/lib/components/AppHeader/AppHeaderNav.tsx +2 -3
  148. package/lib/components/AppHeader/AppHeaderTop.tsx +1 -1
  149. package/lib/components/AppHeader/__tests__/__snapshots__/AppHeader.test.tsx.snap +2 -2
  150. package/lib/components/AppMenu/__tests__/__snapshots__/AppMenu.test.tsx.snap +6 -19
  151. package/lib/components/Avatar/Avatar.mdx +117 -0
  152. package/lib/components/Avatar/Avatar.stories.tsx +110 -2
  153. package/lib/components/Badge/Badge.stories.tsx +1 -1
  154. package/lib/components/Blanket/Blanket.stories.tsx +1 -1
  155. package/lib/components/Breadcrumbs/Breadcrumb.tsx +26 -12
  156. package/lib/components/Breadcrumbs/Breadcrumbs.tsx +1 -1
  157. package/lib/components/Breadcrumbs/__tests__/Breadcrumbs.test.tsx +9 -27
  158. package/lib/components/Breadcrumbs/__tests__/__snapshots__/Breadcrumbs.test.tsx.snap +24 -20
  159. package/lib/components/Button/Button.mdx +32 -279
  160. package/lib/components/Button/Button.stories.tsx +44 -51
  161. package/lib/components/Button/Button.tsx +166 -25
  162. package/lib/components/Button/__tests__/Button.test.tsx +49 -15
  163. package/lib/components/Button/__tests__/__snapshots__/Button.test.tsx.snap +80 -73
  164. package/lib/components/Button/style/buttonAccentStyle.ts +53 -0
  165. package/lib/components/Button/style/buttonPrimaryDestructiveStyle.ts +55 -0
  166. package/lib/components/Button/style/buttonPrimaryStyle.ts +53 -0
  167. package/lib/components/Button/style/buttonPrimarySubtleStyle.ts +64 -0
  168. package/lib/components/Button/style/buttonPrimaryWarningStyle.ts +56 -0
  169. package/lib/components/Button/style/buttonSecondaryDestructiveStyle.ts +63 -0
  170. package/lib/components/Button/style/buttonSecondaryStyle.ts +62 -0
  171. package/lib/components/Button/style/buttonSecondarySubtleStyle.ts +72 -0
  172. package/lib/components/Button/style/buttonTertiaryDestructiveStyle.ts +65 -0
  173. package/lib/components/Button/style/buttonTertiaryNoPaddingStyle.ts +52 -0
  174. package/lib/components/Button/style/buttonTertiaryStyle.ts +62 -0
  175. package/lib/components/Calendar/Calendar.stories.tsx +1 -1
  176. package/lib/components/Calendar/Calendar.tsx +2 -2
  177. package/lib/components/Calendar/__tests__/Calendar.test.tsx +23 -15
  178. package/lib/components/Calendar/__tests__/__snapshots__/Calendar.test.tsx.snap +99 -95
  179. package/lib/components/Calendar/index.ts +1 -5
  180. package/lib/components/Calendar/subcomponents/AcademicWeek.tsx +2 -1
  181. package/lib/components/Calendar/subcomponents/AcademicWeeks.tsx +1 -1
  182. package/lib/components/Calendar/subcomponents/ColumnHeading.tsx +2 -2
  183. package/lib/components/Calendar/subcomponents/Controls.tsx +1 -1
  184. package/lib/components/Calendar/subcomponents/Day.stories.tsx +1 -1
  185. package/lib/components/Calendar/subcomponents/Day.tsx +7 -9
  186. package/lib/components/Calendar/subcomponents/EventDot.tsx +3 -6
  187. package/lib/components/Calendar/subcomponents/index.ts +1 -1
  188. package/lib/components/Calendar/utils/getDatesForCalendarGrid/getDatesForCalendarGrid.ts +43 -11
  189. package/lib/components/Calendar/utils/normaliseMonth/normaliseMonth.test.ts +5 -5
  190. package/lib/components/Checkbox/Checkbox.stories.tsx +1 -1
  191. package/lib/components/Checkbox/Checkbox.tsx +12 -10
  192. package/lib/components/Checkbox/__tests__/Checkbox.test.tsx +29 -0
  193. package/lib/components/Checkbox/__tests__/__snapshots__/Checkbox.test.tsx.snap +4 -4
  194. package/lib/components/Datepicker/Datepicker.lld.md +108 -0
  195. package/lib/components/Datepicker/Datepicker.stories.tsx +44 -5
  196. package/lib/components/Datepicker/Datepicker.tsx +14 -36
  197. package/lib/components/Datepicker/Datepicker.types.ts +5 -14
  198. package/lib/components/Datepicker/__tests__/Datepicker.test.tsx +267 -8
  199. package/lib/components/Datepicker/__tests__/__snapshots__/Datepicker.test.tsx.snap +20 -42
  200. package/lib/components/Datepicker/subcomponents/CustomDatepicker.tsx +48 -5
  201. package/lib/components/Datepicker/subcomponents/DatepickerInput.tsx +30 -17
  202. package/lib/components/Datepicker/subcomponents/Panel.tsx +6 -2
  203. package/lib/components/Datepicker/subcomponents/VisibleField.tsx +46 -8
  204. package/lib/components/Datepicker/subcomponents/index.ts +0 -1
  205. package/lib/components/Datepicker/utils/index.ts +0 -1
  206. package/lib/components/Dialog/BaseDialog.tsx +13 -2
  207. package/lib/components/Dialog/Dialog.stories.tsx +1 -1
  208. package/lib/components/Dialog/Dialog.tsx +8 -1
  209. package/lib/components/Dialog/DialogBody.tsx +5 -1
  210. package/lib/components/Dialog/DialogHeader.tsx +2 -1
  211. package/lib/components/Divider/Divider.stories.tsx +1 -1
  212. package/lib/components/Divider/__tests__/__snapshots__/Breadcrumbs.test.tsx.snap +12 -12
  213. package/lib/components/FeedbackDialog/FeedbackDialog.stories.tsx +1 -1
  214. package/lib/components/FeedbackDialog/FeedbackDialog.tsx +4 -6
  215. package/lib/components/Field/CharacterCount.tsx +2 -2
  216. package/lib/components/Field/ErrorText.tsx +2 -1
  217. package/lib/components/Field/Field.stories.tsx +1 -1
  218. package/lib/components/Field/Field.tsx +1 -1
  219. package/lib/components/Field/HelperText.tsx +3 -1
  220. package/lib/components/Field/__tests__/Field.test.tsx +13 -0
  221. package/lib/components/FileInput/FileInput.stories.tsx +1 -1
  222. package/lib/components/FileInput/__tests__/__snapshots__/FileInput.test.tsx.snap +4 -20
  223. package/lib/components/Footer/Footer.stories.tsx +1 -1
  224. package/lib/components/Footer/__tests__/__snapshots__/Footer.test.tsx.snap +73 -82
  225. package/lib/components/FooterNew/BackToTop.tsx +83 -0
  226. package/lib/components/FooterNew/Footer.tsx +110 -0
  227. package/lib/components/FooterNew/FooterColumn.tsx +79 -0
  228. package/lib/components/FooterNew/FooterLinks.tsx +44 -0
  229. package/lib/components/FooterNew/FooterNavLink.tsx +63 -0
  230. package/lib/components/FooterNew/LegalAndCopyright.tsx +150 -0
  231. package/lib/components/FooterNew/LogoAddressAndSocial.tsx +154 -0
  232. package/lib/components/FooterNew/SocialLink.tsx +108 -0
  233. package/lib/components/FooterNew/__tests__/Footer.test.tsx +51 -0
  234. package/lib/components/FooterNew/__tests__/__snapshots__/Footer.test.tsx.snap +1107 -0
  235. package/lib/components/FooterNew/index.ts +2 -0
  236. package/lib/components/Header/Header.mdx +52 -0
  237. package/lib/components/Header/Header.stories.tsx +98 -0
  238. package/lib/components/Header/Header.tsx +51 -6
  239. package/lib/components/Header/__tests__/Header.test.tsx +17 -1
  240. package/lib/components/HeaderDraft/__tests__/__snapshots__/Header.test.tsx.snap +3 -2
  241. package/lib/components/HeaderNew/Header.tsx +93 -0
  242. package/lib/components/HeaderNew/HeaderBorder.tsx +55 -0
  243. package/lib/components/HeaderNew/HeaderLogo.tsx +70 -0
  244. package/lib/components/HeaderNew/HeaderMenuContainer.tsx +35 -0
  245. package/lib/components/HeaderNew/HeaderTitle.tsx +53 -0
  246. package/lib/components/HeaderNew/__tests__/Header.test.tsx +42 -0
  247. package/lib/components/HeaderNew/__tests__/__snapshots__/Header.test.tsx.snap +79 -0
  248. package/lib/components/HeaderNew/constants.ts +3 -0
  249. package/lib/components/HeaderNew/index.ts +7 -0
  250. package/lib/components/Heading/Heading.stories.tsx +1 -1
  251. package/lib/components/HeadingNew/Heading.tsx +208 -0
  252. package/lib/components/HeadingNew/index.ts +2 -0
  253. package/lib/components/Icon/Icon.stories.tsx +1 -1
  254. package/lib/components/Icon/__tests__/__snapshots__/Icon.test.tsx.snap +16 -12
  255. package/lib/components/Icon/svgImports.ts +318 -296
  256. package/lib/components/IconButton/IconButton.stories.tsx +1 -1
  257. package/lib/components/IconButton/IconButton.tsx +3 -4
  258. package/lib/components/IconButton/__tests__/__snapshots__/IconButton.test.tsx.snap +12 -9
  259. package/lib/components/Input/Input.stories.tsx +1 -1
  260. package/lib/components/Label/Label.stories.tsx +1 -1
  261. package/lib/components/Link/BaseLink.tsx +114 -71
  262. package/lib/components/Link/Link.stories.tsx +1 -1
  263. package/lib/components/Link/Link.tsx +120 -109
  264. package/lib/components/Link/__tests__/__snapshots__/link.test.tsx.snap +2 -2
  265. package/lib/components/Main/Main.stories.tsx +36 -0
  266. package/lib/components/Main/Main.tsx +46 -0
  267. package/lib/components/Main/__tests__/Main.test.tsx +80 -0
  268. package/lib/components/Main/__tests__/__snapshots__/Main.test.tsx.snap +33 -0
  269. package/lib/components/Main/index.ts +2 -0
  270. package/lib/components/MenuNew/Menu.context.tsx +149 -0
  271. package/lib/components/MenuNew/Menu.tsx +75 -0
  272. package/lib/components/MenuNew/MenuContent.tsx +140 -0
  273. package/lib/components/MenuNew/MenuItem.tsx +101 -0
  274. package/lib/components/MenuNew/MenuSection.tsx +47 -0
  275. package/lib/components/MenuNew/index.ts +8 -0
  276. package/lib/components/MenuNew/trigger/ButtonMenuTrigger.tsx +42 -0
  277. package/lib/components/MenuNew/trigger/IconMenuTrigger.tsx +40 -0
  278. package/lib/components/NativeDatepicker/NativeDatepicker.stories.tsx +100 -0
  279. package/lib/components/{Datepicker/subcomponents → NativeDatepicker}/NativeDatepicker.tsx +14 -15
  280. package/lib/components/NativeDatepicker/NativeDatepicker.types.ts +19 -0
  281. package/lib/components/NativeDatepicker/index.ts +2 -0
  282. package/lib/components/{Datepicker → NativeDatepicker}/utils/dateToLocaleISOString/dateToLocaleISOString.ts +1 -1
  283. package/lib/components/NativeDatepicker/utils/index.ts +1 -0
  284. package/lib/components/Pagination/Pagination.stories.tsx +1 -1
  285. package/lib/components/Pagination/PaginationControls.tsx +59 -17
  286. package/lib/components/Pagination/PaginationInfo.tsx +7 -4
  287. package/lib/components/Paragraph/Paragraph.stories.tsx +1 -1
  288. package/lib/components/ParagraphNew/Paragraph.tsx +200 -0
  289. package/lib/components/ParagraphNew/index.ts +6 -0
  290. package/lib/components/Radio/Radio.stories.tsx +1 -1
  291. package/lib/components/Radio/Radio.tsx +8 -8
  292. package/lib/components/Radio/__tests__/__snapshots__/Radio.test.tsx.snap +4 -4
  293. package/lib/components/Search/Search.stories.tsx +1 -1
  294. package/lib/components/Search/Search.tsx +4 -1
  295. package/lib/components/Search/__tests__/Search.test.tsx +19 -1
  296. package/lib/components/Search/__tests__/__snapshots__/Search.test.tsx.snap +12 -32
  297. package/lib/components/Select/Select.mdx +192 -0
  298. package/lib/components/Select/Select.stories.tsx +229 -48
  299. package/lib/components/Select/Select.tsx +50 -15
  300. package/lib/components/Select/Select.types.ts +99 -44
  301. package/lib/components/Select/__tests__/Select.test.tsx +698 -8
  302. package/lib/components/Select/__tests__/__snapshots__/Select.test.tsx.snap +5 -4
  303. package/lib/components/Select/subcomponents/CustomOption.tsx +12 -4
  304. package/lib/components/Select/subcomponents/CustomSelect.tsx +411 -41
  305. package/lib/components/Select/subcomponents/FilterInput.tsx +90 -0
  306. package/lib/components/Select/subcomponents/NativeSelect.tsx +21 -17
  307. package/lib/components/Select/subcomponents/Panel.tsx +2 -2
  308. package/lib/components/Select/subcomponents/VisibleField.tsx +59 -6
  309. package/lib/components/Select/subcomponents/index.tsx +1 -0
  310. package/lib/components/Snackbar/Snackbar.stories.tsx +1 -1
  311. package/lib/components/Snackbar/__tests__/__snapshots__/Snackbar.test.tsx.snap +9 -15
  312. package/lib/components/Spinner/Spinner.stories.tsx +1 -1
  313. package/lib/components/Spinner/Spinner.tsx +24 -5
  314. package/lib/components/Spinner/__tests__/Spinner.test.tsx +35 -5
  315. package/lib/components/Spinner/__tests__/__snapshots__/Spinner.test.tsx.snap +40 -16
  316. package/lib/components/StandaloneLink/StandaloneLink.stories.tsx +1 -1
  317. package/lib/components/StandaloneLink/StandaloneLink.tsx +180 -163
  318. package/lib/components/StandaloneLink/__tests__/__snapshots__/StandaloneLink.test.tsx.snap +2 -2
  319. package/lib/components/Table/Table.stories.tsx +1 -1
  320. package/lib/components/Table/Table.tsx +2 -0
  321. package/lib/components/Table/Table.types.ts +1 -0
  322. package/lib/components/Table/__tests__/Table.test.tsx +19 -0
  323. package/lib/components/Table/__tests__/__snapshots__/Table.test.tsx.snap +7 -3
  324. package/lib/components/Table/subcomponents/Cell/Cell.stories.tsx +1 -1
  325. package/lib/components/Table/subcomponents/Cell/Cell.tsx +23 -2
  326. package/lib/components/Table/subcomponents/Cell/CellContent.tsx +12 -1
  327. package/lib/components/Table/subcomponents/Cell/__tests__/Cell.test.tsx +106 -0
  328. package/lib/components/Table/subcomponents/Cell/__tests__/__snapshots__/Cell.test.tsx.snap +4 -3
  329. package/lib/components/Table/subcomponents/HeadCell/HeadCell.stories.tsx +1 -1
  330. package/lib/components/Table/subcomponents/HeadCell/HeadCell.tsx +28 -6
  331. package/lib/components/Table/subcomponents/HeadCell/HeadCellContent.tsx +3 -0
  332. package/lib/components/Table/subcomponents/HeadCell/__tests__/HeadCell.test.tsx +221 -2
  333. package/lib/components/Table/subcomponents/HeadCell/__tests__/__snapshots__/HeadCell.test.tsx.snap +6 -4
  334. package/lib/components/Table/subcomponents/Row.tsx +2 -2
  335. package/lib/components/Table/subcomponents/SortIcon.tsx +1 -0
  336. package/lib/components/Table/subcomponents/__tests__/Row.test.tsx +59 -0
  337. package/lib/components/Tabs/Tab.tsx +3 -3
  338. package/lib/components/Tabs/Tabs.stories.tsx +1 -1
  339. package/lib/components/Tabs/Tabs.tsx +5 -3
  340. package/lib/components/Tabs/__tests__/__snapshots__/Tabs.test.tsx.snap +4 -4
  341. package/lib/components/Textarea/Textarea.stories.tsx +1 -1
  342. package/lib/components/Timepicker/Timepicker.stories.tsx +1 -1
  343. package/lib/components/Timepicker/Timepicker.tsx +4 -0
  344. package/lib/components/Timepicker/__tests__/__snapshots__/Timepicker.test.tsx.snap +2 -2
  345. package/lib/components/Toggle/Toggle.stories.tsx +1 -1
  346. package/lib/components/Toggle/Toggle.tsx +5 -5
  347. package/lib/components/Toggle/ToggleHandle.tsx +2 -3
  348. package/lib/components/Tooltip/Tooltip.stories.tsx +1 -1
  349. package/lib/components/Tooltip/Tooltip.tsx +2 -2
  350. package/lib/components/Tooltip/__tests__/__snapshots__/tooltip.test.tsx.snap +2 -2
  351. package/lib/components/UclLogoNew/UclLogo.tsx +42 -0
  352. package/lib/components/UclLogoNew/index.ts +2 -0
  353. package/lib/components/WeekPicker/WeekPicker.stories.tsx +145 -0
  354. package/lib/components/WeekPicker/WeekPicker.tsx +2 -2
  355. package/lib/components/WeekPicker/WeekPicker.types.ts +21 -0
  356. package/lib/components/WeekPicker/index.ts +1 -0
  357. package/lib/components/WeekPicker/subcomponents/CustomDatepicker.tsx +1 -1
  358. package/lib/components/common/Common.mdx +1 -2
  359. package/lib/components/index.ts +30 -3
  360. package/lib/hooks/useFocusTrap.ts +40 -4
  361. package/lib/index.ts +1 -0
  362. package/lib/theme/Colours.mdx +1 -1
  363. package/lib/theme/Theme.mdx +1 -1
  364. package/lib/theme/Typography.mdx +1 -1
  365. package/lib/theme/__tests__/fonts.test.ts +37 -0
  366. package/lib/theme/common/themeCommon.ts +515 -0
  367. package/lib/theme/fonts.ts +110 -0
  368. package/lib/theme/index.ts +6 -6
  369. package/lib/theme/light/lightColour.ts +232 -0
  370. package/lib/theme/light/lightTheme.ts +37 -0
  371. package/lib/theme/{defaultTheme.ts → original/color.ts} +17 -199
  372. package/lib/theme/original/defaultTheme.ts +207 -0
  373. package/lib/theme/original/originalColourNewStructure.ts +185 -0
  374. package/lib/theme/useTheme.tsx +72 -15
  375. package/lib/types/assets.d.ts +10 -0
  376. package/lib/utils/__tests__/announce.test.ts +121 -0
  377. package/lib/utils/addAlphaToHex.ts +29 -0
  378. package/lib/utils/announce.ts +134 -0
  379. package/lib/utils/index.ts +1 -0
  380. package/lib/utils/scrollToTop.ts +5 -0
  381. package/package.json +11 -9
  382. package/dist/components/Button/buttonPrimaryStyle.d.ts +0 -4
  383. package/dist/components/Button/buttonSecondaryStyle.d.ts +0 -4
  384. package/dist/components/Button/buttonTertiaryStyle.d.ts +0 -4
  385. package/dist/components/Datepicker/subcomponents/NativeDatepicker.d.ts +0 -6
  386. package/dist/theme/defaultTheme.d.ts +0 -274
  387. package/lib/components/Accordion/Accordion.stories.tsx.NOT_READY +0 -93
  388. package/lib/components/Button/buttonPrimaryStyle.ts +0 -62
  389. package/lib/components/Button/buttonSecondaryStyle.ts +0 -65
  390. package/lib/components/Button/buttonTertiaryStyle.ts +0 -54
  391. /package/dist/components/{Datepicker/utils/dateToLocaleISOString/dateToLocaleISOString.test.d.ts → FooterNew/__tests__/Footer.test.d.ts} +0 -0
  392. /package/lib/components/{Datepicker → NativeDatepicker}/utils/dateToLocaleISOString/dateToLocaleISOString.test.ts +0 -0
@@ -1,5 +1,6 @@
1
- import { describe, expect, test } from 'vitest';
2
- import { render } from '@testing-library/react';
1
+ import { describe, expect, test, vi, beforeAll } from 'vitest';
2
+ import { useState } from 'react';
3
+ import { createEvent, fireEvent, render } from '@testing-library/react';
3
4
  import userEvent from '@testing-library/user-event';
4
5
  import Select from '../Select';
5
6
  import { ThemeContextProvider } from '../../../theme/useTheme';
@@ -11,6 +12,13 @@ const defaultOptions = [
11
12
  ];
12
13
 
13
14
  describe('Select', () => {
15
+ beforeAll(() => {
16
+ Object.defineProperty(Element.prototype, 'scrollIntoView', {
17
+ value: vi.fn(),
18
+ writable: true,
19
+ });
20
+ });
21
+
14
22
  // Snapshot tests
15
23
 
16
24
  test('Snapshot: default', () => {
@@ -19,7 +27,7 @@ describe('Select', () => {
19
27
  <Select
20
28
  options={defaultOptions}
21
29
  value=''
22
- onChange={() => {}}
30
+ onValueChange={() => {}}
23
31
  />
24
32
  </ThemeContextProvider>
25
33
  );
@@ -35,7 +43,7 @@ describe('Select', () => {
35
43
  <Select
36
44
  options={defaultOptions}
37
45
  value=''
38
- onChange={() => {}}
46
+ onValueChange={() => {}}
39
47
  />
40
48
  </ThemeContextProvider>
41
49
  );
@@ -50,7 +58,7 @@ describe('Select', () => {
50
58
  native
51
59
  options={defaultOptions}
52
60
  value=''
53
- onChange={() => {}}
61
+ nativeHtmlAttributes={{ onChange: () => {} }}
54
62
  />
55
63
  </ThemeContextProvider>
56
64
  );
@@ -66,7 +74,7 @@ describe('Select', () => {
66
74
  <Select
67
75
  options={defaultOptions}
68
76
  value=''
69
- onChange={() => {}}
77
+ onValueChange={() => {}}
70
78
  />
71
79
  </ThemeContextProvider>
72
80
  );
@@ -91,7 +99,7 @@ describe('Select', () => {
91
99
  disabled
92
100
  options={defaultOptions}
93
101
  value=''
94
- onChange={() => {}}
102
+ onValueChange={() => {}}
95
103
  />
96
104
  </ThemeContextProvider>
97
105
  );
@@ -109,11 +117,693 @@ describe('Select', () => {
109
117
  native
110
118
  options={defaultOptions}
111
119
  value='1'
112
- onChange={() => {}}
120
+ nativeHtmlAttributes={{ onChange: () => {} }}
113
121
  />
114
122
  </ThemeContextProvider>
115
123
  );
116
124
  const select = renderResult.getByTestId('ucl-uikit-select--native');
117
125
  expect(select).toHaveProperty('disabled');
118
126
  });
127
+
128
+ test('Filters options when filterable is true', async () => {
129
+ const user = userEvent.setup();
130
+ const renderResult = render(
131
+ <ThemeContextProvider>
132
+ <Select
133
+ filterable
134
+ options={defaultOptions}
135
+ value=''
136
+ onValueChange={() => {}}
137
+ />
138
+ </ThemeContextProvider>
139
+ );
140
+ await user.click(renderResult.getByTestId('ucl-uikit-select'));
141
+ const input = renderResult.container.querySelector('input');
142
+ expect(input).not.toBeNull();
143
+ await user.type(input as HTMLInputElement, '2');
144
+
145
+ const options = await renderResult.findAllByRole('option');
146
+ expect(options.length).toBe(1);
147
+ expect(options[0].textContent).toBe('Option 2');
148
+ });
149
+
150
+ test('shows empty state when filter yields no options', async () => {
151
+ const user = userEvent.setup();
152
+ const result = render(
153
+ <ThemeContextProvider>
154
+ <Select
155
+ filterable
156
+ options={defaultOptions}
157
+ value=''
158
+ onValueChange={() => {}}
159
+ />
160
+ </ThemeContextProvider>
161
+ );
162
+
163
+ await user.click(result.getByTestId('ucl-uikit-select'));
164
+ await user.type(result.getByRole('searchbox'), 'zzz');
165
+
166
+ expect(result.getByText('No options')).toBeInTheDocument();
167
+ expect(result.queryAllByTestId('ucl-uikit-select__option')).toHaveLength(0);
168
+ });
169
+
170
+ test('clears filter text after selecting an option', async () => {
171
+ const user = userEvent.setup();
172
+ const ControlledSelect = () => {
173
+ const [value, setValue] = useState('');
174
+ return (
175
+ <Select
176
+ filterable
177
+ options={defaultOptions}
178
+ value={value}
179
+ onValueChange={(next) => setValue(next as string)}
180
+ />
181
+ );
182
+ };
183
+
184
+ const result = render(
185
+ <ThemeContextProvider>
186
+ <ControlledSelect />
187
+ </ThemeContextProvider>
188
+ );
189
+
190
+ await user.click(result.getByTestId('ucl-uikit-select'));
191
+ await user.type(result.getByRole('searchbox'), '2');
192
+ await user.click(result.getByText('Option 2'));
193
+
194
+ await user.click(result.getByTestId('ucl-uikit-select'));
195
+ expect(result.getByRole('searchbox')).toHaveValue('');
196
+ expect(result.getAllByTestId('ucl-uikit-select__option')).toHaveLength(
197
+ defaultOptions.length
198
+ );
199
+ });
200
+
201
+ test('clears filter text when dropdown closes', async () => {
202
+ const user = userEvent.setup();
203
+ const result = render(
204
+ <ThemeContextProvider>
205
+ <Select
206
+ filterable
207
+ options={defaultOptions}
208
+ value=''
209
+ onValueChange={() => {}}
210
+ />
211
+ </ThemeContextProvider>
212
+ );
213
+
214
+ await user.click(result.getByTestId('ucl-uikit-select'));
215
+ await user.type(result.getByRole('searchbox'), '1');
216
+
217
+ await user.click(document.body);
218
+ await user.click(result.getByTestId('ucl-uikit-select'));
219
+
220
+ expect(result.getByRole('searchbox')).toHaveValue('');
221
+ });
222
+
223
+ test('keyboard navigation uses filtered options', async () => {
224
+ const user = userEvent.setup();
225
+ const options = [
226
+ { label: 'Alpha', value: 'alpha' },
227
+ { label: 'Echo', value: 'echo' },
228
+ { label: 'Gamma', value: 'gamma' },
229
+ ];
230
+ const changeSpy = vi.fn();
231
+
232
+ const ControlledSelect = () => {
233
+ const [value, setValue] = useState('');
234
+ const handleChange = vi.fn((next, ev) => {
235
+ setValue(next as string);
236
+ changeSpy(next, ev);
237
+ });
238
+ return (
239
+ <Select
240
+ filterable
241
+ options={options}
242
+ value={value}
243
+ onValueChange={handleChange}
244
+ />
245
+ );
246
+ };
247
+
248
+ const result = render(
249
+ <ThemeContextProvider>
250
+ <ControlledSelect />
251
+ </ThemeContextProvider>
252
+ );
253
+
254
+ await user.click(result.getByTestId('ucl-uikit-select'));
255
+ await user.type(result.getByRole('searchbox'), 'a');
256
+ expect(result.getAllByTestId('ucl-uikit-select__option')).toHaveLength(2);
257
+
258
+ await user.keyboard('{ArrowDown}');
259
+ await user.keyboard('{ArrowDown}');
260
+ await user.keyboard('{ArrowDown}');
261
+
262
+ expect(changeSpy.mock.calls.map(([val]) => val)).toEqual([
263
+ 'alpha',
264
+ 'gamma',
265
+ 'alpha',
266
+ ]);
267
+ });
268
+
269
+ test('selectionBehaviour=commit does not call onValueChange on arrow keys', async () => {
270
+ const user = userEvent.setup();
271
+ const changeSpy = vi.fn();
272
+
273
+ const ControlledSelect = () => {
274
+ const [value, setValue] = useState('');
275
+ return (
276
+ <Select
277
+ options={defaultOptions}
278
+ value={value}
279
+ selectionBehaviour='commit'
280
+ onValueChange={(next, ev) => {
281
+ setValue(next as string);
282
+ changeSpy(next, ev);
283
+ }}
284
+ />
285
+ );
286
+ };
287
+
288
+ const result = render(
289
+ <ThemeContextProvider>
290
+ <ControlledSelect />
291
+ </ThemeContextProvider>
292
+ );
293
+
294
+ await user.click(result.getByTestId('ucl-uikit-select'));
295
+ await user.keyboard('{ArrowDown}');
296
+ await user.keyboard('{ArrowDown}');
297
+ await user.keyboard('{ArrowDown}');
298
+
299
+ expect(changeSpy).not.toHaveBeenCalled();
300
+ });
301
+
302
+ test('selectionBehaviour=commit commits highlighted option on Enter', async () => {
303
+ const user = userEvent.setup();
304
+ const changeSpy = vi.fn();
305
+
306
+ const ControlledSelect = () => {
307
+ const [value, setValue] = useState('');
308
+ return (
309
+ <Select
310
+ options={defaultOptions}
311
+ value={value}
312
+ selectionBehaviour='commit'
313
+ onValueChange={(next, ev) => {
314
+ setValue(next as string);
315
+ changeSpy(next, ev);
316
+ }}
317
+ />
318
+ );
319
+ };
320
+
321
+ const result = render(
322
+ <ThemeContextProvider>
323
+ <ControlledSelect />
324
+ </ThemeContextProvider>
325
+ );
326
+
327
+ await user.click(result.getByTestId('ucl-uikit-select'));
328
+ await user.keyboard('{ArrowDown}');
329
+ await user.keyboard('{ArrowDown}');
330
+ await user.keyboard('{Enter}');
331
+
332
+ expect(changeSpy.mock.calls.map(([val]) => val)).toEqual(['2']);
333
+ expect(
334
+ result.queryByTestId('ucl-uikit-select__panel')
335
+ ).not.toBeInTheDocument();
336
+ expect(result.getByTestId('ucl-uikit-select')).toHaveTextContent(
337
+ 'Option 2'
338
+ );
339
+ });
340
+
341
+ test('keyboard navigation does not get stuck on duplicate values', async () => {
342
+ const user = userEvent.setup();
343
+ const options = [
344
+ { label: 'First duplicate', value: 'dup' },
345
+ { label: 'Second duplicate', value: 'dup' },
346
+ { label: 'Unique', value: 'unique' },
347
+ ];
348
+ const changeSpy = vi.fn();
349
+
350
+ const ControlledSelect = () => {
351
+ const [value, setValue] = useState('');
352
+ const handleChange = vi.fn((next, ev) => {
353
+ setValue(next as string);
354
+ changeSpy(next, ev);
355
+ });
356
+ return (
357
+ <Select
358
+ options={options}
359
+ value={value}
360
+ onValueChange={handleChange}
361
+ />
362
+ );
363
+ };
364
+
365
+ const result = render(
366
+ <ThemeContextProvider>
367
+ <ControlledSelect />
368
+ </ThemeContextProvider>
369
+ );
370
+
371
+ await user.click(result.getByTestId('ucl-uikit-select'));
372
+ await user.keyboard('{ArrowDown}');
373
+ await user.keyboard('{ArrowDown}');
374
+ await user.keyboard('{ArrowDown}');
375
+
376
+ expect(changeSpy.mock.calls.map(([val]) => val)).toEqual([
377
+ 'dup',
378
+ 'dup',
379
+ 'unique',
380
+ ]);
381
+
382
+ const optionElements = result.getAllByRole('option');
383
+ expect(
384
+ optionElements.filter((el) => el.getAttribute('aria-selected') === 'true')
385
+ ).toHaveLength(1);
386
+ expect(optionElements[2]).toHaveAttribute('aria-selected', 'true');
387
+ });
388
+
389
+ test('keeps duplicate label identity when selecting by click', async () => {
390
+ const user = userEvent.setup();
391
+ const options = [
392
+ { label: 'First duplicate', value: 'dup' },
393
+ { label: 'Second duplicate', value: 'dup' },
394
+ ];
395
+
396
+ const ControlledSelect = () => {
397
+ const [value, setValue] = useState('');
398
+ return (
399
+ <Select
400
+ options={options}
401
+ value={value}
402
+ onValueChange={(next) => setValue(next as string)}
403
+ />
404
+ );
405
+ };
406
+
407
+ const result = render(
408
+ <ThemeContextProvider>
409
+ <ControlledSelect />
410
+ </ThemeContextProvider>
411
+ );
412
+
413
+ await user.click(result.getByTestId('ucl-uikit-select'));
414
+ await user.click(result.getAllByRole('option')[1]);
415
+
416
+ expect(result.getByTestId('ucl-uikit-select')).toHaveTextContent(
417
+ 'Second duplicate'
418
+ );
419
+ });
420
+
421
+ test('filterable select keeps option clicks working while the filter input is focused', async () => {
422
+ const user = userEvent.setup();
423
+
424
+ const ControlledSelect = () => {
425
+ const [value, setValue] = useState('');
426
+ return (
427
+ <Select
428
+ filterable
429
+ options={defaultOptions}
430
+ value={value}
431
+ onValueChange={(next) => setValue(next as string)}
432
+ />
433
+ );
434
+ };
435
+
436
+ const result = render(
437
+ <ThemeContextProvider>
438
+ <ControlledSelect />
439
+ </ThemeContextProvider>
440
+ );
441
+
442
+ await user.click(result.getByTestId('ucl-uikit-select'));
443
+ const filterInput = result.getByRole('searchbox');
444
+ expect(filterInput).toHaveFocus();
445
+
446
+ await user.click(result.getByRole('option', { name: 'Option 2' }));
447
+
448
+ expect(
449
+ result.queryByTestId('ucl-uikit-select__panel')
450
+ ).not.toBeInTheDocument();
451
+ expect(result.getByTestId('ucl-uikit-select')).toHaveTextContent(
452
+ 'Option 2'
453
+ );
454
+ });
455
+
456
+ test('forwards id and title attributes to the combobox', () => {
457
+ const result = render(
458
+ <ThemeContextProvider>
459
+ <Select
460
+ id='my-select'
461
+ title='Select an option'
462
+ options={defaultOptions}
463
+ value=''
464
+ onValueChange={() => {}}
465
+ />
466
+ </ThemeContextProvider>
467
+ );
468
+
469
+ const combobox = result.getByRole('combobox');
470
+ expect(combobox).toHaveAttribute('id', 'my-select');
471
+ expect(combobox).toHaveAttribute('title', 'Select an option');
472
+ });
473
+
474
+ test('applies filterInputProps to the filter input', async () => {
475
+ const user = userEvent.setup();
476
+ const result = render(
477
+ <ThemeContextProvider>
478
+ <div>
479
+ <span id='filter-hint'>Filter options by text</span>
480
+ <Select
481
+ filterable
482
+ options={defaultOptions}
483
+ value=''
484
+ onValueChange={() => {}}
485
+ filterInputProps={{ 'aria-describedby': 'filter-hint' }}
486
+ />
487
+ </div>
488
+ </ThemeContextProvider>
489
+ );
490
+
491
+ await user.click(result.getByRole('combobox'));
492
+ const filterInput = result.getByRole('searchbox');
493
+ expect(filterInput).toHaveAttribute('aria-describedby', 'filter-hint');
494
+ });
495
+
496
+ test('opens filter input on keyboard focus', async () => {
497
+ const user = userEvent.setup();
498
+ const result = render(
499
+ <ThemeContextProvider>
500
+ <Select
501
+ filterable
502
+ options={defaultOptions}
503
+ value=''
504
+ onValueChange={() => {}}
505
+ />
506
+ </ThemeContextProvider>
507
+ );
508
+
509
+ await user.tab();
510
+
511
+ const filterInput = await result.findByRole('searchbox');
512
+ expect(filterInput).toHaveFocus();
513
+ expect(result.getByRole('listbox')).toBeInTheDocument();
514
+ });
515
+
516
+ test('pressing Escape closes dropdown and clears filter', async () => {
517
+ const user = userEvent.setup();
518
+ const result = render(
519
+ <ThemeContextProvider>
520
+ <Select
521
+ filterable
522
+ options={defaultOptions}
523
+ value=''
524
+ onValueChange={() => {}}
525
+ />
526
+ </ThemeContextProvider>
527
+ );
528
+
529
+ const combobox = result.getByTestId('ucl-uikit-select');
530
+ await user.click(combobox);
531
+ await user.type(result.getByRole('searchbox'), '1');
532
+ await user.keyboard('{Escape}');
533
+
534
+ expect(
535
+ result.queryByTestId('ucl-uikit-select__panel')
536
+ ).not.toBeInTheDocument();
537
+ expect(combobox).toHaveFocus();
538
+
539
+ await user.click(combobox);
540
+ expect(result.getByRole('searchbox')).toHaveValue('');
541
+ });
542
+
543
+ test('announces active option and option position metadata', async () => {
544
+ const user = userEvent.setup();
545
+ const result = render(
546
+ <ThemeContextProvider>
547
+ <Select
548
+ options={defaultOptions}
549
+ value=''
550
+ onValueChange={() => {}}
551
+ />
552
+ </ThemeContextProvider>
553
+ );
554
+
555
+ const combobox = result.getByRole('combobox');
556
+ await user.click(combobox);
557
+
558
+ const options = result.getAllByRole('option');
559
+ expect(options[0]).toHaveAttribute('aria-posinset', '1');
560
+ expect(options[0]).toHaveAttribute('aria-setsize', '3');
561
+ expect(options[1]).toHaveAttribute('aria-posinset', '2');
562
+ expect(options[1]).toHaveAttribute('aria-setsize', '3');
563
+
564
+ await user.keyboard('{ArrowDown}');
565
+ expect(combobox).toHaveAttribute('aria-activedescendant', options[0].id);
566
+
567
+ await user.keyboard('{ArrowDown}');
568
+ expect(combobox).toHaveAttribute('aria-activedescendant', options[1].id);
569
+ });
570
+
571
+ test('filterable input tracks active descendant while navigating options', async () => {
572
+ const user = userEvent.setup();
573
+ const result = render(
574
+ <ThemeContextProvider>
575
+ <Select
576
+ filterable
577
+ options={defaultOptions}
578
+ value=''
579
+ onValueChange={() => {}}
580
+ />
581
+ </ThemeContextProvider>
582
+ );
583
+
584
+ await user.click(result.getByRole('combobox'));
585
+ const filterInput = result.getByRole('searchbox');
586
+ expect(filterInput).toHaveFocus();
587
+
588
+ const options = result.getAllByRole('option');
589
+ await user.keyboard('{ArrowDown}');
590
+ expect(filterInput).toHaveAttribute('aria-activedescendant', options[0].id);
591
+
592
+ await user.keyboard('{ArrowDown}');
593
+ expect(filterInput).toHaveAttribute('aria-activedescendant', options[1].id);
594
+ });
595
+
596
+ test('open dropdown closes when tabbing to the next element', async () => {
597
+ const user = userEvent.setup();
598
+ const result = render(
599
+ <ThemeContextProvider>
600
+ <div>
601
+ <Select
602
+ options={defaultOptions}
603
+ value=''
604
+ onValueChange={() => {}}
605
+ />
606
+ <button type='button'>After</button>
607
+ </div>
608
+ </ThemeContextProvider>
609
+ );
610
+
611
+ await user.click(result.getByRole('combobox'));
612
+ expect(result.getByRole('listbox')).toBeInTheDocument();
613
+
614
+ await user.tab();
615
+
616
+ expect(result.queryByRole('listbox')).not.toBeInTheDocument();
617
+ expect(result.getByRole('button', { name: 'After' })).toHaveFocus();
618
+ });
619
+
620
+ test('open filterable dropdown closes when tabbing to the next element', async () => {
621
+ const user = userEvent.setup();
622
+ const result = render(
623
+ <ThemeContextProvider>
624
+ <div>
625
+ <Select
626
+ filterable
627
+ options={defaultOptions}
628
+ value=''
629
+ onValueChange={() => {}}
630
+ />
631
+ <button type='button'>After</button>
632
+ </div>
633
+ </ThemeContextProvider>
634
+ );
635
+
636
+ await user.click(result.getByRole('combobox'));
637
+ expect(result.getByRole('searchbox')).toHaveFocus();
638
+ expect(result.getByRole('listbox')).toBeInTheDocument();
639
+
640
+ await user.tab();
641
+
642
+ expect(result.queryByRole('listbox')).not.toBeInTheDocument();
643
+ expect(result.getByRole('button', { name: 'After' })).toHaveFocus();
644
+ });
645
+
646
+ test('open dropdown closes when shift-tabbing to the previous element', async () => {
647
+ const user = userEvent.setup();
648
+ const result = render(
649
+ <ThemeContextProvider>
650
+ <div>
651
+ <button type='button'>Before</button>
652
+ <Select
653
+ options={defaultOptions}
654
+ value=''
655
+ onValueChange={() => {}}
656
+ />
657
+ </div>
658
+ </ThemeContextProvider>
659
+ );
660
+
661
+ await user.tab();
662
+ await user.tab();
663
+ expect(result.getByRole('combobox')).toHaveFocus();
664
+
665
+ await user.keyboard('{Enter}');
666
+ expect(result.getByRole('listbox')).toBeInTheDocument();
667
+
668
+ await user.tab({ shift: true });
669
+
670
+ expect(result.queryByRole('listbox')).not.toBeInTheDocument();
671
+ expect(result.getByRole('button', { name: 'Before' })).toHaveFocus();
672
+ });
673
+
674
+ test('open filterable dropdown closes when shift-tabbing to the previous element', async () => {
675
+ const user = userEvent.setup();
676
+ const result = render(
677
+ <ThemeContextProvider>
678
+ <div>
679
+ <button type='button'>Before</button>
680
+ <Select
681
+ filterable
682
+ options={defaultOptions}
683
+ value=''
684
+ onValueChange={() => {}}
685
+ />
686
+ </div>
687
+ </ThemeContextProvider>
688
+ );
689
+
690
+ await user.tab();
691
+ await user.tab();
692
+ expect(result.getByRole('searchbox')).toHaveFocus();
693
+ expect(result.getByRole('listbox')).toBeInTheDocument();
694
+
695
+ await user.tab({ shift: true });
696
+
697
+ expect(result.queryByRole('listbox')).not.toBeInTheDocument();
698
+ expect(result.getByRole('button', { name: 'Before' })).toHaveFocus();
699
+ });
700
+
701
+ test('open filterable dropdown at end of tab order does not trap focus on Tab', async () => {
702
+ const result = render(
703
+ <ThemeContextProvider>
704
+ <Select
705
+ filterable
706
+ options={defaultOptions}
707
+ value=''
708
+ onValueChange={() => {}}
709
+ />
710
+ </ThemeContextProvider>
711
+ );
712
+
713
+ fireEvent.click(result.getByRole('combobox'));
714
+ const searchbox = result.getByRole('searchbox');
715
+ expect(result.getByRole('listbox')).toBeInTheDocument();
716
+
717
+ const tabEvent = createEvent.keyDown(searchbox, { key: 'Tab' });
718
+ const dispatchResult = fireEvent(searchbox, tabEvent);
719
+
720
+ expect(dispatchResult).toBe(true);
721
+ expect(result.queryByRole('listbox')).not.toBeInTheDocument();
722
+ });
723
+
724
+ test('open filterable dropdown at start of tab order does not trap focus on shift-tab', async () => {
725
+ const user = userEvent.setup();
726
+ const result = render(
727
+ <ThemeContextProvider>
728
+ <Select
729
+ filterable
730
+ options={defaultOptions}
731
+ value=''
732
+ onValueChange={() => {}}
733
+ />
734
+ </ThemeContextProvider>
735
+ );
736
+
737
+ await user.tab();
738
+ const searchbox = result.getByRole('searchbox');
739
+ expect(searchbox).toHaveFocus();
740
+ expect(result.getByRole('listbox')).toBeInTheDocument();
741
+
742
+ const shiftTabEvent = createEvent.keyDown(searchbox, {
743
+ key: 'Tab',
744
+ shiftKey: true,
745
+ });
746
+ const dispatchResult = fireEvent(searchbox, shiftTabEvent);
747
+
748
+ expect(dispatchResult).toBe(true);
749
+ expect(result.queryByRole('listbox')).not.toBeInTheDocument();
750
+ });
751
+
752
+ test('clear button appears for clearable select and clears to null', async () => {
753
+ const user = userEvent.setup();
754
+ const changeSpy = vi.fn();
755
+
756
+ const ControlledSelect = () => {
757
+ const [value, setValue] = useState<string | null>('2');
758
+ return (
759
+ <Select
760
+ clearable
761
+ options={defaultOptions}
762
+ value={value}
763
+ placeholder='Select an option'
764
+ onValueChange={(next, ev) => {
765
+ setValue(next as string | null);
766
+ changeSpy(next, ev);
767
+ }}
768
+ />
769
+ );
770
+ };
771
+
772
+ const result = render(
773
+ <ThemeContextProvider>
774
+ <ControlledSelect />
775
+ </ThemeContextProvider>
776
+ );
777
+
778
+ const clearButton = result.getByTestId(
779
+ 'ucl-uikit-select__visible-field-clear-button'
780
+ );
781
+ expect(result.getByRole('combobox')).toHaveTextContent('Option 2');
782
+
783
+ await user.click(clearButton);
784
+
785
+ expect(changeSpy).toHaveBeenCalledWith(null, expect.any(Object));
786
+ expect(result.getByRole('combobox')).toHaveTextContent('Select an option');
787
+ expect(
788
+ result.queryByTestId('ucl-uikit-select__visible-field-clear-button')
789
+ ).not.toBeInTheDocument();
790
+ });
791
+
792
+ test('disabled clearable select does not show clear button', () => {
793
+ const result = render(
794
+ <ThemeContextProvider>
795
+ <Select
796
+ clearable
797
+ disabled
798
+ options={defaultOptions}
799
+ value='1'
800
+ onValueChange={() => {}}
801
+ />
802
+ </ThemeContextProvider>
803
+ );
804
+
805
+ expect(
806
+ result.queryByTestId('ucl-uikit-select__visible-field-clear-button')
807
+ ).not.toBeInTheDocument();
808
+ });
119
809
  });