1use crate::{prelude::*, Icon, IconButton, Input, Label};
2use chrono::NaiveDateTime;
3use gpui::{prelude::*, Div, Stateful};
4
5#[derive(RenderOnce)]
6pub struct ChatPanel {
7 element_id: ElementId,
8 messages: Vec<ChatMessage>,
9}
10
11impl<V: 'static> Component<V> for ChatPanel {
12 type Rendered = Stateful<V, Div<V>>;
13
14 fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
15 div()
16 .id(self.element_id.clone())
17 .flex()
18 .flex_col()
19 .justify_between()
20 .h_full()
21 .px_2()
22 .gap_2()
23 // Header
24 .child(
25 div()
26 .flex()
27 .justify_between()
28 .py_2()
29 .child(div().flex().child(Label::new("#design")))
30 .child(
31 div()
32 .flex()
33 .items_center()
34 .gap_px()
35 .child(IconButton::new("file", Icon::File))
36 .child(IconButton::new("audio_on", Icon::AudioOn)),
37 ),
38 )
39 .child(
40 div()
41 .flex()
42 .flex_col()
43 // Chat Body
44 .child(
45 div()
46 .id("chat-body")
47 .w_full()
48 .flex()
49 .flex_col()
50 .gap_3()
51 .overflow_y_scroll()
52 .children(self.messages),
53 )
54 // Composer
55 .child(div().flex().my_2().child(Input::new("Message #design"))),
56 )
57 }
58}
59
60impl ChatPanel {
61 pub fn new(element_id: impl Into<ElementId>) -> Self {
62 Self {
63 element_id: element_id.into(),
64 messages: Vec::new(),
65 }
66 }
67
68 pub fn messages(mut self, messages: Vec<ChatMessage>) -> Self {
69 self.messages = messages;
70 self
71 }
72}
73
74#[derive(RenderOnce)]
75pub struct ChatMessage {
76 author: String,
77 text: String,
78 sent_at: NaiveDateTime,
79}
80
81impl<V: 'static> Component<V> for ChatMessage {
82 type Rendered = Div<V>;
83
84 fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
85 div()
86 .flex()
87 .flex_col()
88 .child(
89 div()
90 .flex()
91 .gap_2()
92 .child(Label::new(self.author.clone()))
93 .child(
94 Label::new(self.sent_at.format("%m/%d/%Y").to_string())
95 .color(TextColor::Muted),
96 ),
97 )
98 .child(div().child(Label::new(self.text.clone())))
99 }
100}
101
102impl ChatMessage {
103 pub fn new(author: String, text: String, sent_at: NaiveDateTime) -> Self {
104 Self {
105 author,
106 text,
107 sent_at,
108 }
109 }
110}
111
112#[cfg(feature = "stories")]
113pub use stories::*;
114
115#[cfg(feature = "stories")]
116mod stories {
117 use chrono::DateTime;
118 use gpui::{Div, Render};
119
120 use crate::{Panel, Story};
121
122 use super::*;
123
124 pub struct ChatPanelStory;
125
126 impl Render<Self> for ChatPanelStory {
127 type Element = Div<Self>;
128
129 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
130 Story::container(cx)
131 .child(Story::title_for::<_, ChatPanel>(cx))
132 .child(Story::label(cx, "Default"))
133 .child(
134 Panel::new("chat-panel-1-outer", cx)
135 .child(ChatPanel::new("chat-panel-1-inner")),
136 )
137 .child(Story::label(cx, "With Mesages"))
138 .child(Panel::new("chat-panel-2-outer", cx).child(
139 ChatPanel::new("chat-panel-2-inner").messages(vec![
140 ChatMessage::new(
141 "osiewicz".to_string(),
142 "is this thing on?".to_string(),
143 DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
144 .unwrap()
145 .naive_local(),
146 ),
147 ChatMessage::new(
148 "maxdeviant".to_string(),
149 "Reading you loud and clear!".to_string(),
150 DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
151 .unwrap()
152 .naive_local(),
153 ),
154 ]),
155 ))
156 }
157 }
158}