workspace: Adjust remote projects modal (#49019)

Danilo Leal created

This PR makes the remote projects a bit more consistent with other modal
dialogs by adding a footer to better expose the "open" and "open in new
window" options.

<img width="550" height="888" alt="Screenshot 2026-02-12 at 9  53@2x"
src="https://github.com/user-attachments/assets/1d7a67c4-fc69-40db-8dc4-8099bde4ebd7"
/>

- [x] Code Reviewed
- [x] Manual QA

Release Notes:

- N/A

Change summary

crates/agent_ui/src/agent_configuration/add_llm_provider_modal.rs |  1 
crates/recent_projects/src/remote_servers.rs                      | 58 
crates/ui/src/components/modal.rs                                 | 13 
3 files changed, 44 insertions(+), 28 deletions(-)

Detailed changes

crates/recent_projects/src/remote_servers.rs 🔗

@@ -44,7 +44,8 @@ use std::{
 };
 use ui::{
     CommonAnimationExt, IconButtonShape, KeyBinding, List, ListItem, ListSeparator, Modal,
-    ModalHeader, Navigable, NavigableEntry, Section, Tooltip, WithScrollbar, prelude::*,
+    ModalFooter, ModalHeader, Navigable, NavigableEntry, Section, Tooltip, WithScrollbar,
+    prelude::*,
 };
 use util::{
     ResultExt,
@@ -2749,31 +2750,15 @@ impl RemoteServerProjects {
         }
         let mut modal_section = modal_section.render(window, cx).into_any_element();
 
-        let (create_window, reuse_window) = if self.create_new_window {
-            (
-                window.keystroke_text_for(&menu::Confirm),
-                window.keystroke_text_for(&menu::SecondaryConfirm),
-            )
-        } else {
-            (
-                window.keystroke_text_for(&menu::SecondaryConfirm),
-                window.keystroke_text_for(&menu::Confirm),
-            )
-        };
-        let placeholder_text = Arc::from(format!(
-            "{reuse_window} reuses this window, {create_window} opens a new one",
-        ));
+        let is_project_selected = state.servers.iter().any(|server| match server {
+            RemoteEntry::Project { projects, .. } => projects
+                .iter()
+                .any(|(entry, _)| entry.focus_handle.contains_focused(window, cx)),
+            RemoteEntry::SshConfig { .. } => false,
+        });
 
         Modal::new("remote-projects", None)
-            .header(
-                ModalHeader::new()
-                    .child(Headline::new("Remote Projects").size(HeadlineSize::XSmall))
-                    .child(
-                        Label::new(placeholder_text)
-                            .color(Color::Muted)
-                            .size(LabelSize::XSmall),
-                    ),
-            )
+            .header(ModalHeader::new().headline("Remote Projects"))
             .section(
                 Section::new().padded(false).child(
                     v_flex()
@@ -2801,6 +2786,31 @@ impl RemoteServerProjects {
                         .vertical_scrollbar_for(&state.scroll_handle, window, cx),
                 ),
             )
+            .footer(ModalFooter::new().end_slot({
+                let confirm_button = |label: SharedString| {
+                    Button::new("select", label)
+                        .key_binding(KeyBinding::for_action(&menu::Confirm, cx))
+                        .on_click(|_, window, cx| {
+                            window.dispatch_action(menu::Confirm.boxed_clone(), cx)
+                        })
+                };
+
+                if is_project_selected {
+                    h_flex()
+                        .gap_1()
+                        .child(
+                            Button::new("open_new_window", "New Window")
+                                .key_binding(KeyBinding::for_action(&menu::SecondaryConfirm, cx))
+                                .on_click(|_, window, cx| {
+                                    window.dispatch_action(menu::SecondaryConfirm.boxed_clone(), cx)
+                                }),
+                        )
+                        .child(confirm_button("Open".into()))
+                        .into_any_element()
+                } else {
+                    confirm_button("Select".into()).into_any_element()
+                }
+            }))
             .into_any_element()
     }
 

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

@@ -77,7 +77,6 @@ impl RenderOnce for Modal {
                     .w_full()
                     .flex_1()
                     .gap(DynamicSpacing::Base08.rems(cx))
-                    .when(self.footer.is_some(), |this| this.pb_4())
                     .when_some(
                         self.container_scroll_handler,
                         |this, container_scroll_handle| {
@@ -366,15 +365,21 @@ impl RenderOnce for Section {
                         .border_1()
                         .border_color(cx.theme().colors().border)
                         .bg(section_bg)
-                        .py(DynamicSpacing::Base06.rems(cx))
-                        .gap_y(DynamicSpacing::Base04.rems(cx))
-                        .child(div().flex().flex_1().size_full().children(self.children)),
+                        .child(
+                            div()
+                                .flex()
+                                .flex_1()
+                                .pb_2()
+                                .size_full()
+                                .children(self.children),
+                        ),
                 )
         } else {
             v_flex()
                 .w_full()
                 .flex_1()
                 .gap_y(DynamicSpacing::Base04.rems(cx))
+                .pb_2()
                 .when(self.padded, |this| {
                     this.px(DynamicSpacing::Base06.rems(cx) + DynamicSpacing::Base06.rems(cx))
                 })