sidebar: Refactor Sidebar to support Styled. (#1658)

## Break Change

- The `width`, `border_width` method has been removed from Sidebar, use
`Styled` trait to use `w`, `border` method from GPUI instead.

```diff
Sidebar::left()
-   .width(relative(1.))
-   .border_width(px(0.))
+   .w(relative(1.))
+   .border_0()
    .collapsed(false)
```
This commit is contained in:
Jason Lee 2025-11-21 22:44:35 +08:00 committed by GitHub
parent 44829a05e4
commit c523b3485b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 68 additions and 41 deletions

View file

@ -158,8 +158,8 @@ impl Render for Gallery {
.size_range(px(200.)..px(320.)) .size_range(px(200.)..px(320.))
.child( .child(
Sidebar::left() Sidebar::left()
.width(relative(1.)) .w(relative(1.))
.border_width(px(0.)) .border_0()
.collapsed(self.collapsed) .collapsed(self.collapsed)
.header( .header(
v_flex() v_flex()

View file

@ -2,7 +2,7 @@ use std::collections::HashMap;
use gpui::{ use gpui::{
Action, App, AppContext, ClickEvent, Context, Entity, Focusable, IntoElement, ParentElement, Action, App, AppContext, ClickEvent, Context, Entity, Focusable, IntoElement, ParentElement,
Render, SharedString, Styled, Window, div, prelude::FluentBuilder, relative, Render, SharedString, Styled, Window, div, prelude::FluentBuilder, px, relative,
}; };
use gpui_component::{ use gpui_component::{
@ -280,9 +280,10 @@ impl Render for SidebarStory {
.child( .child(
Sidebar::new(self.side) Sidebar::new(self.side)
.collapsed(self.collapsed) .collapsed(self.collapsed)
.w(px(220.))
.p_3()
.header( .header(
SidebarHeader::new() SidebarHeader::new()
.w_full()
.child( .child(
div() div()
.flex() .flex()

View file

@ -136,8 +136,8 @@ impl Settings {
let search_input = state.read(cx).search_input.clone(); let search_input = state.read(cx).search_input.clone();
Sidebar::left() Sidebar::left()
.width(relative(1.)) .w(relative(1.))
.border_width(px(0.)) .border_0()
.collapsed(false) .collapsed(false)
.header( .header(
div() div()

View file

@ -54,7 +54,6 @@ impl<E: Collapsible + IntoElement> RenderOnce for SidebarGroup<E> {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
v_flex() v_flex()
.relative() .relative()
.p_2()
.when(!self.collapsed, |this| { .when(!self.collapsed, |this| {
this.child( this.child(
div() div()

View file

@ -1,14 +1,16 @@
use gpui::{ use gpui::{
prelude::FluentBuilder as _, Div, InteractiveElement, IntoElement, ParentElement, RenderOnce, div, prelude::FluentBuilder as _, AnyElement, Div, InteractiveElement, IntoElement,
Styled, ParentElement, RenderOnce, StyleRefinement, Styled,
}; };
use crate::{h_flex, menu::DropdownMenu, ActiveTheme as _, Collapsible, Selectable}; use crate::{menu::DropdownMenu, ActiveTheme as _, Collapsible, Selectable, StyledExt};
/// Header for the [`super::Sidebar`] /// Header for the [`super::Sidebar`]
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct SidebarHeader { pub struct SidebarHeader {
base: Div, base: Div,
style: StyleRefinement,
children: Vec<AnyElement>,
selected: bool, selected: bool,
collapsed: bool, collapsed: bool,
} }
@ -17,13 +19,21 @@ impl SidebarHeader {
/// Create a new [`SidebarHeader`]. /// Create a new [`SidebarHeader`].
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
base: h_flex().gap_2().w_full(), base: div(),
style: StyleRefinement::default(),
children: Vec::new(),
selected: false, selected: false,
collapsed: false, collapsed: false,
} }
} }
} }
impl Default for SidebarHeader {
fn default() -> Self {
Self::new()
}
}
impl Selectable for SidebarHeader { impl Selectable for SidebarHeader {
fn selected(mut self, selected: bool) -> Self { fn selected(mut self, selected: bool) -> Self {
self.selected = selected; self.selected = selected;
@ -48,13 +58,13 @@ impl Collapsible for SidebarHeader {
impl ParentElement for SidebarHeader { impl ParentElement for SidebarHeader {
fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) { fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {
self.base.extend(elements); self.children.extend(elements);
} }
} }
impl Styled for SidebarHeader { impl Styled for SidebarHeader {
fn style(&mut self) -> &mut gpui::StyleRefinement { fn style(&mut self) -> &mut gpui::StyleRefinement {
self.base.style() &mut self.style
} }
} }
@ -68,13 +78,15 @@ impl DropdownMenu for SidebarHeader {}
impl RenderOnce for SidebarHeader { impl RenderOnce for SidebarHeader {
fn render(self, _: &mut gpui::Window, cx: &mut gpui::App) -> impl gpui::IntoElement { fn render(self, _: &mut gpui::Window, cx: &mut gpui::App) -> impl gpui::IntoElement {
h_flex() self.base
.id("sidebar-header") .id("sidebar-header")
.h_flex()
.gap_2() .gap_2()
.p_2() .p_2()
.w_full() .w_full()
.justify_between() .justify_between()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.refine_style(&self.style)
.hover(|this| { .hover(|this| {
this.bg(cx.theme().sidebar_accent) this.bg(cx.theme().sidebar_accent)
.text_color(cx.theme().sidebar_accent_foreground) .text_color(cx.theme().sidebar_accent_foreground)
@ -83,6 +95,6 @@ impl RenderOnce for SidebarHeader {
this.bg(cx.theme().sidebar_accent) this.bg(cx.theme().sidebar_accent)
.text_color(cx.theme().sidebar_accent_foreground) .text_color(cx.theme().sidebar_accent_foreground)
}) })
.child(self.base) .children(self.children)
} }
} }

View file

@ -5,8 +5,9 @@ use crate::{
v_flex, ActiveTheme, Collapsible, Icon, IconName, Side, Sizable, StyledExt, v_flex, ActiveTheme, Collapsible, Icon, IconName, Side, Sizable, StyledExt,
}; };
use gpui::{ use gpui::{
div, prelude::FluentBuilder, px, AnyElement, App, ClickEvent, DefiniteLength, div, prelude::FluentBuilder, px, AnyElement, App, ClickEvent, EdgesRefinement,
InteractiveElement as _, IntoElement, ParentElement, Pixels, RenderOnce, Styled, Window, InteractiveElement as _, IntoElement, ParentElement, Pixels, RenderOnce, StyleRefinement,
Styled, Window,
}; };
use std::rc::Rc; use std::rc::Rc;
@ -25,6 +26,7 @@ const COLLAPSED_WIDTH: Pixels = px(48.);
/// A Sidebar element that can contain collapsible child elements. /// A Sidebar element that can contain collapsible child elements.
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct Sidebar<E: Collapsible + IntoElement + 'static> { pub struct Sidebar<E: Collapsible + IntoElement + 'static> {
style: StyleRefinement,
content: Vec<E>, content: Vec<E>,
/// header view /// header view
header: Option<AnyElement>, header: Option<AnyElement>,
@ -33,8 +35,6 @@ pub struct Sidebar<E: Collapsible + IntoElement + 'static> {
/// The side of the sidebar /// The side of the sidebar
side: Side, side: Side,
collapsible: bool, collapsible: bool,
width: DefiniteLength,
border_width: Pixels,
collapsed: bool, collapsed: bool,
} }
@ -42,13 +42,12 @@ impl<E: Collapsible + IntoElement> Sidebar<E> {
/// Create a new Sidebar on the given [`Side`]. /// Create a new Sidebar on the given [`Side`].
pub fn new(side: Side) -> Self { pub fn new(side: Side) -> Self {
Self { Self {
style: StyleRefinement::default(),
content: vec![], content: vec![],
header: None, header: None,
footer: None, footer: None,
side, side,
collapsible: true, collapsible: true,
width: DEFAULT_WIDTH.into(),
border_width: px(1.),
collapsed: false, collapsed: false,
} }
} }
@ -63,18 +62,6 @@ impl<E: Collapsible + IntoElement> Sidebar<E> {
Self::new(Side::Right) Self::new(Side::Right)
} }
/// Set the width of the sidebar
pub fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
self.width = width.into();
self
}
/// Set border width of the sidebar
pub fn border_width(mut self, border_width: impl Into<Pixels>) -> Self {
self.border_width = border_width.into();
self
}
/// Set the sidebar to be collapsible, default is true /// Set the sidebar to be collapsible, default is true
pub fn collapsible(mut self, collapsible: bool) -> Self { pub fn collapsible(mut self, collapsible: bool) -> Self {
self.collapsible = collapsible; self.collapsible = collapsible;
@ -192,41 +179,69 @@ impl RenderOnce for SidebarToggleButton {
} }
} }
impl<E: Collapsible + IntoElement> Styled for Sidebar<E> {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl<E: Collapsible + IntoElement> RenderOnce for Sidebar<E> { impl<E: Collapsible + IntoElement> RenderOnce for Sidebar<E> {
fn render(mut self, _: &mut Window, cx: &mut App) -> impl IntoElement { fn render(mut self, _: &mut Window, cx: &mut App) -> impl IntoElement {
self.style.padding = EdgesRefinement::default();
v_flex() v_flex()
.id("sidebar") .id("sidebar")
.w(self.width) .w(DEFAULT_WIDTH)
.when(self.collapsed, |this| this.w(COLLAPSED_WIDTH))
.flex_shrink_0() .flex_shrink_0()
.h_full() .h_full()
.gap_3()
.overflow_hidden() .overflow_hidden()
.relative() .relative()
.bg(cx.theme().sidebar) .bg(cx.theme().sidebar)
.text_color(cx.theme().sidebar_foreground) .text_color(cx.theme().sidebar_foreground)
.border_color(cx.theme().sidebar_border) .border_color(cx.theme().sidebar_border)
.map(|this| match self.side { .map(|this| match self.side {
Side::Left => this.border_r(self.border_width), Side::Left => this.border_r_1(),
Side::Right => this.border_l(self.border_width), Side::Right => this.border_l_1(),
}) })
.refine_style(&self.style)
.when(self.collapsed, |this| this.w(COLLAPSED_WIDTH).gap_2())
.when_some(self.header.take(), |this, header| { .when_some(self.header.take(), |this, header| {
this.child(h_flex().id("header").p_2().gap_2().child(header)) this.child(
h_flex()
.id("header")
.pt_3()
.px_3()
.gap_2()
.when(self.collapsed, |this| this.pt_2().px_2())
.child(header),
)
}) })
.child( .child(
v_flex().id("content").flex_1().min_h_0().child( v_flex().id("content").flex_1().min_h_0().child(
div() v_flex()
.gap_3()
.p_3()
.when(self.collapsed, |this| this.p_2())
.children( .children(
self.content self.content
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(|(ix, c)| div().id(ix).child(c.collapsed(self.collapsed))), .map(|(ix, c)| div().id(ix).child(c.collapsed(self.collapsed))),
) )
.gap_2()
.scrollable(ScrollbarAxis::Vertical), .scrollable(ScrollbarAxis::Vertical),
), ),
) )
.when_some(self.footer.take(), |this, footer| { .when_some(self.footer.take(), |this, footer| {
this.child(h_flex().id("footer").gap_2().p_2().child(footer)) this.child(
h_flex()
.id("footer")
.pb_3()
.px_3()
.gap_2()
.when(self.collapsed, |this| this.pt_2().px_2())
.child(footer),
)
}) })
} }
} }