Skip to main content

ui/components/
item.rs

1use gpui::{AnyElement, FontWeight};
2
3use crate::{Avatar, prelude::*};
4
5/// Media slot for an [`Item`] row (icon, avatar, thumbnail, etc.).
6#[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/// Primary text block for an [`Item`] row.
26#[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/// Trailing actions slot for an [`Item`] row.
61#[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/// A generic row primitive with media, content, and trailing actions slots.
81#[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}