1use component::{Component, ComponentScope, example_group_with_title, single_example};
2use gpui::AnyElement;
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 /// 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![example_group_with_title(
113 "Basic Lists",
114 vec![
115 single_example(
116 "Simple List",
117 List::new()
118 .child(ListItem::new("item1").child(Label::new("Item 1")))
119 .child(ListItem::new("item2").child(Label::new("Item 2")))
120 .child(ListItem::new("item3").child(Label::new("Item 3")))
121 .into_any_element(),
122 ),
123 single_example(
124 "With Header",
125 List::new()
126 .header(ListHeader::new("Section Header"))
127 .child(ListItem::new("item1").child(Label::new("Item 1")))
128 .child(ListItem::new("item2").child(Label::new("Item 2")))
129 .into_any_element(),
130 ),
131 single_example(
132 "Empty List",
133 List::new()
134 .empty_message("No items to display")
135 .into_any_element(),
136 ),
137 ],
138 )])
139 .into_any_element(),
140 )
141 }
142}