1use gpui::prelude::*;
4use gpui::{div, px, App, Context, IntoElement, SharedString, Window};
5
6use super::Content;
7use crate::theme::{theme, Size};
8
9struct AccItem {
10 label: SharedString,
11 content: Content,
12}
13
14pub struct Accordion {
17 items: Vec<AccItem>,
18 open: Vec<bool>,
19 multiple: bool,
20}
21
22impl Accordion {
23 pub fn new(_cx: &mut Context<Self>) -> Self {
24 Accordion {
25 items: Vec::new(),
26 open: Vec::new(),
27 multiple: false,
28 }
29 }
30
31 pub fn item<E>(
33 mut self,
34 label: impl Into<SharedString>,
35 content: impl Fn(&mut Window, &mut App) -> E + 'static,
36 ) -> Self
37 where
38 E: IntoElement,
39 {
40 self.items.push(AccItem {
41 label: label.into(),
42 content: Box::new(move |window, cx| content(window, cx).into_any_element()),
43 });
44 self.open.push(false);
45 self
46 }
47
48 pub fn multiple(mut self, multiple: bool) -> Self {
50 self.multiple = multiple;
51 self
52 }
53
54 pub fn default_open(mut self, index: usize) -> Self {
56 if let Some(slot) = self.open.get_mut(index) {
57 *slot = true;
58 }
59 self
60 }
61}
62
63impl Render for Accordion {
64 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
65 let t = theme(cx);
66 let text = t.text().hsla();
67 let dimmed = t.dimmed().hsla();
68 let line = t.border().hsla();
69 let surface_hover = t.surface_hover().hsla();
70 let radius = t.radius(Size::Md);
71 let font = t.font_size(Size::Md);
72 let font_sm = t.font_size(Size::Sm);
73
74 let mut root = div().flex().flex_col().gap(px(8.0));
75
76 for (i, item) in self.items.iter().enumerate() {
77 let is_open = self.open.get(i).copied().unwrap_or(false);
78
79 let header = div()
80 .id(("guise-accordion-header", i))
81 .flex()
82 .items_center()
83 .justify_between()
84 .px(px(14.0))
85 .py(px(12.0))
86 .text_size(px(font))
87 .text_color(text)
88 .hover(move |s| s.bg(surface_hover))
89 .child(item.label.clone())
90 .child(
91 div()
92 .text_color(dimmed)
93 .child(SharedString::new_static(if is_open {
94 "\u{25be}"
95 } else {
96 "\u{25b8}"
97 })),
98 )
99 .on_click(cx.listener(move |this, _ev, _window, cx| {
100 let was = this.open.get(i).copied().unwrap_or(false);
101 if !this.multiple {
102 this.open.iter_mut().for_each(|o| *o = false);
103 }
104 if let Some(slot) = this.open.get_mut(i) {
105 *slot = !was;
106 }
107 cx.notify();
108 }));
109
110 let mut panel = div()
111 .flex()
112 .flex_col()
113 .rounded(px(radius))
114 .border_1()
115 .border_color(line)
116 .child(header);
117
118 if is_open {
119 let body = (item.content)(window, cx);
120 panel = panel.child(
121 div()
122 .px(px(14.0))
123 .pb(px(12.0))
124 .text_size(px(font_sm))
125 .text_color(dimmed)
126 .child(body),
127 );
128 }
129
130 root = root.child(panel);
131 }
132
133 root
134 }
135}