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: 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: 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(
36 mut self,
37 handler: impl Into<Option<Arc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>>,
38 ) -> Self {
39 self.on_toggle = 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 = 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 .when_some(self.visible_on_hover.clone(), |this, group_name| {
100 this.visible_on_hover(group_name)
101 })
102 .when_some(self.on_toggle, move |this, on_toggle| {
103 this.on_click(move |event, window, cx| on_toggle(event, window, cx))
104 })
105 }
106}
107
108impl Component for Disclosure {
109 fn scope() -> ComponentScope {
110 ComponentScope::Input
111 }
112
113 fn description() -> Option<&'static str> {
114 Some(
115 "An interactive element used to show or hide content, typically used in expandable sections or tree-like structures.",
116 )
117 }
118
119 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
120 Some(
121 v_flex()
122 .gap_6()
123 .children(vec![
124 example_group_with_title(
125 "Disclosure States",
126 vec![
127 single_example(
128 "Closed",
129 Disclosure::new("closed", false).into_any_element(),
130 ),
131 single_example(
132 "Open",
133 Disclosure::new("open", true).into_any_element(),
134 ),
135 ],
136 ),
137 example_group_with_title(
138 "Interactive Example",
139 vec![single_example(
140 "Toggleable",
141 v_flex()
142 .gap_2()
143 .child(Disclosure::new("interactive", false).into_any_element())
144 .child(Label::new("Click to toggle"))
145 .into_any_element(),
146 )],
147 ),
148 ])
149 .into_any_element(),
150 )
151 }
152}