agent: Show nav dropdown close button only on hover (#29732)

Danilo Leal created

Change summary

crates/agent/src/assistant_panel.rs        |  3 
crates/ui/src/components/context_menu.rs   | 55 ++++++++++++++++++++++-
crates/ui/src/components/list/list_item.rs |  8 +++
3 files changed, 61 insertions(+), 5 deletions(-)

Detailed changes

crates/agent/src/assistant_panel.rs 🔗

@@ -458,7 +458,8 @@ impl AssistantPanel {
 
                         for entry in recently_opened.iter() {
                             let summary = entry.summary(cx);
-                            menu = menu.entry_with_end_slot(
+
+                            menu = menu.entry_with_end_slot_on_hover(
                                 summary,
                                 None,
                                 {

crates/ui/src/components/context_menu.rs 🔗

@@ -52,6 +52,7 @@ pub struct ContextMenuEntry {
     end_slot_icon: Option<IconName>,
     end_slot_title: Option<SharedString>,
     end_slot_handler: Option<Rc<dyn Fn(Option<&FocusHandle>, &mut Window, &mut App)>>,
+    show_end_slot_on_hover: bool,
 }
 
 impl ContextMenuEntry {
@@ -70,6 +71,7 @@ impl ContextMenuEntry {
             end_slot_icon: None,
             end_slot_title: None,
             end_slot_handler: None,
+            show_end_slot_on_hover: false,
         }
     }
 
@@ -365,6 +367,7 @@ impl ContextMenu {
             end_slot_icon: None,
             end_slot_title: None,
             end_slot_handler: None,
+            show_end_slot_on_hover: false,
         }));
         self
     }
@@ -392,6 +395,35 @@ impl ContextMenu {
             end_slot_icon: Some(end_slot_icon),
             end_slot_title: Some(end_slot_title),
             end_slot_handler: Some(Rc::new(move |_, window, cx| end_slot_handler(window, cx))),
+            show_end_slot_on_hover: false,
+        }));
+        self
+    }
+
+    pub fn entry_with_end_slot_on_hover(
+        mut self,
+        label: impl Into<SharedString>,
+        action: Option<Box<dyn Action>>,
+        handler: impl Fn(&mut Window, &mut App) + 'static,
+        end_slot_icon: IconName,
+        end_slot_title: SharedString,
+        end_slot_handler: impl Fn(&mut Window, &mut App) + 'static,
+    ) -> Self {
+        self.items.push(ContextMenuItem::Entry(ContextMenuEntry {
+            toggle: None,
+            label: label.into(),
+            handler: Rc::new(move |_, window, cx| handler(window, cx)),
+            icon: None,
+            icon_position: IconPosition::End,
+            icon_size: IconSize::Small,
+            icon_color: None,
+            action,
+            disabled: false,
+            documentation_aside: None,
+            end_slot_icon: Some(end_slot_icon),
+            end_slot_title: Some(end_slot_title),
+            end_slot_handler: Some(Rc::new(move |_, window, cx| end_slot_handler(window, cx))),
+            show_end_slot_on_hover: true,
         }));
         self
     }
@@ -418,6 +450,7 @@ impl ContextMenu {
             end_slot_icon: None,
             end_slot_title: None,
             end_slot_handler: None,
+            show_end_slot_on_hover: false,
         }));
         self
     }
@@ -472,6 +505,7 @@ impl ContextMenu {
             end_slot_icon: None,
             end_slot_title: None,
             end_slot_handler: None,
+            show_end_slot_on_hover: false,
         }));
         self
     }
@@ -500,6 +534,7 @@ impl ContextMenu {
             end_slot_icon: None,
             end_slot_title: None,
             end_slot_handler: None,
+            show_end_slot_on_hover: false,
         }));
         self
     }
@@ -519,6 +554,7 @@ impl ContextMenu {
             end_slot_icon: None,
             end_slot_title: None,
             end_slot_handler: None,
+            show_end_slot_on_hover: false,
         }));
         self
     }
@@ -822,6 +858,7 @@ impl ContextMenu {
             end_slot_icon,
             end_slot_title,
             end_slot_handler,
+            show_end_slot_on_hover,
         } = entry;
         let this = cx.weak_entity();
 
@@ -884,6 +921,7 @@ impl ContextMenu {
             )
             .child(
                 ListItem::new(ix)
+                    .group_name("label_container")
                     .inset(true)
                     .disabled(*disabled)
                     .toggle_state(Some(ix) == self.selected_index)
@@ -942,8 +980,8 @@ impl ContextMenu {
                             .zip(end_slot_title.as_ref())
                             .zip(end_slot_handler.as_ref()),
                         |el, (((icon, action), title), handler)| {
-                            el.end_slot(
-                                IconButton::new("end-slot-icon", *icon)
+                            el.end_slot({
+                                let icon_button = IconButton::new("end-slot-icon", *icon)
                                     .shape(IconButtonShape::Square)
                                     .tooltip({
                                         let action_context = self.action_context.clone();
@@ -981,8 +1019,17 @@ impl ContextMenu {
                                             })
                                             .ok();
                                         }
-                                    }),
-                            )
+                                    });
+
+                                if *show_end_slot_on_hover {
+                                    div()
+                                        .visible_on_hover("label_container")
+                                        .child(icon_button)
+                                        .into_any_element()
+                                } else {
+                                    icon_button.into_any_element()
+                                }
+                            })
                         },
                     )
                     .on_click({

crates/ui/src/components/list/list_item.rs 🔗

@@ -16,6 +16,7 @@ pub enum ListItemSpacing {
 #[derive(IntoElement)]
 pub struct ListItem {
     id: ElementId,
+    group_name: Option<SharedString>,
     disabled: bool,
     selected: bool,
     spacing: ListItemSpacing,
@@ -48,6 +49,7 @@ impl ListItem {
     pub fn new(id: impl Into<ElementId>) -> Self {
         Self {
             id: id.into(),
+            group_name: None,
             disabled: false,
             selected: false,
             spacing: ListItemSpacing::Dense,
@@ -72,6 +74,11 @@ impl ListItem {
         }
     }
 
+    pub fn group_name(mut self, group_name: impl Into<SharedString>) -> Self {
+        self.group_name = Some(group_name.into());
+        self
+    }
+
     pub fn spacing(mut self, spacing: ListItemSpacing) -> Self {
         self.spacing = spacing;
         self
@@ -196,6 +203,7 @@ impl RenderOnce for ListItem {
     fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
         h_flex()
             .id(self.id)
+            .when_some(self.group_name, |this, group| this.group(group))
             .w_full()
             .relative()
             // When an item is inset draw the indent spacing outside of the item