Rework `Disclosure` component

Marshall Bowers created

Change summary

crates/storybook2/src/story_selector.rs         |  2 
crates/ui2/src/components/disclosure.rs         | 67 +++++++++++++-----
crates/ui2/src/components/list/list_header.rs   |  6 -
crates/ui2/src/components/list/list_item.rs     |  4 
crates/ui2/src/components/stories.rs            |  2 
crates/ui2/src/components/stories/disclosure.rs | 22 ++++++
6 files changed, 76 insertions(+), 27 deletions(-)

Detailed changes

crates/storybook2/src/story_selector.rs 🔗

@@ -16,6 +16,7 @@ pub enum ComponentStory {
     Button,
     Checkbox,
     ContextMenu,
+    Disclosure,
     Focus,
     Icon,
     IconButton,
@@ -36,6 +37,7 @@ impl ComponentStory {
             Self::Button => cx.build_view(|_| ui::ButtonStory).into(),
             Self::Checkbox => cx.build_view(|_| ui::CheckboxStory).into(),
             Self::ContextMenu => cx.build_view(|_| ui::ContextMenuStory).into(),
+            Self::Disclosure => cx.build_view(|_| ui::DisclosureStory).into(),
             Self::Focus => FocusStory::view(cx).into(),
             Self::Icon => cx.build_view(|_| ui::IconStory).into(),
             Self::IconButton => cx.build_view(|_| ui::IconButtonStory).into(),

crates/ui2/src/components/disclosure.rs 🔗

@@ -1,30 +1,55 @@
 use std::rc::Rc;
 
-use gpui::{div, ClickEvent, Element, IntoElement, ParentElement, WindowContext};
+use gpui::{ClickEvent, Div};
 
+use crate::prelude::*;
 use crate::{Color, Icon, IconButton, IconSize, Toggle};
 
-pub fn disclosure_control(
+#[derive(IntoElement)]
+pub struct Disclosure {
     toggle: Toggle,
     on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
-) -> impl Element {
-    match (toggle.is_toggleable(), toggle.is_toggled()) {
-        (false, _) => div(),
-        (_, true) => div().child(
-            IconButton::new("toggle", Icon::ChevronDown)
-                .color(Color::Muted)
-                .size(IconSize::Small)
-                .when_some(on_toggle, move |el, on_toggle| {
-                    el.on_click(move |e, cx| on_toggle(e, cx))
-                }),
-        ),
-        (_, false) => div().child(
-            IconButton::new("toggle", Icon::ChevronRight)
-                .color(Color::Muted)
-                .size(IconSize::Small)
-                .when_some(on_toggle, move |el, on_toggle| {
-                    el.on_click(move |e, cx| on_toggle(e, cx))
-                }),
-        ),
+}
+
+impl Disclosure {
+    pub fn new(toggle: Toggle) -> Self {
+        Self {
+            toggle,
+            on_toggle: None,
+        }
+    }
+
+    pub fn on_toggle(
+        mut self,
+        handler: impl Into<Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>>,
+    ) -> Self {
+        self.on_toggle = handler.into();
+        self
+    }
+}
+
+impl RenderOnce for Disclosure {
+    type Rendered = Div;
+
+    fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
+        if !self.toggle.is_toggleable() {
+            return div();
+        }
+
+        div().child(
+            IconButton::new(
+                "toggle",
+                if self.toggle.is_toggled() {
+                    Icon::ChevronDown
+                } else {
+                    Icon::ChevronRight
+                },
+            )
+            .color(Color::Muted)
+            .size(IconSize::Small)
+            .when_some(self.on_toggle, move |this, on_toggle| {
+                this.on_click(move |event, cx| on_toggle(event, cx))
+            }),
+        )
     }
 }

crates/ui2/src/components/list/list_header.rs 🔗

@@ -3,7 +3,7 @@ use std::rc::Rc;
 use gpui::{ClickEvent, Div};
 
 use crate::prelude::*;
-use crate::{disclosure_control, h_stack, Icon, IconButton, IconElement, IconSize, Label, Toggle};
+use crate::{h_stack, Disclosure, Icon, IconButton, IconElement, IconSize, Label, Toggle};
 
 pub enum ListHeaderMeta {
     Tools(Vec<IconButton>),
@@ -73,8 +73,6 @@ 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()
@@ -115,7 +113,7 @@ impl RenderOnce for ListHeader {
                                 }))
                                 .child(Label::new(self.label.clone()).color(Color::Muted)),
                         )
-                        .child(disclosure_control),
+                        .child(Disclosure::new(self.toggle).on_toggle(self.on_toggle)),
                 )
                 .child(meta),
         )

crates/ui2/src/components/list/list_item.rs 🔗

@@ -6,7 +6,7 @@ use gpui::{
 use smallvec::SmallVec;
 
 use crate::prelude::*;
-use crate::{disclosure_control, Avatar, GraphicSlot, Icon, IconElement, IconSize, Toggle};
+use crate::{Avatar, Disclosure, GraphicSlot, Icon, IconElement, IconSize, Toggle};
 
 #[derive(IntoElement)]
 pub struct ListItem {
@@ -150,7 +150,7 @@ impl RenderOnce for ListItem {
                     .gap_1()
                     .items_center()
                     .relative()
-                    .child(disclosure_control(self.toggle, self.on_toggle))
+                    .child(Disclosure::new(self.toggle).on_toggle(self.on_toggle))
                     .map(|this| match self.left_slot {
                         Some(GraphicSlot::Icon(i)) => this.child(
                             IconElement::new(i)

crates/ui2/src/components/stories.rs 🔗

@@ -2,6 +2,7 @@ mod avatar;
 mod button;
 mod checkbox;
 mod context_menu;
+mod disclosure;
 mod icon;
 mod icon_button;
 mod keybinding;
@@ -13,6 +14,7 @@ pub use avatar::*;
 pub use button::*;
 pub use checkbox::*;
 pub use context_menu::*;
+pub use disclosure::*;
 pub use icon::*;
 pub use icon_button::*;
 pub use keybinding::*;

crates/ui2/src/components/stories/disclosure.rs 🔗

@@ -0,0 +1,22 @@
+use gpui::{Div, Render};
+use story::Story;
+
+use crate::prelude::*;
+use crate::{Disclosure, Toggle};
+
+pub struct DisclosureStory;
+
+impl Render for DisclosureStory {
+    type Element = Div;
+
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+        Story::container()
+            .child(Story::title_for::<Disclosure>())
+            .child(Story::label("Toggled"))
+            .child(Disclosure::new(Toggle::Toggled(true)))
+            .child(Story::label("Not Toggled"))
+            .child(Disclosure::new(Toggle::Toggled(false)))
+            .child(Story::label("Not Toggleable"))
+            .child(Disclosure::new(Toggle::NotToggleable))
+    }
+}