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