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
@@ -4,7 +4,7 @@ exports[`Select > Snapshot: default 1`] = `
4
4
  <div
5
5
  aria-expanded="false"
6
6
  aria-haspopup="listbox"
7
- class="ucl-uikit-select css-1g9yg1n"
7
+ class="ucl-uikit-select css-a6n340"
8
8
  data-testid="ucl-uikit-select"
9
9
  role="combobox"
10
10
  tabindex="0"
@@ -17,9 +17,10 @@ exports[`Select > Snapshot: default 1`] = `
17
17
  class="css-16t5nns"
18
18
  />
19
19
  <svg
20
- class="ucl-uikit-icon css-hseitk"
20
+ class="ucl-uikit-icon css-1pg9a4v"
21
21
  data-testid="ucl-uikit-icon"
22
22
  fill="none"
23
+ focusable="false"
23
24
  height="24"
24
25
  stroke="currentColor"
25
26
  stroke-linecap="round"
@@ -29,8 +30,8 @@ exports[`Select > Snapshot: default 1`] = `
29
30
  width="24"
30
31
  xmlns="http://www.w3.org/2000/svg"
31
32
  >
32
- <polyline
33
- points="6 9 12 15 18 9"
33
+ <path
34
+ d="m6 9 6 6 6-6"
34
35
  />
35
36
  </svg>
36
37
  </div>
@@ -7,6 +7,7 @@ const NAME = 'ucl-uikit-select__option';
7
7
 
8
8
  const CustomOption = <T extends string | number>({
9
9
  value,
10
+ optionIndex,
10
11
  isSelected = false,
11
12
  onSelect,
12
13
  lineBreak = false,
@@ -29,10 +30,16 @@ const CustomOption = <T extends string | number>({
29
30
  }, [isSelected]);
30
31
 
31
32
  const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
32
- onSelect(event, value);
33
+ onSelect(event, value, optionIndex);
33
34
  event.stopPropagation(); // Otherwise the panel will open again instantaneously
34
35
  };
35
36
 
37
+ const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {
38
+ // Keep focus on the combobox/filter input so blur does not close the panel
39
+ // before the option click handler runs.
40
+ event.preventDefault();
41
+ };
42
+
36
43
  const baseStyle = css`
37
44
  gap: 16px;
38
45
  min-height: 40px;
