@transferwise/components 0.0.0-experimental-a18466a → 0.0.0-experimental-e789c10

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 (257) hide show
  1. package/build/criticalBanner/CriticalCommsBanner.js +68 -3
  2. package/build/criticalBanner/CriticalCommsBanner.js.map +1 -1
  3. package/build/criticalBanner/CriticalCommsBanner.mjs +69 -4
  4. package/build/criticalBanner/CriticalCommsBanner.mjs.map +1 -1
  5. package/build/dateInput/DateInput.js +12 -5
  6. package/build/dateInput/DateInput.js.map +1 -1
  7. package/build/dateInput/DateInput.mjs +11 -4
  8. package/build/dateInput/DateInput.mjs.map +1 -1
  9. package/build/expressiveMoneyInput/currencySelector/CurrencySelector.js +16 -8
  10. package/build/expressiveMoneyInput/currencySelector/CurrencySelector.js.map +1 -1
  11. package/build/expressiveMoneyInput/currencySelector/CurrencySelector.mjs +14 -6
  12. package/build/expressiveMoneyInput/currencySelector/CurrencySelector.mjs.map +1 -1
  13. package/build/index.js +12 -7
  14. package/build/index.js.map +1 -1
  15. package/build/index.mjs +9 -3
  16. package/build/index.mjs.map +1 -1
  17. package/build/inputs/{_BottomSheet.js → SelectInput/BottomSheet/SelectInputBottomSheet.js} +7 -7
  18. package/build/inputs/SelectInput/BottomSheet/SelectInputBottomSheet.js.map +1 -0
  19. package/build/inputs/{_BottomSheet.mjs → SelectInput/BottomSheet/SelectInputBottomSheet.mjs} +7 -7
  20. package/build/inputs/SelectInput/BottomSheet/SelectInputBottomSheet.mjs.map +1 -0
  21. package/build/inputs/{_ButtonInput.js → SelectInput/ButtonInput/SelectInputButtonInput.js} +5 -5
  22. package/build/inputs/SelectInput/ButtonInput/SelectInputButtonInput.js.map +1 -0
  23. package/build/inputs/{_ButtonInput.mjs → SelectInput/ButtonInput/SelectInputButtonInput.mjs} +5 -5
  24. package/build/inputs/SelectInput/ButtonInput/SelectInputButtonInput.mjs.map +1 -0
  25. package/build/inputs/SelectInput/ClearButton/SelectInputClearButton.js +26 -0
  26. package/build/inputs/SelectInput/ClearButton/SelectInputClearButton.js.map +1 -0
  27. package/build/inputs/SelectInput/ClearButton/SelectInputClearButton.mjs +24 -0
  28. package/build/inputs/SelectInput/ClearButton/SelectInputClearButton.mjs.map +1 -0
  29. package/build/inputs/SelectInput/DefaultRenderTrigger/SelectInputDefaultRenderTrigger.js +59 -0
  30. package/build/inputs/SelectInput/DefaultRenderTrigger/SelectInputDefaultRenderTrigger.js.map +1 -0
  31. package/build/inputs/SelectInput/DefaultRenderTrigger/SelectInputDefaultRenderTrigger.mjs +56 -0
  32. package/build/inputs/SelectInput/DefaultRenderTrigger/SelectInputDefaultRenderTrigger.mjs.map +1 -0
  33. package/build/inputs/SelectInput/ItemView/GroupItemView/SelectInputGroupItemView.js +50 -0
  34. package/build/inputs/SelectInput/ItemView/GroupItemView/SelectInputGroupItemView.js.map +1 -0
  35. package/build/inputs/SelectInput/ItemView/GroupItemView/SelectInputGroupItemView.mjs +48 -0
  36. package/build/inputs/SelectInput/ItemView/GroupItemView/SelectInputGroupItemView.mjs.map +1 -0
  37. package/build/inputs/SelectInput/ItemView/SelectInputItemView.js +47 -0
  38. package/build/inputs/SelectInput/ItemView/SelectInputItemView.js.map +1 -0
  39. package/build/inputs/SelectInput/ItemView/SelectInputItemView.mjs +45 -0
  40. package/build/inputs/SelectInput/ItemView/SelectInputItemView.mjs.map +1 -0
  41. package/build/inputs/SelectInput/Option/SelectInputOption.js +42 -0
  42. package/build/inputs/SelectInput/Option/SelectInputOption.js.map +1 -0
  43. package/build/inputs/SelectInput/Option/SelectInputOption.mjs +40 -0
  44. package/build/inputs/SelectInput/Option/SelectInputOption.mjs.map +1 -0
  45. package/build/inputs/SelectInput/OptionContent/SelectInputOptionContent.js +40 -0
  46. package/build/inputs/SelectInput/OptionContent/SelectInputOptionContent.js.map +1 -0
  47. package/build/inputs/SelectInput/OptionContent/SelectInputOptionContent.mjs +38 -0
  48. package/build/inputs/SelectInput/OptionContent/SelectInputOptionContent.mjs.map +1 -0
  49. package/build/inputs/SelectInput/Options/OptionsContainer/SelectInputOptionsContainer.js +48 -0
  50. package/build/inputs/SelectInput/Options/OptionsContainer/SelectInputOptionsContainer.js.map +1 -0
  51. package/build/inputs/SelectInput/Options/OptionsContainer/SelectInputOptionsContainer.mjs +46 -0
  52. package/build/inputs/SelectInput/Options/OptionsContainer/SelectInputOptionsContainer.mjs.map +1 -0
  53. package/build/inputs/SelectInput/Options/SelectInputOptions.js +300 -0
  54. package/build/inputs/SelectInput/Options/SelectInputOptions.js.map +1 -0
  55. package/build/inputs/SelectInput/Options/SelectInputOptions.mjs +298 -0
  56. package/build/inputs/SelectInput/Options/SelectInputOptions.mjs.map +1 -0
  57. package/build/inputs/{_Popover.js → SelectInput/Popover/SelectInputPopover.js} +7 -7
  58. package/build/inputs/SelectInput/Popover/SelectInputPopover.js.map +1 -0
  59. package/build/inputs/{_Popover.mjs → SelectInput/Popover/SelectInputPopover.mjs} +7 -7
  60. package/build/inputs/SelectInput/Popover/SelectInputPopover.mjs.map +1 -0
  61. package/build/inputs/SelectInput/SelectInput.contexts.js +29 -0
  62. package/build/inputs/SelectInput/SelectInput.contexts.js.map +1 -0
  63. package/build/inputs/SelectInput/SelectInput.contexts.mjs +24 -0
  64. package/build/inputs/SelectInput/SelectInput.contexts.mjs.map +1 -0
  65. package/build/inputs/SelectInput/SelectInput.js +222 -0
  66. package/build/inputs/SelectInput/SelectInput.js.map +1 -0
  67. package/build/inputs/SelectInput/SelectInput.messages.js.map +1 -0
  68. package/build/inputs/SelectInput/SelectInput.messages.mjs.map +1 -0
  69. package/build/inputs/SelectInput/SelectInput.mjs +216 -0
  70. package/build/inputs/SelectInput/SelectInput.mjs.map +1 -0
  71. package/build/inputs/SelectInput/SelectInput.utils.js +164 -0
  72. package/build/inputs/SelectInput/SelectInput.utils.js.map +1 -0
  73. package/build/inputs/SelectInput/SelectInput.utils.mjs +154 -0
  74. package/build/inputs/SelectInput/SelectInput.utils.mjs.map +1 -0
  75. package/build/inputs/SelectInput/TriggerButton/SelectInputTriggerButton.js +42 -0
  76. package/build/inputs/SelectInput/TriggerButton/SelectInputTriggerButton.js.map +1 -0
  77. package/build/inputs/SelectInput/TriggerButton/SelectInputTriggerButton.mjs +36 -0
  78. package/build/inputs/SelectInput/TriggerButton/SelectInputTriggerButton.mjs.map +1 -0
  79. package/build/main.css +123 -105
  80. package/build/moneyInput/MoneyInput.js +9 -2
  81. package/build/moneyInput/MoneyInput.js.map +1 -1
  82. package/build/moneyInput/MoneyInput.mjs +8 -1
  83. package/build/moneyInput/MoneyInput.mjs.map +1 -1
  84. package/build/phoneNumberInput/PhoneNumberInput.js +10 -3
  85. package/build/phoneNumberInput/PhoneNumberInput.js.map +1 -1
  86. package/build/phoneNumberInput/PhoneNumberInput.mjs +9 -2
  87. package/build/phoneNumberInput/PhoneNumberInput.mjs.map +1 -1
  88. package/build/styles/criticalBanner/CriticalCommsBanner.css +33 -15
  89. package/build/styles/inputs/SelectInput/BottomSheet/SelectInputBottomSheet.css +96 -0
  90. package/build/styles/inputs/SelectInput/ButtonInput/SelectInputButtonInput.css +16 -0
  91. package/build/styles/inputs/SelectInput/ClearButton/SelectInputClearButton.css +46 -0
  92. package/build/styles/inputs/SelectInput/ItemView/SelectInputItemView.css +16 -0
  93. package/build/styles/inputs/SelectInput/Option/SelectInputOption.css +33 -0
  94. package/build/styles/inputs/SelectInput/OptionContent/SelectInputOptionContent.css +37 -0
  95. package/build/styles/inputs/SelectInput/Options/SelectInputOptions.css +81 -0
  96. package/build/styles/inputs/SelectInput/Popover/SelectInputPopover.css +46 -0
  97. package/build/styles/main.css +123 -105
  98. package/build/types/criticalBanner/CriticalCommsBanner.d.ts +4 -1
  99. package/build/types/criticalBanner/CriticalCommsBanner.d.ts.map +1 -1
  100. package/build/types/criticalBanner/index.d.ts +1 -0
  101. package/build/types/criticalBanner/index.d.ts.map +1 -1
  102. package/build/types/index.d.ts +2 -1
  103. package/build/types/index.d.ts.map +1 -1
  104. package/build/types/inputs/{_BottomSheet.d.ts → SelectInput/BottomSheet/SelectInputBottomSheet.d.ts} +3 -3
  105. package/build/types/inputs/SelectInput/BottomSheet/SelectInputBottomSheet.d.ts.map +1 -0
  106. package/build/types/inputs/SelectInput/BottomSheet/index.d.ts +3 -0
  107. package/build/types/inputs/SelectInput/BottomSheet/index.d.ts.map +1 -0
  108. package/build/types/inputs/SelectInput/ButtonInput/SelectInputButtonInput.d.ts +5 -0
  109. package/build/types/inputs/SelectInput/ButtonInput/SelectInputButtonInput.d.ts.map +1 -0
  110. package/build/types/inputs/SelectInput/ButtonInput/index.d.ts +3 -0
  111. package/build/types/inputs/SelectInput/ButtonInput/index.d.ts.map +1 -0
  112. package/build/types/inputs/SelectInput/ClearButton/SelectInputClearButton.d.ts +7 -0
  113. package/build/types/inputs/SelectInput/ClearButton/SelectInputClearButton.d.ts.map +1 -0
  114. package/build/types/inputs/SelectInput/ClearButton/index.d.ts +3 -0
  115. package/build/types/inputs/SelectInput/ClearButton/index.d.ts.map +1 -0
  116. package/build/types/inputs/SelectInput/DefaultRenderTrigger/SelectInputDefaultRenderTrigger.d.ts +16 -0
  117. package/build/types/inputs/SelectInput/DefaultRenderTrigger/SelectInputDefaultRenderTrigger.d.ts.map +1 -0
  118. package/build/types/inputs/SelectInput/DefaultRenderTrigger/index.d.ts +2 -0
  119. package/build/types/inputs/SelectInput/DefaultRenderTrigger/index.d.ts.map +1 -0
  120. package/build/types/inputs/SelectInput/ItemView/GroupItemView/SelectInputGroupItemView.d.ts +9 -0
  121. package/build/types/inputs/SelectInput/ItemView/GroupItemView/SelectInputGroupItemView.d.ts.map +1 -0
  122. package/build/types/inputs/SelectInput/ItemView/GroupItemView/index.d.ts +3 -0
  123. package/build/types/inputs/SelectInput/ItemView/GroupItemView/index.d.ts.map +1 -0
  124. package/build/types/inputs/SelectInput/ItemView/SelectInputItemView.d.ts +11 -0
  125. package/build/types/inputs/SelectInput/ItemView/SelectInputItemView.d.ts.map +1 -0
  126. package/build/types/inputs/SelectInput/ItemView/index.d.ts +4 -0
  127. package/build/types/inputs/SelectInput/ItemView/index.d.ts.map +1 -0
  128. package/build/types/inputs/SelectInput/Option/SelectInputOption.d.ts +11 -0
  129. package/build/types/inputs/SelectInput/Option/SelectInputOption.d.ts.map +1 -0
  130. package/build/types/inputs/SelectInput/Option/index.d.ts +3 -0
  131. package/build/types/inputs/SelectInput/Option/index.d.ts.map +1 -0
  132. package/build/types/inputs/SelectInput/OptionContent/SelectInputOptionContent.d.ts +13 -0
  133. package/build/types/inputs/SelectInput/OptionContent/SelectInputOptionContent.d.ts.map +1 -0
  134. package/build/types/inputs/SelectInput/OptionContent/index.d.ts +3 -0
  135. package/build/types/inputs/SelectInput/OptionContent/index.d.ts.map +1 -0
  136. package/build/types/inputs/SelectInput/Options/OptionsContainer/SelectInputOptionsContainer.d.ts +9 -0
  137. package/build/types/inputs/SelectInput/Options/OptionsContainer/SelectInputOptionsContainer.d.ts.map +1 -0
  138. package/build/types/inputs/SelectInput/Options/OptionsContainer/index.d.ts +3 -0
  139. package/build/types/inputs/SelectInput/Options/OptionsContainer/index.d.ts.map +1 -0
  140. package/build/types/inputs/SelectInput/Options/SelectInputOptions.d.ts +21 -0
  141. package/build/types/inputs/SelectInput/Options/SelectInputOptions.d.ts.map +1 -0
  142. package/build/types/inputs/SelectInput/Options/index.d.ts +4 -0
  143. package/build/types/inputs/SelectInput/Options/index.d.ts.map +1 -0
  144. package/build/types/inputs/{_Popover.d.ts → SelectInput/Popover/SelectInputPopover.d.ts} +3 -3
  145. package/build/types/inputs/SelectInput/Popover/SelectInputPopover.d.ts.map +1 -0
  146. package/build/types/inputs/SelectInput/Popover/index.d.ts +3 -0
  147. package/build/types/inputs/SelectInput/Popover/index.d.ts.map +1 -0
  148. package/build/types/inputs/SelectInput/SelectInput.contexts.d.ts +33 -0
  149. package/build/types/inputs/SelectInput/SelectInput.contexts.d.ts.map +1 -0
  150. package/build/types/inputs/SelectInput/SelectInput.d.ts +10 -0
  151. package/build/types/inputs/SelectInput/SelectInput.d.ts.map +1 -0
  152. package/build/types/inputs/SelectInput/SelectInput.messages.d.ts.map +1 -0
  153. package/build/types/inputs/{SelectInput.d.ts → SelectInput/SelectInput.types.d.ts} +12 -38
  154. package/build/types/inputs/SelectInput/SelectInput.types.d.ts.map +1 -0
  155. package/build/types/inputs/SelectInput/SelectInput.utils.d.ts +60 -0
  156. package/build/types/inputs/SelectInput/SelectInput.utils.d.ts.map +1 -0
  157. package/build/types/inputs/SelectInput/TriggerButton/SelectInputTriggerButton.d.ts +12 -0
  158. package/build/types/inputs/SelectInput/TriggerButton/SelectInputTriggerButton.d.ts.map +1 -0
  159. package/build/types/inputs/SelectInput/TriggerButton/index.d.ts +3 -0
  160. package/build/types/inputs/SelectInput/TriggerButton/index.d.ts.map +1 -0
  161. package/build/types/inputs/SelectInput/components.d.ts +10 -0
  162. package/build/types/inputs/SelectInput/components.d.ts.map +1 -0
  163. package/build/types/inputs/SelectInput/index.d.ts +10 -0
  164. package/build/types/inputs/SelectInput/index.d.ts.map +1 -0
  165. package/build/types/uploadInput/UploadInput.d.ts +2 -2
  166. package/build/types/uploadInput/UploadInput.d.ts.map +1 -1
  167. package/build/uploadInput/UploadInput.js.map +1 -1
  168. package/build/uploadInput/UploadInput.mjs.map +1 -1
  169. package/package.json +1 -1
  170. package/src/criticalBanner/CriticalCommsBanner.css +33 -15
  171. package/src/criticalBanner/CriticalCommsBanner.less +46 -36
  172. package/src/criticalBanner/CriticalCommsBanner.story.tsx +9 -15
  173. package/src/criticalBanner/CriticalCommsBanner.test.story.tsx +70 -0
  174. package/src/criticalBanner/CriticalCommsBanner.test.tsx +66 -0
  175. package/src/criticalBanner/CriticalCommsBanner.tsx +54 -5
  176. package/src/criticalBanner/index.ts +1 -0
  177. package/src/index.ts +1 -1
  178. package/src/inputs/SelectInput/BottomSheet/SelectInputBottomSheet.css +96 -0
  179. package/src/inputs/{_BottomSheet.tsx → SelectInput/BottomSheet/SelectInputBottomSheet.tsx} +7 -7
  180. package/src/inputs/SelectInput/BottomSheet/index.ts +2 -0
  181. package/src/inputs/SelectInput/ButtonInput/SelectInputButtonInput.css +16 -0
  182. package/src/inputs/{_ButtonInput.tsx → SelectInput/ButtonInput/SelectInputButtonInput.tsx} +5 -5
  183. package/src/inputs/SelectInput/ButtonInput/index.ts +2 -0
  184. package/src/inputs/SelectInput/ClearButton/SelectInputClearButton.css +46 -0
  185. package/src/inputs/SelectInput/ClearButton/SelectInputClearButton.less +39 -0
  186. package/src/inputs/SelectInput/ClearButton/SelectInputClearButton.tsx +27 -0
  187. package/src/inputs/SelectInput/ClearButton/index.ts +2 -0
  188. package/src/inputs/SelectInput/DefaultRenderTrigger/SelectInputDefaultRenderTrigger.tsx +74 -0
  189. package/src/inputs/SelectInput/DefaultRenderTrigger/index.ts +5 -0
  190. package/src/inputs/SelectInput/ItemView/GroupItemView/SelectInputGroupItemView.tsx +61 -0
  191. package/src/inputs/SelectInput/ItemView/GroupItemView/index.ts +2 -0
  192. package/src/inputs/SelectInput/ItemView/SelectInputItemView.css +16 -0
  193. package/src/inputs/SelectInput/ItemView/SelectInputItemView.less +17 -0
  194. package/src/inputs/SelectInput/ItemView/SelectInputItemView.tsx +48 -0
  195. package/src/inputs/SelectInput/ItemView/index.ts +3 -0
  196. package/src/inputs/SelectInput/Option/SelectInputOption.css +33 -0
  197. package/src/inputs/SelectInput/Option/SelectInputOption.less +32 -0
  198. package/src/inputs/SelectInput/Option/SelectInputOption.tsx +57 -0
  199. package/src/inputs/SelectInput/Option/index.ts +2 -0
  200. package/src/inputs/SelectInput/OptionContent/SelectInputOptionContent.css +37 -0
  201. package/src/inputs/SelectInput/OptionContent/SelectInputOptionContent.less +38 -0
  202. package/src/inputs/SelectInput/OptionContent/SelectInputOptionContent.tsx +72 -0
  203. package/src/inputs/SelectInput/OptionContent/index.ts +2 -0
  204. package/src/inputs/SelectInput/Options/OptionsContainer/SelectInputOptionsContainer.tsx +59 -0
  205. package/src/inputs/SelectInput/Options/OptionsContainer/index.ts +2 -0
  206. package/src/inputs/SelectInput/Options/SelectInputOptions.css +81 -0
  207. package/src/inputs/SelectInput/Options/SelectInputOptions.less +77 -0
  208. package/src/inputs/SelectInput/Options/SelectInputOptions.tsx +411 -0
  209. package/src/inputs/SelectInput/Options/index.ts +3 -0
  210. package/src/inputs/SelectInput/Popover/SelectInputPopover.css +46 -0
  211. package/src/inputs/{_Popover.tsx → SelectInput/Popover/SelectInputPopover.tsx} +7 -7
  212. package/src/inputs/SelectInput/Popover/index.ts +2 -0
  213. package/src/inputs/SelectInput/SelectInput.contexts.tsx +40 -0
  214. package/src/inputs/SelectInput/SelectInput.less +22 -0
  215. package/src/inputs/{SelectInput.test.tsx → SelectInput/SelectInput.test.tsx} +9 -11
  216. package/src/inputs/SelectInput/SelectInput.tsx +257 -0
  217. package/src/inputs/SelectInput/SelectInput.types.ts +113 -0
  218. package/src/inputs/SelectInput/SelectInput.utils.ts +205 -0
  219. package/src/inputs/SelectInput/TriggerButton/SelectInputTriggerButton.tsx +36 -0
  220. package/src/inputs/SelectInput/TriggerButton/index.ts +5 -0
  221. package/src/inputs/{SelectInput.docs.mdx → SelectInput/_stories/SelectInput.docs.mdx} +0 -1
  222. package/src/inputs/{SelectInput.story.tsx → SelectInput/_stories/SelectInput.story.tsx} +11 -8
  223. package/src/inputs/{SelectInput.test.story.tsx → SelectInput/_stories/SelectInput.test.story.tsx} +6 -10
  224. package/src/inputs/SelectInput/components.ts +10 -0
  225. package/src/inputs/SelectInput/index.ts +12 -0
  226. package/src/main.css +123 -105
  227. package/src/main.less +1 -1
  228. package/src/uploadInput/UploadInput.tsx +2 -2
  229. package/build/inputs/SelectInput.js +0 -890
  230. package/build/inputs/SelectInput.js.map +0 -1
  231. package/build/inputs/SelectInput.messages.js.map +0 -1
  232. package/build/inputs/SelectInput.messages.mjs.map +0 -1
  233. package/build/inputs/SelectInput.mjs +0 -881
  234. package/build/inputs/SelectInput.mjs.map +0 -1
  235. package/build/inputs/_BottomSheet.js.map +0 -1
  236. package/build/inputs/_BottomSheet.mjs.map +0 -1
  237. package/build/inputs/_ButtonInput.js.map +0 -1
  238. package/build/inputs/_ButtonInput.mjs.map +0 -1
  239. package/build/inputs/_Popover.js.map +0 -1
  240. package/build/inputs/_Popover.mjs.map +0 -1
  241. package/build/types/inputs/SelectInput.d.ts.map +0 -1
  242. package/build/types/inputs/SelectInput.messages.d.ts.map +0 -1
  243. package/build/types/inputs/_BottomSheet.d.ts.map +0 -1
  244. package/build/types/inputs/_ButtonInput.d.ts +0 -5
  245. package/build/types/inputs/_ButtonInput.d.ts.map +0 -1
  246. package/build/types/inputs/_Popover.d.ts.map +0 -1
  247. package/src/inputs/SelectInput.less +0 -219
  248. package/src/inputs/SelectInput.tsx +0 -1269
  249. package/build/inputs/{SelectInput.messages.js → SelectInput/SelectInput.messages.js} +0 -0
  250. package/build/inputs/{SelectInput.messages.mjs → SelectInput/SelectInput.messages.mjs} +0 -0
  251. package/build/styles/inputs/{SelectInput.css → SelectInput/SelectInput.css} +90 -90
  252. package/build/types/inputs/{SelectInput.messages.d.ts → SelectInput/SelectInput.messages.d.ts} +0 -0
  253. package/src/inputs/{_BottomSheet.less → SelectInput/BottomSheet/SelectInputBottomSheet.less} +0 -0
  254. package/src/inputs/{_ButtonInput.less → SelectInput/ButtonInput/SelectInputButtonInput.less} +0 -0
  255. package/src/inputs/{_Popover.less → SelectInput/Popover/SelectInputPopover.less} +0 -0
  256. package/src/inputs/{SelectInput.css → SelectInput/SelectInput.css} +90 -90
  257. /package/src/inputs/{SelectInput.messages.ts → SelectInput/SelectInput.messages.ts} +0 -0
