diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index b62056a3be..b90df68c2a 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -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), ) } diff --git a/crates/storybook2/src/story_selector.rs b/crates/storybook2/src/story_selector.rs index 0354097c0b..994b31c336 100644 --- a/crates/storybook2/src/story_selector.rs +++ b/crates/storybook2/src/story_selector.rs @@ -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(), diff --git a/crates/ui2/src/components/button/mod.rs b/crates/ui2/src/components/button.rs similarity index 100% rename from crates/ui2/src/components/button/mod.rs rename to crates/ui2/src/components/button.rs diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index aafd045391..88650b6ae8 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -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, - toggle: Option, - 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) -> 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>) -> 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)), - }) - } -} diff --git a/crates/ui2/src/components/list/list.rs b/crates/ui2/src/components/list/list.rs new file mode 100644 index 0000000000..fdfe256bd6 --- /dev/null +++ b/crates/ui2/src/components/list/list.rs @@ -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, + toggle: Option, + 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) -> Self { + self.empty_message = empty_message.into(); + self + } + + pub fn header(mut self, header: impl Into>) -> Self { + self.header = header.into(); + self + } + + pub fn toggle(mut self, toggle: impl Into>) -> 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)), + } + }) + } +} diff --git a/crates/ui2/src/components/list/list_header.rs b/crates/ui2/src/components/list/list_header.rs index 431665ffd3..799b1c5dae 100644 --- a/crates/ui2/src/components/list/list_header.rs +++ b/crates/ui2/src/components/list/list_header.rs @@ -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), - // 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, - meta: Option, + meta: SmallVec<[AnyElement; 2]>, toggle: Option, on_toggle: Option>, 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) -> Self { - self.left_icon = left_icon; + pub fn left_icon(mut self, left_icon: impl Into>) -> 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) -> 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)), ) } } diff --git a/crates/ui2/src/components/stories.rs b/crates/ui2/src/components/stories.rs index e870515caf..113c2679b7 100644 --- a/crates/ui2/src/components/stories.rs +++ b/crates/ui2/src/components/stories.rs @@ -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::*; diff --git a/crates/ui2/src/components/stories/list.rs b/crates/ui2/src/components/stories/list.rs index 75a16deb40..1795601944 100644 --- a/crates/ui2/src/components/stories/list.rs +++ b/crates/ui2/src/components/stories/list.rs @@ -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")) diff --git a/crates/ui2/src/components/stories/list_header.rs b/crates/ui2/src/components/stories/list_header.rs new file mode 100644 index 0000000000..056eaa2762 --- /dev/null +++ b/crates/ui2/src/components/stories/list_header.rs @@ -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::Element { + Story::container() + .child(Story::title_for::()) + .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)), + ) + } +}