Make `Disclosure` accept an ID (#3701)

Marshall Bowers created

This PR makes the `Disclosure` component accept an ID rather than using
a static ID for all disclosures.

Release Notes:

- N/A

Change summary

crates/ui2/src/components/disclosure.rs         |  6 
crates/ui2/src/components/list/list_header.rs   | 87 ++++++++++--------
crates/ui2/src/components/list/list_item.rs     |  2 
crates/ui2/src/components/stories/disclosure.rs |  4 
4 files changed, 53 insertions(+), 46 deletions(-)

Detailed changes

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

@@ -4,13 +4,15 @@ use crate::{prelude::*, Color, Icon, IconButton, IconSize};
 
 #[derive(IntoElement)]
 pub struct Disclosure {
+    id: ElementId,
     is_open: bool,
     on_toggle: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
 }
 
 impl Disclosure {
-    pub fn new(is_open: bool) -> Self {
+    pub fn new(id: impl Into<ElementId>, is_open: bool) -> Self {
         Self {
+            id: id.into(),
             is_open,
             on_toggle: None,
         }
@@ -30,7 +32,7 @@ impl RenderOnce for Disclosure {
 
     fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
         IconButton::new(
-            "toggle",
+            self.id,
             match self.is_open {
                 true => Icon::ChevronDown,
                 false => Icon::ChevronRight,

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

@@ -1,5 +1,6 @@
+use gpui::{AnyElement, ClickEvent, Div, Stateful};
+
 use crate::{h_stack, prelude::*, Disclosure, Label};
-use gpui::{AnyElement, ClickEvent, Div};
 
 #[derive(IntoElement)]
 pub struct ListHeader {
@@ -75,48 +76,52 @@ impl Selectable for ListHeader {
 }
 
 impl RenderOnce for ListHeader {
-    type Rendered = Div;
+    type Rendered = Stateful<Div>;
 
     fn render(self, cx: &mut WindowContext) -> Self::Rendered {
-        h_stack().w_full().relative().group("list_header").child(
-            div()
-                .h_7()
-                .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()
-                        .children(
-                            self.toggle
-                                .map(|is_open| Disclosure::new(is_open).on_toggle(self.on_toggle)),
-                        )
-                        .child(
-                            div()
-                                .flex()
-                                .gap_1()
-                                .items_center()
-                                .children(self.start_slot)
-                                .child(Label::new(self.label.clone()).color(Color::Muted)),
-                        ),
-                )
-                .child(h_stack().children(self.end_slot))
-                .when_some(self.end_hover_slot, |this, end_hover_slot| {
-                    this.child(
-                        div()
-                            .absolute()
-                            .right_0()
-                            .visible_on_hover("list_header")
-                            .child(end_hover_slot),
+        h_stack()
+            .id(self.label.clone())
+            .w_full()
+            .relative()
+            .group("list_header")
+            .child(
+                div()
+                    .h_7()
+                    .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()
+                            .children(self.toggle.map(|is_open| {
+                                Disclosure::new("toggle", is_open).on_toggle(self.on_toggle)
+                            }))
+                            .child(
+                                div()
+                                    .flex()
+                                    .gap_1()
+                                    .items_center()
+                                    .children(self.start_slot)
+                                    .child(Label::new(self.label.clone()).color(Color::Muted)),
+                            ),
                     )
-                }),
-        )
+                    .child(h_stack().children(self.end_slot))
+                    .when_some(self.end_hover_slot, |this, end_hover_slot| {
+                        this.child(
+                            div()
+                                .absolute()
+                                .right_0()
+                                .visible_on_hover("list_header")
+                                .child(end_hover_slot),
+                        )
+                    }),
+            )
     }
 }

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

@@ -193,7 +193,7 @@ impl RenderOnce for ListItem {
                             .absolute()
                             .left(rems(-1.))
                             .when(is_open, |this| this.visible_on_hover(""))
-                            .child(Disclosure::new(is_open).on_toggle(self.on_toggle))
+                            .child(Disclosure::new("toggle", is_open).on_toggle(self.on_toggle))
                     }))
                     .child(
                         h_stack()

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

@@ -13,8 +13,8 @@ impl Render for DisclosureStory {
         Story::container()
             .child(Story::title_for::<Disclosure>())
             .child(Story::label("Toggled"))
-            .child(Disclosure::new(true))
+            .child(Disclosure::new("toggled", true))
             .child(Story::label("Not Toggled"))
-            .child(Disclosure::new(false))
+            .child(Disclosure::new("not_toggled", false))
     }
 }