1use std::sync::Arc;
2
3use gpui::{ClickEvent, CursorStyle, SharedString};
4
5use crate::{Color, IconButton, IconButtonShape, IconName, IconSize, prelude::*};
6
7#[derive(IntoElement, RegisterComponent)]
8pub struct Disclosure {
9 id: ElementId,
10 is_open: bool,
11 selected: bool,
12 disabled: bool,
13 on_toggle_expanded: Option<Arc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
14 cursor_style: CursorStyle,
15 opened_icon: IconName,
16 closed_icon: IconName,
17 visible_on_hover: Option<SharedString>,
18}
19
20impl Disclosure {
21 pub fn new(id: impl Into<ElementId>, is_open: bool) -> Self {
22 Self {
23 id: id.into(),
24 is_open,
25 selected: false,
26 disabled: false,
27 on_toggle_expanded: None,
28 cursor_style: CursorStyle::PointingHand,
29 opened_icon: IconName::ChevronDown,
30 closed_icon: IconName::ChevronRight,
31 visible_on_hover: None,
32 }
33 }
34
35 pub fn on_toggle_expanded(
36 mut self,
37 handler: impl Into<Option<Arc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>>,
38 ) -> Self {
39 self.on_toggle_expanded = handler.into();
40 self
41 }
42
43 pub fn opened_icon(mut self, icon: IconName) -> Self {
44 self.opened_icon = icon;
45 self
46 }
47
48 pub fn closed_icon(mut self, icon: IconName) -> Self {
49 self.closed_icon = icon;
50 self
51 }
52
53 pub fn disabled(mut self, disabled: bool) -> Self {
54 self.disabled = disabled;
55 self
56 }
57}
58
59impl Toggleable for Disclosure {
60 fn toggle_state(mut self, selected: bool) -> Self {
61 self.selected = selected;
62 self
63 }
64}
65
66impl Clickable for Disclosure {
67 fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
68 self.on_toggle_expanded = Some(Arc::new(handler));
69 self
70 }
71
72 fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self {
73 self.cursor_style = cursor_style;
74 self
75 }
76}
77
78impl VisibleOnHover for Disclosure {
79 fn visible_on_hover(mut self, group_name: impl Into<SharedString>) -> Self {
80 self.visible_on_hover = Some(group_name.into());
81 self
82 }
83}
84
85impl RenderOnce for Disclosure {
86 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
87 IconButton::new(
88 self.id,
89 match self.is_open {
90 true => self.opened_icon,
91 false => self.closed_icon,
92 },
93 )
94 .shape(IconButtonShape::Square)
95 .icon_color(Color::Muted)
96 .icon_size(IconSize::Small)
97 .disabled(self.disabled)
98 .toggle_state(self.selected)
99 .aria_expanded(self.is_open)
100 .when_some(self.visible_on_hover.clone(), |this, group_name| {
101 this.visible_on_hover(group_name)
102 })
103 .when_some(self.on_toggle_expanded, move |this, on_toggle| {
104 this.on_click(move |event, window, cx| on_toggle(event, window, cx))
105 })
106 }
107}
108
109impl Component for Disclosure {
110 fn scope() -> ComponentScope {
111 ComponentScope::Input
112 }
113
114 fn description() -> Option<&'static str> {
115 Some(
116 "An interactive element used to show or hide content, typically used in expandable sections or tree-like structures.",
117 )
118 }
119
120 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
121 Some(
122 v_flex()
123 .gap_6()
124 .children(vec![
125 example_group_with_title(
126 "Disclosure States",
127 vec![
128 single_example(
129 "Closed",
130 Disclosure::new("closed", false).into_any_element(),
131 ),
132 single_example(
133 "Open",
134 Disclosure::new("open", true).into_any_element(),
135 ),
136 ],
137 ),
138 example_group_with_title(
139 "Interactive Example",
140 vec![single_example(
141 "Toggleable",
142 v_flex()
143 .gap_2()
144 .child(Disclosure::new("interactive", false).into_any_element())
145 .child(Label::new("Click to toggle"))
146 .into_any_element(),
147 )],
148 ),
149 ])
150 .into_any_element(),
151 )
152 }
153}