[JEWEL-1029] [JEWEL-1230] Reimplement Dropdown component - #3409
[JEWEL-1029] [JEWEL-1230] Reimplement Dropdown component#3409Wellington Costa (wellingtoncosta) wants to merge 1 commit into
Dropdown component#3409Conversation
Daniel Bertoldi (DanielSouzaBertoldi)
left a comment
There was a problem hiding this comment.
Awesome stuff! 🚀
Just some minor changes + a little nit-picking 😈
9196ed0 to
f0e4d72
Compare
|
Daniel Bertoldi (@DanielSouzaBertoldi) I resolved all your comments (I hope so), so please take a look on this PR again once you have a chance. Let me know if you have more questions or concerns. |
f0e4d72 to
5d93115
Compare
d9be05c to
96b26ca
Compare
|
Wellington Costa (@wellingtoncosta) I noticed some changes are still pending. Let me know once you reply to my comments/add the changes, please |
96b26ca to
69a86fb
Compare
| showIcons = anyItemHasIcon, | ||
| showKeybindings = anyItemHasKeybinding, | ||
| selectedSubMenu = selectedSubMenu, | ||
| setSelectedSubMenu = { selectedSubMenu = it }, |
There was a problem hiding this comment.
ContextMenu loses all visual container styling
High Severity
The MenuContent function was refactored to remove all container styling (shadow, border, background, rounded corners, intrinsic width, scrolling, and scrollbar), moving those responsibilities to PopupContainer. However, ContextMenu still calls MenuContent directly inside a raw Popup without PopupContainer. This means all right-click context menus rendered through ContextMenuRepresentation will appear as unstyled floating items with no background, border, shadow, or scroll support.
There was a problem hiding this comment.
Oh just saw Cursor also commented the same lol
Daniel Bertoldi (DanielSouzaBertoldi)
left a comment
There was a problem hiding this comment.
Awesome work! 🥳
| showIcons = anyItemHasIcon, | ||
| showKeybindings = anyItemHasKeybinding, | ||
| selectedSubMenu = selectedSubMenu, | ||
| setSelectedSubMenu = { selectedSubMenu = it }, |
There was a problem hiding this comment.
Oh just saw Cursor also commented the same lol
69a86fb to
d2c6822
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| true | ||
| } | ||
| ) | ||
| } |
There was a problem hiding this comment.
MenuComboBox doesn't close popup on item selection
Medium Severity
The menuController in MenuComboBox is created via remember without keys, but the onDismissRequest lambda it captures closes the popup via popupManager. The menuController.closeAll() (called by MenuItemBase on item click) invokes onDismissRequest which returns true, correctly closing the root. However, the menuController doesn't receive the InputMode from closeAll — it uses a fixed lambda. The real concern is that menuController is not keyed on popupManager, so if popupManager were ever recreated (it won't in this case since it's also remember-ed), it would capture a stale reference. This is safe as-is but fragile.
There was a problem hiding this comment.
If popupManager was keyed on another variable then the stale callback thing would have a chance to happen, I'll give you that.
However, keying menuController to popupManager would basically be a no-op. popupManager is remembered without keys so it's stable for the entire lifetime of the composable.
d2c6822 to
3ddb9d8
Compare
|
You have used all of your free Bugbot PR reviews. To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial. |
3ddb9d8 to
ddb9d8d
Compare
9140ac4 to
5c5b364
Compare
|
bugbot run |
5c5b364 to
5ff6a21
Compare
| ) { | ||
| // To avoid source/binary breaking change for clients that read this property | ||
| @Deprecated(message = "Use PopupContainerStyle.metrics.menuMargin instead.", level = DeprecationLevel.WARNING) | ||
| public val menuMargin: PaddingValues = PaddingValues() |
There was a problem hiding this comment.
This preserves the old menuMargin symbol, which is great for compat, but not the old value, which may be a compat issue. The hidden constructor still accepts menuMargin, and existing compiled callers may also read menuStyle.metrics.menuMargin, but now always see PaddingValues() instead of the value they provided. This can lead to unexpected behaviours.
Can we keep storing the old value for compatibility, even if new code should use PopupContainerStyle.metrics.menuMargin and this value is effectively never used? In any case, I reckon the deprecation message/KDoc should make that explicit.
| } | ||
|
|
||
| val needsScrolling = maxHeight != Dp.Unspecified | ||
| val scrollState = if (needsScrolling || useIntrinsicWidth) rememberScrollState() else null |
There was a problem hiding this comment.
Wouldn't this make intrinsic-width popups scrollable even when no maxHeight was requested? PopupMenu, ContextMenu, MenuComboBox, and submenus all use intrinsic width, so they all get verticalScroll/scrollbar wiring even with maxHeight = Dp.Unspecified.
I think intrinsic width and scrolling should stay separate: use the intrinsic-width layout branch if needed, but only create/apply the scroll state when needsScrolling is true.
Note
This is an AI finding, I did not manage to confirm it, but seems legit?
There was a problem hiding this comment.
Yes, I do think this is a valid point. useIntrinsicWidth should only expand the container to fit its content 🤔 if we were applying an HorizontallyScrollbar then that'd be a different story, but that's not the case here. Scroll should only be applied when the height of the popup is not enough to display everything it has to
| .onClick { popupManager.setPopupVisible(false) }, | ||
| horizontalAlignment = horizontalPopupAlignment, | ||
| popupProperties = PopupProperties(focusable = false), | ||
| useIntrinsicWidth = useIntrinsicPopupWidth, |
There was a problem hiding this comment.
Shouldn't we also pass the resolved maxHeight to PopupContainer(maxHeight = ...) here? Right now ComboBoxImpl caps the outer modifier with heightIn(max = maxHeight), but the new scrolling contract lives in PopupContainer.maxHeight.
This is currently masked a bit by intrinsic width also creating a scroll state, but if that is fixed, MenuComboBox(maxPopupHeight = ...) with non-lazy MenuContent would be height-capped without the container knowing it should make the content scrollable.
There was a problem hiding this comment.
Not sure if this is indeed how it works, but it seems to me this may be missing the max height; this call caps the popup height via splitButtonPopupModifier.heightIn(max = maxPopupHeight), but the menu branch doesn't pass maxHeight to PopupMenu, so PopupContainer doesn't own scrolling for overflowing menu content.
Can we pass maxHeight = maxPopupHeight here?
There was a problem hiding this comment.
Yes sir 🫡 this is basically the same fix as the one before this comment. Thanks!
0ca91de to
fdbc602
Compare
Sebastiano Poggi (rock3r)
left a comment
There was a problem hiding this comment.
Thanks, all my concerns have been addressed.
86ff660 to
02e9017
Compare
02e9017 to
17e2a5f
Compare


