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