1use gpui::{AnyElement, FontWeight};
2
3use crate::{Avatar, prelude::*};
4
5#[derive(IntoElement)]
7pub struct ItemMedia {
8 child: AnyElement,
9}
10
11impl ItemMedia {
12 pub fn new(child: impl IntoElement) -> Self {
13 Self {
14 child: child.into_any_element(),
15 }
16 }
17}
18
19impl RenderOnce for ItemMedia {
20 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
21 div().flex_none().child(self.child)
22 }
23}
24
25#[derive(IntoElement)]
27pub struct ItemContent {
28 title: SharedString,
29 description: Option<SharedString>,
30}
31
32impl ItemContent {
33 pub fn new(title: impl Into<SharedString>) -> Self {
34 Self {
35 title: title.into(),
36 description: None,
37 }
38 }
39
40 pub fn description(mut self, description: impl Into<SharedString>) -> Self {
41 self.description = Some(description.into());
42 self
43 }
44}
45
46impl RenderOnce for ItemContent {
47 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
48 v_flex()
49 .flex_1()
50 .min_w_0()
51 .gap_0p5()
52 .child(Label::new(self.title).weight(FontWeight::MEDIUM))
53 .children(
54 self.description
55 .map(|d| Label::new(d).size(LabelSize::Small).color(Color::Muted)),
56 )
57 }
58}
59
60#[derive(IntoElement)]
62pub struct ItemActions {
63 child: AnyElement,
64}
65
66impl ItemActions {
67 pub fn new(child: impl IntoElement) -> Self {
68 Self {
69 child: child.into_any_element(),
70 }
71 }
72}
73
74impl RenderOnce for ItemActions {
75 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
76 div().flex_none().child(self.child)
77 }
78}
79
80#[derive(IntoElement, RegisterComponent)]
82pub struct Item {
83 media: Option<AnyElement>,
84 content: Option<AnyElement>,
85 actions: Option<AnyElement>,
86}
87
88impl Item {
89 pub fn new() -> Self {
90 Self {
91 media: None,
92 content: None,
93 actions: None,
94 }
95 }
96
97 pub fn media(mut self, media: impl IntoElement) -> Self {
98 self.media = Some(media.into_any_element());
99 self
100 }
101
102 pub fn content(mut self, content: impl IntoElement) -> Self {
103 self.content = Some(content.into_any_element());
104 self
105 }
106
107 pub fn actions(mut self, actions: impl IntoElement) -> Self {
108 self.actions = Some(actions.into_any_element());
109 self
110 }
111}
112
113impl Default for Item {
114 fn default() -> Self {
115 Self::new()
116 }
117}
118
119impl RenderOnce for Item {
120 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
121 h_flex()
122 .id("item")
123 .w_full()
124 .items_center()
125 .gap_3()
126 .px_3()
127 .py_2()
128 .rounded_md()
129 .hover(|style| style.bg(semantic::hover_bg(cx)))
130 .children(self.media)
131 .children(self.content)
132 .children(self.actions)
133 }
134}
135
136impl Component for Item {
137 fn scope() -> ComponentScope {
138 ComponentScope::DataDisplay
139 }
140
141 fn description() -> Option<&'static str> {
142 Some("A generic row primitive with media, content, and trailing actions slots.")
143 }
144
145 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
146 let example_avatar = "https://avatars.githubusercontent.com/u/1714999?v=4";
147
148 Some(
149 v_flex()
150 .gap_2()
151 .w(px(360.))
152 .child(
153 Item::new()
154 .media(ItemMedia::new(Avatar::new(example_avatar).size(px(40.))))
155 .content(
156 ItemContent::new("Jane Cooper")
157 .description("Regional Paradigm Technician"),
158 )
159 .actions(IconButton::new("item-action", IconName::Ellipsis)),
160 )
161 .child(
162 Item::new()
163 .media(ItemMedia::new(
164 Icon::new(IconName::File)
165 .size(IconSize::Medium)
166 .color(Color::Muted),
167 ))
168 .content(
169 ItemContent::new("project-plan.pdf").description("Updated 2 hours ago"),
170 ),
171 )
172 .into_any_element(),
173 )
174 }
175}