1use std::sync::Arc;
2
3use crate::{h_flex, prelude::*, Disclosure, Label};
4use gpui::{AnyElement, ClickEvent};
5
6#[derive(IntoElement)]
7pub struct ListHeader {
8 /// The label of the header.
9 label: SharedString,
10 /// A slot for content that appears before the label, like an icon or avatar.
11 start_slot: Option<AnyElement>,
12 /// A slot for content that appears after the label, usually on the other side of the header.
13 /// This might be a button, a disclosure arrow, a face pile, etc.
14 end_slot: Option<AnyElement>,
15 /// A slot for content that appears on hover after the label
16 /// It will obscure the `end_slot` when visible.
17 end_hover_slot: Option<AnyElement>,
18 toggle: Option<bool>,
19 on_toggle: Option<Arc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
20 inset: bool,
21 selected: bool,
22}
23
24impl ListHeader {
25 pub fn new(label: impl Into<SharedString>) -> Self {
26 Self {
27 label: label.into(),
28 start_slot: None,
29 end_slot: None,
30 end_hover_slot: None,
31 inset: false,
32 toggle: None,
33 on_toggle: None,
34 selected: false,
35 }
36 }
37
38 pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
39 self.toggle = toggle.into();
40 self
41 }
42
43 pub fn on_toggle(
44 mut self,
45 on_toggle: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
46 ) -> Self {
47 self.on_toggle = Some(Arc::new(on_toggle));
48 self
49 }
50
51 pub fn start_slot<E: IntoElement>(mut self, start_slot: impl Into<Option<E>>) -> Self {
52 self.start_slot = start_slot.into().map(IntoElement::into_any_element);
53 self
54 }
55
56 pub fn end_slot<E: IntoElement>(mut self, end_slot: impl Into<Option<E>>) -> Self {
57 self.end_slot = end_slot.into().map(IntoElement::into_any_element);
58 self
59 }
60
61 pub fn end_hover_slot<E: IntoElement>(mut self, end_hover_slot: impl Into<Option<E>>) -> Self {
62 self.end_hover_slot = end_hover_slot.into().map(IntoElement::into_any_element);
63 self
64 }
65
66 pub fn inset(mut self, inset: bool) -> Self {
67 self.inset = inset;
68 self
69 }
70}
71
72impl Selectable for ListHeader {
73 fn selected(mut self, selected: bool) -> Self {
74 self.selected = selected;
75 self
76 }
77}
78
79impl RenderOnce for ListHeader {
80 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
81 h_flex()
82 .id(self.label.clone())
83 .w_full()
84 .relative()
85 .group("list_header")
86 .child(
87 div()
88 .h_7()
89 .when(self.inset, |this| this.px_2())
90 .when(self.selected, |this| {
91 this.bg(cx.theme().colors().ghost_element_selected)
92 })
93 .flex()
94 .flex_1()
95 .items_center()
96 .justify_between()
97 .w_full()
98 .gap_1()
99 .child(
100 h_flex()
101 .gap_1()
102 .children(self.toggle.map(|is_open| {
103 Disclosure::new("toggle", is_open).on_toggle(self.on_toggle.clone())
104 }))
105 .child(
106 div()
107 .id("label_container")
108 .flex()
109 .gap_1()
110 .items_center()
111 .children(self.start_slot)
112 .child(Label::new(self.label.clone()).color(Color::Muted))
113 .when_some(self.on_toggle, |this, on_toggle| {
114 this.on_click(move |event, cx| on_toggle(event, cx))
115 }),
116 ),
117 )
118 .child(h_flex().children(self.end_slot))
119 .when_some(self.end_hover_slot, |this, end_hover_slot| {
120 this.child(
121 div()
122 .absolute()
123 .right_0()
124 .visible_on_hover("list_header")
125 .child(end_hover_slot),
126 )
127 }),
128 )
129 }
130}