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.))
.child(
Sidebar::left()
.width(relative(1.))
.border_width(px(0.))
.w(relative(1.))
.border_0()
.collapsed(self.collapsed)
.header(
v_flex()

View file

@ -2,7 +2,7 @@ use std::collections::HashMap;
use gpui::{
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::{
@ -280,9 +280,10 @@ impl Render for SidebarStory {
.child(
Sidebar::new(self.side)
.collapsed(self.collapsed)
.w(px(220.))
.p_3()
.header(
SidebarHeader::new()
.w_full()
.child(
div()
.flex()

View file

@ -136,8 +136,8 @@ impl Settings {
let search_input = state.read(cx).search_input.clone();
Sidebar::left()
.width(relative(1.))
.border_width(px(0.))
.w(relative(1.))
.border_0()
.collapsed(false)
.header(
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 {
v_flex()
.relative()
.p_2()
.when(!self.collapsed, |this| {
this.child(
div()

View file

@ -1,14 +1,16 @@
use gpui::{
prelude::FluentBuilder as _, Div, InteractiveElement, IntoElement, ParentElement, RenderOnce,
Styled,
div, prelude::FluentBuilder as _, AnyElement, Div, InteractiveElement, IntoElement,
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`]
#[derive(IntoElement)]
pub struct SidebarHeader {
base: Div,
style: StyleRefinement,
children: Vec<AnyElement>,
selected: bool,
collapsed: bool,
}
@ -17,13 +19,21 @@ impl SidebarHeader {
/// Create a new [`SidebarHeader`].
pub fn new() -> Self {
Self {
base: h_flex().gap_2().w_full(),
base: div(),
style: StyleRefinement::default(),
children: Vec::new(),
selected: false,
collapsed: false,
}
}
}
impl Default for SidebarHeader {
fn default() -> Self {
Self::new()
}
}
impl Selectable for SidebarHeader {
fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
@ -48,13 +58,13 @@ impl Collapsible for SidebarHeader {
impl ParentElement for SidebarHeader {
fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {
self.base.extend(elements);
self.children.extend(elements);
}
}
impl Styled for SidebarHeader {
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 {
fn render(self, _: &mut gpui::Window, cx: &mut gpui::App) -> impl gpui::IntoElement {
h_flex()
self.base
.id("sidebar-header")
.h_flex()
.gap_2()
.p_2()
.w_full()
.justify_between()
.rounded(cx.theme().radius)
.refine_style(&self.style)
.hover(|this| {
this.bg(cx.theme().sidebar_accent)
.text_color(cx.theme().sidebar_accent_foreground)
@ -83,6 +95,6 @@ impl RenderOnce for SidebarHeader {
this.bg(cx.theme().sidebar_accent)
.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,
};
use gpui::{
div, prelude::FluentBuilder, px, AnyElement, App, ClickEvent, DefiniteLength,
InteractiveElement as _, IntoElement, ParentElement, Pixels, RenderOnce, Styled, Window,
div, prelude::FluentBuilder, px, AnyElement, App, ClickEvent, EdgesRefinement,
InteractiveElement as _, IntoElement, ParentElement, Pixels, RenderOnce, StyleRefinement,
Styled, Window,
};
use std::rc::Rc;
@ -25,6 +26,7 @@ const COLLAPSED_WIDTH: Pixels = px(48.);
/// A Sidebar element that can contain collapsible child elements.
#[derive(IntoElement)]
pub struct Sidebar<E: Collapsible + IntoElement + 'static> {
style: StyleRefinement,
content: Vec<E>,
/// header view
header: Option<AnyElement>,
@ -33,8 +35,6 @@ pub struct Sidebar<E: Collapsible + IntoElement + 'static> {
/// The side of the sidebar
side: Side,
collapsible: bool,
width: DefiniteLength,
border_width: Pixels,
collapsed: bool,
}
@ -42,13 +42,12 @@ impl<E: Collapsible + IntoElement> Sidebar<E> {
/// Create a new Sidebar on the given [`Side`].
pub fn new(side: Side) -> Self {
Self {
style: StyleRefinement::default(),
content: vec![],
header: None,
footer: None,
side,
collapsible: true,
width: DEFAULT_WIDTH.into(),
border_width: px(1.),
collapsed: false,
}
}
@ -63,18 +62,6 @@ impl<E: Collapsible + IntoElement> Sidebar<E> {
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
pub fn collapsible(mut self, collapsible: bool) -> Self {
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> {
fn render(mut self, _: &mut Window, cx: &mut App) -> impl IntoElement {
self.style.padding = EdgesRefinement::default();
v_flex()
.id("sidebar")
.w(self.width)
.when(self.collapsed, |this| this.w(COLLAPSED_WIDTH))
.w(DEFAULT_WIDTH)
.flex_shrink_0()
.h_full()
.gap_3()
.overflow_hidden()
.relative()
.bg(cx.theme().sidebar)
.text_color(cx.theme().sidebar_foreground)
.border_color(cx.theme().sidebar_border)
.map(|this| match self.side {
Side::Left => this.border_r(self.border_width),
Side::Right => this.border_l(self.border_width),
Side::Left => this.border_r_1(),
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| {
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(
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(
self.content
.into_iter()
.enumerate()
.map(|(ix, c)| div().id(ix).child(c.collapsed(self.collapsed))),
)
.gap_2()
.scrollable(ScrollbarAxis::Vertical),
),
)
.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),
)
})
}
}