@graphprotocol/gds-react 0.1.2 → 0.2.1

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