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