@graphprotocol/gds-react 0.1.1 → 0.2.0

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 (390) hide show
  1. package/dist/GDSProvider.d.ts +12 -10
  2. package/dist/GDSProvider.d.ts.map +1 -1
  3. package/dist/GDSProvider.js +10 -8
  4. package/dist/GDSProvider.js.map +1 -1
  5. package/dist/components/Address.js +2 -2
  6. package/dist/components/Address.meta.d.ts +1 -13
  7. package/dist/components/Address.meta.d.ts.map +1 -1
  8. package/dist/components/Avatar.d.ts.map +1 -1
  9. package/dist/components/Avatar.js +7 -11
  10. package/dist/components/Avatar.js.map +1 -1
  11. package/dist/components/Avatar.meta.d.ts +0 -2
  12. package/dist/components/Avatar.meta.d.ts.map +1 -1
  13. package/dist/components/AvatarGroup.meta.d.ts +8 -2
  14. package/dist/components/AvatarGroup.meta.d.ts.map +1 -1
  15. package/dist/components/Breadcrumbs.meta.d.ts +2 -3
  16. package/dist/components/Breadcrumbs.meta.d.ts.map +1 -1
  17. package/dist/components/Breadcrumbs.meta.js +3 -1
  18. package/dist/components/Breadcrumbs.meta.js.map +1 -1
  19. package/dist/components/Breadcrumbs.parts.d.ts.map +1 -1
  20. package/dist/components/Breadcrumbs.parts.js +15 -23
  21. package/dist/components/Breadcrumbs.parts.js.map +1 -1
  22. package/dist/components/Button.d.ts.map +1 -1
  23. package/dist/components/Button.js +17 -12
  24. package/dist/components/Button.js.map +1 -1
  25. package/dist/components/Button.meta.d.ts +1 -4
  26. package/dist/components/Button.meta.d.ts.map +1 -1
  27. package/dist/components/ButtonGroup.d.ts.map +1 -1
  28. package/dist/components/ButtonGroup.js +1 -5
  29. package/dist/components/ButtonGroup.js.map +1 -1
  30. package/dist/components/ButtonGroup.meta.d.ts +1 -5
  31. package/dist/components/ButtonGroup.meta.d.ts.map +1 -1
  32. package/dist/components/ButtonGroup.meta.js +8 -0
  33. package/dist/components/ButtonGroup.meta.js.map +1 -1
  34. package/dist/components/Card.js +1 -1
  35. package/dist/components/Card.js.map +1 -1
  36. package/dist/components/Card.meta.d.ts +1 -2
  37. package/dist/components/Card.meta.d.ts.map +1 -1
  38. package/dist/components/Card.meta.js +1 -0
  39. package/dist/components/Card.meta.js.map +1 -1
  40. package/dist/components/Checkbox.meta.d.ts +1 -6
  41. package/dist/components/Checkbox.meta.d.ts.map +1 -1
  42. package/dist/components/Checkbox.meta.js +1 -5
  43. package/dist/components/Checkbox.meta.js.map +1 -1
  44. package/dist/components/Chip.d.ts +6 -0
  45. package/dist/components/Chip.d.ts.map +1 -0
  46. package/dist/components/Chip.js +5 -0
  47. package/dist/components/Chip.js.map +1 -0
  48. package/dist/components/Chip.meta.d.ts +15 -0
  49. package/dist/components/Chip.meta.d.ts.map +1 -0
  50. package/dist/components/Chip.meta.js +22 -0
  51. package/dist/components/Chip.meta.js.map +1 -0
  52. package/dist/components/Chip.parts.d.ts +52 -0
  53. package/dist/components/Chip.parts.d.ts.map +1 -0
  54. package/dist/components/Chip.parts.js +114 -0
  55. package/dist/components/Chip.parts.js.map +1 -0
  56. package/dist/components/Cluster.meta.d.ts +8 -2
  57. package/dist/components/Cluster.meta.d.ts.map +1 -1
  58. package/dist/components/CodeBlock.d.ts +1 -1
  59. package/dist/components/CodeBlock.meta.d.ts +2 -4
  60. package/dist/components/CodeBlock.meta.d.ts.map +1 -1
  61. package/dist/components/CodeBlock.parts.d.ts +6 -7
  62. package/dist/components/CodeBlock.parts.d.ts.map +1 -1
  63. package/dist/components/CodeBlock.parts.js +28 -10
  64. package/dist/components/CodeBlock.parts.js.map +1 -1
  65. package/dist/components/CodeInline.js +3 -3
  66. package/dist/components/CodeInline.meta.d.ts +1 -1
  67. package/dist/components/CodeInline.meta.d.ts.map +1 -1
  68. package/dist/components/CopyButton.d.ts.map +1 -1
  69. package/dist/components/CopyButton.js +2 -4
  70. package/dist/components/CopyButton.js.map +1 -1
  71. package/dist/components/CopyButton.meta.d.ts +1 -12
  72. package/dist/components/CopyButton.meta.d.ts.map +1 -1
  73. package/dist/components/CopyButton.meta.js +1 -6
  74. package/dist/components/CopyButton.meta.js.map +1 -1
  75. package/dist/components/CurrencyInput.meta.d.ts +1 -6
  76. package/dist/components/CurrencyInput.meta.d.ts.map +1 -1
  77. package/dist/components/CurrencyInput.meta.js +1 -5
  78. package/dist/components/CurrencyInput.meta.js.map +1 -1
  79. package/dist/components/DescriptionList.meta.d.ts +2 -5
  80. package/dist/components/DescriptionList.meta.d.ts.map +1 -1
  81. package/dist/components/DescriptionList.parts.d.ts +3 -0
  82. package/dist/components/DescriptionList.parts.d.ts.map +1 -1
  83. package/dist/components/DescriptionList.parts.js +2 -1
  84. package/dist/components/DescriptionList.parts.js.map +1 -1
  85. package/dist/components/Divider.meta.d.ts +1 -3
  86. package/dist/components/Divider.meta.d.ts.map +1 -1
  87. package/dist/components/Icon.js +4 -4
  88. package/dist/components/Icon.js.map +1 -1
  89. package/dist/components/Icon.meta.d.ts +0 -2
  90. package/dist/components/Icon.meta.d.ts.map +1 -1
  91. package/dist/components/Icon.meta.js +1 -0
  92. package/dist/components/Icon.meta.js.map +1 -1
  93. package/dist/components/Input.d.ts +5 -4
  94. package/dist/components/Input.d.ts.map +1 -1
  95. package/dist/components/Input.js +1 -0
  96. package/dist/components/Input.js.map +1 -1
  97. package/dist/components/Input.meta.d.ts +1 -6
  98. package/dist/components/Input.meta.d.ts.map +1 -1
  99. package/dist/components/Input.meta.js +1 -5
  100. package/dist/components/Input.meta.js.map +1 -1
  101. package/dist/components/Keyboard.js +1 -1
  102. package/dist/components/Keyboard.meta.d.ts +0 -1
  103. package/dist/components/Keyboard.meta.d.ts.map +1 -1
  104. package/dist/components/Label.meta.d.ts +1 -3
  105. package/dist/components/Label.meta.d.ts.map +1 -1
  106. package/dist/components/Link.d.ts +1 -1
  107. package/dist/components/Link.d.ts.map +1 -1
  108. package/dist/components/Link.js +1 -2
  109. package/dist/components/Link.js.map +1 -1
  110. package/dist/components/Link.meta.d.ts +1 -2
  111. package/dist/components/Link.meta.d.ts.map +1 -1
  112. package/dist/components/Link.meta.js +1 -0
  113. package/dist/components/Link.meta.js.map +1 -1
  114. package/dist/components/Menu.meta.d.ts +31 -2
  115. package/dist/components/Menu.meta.d.ts.map +1 -1
  116. package/dist/components/Menu.meta.js +39 -1
  117. package/dist/components/Menu.meta.js.map +1 -1
  118. package/dist/components/Menu.parts.d.ts +23 -32
  119. package/dist/components/Menu.parts.d.ts.map +1 -1
  120. package/dist/components/Menu.parts.js +284 -303
  121. package/dist/components/Menu.parts.js.map +1 -1
  122. package/dist/components/Modal.d.ts +1 -1
  123. package/dist/components/Modal.meta.d.ts +1 -3
  124. package/dist/components/Modal.meta.d.ts.map +1 -1
  125. package/dist/components/Modal.meta.js +1 -1
  126. package/dist/components/Modal.meta.js.map +1 -1
  127. package/dist/components/Modal.parts.d.ts +14 -15
  128. package/dist/components/Modal.parts.d.ts.map +1 -1
  129. package/dist/components/Modal.parts.js +36 -32
  130. package/dist/components/Modal.parts.js.map +1 -1
  131. package/dist/components/OTCInput.js +1 -1
  132. package/dist/components/OTCInput.meta.d.ts +1 -6
  133. package/dist/components/OTCInput.meta.d.ts.map +1 -1
  134. package/dist/components/OTCInput.meta.js +1 -5
  135. package/dist/components/OTCInput.meta.js.map +1 -1
  136. package/dist/components/Radio.meta.d.ts +1 -6
  137. package/dist/components/Radio.meta.d.ts.map +1 -1
  138. package/dist/components/Radio.meta.js +1 -5
  139. package/dist/components/Radio.meta.js.map +1 -1
  140. package/dist/components/Search.meta.d.ts +1 -3
  141. package/dist/components/Search.meta.d.ts.map +1 -1
  142. package/dist/components/SegmentedControl.meta.d.ts +2 -3
  143. package/dist/components/SegmentedControl.meta.d.ts.map +1 -1
  144. package/dist/components/SegmentedControl.meta.js +3 -1
  145. package/dist/components/SegmentedControl.meta.js.map +1 -1
  146. package/dist/components/SegmentedControl.parts.d.ts.map +1 -1
  147. package/dist/components/SegmentedControl.parts.js +15 -31
  148. package/dist/components/SegmentedControl.parts.js.map +1 -1
  149. package/dist/components/Status.meta.d.ts +0 -2
  150. package/dist/components/Status.meta.d.ts.map +1 -1
  151. package/dist/components/Stepper.meta.d.ts +1 -2
  152. package/dist/components/Stepper.meta.d.ts.map +1 -1
  153. package/dist/components/Stepper.meta.js +1 -0
  154. package/dist/components/Stepper.meta.js.map +1 -1
  155. package/dist/components/Stepper.parts.d.ts.map +1 -1
  156. package/dist/components/Stepper.parts.js +1 -1
  157. package/dist/components/Stepper.parts.js.map +1 -1
  158. package/dist/components/Switch.meta.d.ts +1 -6
  159. package/dist/components/Switch.meta.d.ts.map +1 -1
  160. package/dist/components/Switch.meta.js +1 -5
  161. package/dist/components/Switch.meta.js.map +1 -1
  162. package/dist/components/TabSet.meta.d.ts +2 -5
  163. package/dist/components/TabSet.meta.d.ts.map +1 -1
  164. package/dist/components/TabSet.meta.js +3 -1
  165. package/dist/components/TabSet.meta.js.map +1 -1
  166. package/dist/components/TabSet.parts.js +1 -1
  167. package/dist/components/Tag.meta.d.ts +0 -2
  168. package/dist/components/Tag.meta.d.ts.map +1 -1
  169. package/dist/components/TextArea.meta.d.ts +1 -6
  170. package/dist/components/TextArea.meta.d.ts.map +1 -1
  171. package/dist/components/TextArea.meta.js +1 -5
  172. package/dist/components/TextArea.meta.js.map +1 -1
  173. package/dist/components/ToggleButton.d.ts +0 -2
  174. package/dist/components/ToggleButton.d.ts.map +1 -1
  175. package/dist/components/ToggleButton.js +2 -2
  176. package/dist/components/ToggleButton.js.map +1 -1
  177. package/dist/components/ToggleButton.meta.d.ts +1 -12
  178. package/dist/components/ToggleButton.meta.d.ts.map +1 -1
  179. package/dist/components/ToggleButton.meta.js +1 -6
  180. package/dist/components/ToggleButton.meta.js.map +1 -1
  181. package/dist/components/Tooltip.d.ts +2 -2
  182. package/dist/components/Tooltip.d.ts.map +1 -1
  183. package/dist/components/Tooltip.js +2 -2
  184. package/dist/components/Tooltip.js.map +1 -1
  185. package/dist/components/Tooltip.meta.d.ts +12 -7
  186. package/dist/components/Tooltip.meta.d.ts.map +1 -1
  187. package/dist/components/Tooltip.meta.js +13 -2
  188. package/dist/components/Tooltip.meta.js.map +1 -1
  189. package/dist/components/Tooltip.parts.d.ts +20 -20
  190. package/dist/components/Tooltip.parts.d.ts.map +1 -1
  191. package/dist/components/Tooltip.parts.js +129 -88
  192. package/dist/components/Tooltip.parts.js.map +1 -1
  193. package/dist/components/base/Addon.meta.d.ts +1 -1
  194. package/dist/components/base/Addon.meta.d.ts.map +1 -1
  195. package/dist/components/base/Addon.meta.js +3 -1
  196. package/dist/components/base/Addon.meta.js.map +1 -1
  197. package/dist/components/base/ButtonOrLink.d.ts +1 -1
  198. package/dist/components/base/ButtonOrLink.parts.d.ts +14 -10
  199. package/dist/components/base/ButtonOrLink.parts.d.ts.map +1 -1
  200. package/dist/components/base/ButtonOrLink.parts.js +47 -47
  201. package/dist/components/base/ButtonOrLink.parts.js.map +1 -1
  202. package/dist/components/base/Checkable.meta.d.ts +1 -2
  203. package/dist/components/base/Checkable.meta.d.ts.map +1 -1
  204. package/dist/components/base/Checkable.parts.d.ts +6 -6
  205. package/dist/components/base/Checkable.parts.d.ts.map +1 -1
  206. package/dist/components/base/Checkable.parts.js +2 -2
  207. package/dist/components/base/Checkable.parts.js.map +1 -1
  208. package/dist/components/base/Field.meta.d.ts +1 -2
  209. package/dist/components/base/Field.meta.d.ts.map +1 -1
  210. package/dist/components/base/Field.parts.d.ts +5 -4
  211. package/dist/components/base/Field.parts.d.ts.map +1 -1
  212. package/dist/components/base/Field.parts.js +1 -1
  213. package/dist/components/base/Field.parts.js.map +1 -1
  214. package/dist/components/base/MaybeButtonOrLink.d.ts +1 -1
  215. package/dist/components/base/MaybeButtonOrLink.d.ts.map +1 -1
  216. package/dist/components/base/Portal.d.ts +1 -1
  217. package/dist/components/base/Portal.d.ts.map +1 -1
  218. package/dist/components/base/Portal.js +3 -6
  219. package/dist/components/base/Portal.js.map +1 -1
  220. package/dist/components/base/Render.d.ts +21 -6
  221. package/dist/components/base/Render.d.ts.map +1 -1
  222. package/dist/components/base/Render.js +3 -2
  223. package/dist/components/base/Render.js.map +1 -1
  224. package/dist/components/base/Transition.js +2 -2
  225. package/dist/components/base/Transition.meta.d.ts +1 -1
  226. package/dist/components/base/Transition.meta.d.ts.map +1 -1
  227. package/dist/components/base/Transition.meta.js +1 -0
  228. package/dist/components/base/Transition.meta.js.map +1 -1
  229. package/dist/components/base/index.d.ts +1 -2
  230. package/dist/components/base/index.d.ts.map +1 -1
  231. package/dist/components/base/index.js +1 -2
  232. package/dist/components/base/index.js.map +1 -1
  233. package/dist/components/index.d.ts +3 -1
  234. package/dist/components/index.d.ts.map +1 -1
  235. package/dist/components/index.js +3 -1
  236. package/dist/components/index.js.map +1 -1
  237. package/dist/hooks/index.d.ts +1 -0
  238. package/dist/hooks/index.d.ts.map +1 -1
  239. package/dist/hooks/index.js +1 -0
  240. package/dist/hooks/index.js.map +1 -1
  241. package/dist/hooks/useCSSProp.d.ts.map +1 -1
  242. package/dist/hooks/useCSSProp.js +6 -6
  243. package/dist/hooks/useCSSProp.js.map +1 -1
  244. package/dist/hooks/useCSSProps.d.ts +11 -13
  245. package/dist/hooks/useCSSProps.d.ts.map +1 -1
  246. package/dist/hooks/useCSSProps.js +11 -19
  247. package/dist/hooks/useCSSProps.js.map +1 -1
  248. package/dist/hooks/useCSSPropsPolyfill.d.ts +1 -1
  249. package/dist/hooks/useCSSPropsPolyfill.d.ts.map +1 -1
  250. package/dist/hooks/useCSSPropsPolyfill.js +12 -20
  251. package/dist/hooks/useCSSPropsPolyfill.js.map +1 -1
  252. package/dist/hooks/useCSSState.d.ts.map +1 -1
  253. package/dist/hooks/useCSSState.js +7 -3
  254. package/dist/hooks/useCSSState.js.map +1 -1
  255. package/dist/hooks/useEffectWithRefDeps.d.ts +2 -2
  256. package/dist/hooks/useEffectWithRefDeps.d.ts.map +1 -1
  257. package/dist/hooks/useEffectWithRefDeps.js +1 -1
  258. package/dist/hooks/useEffectWithRefDeps.js.map +1 -1
  259. package/dist/hooks/useFirstRender.d.ts +14 -0
  260. package/dist/hooks/useFirstRender.d.ts.map +1 -0
  261. package/dist/hooks/useFirstRender.js +20 -0
  262. package/dist/hooks/useFirstRender.js.map +1 -0
  263. package/dist/hooks/useGDS.d.ts +1 -1
  264. package/dist/hooks/usePrevious.d.ts +6 -4
  265. package/dist/hooks/usePrevious.d.ts.map +1 -1
  266. package/dist/hooks/usePrevious.js +6 -4
  267. package/dist/hooks/usePrevious.js.map +1 -1
  268. package/dist/hooks/useRefWithInit.d.ts +2 -2
  269. package/dist/hooks/useRefWithInit.d.ts.map +1 -1
  270. package/dist/hooks/useRefWithInit.js.map +1 -1
  271. package/dist/hooks/useStyleObserver.d.ts +2 -2
  272. package/dist/hooks/useStyleObserver.d.ts.map +1 -1
  273. package/dist/hooks/useStyleObserver.js.map +1 -1
  274. package/dist/icons/CalendarDynamicIcon.d.ts +8 -5
  275. package/dist/icons/CalendarDynamicIcon.d.ts.map +1 -1
  276. package/dist/icons/CalendarDynamicIcon.js +5 -2
  277. package/dist/icons/CalendarDynamicIcon.js.map +1 -1
  278. package/dist/icons/CopyInteractiveIcon.d.ts +5 -4
  279. package/dist/icons/CopyInteractiveIcon.d.ts.map +1 -1
  280. package/dist/icons/CopyInteractiveIcon.js +2 -2
  281. package/dist/icons/CopyInteractiveIcon.js.map +1 -1
  282. package/dist/icons/SidebarLeftInteractiveIcon.d.ts +4 -3
  283. package/dist/icons/SidebarLeftInteractiveIcon.d.ts.map +1 -1
  284. package/dist/icons/SidebarLeftInteractiveIcon.js +2 -2
  285. package/dist/icons/SidebarLeftInteractiveIcon.js.map +1 -1
  286. package/dist/icons/SidebarRightInteractiveIcon.d.ts +4 -3
  287. package/dist/icons/SidebarRightInteractiveIcon.d.ts.map +1 -1
  288. package/dist/icons/SidebarRightInteractiveIcon.js +2 -2
  289. package/dist/icons/SidebarRightInteractiveIcon.js.map +1 -1
  290. package/dist/tailwind-plugin.d.ts.map +1 -1
  291. package/dist/tailwind-plugin.js +8 -5
  292. package/dist/tailwind-plugin.js.map +1 -1
  293. package/dist/utils/cn.d.ts +3 -1
  294. package/dist/utils/cn.d.ts.map +1 -1
  295. package/dist/utils/cn.js +3 -1
  296. package/dist/utils/cn.js.map +1 -1
  297. package/dist/utils/getCSSPropsAttributes.d.ts +10 -3
  298. package/dist/utils/getCSSPropsAttributes.d.ts.map +1 -1
  299. package/dist/utils/getCSSPropsAttributes.js +4 -5
  300. package/dist/utils/getCSSPropsAttributes.js.map +1 -1
  301. package/dist/utils/splitProps.d.ts +1 -4
  302. package/dist/utils/splitProps.d.ts.map +1 -1
  303. package/dist/utils/splitProps.js +2 -7
  304. package/dist/utils/splitProps.js.map +1 -1
  305. package/dist/utils/trimReactNode.d.ts +10 -8
  306. package/dist/utils/trimReactNode.d.ts.map +1 -1
  307. package/dist/utils/trimReactNode.js +10 -8
  308. package/dist/utils/trimReactNode.js.map +1 -1
  309. package/package.json +17 -17
  310. package/src/GDSProvider.tsx +11 -9
  311. package/src/components/Address.tsx +2 -2
  312. package/src/components/Avatar.tsx +10 -11
  313. package/src/components/Breadcrumbs.meta.ts +3 -1
  314. package/src/components/Breadcrumbs.parts.tsx +18 -30
  315. package/src/components/Button.tsx +27 -13
  316. package/src/components/ButtonGroup.meta.ts +8 -0
  317. package/src/components/ButtonGroup.tsx +1 -5
  318. package/src/components/Card.meta.ts +1 -0
  319. package/src/components/Card.tsx +1 -1
  320. package/src/components/Checkbox.meta.ts +1 -5
  321. package/src/components/Chip.meta.ts +23 -0
  322. package/src/components/Chip.parts.tsx +319 -0
  323. package/src/components/Chip.tsx +7 -0
  324. package/src/components/CodeBlock.parts.tsx +75 -50
  325. package/src/components/CodeInline.tsx +3 -3
  326. package/src/components/CopyButton.meta.ts +1 -6
  327. package/src/components/CopyButton.tsx +3 -4
  328. package/src/components/CurrencyInput.meta.ts +1 -5
  329. package/src/components/DescriptionList.parts.tsx +2 -1
  330. package/src/components/Icon.meta.ts +1 -0
  331. package/src/components/Icon.tsx +4 -4
  332. package/src/components/Input.meta.ts +1 -5
  333. package/src/components/Input.tsx +5 -6
  334. package/src/components/Keyboard.tsx +1 -1
  335. package/src/components/Link.meta.ts +1 -0
  336. package/src/components/Link.tsx +2 -3
  337. package/src/components/Menu.meta.ts +39 -1
  338. package/src/components/Menu.parts.tsx +553 -549
  339. package/src/components/Modal.meta.ts +1 -1
  340. package/src/components/Modal.parts.tsx +67 -68
  341. package/src/components/OTCInput.meta.ts +1 -5
  342. package/src/components/OTCInput.tsx +1 -1
  343. package/src/components/Radio.meta.ts +1 -5
  344. package/src/components/SegmentedControl.meta.ts +3 -1
  345. package/src/components/SegmentedControl.parts.tsx +40 -44
  346. package/src/components/Stepper.meta.ts +1 -0
  347. package/src/components/Stepper.parts.tsx +3 -1
  348. package/src/components/Switch.meta.ts +1 -5
  349. package/src/components/TabSet.meta.ts +3 -1
  350. package/src/components/TabSet.parts.tsx +1 -1
  351. package/src/components/TextArea.meta.ts +1 -5
  352. package/src/components/ToggleButton.meta.ts +1 -6
  353. package/src/components/ToggleButton.tsx +1 -3
  354. package/src/components/Tooltip.meta.ts +13 -2
  355. package/src/components/Tooltip.parts.tsx +215 -159
  356. package/src/components/Tooltip.tsx +2 -2
  357. package/src/components/base/Addon.meta.ts +3 -1
  358. package/src/components/base/ButtonOrLink.parts.tsx +91 -73
  359. package/src/components/base/Checkable.parts.tsx +6 -13
  360. package/src/components/base/Field.parts.tsx +5 -5
  361. package/src/components/base/Portal.tsx +5 -7
  362. package/src/components/base/Render.tsx +37 -15
  363. package/src/components/base/Transition.meta.ts +1 -0
  364. package/src/components/base/Transition.tsx +2 -2
  365. package/src/components/base/index.ts +8 -2
  366. package/src/components/index.ts +3 -2
  367. package/src/hooks/index.ts +1 -0
  368. package/src/hooks/useCSSProp.ts +6 -8
  369. package/src/hooks/useCSSProps.ts +13 -22
  370. package/src/hooks/useCSSPropsPolyfill.ts +15 -23
  371. package/src/hooks/useCSSState.ts +11 -6
  372. package/src/hooks/useEffectWithRefDeps.ts +2 -2
  373. package/src/hooks/useFirstRender.ts +36 -0
  374. package/src/hooks/usePrevious.ts +6 -4
  375. package/src/hooks/useRefWithInit.ts +2 -2
  376. package/src/hooks/useStyleObserver.ts +5 -1
  377. package/src/icons/CalendarDynamicIcon.tsx +16 -6
  378. package/src/icons/CopyInteractiveIcon.tsx +10 -5
  379. package/src/icons/SidebarLeftInteractiveIcon.tsx +9 -5
  380. package/src/icons/SidebarRightInteractiveIcon.tsx +9 -5
  381. package/src/tailwind-plugin.ts +8 -5
  382. package/src/utils/cn.ts +3 -1
  383. package/src/utils/getCSSPropsAttributes.ts +13 -8
  384. package/src/utils/splitProps.ts +9 -9
  385. package/src/utils/trimReactNode.tsx +10 -8
  386. package/dist/components/base/ButtonOrLink.meta.d.ts +0 -2
  387. package/dist/components/base/ButtonOrLink.meta.d.ts.map +0 -1
  388. package/dist/components/base/ButtonOrLink.meta.js +0 -6
  389. package/dist/components/base/ButtonOrLink.meta.js.map +0 -1
  390. package/src/components/base/ButtonOrLink.meta.ts +0 -6
