commit 2b8e983acc9d9dab65c1eeb8e251474a1433e392
parent 0dc179d8d4c8c218dc94bb64c5eb9a09c2052f85
Author: triesap <tyson@radroots.org>
Date: Sun, 7 Jun 2026 18:53:31 -0700
ui: add sidebar account menu
Diffstat:
4 files changed, 168 insertions(+), 14 deletions(-)
diff --git a/crates/desktop/src/source_guards.rs b/crates/desktop/src/source_guards.rs
@@ -171,6 +171,7 @@ const ALLOWED_WINDOW_LITERALS: &[&str] = &[
"home-farm-setup-shipping",
"home-farm-setup-start",
"home-generate-key",
+ "home-sidebar-account-menu",
"home-nav-orders",
"home-nav-pack-day",
"home-nav-products",
diff --git a/crates/desktop/src/window.rs b/crates/desktop/src/window.rs
@@ -41,7 +41,7 @@ use radroots_app_ui::{
app_button_primary_disabled as action_button_primary_disabled,
app_button_primary_full_width as action_button_primary_full_width,
app_button_secondary as action_button, app_button_secondary_disabled as action_button_disabled,
- app_button_secondary_full_width as action_button_full_width,
+ app_button_secondary_full_width as action_button_full_width, app_button_sidebar_account_menu,
app_button_square_dropdown_secondary as action_dropdown_button, app_button_text as text_button,
app_checkbox_button as action_checkbox_button, app_checkbox_field, app_cluster, app_detail_row,
app_divider as section_divider, app_focused_detail_view, app_focused_task_view, app_form_field,
@@ -1972,6 +1972,22 @@ impl HomeView {
}
}
+ fn open_account_tab(&mut self, tab: AccountTab, cx: &mut Context<Self>) {
+ let route_changed = self.runtime.select_account();
+ if route_changed {
+ self.products_stock_editor = None;
+ self.product_editor_form = None;
+ self.clear_focused_view();
+ }
+
+ let tab_changed = self.selected_account_tab != tab;
+ self.selected_account_tab = tab;
+
+ if route_changed || tab_changed {
+ cx.notify();
+ }
+ }
+
fn select_account_tab(&mut self, tab: AccountTab, cx: &mut Context<Self>) {
if self.selected_account_tab != tab {
self.selected_account_tab = tab;
@@ -4193,6 +4209,10 @@ impl HomeView {
}),
cx.listener(|this, _, _, cx| this.open_orders(cx)),
cx.listener(|this, _, _, cx| this.open_pack_day(None, cx)),
+ cx.listener(|this, _, _, cx| this.open_account_tab(AccountTab::Profile, cx)),
+ cx.listener(|this, _, _, cx| this.open_account_tab(AccountTab::FarmDetails, cx)),
+ cx.listener(|this, _, _, cx| this.open_account_tab(AccountTab::Preferences, cx)),
+ cx.listener(|this, _, _, cx| this.open_account_tab(AccountTab::Settings, cx)),
cx,
)
.into_any_element(),
@@ -5526,6 +5546,10 @@ impl HomeView {
}),
cx.listener(|this, _, _, cx| this.open_orders(cx)),
cx.listener(|this, _, _, cx| this.open_pack_day(None, cx)),
+ cx.listener(|this, _, _, cx| this.open_account_tab(AccountTab::Profile, cx)),
+ cx.listener(|this, _, _, cx| this.open_account_tab(AccountTab::FarmDetails, cx)),
+ cx.listener(|this, _, _, cx| this.open_account_tab(AccountTab::Preferences, cx)),
+ cx.listener(|this, _, _, cx| this.open_account_tab(AccountTab::Settings, cx)),
cx,
)
.into_any_element()
@@ -13127,6 +13151,10 @@ fn home_sidebar(
on_select_products: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
on_select_orders: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
on_select_pack_day: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
+ on_select_account_profile: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
+ on_select_account_farm_details: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
+ on_select_account_preferences: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
+ on_select_account_settings: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
cx: &App,
) -> impl IntoElement {
let selected_section = selected_farmer_section(runtime);
@@ -13138,6 +13166,10 @@ fn home_sidebar(
let on_select_products = Arc::new(on_select_products);
let on_select_orders = Arc::new(on_select_orders);
let on_select_pack_day = Arc::new(on_select_pack_day);
+ let on_select_account_profile = Arc::new(on_select_account_profile);
+ let on_select_account_farm_details = Arc::new(on_select_account_farm_details);
+ let on_select_account_preferences = Arc::new(on_select_account_preferences);
+ let on_select_account_settings = Arc::new(on_select_account_settings);
let mut navigation_elements = Vec::with_capacity(navigation_sections.len());
for section in navigation_sections {
let element = match section {
@@ -13211,11 +13243,60 @@ fn home_sidebar(
.gap(px(APP_UI_THEME.shells.home_stack_gap_px))
.children(navigation_elements),
)
- .child(
- div().child(div().when_some(home_saved_farm(runtime), |this, farm| {
- this.child(home_body_text(farm.display_name.clone()))
- })),
- ),
+ .when_some(home_saved_farm(runtime), |this, farm| {
+ this.child(home_sidebar_account_menu(
+ farm.display_name.clone(),
+ Arc::clone(&on_select_account_profile),
+ Arc::clone(&on_select_account_farm_details),
+ Arc::clone(&on_select_account_preferences),
+ Arc::clone(&on_select_account_settings),
+ cx,
+ ))
+ }),
+ )
+}
+
+fn home_sidebar_account_menu<ProfileAction, FarmDetailsAction, PreferencesAction, SettingsAction>(
+ label: impl Into<SharedString>,
+ on_select_profile: Arc<ProfileAction>,
+ on_select_farm_details: Arc<FarmDetailsAction>,
+ on_select_preferences: Arc<PreferencesAction>,
+ on_select_settings: Arc<SettingsAction>,
+ cx: &App,
+) -> impl IntoElement
+where
+ ProfileAction: Fn(&ClickEvent, &mut Window, &mut App) + 'static,
+ FarmDetailsAction: Fn(&ClickEvent, &mut Window, &mut App) + 'static,
+ PreferencesAction: Fn(&ClickEvent, &mut Window, &mut App) + 'static,
+ SettingsAction: Fn(&ClickEvent, &mut Window, &mut App) + 'static,
+{
+ app_button_sidebar_account_menu(
+ "home-sidebar-account-menu",
+ label,
+ move |menu, _, _| {
+ let on_select_profile = Arc::clone(&on_select_profile);
+ let on_select_farm_details = Arc::clone(&on_select_farm_details);
+ let on_select_preferences = Arc::clone(&on_select_preferences);
+ let on_select_settings = Arc::clone(&on_select_settings);
+
+ menu.item(
+ PopupMenuItem::new(app_text(AccountTab::Profile.text_key()))
+ .on_click(move |event, window, app| on_select_profile(event, window, app)),
+ )
+ .item(
+ PopupMenuItem::new(app_text(AccountTab::FarmDetails.text_key()))
+ .on_click(move |event, window, app| on_select_farm_details(event, window, app)),
+ )
+ .item(
+ PopupMenuItem::new(app_text(AccountTab::Preferences.text_key()))
+ .on_click(move |event, window, app| on_select_preferences(event, window, app)),
+ )
+ .item(
+ PopupMenuItem::new(app_text(AccountTab::Settings.text_key()))
+ .on_click(move |event, window, app| on_select_settings(event, window, app)),
+ )
+ },
+ cx,
)
}
diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs
@@ -11,14 +11,15 @@ pub use primitives::{
app_button_icon, app_button_list_row, app_button_primary, app_button_primary_compact,
app_button_primary_compact_disabled, app_button_primary_disabled,
app_button_primary_full_width, app_button_secondary, app_button_secondary_disabled,
- app_button_secondary_full_width, app_button_square_dropdown_secondary, app_button_text,
- app_checkbox_button, app_checkbox_field, app_cluster, app_detail_row, app_divider,
- app_focused_detail_view, app_focused_task_view, app_form_field, app_form_input_text,
- app_form_section, app_heading_section, app_heading_view, app_input_text, app_pill_tabs,
- app_scroll_panel, app_segment_button_icon, app_split_shell, app_stack_h, app_stack_v,
- app_status_indicator, app_surface_card, app_surface_card_section, app_surface_panel,
- app_surface_sidebar, app_surface_window, app_text_badge, app_text_body, app_text_body_subtle,
- app_text_label, app_text_value, app_underline_tabs, label_value_list, utility_title_row,
+ app_button_secondary_full_width, app_button_sidebar_account_menu,
+ app_button_square_dropdown_secondary, app_button_text, app_checkbox_button, app_checkbox_field,
+ app_cluster, app_detail_row, app_divider, app_focused_detail_view, app_focused_task_view,
+ app_form_field, app_form_input_text, app_form_section, app_heading_section, app_heading_view,
+ app_input_text, app_pill_tabs, app_scroll_panel, app_segment_button_icon, app_split_shell,
+ app_stack_h, app_stack_v, app_status_indicator, app_surface_card, app_surface_card_section,
+ app_surface_panel, app_surface_sidebar, app_surface_window, app_text_badge, app_text_body,
+ app_text_body_subtle, app_text_label, app_text_value, app_underline_tabs, label_value_list,
+ utility_title_row,
};
pub use text::{
SettingsPreferencesGeneralRowState, app_shared_label_text, app_shared_text,
diff --git a/crates/ui/src/primitives.rs b/crates/ui/src/primitives.rs
@@ -1012,6 +1012,77 @@ pub fn app_button_ellipsis_menu(
.dropdown_menu_with_anchor(Corner::BottomRight, menu)
}
+pub fn app_button_sidebar_account_menu(
+ id: &'static str,
+ label: impl Into<SharedString>,
+ menu: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
+ cx: &App,
+) -> impl IntoElement {
+ let label = label.into();
+ let sizing = APP_UI_THEME.components.app_button.sizing;
+ let row = APP_UI_THEME.components.app_account_selector_row;
+
+ Button::new(id)
+ .custom(
+ ButtonCustomVariant::new(cx)
+ .color(rgb(row.inactive_background).into())
+ .foreground(rgb(APP_UI_THEME.foundation.text.secondary).into())
+ .border(transparent_black())
+ .hover(rgb(row.active_background).into())
+ .active(rgb(row.active_background).into()),
+ )
+ .w_full()
+ .h(px(APP_UI_THEME
+ .shells
+ .settings_account_sidebar_button_height_px))
+ .rounded(ButtonRounded::Size(px(APP_UI_THEME
+ .shells
+ .settings_account_sidebar_button_corner_radius_px)))
+ .tab_stop(false)
+ .child(
+ div()
+ .size_full()
+ .px(px(APP_UI_THEME
+ .shells
+ .settings_account_sidebar_footer_button_gap_px))
+ .flex()
+ .items_center()
+ .justify_between()
+ .gap(px(APP_UI_THEME
+ .shells
+ .settings_account_sidebar_button_gap_px))
+ .child(
+ div()
+ .flex()
+ .items_center()
+ .min_w_0()
+ .gap(px(APP_UI_THEME
+ .shells
+ .settings_account_sidebar_button_gap_px))
+ .child(
+ Icon::new(IconName::CircleUser)
+ .with_size(Size::Size(px(sizing.icon_size_px)))
+ .text_color(rgb(APP_UI_THEME.foundation.text.secondary)),
+ )
+ .child(
+ div()
+ .min_w_0()
+ .truncate()
+ .text_size(px(APP_UI_THEME.foundation.typography.body_text_px))
+ .font_weight(gpui::FontWeight::MEDIUM)
+ .text_color(rgb(APP_UI_THEME.foundation.text.secondary))
+ .child(label),
+ ),
+ )
+ .child(
+ Icon::new(IconName::ChevronsUpDown)
+ .with_size(Size::Size(px(sizing.icon_size_px)))
+ .text_color(rgb(APP_UI_THEME.foundation.text.secondary)),
+ ),
+ )
+ .dropdown_menu_with_anchor(Corner::TopLeft, menu)
+}
+
fn app_button_label(
button: Button,
label: SharedString,