Detailed changes
@@ -2511,7 +2511,7 @@ impl CollabPanel {
} else {
el.child(
ListHeader::new(text)
- .when_some(button, |el, button| el.right_button(button))
+ .when_some(button, |el, button| el.meta(button))
.selected(is_selected),
)
}
@@ -23,6 +23,7 @@ pub enum ComponentStory {
Keybinding,
Label,
List,
+ ListHeader,
ListItem,
Scroll,
Text,
@@ -44,6 +45,7 @@ impl ComponentStory {
Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into(),
Self::Label => cx.build_view(|_| ui::LabelStory).into(),
Self::List => cx.build_view(|_| ui::ListStory).into(),
+ Self::ListHeader => cx.build_view(|_| ui::ListHeaderStory).into(),
Self::ListItem => cx.build_view(|_| ui::ListItemStory).into(),
Self::Scroll => ScrollStory::view(cx).into(),
Self::Text => TextStory::view(cx).into(),
@@ -1,73 +1,11 @@
+mod list;
mod list_header;
mod list_item;
mod list_separator;
mod list_sub_header;
-use gpui::{AnyElement, Div};
-use smallvec::SmallVec;
-
-use crate::prelude::*;
-use crate::{v_stack, Label};
-
+pub use list::*;
pub use list_header::*;
pub use list_item::*;
pub use list_separator::*;
pub use list_sub_header::*;
-
-#[derive(IntoElement)]
-pub struct List {
- /// Message to display when the list is empty
- /// Defaults to "No items"
- empty_message: SharedString,
- header: Option<ListHeader>,
- toggle: Option<bool>,
- children: SmallVec<[AnyElement; 2]>,
-}
-
-impl List {
- pub fn new() -> Self {
- Self {
- empty_message: "No items".into(),
- header: None,
- toggle: None,
- children: SmallVec::new(),
- }
- }
-
- pub fn empty_message(mut self, empty_message: impl Into<SharedString>) -> Self {
- self.empty_message = empty_message.into();
- self
- }
-
- pub fn header(mut self, header: ListHeader) -> Self {
- self.header = Some(header);
- self
- }
-
- pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
- self.toggle = toggle.into();
- self
- }
-}
-
-impl ParentElement for List {
- fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
- &mut self.children
- }
-}
-
-impl RenderOnce for List {
- type Rendered = Div;
-
- fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
- v_stack()
- .w_full()
- .py_1()
- .children(self.header.map(|header| header))
- .map(|this| match (self.children.is_empty(), self.toggle) {
- (false, _) => this.children(self.children),
- (true, Some(false)) => this,
- (true, _) => this.child(Label::new(self.empty_message.clone()).color(Color::Muted)),
- })
- }
-}
@@ -0,0 +1,60 @@
+use gpui::{AnyElement, Div};
+use smallvec::SmallVec;
+
+use crate::{prelude::*, v_stack, Label, ListHeader};
+
+#[derive(IntoElement)]
+pub struct List {
+ /// Message to display when the list is empty
+ /// Defaults to "No items"
+ empty_message: SharedString,
+ header: Option<ListHeader>,
+ toggle: Option<bool>,
+ children: SmallVec<[AnyElement; 2]>,
+}
+
+impl List {
+ pub fn new() -> Self {
+ Self {
+ empty_message: "No items".into(),
+ header: None,
+ toggle: None,
+ children: SmallVec::new(),
+ }
+ }
+
+ pub fn empty_message(mut self, empty_message: impl Into<SharedString>) -> Self {
+ self.empty_message = empty_message.into();
+ self
+ }
+
+ pub fn header(mut self, header: impl Into<Option<ListHeader>>) -> Self {
+ self.header = header.into();
+ self
+ }
+
+ pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
+ self.toggle = toggle.into();
+ self
+ }
+}
+
+impl ParentElement for List {
+ fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
+ &mut self.children
+ }
+}
+
+impl RenderOnce for List {
+ type Rendered = Div;
+
+ fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
+ v_stack().w_full().py_1().children(self.header).map(|this| {
+ match (self.children.is_empty(), self.toggle) {
+ (false, _) => this.children(self.children),
+ (true, Some(false)) => this,
+ (true, _) => this.child(Label::new(self.empty_message.clone()).color(Color::Muted)),
+ }
+ })
+ }
+}
@@ -1,22 +1,16 @@
use std::rc::Rc;
-use gpui::{ClickEvent, Div};
+use gpui::{AnyElement, ClickEvent, Div};
+use smallvec::SmallVec;
use crate::prelude::*;
-use crate::{h_stack, Disclosure, Icon, IconButton, IconElement, IconSize, Label};
-
-pub enum ListHeaderMeta {
- Tools(Vec<IconButton>),
- // TODO: This should be a button
- Button(Label),
- Text(Label),
-}
+use crate::{h_stack, Disclosure, Icon, IconElement, IconSize, Label};
#[derive(IntoElement)]
pub struct ListHeader {
label: SharedString,
left_icon: Option<Icon>,
- meta: Option<ListHeaderMeta>,
+ meta: SmallVec<[AnyElement; 2]>,
toggle: Option<bool>,
on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
inset: bool,
@@ -28,7 +22,7 @@ impl ListHeader {
Self {
label: label.into(),
left_icon: None,
- meta: None,
+ meta: SmallVec::new(),
inset: false,
toggle: None,
on_toggle: None,
@@ -49,17 +43,13 @@ impl ListHeader {
self
}
- pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
- self.left_icon = left_icon;
+ pub fn left_icon(mut self, left_icon: impl Into<Option<Icon>>) -> Self {
+ self.left_icon = left_icon.into();
self
}
- pub fn right_button(self, button: IconButton) -> Self {
- self.meta(Some(ListHeaderMeta::Tools(vec![button])))
- }
-
- pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
- self.meta = meta;
+ pub fn meta(mut self, meta: impl IntoElement) -> Self {
+ self.meta.push(meta.into_any_element());
self
}
}
@@ -75,18 +65,6 @@ impl RenderOnce for ListHeader {
type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
- let meta = match self.meta {
- Some(ListHeaderMeta::Tools(icons)) => div().child(
- h_stack()
- .gap_2()
- .items_center()
- .children(icons.into_iter().map(|i| i.icon_color(Color::Muted))),
- ),
- Some(ListHeaderMeta::Button(label)) => div().child(label),
- Some(ListHeaderMeta::Text(label)) => div().child(label),
- None => div(),
- };
-
h_stack().w_full().relative().child(
div()
.h_5()
@@ -120,7 +98,7 @@ impl RenderOnce for ListHeader {
.map(|is_open| Disclosure::new(is_open).on_toggle(self.on_toggle)),
),
)
- .child(meta),
+ .child(h_stack().gap_2().items_center().children(self.meta)),
)
}
}
@@ -8,6 +8,7 @@ mod icon_button;
mod keybinding;
mod label;
mod list;
+mod list_header;
mod list_item;
pub use avatar::*;
@@ -20,4 +21,5 @@ pub use icon_button::*;
pub use keybinding::*;
pub use label::*;
pub use list::*;
+pub use list_header::*;
pub use list_item::*;
@@ -22,12 +22,12 @@ impl Render for ListStory {
.child(Story::label("With sections"))
.child(
List::new()
- .child(ListHeader::new("Fruits"))
+ .header(ListHeader::new("Produce"))
+ .child(ListSubHeader::new("Fruits"))
.child(ListItem::new("apple").child("Apple"))
.child(ListItem::new("banana").child("Banana"))
.child(ListItem::new("cherry").child("Cherry"))
.child(ListSeparator)
- .child(ListHeader::new("Vegetables"))
.child(ListSubHeader::new("Root Vegetables"))
.child(ListItem::new("carrot").child("Carrot"))
.child(ListItem::new("potato").child("Potato"))
@@ -0,0 +1,33 @@
+use gpui::{Div, Render};
+use story::Story;
+
+use crate::{prelude::*, IconButton};
+use crate::{Icon, ListHeader};
+
+pub struct ListHeaderStory;
+
+impl Render for ListHeaderStory {
+ type Element = Div;
+
+ fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+ Story::container()
+ .child(Story::title_for::<ListHeader>())
+ .child(Story::label("Default"))
+ .child(ListHeader::new("Section 1"))
+ .child(Story::label("With left icon"))
+ .child(ListHeader::new("Section 2").left_icon(Icon::Bell))
+ .child(Story::label("With left icon and meta"))
+ .child(
+ ListHeader::new("Section 3")
+ .left_icon(Icon::BellOff)
+ .meta(IconButton::new("action_1", Icon::Bolt)),
+ )
+ .child(Story::label("With multiple meta"))
+ .child(
+ ListHeader::new("Section 4")
+ .meta(IconButton::new("action_1", Icon::Bolt))
+ .meta(IconButton::new("action_2", Icon::ExclamationTriangle))
+ .meta(IconButton::new("action_3", Icon::Plus)),
+ )
+ }
+}