list.rs

  1use component::{Component, ComponentScope, example_group_with_title, single_example};
  2use gpui::{AnyElement, ElementId, Role};
  3use smallvec::SmallVec;
  4
  5use crate::{Label, ListHeader, ListItem, prelude::*};
  6
  7pub enum EmptyMessage {
  8    Text(SharedString),
  9    Element(AnyElement),
 10}
 11
 12#[derive(IntoElement, RegisterComponent)]
 13pub struct List {
 14    id: Option<ElementId>,
 15    /// Message to display when the list is empty
 16    /// Defaults to "No items"
 17    empty_message: EmptyMessage,
 18    header: Option<ListHeader>,
 19    toggle: Option<bool>,
 20    children: SmallVec<[AnyElement; 2]>,
 21}
 22
 23impl Default for List {
 24    fn default() -> Self {
 25        Self::new()
 26    }
 27}
 28
 29impl List {
 30    pub fn new() -> Self {
 31        Self {
 32            id: None,
 33            empty_message: EmptyMessage::Text("No items".into()),
 34            header: None,
 35            toggle: None,
 36            children: SmallVec::new(),
 37        }
 38    }
 39
 40    pub fn id(mut self, id: impl Into<ElementId>) -> Self {
 41        self.id = Some(id.into());
 42        self
 43    }
 44
 45    pub fn empty_message(mut self, message: impl Into<EmptyMessage>) -> Self {
 46        self.empty_message = message.into();
 47        self
 48    }
 49
 50    pub fn header(mut self, header: impl Into<Option<ListHeader>>) -> Self {
 51        self.header = header.into();
 52        self
 53    }
 54
 55    pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
 56        self.toggle = toggle.into();
 57        self
 58    }
 59}
 60
 61impl ParentElement for List {
 62    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
 63        self.children.extend(elements)
 64    }
 65}
 66
 67impl From<String> for EmptyMessage {
 68    fn from(s: String) -> Self {
 69        EmptyMessage::Text(SharedString::from(s))
 70    }
 71}
 72
 73impl From<&str> for EmptyMessage {
 74    fn from(s: &str) -> Self {
 75        EmptyMessage::Text(SharedString::from(s.to_owned()))
 76    }
 77}
 78
 79impl From<AnyElement> for EmptyMessage {
 80    fn from(e: AnyElement) -> Self {
 81        EmptyMessage::Element(e)
 82    }
 83}
 84
 85impl RenderOnce for List {
 86    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
 87        let styled = v_flex()
 88            .w_full()
 89            .py(DynamicSpacing::Base04.rems(cx))
 90            .children(self.header)
 91            .map(|this| match (self.children.is_empty(), self.toggle) {
 92                (false, _) => this.children(self.children),
 93                (true, Some(false)) => this,
 94                (true, _) => match self.empty_message {
 95                    EmptyMessage::Text(text) => {
 96                        this.px_2().child(Label::new(text).color(Color::Muted))
 97                    }
 98                    EmptyMessage::Element(element) => this.child(element),
 99                },
100            });
101
102        if let Some(id) = self.id {
103            styled.id(id).role(Role::List).into_any_element()
104        } else {
105            styled.into_any_element()
106        }
107    }
108}
109
110impl Component for List {
111    fn scope() -> ComponentScope {
112        ComponentScope::Layout
113    }
114
115    fn description() -> Option<&'static str> {
116        Some(
117            "A container component for displaying a collection of list items with optional header and empty state.",
118        )
119    }
120
121    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
122        Some(
123            v_flex()
124                .gap_6()
125                .children(vec![example_group_with_title(
126                    "Basic Lists",
127                    vec![
128                        single_example(
129                            "Simple List",
130                            List::new().id("simple-list")
131                                .child(ListItem::new("item1").child(Label::new("Item 1")))
132                                .child(ListItem::new("item2").child(Label::new("Item 2")))
133                                .child(ListItem::new("item3").child(Label::new("Item 3")))
134                                .into_any_element(),
135                        ),
136                        single_example(
137                            "With Header",
138                            List::new().id("header-list")
139                                .header(ListHeader::new("Section Header"))
140                                .child(ListItem::new("item1").child(Label::new("Item 1")))
141                                .child(ListItem::new("item2").child(Label::new("Item 2")))
142                                .into_any_element(),
143                        ),
144                        single_example(
145                            "Empty List",
146                            List::new().id("empty-list")
147                                .empty_message("No items to display")
148                                .into_any_element(),
149                        ),
150                    ],
151                )])
152                .into_any_element(),
153        )
154    }
155}