@@ -1,15 +1,6 @@
1
1
  'use client'
2
2
 
3
- import {
4
- createContext,
5
- isValidElement,
6
- useContext,
7
- useRef,
8
- type ComponentProps,
9
- type HTMLProps,
10
- type ReactElement,
11
- type ReactNode,
12
- } from 'react'
3
+ import { createContext, useContext, useRef, type ComponentProps, type ReactNode } from 'react'
13
4
  import { Menu } from '@base-ui/react/menu'
14
5
  import { useMergedRefs } from '@base-ui/utils/useMergedRefs'
15
6
  import type { Merge } from 'type-fest'
@@ -25,27 +16,24 @@ import {
25
16
  XIcon,
26
17
  } from '@graphprotocol/gds-react/icons'
27
18
 
28
- import { useAutoValue, useControlled, useCSSState, useGDS } from '../hooks/index.ts'
29
- import { cn, type OptionValue } from '../utils/index.ts'
19
+ import {
20
+ useAutoValue,
21
+ useControlled,
22
+ useCSSPropsPolyfill,
23
+ useCSSState,
24
+ useGDS,
25
+ } from '../hooks/index.ts'
26
+ import { cn, getCSSPropsAttributes, splitProps, type OptionValue } from '../utils/index.ts'
30
27
  import { renderAddon, type AddonValue } from './base/Addon.tsx'