@@ -41,11 +48,11 @@ const CustomOption = <T extends string | number>({
41
48
  font-family: ${theme.font.family.primary};
42
49
  font-size: ${theme.font.size.f16};
43
50
  line-height: ${theme.font.lineHeight.h140};
44
- background-color: ${theme.color.neutral.white};
51
+ background-color: ${theme.colour.fill.inverse};
45
52
  overflow: hidden;
46
53
 
47
54
  &:hover {
48
- background-color: ${theme.color.neutral.grey5};
55
+ background-color: ${theme.colour.fill.brandSubtleHover};
49
56
  }
50
57
  `;
51
58
 
@@ -59,7 +66,7 @@ const CustomOption = <T extends string | number>({
59
66
  `;
60
67
 
61
68
  const selectedStyle = css`
62
- background-color: ${theme.color.neutral.grey5};
69
+ background-color: ${theme.colour.fill.brandSubtleSelected};
63
70
  `;
64
71
 
65
72
  const style = cx(
@@ -73,6 +80,7 @@ const CustomOption = <T extends string | number>({
73
80
 
74
81
  return (
75
82
  <div
83
+ onMouseDown={handleMouseDown}
76
84
  onClick={handleClick}
77
85
  className={style}
78
86
  data-testid={testId}
@@ -1,22 +1,43 @@
1
- import { useState, useRef, useEffect } from 'react';
1
+ import { useState, useRef, useEffect, useMemo, useId } from 'react';
2
2
  import { css, cx } from '@emotion/css';
3
- import { VisibleField, Panel, CustomOption } from '.';
3
+ import { VisibleField, Panel, CustomOption, FilterInput } from '.';
4
4
  import { useTheme } from '../../../theme';
5
- import type { CustomSelectProps } from '../Select.types';
5
+ import type { InternalSelectProps } from '../Select.types';
6
6
 
7
7
  const NAME = 'ucl-uikit-select';
8
+ const FOCUSABLE_ELEMENTS = [
9
+ 'a[href]',
10
+ 'button:not([disabled])',
11
+ 'textarea:not([disabled])',
12
+ 'input:not([disabled])',
13
+ 'select:not([disabled])',
14
+ '[tabindex]:not([tabindex="-1"])',
15
+ '[contenteditable="true"]',
16
+ 'audio[controls]',
17
+ 'video[controls]',
18
+ 'details > summary:first-of-type',
19
+ ].join(', ');
20
+
21
+ type CustomSelectProps<T> = Omit<
22
+ InternalSelectProps<T>,
23
+ 'native' | 'nativeHtmlAttributes'
24
+ >;
8
25
 
9
26
  const CustomSelect = <T extends string | number>({
27
+ selectionBehaviour = 'focus',
10
28
  value,
11
29
  options = [],
12
30
  onValueChange,
13
31
  disabled,
32
+ clearable = false,
14
33
  placeholder,
15
34
  lineBreak = false,
35
+ filterInputProps,
16
36
  width,
17
37
  testId = NAME,
18
38
  className,
19
39
  panelClassName,
40
+ filterable = false,
20
41
  ref,
21
42
  ...props
22
43
  }: CustomSelectProps<T>) => {
@@ -35,11 +56,108 @@ const CustomSelect = <T extends string | number>({
35
56
  console.warn('Select option icon prop is deprecated; it has no effect.');
36
57
  }
37
58
 
59
+ const duplicateOptionValues = useMemo(() => {
60
+ const seen = new Set<T>();
61
+ const duplicates = new Set<T>();
62
+
63
+ for (const option of options) {
64
+ if (seen.has(option.value)) duplicates.add(option.value);
65
+ else seen.add(option.value);
66
+ }
67
+
68
+ return Array.from(duplicates);
69
+ }, [options]);
70
+
71
+ useEffect(() => {
72
+ if (duplicateOptionValues.length > 0) {
73
+ console.warn(
74
+ `Select options contain non-unique values: ${duplicateOptionValues
75
+ .map(String)
76
+ .join(', ')}`
77
+ );
78
+ }
79
+ }, [duplicateOptionValues]);
80
+
38
81
  const internalRef = useRef<HTMLDivElement>(null);
39
- const effectiveRef = ref || internalRef;
82
+ const effectiveRef = internalRef;
83
+ const openedViaFocusRef = useRef(false);
84
+ const skipOpenOnFocusRef = useRef(false);
85
+
86
+ const setRefs = (node: HTMLDivElement | null) => {
87
+ internalRef.current = node;
88
+ if (typeof ref === 'function') {
89
+ ref(node);
90
+ } else if (
91
+ ref &&
92
+ 'current' in
93
+ (ref as React.RefObject<HTMLDivElement | HTMLSelectElement | null>)
94
+ ) {
95
+ (
96
+ ref as React.RefObject<HTMLDivElement | HTMLSelectElement | null>
97
+ ).current = node;
98
+ }
99
+ };
40
100
 
41
101
  const [theme] = useTheme();
42
102
  const [isOpen, setIsOpen] = useState(false);
103
+ const [filterText, setFilterText] = useState('');
104
+ const [activeOptionIndex, setActiveOptionIndex] = useState<number | null>(
105
+ null
106
+ );
107
+ const [selectedOptionIndex, setSelectedOptionIndex] = useState<number | null>(
108
+ null
109
+ );
110
+ const filterInputRef = useRef<HTMLInputElement | null>(null);
111
+ const clearButtonRef = useRef<HTMLButtonElement | null>(null);
112
+ const reactId = useId();
113
+ const idBase = props.id ?? `${testId}-${reactId.replace(/[:]/g, '')}`;
114
+ const listboxId = `${idBase}-listbox`;
115
+
116
+ // Returns a list of indexes of options that are currently visible based on the filter text
117
+ const visibleOptionIndexes = useMemo(() => {
118
+ const normalizedFilterText = filterText.toLowerCase();
119
+ return filterable
120
+ ? options.reduce<number[]>((visibleIndexes, option, index) => {
121
+ if (option.label.toLowerCase().includes(normalizedFilterText)) {
122
+ visibleIndexes.push(index);
123
+ }
124
+ return visibleIndexes;
125
+ }, [])
126
+ : options.map((_, index) => index);
127
+ }, [filterable, options, filterText]);
128
+ const visibleOptions = useMemo(
129
+ () => visibleOptionIndexes.map((index) => options[index]),
130
+ [visibleOptionIndexes, options]
131
+ );
132
+
133
+ useEffect(() => {
134
+ if (!isOpen && filterText) setFilterText('');
135
+ }, [isOpen, filterText]);
136
+
137
+ useEffect(() => {
138
+ const matchingIndexes = options.reduce<number[]>(
139
+ (matches, option, index) => {
140
+ if (option.value === value) matches.push(index);
141
+ return matches;
142
+ },
143
+ []
144
+ );
145
+
146
+ if (matchingIndexes.length === 0) {
147
+ if (selectedOptionIndex !== null) setSelectedOptionIndex(null);
148
+ return;
149
+ }
150
+
151
+ // If the currently selected option is among the matches, keep it selected
152
+ if (
153
+ selectedOptionIndex !== null &&
154
+ matchingIndexes.includes(selectedOptionIndex)
155
+ ) {
156
+ return;
157
+ }
158
+ // Otherwise, select the first matching option
159
+ setSelectedOptionIndex(matchingIndexes[0]);
160
+ }, [options, selectedOptionIndex, value]);
43
161
 
44
162
  useEffect(() => {
45
163
  const handleClickOutside = (event: MouseEvent) => {
@@ -50,17 +168,97 @@ const CustomSelect = <T extends string | number>({
50
168
  setIsOpen(false);
51
169
  }
52
170
  };
171
+
53
172
  document.addEventListener('mousedown', handleClickOutside);
54
173
  return () => {
55
174
  document.removeEventListener('mousedown', handleClickOutside);
56
175
  };
57
176
  }, [effectiveRef]);
58
177
 
178
+ const handleBlur = (event: React.FocusEvent<HTMLElement>) => {
179
+ const nextFocusedElement = event.relatedTarget as Node | null;
180
+ if (
181
+ effectiveRef.current &&
182
+ nextFocusedElement &&
183
+ effectiveRef.current.contains(nextFocusedElement)
184
+ ) {
185
+ return;
186
+ }
187
+ closePanel();
188
+ };
189
+
190
+ const isFocusableElement = (element: HTMLElement) => {
191
+ if (!element.isConnected) return false;
192
+ if (element instanceof HTMLInputElement && element.type === 'hidden') {
193
+ return false;
194
+ }
195
+ if (element.tabIndex < 0) return false;
196
+ if ('disabled' in element && element.disabled) return false;
197
+
198
+ const style = window.getComputedStyle(element);
199
+ if (style.display === 'none' || style.visibility === 'hidden') {
200
+ return false;
201
+ }
202
+
203
+ return true;
204
+ };
205
+
206
+ const moveFocusOutsideSelect = (isReverse: boolean) => {
207
+ if (!effectiveRef.current) return false;
208
+
209
+ const focusableElements = Array.from(
210
+ document.querySelectorAll<HTMLElement>(FOCUSABLE_ELEMENTS)
211
+ ).filter(isFocusableElement);
212
+
213
+ const currentElement = filterInputRef.current;
214
+ if (!currentElement) return false;
215
+
216
+ const currentIndex = focusableElements.indexOf(currentElement);
217
+ if (currentIndex === -1) return false;
218
+
219
+ const step = isReverse ? -1 : 1;
220
+ let nextIndex = currentIndex + step;
221
+
222
+ while (nextIndex >= 0 && nextIndex < focusableElements.length) {
223
+ const candidate = focusableElements[nextIndex];
224
+ if (!effectiveRef.current.contains(candidate)) {
225
+ candidate.focus();
226
+ if (document.activeElement === candidate) {
227
+ return true;
228
+ }
229
+ }
230
+ nextIndex += step;
231
+ }
232
+
233
+ return false;
234
+ };
235
+
236
+ const handleFilterTabOut = (event: React.KeyboardEvent<HTMLInputElement>) => {
237
+ closePanel();
238
+ // If this widget gains more tabbable descendants, revisit this handoff.
239
+ if (!event.shiftKey && clearButtonRef.current) {
240
+ clearButtonRef.current.focus();
241
+ if (document.activeElement === clearButtonRef.current) {
242
+ event.preventDefault();
243
+ return;
244
+ }
245
+ }
246
+ if (moveFocusOutsideSelect(event.shiftKey)) {
247
+ event.preventDefault();
248
+ }
249
+ };
250
+
59
251
  useEffect(() => {
60
252
  // Close the dropdown if it becomes disabled
61
253
  if (disabled && isOpen) setIsOpen(false);
62
254
  }, [disabled, isOpen]);
63
255
 
256
+ useEffect(() => {
257
+ if (filterable && isOpen && filterInputRef.current) {
258
+ filterInputRef.current.focus();
259
+ }
260
+ }, [filterable, isOpen]);
261
+
64
262
  const togglePanel = () => {
65
263
  if (!disabled) setIsOpen((prev) => !prev);
66
264
  };
@@ -70,18 +268,93 @@ const CustomSelect = <T extends string | number>({
70
268
  };
71
269
 
72
270
  const closePanel = () => {
73
- if (!disabled) setIsOpen(false);
271
+ if (!disabled) {
272
+ setIsOpen(false);
273
+ setActiveOptionIndex(null);
274
+ }
275
+ };
276
+
277
+ const handleClick = (event: React.MouseEvent) => {
278
+ if (disabled) return;
279
+ if (openedViaFocusRef.current) {
280
+ openedViaFocusRef.current = false;
281
+ return;
282
+ }
283
+ if (!isOpen) {
284
+ openPanel();
285
+ if (filterable && filterInputRef.current) {
286
+ filterInputRef.current.focus();
287
+ }
288
+ return;
289
+ }
290
+ // If filter is enabled and the click was on the input, keep it open
291
+ if (
292
+ filterable &&
293
+ filterInputRef.current &&
294
+ filterInputRef.current.contains(event.target as Node)
295
+ ) {
296
+ return;
297
+ }
298
+ closePanel();
299
+ };
300
+
301
+ const handleClear = (event: React.MouseEvent | React.KeyboardEvent) => {
302
+ if (disabled) return;
303
+ event.stopPropagation();
304
+ setSelectedOptionIndex(null);
305
+ setActiveOptionIndex(null);
306
+ setFilterText('');
307
+ closePanel();
308
+ effectiveRef.current?.focus();
309
+ onValueChange?.(null, event);
74
310
  };
75
311
 
76
312
  // Used by <CustomOption> and passed as prop
77
- const handleSelect = (event: React.MouseEvent, optionValue: T) => {
78
- if (onValueChange) onValueChange(optionValue, event);
313
+ const handleSelect = (
314
+ event: React.MouseEvent,
315
+ optionValue: T,
316
+ optionIndex?: number
317
+ ) => {
318
+ if (typeof optionIndex === 'number') {
319
+ setSelectedOptionIndex(optionIndex);
320
+ }
321
+ onValueChange?.(optionValue, event);
322
+ setFilterText('');
79
323
  closePanel();
80
324
  };
81
325
 
82
- const selectedOption = options.find((option) => option.value === value);
326
+ // Get the currently selected option object from its index among the visible options
327
+ const selectedOption =
328
+ selectedOptionIndex !== null ? options[selectedOptionIndex] : undefined;
329
+ // Get the index of the selected option among the visible options, or -1 if it's not visible
330
+ const selectedVisibleIndex =
331
+ selectedOptionIndex !== null
332
+ ? visibleOptionIndexes.indexOf(selectedOptionIndex)
333
+ : -1;
334
+ // Ensure the active option index is within bounds of the visible options
335
+ const effectiveActiveOptionIndex =
336
+ activeOptionIndex !== null &&
337
+ activeOptionIndex >= 0 &&
338
+ activeOptionIndex < visibleOptions.length
339
+ ? activeOptionIndex
340
+ : null;
341
+ // Get the index of the currently highlighted option among the visible options, or null if none is highlighted
342
+ const highlightedVisibleIndex =
343
+ effectiveActiveOptionIndex !== null
344
+ ? effectiveActiveOptionIndex
345
+ : selectedVisibleIndex >= 0
346
+ ? selectedVisibleIndex
347
+ : null;
348
+ const highlightedOptionSourceIndex =
349
+ highlightedVisibleIndex !== null
350
+ ? visibleOptionIndexes[highlightedVisibleIndex]
351
+ : null;
352
+ const activeDescendantId =
353
+ isOpen && highlightedOptionSourceIndex !== null
354
+ ? `${idBase}-option-${highlightedOptionSourceIndex}`
355
+ : undefined;
83
356
 
84
- const handleKeyDown = (event: React.KeyboardEvent) => {
357
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
85
358
  // Prevent scrolling the page when the select is open
86
359
  if (
87
360
  event.key === 'ArrowUp' ||
@@ -91,9 +364,48 @@ const CustomSelect = <T extends string | number>({
91
364
  )
92
365
  event.preventDefault();
93
366
 
367
+ // For current option index, activeOptionIndex > selectedVisibleIndex > value match > null
368
+ const getCurrentOptionIndex = () => {
369
+ if (
370
+ activeOptionIndex !== null &&
371
+ activeOptionIndex >= 0 &&
372
+ activeOptionIndex < visibleOptions.length
373
+ ) {
374
+ return activeOptionIndex;
375
+ }
376
+ if (selectedOptionIndex !== null) {
377
+ const selectedVisibleIndex =
378
+ visibleOptionIndexes.indexOf(selectedOptionIndex);
379
+ if (selectedVisibleIndex >= 0) {
380
+ return selectedVisibleIndex;
381
+ }
382
+ }
383
+ const selectedIndex = visibleOptions.findIndex(
384
+ (option) => option.value === value
385
+ );
386
+ return selectedIndex >= 0 ? selectedIndex : null;
387
+ };
388
+
94
389
  if (disabled) return;
95
390
 
96
391
  if (event.key === 'Enter') {
392
+ if (!isOpen) {
393
+ openPanel();
394
+ return;
395
+ }
396
+
397
+ if (selectionBehaviour === 'commit' && visibleOptions.length > 0) {
398
+ const currentOptionIndex = getCurrentOptionIndex();
399
+ if (currentOptionIndex !== null) {
400
+ const currentSourceIndex = visibleOptionIndexes[currentOptionIndex];
401
+ setSelectedOptionIndex(currentSourceIndex);
402
+ onValueChange?.(visibleOptions[currentOptionIndex].value, event);
403
+ setFilterText('');
404
+ }
405
+ closePanel();
406
+ return;
407
+ }
408
+
97
409
  togglePanel();
98
410
  return;
99
411
  }
@@ -103,6 +415,8 @@ const CustomSelect = <T extends string | number>({
103
415
  }
104
416
  if (isOpen && event.key === 'Escape') {
105
417
  closePanel();
418
+ skipOpenOnFocusRef.current = true;
419
+ event.currentTarget.focus();
106
420
  return;
107
421
  }
108
422
  // Select the previous option
@@ -111,17 +425,21 @@ const CustomSelect = <T extends string | number>({
111
425
  openPanel();
112
426
  return;
113
427
  }
114
- if (!value) {
115
- // Initialise at the last option if no value provided
116
- onValueChange?.(options[options.length - 1].value, event);
428
+ if (visibleOptions.length === 0) {
117
429
  return;
118
430
  }
119
- const currentOptionIndex = options.findIndex(
120
- (option) => option.value === value
121
- );
431
+ const currentOptionIndex = getCurrentOptionIndex();
122
432
  const previousOptionIndex =
123
- (currentOptionIndex - 1 + options.length) % options.length;
124
- onValueChange?.(options[previousOptionIndex].value, event);
433
+ currentOptionIndex === null
434
+ ? visibleOptions.length - 1
435
+ : (currentOptionIndex - 1 + visibleOptions.length) %
436
+ visibleOptions.length;
437
+ const previousSourceIndex = visibleOptionIndexes[previousOptionIndex];
438
+ setActiveOptionIndex(previousOptionIndex);
439
+ if (selectionBehaviour === 'focus') {
440
+ setSelectedOptionIndex(previousSourceIndex);
441
+ onValueChange?.(visibleOptions[previousOptionIndex].value, event);
442
+ }
125
443
  return;
126
444
  }
127
445
  // Select the next option
@@ -130,18 +448,36 @@ const CustomSelect = <T extends string | number>({
130
448
  openPanel();
131
449
  return;
132
450
  }
133
- if (!value) {
134
- // Initialise at the first option if no value provided
135
- onValueChange?.(options[0].value, event);
451
+ if (visibleOptions.length === 0) {
136
452
  return;
137
453
  }
138
- const currentOptionIndex = options.findIndex(
139
- (option) => option.value === value
140
- );
141
- const nextOptionIndex = (currentOptionIndex + 1) % options.length;
142
- onValueChange?.(options[nextOptionIndex].value, event);
454
+ const currentOptionIndex = getCurrentOptionIndex();
455
+ const nextOptionIndex =
456
+ currentOptionIndex === null
457
+ ? 0
458
+ : (currentOptionIndex + 1) % visibleOptions.length;
459
+ const nextSourceIndex = visibleOptionIndexes[nextOptionIndex];
460
+ setActiveOptionIndex(nextOptionIndex);
461
+ if (selectionBehaviour === 'focus') {
462
+ setSelectedOptionIndex(nextSourceIndex);
463
+ onValueChange?.(visibleOptions[nextOptionIndex].value, event);
464
+ }
465
+ return;
466
+ }
467
+ };
468
+
469
+ const handleFocus = (event: React.FocusEvent<HTMLDivElement>) => {
470
+ if (disabled) return;
471
+ if (skipOpenOnFocusRef.current) {
472
+ skipOpenOnFocusRef.current = false;
143
473
  return;
144
474
  }
475
+ const isKeyboardFocus = event.currentTarget.matches(':focus-visible');
476
+ if (filterable && isKeyboardFocus) {
477
+ openedViaFocusRef.current = true;
478
+ openPanel();
479
+ if (filterInputRef.current) filterInputRef.current.focus();
480
+ }
145
481
  };
146
482
 
147
483
  const baseStyle = css`
@@ -155,19 +491,16 @@ const CustomSelect = <T extends string | number>({
155
491
  height: 48px;
156
492
  box-sizing: border-box;
157
493
  padding: 0 16px;
158
- background-color: ${theme.color.neutral.white};
159
- color: ${theme.color.text.primary};
494
+ background-color: ${theme.colour.fill.inverse};
495
+ color: ${theme.colour.text.default};
160
496
  font-family: ${theme.font.family.primary};
161
497
  font-size: ${theme.font.size.f16};
162
- border: ${theme.border.b1} solid ${theme.color.neutral.grey60};
498
+ border: ${theme.border.b1} solid ${theme.colour.speciality.inputDefault};
163
499
  cursor: pointer;
164
500
  user-select: none;
165
501
 
166
- &:hover {
167
- ${!isOpen && `background-color: ${theme.color.neutral.grey5};`}
168
- }
169
-
170
- &:focus-visible {
502
+ &:focus-visible,
503
+ &:focus-within {
171
504
  outline: none;
172
505
  box-shadow: ${theme.boxShadow.focus};
173
506
  }
@@ -183,19 +516,28 @@ const CustomSelect = <T extends string | number>({
183
516
  }
184
517
  `;
185
518
 
519
+ const noOptionsStyle = css`
520
+ padding: ${theme.padding.p8} ${theme.padding.p16};
521
+ color: ${theme.color.text.secondary};
522
+ `;
523
+
186
524
  const style = cx(NAME, baseStyle, disabled && disabledStyle, className);
187
525
 
188
526
  return (
189
527
  <div
190
- onClick={togglePanel}
528
+ onClick={handleClick}
191
529
  onKeyDown={handleKeyDown}
192
- tabIndex={disabled ? -1 : 0}
530
+ onFocus={handleFocus}
531
+ onBlur={handleBlur}
532
+ tabIndex={disabled || (filterable && isOpen) ? -1 : 0}
193
533
  className={style}
194
534
  data-testid={testId}
195
- ref={effectiveRef}
535
+ ref={setRefs}
196
536
  role='combobox'
197
537
  aria-haspopup='listbox'
198
538
  aria-expanded={isOpen}
539
+ aria-controls={isOpen ? listboxId : undefined}
540
+ aria-activedescendant={activeDescendantId}
199
541
  {...props}
200
542
  >
201
543
  <VisibleField
@@ -203,26 +545,54 @@ const CustomSelect = <T extends string | number>({
203
545
  selectedOption={selectedOption}
204
546
  placeholder={placeholder}
205
547
  disabled={disabled}
206
- />
548
+ filterable={filterable}
549
+ clearable={clearable}
550
+ onClear={handleClear}
551
+ clearButtonRef={clearButtonRef}
552
+ >
553
+ {filterable && (
554
+ <FilterInput
555
+ value={filterText}
556
+ onChange={setFilterText}
557
+ placeholder={placeholder}
558
+ disabled={disabled}
559
+ inputRef={filterInputRef}
560
+ onBlur={handleBlur}
561
+ onTabOut={handleFilterTabOut}
562
+ ariaControls={listboxId}
563
+ ariaExpanded={isOpen}
564
+ ariaActiveDescendant={activeDescendantId}
565
+ {...filterInputProps}
566
+ />
567
+ )}
568
+ </VisibleField>
207
569
  {isOpen && (
208
570
  <Panel
209
571
  className={panelClassName}
572
+ id={listboxId}
210
573
  role='listbox'
211
574
  >
212
- {options.map((option) => (
575
+ {visibleOptions.map((option, index) => (
213
576
  <CustomOption<T>
214
- key={option.value}
577
+ key={`${String(option.value)}-${visibleOptionIndexes[index]}`}
578
+ id={`${idBase}-option-${visibleOptionIndexes[index]}`}
215
579
  value={option.value}
216
- isSelected={value === option.value}
580
+ optionIndex={visibleOptionIndexes[index]}
581
+ isSelected={highlightedVisibleIndex === index}
217
582
  onSelect={handleSelect}
218
583
  lineBreak={lineBreak}
219
584
  role='option'
220
- aria-selected={value === option.value}
585
+ aria-selected={highlightedVisibleIndex === index}
586
+ aria-posinset={index + 1}
587
+ aria-setsize={visibleOptions.length}
221
588
  {...option.optionProps}
222
589
  >
223
590
  {option.label}
224
591
  </CustomOption>
225
592
  ))}
593
+ {visibleOptions.length === 0 && (
594
+ <div className={noOptionsStyle}>No options</div>
595
+ )}
226
596
  </Panel>
227
597
  )}
228
598
  </div>