update sidebar

This commit is contained in:
2026-09-20 09:02:57 +07:00
parent 8401628412
commit 9c735febc1
6 changed files with 104 additions and 73 deletions
-6
View File
@@ -116,26 +116,20 @@ pub trait ButtonVariants: Sized {
#[allow(clippy::type_complexity)]
pub struct Button {
base: BaseButton,
icon: Option<Icon>,
label: Option<SharedString>,
tooltip: Option<SharedString>,
children: Vec<AnyElement>,
variant: ButtonVariant,
size: Size,
disabled: bool,
loading: bool,
rounded: bool,
compact: bool,
caret: bool,
indicator: bool,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
on_hover: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,
tab_index: isize,
tab_stop: bool,
+10 -4
View File
@@ -255,11 +255,13 @@ fn rows_for(tab: SidebarTab, cx: &App) -> Vec<SidebarRow> {
}];
}
let mut rows = vec![SidebarRow::Section {
let mut rows = Vec::new();
if !communities.is_empty() {
rows.push(SidebarRow::Section {
label: "Communities".into(),
count: community_count,
}];
});
rows.extend(
communities
.into_iter()
@@ -269,7 +271,9 @@ fn rows_for(tab: SidebarTab, cx: &App) -> Vec<SidebarRow> {
label: "Show all communities".into(),
tab: SidebarTab::Communities,
});
}
if !rooms.is_empty() {
rows.push(SidebarRow::Section {
label: "Chats".into(),
count: rooms.len(),
@@ -284,6 +288,7 @@ fn rows_for(tab: SidebarTab, cx: &App) -> Vec<SidebarRow> {
label: "Show all chats".into(),
tab: SidebarTab::Chats,
});
}
rows
}
@@ -400,6 +405,7 @@ fn render_rows(range: Range<usize>, rows: &[SidebarRow], cx: &Context<Sidebar>)
TreeRowKind::Action,
label.clone(),
)
.icon(IconName::ArrowRight)
.on_click(cx.listener(move |this, _event, _window, cx| {
this.select_tab(tab, cx);
}))
@@ -590,7 +596,7 @@ impl Render for Sidebar {
this.child(
div()
.absolute()
.bottom_12()
.bottom_16()
.left_0()
.h_9()
.w_full()
+12 -4
View File
@@ -1,7 +1,7 @@
use std::rc::Rc;
use gpui::prelude::FluentBuilder;
use gpui::{App, IntoElement, ParentElement, RenderOnce, Styled, Window, div};
use gpui::{App, InteractiveElement, IntoElement, ParentElement, RenderOnce, Styled, Window, div};
use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants};
use ui::{IconName, Selectable, h_flex};
@@ -85,13 +85,20 @@ impl RenderOnce for TabBar {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let Self { active, on_select } = self;
div().absolute().bottom_2().left_0().w_full().px_2().child(
div()
.id("sidebar-tabs")
.absolute()
.bottom_3()
.left_0()
.w_full()
.px_4()
.child(
h_flex()
.w_full()
.p_1()
.gap_1()
.rounded(cx.theme().radius_lg)
.bg(cx.theme().elevated_surface_background)
.rounded_full()
.bg(cx.theme().background)
.when(cx.theme().shadow, |this| this.shadow_md())
.children(SidebarTab::ALL.into_iter().map(|tab| {
let on_select = on_select.clone();
@@ -100,6 +107,7 @@ impl RenderOnce for TabBar {
.icon(tab.icon())
.ghost()
.flex_1()
.rounded()
.selected(tab == active)
.tooltip(tab.label())
.on_click(move |_event, window, cx| {
+25 -7
View File
@@ -5,7 +5,7 @@ use community::Community;
use gpui::prelude::FluentBuilder;
use gpui::{
App, ClickEvent, ElementId, Entity, ImageSource, InteractiveElement, IntoElement,
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window, div,
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window, div, px,
};
use settings::AppSettings;
use theme::ActiveTheme;
@@ -50,6 +50,7 @@ pub struct TreeRow {
label: SharedString,
avatar: Option<SharedString>,
picture: Option<ImageSource>,
icon: Option<IconName>,
count: Option<usize>,
created_at: Option<SharedString>,
selected: bool,
@@ -69,6 +70,7 @@ impl TreeRow {
label: label.into(),
avatar: None,
picture: None,
icon: None,
count: None,
created_at: None,
selected: false,
@@ -88,6 +90,12 @@ impl TreeRow {
self
}
/// Shows `icon` in the avatar slot when the row has no avatar or picture.
pub fn icon(mut self, icon: IconName) -> Self {
self.icon = Some(icon);
self
}
pub fn count(mut self, count: usize) -> Self {
self.count = Some(count);
self
@@ -150,6 +158,18 @@ impl RenderOnce for TreeRow {
}
};
let avatar = avatar.or_else(|| {
self.icon.map(|icon| {
h_flex()
.flex_shrink_0()
.w(px(20.))
.justify_center()
.text_color(cx.theme().icon_muted)
.child(Icon::new(icon).small())
.into_any_element()
})
});
h_flex()
.id(self.id)
.h_8()
@@ -159,14 +179,12 @@ impl RenderOnce for TreeRow {
.rounded(cx.theme().radius)
.when(is_section, |this| {
this.text_xs()
.text_color(cx.theme().text_muted)
.text_color(cx.theme().text_placeholder)
.font_semibold()
})
.when(is_room || is_community, |this| this.text_sm())
.when(is_action, |this| {
this.text_sm()
.font_medium()
.text_color(cx.theme().text_accent)
this.text_sm().text_color(cx.theme().text_muted)
})
.when(is_hint, |this| {
this.text_xs()
@@ -197,11 +215,11 @@ impl RenderOnce for TreeRow {
this.child(div().flex_shrink_0().font_normal().child(count.to_string()))
})
.when_some(self.created_at, |this, created_at| {
this.child(
this.child(div().flex_1()).child(
div()
.flex_shrink_0()
.text_xs()
.text_color(cx.theme().text_placeholder)
.text_xs()
.child(created_at),
)
}),
+4 -3
View File
@@ -602,9 +602,10 @@ client.subscribe(filter).with_id(sub_id).await?;
`crates/community`.** `concord` has no subscriptions, no `init`, and no
`Entity<Community>`; `community::CommunityRegistry` owns one `Entity<Community>`
per state document, subscribes when a community's plane set changes, and
re-folds on an inbound wrap. The sidebar observes the registry, logs
`CommunityEvent::Error` through `log::error!`, and its "New community" row opens
a name prompt that calls `CommunityRegistry::create`. `create` persists the
re-folds on an inbound wrap. The sidebar subscribes to the registry, surfaces
`CommunityEvent::Error` as a window notification, and its "New community" row
in the Communities tab dispatches `Command::NewCommunity`, whose name prompt
calls `CommunityRegistry::create`. `create` persists the
genesis locally, publishes the wraps to the community's relays, and records the
membership in the account's Community List — all best-effort, so a relay that is
down warns without losing the community. Discovery
+9 -5
View File
@@ -205,7 +205,7 @@ Empty list shows the existing "No communities yet" hint.
Each phase is independently reviewable and leaves the app runnable.
### Phase 1 — tab shell
### Phase 1 — tab shell — DONE
Files: `crates/workspace/src/sidebar/mod.rs`,
`crates/workspace/src/sidebar/tab.rs` (new), `sidebar/tree.rs`,
@@ -228,7 +228,7 @@ Files: `crates/workspace/src/sidebar/mod.rs`,
Validation: app runs signed in and signed out; chats and communities list and
open as before; tab switching works; requests dot still clears.
### Phase 2 — Recents tab
### Phase 2 — Recents tab — DONE
Files: `crates/settings/src/lib.rs`, `crates/workspace/src/sidebar/mod.rs`,
`sidebar/tree.rs`.
@@ -242,7 +242,7 @@ Files: `crates/settings/src/lib.rs`, `crates/workspace/src/sidebar/mod.rs`,
Validation: `cargo test -p settings`; manually open communities, restart, and
confirm the Recents order; confirm ≤3 / ≤5 rendering and both "Show all" rows.
### Phase 3 — tab actions
### Phase 3 — tab actions — DONE
Files: `crates/workspace/src/dialogs/new_chat.rs` (new),
`dialogs/new_community.rs` (new), `crates/workspace/src/dialogs/mod.rs`,
@@ -264,7 +264,7 @@ Validation: create a chat from an npub and confirm the room opens; create a
community and confirm it appears in the Communities tab and in Recents;
requests/contacts/browse still dispatch.
### Phase 4 — onboarding sidebar
### Phase 4 — onboarding sidebar — DONE, except `Join now`
Files: `crates/state/src/lib.rs`, `crates/workspace/src/sidebar/mod.rs`,
`sidebar/onboarding.rs` (new), `crates/workspace/src/dialogs/create_identity.rs`
@@ -291,7 +291,11 @@ Validation: with no stored credentials the sidebar shows onboarding and no
modal; `Import identity` still signs in; `Join now` signs in with a fresh key;
with bunker credentials the tabs appear without an onboarding flash.
### Phase 5 — polish and cleanup
**Deferred.** `dialogs/create_identity.rs` is not implemented, so `Join now`
renders without a click handler, and the §2.1 banner assets were skipped in
favor of a plain theme-colored background with the brand mark.
### Phase 5 — polish and cleanup — DONE
- Reposition the "Getting messages…" pill above the tab bar.
- Empty states and counts for all three tabs; truncation rules (§3).