From 390b0d8d560ff78d0bd50f585c6739157bbec899 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 29 Nov 2023 13:34:50 -0500 Subject: [PATCH 1/2] Reorganize list components --- crates/storybook2/src/story_selector.rs | 2 + crates/ui2/src/components/list.rs | 396 ++---------------- crates/ui2/src/components/list/list_header.rs | 123 ++++++ crates/ui2/src/components/list/list_item.rs | 167 ++++++++ .../ui2/src/components/list/list_separator.rs | 20 + .../src/components/list/list_sub_header.rs | 56 +++ crates/ui2/src/components/stories.rs | 3 + crates/ui2/src/components/stories/list.rs | 38 ++ 8 files changed, 434 insertions(+), 371 deletions(-) create mode 100644 crates/ui2/src/components/list/list_header.rs create mode 100644 crates/ui2/src/components/list/list_item.rs create mode 100644 crates/ui2/src/components/list/list_separator.rs create mode 100644 crates/ui2/src/components/list/list_sub_header.rs create mode 100644 crates/ui2/src/components/stories/list.rs diff --git a/crates/storybook2/src/story_selector.rs b/crates/storybook2/src/story_selector.rs index 1c0890e4bedcadf05c08118edeb7d91c0e6308c0..1e6ade644cddb4f422904c0238584f45be250fa2 100644 --- a/crates/storybook2/src/story_selector.rs +++ b/crates/storybook2/src/story_selector.rs @@ -21,6 +21,7 @@ pub enum ComponentStory { IconButton, Keybinding, Label, + List, ListItem, Scroll, Text, @@ -40,6 +41,7 @@ impl ComponentStory { Self::IconButton => cx.build_view(|_| ui::IconButtonStory).into(), 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::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/list.rs b/crates/ui2/src/components/list.rs index 5cd9c7f70940c7e720f4cfe02e3b06d3472863c8..227bb801a2e9a08fc44e2c362eed11dab3344f47 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -1,249 +1,46 @@ -use std::rc::Rc; +mod list_header; +mod list_item; +mod list_separator; +mod list_sub_header; -use gpui::{ - div, px, AnyElement, ClickEvent, Div, ImageSource, IntoElement, MouseButton, MouseDownEvent, - Pixels, Stateful, StatefulInteractiveElement, -}; +use gpui::{AnyElement, Div}; use smallvec::SmallVec; -use crate::{ - disclosure_control, h_stack, v_stack, Avatar, Icon, IconButton, IconElement, IconSize, Label, - Toggle, -}; -use crate::{prelude::*, GraphicSlot}; +use crate::prelude::*; +use crate::{v_stack, Label, Toggle}; -pub enum ListHeaderMeta { - Tools(Vec), - // TODO: This should be a button - Button(Label), - Text(Label), -} - -#[derive(IntoElement)] -pub struct ListHeader { - label: SharedString, - left_icon: Option, - meta: Option, - toggle: Toggle, - on_toggle: Option>, - inset: bool, - selected: bool, -} - -impl ListHeader { - pub fn new(label: impl Into) -> Self { - Self { - label: label.into(), - left_icon: None, - meta: None, - inset: false, - toggle: Toggle::NotToggleable, - on_toggle: None, - selected: false, - } - } - - pub fn toggle(mut self, toggle: Toggle) -> Self { - self.toggle = toggle; - self - } - - pub fn on_toggle( - mut self, - on_toggle: impl Fn(&ClickEvent, &mut WindowContext) + 'static, - ) -> Self { - self.on_toggle = Some(Rc::new(on_toggle)); - self - } - - pub fn left_icon(mut self, left_icon: Option) -> Self { - self.left_icon = left_icon; - 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; - self - } - - pub fn selected(mut self, selected: bool) -> Self { - self.selected = selected; - self - } -} - -impl RenderOnce for ListHeader { - type Rendered = Div; - - fn render(self, cx: &mut WindowContext) -> Self::Rendered { - let disclosure_control = disclosure_control(self.toggle, self.on_toggle); - - let meta = match self.meta { - Some(ListHeaderMeta::Tools(icons)) => div().child( - h_stack() - .gap_2() - .items_center() - .children(icons.into_iter().map(|i| i.color(Color::Muted))), - ), - Some(ListHeaderMeta::Button(label)) => div().child(label), - Some(ListHeaderMeta::Text(label)) => div().child(label), - None => div(), - }; - - h_stack() - .w_full() - .bg(cx.theme().colors().surface_background) - .relative() - .child( - div() - .h_5() - .when(self.inset, |this| this.px_2()) - .when(self.selected, |this| { - this.bg(cx.theme().colors().ghost_element_selected) - }) - .flex() - .flex_1() - .items_center() - .justify_between() - .w_full() - .gap_1() - .child( - h_stack() - .gap_1() - .child( - div() - .flex() - .gap_1() - .items_center() - .children(self.left_icon.map(|i| { - IconElement::new(i) - .color(Color::Muted) - .size(IconSize::Small) - })) - .child(Label::new(self.label.clone()).color(Color::Muted)), - ) - .child(disclosure_control), - ) - .child(meta), - ) - } -} - -#[derive(IntoElement, Clone)] -pub struct ListSubHeader { - label: SharedString, - left_icon: Option, - inset: bool, -} - -impl ListSubHeader { - pub fn new(label: impl Into) -> Self { - Self { - label: label.into(), - left_icon: None, - inset: false, - } - } - - pub fn left_icon(mut self, left_icon: Option) -> Self { - self.left_icon = left_icon; - self - } -} - -impl RenderOnce for ListSubHeader { - type Rendered = Div; - - fn render(self, _cx: &mut WindowContext) -> Self::Rendered { - h_stack().flex_1().w_full().relative().py_1().child( - div() - .h_6() - .when(self.inset, |this| this.px_2()) - .flex() - .flex_1() - .w_full() - .gap_1() - .items_center() - .justify_between() - .child( - div() - .flex() - .gap_1() - .items_center() - .children(self.left_icon.map(|i| { - IconElement::new(i) - .color(Color::Muted) - .size(IconSize::Small) - })) - .child(Label::new(self.label.clone()).color(Color::Muted)), - ), - ) - } -} +pub use list_header::*; +pub use list_item::*; +pub use list_separator::*; +pub use list_sub_header::*; #[derive(IntoElement)] -pub struct ListItem { - id: ElementId, - selected: bool, - // TODO: Reintroduce this - // disclosure_control_style: DisclosureControlVisibility, - indent_level: usize, - indent_step_size: Pixels, - left_slot: Option, +pub struct List { + /// Message to display when the list is empty + /// Defaults to "No items" + empty_message: SharedString, + header: Option, toggle: Toggle, - inset: bool, - on_click: Option>, - on_toggle: Option>, - on_secondary_mouse_down: Option>, children: SmallVec<[AnyElement; 2]>, } -impl ListItem { - pub fn new(id: impl Into) -> Self { +impl List { + pub fn new() -> Self { Self { - id: id.into(), - selected: false, - indent_level: 0, - indent_step_size: px(12.), - left_slot: None, + empty_message: "No items".into(), + header: None, toggle: Toggle::NotToggleable, - inset: false, - on_click: None, - on_secondary_mouse_down: None, - on_toggle: None, children: SmallVec::new(), } } - pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self { - self.on_click = Some(Rc::new(handler)); - self - } - - pub fn on_secondary_mouse_down( - mut self, - handler: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, - ) -> Self { - self.on_secondary_mouse_down = Some(Rc::new(handler)); - self - } - - pub fn inset(mut self, inset: bool) -> Self { - self.inset = inset; - self - } - - pub fn indent_level(mut self, indent_level: usize) -> Self { - self.indent_level = indent_level; + pub fn empty_message(mut self, empty_message: impl Into) -> Self { + self.empty_message = empty_message.into(); self } - pub fn indent_step_size(mut self, indent_step_size: Pixels) -> Self { - self.indent_step_size = indent_step_size; + pub fn header(mut self, header: ListHeader) -> Self { + self.header = Some(header); self } @@ -251,125 +48,14 @@ impl ListItem { self.toggle = toggle; self } - - pub fn on_toggle( - mut self, - on_toggle: impl Fn(&ClickEvent, &mut WindowContext) + 'static, - ) -> Self { - self.on_toggle = Some(Rc::new(on_toggle)); - self - } - - pub fn selected(mut self, selected: bool) -> Self { - self.selected = selected; - self - } - - pub fn left_content(mut self, left_content: GraphicSlot) -> Self { - self.left_slot = Some(left_content); - self - } - - pub fn left_icon(mut self, left_icon: Icon) -> Self { - self.left_slot = Some(GraphicSlot::Icon(left_icon)); - self - } - - pub fn left_avatar(mut self, left_avatar: impl Into) -> Self { - self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into())); - self - } -} - -impl RenderOnce for ListItem { - type Rendered = Stateful
; - - fn render(self, cx: &mut WindowContext) -> Self::Rendered { - div() - .id(self.id) - .relative() - // TODO: Add focus state - // .when(self.state == InteractionState::Focused, |this| { - // this.border() - // .border_color(cx.theme().colors().border_focused) - // }) - .when(self.inset, |this| this.rounded_md()) - .hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) - .active(|style| style.bg(cx.theme().colors().ghost_element_active)) - .when(self.selected, |this| { - this.bg(cx.theme().colors().ghost_element_selected) - }) - .when_some(self.on_click, |this, on_click| { - this.cursor_pointer().on_click(move |event, cx| { - // HACK: GPUI currently fires `on_click` with any mouse button, - // but we only care about the left button. - if event.down.button == MouseButton::Left { - (on_click)(event, cx) - } - }) - }) - .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| { - this.on_mouse_down(MouseButton::Right, move |event, cx| { - (on_mouse_down)(event, cx) - }) - }) - .child( - div() - .when(self.inset, |this| this.px_2()) - .ml(self.indent_level as f32 * self.indent_step_size) - .flex() - .gap_1() - .items_center() - .relative() - .child(disclosure_control(self.toggle, self.on_toggle)) - .map(|this| match self.left_slot { - Some(GraphicSlot::Icon(i)) => this.child( - IconElement::new(i) - .size(IconSize::Small) - .color(Color::Muted), - ), - Some(GraphicSlot::Avatar(src)) => this.child(Avatar::source(src)), - Some(GraphicSlot::PublicActor(src)) => this.child(Avatar::uri(src)), - None => this, - }) - .children(self.children), - ) - } } -impl ParentElement for ListItem { +impl ParentElement for List { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { &mut self.children } } -#[derive(IntoElement, Clone)] -pub struct ListSeparator; - -impl ListSeparator { - pub fn new() -> Self { - Self - } -} - -impl RenderOnce for ListSeparator { - type Rendered = Div; - - fn render(self, cx: &mut WindowContext) -> Self::Rendered { - div().h_px().w_full().bg(cx.theme().colors().border_variant) - } -} - -#[derive(IntoElement)] -pub struct List { - /// Message to display when the list is empty - /// Defaults to "No items" - empty_message: SharedString, - header: Option, - toggle: Toggle, - children: SmallVec<[AnyElement; 2]>, -} - impl RenderOnce for List { type Rendered = Div; @@ -385,35 +71,3 @@ impl RenderOnce for List { }) } } - -impl List { - pub fn new() -> Self { - Self { - empty_message: "No items".into(), - header: None, - toggle: Toggle::NotToggleable, - 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: Toggle) -> Self { - self.toggle = toggle; - self - } -} - -impl ParentElement for List { - fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { - &mut self.children - } -} diff --git a/crates/ui2/src/components/list/list_header.rs b/crates/ui2/src/components/list/list_header.rs new file mode 100644 index 0000000000000000000000000000000000000000..248eb23e60d24e81c3329cbc5d77c49d523921c6 --- /dev/null +++ b/crates/ui2/src/components/list/list_header.rs @@ -0,0 +1,123 @@ +use std::rc::Rc; + +use gpui::{ClickEvent, Div}; + +use crate::prelude::*; +use crate::{disclosure_control, h_stack, Icon, IconButton, IconElement, IconSize, Label, Toggle}; + +pub enum ListHeaderMeta { + Tools(Vec), + // TODO: This should be a button + Button(Label), + Text(Label), +} + +#[derive(IntoElement)] +pub struct ListHeader { + label: SharedString, + left_icon: Option, + meta: Option, + toggle: Toggle, + on_toggle: Option>, + inset: bool, + selected: bool, +} + +impl ListHeader { + pub fn new(label: impl Into) -> Self { + Self { + label: label.into(), + left_icon: None, + meta: None, + inset: false, + toggle: Toggle::NotToggleable, + on_toggle: None, + selected: false, + } + } + + pub fn toggle(mut self, toggle: Toggle) -> Self { + self.toggle = toggle; + self + } + + pub fn on_toggle( + mut self, + on_toggle: impl Fn(&ClickEvent, &mut WindowContext) + 'static, + ) -> Self { + self.on_toggle = Some(Rc::new(on_toggle)); + self + } + + pub fn left_icon(mut self, left_icon: Option) -> Self { + self.left_icon = left_icon; + 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; + self + } + + pub fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } +} + +impl RenderOnce for ListHeader { + type Rendered = Div; + + fn render(self, cx: &mut WindowContext) -> Self::Rendered { + let disclosure_control = disclosure_control(self.toggle, self.on_toggle); + + let meta = match self.meta { + Some(ListHeaderMeta::Tools(icons)) => div().child( + h_stack() + .gap_2() + .items_center() + .children(icons.into_iter().map(|i| i.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() + .when(self.inset, |this| this.px_2()) + .when(self.selected, |this| { + this.bg(cx.theme().colors().ghost_element_selected) + }) + .flex() + .flex_1() + .items_center() + .justify_between() + .w_full() + .gap_1() + .child( + h_stack() + .gap_1() + .child( + div() + .flex() + .gap_1() + .items_center() + .children(self.left_icon.map(|i| { + IconElement::new(i) + .color(Color::Muted) + .size(IconSize::Small) + })) + .child(Label::new(self.label.clone()).color(Color::Muted)), + ) + .child(disclosure_control), + ) + .child(meta), + ) + } +} diff --git a/crates/ui2/src/components/list/list_item.rs b/crates/ui2/src/components/list/list_item.rs new file mode 100644 index 0000000000000000000000000000000000000000..8a689d140dce4fb769d1df938533090af8f3709e --- /dev/null +++ b/crates/ui2/src/components/list/list_item.rs @@ -0,0 +1,167 @@ +use std::rc::Rc; + +use gpui::{ + px, AnyElement, ClickEvent, Div, ImageSource, MouseButton, MouseDownEvent, Pixels, Stateful, +}; +use smallvec::SmallVec; + +use crate::prelude::*; +use crate::{disclosure_control, Avatar, GraphicSlot, Icon, IconElement, IconSize, Toggle}; + +#[derive(IntoElement)] +pub struct ListItem { + id: ElementId, + selected: bool, + // TODO: Reintroduce this + // disclosure_control_style: DisclosureControlVisibility, + indent_level: usize, + indent_step_size: Pixels, + left_slot: Option, + toggle: Toggle, + inset: bool, + on_click: Option>, + on_toggle: Option>, + on_secondary_mouse_down: Option>, + children: SmallVec<[AnyElement; 2]>, +} + +impl ListItem { + pub fn new(id: impl Into) -> Self { + Self { + id: id.into(), + selected: false, + indent_level: 0, + indent_step_size: px(12.), + left_slot: None, + toggle: Toggle::NotToggleable, + inset: false, + on_click: None, + on_secondary_mouse_down: None, + on_toggle: None, + children: SmallVec::new(), + } + } + + pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self { + self.on_click = Some(Rc::new(handler)); + self + } + + pub fn on_secondary_mouse_down( + mut self, + handler: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, + ) -> Self { + self.on_secondary_mouse_down = Some(Rc::new(handler)); + self + } + + pub fn inset(mut self, inset: bool) -> Self { + self.inset = inset; + self + } + + pub fn indent_level(mut self, indent_level: usize) -> Self { + self.indent_level = indent_level; + self + } + + pub fn indent_step_size(mut self, indent_step_size: Pixels) -> Self { + self.indent_step_size = indent_step_size; + self + } + + pub fn toggle(mut self, toggle: Toggle) -> Self { + self.toggle = toggle; + self + } + + pub fn on_toggle( + mut self, + on_toggle: impl Fn(&ClickEvent, &mut WindowContext) + 'static, + ) -> Self { + self.on_toggle = Some(Rc::new(on_toggle)); + self + } + + pub fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } + + pub fn left_content(mut self, left_content: GraphicSlot) -> Self { + self.left_slot = Some(left_content); + self + } + + pub fn left_icon(mut self, left_icon: Icon) -> Self { + self.left_slot = Some(GraphicSlot::Icon(left_icon)); + self + } + + pub fn left_avatar(mut self, left_avatar: impl Into) -> Self { + self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into())); + self + } +} + +impl ParentElement for ListItem { + fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { + &mut self.children + } +} + +impl RenderOnce for ListItem { + type Rendered = Stateful
; + + fn render(self, cx: &mut WindowContext) -> Self::Rendered { + div() + .id(self.id) + .relative() + // TODO: Add focus state + // .when(self.state == InteractionState::Focused, |this| { + // this.border() + // .border_color(cx.theme().colors().border_focused) + // }) + .when(self.inset, |this| this.rounded_md()) + .hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) + .active(|style| style.bg(cx.theme().colors().ghost_element_active)) + .when(self.selected, |this| { + this.bg(cx.theme().colors().ghost_element_selected) + }) + .when_some(self.on_click, |this, on_click| { + this.cursor_pointer().on_click(move |event, cx| { + // HACK: GPUI currently fires `on_click` with any mouse button, + // but we only care about the left button. + if event.down.button == MouseButton::Left { + (on_click)(event, cx) + } + }) + }) + .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| { + this.on_mouse_down(MouseButton::Right, move |event, cx| { + (on_mouse_down)(event, cx) + }) + }) + .child( + div() + .when(self.inset, |this| this.px_2()) + .ml(self.indent_level as f32 * self.indent_step_size) + .flex() + .gap_1() + .items_center() + .relative() + .child(disclosure_control(self.toggle, self.on_toggle)) + .map(|this| match self.left_slot { + Some(GraphicSlot::Icon(i)) => this.child( + IconElement::new(i) + .size(IconSize::Small) + .color(Color::Muted), + ), + Some(GraphicSlot::Avatar(src)) => this.child(Avatar::source(src)), + Some(GraphicSlot::PublicActor(src)) => this.child(Avatar::uri(src)), + None => this, + }) + .children(self.children), + ) + } +} diff --git a/crates/ui2/src/components/list/list_separator.rs b/crates/ui2/src/components/list/list_separator.rs new file mode 100644 index 0000000000000000000000000000000000000000..e986c8014d21f1327efa780fbdc2a7bdf65b3d45 --- /dev/null +++ b/crates/ui2/src/components/list/list_separator.rs @@ -0,0 +1,20 @@ +use gpui::Div; + +use crate::prelude::*; + +#[derive(IntoElement, Clone)] +pub struct ListSeparator; + +impl ListSeparator { + pub fn new() -> Self { + Self + } +} + +impl RenderOnce for ListSeparator { + type Rendered = Div; + + fn render(self, cx: &mut WindowContext) -> Self::Rendered { + div().h_px().w_full().bg(cx.theme().colors().border_variant) + } +} diff --git a/crates/ui2/src/components/list/list_sub_header.rs b/crates/ui2/src/components/list/list_sub_header.rs new file mode 100644 index 0000000000000000000000000000000000000000..361e721e18f54c73c42057057b0ca391ea3f1217 --- /dev/null +++ b/crates/ui2/src/components/list/list_sub_header.rs @@ -0,0 +1,56 @@ +use gpui::Div; + +use crate::prelude::*; +use crate::{h_stack, Icon, IconElement, IconSize, Label}; + +#[derive(IntoElement, Clone)] +pub struct ListSubHeader { + label: SharedString, + left_icon: Option, + inset: bool, +} + +impl ListSubHeader { + pub fn new(label: impl Into) -> Self { + Self { + label: label.into(), + left_icon: None, + inset: false, + } + } + + pub fn left_icon(mut self, left_icon: Option) -> Self { + self.left_icon = left_icon; + self + } +} + +impl RenderOnce for ListSubHeader { + type Rendered = Div; + + fn render(self, _cx: &mut WindowContext) -> Self::Rendered { + h_stack().flex_1().w_full().relative().py_1().child( + div() + .h_6() + .when(self.inset, |this| this.px_2()) + .flex() + .flex_1() + .w_full() + .gap_1() + .items_center() + .justify_between() + .child( + div() + .flex() + .gap_1() + .items_center() + .children(self.left_icon.map(|i| { + IconElement::new(i) + .color(Color::Muted) + .size(IconSize::Small) + })) + .child(Label::new(self.label.clone()).color(Color::Muted)), + ), + ) + } +} diff --git a/crates/ui2/src/components/stories.rs b/crates/ui2/src/components/stories.rs index 475f5e98277f8cfc14b135eb1d5c77c2679afbfc..8d3675d521a96a8d32a4f91f5f40183b878003ac 100644 --- a/crates/ui2/src/components/stories.rs +++ b/crates/ui2/src/components/stories.rs @@ -6,7 +6,9 @@ mod icon; mod icon_button; mod keybinding; mod label; +mod list; mod list_item; + pub use avatar::*; pub use button::*; pub use checkbox::*; @@ -15,4 +17,5 @@ pub use icon::*; pub use icon_button::*; pub use keybinding::*; pub use label::*; +pub use list::*; pub use list_item::*; diff --git a/crates/ui2/src/components/stories/list.rs b/crates/ui2/src/components/stories/list.rs new file mode 100644 index 0000000000000000000000000000000000000000..75a16deb40743dd3a517d945c08f5dec88b3385f --- /dev/null +++ b/crates/ui2/src/components/stories/list.rs @@ -0,0 +1,38 @@ +use gpui::{Div, Render}; +use story::Story; + +use crate::{prelude::*, ListHeader, ListSeparator, ListSubHeader}; +use crate::{List, ListItem}; + +pub struct ListStory; + +impl Render for ListStory { + type Element = Div; + + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { + Story::container() + .child(Story::title_for::()) + .child(Story::label("Default")) + .child( + List::new() + .child(ListItem::new("apple").child("Apple")) + .child(ListItem::new("banana").child("Banana")) + .child(ListItem::new("cherry").child("Cherry")), + ) + .child(Story::label("With sections")) + .child( + List::new() + .child(ListHeader::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")) + .child(ListSubHeader::new("Leafy Vegetables")) + .child(ListItem::new("kale").child("Kale")), + ) + } +} From c7b79c9aef988fcfddcd60b4f2667c83fd74aa1f Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 29 Nov 2023 13:38:05 -0500 Subject: [PATCH 2/2] Remove unnecessary constructor and `Clone` derives --- crates/ui2/src/components/context_menu.rs | 2 +- crates/ui2/src/components/list/list_separator.rs | 8 +------- crates/ui2/src/components/list/list_sub_header.rs | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/crates/ui2/src/components/context_menu.rs b/crates/ui2/src/components/context_menu.rs index 3772fb1bd2681ee635f3cc896568739062280c19..f071d188a12f8dc1b892215c11ba5e4c0ff650f5 100644 --- a/crates/ui2/src/components/context_menu.rs +++ b/crates/ui2/src/components/context_menu.rs @@ -171,7 +171,7 @@ impl Render for ContextMenu { .child( List::new().children(self.items.iter().enumerate().map( |(ix, item)| match item { - ContextMenuItem::Separator => ListSeparator::new().into_any_element(), + ContextMenuItem::Separator => ListSeparator.into_any_element(), ContextMenuItem::Header(header) => { ListSubHeader::new(header.clone()).into_any_element() } diff --git a/crates/ui2/src/components/list/list_separator.rs b/crates/ui2/src/components/list/list_separator.rs index e986c8014d21f1327efa780fbdc2a7bdf65b3d45..0398a110e977f94dd75bf458f7ef879c7a1a3cfd 100644 --- a/crates/ui2/src/components/list/list_separator.rs +++ b/crates/ui2/src/components/list/list_separator.rs @@ -2,15 +2,9 @@ use gpui::Div; use crate::prelude::*; -#[derive(IntoElement, Clone)] +#[derive(IntoElement)] pub struct ListSeparator; -impl ListSeparator { - pub fn new() -> Self { - Self - } -} - impl RenderOnce for ListSeparator { type Rendered = Div; diff --git a/crates/ui2/src/components/list/list_sub_header.rs b/crates/ui2/src/components/list/list_sub_header.rs index 361e721e18f54c73c42057057b0ca391ea3f1217..17f07b7b0bf90712dc9b8703c7f51261f5e5c49f 100644 --- a/crates/ui2/src/components/list/list_sub_header.rs +++ b/crates/ui2/src/components/list/list_sub_header.rs @@ -3,7 +3,7 @@ use gpui::Div; use crate::prelude::*; use crate::{h_stack, Icon, IconElement, IconSize, Label}; -#[derive(IntoElement, Clone)] +#[derive(IntoElement)] pub struct ListSubHeader { label: SharedString, left_icon: Option,