Skip to main content

ui/components/list/
list.rs

1use component::{Component, ComponentScope, example_group_with_title, single_example};
2use gpui::AnyElement;
3use smallvec::SmallVec;
4
5use crate::{Label, ListHeader, ListItem, ListItemSpacing, prelude::*};
6
7pub enum EmptyMessage {
8    Text(SharedString),
9    Element(AnyElement),
10}
11
12#[derive(IntoElement, RegisterComponent)]
13pub struct List {
14    /// Message to display when the list is empty
15    /// Defaults to "No items"
16    empty_message: EmptyMessage,
17    header: Option<ListHeader>,
18    toggle: Option<bool>,
19    children: SmallVec<[AnyElement; 2]>,
20}
21
22impl Default for List {
23    fn default() -> Self {
24        Self::new()
25    }
26}
27
28impl List {
29    pub fn new() -> Self {
30        Self {
31            empty_message: EmptyMessage::Text("No items".into()),
32            header: None,
33            toggle: None,
34            children: SmallVec::new(),
35        }
36    }
37
38    pub fn empty_message(mut self, message: impl Into<EmptyMessage>) -> Self {
39        self.empty_message = message.into();
40        self
41    }
42
43    pub fn header(mut self, header: impl Into<Option<ListHeader>>) -> Self {
44        self.header = header.into();
45        self
46    }
47
48    pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
49        self.toggle = toggle.into();
50        self
51    }
52}
53
54impl ParentElement for List {
55    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
56        self.children.extend(elements)
57    }
58}
59
60impl From<String> for EmptyMessage {
61    fn from(s: String) -> Self {
62        EmptyMessage::Text(SharedString::from(s))
63    }
64}
65
66impl From<&str> for EmptyMessage {
67    fn from(s: &str) -> Self {
68        EmptyMessage::Text(SharedString::from(s.to_owned()))
69    }
70}
71
72impl From<AnyElement> for EmptyMessage {
73    fn from(e: AnyElement) -> Self {
74        EmptyMessage::Element(e)
75    }
76}
77
78impl RenderOnce for List {
79    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
80        v_flex()
81            .w_full()
82            .py(DynamicSpacing::Base04.rems(cx))
83            .children(self.header)
84            .map(|this| match (self.children.is_empty(), self.toggle) {
85                (false, _) => this.children(self.children),
86                (true, Some(false)) => this,
87                (true, _) => match self.empty_message {
88                    EmptyMessage::Text(text) => {
89                        this.px_2().child(Label::new(text).color(Color::Muted))
90                    }
91                    EmptyMessage::Element(element) => this.child(element),
92                },
93            })
94    }
95}
96
97impl Component for List {
98    fn scope() -> ComponentScope {
99        ComponentScope::Layout
100    }
101
102    fn description() -> Option<&'static str> {
103        Some(
104            "A container component for displaying a collection of list items with optional header and empty state.",
105        )
106    }
107
108    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
109        Some(
110            v_flex()
111                .gap_6()
112                .children(vec![
113                    example_group_with_title(
114                        "Basic Lists",
115                        vec![
116                            single_example(
117                                "Simple List",
118                                List::new()
119                                    .child(ListItem::new("item1").child(Label::new("Item 1")))
120                                    .child(ListItem::new("item2").child(Label::new("Item 2")))
121                                    .child(ListItem::new("item3").child(Label::new("Item 3")))
122                                    .into_any_element(),
123                            ),
124                            single_example(
125                                "With Header",
126                                List::new()
127                                    .header(ListHeader::new("Section Header"))
128                                    .child(ListItem::new("item1").child(Label::new("Item 1")))
129                                    .child(ListItem::new("item2").child(Label::new("Item 2")))
130                                    .into_any_element(),
131                            ),
132                            single_example(
133                                "Empty List",
134                                List::new()
135                                    .empty_message("No items to display")
136                                    .into_any_element(),
137                            ),
138                        ],
139                    ),
140                    example_group_with_title(
141                        "With Icons + Selected State",
142                        vec![single_example(
143                            "Team Members",
144                            List::new()
145                                .header(ListHeader::new("Team Members"))
146                                .child(
147                                    ListItem::new("member-ada")
148                                        .start_slot(Icon::new(IconName::Check))
149                                        .toggle_state(true)
150                                        .child(Label::new("Ada Lovelace")),
151                                )
152                                .child(
153                                    ListItem::new("member-grace")
154                                        .start_slot(Icon::new(IconName::File))
155                                        .child(Label::new("Grace Hopper")),
156                                )
157                                .child(
158                                    ListItem::new("member-alan")
159                                        .start_slot(Icon::new(IconName::File))
160                                        .child(Label::new("Alan Turing")),
161                                )
162                                .into_any_element(),
163                        )],
164                    ),
165                    example_group_with_title(
166                        "Density",
167                        vec![
168                            single_example(
169                                "Dense",
170                                List::new()
171                                    .child(
172                                        ListItem::new("dense-1")
173                                            .spacing(ListItemSpacing::Dense)
174                                            .child(Label::new("Invoice #1042")),
175                                    )
176                                    .child(
177                                        ListItem::new("dense-2")
178                                            .spacing(ListItemSpacing::Dense)
179                                            .child(Label::new("Invoice #1043")),
180                                    )
181                                    .into_any_element(),
182                            ),
183                            single_example(
184                                "Sparse",
185                                List::new()
186                                    .child(
187                                        ListItem::new("sparse-1")
188                                            .spacing(ListItemSpacing::Sparse)
189                                            .child(Label::new("Invoice #1042")),
190                                    )
191                                    .child(
192                                        ListItem::new("sparse-2")
193                                            .spacing(ListItemSpacing::Sparse)
194                                            .child(Label::new("Invoice #1043")),
195                                    )
196                                    .into_any_element(),
197                            ),
198                        ],
199                    ),
200                ])
201                .into_any_element(),
202        )
203    }
204}