@@ -0,0 +1,411 @@
1
+ import { CrossCircle } from '@transferwise/icons';
2
+ import { ListboxOptions } from '@headlessui/react';
3
+ import { clsx } from 'clsx';
4
+ import { useEffect, useId, useMemo, useRef, useState } from 'react';
5
+ import { useIntl } from 'react-intl';
6
+ import { Virtualizer, type VirtualizerHandle } from 'virtua';
7
+
8
+ import { SearchInput } from '../../SearchInput';
9
+ import {
10
+ SelectInputItemsCountContext,
11
+ SelectInputItemPositionContext,
12
+ } from '../SelectInput.contexts';
13
+ import {
14
+ dedupeSelectInputItems,
15
+ filterSelectInputItems,
16
+ MAX_ITEMS_WITHOUT_VIRTUALIZATION,
17
+ searchableString,
18
+ selectInputOptionItemIncludesNeedle,
19
+ sortSelectInputItems,
20
+ } from '../SelectInput.utils';
21
+ import { SelectInputOptionItem, SelectInputProps, SelectInputItem } from '../SelectInput.types';
22
+ import messages from '../SelectInput.messages';
23
+
24
+ import { SelectInputItemView } from '../ItemView';
25
+ import { SelectInputOptionsContainer } from './OptionsContainer';
26
+
27
+ /**
28
+ * Props for the SelectInputOptions component.
29
+ */
30
+ export interface SelectInputOptionsProps<T = string> extends Pick<
31
+ SelectInputProps<T>,
32
+ | 'items'
33
+ | 'renderValue'
34
+ | 'renderFooter'
35
+ | 'filterable'
36
+ | 'filterPlaceholder'
37
+ | 'id'
38
+ | 'parentId'
39
+ | 'compareValues'
40
+ | 'sortFilteredOptions'
41
+ > {
42
+ searchInputRef: React.MutableRefObject<HTMLInputElement | null>;
43
+ listboxRef: React.MutableRefObject<HTMLDivElement | null>;
44
+ filterQuery: string;
45
+ onFilterChange: (query: string) => void;
46
+ listBoxLabel?: string;
47
+ listBoxLabelledBy?: string;
48
+ autocomplete?: string;
49
+ name?: string;
50
+ onAutocompleteSelect?: (value: T) => void;
51
+ }
52
+
53
+ /**
54
+ * The main options container component for SelectInput.
55
+ * Manages filtering, virtualisation, and rendering of options.
56
+ */
57
+ export function SelectInputOptions<T = string>({
58
+ id,
59
+ parentId,
60
+ items,
61
+ compareValues: compareValuesProp,
62
+ renderValue = String,
63
+ renderFooter,
64
+ filterable = false,
65
+ filterPlaceholder,
66
+ sortFilteredOptions,
67
+ searchInputRef,
68
+ listboxRef,
69
+ filterQuery,
70
+ onFilterChange,
71
+ listBoxLabel,
72
+ listBoxLabelledBy,
73
+ autocomplete,
74
+ name,
75
+ onAutocompleteSelect,
76
+ }: SelectInputOptionsProps<T>) {
77
+ const intl = useIntl();
78
+ const virtualiserHandlerRef = useRef<VirtualizerHandle>(null);
79
+ const controllerRef = filterable ? searchInputRef : listboxRef;
80
+ const [initialRender, setInitialRender] = useState(true);
81
+
82
+ const needle = useMemo(() => {
83
+ if (filterable) {
84
+ return filterQuery ? searchableString(filterQuery) : null;
85
+ }
86
+ return undefined;
87
+ }, [filterQuery, filterable]);
88
+ useEffect(() => {
89
+ if (needle) {
90
+ // Ensure having an active option while filtering.
91
+ // Without `requestAnimationFrame` upon which React depends for scheduling
92
+ // updates, the active status would only show for a split second and then
93
+ // disappear inadvertently.
94
+ requestAnimationFrame(() => {
95
+ if (
96
+ controllerRef.current != null &&
97
+ !controllerRef.current.hasAttribute('aria-activedescendant')
98
+ ) {
99
+ // Activate first option via synthetic key press
100
+ controllerRef.current.dispatchEvent(
101
+ new KeyboardEvent('keydown', { key: 'Home', bubbles: true }),
102
+ );
103
+ }
104
+ });
105
+ }
106
+ }, [controllerRef, needle]);
107
+
108
+ const compareValues = useMemo(() => {
109
+ if (!compareValuesProp) {
110
+ return undefined;
111
+ }
112
+
113
+ if (typeof compareValuesProp === 'function') {
114
+ return (a: NonNullable<T>, b: NonNullable<T>) => compareValuesProp(a, b);
115
+ }
116
+
117
+ const key = compareValuesProp;
118
+ return (a: NonNullable<T>, b: NonNullable<T>) => {
119
+ if (typeof a === 'object' && a != null && typeof b === 'object' && b != null) {
120
+ return (a as Record<string, unknown>)[key] === (b as Record<string, unknown>)[key];
121
+ }
122
+ return a === b;
123
+ };
124
+ }, [compareValuesProp]);
125
+
126
+ const filteredItems: readonly SelectInputItem<NonNullable<T> | undefined>[] = useMemo(() => {
127
+ if (needle == null) {
128
+ return items;
129
+ }
130
+
131
+ const dedupedItems = dedupeSelectInputItems(items, compareValues);
132
+
133
+ if (sortFilteredOptions) {
134
+ // When sorting, filter out non-matching items completely to avoid ghost items
135
+ const filtered = dedupedItems.map((item) => {
136
+ if (item.type === 'option') {
137
+ return selectInputOptionItemIncludesNeedle(item, needle)
138
+ ? item
139
+ : { ...item, value: undefined };
140
+ }
141
+ if (item.type === 'group') {
142
+ return {
143
+ ...item,
144
+ options: item.options.map((option) =>
145
+ selectInputOptionItemIncludesNeedle(option, needle)
146
+ ? option
147
+ : { ...option, value: undefined },
148
+ ),
149
+ };
150
+ }
151
+ return item;
152
+ });
153
+
154
+ return sortSelectInputItems(filtered, sortFilteredOptions, filterQuery);
155
+ }
156
+
157
+ return filterSelectInputItems(dedupedItems, (item) =>
158
+ selectInputOptionItemIncludesNeedle(item, needle),
159
+ );
160
+ // eslint-disable-next-line react-hooks/exhaustive-deps
161
+ }, [needle, items, compareValues]);
162
+ const resultsEmpty = needle != null && filteredItems.length === 0;
163
+
164
+ const virtualized = filteredItems.length > MAX_ITEMS_WITHOUT_VIRTUALIZATION;
165
+
166
+ // Items shown once shall be kept mounted until the needle changes, otherwise
167
+ // the scroll position may jump around inadvertently. Pattern adopted from:
168
+ // https://inokawa.github.io/virtua/?path=/story/advanced-keep-offscreen-items--append-only
169
+ const [mountedIndexes, setMountedIndexes] = useState<number[]>([]);
170
+ const prevNeedleRef = useRef(needle);
171
+
172
+ useEffect(() => {
173
+ const needleChanged = prevNeedleRef.current !== needle;
174
+ prevNeedleRef.current = needle;
175
+
176
+ if (needleChanged) {
177
+ // Reset mounted indexes when search changes to avoid stale scroll positions
178
+ setMountedIndexes([]);
179
+ return;
180
+ }
181
+
182
+ // Ensure the 'End' key works as intended by keeping the last item mounted.
183
+ // Skipped on needle change to prevent auto-scrolling on search.
184
+ if (filteredItems.length > 0) {
185
+ setMountedIndexes((prevMountedIndexes) => {
186
+ // Create a new array with existing indexes plus the last item index
187
+ return [...new Set([...prevMountedIndexes, filteredItems.length - 1])]; // Sorting is redundant by nature here
188
+ });
189
+ }
190
+ }, [needle, filteredItems.length]);
191
+
192
+ const listboxContainerRef = useRef<HTMLDivElement>(null);
193
+ useEffect(() => {
194
+ if (listboxContainerRef.current != null) {
195
+ listboxContainerRef.current.style.setProperty(
196
+ '--initial-height',
197
+ `${listboxContainerRef.current.offsetHeight}px`,
198
+ );
199
+ }
200
+ }, []);
201
+
202
+ useEffect(() => {
203
+ setInitialRender(false);
204
+ }, []);
205
+
206
+ const showStatus = resultsEmpty;
207
+ const statusId = useId();
208
+ const listboxId = useId();
209
+
210
+ const getItemNode = (index: number) => {
211
+ const item = filteredItems[index];
212
+ return (
213
+ <SelectInputItemView key={index} item={item} renderValue={renderValue} needle={needle} />
214
+ );
215
+ };
216
+
217
+ const findMatchingItem = (autocompleteValue: string): T | null => {
218
+ const flatOptions = items
219
+ .flatMap((item) =>
220
+ item.type === 'group' ? item.options : item.type === 'option' ? [item] : [],
221
+ )
222
+ .filter(
223
+ (item): item is SelectInputOptionItem<NonNullable<T>> =>
224
+ item.type === 'option' && item.value != null,
225
+ );
226
+
227
+ const exactMatch = flatOptions.find(
228
+ (option) =>
229
+ String(option.value) === autocompleteValue ||
230
+ option.filterMatchers?.some((matcher) => matcher === autocompleteValue),
231
+ );
232
+
233
+ if (exactMatch) {
234
+ return exactMatch.value;
235
+ }
236
+
237
+ const fuzzyMatch = flatOptions.find((option) =>
238
+ option.filterMatchers?.some((matcher) =>
239
+ matcher.toLowerCase().includes(autocompleteValue.toLowerCase()),
240
+ ),
241
+ );
242
+
243
+ return fuzzyMatch ? fuzzyMatch.value : null;
244
+ };
245
+
246
+ return (
247
+ <ListboxOptions
248
+ modal
249
+ as={SelectInputOptionsContainer}
250
+ static
251
+ className="np-select-input-options-container"
252
+ onAriaActiveDescendantChange={(value: React.AriaAttributes['aria-activedescendant']) => {
253
+ if (controllerRef.current != null) {
254
+ if (!initialRender && value != null) {
255
+ controllerRef.current.setAttribute('aria-activedescendant', value);
256
+ } else {
257
+ controllerRef.current.removeAttribute('aria-activedescendant');
258
+ }
259
+ }
260
+ }}
261
+ >
262
+ {filterable ? (
263
+ <div className="np-select-input-query-container">
264
+ <SearchInput
265
+ ref={searchInputRef}
266
+ id={id}
267
+ name={name}
268
+ autoComplete={autocomplete}
269
+ role="combobox"
270
+ shape="rectangle"
271
+ placeholder={filterPlaceholder}
272
+ aria-label={filterPlaceholder}
273
+ defaultValue={filterQuery}
274
+ aria-autocomplete="list"
275
+ aria-expanded
276
+ aria-controls={listboxId}
277
+ aria-describedby={showStatus ? statusId : undefined}
278
+ onKeyDown={(event: React.KeyboardEvent<HTMLInputElement>) => {
279
+ // Prevent interfering with the matcher of Headless UI
280
+ // https://mathiasbynens.be/notes/javascript-unicode#regex
281
+ if (/^.$/u.test(event.key)) {
282
+ event.stopPropagation();
283
+ }
284
+ }}
285
+ onChange={(event) => {
286
+ // Free up resources and ensure not to go out of bounds when the
287
+ // resulting item count is less than before
288
+ const inputValue = event.currentTarget.value;
289
+
290
+ // Free up resources and ensure not to go out of bounds
291
+ setMountedIndexes([]);
292
+ onFilterChange(inputValue);
293
+ }}
294
+ onInput={(event) => {
295
+ const inputValue = event.currentTarget.value;
296
+ const inputElement = event.currentTarget;
297
+
298
+ if (autocomplete && onAutocompleteSelect && inputValue) {
299
+ setTimeout(() => {
300
+ if (inputElement.value === inputValue && inputValue.length > 2) {
301
+ const matchedValue = findMatchingItem(inputValue);
302
+ if (matchedValue !== null) {
303
+ onAutocompleteSelect(matchedValue);
304
+ }
305
+ }
306
+ }, 50);
307
+ }
308
+ }}
309
+ />
310
+ </div>
311
+ ) : null}
312
+
313
+ <section
314
+ ref={listboxContainerRef}
315
+ tabIndex={-1}
316
+ className={clsx(
317
+ 'np-select-input-listbox-container',
318
+ virtualized && 'np-select-input-listbox-container--virtualized',
319
+ needle == null && // Groups aren't shown when filtering
320
+ items.some((item) => item.type === 'group') &&
321
+ 'np-select-input-listbox-container--has-group',
322
+ )}
323
+ data-wds-parent={parentId ?? undefined}
324
+ >
325
+ {resultsEmpty ? (
326
+ <div id={statusId} className="np-select-input-options-status">
327
+ <CrossCircle size={16} className="np-select-input-options-status-icon" />
328
+ {intl.formatMessage(messages.noResultsFound)}
329
+ </div>
330
+ ) : null}
331
+
332
+ <div
333
+ ref={listboxRef}
334
+ id={listboxId}
335
+ role="listbox"
336
+ aria-orientation="vertical"
337
+ aria-label={listBoxLabel}
338
+ aria-labelledby={listBoxLabelledBy}
339
+ tabIndex={0}
340
+ className="np-select-input-listbox"
341
+ >
342
+ {!virtualized ? (
343
+ filteredItems.map((_, index) => getItemNode(index))
344
+ ) : (
345
+ <Virtualizer
346
+ ref={virtualiserHandlerRef}
347
+ data={filteredItems}
348
+ keepMounted={mountedIndexes}
349
+ scrollRef={listboxRef} // `VList` doesn't expose this
350
+ onScroll={async () => {
351
+ if (!virtualiserHandlerRef.current) return;
352
+
353
+ const startIndex = virtualiserHandlerRef.current.findItemIndex(
354
+ virtualiserHandlerRef.current.scrollOffset,
355
+ );
356
+ const endIndex = virtualiserHandlerRef.current.findItemIndex(
357
+ virtualiserHandlerRef.current.scrollOffset +
358
+ virtualiserHandlerRef.current.viewportSize,
359
+ );
360
+
361
+ setMountedIndexes((prevMountedIndexes) => {
362
+ // Create an array of all indexes that should be visible
363
+
364
+ const visibleIndexes = [];
365
+ for (let index = startIndex; index <= endIndex; index += 1) {
366
+ // eslint-disable-next-line functional/immutable-data
367
+ visibleIndexes.push(index);
368
+ }
369
+
370
+ // Combine with previous indexes and sort
371
+ return [...new Set([...prevMountedIndexes, ...visibleIndexes])].sort(
372
+ (a, b) => a - b,
373
+ );
374
+ });
375
+ }}
376
+ >
377
+ {(item, index) => (
378
+ // The position of each item can't be inferred by browsers when
379
+ // virtualizing, as some of the items may not be in the DOM
380
+ <SelectInputItemsCountContext.Provider value={filteredItems.length}>
381
+ <SelectInputItemPositionContext.Provider value={index + 1}>
382
+ {getItemNode(index)}
383
+ </SelectInputItemPositionContext.Provider>
384
+ </SelectInputItemsCountContext.Provider>
385
+ )}
386
+ </Virtualizer>
387
+ )}
388
+ </div>
389
+
390
+ {renderFooter != null ? (
391
+ <footer className="np-select-input-footer">
392
+ <div
393
+ role="none"
394
+ onKeyDown={(event) => {
395
+ // Prevent interfering with Headless UI
396
+ if (event.key !== 'Escape') {
397
+ event.stopPropagation();
398
+ }
399
+ }}
400
+ >
401
+ {renderFooter({
402
+ resultsEmpty,
403
+ queryNormalized: needle,
404
+ })}
405
+ </div>
406
+ </footer>
407
+ ) : null}
408
+ </section>
409
+ </ListboxOptions>
410
+ );
411
+ }
@@ -0,0 +1,3 @@
1
+ export { SelectInputOptions } from './SelectInputOptions';
2
+ export type { SelectInputOptionsProps } from './SelectInputOptions';
3
+ export * from './OptionsContainer';
@@ -0,0 +1,46 @@
1
+ .np-popover-v2-container {
2
+ z-index: 1060;
3
+ display: flex;
4
+ max-height: var(--max-height);
5
+ width: var(--width);
6
+ flex-direction: column;
7
+ overflow: hidden;
8
+ border-radius: 10px;
9
+ border-radius: var(--radius-small);
10
+ background-color: #ffffff;
11
+ background-color: var(--color-background-elevated);
12
+ box-shadow: 0 0 40px rgba(69, 71, 69, 0.2);
13
+ }
14
+ .np-popover-v2-container--size-md {
15
+ min-width: 20rem;
16
+ }
17
+ .np-popover-v2-container--size-lg {
18
+ min-width: 24rem;
19
+ }
20
+ .np-popover-v2-container:focus {
21
+ outline: none;
22
+ }
23
+ .np-popover-v2 {
24
+ display: grid;
25
+ grid-row-gap: 8px;
26
+ grid-row-gap: var(--size-8);
27
+ row-gap: 8px;
28
+ row-gap: var(--size-8);
29
+ overflow-y: auto;
30
+ grid-template-rows: minmax(0, 1fr);
31
+ }
32
+ .np-popover-v2--has-title {
33
+ grid-template-rows: auto minmax(0, 1fr);
34
+ }
35
+ .np-popover-v2--padding-md {
36
+ padding: 16px;
37
+ padding: var(--size-16);
38
+ }
39
+ .np-popover-v2-title {
40
+ color: #37517e;
41
+ color: var(--color-content-primary);
42
+ }
43
+ .np-popover-v2-content {
44
+ color: #5d7079;
45
+ color: var(--color-content-secondary);
46
+ }
@@ -18,9 +18,9 @@ import { ThemeProvider, useTheme } from '@wise/components-theming';
18
18
  import { clsx } from 'clsx';
