1use gpui::AnyElement;
2
3use crate::Avatar;
4use crate::prelude::*;
5
6#[derive(IntoElement, RegisterComponent)]
9pub struct Feed {
10 entries: Vec<(AnyElement, SharedString, SharedString)>,
11}
12
13impl Feed {
14 pub fn new() -> Self {
15 Self {
16 entries: Vec::new(),
17 }
18 }
19
20 pub fn entry(
21 mut self,
22 avatar: impl IntoElement,
23 content: impl Into<SharedString>,
24 timestamp: impl Into<SharedString>,
25 ) -> Self {
26 self.entries
27 .push((avatar.into_any_element(), content.into(), timestamp.into()));
28 self
29 }
30}
31
32impl Default for Feed {
33 fn default() -> Self {
34 Self::new()
35 }
36}
37
38impl RenderOnce for Feed {
39 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
40 v_flex()
41 .w_full()
42 .border_l_1()
43 .border_color(semantic::border_muted(cx))
44 .pl_4()
45 .children(
46 self.entries
47 .into_iter()
48 .map(|(avatar, content, timestamp)| {
49 h_flex().items_start().gap_3().mb_4().child(avatar).child(
50 v_flex().gap_0p5().child(Label::new(content)).child(
51 Label::new(timestamp)
52 .size(LabelSize::XSmall)
53 .color(Color::Muted),
54 ),
55 )
56 }),
57 )
58 }
59}
60
61impl Component for Feed {
62 fn scope() -> ComponentScope {
63 ComponentScope::DataDisplay
64 }
65
66 fn description() -> Option<&'static str> {
67 Some("A vertical activity timeline with a connecting line and avatar/text entries.")
68 }
69
70 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
71 let example_avatar = "https://avatars.githubusercontent.com/u/1714999?v=4";
72
73 Some(
74 v_flex()
75 .gap_6()
76 .child(
77 Feed::new()
78 .entry(
79 Avatar::new(example_avatar).size(px(32.)),
80 "Jane Cooper created the project",
81 "1h ago",
82 )
83 .entry(
84 Avatar::new(example_avatar).size(px(32.)),
85 "Cody Fisher commented on an issue",
86 "3h ago",
87 )
88 .entry(
89 Avatar::new(example_avatar).size(px(32.)),
90 "Jenny Wilson closed the milestone",
91 "1d ago",
92 ),
93 )
94 .into_any_element(),
95 )
96 }
97}