ui/components/list/
list.rs1use 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 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}