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