19
19
  import { useState } from 'react';
20
20
 
21
- import { PreventScroll } from '../common/preventScroll/PreventScroll';
21
+ import { PreventScroll } from '../../../common/preventScroll/PreventScroll';
22
22
 
23
- export interface PopoverProps {
23
+ export interface SelectInputPopoverProps {
24
24
  placement?: Placement;
25
25
  open: boolean;
26
26
  renderTrigger: (args: {
@@ -37,9 +37,9 @@ export interface PopoverProps {
37
37
  onCloseEnd?: () => void;
38
38
  }
39
39
 
40
- const floatingPadding = 16;
40
+ const FLOATING_PADDING = 16;
41
41
 
42
- export function Popover({
42
+ export function SelectInputPopover({
43
43
  placement,
44
44
  open,
45
45
  renderTrigger,
@@ -49,16 +49,16 @@ export function Popover({
49
49
  children,
50
50
  onClose,
51
51
  onCloseEnd,
52
- }: PopoverProps) {
52
+ }: SelectInputPopoverProps) {
53
53
  const { refs, floatingStyles, context } = useFloating<Element>({
54
54
  strategy: 'fixed',
55
55
  placement,
56
56
  middleware: [
57
57
  offset(8),
58
- flip({ padding: floatingPadding, crossAxis: false }),
58
+ flip({ padding: FLOATING_PADDING, crossAxis: false }),
59
59
  shift(),
60
60
  floatingSize({
61
- padding: floatingPadding,
61
+ padding: FLOATING_PADDING,
62
62
  apply: ({ elements, rects, availableHeight }) => {
63
63
  elements.floating.style.setProperty('--max-height', `${availableHeight}px`);
64
64
  elements.floating.style.setProperty('--width', `${rects.reference.width}px`);
@@ -0,0 +1,2 @@
1
+ export { SelectInputPopover } from './SelectInputPopover';
2
+ export type { SelectInputPopoverProps } from './SelectInputPopover';
@@ -0,0 +1,40 @@
1
+ import React, { createContext } from 'react';
2
+
3
+ /**
4
+ * Context for passing props to the SelectInputTriggerButton component.
5
+ */
6
+ export interface SelectInputTriggerButtonPropsContextValue {
7
+ ref?: React.ForwardedRef<HTMLButtonElement | null>;
8
+ id?: string;
9
+ onClick?: (event: React.MouseEvent) => void;
10
+ onKeyDown?: (event: React.KeyboardEvent) => void;
11
+ size?: 'sm' | 'md' | 'lg';
12
+ [key: string]: unknown;
13
+ }
14
+
15
+ /**
16
+ * Context for passing props to the SelectInputTriggerButton component.
17
+ */
18
+ export const SelectInputTriggerButtonPropsContext =
19
+ createContext<SelectInputTriggerButtonPropsContextValue>({});
20
+
21
+ /**
22
+ * Context for providing the total count of items in a SelectInput.
23
+ * Used for ARIA accessibility to inform screen readers about the total number of options.
24
+ */
25
+ export const SelectInputItemsCountContext = createContext<number | undefined>(undefined);
26
+
27
+ /**
28
+ * Context for providing the current item position in a SelectInput.
29
+ * Used for ARIA accessibility to inform screen readers about the position of the option.
30
+ */
31
+ export const SelectInputItemPositionContext = createContext<number | undefined>(undefined);
32
+
33
+ /**
34
+ * Context indicating whether an option's content is rendered within the trigger button.
35
+ * When true, certain styling adjustments are applied to make the content fit better in the trigger.
36
+ */
37
+ export const SelectInputOptionContentWithinTriggerContext = createContext(false);
38
+
39
+ // Re-export types from the original contexts module for backward compatibility
40
+ export type { WithInputAttributesProps } from '../contexts';
@@ -0,0 +1,22 @@
1
+ @import (reference) "../../../node_modules/@transferwise/neptune-css/src/less/ring.less";
2
+
3
+ // Import component styles
4
+ @import "./BottomSheet/SelectInputBottomSheet.less";
5
+ @import "./ButtonInput/SelectInputButtonInput.less";
6
+ @import "./Popover/SelectInputPopover.less";
7
+ @import "./Option/SelectInputOption.less";
8
+ @import "./OptionContent/SelectInputOptionContent.less";
9
+ @import "./ItemView/SelectInputItemView.less";
10
+ @import "./Options/SelectInputOptions.less";
11
+ @import "./ClearButton/SelectInputClearButton.less";
12
+
13
+ // Main SelectInput styles
14
+ .np-select-input-content {
15
+ overflow: hidden;
16
+ text-overflow: ellipsis;
17
+ white-space: nowrap;
18
+ }
19
+
20
+ .np-select-input-placeholder {
21
+ color: var(--color-content-tertiary);
22
+ }
@@ -2,15 +2,15 @@ import { screen, waitFor, within } from '@testing-library/react';
2
2
  import { userEvent } from '@testing-library/user-event';
3
3
  import { mockAnimationsApi } from 'jsdom-testing-mocks';
4
4
 
5
- import { render, mockMatchMedia, mockResizeObserver } from '../test-utils';
5
+ import { render, mockMatchMedia, mockResizeObserver } from '../../test-utils';
6
6
 
7
7
  import {
8
8
  SelectInput,
9
9
  SelectInputOptionContent,
10
10
  type SelectInputOptionItem,
11
11
  type SelectInputProps,
12
- } from './SelectInput';
13
- import { Field } from '../field/Field';
12
+ } from '.';
13
+ import { Field } from '../../field/Field';
14
14
 
15
15
  mockMatchMedia();
16
16
  mockResizeObserver();
@@ -40,7 +40,7 @@ describe('SelectInput', () => {
40
40
  ]}
41
41
  renderFooter={({ queryNormalized: normalizedQuery }) =>
42
42
  normalizedQuery != null ? (
43
- <>Showing results for {normalizedQuery}’</>
43
+ <>Showing results for &apos;{normalizedQuery}&apos;</>
44
44
  ) : (
45
45
  <>All items shown</>
46
46
  )
@@ -56,16 +56,16 @@ describe('SelectInput', () => {
56
56
  expect(footer).toBeInTheDocument();
57
57
 
58
58
  await userEvent.keyboard('u');
59
- expect(footer).toHaveTextContent(/‘u’$/);
59
+ expect(footer).toHaveTextContent(/'u'$/);
60
60
 
61
61
  await userEvent.keyboard('r');
62
- expect(footer).toHaveTextContent(/‘ur’$/);
62
+ expect(footer).toHaveTextContent(/'ur'$/);
63
63
 
64
64
  await userEvent.keyboard('x');
65
- expect(footer).toHaveTextContent(/‘urx’$/);
65
+ expect(footer).toHaveTextContent(/'urx'$/);
66
66
 
67
67
  await userEvent.keyboard('{Backspace}');
68
- expect(footer).toHaveTextContent(/‘ur’$/);
68
+ expect(footer).toHaveTextContent(/'ur'$/);
69
69
  });
70
70
 
71
71
  it('allows navigating the listbox with cursors', async () => {
@@ -76,9 +76,7 @@ describe('SelectInput', () => {
76
76
  { type: 'option', value: 'EUR' },
77
77
  { type: 'option', value: 'USD' },
78
78
  ]}
79
- renderFooter={({ queryNormalized: normalizedQuery }) => (
80
- <button type="button">Footer button</button>
81
- )}
79
+ renderFooter={() => <button type="button">Footer button</button>}
82
80
  filterable
83
81
  />,
84
82
  );