Summary
This PR introduces a significant refactoring of
PopupMenuto usePopupContainerinstead of directly usingPopup, consolidating visual styling (shadows, borders, backgrounds) and scrolling logic in one place. Previously,MenuContenthad its own implementation of these features; nowPopupContainerhandles all popup styling consistently across the application, withMenuContentfocusing solely on rendering menu items. Both menu content and ad content are now in the same scrollable area withinPopupContainer.To support this refactoring,
PopupContainerwas enhanced with scrolling support viamaxHeight,useIntrinsicWidthfor width control, and key event handling. ThePopupMenuAPI now accepts bothmenuStyleandpopupContainerStyleparameters to better separate menu item styling from container styling.Additionally, this PR introduces
MenuComboBox, a modern replacement for the deprecatedDropdowncomponent that combinesComboBoxUI withMenuContentfor richer popup functionality including icons, keybindings, separators, and submenus. The component provides better keyboard navigation, focus management, and accessibility support.Showcase samples were updated to demonstrate
MenuComboBoxusage.Screen recording
Screen.Recording.2026-02-05.at.11.51.22.AM.mov
Release notes
Dropdowncomponent is now deprecated in favor ofMenuComboBoxNew features
PopupContainerwith optionalmaxHeightparameter and vertical scrollbaruseIntrinsicWidthparameter toPopupContainerfor controlling width measurement (set tofalseforSubcomposeLayout-based components likeLazyColumn)PopupContainerviaonPreviewKeyEventandonKeyEventparametersPopupMenuoverloads accepting bothmenuStyleandpopupContainerStylefor better separation of menu item styling from container stylingMenuComboBoxas modernDropdownreplacement with support for menu items with icons, keybindings, separators, and submenusDeprecated API
Dropdownin favor ofMenuComboBoxwithDeprecationLevel.WARNINGPopupContainer,PopupMenu, andComboBoxoverloads in favor of new variants with enhanced parametersNote
Medium Risk
Moderate risk due to new/deprecated public UI APIs and changes to popup sizing/scrolling/focus behavior that could affect menus and combo boxes across the library.
Overview
Introduces experimental
MenuComboBox(menu-backed dropdown) built onComboBox+MenuContent, and deprecates the olderDropdownAPI with an automatedReplaceWith.Refactors
PopupMenuto render viaPopupContainer(instead of rawPopup), splittingmenuStylevspopupContainerStyleand addingmaxHeight-based scrolling support;MenuContentis simplified to item rendering only.Extends
PopupContainerwithuseIntrinsicWidth,maxHeight, and key-event hooks, and extends experimentalComboBoxto acceptPopupProperties, popup key handling, and intrinsic-width control; updatesListComboBoxpopup focusability handling and fixes staleonPopupVisibleChangecaptures.Updates the showcase to demonstrate
MenuComboBox(icons, separators, submenus, disabled state) and refreshes API dumps accordingly.Written by Cursor Bugbot for commit d2c6822. This will update automatically on new commits. Configure here.