1use std::sync::Arc;
2
3use gpui::{ClickEvent, CursorStyle, SharedString};
4
5use crate::{Color, IconButton, 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 .icon_color(Color::Muted)
95 .disabled(self.disabled)
96 .toggle_state(self.selected)
97 .when_some(self.visible_on_hover.clone(), |this, group_name| {
98 this.visible_on_hover(group_name)
99 })
100 .when_some(self.on_toggle_expanded, move |this, on_toggle| {
101 this.on_click(move |event, window, cx| on_toggle(event, window, cx))
102 })
103 }
104}
105
106impl Component for Disclosure {
107 fn scope() -> ComponentScope {
108 ComponentScope::Input
109 }
110
111 fn description() -> Option<&'static str> {
112 Some(
113 "An interactive element used to show or hide content, typically used in expandable sections or tree-like structures.",
114 )
115 }
116
117 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
118 Some(
119 v_flex()
120 .gap_6()
121 .children(vec![
122 example_group_with_title(
123 "Disclosure States",
124 vec![
125 single_example(
126 "Closed",
127 Disclosure::new("closed", false).into_any_element(),
128 ),
129 single_example(
130 "Open",
131 Disclosure::new("open", true).into_any_element(),
132 ),
133 ],
134 ),
135 example_group_with_title(
136 "Interactive Example",
137 vec![single_example(
138 "Toggleable",
139 v_flex()
140 .gap_2()
141 .child(Disclosure::new("interactive", false).into_any_element())
142 .child(Label::new("Click to toggle"))
143 .into_any_element(),
144 )],
145 ),
146 ])
147 .into_any_element(),
148 )
149 }
150}