title_bar: Add organization selector (#49763)

Marshall Bowers created

This PR adds an organization selector to the title bar for switching
between organizations.

Closes CLO-301.

Release Notes:

- N/A

Change summary

crates/client/src/user.rs         |  8 ++++
crates/title_bar/src/title_bar.rs | 62 +++++++++++++++++++++++++++++++-
2 files changed, 67 insertions(+), 3 deletions(-)

Detailed changes

crates/client/src/user.rs 🔗

@@ -690,6 +690,14 @@ impl UserStore {
         self.current_organization.clone()
     }
 
+    pub fn set_current_organization(&mut self, organization: Arc<Organization>) {
+        self.current_organization.replace(organization);
+    }
+
+    pub fn organizations(&self) -> &Vec<Arc<Organization>> {
+        &self.organizations
+    }
+
     pub fn plan(&self) -> Option<Plan> {
         #[cfg(debug_assertions)]
         if let Ok(plan) = std::env::var("ZED_SIMULATE_PLAN").as_ref() {

crates/title_bar/src/title_bar.rs 🔗

@@ -24,9 +24,9 @@ use client::{Client, UserStore, zed_urls};
 use cloud_api_types::Plan;
 use feature_flags::{AgentV2FeatureFlag, FeatureFlagAppExt};
 use gpui::{
-    Action, AnyElement, App, Context, Corner, Element, Entity, Focusable, InteractiveElement,
-    IntoElement, MouseButton, ParentElement, Render, StatefulInteractiveElement, Styled,
-    Subscription, WeakEntity, Window, actions, div,
+    Action, AnyElement, App, Context, Corner, Element, Empty, Entity, Focusable,
+    InteractiveElement, IntoElement, MouseButton, ParentElement, Render,
+    StatefulInteractiveElement, Styled, Subscription, WeakEntity, Window, actions, div,
 };
 use onboarding_banner::OnboardingBanner;
 use project::{Project, git_store::GitStoreEvent, trusted_worktrees::TrustedWorktrees};
@@ -225,6 +225,7 @@ impl Render for TitleBar {
                     user.is_none() && TitleBarSettings::get_global(cx).show_sign_in,
                     |this| this.child(self.render_sign_in_button(cx)),
                 )
+                .child(self.render_organization_menu_button(cx))
                 .when(TitleBarSettings::get_global(cx).show_user_menu, |this| {
                     this.child(self.render_user_menu_button(cx))
                 })
@@ -953,6 +954,61 @@ impl TitleBar {
             })
     }
 
+    pub fn render_organization_menu_button(&mut self, cx: &mut Context<Self>) -> AnyElement {
+        let Some(organization) = self.user_store.read(cx).current_organization() else {
+            return Empty.into_any_element();
+        };
+
+        PopoverMenu::new("organization-menu")
+            .anchor(Corner::TopRight)
+            .menu({
+                let user_store = self.user_store.clone();
+                move |window, cx| {
+                    ContextMenu::build(window, cx, |mut menu, _window, cx| {
+                        menu = menu.header("Organizations").separator();
+
+                        for organization in user_store.read(cx).organizations() {
+                            let organization = organization.clone();
+
+                            menu = menu.custom_entry(
+                                {
+                                    let organization = organization.clone();
+                                    move |_window, _cx| {
+                                        h_flex()
+                                            .w_full()
+                                            .justify_between()
+                                            .child(Label::new(&organization.name))
+                                            .into_any_element()
+                                    }
+                                },
+                                {
+                                    let user_store = user_store.clone();
+                                    let organization = organization.clone();
+                                    move |_window, cx| {
+                                        user_store.update(cx, |user_store, _cx| {
+                                            user_store
+                                                .set_current_organization(organization.clone());
+                                        });
+                                    }
+                                },
+                            );
+                        }
+
+                        menu
+                    })
+                    .into()
+                }
+            })
+            .trigger_with_tooltip(
+                Button::new("organization-menu", &organization.name)
+                    .selected_style(ButtonStyle::Tinted(TintColor::Accent))
+                    .label_size(LabelSize::Small),
+                Tooltip::text("Toggle Organization Menu"),
+            )
+            .anchor(gpui::Corner::TopRight)
+            .into_any_element()
+    }
+
     pub fn render_user_menu_button(&mut self, cx: &mut Context<Self>) -> impl Element {
         let show_update_badge = self.update_version.read(cx).show_update_in_menu_bar();