1use gpui::prelude::*;
18use gpui::{div, px, AnyElement, App, ClickEvent, ElementId, IntoElement, SharedString, Window};
19
20use crate::devtools::Probed;
21use crate::input::ClickHandler;
22use crate::theme::{theme, ColorName, Size};
23
24#[derive(IntoElement)]
29pub struct Spoiler {
30 id: ElementId,
31 children: Vec<AnyElement>,
32 max_height: f32,
33 expanded: bool,
34 show_label: SharedString,
35 hide_label: SharedString,
36 color: ColorName,
37 size: Size,
38 on_toggle: Option<ClickHandler>,
39}
40
41impl Spoiler {
42 pub fn new(id: impl Into<ElementId>) -> Self {
43 Spoiler {
44 id: id.into(),
45 children: Vec::new(),
46 max_height: 100.0,
47 expanded: false,
48 show_label: SharedString::new_static("Show more"),
49 hide_label: SharedString::new_static("Hide"),
50 color: ColorName::Blue,
51 size: Size::Sm,
52 on_toggle: None,
53 }
54 }
55
56 pub fn max_height(mut self, max_height: f32) -> Self {
58 self.max_height = max_height;
59 self
60 }
61
62 pub fn expanded(mut self, expanded: bool) -> Self {
64 self.expanded = expanded;
65 self
66 }
67
68 pub fn show_label(mut self, label: impl Into<SharedString>) -> Self {
70 self.show_label = label.into();
71 self
72 }
73
74 pub fn hide_label(mut self, label: impl Into<SharedString>) -> Self {
76 self.hide_label = label.into();
77 self
78 }
79
80 pub fn color(mut self, color: ColorName) -> Self {
82 self.color = color;
83 self
84 }
85
86 pub fn size(mut self, size: Size) -> Self {
88 self.size = size;
89 self
90 }
91
92 pub fn on_toggle(
95 mut self,
96 handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
97 ) -> Self {
98 self.on_toggle = Some(Box::new(handler));
99 self
100 }
101}
102
103impl ParentElement for Spoiler {
104 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
105 self.children.extend(elements);
106 }
107}
108
109impl RenderOnce for Spoiler {
110 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
111 let t = theme(cx);
112 let dark = t.scheme.is_dark();
113 let link = t.color(self.color, if dark { 4 } else { 6 }).hsla();
114 let link_hover = t.color(self.color, if dark { 3 } else { 7 }).hsla();
115 let font = t.font_size(self.size);
116 let gap = t.spacing(Size::Xs);
117
118 let mut content = div().w_full().children(self.children);
119 if !self.expanded {
120 content = content.max_h(px(self.max_height)).overflow_hidden();
121 }
122
123 let label = if self.expanded {
124 self.hide_label
125 } else {
126 self.show_label
127 };
128 let mut toggle = div()
129 .id(self.id)
130 .cursor_pointer()
131 .text_size(px(font))
132 .text_color(link)
133 .hover(move |s| s.text_color(link_hover))
134 .child(label);
135 if let Some(handler) = self.on_toggle {
136 toggle = toggle.on_click(handler);
137 }
138
139 div()
140 .flex()
141 .flex_col()
142 .items_start()
143 .gap(px(gap))
144 .child(content)
145 .child(toggle)
146 .probe("Spoiler")
147 }
148}