31
28
  import { ButtonOrLink, type ButtonOrLinkProps } from './base/ButtonOrLink.tsx'
32
- import { Render } from './base/Render.tsx'
29
+ import { Render, type RenderFnProps, type RenderProp } from './base/Render.tsx'
33
30
  import { Button } from './Button.tsx'
34
31
  import { Checkbox } from './Checkbox.tsx'
35
- import type { MenuItemMeta, MenuMeta } from './Menu.meta.ts'
32
+ import { MenuMeta, type MenuItemMeta } from './Menu.meta.js'
33
+ import { Tooltip } from './Tooltip.tsx'
36
34
 
37
35
  const MenuTriggerContext = createContext<boolean>(false)
38
36
 
39
- type MenuPosition = {
40
- align: 'start' | 'center' | 'end'
41
- alignOffset: number
42
- side: 'top' | 'bottom' | 'inline-start' | 'inline-end'
43
- gap: number
44
- matchWidth: boolean | 'at-least' | 'at-most'
45
- matchHeight: boolean | 'at-least' | 'at-most'
46
- openOnHover: boolean | number
47
- }
48
-
49
37
  type MenuItemType = 'plain' | 'checkbox' | 'radio'
50
38
 
51
39
  declare namespace GroupProps {
@@ -71,37 +59,30 @@ declare namespace MenuProps {
71
59
  extends
72
60
  Omit<ComponentProps<'div'>, 'defaultValue' | 'onChange'>,
73
61
  GDSComponentProps<typeof MenuMeta> {
74
- open?: boolean | undefined
75
- defaultOpen?: boolean | undefined
76
- onOpenChange?: ((open: boolean) => void) | undefined
77
- /** Element that opens the menu when clicked, or function that returns such an element. */
78
- trigger:
79
- | ReactElement
80
- | ((
81
- renderProps: Record<string, unknown>,
82
- state: {
83
- open: boolean
84
- setOpen: (open: boolean) => void
85
- openMenu: () => void
86
- },
87
- ) => ReactElement)
88
62
  /**
89
- * How the menu should be positioned relative to the trigger. Can be set to `false` to render
90
- * the menu inline instead of in a portal. Nested menus have a different default position (they
91
- * appear to the right of the parent menu), so in that case, `false` means to use the regular
92
- * default position.
63
+ * Element that opens the menu when clicked or hovered (depending on `triggerMode`), or a
64
+ * function that returns such an element. If not provided, the menu is rendered inline rather
65
+ * than as a floating overlay, and all positioning props are ignored (`side`, `gap`, `align`,
66
+ * `alignOffset`, `matchTriggerWidth`, and `matchTriggerHeight`).
67
+ */
68
+ trigger?:
69
+ | RenderProp<
70
+ { open: boolean; setOpen: (open: boolean) => void; openMenu: () => void },
71
+ { disabled?: boolean | undefined }
72
+ >
73
+ | undefined
74
+ /**
75
+ * Controls how the menu is opened when `trigger` is an element. Ignored if `trigger` is a
76
+ * function, since you can fully customize the interaction that opens the menu (e.g.
77
+ * `trigger={(props, { openMenu }) => <button {...props} onMouseEnter={openMenu} />}`).
93
78
  *
94
- * @default {
95
- * align: 'start'
96
- * alignOffset: 0
97
- * side: 'bottom'
98
- * gap: 1
99
- * matchWidth: false
100
- * matchHeight: false
101
- * openOnHover: false
102
- * }
79
+ * @default 'click' // or for nested menus, 'hover'
103
80
  */
104
- position?: Partial<MenuPosition> | false | undefined
81
+ triggerMode?: 'click' | 'hover' | undefined
82
+ /** Defaults to `true` if `trigger` is not provided, or `undefined` otherwise. */
83
+ open?: boolean | undefined
84
+ defaultOpen?: boolean | undefined
85
+ onOpenChange?: ((open: boolean) => void) | undefined
105
86
  header?: ReactNode | undefined
106
87
  footer?: ReactNode | undefined
107
88
  }
@@ -115,11 +96,17 @@ export type MenuProps<T extends OptionValue = OptionValue> =
115
96
 
116
97
  export function MenuRoot<T extends OptionValue>({
117
98
  ref: passedRef,
99
+ trigger,
100
+ triggerMode: passedTriggerMode,
101
+ side,
102
+ gap,
103
+ align,
104
+ alignOffset,
105
+ matchTriggerWidth,
106
+ matchTriggerHeight,
118
107
  open: controlledOpen,
119
108
  defaultOpen,
120
109
  onOpenChange,
121
- trigger,
122
- position: passedPosition = {},
123
110
  header,
124
111
  footer,
125
112
  label,
@@ -128,351 +115,373 @@ export function MenuRoot<T extends OptionValue>({
128
115
  defaultValue,
129
116
  onChange,
130
117
  className,
118
+ style,
131
119
  children,
132
120
  ...props
133
121
  }: MenuProps<T>) {
134
- useGDS()
135
-
136
- const popupRef = useRef<HTMLDivElement>(null)
137
- const popupPassedRef = useMergedRefs(popupRef, passedRef)
138
-
139
- const isNested = useContext(MenuItemsContext) !== null
140
- const Root = isNested ? Menu.SubmenuRoot : Menu.Root
141
- const Trigger = isNested ? Menu.SubmenuTrigger : Menu.Trigger
142
- const triggerIsElement = isValidElement<{ disabled?: boolean }>(trigger)
143
-
144
- const [open, setOpen] = useControlled(controlledOpen, defaultOpen ?? false, onOpenChange)
145
-
146
- const defaultPosition: MenuPosition = {
147
- align: 'start',
148
- alignOffset: 0,
149
- side: 'bottom',
150
- gap: 1,
151
- matchWidth: false,
152
- matchHeight: false,
153
- openOnHover: false,
154
- }
155
- const defaultNestedPosition: MenuPosition = {
156
- ...defaultPosition,
157
- alignOffset:
158
- passedPosition &&
159
- (passedPosition.side === 'top' ||
160
- passedPosition.side === 'bottom' ||
161
- passedPosition.align === 'center')
162
- ? 0
163
- : -2,
164
- side: 'inline-end',
165
- gap:
166
- passedPosition && (passedPosition.side === 'top' || passedPosition.side === 'bottom') ? 0 : 3,
167
- openOnHover: true,
168
- }
169
- const position = isNested
170
- ? passedPosition === false
171
- ? defaultPosition
172
- : { ...defaultNestedPosition, ...passedPosition }
173
- : passedPosition === false
174
- ? false
175
- : {
176
- ...defaultPosition,
177
- ...passedPosition,
178
- }
122
+ const { dirProps } = useGDS()
179
123
 
180
- const simulateKeyPress = (element: Element, key: string, keyCode: number) => {
181
- window.setTimeout(() => {
182
- const newEvent = new KeyboardEvent('keydown', {
183
- key,
184
- code: key,
185
- keyCode,
186
- which: keyCode,
187
- bubbles: true,
188
- cancelable: true,
189
- })
190
- element.dispatchEvent(newEvent)
191
- }, 0)
124
+ const { rootProps, nestedProps } = splitProps(props)
125
+
126
+ const portalRef = useRef<HTMLDivElement>(null)
127
+
128
+ const [cssPropsPolyfillRef, cssPropsPolyfillAttributes, cssProps] = useCSSPropsPolyfill(
129
+ MenuMeta,
130
+ {
131
+ side,
132
+ align,
133
+ matchTriggerWidth,
134
+ matchTriggerHeight,
135
+ },
136
+ {
137
+ returnPropValues: { side, gap, align, alignOffset, matchTriggerWidth, matchTriggerHeight },
138
+ },
139
+ )
140
+
141
+ const isNested = useContext(MenuGroupContext) !== null
142
+ const hasTrigger = trigger !== undefined
143
+ const triggerIsElement = hasTrigger && typeof trigger !== 'function'
144
+ const triggerMode = passedTriggerMode ?? (isNested ? 'hover' : 'click')
145
+ const MenuRoot = isNested ? Menu.SubmenuRoot : Menu.Root
146
+ const MenuTrigger = isNested ? Menu.SubmenuTrigger : Menu.Trigger
147
+
148
+ const [open, setOpen] = useControlled(
149
+ controlledOpen ?? (!hasTrigger ? true : undefined),
150
+ defaultOpen ?? false,
151
+ onOpenChange,
152
+ )
153
+ const initialOpen = useRef(open)
154
+ if (!open) {
155
+ initialOpen.current = false
192
156
  }
193
157
 
194
158
  return (
195
- <Root
196
- open={open}
197
- onOpenChange={(newOpen, eventDetails) => {
198
- // Don't let a non-positioned menu close automatically when interacting outside of it, since it's not modal
199
- if (
200
- !newOpen &&
201
- !position &&
202
- (eventDetails.reason === 'trigger-focus' ||
203
- eventDetails.reason === 'trigger-hover' ||
204
- eventDetails.reason === 'outside-press')
205
- ) {
206
- return
207
- }
208
- setOpen(newOpen)
209
- }}
210
- modal={!isNested ? position !== false : (undefined as never)}
211
- /**
212
- * This seems to be the only way to disable a `Menu.SubmenuTrigger` (see
213
- * https://github.com/mui/base-ui/issues/1976#issuecomment-2914529452).
214
- */
215
- disabled={triggerIsElement ? Boolean(trigger.props.disabled) : (undefined as never)}
216
- closeParentOnEsc
159
+ <div
160
+ ref={cssPropsPolyfillRef}
161
+ data-nested={isNested || undefined}
162
+ data-has-trigger={hasTrigger || undefined}
163
+ className={cn(
164
+ `gds-menu root-contents [anchor-scope:all]
165
+ not-data-has-trigger:**:data-base-ui-focus-guard:hidden
166
+ u:rounded-12
167
+ u:data-nested:default-align-offset-[-2]
168
+ u:data-nested:default-gap-3
169
+ u:data-nested:default-side-end`,
170
+ className,
171
+ )}
172
+ {...getCSSPropsAttributes(
173
+ MenuMeta,
174
+ { side, gap, align, alignOffset, matchTriggerWidth, matchTriggerHeight },
175
+ style,
176
+ )}
177
+ {...cssPropsPolyfillAttributes}
178
+ {...rootProps}
217
179
  >
218
- <MenuTriggerContext.Provider value={true}>
219
- <Trigger
220
- nativeButton={isNested ? false : triggerIsElement}
221
- openOnHover={position && position.openOnHover !== false}
222
- delay={position && typeof position.openOnHover === 'number' ? position.openOnHover : 100}
223
- data-open-on-hover={Boolean(position && position.openOnHover) || undefined}
224
- className={`
225
- u:i:open:data-open-on-hover:state-hover
226
- u:iii:open:not-data-open-on-hover:state-active
227
- `}
228
- render={
229
- typeof trigger === 'function'
230
- ? (renderProps: HTMLProps<unknown>) => {
231
- // Don't pass event handlers and other potentially too specific props (e.g. `type`)
232
- const filteredRenderProps = Object.fromEntries(
233
- Object.entries(renderProps).filter(
234
- ([prop]) =>
235
- prop === 'ref' ||
236
- prop === 'id' ||
237
- prop.startsWith('aria-') ||
238
- prop.startsWith('data-'),
239
- ),
240
- )
241
- return trigger(filteredRenderProps, {
242
- open,
243
- setOpen,
244
- openMenu: () => setOpen(true),
245
- })
246
- }
247
- : // oxlint-disable-next-line no-explicit-any
248
- (trigger satisfies ReactElement<any> as ReactElement<any>)
249
- }
250
- />
251
- </MenuTriggerContext.Provider>
252
- <MaybePortal
253
- position={position}
254
- data-positioned={position !== false || undefined}
255
- data-match-width={position ? position.matchWidth || undefined : undefined}
256
- data-match-height={position ? position.matchHeight || undefined : undefined}
257
- className={cn(
258
- `gds-menu root-flex
259
- [--available-height-but-not-too-small:max(var(--available-height),--spacing(16))]
260
- has-nested/menu-footer:[--available-height-but-not-too-small:max(var(--available-height),--spacing(32))]
261
- has-nested/menu-header:[--available-height-but-not-too-small:max(var(--available-height),--spacing(32))]
262
- u:w-max
263
- u:max-w-full
264
- u:rounded-12
265
- u:data-positioned:max-h-(--available-height-but-not-too-small)
266
- u:data-positioned:max-w-(--available-width)
267
- u:i:data-[match-height=at-least]:min-h-[min(var(--anchor-height),var(--available-height-but-not-too-small))]
268
- u:i:data-[match-height=at-most]:max-h-[min(var(--anchor-height),var(--available-height-but-not-too-small))]
269
- u:i:data-[match-height=true]:h-(--anchor-height)
270
- u:i:data-[match-width=at-least]:min-w-[min(var(--anchor-width),var(--available-width))]
271
- u:i:data-[match-width=at-most]:max-w-[min(var(--anchor-width),var(--available-width))]
272
- u:i:data-[match-width=true]:w-(--anchor-width)`,
273
- className,
274
- )}
180
+ <MenuRoot
181
+ key={`${hasTrigger}-${triggerMode}`}
182
+ open={open}
183
+ onOpenChange={(newOpen) => {
184
+ // A menu with no trigger can still be opened/closed by the consumer, but it should not send events
185
+ if (hasTrigger) setOpen(newOpen)
186
+ }}
187
+ modal={!isNested ? hasTrigger : (undefined as never)}
188
+ closeParentOnEsc
275
189
  >
276
- <Menu.Popup
277
- ref={popupPassedRef}
278
- className={`
279
- flex grow origin-(--transform-origin) flex-col overflow-clip rounded-inherit bg-muted outline-0 transition
280
- data-ending-style:scale-95
281
- data-ending-style:opacity-0
282
- data-starting-style:scale-95
283
- data-starting-style:opacity-0
284
- `}
285
- {...(props as ComponentProps<typeof Menu.Popup>)}
286
- onKeyDown={(event) => {
287
- props.onKeyDown?.(event)
190
+ <MenuTriggerContext.Provider value={true}>
191
+ {hasTrigger ? (
192
+ // Hoist any tooltip used in `trigger` for its interactions to work properly
193
+ <Tooltip>
194
+ <MenuTrigger
195
+ nativeButton={!isNested}
196
+ // Ensure submenu triggers get disabled properly (see https://github.com/mui/base-ui/issues/3850)
197
+ disabled={triggerIsElement && trigger.props.disabled === true}
198
+ openOnHover={triggerMode === 'hover'}
199
+ data-open-on-hover={triggerMode === 'hover' || undefined}
200
+ className={`
201
+ u:i:open:data-open-on-hover:state-hover
202
+ u:iii:open:not-data-open-on-hover:state-active
203
+ `}
204
+ render={
205
+ triggerIsElement
206
+ ? trigger
207
+ : (renderProps: RenderFnProps) => {
208
+ // Don't pass event handlers and other potentially too specific props (e.g. `type`)
209
+ const filteredRenderProps = Object.fromEntries(
210
+ Object.entries(renderProps).filter(
211
+ ([prop]) =>
212
+ prop === 'ref' ||
213
+ prop === 'id' ||
214
+ prop === 'className' ||
215
+ prop.startsWith('aria-') ||
216
+ prop.startsWith('data-'),
217
+ ),
218
+ )
219
+ return trigger(filteredRenderProps, {
220
+ open,
221
+ setOpen,
222
+ openMenu: () => setOpen(true),
223
+ })
224
+ }
225
+ }
226
+ />
227
+ </Tooltip>
228
+ ) : (
288
229
  /**
289
- * Ensure Shift + Tab doesn't close the menu if there is a previous focusable element
290
- * that is not a menu item (or if the previous focusable element is a menu item while
291
- * the element that is currently focused is not).
230
+ * Base UI doesn't explicitly support menus with no `Menu.Trigger`, `Menu.Portal`, or
231
+ * `Menu.Positioner`, so we work around it. We render an invisible trigger to satisfy
232
+ * focus and keyboard expectations (note that it is visible when focused, as an
233
+ * outline). Then, to ensure the menu is rendered inline and not anchored to that
234
+ * trigger, we customize the portal container to be a sibling element (with `contents
235
+ * *:contents` so it's a transparent wrapper), and we apply positioning styles only when
236
+ * a trigger is provided (see `Menu.Positioner`'s `render` prop below). Finally, to
237
+ * prevent focus-trapping behavior that assumes a floating menu, we disable `modal` and
238
+ * hide Base UI’s focus guards with CSS (see root styles above).
292
239
  */
293
- if (event.key === 'Tab' && event.shiftKey) {
294
- const focusableElements = [
295
- ...event.currentTarget.querySelectorAll<HTMLElement>(`
296
- a[href],
297
- button:enabled,
298
- input:enabled,
299
- select:enabled,
300
- textarea:enabled,
301
- summary,
302
- [tabindex]:not([tabindex="-1"]),
303
- [contenteditable="true"]
304
- `),
305
- ]
306
- const previousElement = focusableElements.reverse().find((element) => {
307
- return (
308
- element.compareDocumentPosition(document.activeElement!) &
309
- Node.DOCUMENT_POSITION_FOLLOWING
310
- )
311
- })
312
- const activeElementIsMenuItem =
313
- document.activeElement?.classList.contains('gds-menu-item')
314
- const previousElementIsMenuItem = previousElement?.classList.contains('gds-menu-item')
315
- if (
316
- (previousElement && !previousElementIsMenuItem) ||
317
- (!activeElementIsMenuItem && previousElementIsMenuItem)
318
- ) {
319
- event.preventBaseUIHandler()
320
- return
321
- }
240
+ <>
241
+ <Menu.Trigger
242
+ aria-label={
243
+ props['aria-label'] ||
244
+ (label && typeof label === 'string' ? label : undefined) ||
245
+ 'Menu'
246
+ }
247
+ className={`
248
+ pointer-events-none absolute inset-[anchor(inside)] rounded-inherit [position-anchor:--gds-menu]
249
+ not-open:hidden
250
+ `}
251
+ />
252
+ <div ref={portalRef} className="contents *:contents" />
253
+ </>
254
+ )}
255
+ </MenuTriggerContext.Provider>
256
+ <Menu.Portal
257
+ container={
258
+ !hasTrigger
259
+ ? portalRef
260
+ : /**
261
+ * Specifying `document.body` because while it is the default for top-level menus, a nested menu
262
+ * defaults to using the same portal as its parent, which is not desirable when the parent is
263
+ * rendered inline (it can cause the nested menu to appear behind other elements).
264
+ */
265
+ document?.body
266
+ }
267
+ >
268
+ <Menu.Positioner
269
+ side={
270
+ cssProps.side === 'start'
271
+ ? 'inline-start'
272
+ : cssProps.side === 'end'
273
+ ? 'inline-end'
274
+ : cssProps.side
322
275
  }
323
- // If there is a search input in the menu...
324
- const searchInput = event.currentTarget.querySelector('input[type="search"]')
325
- if (searchInput instanceof HTMLInputElement) {
326
- // If the user typed some text, ensure it goes in the input instead of Base UI focusing the item that matches
327
- if (
328
- (event.key.length === 1 && event.key !== ' ') ||
329
- event.key === 'Backspace' ||
330
- event.key === 'ArrowLeft' ||
331
- event.key === 'ArrowRight'
332
- ) {
333
- event.preventBaseUIHandler()
334
- searchInput.focus()
335
- } else {
336
- const searchInputIsFocused = document.activeElement === searchInput
337
- const menuItems = [
338
- ...event.currentTarget.querySelectorAll<HTMLElement>('.gds-menu-item'),
339
- ]
340
- const highlightedMenuItem = menuItems.find((item) =>
341
- item.matches('[data-highlighted]'),
342
- )
276
+ sideOffset={twToPx(cssProps.gap)}
277
+ align={cssProps.align}
278
+ alignOffset={twToPx(cssProps.alignOffset)}
279
+ collisionPadding={8}
280
+ collisionAvoidance={{
281
+ side: 'flip',
282
+ align: 'flip',
283
+ fallbackAxisSide:
284
+ cssProps.side === 'start' || cssProps.side === 'end' ? 'end' : 'none',
285
+ }}
286
+ data-has-trigger={hasTrigger || undefined}
287
+ data-match-trigger-width={cssProps.matchTriggerWidth || undefined}
288
+ data-match-trigger-height={cssProps.matchTriggerHeight || undefined}
289
+ className={cn(
290
+ `root-flex
291
+ [--available-height-but-not-too-small:max(var(--available-height),--spacing(16))]
292
+ [anchor-name:--gds-menu]
293
+ has-nested/menu-footer:[--available-height-but-not-too-small:max(var(--available-height),--spacing(32))]
294
+ has-nested/menu-header:[--available-height-but-not-too-small:max(var(--available-height),--spacing(32))]
295
+ u:w-max
296
+ u:max-w-full
297
+ u:rounded-12
298
+ u:data-has-trigger:max-h-(--available-height-but-not-too-small)
299
+ u:data-has-trigger:max-w-(--available-width)
300
+ u:i:data-[match-trigger-height=at-least]:min-h-[min(var(--anchor-height),var(--available-height-but-not-too-small))]
301
+ u:i:data-[match-trigger-height=at-most]:max-h-[min(var(--anchor-height),var(--available-height-but-not-too-small))]
302
+ u:i:data-[match-trigger-height=true]:h-(--anchor-height)
303
+ u:i:data-[match-trigger-width=at-least]:min-w-[min(var(--anchor-width),var(--available-width))]
304
+ u:i:data-[match-trigger-width=at-most]:max-w-[min(var(--anchor-width),var(--available-width))]
305
+ u:i:data-[match-trigger-width=true]:w-(--anchor-width)`,
306
+ className,
307
+ )}
308
+ {...dirProps}
309
+ render={(renderProps) => (
310
+ <div {...renderProps} style={hasTrigger ? renderProps.style : undefined} />
311
+ )}
312
+ >
313
+ <Menu.Popup
314
+ ref={passedRef}
315
+ data-initial-open={initialOpen.current || undefined}
316
+ className={`
317
+ flex grow origin-(--transform-origin) flex-col overflow-clip rounded-inherit bg-muted outline-0 transition
318
+ data-ending-style:scale-95
319
+ data-ending-style:opacity-0
320
+ data-starting-style:not-data-initial-open:scale-95
321
+ data-starting-style:not-data-initial-open:opacity-0
322
+ `}
323
+ {...(nestedProps as ComponentProps<typeof Menu.Popup>)}
324
+ onKeyDown={(event) => {
325
+ nestedProps.onKeyDown?.(event)
343
326
  /**
344
- * If the user pressed the down arrow while the search input is focused and there is
345
- * a single menu item in the results, move the focus to it because it may already be
346
- * highlighted, which would prevent Base UI from focusing it.
327
+ * Ensure Shift + Tab doesn't close the menu if there is a previous focusable
328
+ * element that is not a menu item (or if the previous focusable element is a menu
329
+ * item while the element that is currently focused is not).
347
330
  */
348
- if (event.key === 'ArrowDown' && searchInputIsFocused && menuItems.length === 1) {
349
- event.preventBaseUIHandler()
350
- menuItems[0]!.focus()
351
- }
352
- // Inversely, if the user pressed the up arrow and the focus is on the only menu item, move it back to the search input
353
- else if (
354
- event.key === 'ArrowUp' &&
355
- menuItems.length === 1 &&
356
- document.activeElement === menuItems[0]
357
- ) {
358
- event.preventDefault()
359
- searchInput.focus()
331
+ if (event.key === 'Tab' && event.shiftKey) {
332
+ const focusableElements = [
333
+ ...event.currentTarget.querySelectorAll<HTMLElement>(`
334
+ a[href],
335
+ button:enabled,
336
+ input:enabled,
337
+ select:enabled,
338
+ textarea:enabled,
339
+ summary,
340
+ [tabindex]:not([tabindex="-1"]),
341
+ [contenteditable="true"]
342
+ `),
343
+ ]
344
+ const previousElement = focusableElements.reverse().find((element) => {
345
+ return (
346
+ element.compareDocumentPosition(document.activeElement!) &
347
+ Node.DOCUMENT_POSITION_FOLLOWING
348
+ )
349
+ })
350
+ const activeElementIsMenuItem =
351
+ document.activeElement?.classList.contains('gds-menu-item')
352
+ const previousElementIsMenuItem =
353
+ previousElement?.classList.contains('gds-menu-item')
354
+ if (
355
+ (previousElement && !previousElementIsMenuItem) ||
356
+ (!activeElementIsMenuItem && previousElementIsMenuItem)
357
+ ) {
358
+ event.preventBaseUIHandler()
359
+ return
360
+ }
360
361
  }
361
- // If the user pressed Enter while the search input is focused and a menu item is highlighted, select it
362
- else if (event.key === 'Enter' && searchInputIsFocused && highlightedMenuItem) {
363
- highlightedMenuItem.focus()
364
- simulateKeyPress(highlightedMenuItem, 'Enter', 13)
362
+ // If there is a search input in the menu...
363
+ const searchInput = event.currentTarget.querySelector('input[type="search"]')
364
+ if (searchInput instanceof HTMLInputElement) {
365
+ // If the user typed some text, ensure it goes in the input instead of Base UI focusing the item that matches
366
+ if (
367
+ (event.key.length === 1 && event.key !== ' ') ||
368
+ event.key === 'Backspace' ||
369
+ event.key === 'ArrowLeft' ||
370
+ event.key === 'ArrowRight'
371
+ ) {
372
+ event.preventBaseUIHandler()
373
+ searchInput.focus()
374
+ } else {
375
+ const searchInputIsFocused = document.activeElement === searchInput
376
+ const menuItems = [
377
+ ...event.currentTarget.querySelectorAll<HTMLElement>('.gds-menu-item'),
378
+ ]
379
+ const highlightedMenuItem = menuItems.find((item) =>
380
+ item.matches('[data-highlighted]'),
381
+ )
382
+ /**
383
+ * If the user pressed the down arrow while the search input is focused and
384
+ * there is a single menu item in the results, move the focus to it because it
385
+ * may already be highlighted, which would prevent Base UI from focusing it.
386
+ */
387
+ if (
388
+ event.key === 'ArrowDown' &&
389
+ searchInputIsFocused &&
390
+ menuItems.length === 1
391
+ ) {
392
+ event.preventBaseUIHandler()
393
+ menuItems[0]!.focus()
394
+ }
395
+ // Inversely, if the user pressed the up arrow and the focus is on the only menu item, move it back to the search input
396
+ else if (
397
+ event.key === 'ArrowUp' &&
398
+ menuItems.length === 1 &&
399
+ document.activeElement === menuItems[0]
400
+ ) {
401
+ event.preventDefault()
402
+ searchInput.focus()
403
+ }
404
+ // If the user pressed Enter while the search input is focused and a menu item is highlighted, select it
405
+ else if (event.key === 'Enter' && searchInputIsFocused && highlightedMenuItem) {
406
+ highlightedMenuItem.focus()
407
+ window.setTimeout(() => {
408
+ highlightedMenuItem.dispatchEvent(
409
+ new KeyboardEvent('keydown', {
410
+ key: 'Enter',
411
+ code: 'Enter',
412
+ keyCode: 13,
413
+ which: 13,
414
+ bubbles: true,
415
+ cancelable: true,
416
+ }),
417
+ )
418
+ }, 0)
419
+ }
420
+ }
365
421
  }
366
- }
367
- }
368
- }}
369
- >
370
- {/**
371
- * This div gives the menu a "minimum width" that is not as strict as `min-width` (a fixed width
372
- * overrides it, for example). A more elegant way to do this would be to change `u:w-max` to
373
- * `u:w-[calc-size(max-content,max(size,theme(spacing.58)))]`, which would also allow to override
374
- * that min width if needed, but `calc-size()` is not supported in Safari or Firefox yet.
375
- */}
376
- <div className="w-58" />
377
- {header ? (
378
- <div className="nested/menu-header u:shrink-0 u:border-b u:border-muted u:p-4">
379
- {header}
380
- </div>
381
- ) : null}
382
- <div className="scrollbar-thin grow scroll-p-8 overflow-x-clip overflow-y-auto gradient-mask-y">
383
- <div className="overflow-clip p-2">
384
- <MenuItemsContext.Provider value={{ position }}>
385
- <MenuGroupContext.Provider value={null}>
386
- {/* When `label` or `type` is provided at the root level, automatically wrap `children` in a group */}
387
- {label !== undefined || type !== undefined ? (
388
- type === 'radio' ? (
389
- <MenuGroup
390
- label={label}
391
- type={type}
392
- value={value}
393
- defaultValue={defaultValue}
394
- onChange={onChange}
395
- >
396
- {children}
397
- </MenuGroup>
422
+ }}
423
+ >
424
+ {/**
425
+ * This div gives the menu a "minimum width" that is not as strict as `min-width` (a fixed width
426
+ * overrides it, for example). A more elegant way to do this would be to change `u:w-max` to
427
+ * `u:w-[calc-size(max-content,max(size,theme(spacing.58)))]`, which would also allow to override
428
+ * that min width if needed, but `calc-size()` is not supported in Safari or Firefox yet.
429
+ */}
430
+ <div className="w-58" />
431
+ {header ? (
432
+ <div className="nested/menu-header u:shrink-0 u:border-b u:border-muted u:p-4">
433
+ {header}
434
+ </div>
435
+ ) : null}
436
+ <div className="scrollbar-thin grow scroll-p-8 overflow-x-clip overflow-y-auto gradient-mask-y">
437
+ <div className="overflow-clip p-2">
438
+ <MenuGroupContext.Provider value="root">
439
+ {/* When `label` or `type` is provided at the root level, automatically wrap `children` in a group */}
440
+ {label !== undefined || type !== undefined ? (
441
+ type === 'radio' ? (
442
+ <MenuGroup
443
+ label={label}
444
+ type={type}
445
+ value={value}
446
+ defaultValue={defaultValue}
447
+ onChange={onChange}
448
+ >
449
+ {children}
450
+ </MenuGroup>
451
+ ) : (
452
+ <MenuGroup label={label} type={type}>
453
+ {children}
454
+ </MenuGroup>
455
+ )
398
456
  ) : (
399
- <MenuGroup label={label} type={type}>
400
- {children}
401
- </MenuGroup>
402
- )
403
- ) : (
404
- children
405
- )}
406
- <div className="mt-[calc(-2*(--spacing(2))-1px)] not-preceded-by-peer/menu-group:hidden" />
407
- </MenuGroupContext.Provider>
408
- </MenuItemsContext.Provider>
409
- </div>
410
- </div>
411
- {footer ? (
412
- <div className="nested/menu-footer u:shrink-0 u:border-t u:border-muted u:p-4">
413
- {footer}
414
- </div>
415
- ) : null}
416
- <div className="pointer-events-none absolute inset-0 rounded-inherit border border-muted" />
417
- </Menu.Popup>
418
- </MaybePortal>
419
- </Root>
457
+ children
458
+ )}
459
+ <div className="mt-[calc(-2*(--spacing(2))-1px)] not-preceded-by-peer/menu-group:hidden" />
460
+ </MenuGroupContext.Provider>
461
+ </div>
462
+ </div>
463
+ {footer ? (
464
+ <div className="nested/menu-footer u:shrink-0 u:border-t u:border-muted u:p-4">
465
+ {footer}
466
+ </div>
467
+ ) : null}
468
+ <div className="pointer-events-none absolute inset-0 rounded-inherit border border-muted" />
469
+ </Menu.Popup>
470
+ </Menu.Positioner>
471
+ </Menu.Portal>
472
+ </MenuRoot>
473
+ </div>
420
474
  )
421
475
  }
422
476
  MenuRoot.displayName = 'Menu'
423
477
 
424
- const MenuItemsContext = createContext<{
425
- position: MenuPosition | false
426
- } | null>(null)
427
-
428
- const MaybePortal = ({
429
- position,
430
- ...props
431
- }: ComponentProps<'div'> & { position: MenuPosition | false }) => {
432
- const containerRef = useRef<HTMLDivElement>(null)
433
- const { dirProps } = useGDS()
434
-
435
- return (
436
- <>
437
- {/**
438
- * Ideally, we simply wouldn't render a `Menu.Portal` or `Menu.Positioner` when `position` is
439
- * `false`, but they are required by Base UI, so we hack our way around them.
440
- */}
441
- {!position ? <div ref={containerRef} className="contents *:contents" /> : null}
442
- <Menu.Portal
443
- container={
444
- !position
445
- ? containerRef
446
- : /**
447
- * Specifying `document.body` because while it is the default for top-level menus, a nested menu
448
- * defaults to using the same portal as its parent, which is not desirable when the parent is not
449
- * positioned (it can cause the nested menu to appear behind other elements).
450
- */
451
- document?.body
452
- }
453
- >
454
- <Menu.Positioner
455
- align={position ? position.align : 'start'}
456
- alignOffset={position ? twToPx(position.alignOffset) : 0}
457
- side={position ? position.side : 'bottom'}
458
- sideOffset={position ? twToPx(position.gap) : 0}
459
- collisionPadding={8}
460
- {...dirProps}
461
- render={({ style, ...otherPositionerProps }) => {
462
- const allowedPositionerProps = position
463
- ? { style, ...otherPositionerProps }
464
- : otherPositionerProps
465
- return <div {...allowedPositionerProps} {...props} />
466
- }}
467
- />
468
- </Menu.Portal>
469
- </>
470
- )
471
- }
472
-
473
- const MenuGroupContext = createContext<{
474
- type: MenuItemType
475
- } | null>(null)
478
+ const MenuGroupContext = createContext<
479
+ | {
480
+ type: MenuItemType
481
+ }
482
+ | 'root'
483
+ | null
484
+ >(null)
476
485
 
477
486
  declare namespace MenuGroupProps {
478
487
  interface BaseProps extends Omit<ComponentProps<'div'>, 'defaultValue' | 'onChange'> {}
@@ -495,7 +504,8 @@ export function MenuGroup<T extends OptionValue>({
495
504
  children,
496
505
  ...props
497
506
  }: MenuGroupProps<T>) {
498
- const isNested = useContext(MenuGroupContext) !== null
507
+ const groupContext = useContext(MenuGroupContext)
508
+ const isNested = groupContext !== null && groupContext !== 'root'
499
509
 
500
510
  if (isNested) {
501
511
  throw new Error(
@@ -617,179 +627,173 @@ export function MenuItem<T extends OptionValue>({
617
627
  const hasAncestorMenuTrigger = useContext(MenuTriggerContext) !== false
618
628
  const type = hasAncestorMenuTrigger
619
629
  ? 'submenu-trigger'
620
- : (passedType ?? groupContext?.type ?? 'plain')
621
-
622
- const Element = (() => {
623
- switch (type) {
624
- case 'plain':
625
- return Menu.Item
626
- case 'checkbox':
627
- return CheckboxItem
628
- case 'radio':
629
- return Menu.RadioItem
630
- case 'submenu-trigger':
631
- return Render
630
+ : (passedType ??
631
+ (groupContext !== null && groupContext !== 'root' ? groupContext.type : undefined) ??
632
+ 'plain')
633
+
634
+ const renderContent = (renderProps: RenderFnProps) => {
635
+ let addonBefore: AddonValue = passedAddonBefore
636
+ let addonInside: AddonValue = undefined
637
+ let addonAfter: AddonValue = passedAddonAfter
638
+ if (type === 'checkbox') {
639
+ if (addonBefore) addonInside = addonBefore
640
+ addonBefore = (
641
+ <Checkbox
642
+ inert={true}
643
+ aria-label="" // Prevent console warning about missing label
644
+ className={`
645
+ @state-checked/menu-item:state-checked
646
+ @state-indeterminate/menu-item:state-indeterminate
647
+ @state-hover/menu-item:state-hover
648
+ @state-active/menu-item:state-active
649
+ @state-disabled/menu-item:state-idle
650
+ @state-disabled/menu-item:@state-checked/menu-item:state-disabled
651
+ `}
652
+ />
653
+ )
632
654
  }
633
- })()
634
- const elementProps = (() => {
635
- switch (type) {
636
- case 'plain':
637
- return { disabled } satisfies ComponentProps<typeof Menu.Item>
638
- case 'checkbox':
639
- return {
640
- disabled,
641
- checked,
642
- defaultChecked,
643
- onCheckedChange: onChange,
644
- closeOnClick: false,
645
- } satisfies ComponentProps<typeof CheckboxItem>
646
- case 'radio':
647
- return {
648
- value: passedValue !== undefined ? passedValue : autoValue,
649
- disabled,
650
- closeOnClick: true,
651
- } satisfies ComponentProps<typeof Menu.RadioItem>
652
- case 'submenu-trigger':
653
- return {}
655
+ if (type === 'radio') {
656
+ if (addonBefore) addonInside = addonBefore
657
+ addonBefore = <CheckIcon alt="" className="@state-unchecked/menu-item:invisible" />
654
658
  }
655
- })()
656
-
657
- return (
658
- <Element
659
- ref={stateElementPassedRef}
660
- {...state.polyfillAttributes}
661
- {...elementProps}
662
- {...(props as ComponentProps<typeof Element>)}
663
- onKeyDown={(
664
- event: Parameters<
665
- NonNullable<
666
- ComponentProps<
667
- typeof Menu.Item | typeof CheckboxItem | typeof Menu.RadioItem
668
- >['onKeyDown']
669
- >
670
- >[0],
671
- ) => {
672
- props.onKeyDown?.(event)
673
- /**
674
- * Allow Space to activate menu items that are links. TODO: This may not be needed in the
675
- * future (see https://github.com/mui/base-ui/issues/1746).
676
- */
677
- if (
678
- 'preventBaseUIHandler' in event &&
679
- !event.baseUIHandlerPrevented &&
680
- event.key === ' ' &&
681
- href !== undefined
682
- ) {
683
- event.preventBaseUIHandler()
684
- elementRef.current?.click()
685
- }
686
- }}
687
- render={(renderProps: Record<string, unknown>) => {
688
- let addonBefore: AddonValue = passedAddonBefore
689
- let addonInside: AddonValue = undefined
690
- let addonAfter: AddonValue = passedAddonAfter
691
- if (type === 'checkbox') {
692
- if (addonBefore) addonInside = addonBefore
693
- addonBefore = (
694
- <Checkbox
695
- inert={true}
696
- aria-label="" // Prevent console warning about missing label
697
- className={`
698
- @state-checked/menu-item:state-checked
699
- @state-indeterminate/menu-item:state-indeterminate
700
- @state-hover/menu-item:state-hover
701
- @state-active/menu-item:state-active
702
- @state-disabled/menu-item:state-idle
703
- @state-disabled/menu-item:@state-checked/menu-item:state-disabled
704
- `}
705
- />
706
- )
707
- }
708
- if (type === 'radio') {
709
- if (addonBefore) addonInside = addonBefore
710
- addonBefore = <CheckIcon alt="" className="@state-unchecked/menu-item:invisible" />
711
- }
712
- if (loading) {
713
- addonAfter = <LoadingIcon />
714
- }
715
- if (type === 'submenu-trigger' && addonAfter === undefined) {
716
- addonAfter = <CaretRightIcon alt="" />
717
- }
718
- if (href !== undefined && addonAfter === undefined) {
719
- addonAfter = (
720
- <ButtonOrLink.Consumer
721
- render={(buttonOrLinkState) =>
722
- buttonOrLinkState?.target === '_blank' ? (
723
- <ArrowUpRightInteractiveIcon hiddenByDefault />
724
- ) : (
725
- <ArrowRightInteractiveIcon hiddenByDefault />
726
- )
727
- }
728
- />
659
+ if (loading) {
660
+ addonAfter = <LoadingIcon />
661
+ }
662
+ if (type === 'submenu-trigger' && addonAfter === undefined) {
663
+ addonAfter = <CaretRightIcon alt="" />
664
+ }
665
+ if (href !== undefined && addonAfter === undefined) {
666
+ addonAfter = (
667
+ <ButtonOrLink.Consumer
668
+ render={(buttonOrLinkState) =>
669
+ buttonOrLinkState?.target === '_blank' ? (
670
+ <ArrowUpRightInteractiveIcon hiddenByDefault />
671
+ ) : (
672
+ <ArrowRightInteractiveIcon hiddenByDefault />
673
+ )
674
+ }
675
+ />
676
+ )
677
+ }
678
+ return (
679
+ <Render
680
+ render={
681
+ href !== undefined ? (
682
+ <ButtonOrLink href={href} target={target} disabled={disabled} />
683
+ ) : (
684
+ <div />
729
685
  )
730
686
  }
731
- return (
732
- <Render
733
- data-type={type}
734
- data-variant={variant}
735
- className={cn(
736
- // TODO: Replace `active:` with `data-pressed:` when https://github.com/mui/base-ui/issues/1726 is added, to style Space presses as well
737
- `gds-menu-item root-flex w-full items-center outline-0 select-none u:rounded-6 u:p-2 u:text-16 u:text-default
738
- u:state-idle
739
- u:checked:state-checked
740
- u:indeterminate:state-indeterminate
741
- u:disabled:pointer-events-none
742
- u:disabled:state-disabled
743
- u:data-highlighted:state-hover
744
- u:data-[variant=danger]:text-status-error-default
745
- u:ii:active:state-active
746
- u:ii:data-[type=submenu-trigger]:not-[a]:cursor-default
747
- u:ii:data-[type=submenu-trigger]:not-[a]:active:state-hover
748
- ${/* Disabled styles */ ''}
749
- u:*:transition-opacity
750
- u:disabled:*:opacity-disabled
751
- u:disabled:*:grayscale`,
752
- className,
753
- )}
754
- {...renderProps}
755
- render={
756
- href !== undefined ? (
757
- <ButtonOrLink href={href} target={target} disabled={disabled} />
758
- ) : (
759
- <div />
760
- )
761
- }
687
+ data-type={type}
688
+ data-variant={variant}
689
+ className={cn(
690
+ // TODO: Replace `active:` with `data-pressed:` when https://github.com/mui/base-ui/issues/1726 is added, to style Space presses as well
691
+ `gds-menu-item root-flex w-full items-center outline-0 select-none u:rounded-6 u:p-2 u:text-16
692
+ u:state-idle
693
+ u:checked:state-checked
694
+ u:indeterminate:state-indeterminate
695
+ u:disabled:pointer-events-none
696
+ u:disabled:state-disabled
697
+ u:data-highlighted:state-hover
698
+ u:data-[variant=danger]:text-status-error-default
699
+ u:ii:active:state-active
700
+ u:ii:data-[type=submenu-trigger]:not-[a]:cursor-default
701
+ u:ii:data-[type=submenu-trigger]:not-[a]:active:state-hover
702
+ ${/* Disabled styles */ ''}
703
+ u:*:transition-opacity
704
+ u:disabled:*:opacity-disabled
705
+ u:disabled:*:grayscale`,
706
+ className,
707
+ )}
708
+ {...renderProps}
709
+ >
710
+ <span
711
+ className={`
712
+ absolute inset-0 rounded-inherit opacity-100 filter-none transition
713
+ @state-hover/menu-item:bg-elevated
714
+ @state-active/menu-item:bg-default
715
+ @state-active/menu-item:transition-none
716
+ `}
717
+ />
718
+ {addonBefore ? (
719
+ <MenuItemAddon
720
+ className={`
721
+ me-2
722
+ has-checkbox:@state-checked/menu-item:opacity-100
723
+ has-checkbox:@state-checked/menu-item:filter-none
724
+ `}
762
725
  >
763
- <span
764
- className={`
765
- absolute inset-0 rounded-inherit opacity-100 filter-none transition
766
- @state-hover/menu-item:bg-elevated
767
- @state-active/menu-item:bg-default
768
- @state-active/menu-item:transition-none
769
- `}
770
- />
771
- {addonBefore ? (
772
- <MenuItemAddon
773
- className={`
774
- me-2
775
- has-checkbox:@state-checked/menu-item:opacity-100
776
- has-checkbox:@state-checked/menu-item:filter-none
777
- `}
778
- >
779
- {renderAddon(addonBefore)}
780
- </MenuItemAddon>
781
- ) : null}
782
- {addonInside ? (
783
- <MenuItemAddon className="me-1">{renderAddon(addonInside)}</MenuItemAddon>
784
- ) : null}
785
- <span className="grow truncate pe-1">{children || <>&nbsp;</>}</span>
786
- {addonAfter ? (
787
- <MenuItemAddon className="ms-2">{renderAddon(addonAfter)}</MenuItemAddon>
788
- ) : null}
789
- </Render>
790
- )
791
- }}
792
- />
726
+ {renderAddon(addonBefore)}
727
+ </MenuItemAddon>
728
+ ) : null}
729
+ {addonInside ? (
730
+ <MenuItemAddon className="me-1">{renderAddon(addonInside)}</MenuItemAddon>
731
+ ) : null}
732
+ <span className="grow truncate pe-1">{children || <>&nbsp;</>}</span>
733
+ {addonAfter ? (
734
+ <MenuItemAddon className="ms-2">{renderAddon(addonAfter)}</MenuItemAddon>
735
+ ) : null}
736
+ </Render>
737
+ )
738
+ }
739
+
740
+ // Filter out undefined values to satisfy Base UI's `exactOptionalPropertyTypes`
741
+ // TODO: Remove when https://github.com/mui/base-ui/pull/3302 is released
742
+ const definedProps = Object.fromEntries(
743
+ Object.entries(props).filter(([, value]) => value !== undefined),
744
+ )
745
+ const baseProps = {
746
+ ref: stateElementPassedRef,
747
+ ...state.polyfillAttributes,
748
+ ...definedProps,
749
+ }
750
+
751
+ if (type === 'submenu-trigger') {
752
+ return renderContent(baseProps)
753
+ }
754
+
755
+ const onKeyDown: ComponentProps<typeof Menu.Item>['onKeyDown'] = (event) => {
756
+ props.onKeyDown?.(event)
757
+ /**
758
+ * Allow Space to activate menu items that are links. This may not be needed in the future (see
759
+ * https://github.com/mui/base-ui/issues/1746).
760
+ */
761
+ if (!event.baseUIHandlerPrevented && event.key === ' ' && href !== undefined) {
762
+ event.preventBaseUIHandler()
763
+ elementRef.current?.click()
764
+ }
765
+ }
766
+
767
+ if (type === 'checkbox') {
768
+ return (
769
+ <CheckboxItem
770
+ {...baseProps}
771
+ closeOnClick={false}
772
+ checked={checked}
773
+ defaultChecked={defaultChecked}
774
+ onCheckedChange={onChange}
775
+ onKeyDown={onKeyDown}
776
+ disabled={disabled}
777
+ render={renderContent}
778
+ />
779
+ )
780
+ }
781
+
782
+ if (type === 'radio') {
783
+ return (
784
+ <Menu.RadioItem
785
+ {...baseProps}
786
+ closeOnClick={true}
787
+ value={passedValue !== undefined ? passedValue : autoValue}
788
+ onKeyDown={onKeyDown}
789
+ disabled={disabled}
790
+ render={renderContent}
791
+ />
792
+ )
793
+ }
794
+
795
+ return (
796
+ <Menu.Item {...baseProps} onKeyDown={onKeyDown} disabled={disabled} render={renderContent} />
793
797
  )
794
798
  }
795
799
  MenuItem.displayName = 'Menu.Item'
@@ -869,8 +873,8 @@ export function MenuSearch({
869
873
  MenuSearch.displayName = 'Menu.Search'
870
874
 
871
875
  /**
872
- * Transparent wrapper for `Menu.CheckboxItem` to support `indeterminate`. TODO: This may not be
873
- * needed in the future (see https://github.com/mui/base-ui/issues/1983).
876
+ * Transparent wrapper for `Menu.CheckboxItem` to support `indeterminate`. This may not be needed in
877
+ * the future (see https://github.com/mui/base-ui/issues/1983).
874
878
  */
875
879
  function CheckboxItem({
876
880
  checked: controlledChecked,