list_header.rs

  1use crate::{h_stack, prelude::*, Disclosure, Icon, IconElement, IconSize, Label};
  2use gpui::{AnyElement, ClickEvent, Div};
  3use smallvec::SmallVec;
  4
  5#[derive(IntoElement)]
  6pub struct ListHeader {
  7    label: SharedString,
  8    left_icon: Option<Icon>,
  9    meta: SmallVec<[AnyElement; 2]>,
 10    toggle: Option<bool>,
 11    on_toggle: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
 12    inset: bool,
 13    selected: bool,
 14}
 15
 16impl ListHeader {
 17    pub fn new(label: impl Into<SharedString>) -> Self {
 18        Self {
 19            label: label.into(),
 20            left_icon: None,
 21            meta: SmallVec::new(),
 22            inset: false,
 23            toggle: None,
 24            on_toggle: None,
 25            selected: false,
 26        }
 27    }
 28
 29    pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
 30        self.toggle = toggle.into();
 31        self
 32    }
 33
 34    pub fn on_toggle(
 35        mut self,
 36        on_toggle: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
 37    ) -> Self {
 38        self.on_toggle = Some(Box::new(on_toggle));
 39        self
 40    }
 41
 42    pub fn left_icon(mut self, left_icon: impl Into<Option<Icon>>) -> Self {
 43        self.left_icon = left_icon.into();
 44        self
 45    }
 46
 47    pub fn meta(mut self, meta: impl IntoElement) -> Self {
 48        self.meta.push(meta.into_any_element());
 49        self
 50    }
 51}
 52
 53impl Selectable for ListHeader {
 54    fn selected(mut self, selected: bool) -> Self {
 55        self.selected = selected;
 56        self
 57    }
 58}
 59
 60impl RenderOnce for ListHeader {
 61    type Rendered = Div;
 62
 63    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
 64        h_stack().w_full().relative().child(
 65            div()
 66                .h_5()
 67                .when(self.inset, |this| this.px_2())
 68                .when(self.selected, |this| {
 69                    this.bg(cx.theme().colors().ghost_element_selected)
 70                })
 71                .flex()
 72                .flex_1()
 73                .items_center()
 74                .justify_between()
 75                .w_full()
 76                .gap_1()
 77                .child(
 78                    h_stack()
 79                        .gap_1()
 80                        .child(
 81                            div()
 82                                .flex()
 83                                .gap_1()
 84                                .items_center()
 85                                .children(self.left_icon.map(|i| {
 86                                    IconElement::new(i)
 87                                        .color(Color::Muted)
 88                                        .size(IconSize::Small)
 89                                }))
 90                                .child(Label::new(self.label.clone()).color(Color::Muted)),
 91                        )
 92                        .children(
 93                            self.toggle
 94                                .map(|is_open| Disclosure::new(is_open).on_toggle(self.on_toggle)),
 95                        ),
 96                )
 97                .child(h_stack().gap_2().items_center().children(self.meta)),
 98        )
 99    }
100}