From 23ac4fd9d23eb3b8847b4c5f84073d34b9c609d5 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Tue, 24 Mar 2026 15:34:52 -0300 Subject: [PATCH] title_bar: Move the organization items inside the user menu (#52343) Closes CLO-339 This PR moves the organization menu items inside the user menu, instead of a standalone button within the title bar. We did it so because 1) we're not fully sure how useful _always_ seeing in which org you are in will be, and 2) seeing in which org you're in and seeing in which _plan_ you are in are very similar in terms of use case, and we don't currently display the plan in the title bar... thus, that served as argument for the same level of visibility. --- - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - N/A --------- Co-authored-by: Gaauwe Rombouts --- crates/title_bar/src/collab.rs | 38 ++--- crates/title_bar/src/title_bar.rs | 230 ++++++++++++++---------------- 2 files changed, 125 insertions(+), 143 deletions(-) diff --git a/crates/title_bar/src/collab.rs b/crates/title_bar/src/collab.rs index d740dd90984cd3cbbfd058f7a00a07bb7326f0cd..474d0d287e47dcc6aad0b0f5c57fce382ebf2ca9 100644 --- a/crates/title_bar/src/collab.rs +++ b/crates/title_bar/src/collab.rs @@ -383,10 +383,29 @@ impl TitleBar { ConnectionQuality::Poor => (IconName::SignalMedium, Some(Color::Warning), "Poor"), ConnectionQuality::Lost => (IconName::SignalLow, Some(Color::Error), "Lost"), }; + let quality_label: SharedString = quality_label.into(); + + children.push( + h_flex() + .gap_1() + .child( + IconButton::new("leave-call", IconName::Exit) + .style(ButtonStyle::Subtle) + .tooltip(Tooltip::text("Leave Call")) + .icon_size(IconSize::Small) + .on_click(move |_, _window, cx| { + ActiveCall::global(cx) + .update(cx, |call, cx| call.hang_up(cx)) + .detach_and_log_err(cx); + }), + ) + .child(Divider::vertical().color(DividerColor::Border)) + .into_any_element(), + ); + children.push( IconButton::new("call-quality", signal_icon) - .style(ButtonStyle::Subtle) .icon_size(IconSize::Small) .when_some(signal_color, |button, color| button.icon_color(color)) .tooltip(move |_window, cx| { @@ -413,23 +432,6 @@ impl TitleBar { }) .into_any_element(), ); - children.push( - h_flex() - .gap_1() - .child( - IconButton::new("leave-call", IconName::Exit) - .style(ButtonStyle::Subtle) - .tooltip(Tooltip::text("Leave Call")) - .icon_size(IconSize::Small) - .on_click(move |_, _window, cx| { - ActiveCall::global(cx) - .update(cx, |call, cx| call.hang_up(cx)) - .detach_and_log_err(cx); - }), - ) - .child(Divider::vertical().color(DividerColor::Border)) - .into_any_element(), - ); if is_local && can_share_projects && !is_connecting_to_project { let is_sharing_disabled = channel.is_some_and(|channel| match channel.visibility { diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index b013a593c86553c40124910a1ff1610ca1f6f00d..a393f71ec73e6b7acb2cdac38f50302410714749 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -27,9 +27,9 @@ use client::{Client, UserStore, zed_urls}; use cloud_api_types::Plan; use gpui::{ - Action, AnyElement, App, Context, Corner, Element, Empty, Entity, Focusable, - InteractiveElement, IntoElement, MouseButton, ParentElement, Render, - StatefulInteractiveElement, Styled, Subscription, WeakEntity, Window, actions, div, + Action, AnyElement, App, Context, Corner, Element, 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}; @@ -260,7 +260,6 @@ 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)) }) @@ -1132,110 +1131,70 @@ impl TitleBar { }) } - pub fn render_organization_menu_button(&mut self, cx: &mut Context) -> 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(); - - let current_organization = user_store.read(cx).current_organization(); - - for organization in user_store.read(cx).organizations() { - let organization = organization.clone(); - let plan = user_store.read(cx).plan_for_organization(&organization.id); - - let is_current = - current_organization - .as_ref() - .is_some_and(|current_organization| { - current_organization.id == organization.id - }); - - menu = menu.custom_entry( - { - let organization = organization.clone(); - move |_window, _cx| { - h_flex() - .w_full() - .gap_1() - .child( - div() - .flex_none() - .when(!is_current, |parent| parent.invisible()) - .child(Icon::new(IconName::Check)), - ) - .child( - h_flex() - .w_full() - .gap_3() - .justify_between() - .child(Label::new(&organization.name)) - .child(PlanChip::new( - plan.unwrap_or(Plan::ZedFree), - )), - ) - .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(), cx); - }); - } - }, - ); - } - - 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) -> impl Element { - let show_update_badge = self.update_version.read(cx).show_update_in_menu_bar(); + let show_update_button = self.update_version.read(cx).show_update_in_menu_bar(); - let user_store = self.user_store.read(cx); - let user = user_store.current_user(); + let user_store = self.user_store.clone(); + let user_store_read = user_store.read(cx); + let user = user_store_read.current_user(); let user_avatar = user.as_ref().map(|u| u.avatar_uri.clone()); let user_login = user.as_ref().map(|u| u.github_login.clone()); let is_signed_in = user.is_some(); - let has_subscription_period = user_store.subscription_period().is_some(); - let plan = user_store.plan().filter(|_| { + let has_subscription_period = user_store_read.subscription_period().is_some(); + let plan = user_store_read.plan().filter(|_| { // Since the user might be on the legacy free plan we filter based on whether we have a subscription period. has_subscription_period }); + let has_organization = user_store_read.current_organization().is_some(); + + let current_organization = user_store_read.current_organization(); + let organizations: Vec<_> = user_store_read + .organizations() + .iter() + .map(|org| { + let plan = user_store_read.plan_for_organization(&org.id); + (org.clone(), plan) + }) + .collect(); + + let show_user_picture = TitleBarSettings::get_global(cx).show_user_picture; + + let trigger = if is_signed_in && show_user_picture { + let avatar = user_avatar.map(|avatar| Avatar::new(avatar)).map(|avatar| { + if show_update_button { + avatar.indicator( + div() + .absolute() + .bottom_0() + .right_0() + .child(Indicator::dot().color(Color::Accent)), + ) + } else { + avatar + } + }); + + ButtonLike::new("user-menu").children(avatar) + } else { + ButtonLike::new("user-menu") + .child(Icon::new(IconName::ChevronDown).size(IconSize::Small)) + }; + PopoverMenu::new("user-menu") - .anchor(Corner::TopRight) + .trigger(trigger) .menu(move |window, cx| { - ContextMenu::build(window, cx, |menu, _, _cx| { - let user_login = user_login.clone(); + let user_login = user_login.clone(); + let current_organization = current_organization.clone(); + let organizations = organizations.clone(); + let user_store = user_store.clone(); + ContextMenu::build(window, cx, |menu, _, _cx| { menu.when(is_signed_in, |this| { + let user_login = user_login.clone(); this.custom_entry( move |_window, _cx| { let user_login = user_login.clone().unwrap_or_default(); @@ -1253,7 +1212,7 @@ impl TitleBar { ) .separator() }) - .when(show_update_badge, |this| { + .when(show_update_button, |this| { this.custom_entry( move |_window, _cx| { h_flex() @@ -1274,6 +1233,58 @@ impl TitleBar { ) .separator() }) + .when(has_organization, |this| { + let mut this = this.header("Organization"); + + for (organization, plan) in &organizations { + let organization = organization.clone(); + let plan = *plan; + + let is_current = + current_organization + .as_ref() + .is_some_and(|current_organization| { + current_organization.id == organization.id + }); + + this = this.custom_entry( + { + let organization = organization.clone(); + move |_window, _cx| { + h_flex() + .w_full() + .gap_4() + .justify_between() + .child( + h_flex() + .gap_1() + .child(Label::new(&organization.name)) + .when(is_current, |this| { + this.child( + Icon::new(IconName::Check) + .color(Color::Accent), + ) + }), + ) + .child(PlanChip::new(plan.unwrap_or(Plan::ZedFree))) + .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(), cx); + }); + } + }, + ); + } + + this.separator() + }) .action("Settings", zed_actions::OpenSettings.boxed_clone()) .action("Keymap", Box::new(zed_actions::OpenKeymap)) .action( @@ -1295,37 +1306,6 @@ impl TitleBar { }) .into() }) - .map(|this| { - if is_signed_in && TitleBarSettings::get_global(cx).show_user_picture { - let avatar = - user_avatar - .clone() - .map(|avatar| Avatar::new(avatar)) - .map(|avatar| { - if show_update_badge { - avatar.indicator( - div() - .absolute() - .bottom_0() - .right_0() - .child(Indicator::dot().color(Color::Accent)), - ) - } else { - avatar - } - }); - this.trigger_with_tooltip( - ButtonLike::new("user-menu").children(avatar), - Tooltip::text("Toggle User Menu"), - ) - } else { - this.trigger_with_tooltip( - IconButton::new("user-menu", IconName::ChevronDown) - .icon_size(IconSize::Small), - Tooltip::text("Toggle User Menu"), - ) - } - }) - .anchor(gpui::Corner::TopRight) + .anchor(Corner::TopRight) } }