feat: replace sidebar tabs with a single collapsible list

This commit is contained in:
2026-10-04 16:19:37 +07:00
parent 2fc1358530
commit acb07126c9
4 changed files with 239 additions and 233 deletions
+3
View File
@@ -10,6 +10,7 @@ use crate::avatar::Avatar;
use crate::button::Button;
use crate::menu::PopupMenu;
use crate::nav::Nav;
use crate::nav_item::NavItem;
use crate::popover::{Popover, PopoverState};
/// Builds the items of a popup menu on each render.
@@ -44,6 +45,8 @@ impl DropdownMenu for Avatar {}
impl DropdownMenu for Nav {}
impl DropdownMenu for NavItem {}
#[derive(IntoElement)]
pub struct DropdownMenuPopover<T: Selectable + IntoElement + 'static> {
id: ElementId,
+43 -6
View File
@@ -2,23 +2,26 @@ use std::rc::Rc;
use gpui::prelude::FluentBuilder as _;
use gpui::{
AnyElement, App, ClickEvent, ElementId, InteractiveElement, IntoElement, ParentElement,
RenderOnce, SharedString, StatefulInteractiveElement as _, StyleRefinement, Styled, Window,
div,
AnyElement, App, ClickEvent, ElementId, InteractiveElement, Interactivity, IntoElement,
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement as _, StyleRefinement,
Styled, Window, div,
};
use theme::ActiveTheme;
use crate::{StyledExt, h_flex};
use crate::{Selectable, StyledExt, h_flex};
/// A single navigation entry in a sidebar.
#[allow(clippy::type_complexity)]
#[derive(IntoElement)]
pub struct NavItem {
id: ElementId,
interactivity: Interactivity,
style: StyleRefinement,
icon: AnyElement,
label: SharedString,
suffix: Option<AnyElement>,
selected: bool,
clickable: bool,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
}
@@ -28,16 +31,30 @@ impl NavItem {
label: impl Into<SharedString>,
icon: impl IntoElement,
) -> Self {
let id = id.into();
let mut interactivity = Interactivity::default();
interactivity.element_id = Some(id.clone());
Self {
id: id.into(),
id,
interactivity,
style: StyleRefinement::default(),
icon: icon.into_any_element(),
label: label.into(),
suffix: None,
selected: false,
clickable: false,
on_click: None,
}
}
/// Marks the item as clickable even when it has no `on_click` handler, so it
/// still shows the hover affordance. Used by items whose click opens a menu.
pub fn clickable(mut self, clickable: bool) -> Self {
self.clickable = clickable;
self
}
pub fn suffix(mut self, suffix: impl IntoElement) -> Self {
self.suffix = Some(suffix.into_any_element());
self
@@ -52,6 +69,23 @@ impl NavItem {
}
}
impl Selectable for NavItem {
fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
fn is_selected(&self) -> bool {
self.selected
}
}
impl InteractiveElement for NavItem {
fn interactivity(&mut self) -> &mut Interactivity {
&mut self.interactivity
}
}
impl Styled for NavItem {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
@@ -60,7 +94,7 @@ impl Styled for NavItem {
impl RenderOnce for NavItem {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let clickable = self.on_click.is_some();
let clickable = self.on_click.is_some() || self.clickable;
h_flex()
.id(self.id)
@@ -86,6 +120,9 @@ impl RenderOnce for NavItem {
.when(clickable, |this| {
this.cursor_pointer()
.hover(|this| this.bg(cx.theme().ghost_element_hover))
.when(self.selected, |this| {
this.bg(cx.theme().ghost_element_active)
})
})
.when_some(self.on_click, |this, handler| {
this.on_click(move |event, window, cx| handler(event, window, cx))